- 1. pid
- 2. 进程的产生:fork
- 3. 进程的消亡及释放资源:wait
- 4. exec函数族
- 综合例子:mybash
- 5. 用户权限和组权限:setuid
- 6. 观摩课
- 7. system
- 8. 进程会计
- 9. 进程时间
- 10.守护进程
- 11. 系统日志
1. pid
- pid_t : 进程号,一般是int_64,不同机器不同,有限资源
ps axf
:查看进程信息ps -axm
:详细信息ps -ax -L
: linux特有形式显示- 进程号是顺次向下使用,区别于 fd(占位最小)
getpid\getppid
:获取进程号\父进程号
##include <sys/types.h>
##include <unistd.h>pid_t getpid(void);
pid_t getppid(void);
2. 进程的产生:fork
##include <sys/types.h>
##include <unistd.h>pid_t fork(void);
- 返回值:
- 出错: -1
- 成功:返回两次。 父进程:子进程pid;子进程:0
- 注意点:fork之前刷新所有该刷新的流,
fflush(NULL)
- 父进程与子进程完全一致(duplicate) ,运行位置也一致
fork
后父子进程的区别:fork返回值不同,pid不同,ppid不同,未决信号量和文件锁不继承,资源利用量清0- 调度器的策略决定哪个进程先运行
例子:fork之前刷新所有该刷新的流
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>int main()
{pid_t pid;printf("[%d]:Begin.\n",getpid());//fork前刷新所有流!!fflush(NULL);pid = fork();if(pid < 0){perror("fork()");exit(1);}if(pid == 0){printf("[%d]:Child is working!\n", getpid());}else{printf("[%d]:Parent is working!\n", getpid());}printf("[%d]:End.\n", getpid());//没有收尸,详见3.进程资源释放exit(0);
}
运行结果:
未加fflush
加了fflush
说明:
文件是全缓冲,如果没有fork之前刷新流,子进程会继承父进程的缓冲区,所以缓冲区里有 “Begin.”,导致打印了两次。
加getchar() ps axf
查看进程信息
例子:多进程筛3000,0000 ~ 3000,0200之间的质数
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>##define LEFT 30000000
##define RIGHT 30000200
int main()
{pid_t pid;int i,j;int mark;for(i = LEFT; i <= RIGHT; i++){pid = fork();if(pid < 0){perror("fork()");exit(1);}if(pid == 0){mark = 1;for(j = 2; j < i/2; j++){if(i%j == 0){mark = 0;break;}}if(mark)printf("%d is a primer!\n", i);//结束子进程,非常重要exit(0);}}exit(0);
}
运行结果:
注意点:
- 子进程内必须要
exit
:否则会子进程会进入外层循环,会继续fork,导致实际创建出的子进程不是201个,而是阶乘级别的数量级。所以写fork的时候要明确子进程的运行范围,可以的话尽量限制在if的语句块中(个人观点) - 无序输出,说明调度关系未定义,取决于调度器
说明孤儿进程和僵尸进程:
父进程sleep:子进程全部结束,父进程还没有结束,所以子进程不是孤儿,并且由于资源还没有回收,所以是僵尸态
子进程sleep:父进程已经结束退出,子进程还没结束,所以子进程变成孤儿被 init 进程托管,同时资源还没被回收,所以也是僵尸进程(之后init会陆续回收资源)
进程关系中出现僵尸进程是非常正常的,但应该是一闪即逝,或者轮批次(父进程或init忙)
僵尸本身几乎就是一个结构体,所以占用的内存不是很大,但是它占用了pid
fork写时拷贝:需要改写的时候,才复制一份进行改动
3. 进程的消亡及释放资源:wait
Untitled
##include <sys/types.h>
##include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
-
作用:回收某一个子进程
-
参数:
pid_t
: 要回收的子进程的pidwstatus
: 回调参数,进程结束状态options
:选项。WNOHANG
非阻塞
-
返回值:
- 成功:回收的子进程的pid
- 失败:回收完或被中断打断,-1
-
pid
的取值:
例子:利用交叉分配重构 求素数的程序
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>
##include<sys/wait.h>##define LEFT 30000000
##define RIGHT 30000200##define N 3int main()
{pid_t pid;int i,j,n;int mark;for(n = 0; n < N; n++){pid = fork();if(pid < 0){perror("fork()");exit(1);}if(pid > 0){for(i = LEFT+n; i <= RIGHT; i+=N){mark = 1;for(j = 2; j < i/2; j++){if(i%j == 0){mark = 0;break;}}if(mark)printf("[%d]:%d is a primer!\n", n,i);//exit(0);}//子进程是处理完自己的部分后才结束exit(0);}}for(n = 0; n < N; n++)wait(NULL);exit(0);
}
运行结果:
说明:
为什么编号为0的进程没有打印,因为处理的数都是3的倍数,0,3,6,9…都是非素数。进程间的负载不均衡,可以用池化算法改进。
4. exec函数族
shell执行程序命令的方式:
- shell
fork
出一个子进程 - 子进程
exec
成待执行的程序,并执行 - shell
wait
自己的子进程结束
##include <unistd.h>
extern char **environ;int execl(const char *pathname, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
-
作用:用一个进程image替代当前进程image,换内不换壳,pid不变
-
参数:
- 前三个是定参,后面传的参数是要执行的程序的参数,以
NULL
结尾 - 后两个是变参,用
argv
实现,类似命令行参数
- 前三个是定参,后面传的参数是要执行的程序的参数,以
-
返回值:失败返回-1,可以不检验,直接报错(因为如果成功,进程空间就直接切出去了,不会回来)
-
注意:
fflush
-
类似
argv
[变参,arg1,arg2, … 以NULL结尾] 的结构命令行参数;
glob
;execv
例子:few 结合使用
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<unistd.h>
##include<sys/types.h>
##include<sys/wait.h>int main()
{pid_t pid;puts("Begin.");fflush(NULL); /*!!!*/pid = fork();if(pid < 0){perror("fork()");exit(1);}if(pid > 0) //子进程:打印时戳{execl("/bin/date", "date", "+%s", NULL);//错误处理perror("exec()");exit(1);}else //父进程:回收子进程{wait(NULL);}exit(0);
}
运行结果:
- 其他问题:
-
没有wait的时候为什么命令行先输出:父进程结束后子进程没结束,父进程结束回到bash,打印命令行,然后子进程打印结果
-
为什么父子进程的结果打印在同一个终端上,父子进程通信原型:子进程继承父进程的文件描述符,二者 fd0标准输出都一样,都是同一个终端
-
综合例子:mybash
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<unistd.h>
##include<string.h>
##include<glob.h>
##include<sys/wait.h>##define LIMITS " \n\t"struct cmd_st
{glob_t globres;
};static void prompt()
{printf("mysh-0.1$ ");
}static void perse(char *line, struct cmd_st *res)
{char *tok;int i = 0;while(1){tok = strsep(&line, LIMITS);if(tok == NULL) //结束break;if(tok[0] == '\0') //多个连续分隔符continue;//精髓:glob(tok, GLOB_NOCHECK|GLOB_APPEND*i, NULL, &res->globres);i = 1;}}int main()
{char *linebuf = NULL;size_t linebuf_size = 0;struct cmd_st cmd;size_t pid;while(1){prompt(); //打印提示信息if(getline(&linebuf, &linebuf_size, stdin)<0)break;perse(linebuf, &cmd);if(0) //内部命令{ }else //外部命令{pid = fork();if(pid < 0){perror("fork()");exit(1);}if(pid == 0) //child{execvp(cmd.globres.gl_pathv[0], cmd.globres.gl_pathv);perror("execl()");exit(1);}else //parent{wait(NULL);}}}exit(0);
}
运行结果:
说明:
-
利用 glob_t 中的 gl_pathv 传递变参
-
strtok 的用法
理解strtok函数返回值_今天也要学习哒的博客-CSDN博客_strtok返回值
使用:
5. 用户权限和组权限:setuid
##include <sys/types.h>
##include <unistd.h>
int setuid(uid_t uid); //设置用户 effective id
例子:实现 mysu
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>
##include<sys/wait.h>int main(int argc, char **argv)
{pid_t pid;pid = fork();if(pid < 0){perror("fork()");exit(1);}if(pid == 0){setuid(atoi(argv[1]));execvp(argv[2], argv+2);perror("exec()");exit(1);}wait(NULL);exit(0);
}
运行结果:
- 切换到root用户
- 把
mysu
的所有者更改为root,并添加 u+s 权限
基础知识:
- 权限确定:
res
:real,effective,save。save几乎不用,effective用来确定身份。 - 什么是
u+s
/g+s
权限:执行该文件时,暂时获得其所有者的权限。比如passwd
命令有u+s
所以在执行的时候暂时获得其所有者root的权限,这就是为什么普通用户无法查看 /etc/shadow,却可以通过passwd
命令更改该文件。u+s
和g+s
本质是root权限的下放。
- shell如何获取身份:init[root身份] —fork\exec—>getey(name)—fork\exec—>login(pw)—成功—>shell[用户身份 res]
6. 观摩课
unix讲机制,不讲策略
对于 #!/bin/bash
shell看到 #!
知道它是一个脚本,所以不装载该程序,而是装载 /bin/bash
执行下面的语句。
如果改为 #!/bin/bash
就会出现下述情况:
把top命令做成了登录shell:
7. system
理解: fork() + exec() + wait()
的封装。
##include <stdlib.h>
int system(const char *command);
system(”date +%s”)
相当于
8. 进程会计
acct()函数
:仅作为了解
9. 进程时间
可以转换成秒了 time times
10.守护进程
进程组模型:
-
session是一些进程组的集合:每一个登录的shell是一个session,有自己的sid。
-
分组都是为了方便管理。用户有用户组,进程也有进程组,有pgid。父子进程在同一个进程组,执行
ls|more
的时候,进程ls
和 进程more
也是在同一个进程组。 -
进程是一个容器,包含许多线程
-
前后台进程:前台进程关联标准io,最多有一个。后台进程不关联标准io
守护进程:
- 是进程组的leader,是session的leader
- 脱离标准io
- 体现在终端上
ps axj
: pid = pgid = sid; tty = ? ;ppid = 1
##include <sys/types.h>
##include <unistd.h>
pid_t setsid(void);
- 使用:不能由父进程执行,只能由子进程
- 流程:
-
创建一个session及进程组,调用该函数的进程编程该session和进程组的leader
-
脱离控制终端
-
例子:创建守护进程,关注 fork
的一模一样
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>
##include<sys/stat.h>
##include<fcntl.h>##define FILENAME "/tmp/out"//创建守护进程:成功返回非0值
static int daemonize()
{pid_t pid;int fd;pid = fork();if(pid < 0){perror("fork()");return -1;}if(pid > 0) //父进程直接退出exit(0);//子进程fd = open("/dev/null", O_RDWR);if(fd < 0){perror("open()");return -1;}//文件重定向dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);if(fd > 2)close(fd);//创建sessionsetsid();//更改工作目录;(修改umask)chdir("/");//umask(0);return 0;}int main()
{FILE *fp;int i;if(daemonize())exit(1);fp = fopen(FILENAME, "w");if(fp == NULL){perror("fopen()");exit(1);}for(i = 0; ; i++){fprintf(fp, "%d\n", i);fflush(NULL); //文件是全缓冲sleep(1);}fclose(fp);exit(0);
}
运行结果:
分析: daemon
中创建了子进程,父进程终止,子进程执行 setid
,化身为守护进程,然后从父进程相同的位置继续执行,退出到main,执行死循环。
问题:错误都往标准输出上报,但是守护进程已经脱离标准输出了,用系统日志
11. 系统日志
权限分离
syslogd服务
##include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>
##include<sys/stat.h>
##include<fcntl.h>
##include<syslog.h>
##include<errno.h>
##include<string.h>##define FILENAME "/tmp/out"//创建守护进程:成功返回非0值
static int daemonize()
{pid_t pid;int fd;pid = fork();if(pid < 0){return -1;}if(pid > 0) //父进程直接退出exit(0);//子进程fd = open("/dev/null", O_RDWR);if(fd < 0){return -1;}//文件重定向dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);if(fd > 2)close(fd);//创建sessionsetsid();//更改工作目录;(修改umask)chdir("/");//umask(0);return 0;}int main()
{FILE *fp;int i;openlog("mydaemon", LOG_PID, LOG_DAEMON);if(daemonize()){syslog(LOG_ERR, "daemonize failed!");exit(1);}else{syslog(LOG_INFO, "daemonize success!");}fp = fopen(FILENAME, "w");if(fp == NULL){syslog(LOG_ERR, "fopen():%s", strerror(errno));exit(1);}syslog(LOG_INFO, "%s was open", FILENAME);for(i = 0; ; i++){fprintf(fp, "%d\n", i);fflush(NULL); //文件是全缓冲sleep(1);}fclose(fp);exit(0);
}
运行结果:
su root
tail -f /var/log/syslog