1. 学习目标
- 了解进程相关的概念
- 掌握fork/getpid/getppid函数的使用
- 熟练掌握ps/kill命令的使用
- 熟练掌握execl/execlp函数的使用
- 说出什么是孤儿进程什么是僵尸进程
- 熟练掌握wait函数的使用
- 熟练掌握waitpid函数的使用
2 进程相关概念
2.1 程序和进程
- 程序,是指编译好的二进制文件,在磁盘上,占用磁盘空间, 是一个静态的概念.
- 进程,一个启动的程序, 进程占用的是系统资源,如:物理内存,CPU,终端等,是一个动态的概念
- 程序 → 剧本(纸)
- 进程 → 戏(舞台、演员、灯光、道具...)
同一个剧本可以在多个舞台同时上演。同样,同一个程序也可以加载为不同的进程(彼此之间互不影响)
2.2 并行和并发
- 并发,在一个时间段内, 是在同一个cpu上, 同时运行多个程序。
如:若将CPU的1S的时间分成1000个时间片,每个进程执行完一个时间片必须无条件让出CPU的使用权,这样1S中就可以执行1000个进程。
- 并行性指两个或两个以上的程序在同一时刻发生(需要有多颗)。
2.3 PCB-进程控制块
每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。
/usr/src/linux-headers-4.4.0-96/include/linux/sched.h文件的1390行处可以查看struct task_struct 结构体定义。其内部成员有很多,我们重点掌握以下部分即可:
- 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
- 进程的状态,有就绪、运行、挂起、停止等状态。
- 进程切换时需要保存和恢复的一些CPU寄存器。
- 描述虚拟地址空间的信息。
- 描述控制终端的信息。
- 当前工作目录(Current Working Directory)。
- getcwd --pwd
- umask掩码。
- 文件描述符表,包含很多指向file结构体的指针。
- 和信号相关的信息。
- 用户id和组id。
- 会话(Session)和进程组。
- 进程可以使用的资源上限(Resource Limit)。
- ulimit -a
2.4 进程状态(面试考)
- 进程基本的状态有5种。分别为初始态,就绪态,运行态,挂起态与终止态。其中初始态为进程准备阶段,常与就绪态结合来看。
3 创建进程
3.1 fork函数
- 函数作用:创建子进程
- 原型: pid_t fork(void);
函数参数:无
返回值:调用成功:父进程返回子进程的PID,子进程返回0;(父子进程各返回一个值)
调用失败:返回-1,设置errno值。
- fork函数代码片段实例
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main(int argc,char* argv[]) { printf("before fork, pid:[%d]\n",getpid()); // pid_t fork(void); pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid()); // pid_t getpid(void); // pid_t getppid(void); sleep(1); //延迟1s,让子进程先退出 } else { printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); } printf("after fork, pid:[%d]\n",getpid()); return 0; } [holo@holocom 0406]$ ./fork before fork, pid:[69024] father:pid == [69024],fpid == [49354] //49354是当前shell son:pid == [69025], fpid == [69024] after fork, pid:[69025] after fork, pid:[69024] [holo@holocom ~]$ ps -ef | grep 49354 holo 49354 49349 0 16:57 pts/0 00:00:00 -bash holo 75618 49355 0 17:45 pts/1 00:00:00 grep --color=auto 49354 |
上述代码注释掉sleep(1)后,父进程(有概率)先死,子进程就会变为孤儿进程 [holo@holocom 0406]$ make fork cc fork.c -o fork [holo@holocom 0406]$ ./fork before fork, pid:[83311] father:pid == [83311],fpid == [49354] after fork, pid:[83311] [holo@holocom 0406]$ son:pid == [83312], fpid == [1] //1号进程:init进程(很多进程的父进程) after fork, pid:[83312] [holo@holocom ~]$ ps -ef | grep init root 6397 1 0 11:26 ? 00:00:00 /usr/sbin/alsactl -s -n 19 -c -E ALSA_CONFIG_PATH=/etc/alsa/alsactl.conf --initfile=/lib/alsa/init/00main rdaemon holo 96489 49355 0 17:51 pts/1 00:00:00 grep --color=auto init |
- 调用fork函数的内核实现原理:
父进程调用Fork函数创建子进程,子进程的用户区和父进程的用户区完全一样,但是内核区不完全一样.如他俩的PID不一样
pid_t fork(void);
父子进程的执行逻辑:
父进程执行pid>0的值,子进程执行pid=0的值
- fork函数总结
pid_t fork(void);
►fork函数的返回值?
父进程返回子进程的PID,是一个大于0数;
子进程返回0;
特别需要注意的是:不是fork函数在一个进程中返回2个值,而是在父子进程各自返回一个值。
►子进程创建成功后,代码的执行位置?
父进程执行到什么位置,子进程就从哪里执行
►如何区分父子进程
通过fork函数的返回值
►父子进程的执行顺序
不一定,哪个进程先抢到CPU,哪个进程就先执行
3.2 ps命令和kill命令
- ps aux | grep "xxx"
- ps ajx | grep "xxx"
- -a:(all)当前系统所有用户的进程
- -u:查看进程所有者及其他一些信息
- -x:显示没有控制终端的进程 -- 不能与用户进行交互的进程【输入、输出】
- -j: 列出与作业控制相关的信息
- kill -l 查看系统有哪些信号
- kill -9 pid 杀死某个线程
找pid 用 ps -ef就够了
3.3 getpid/getppid
- getpid - 得到当前进程的PID
pid_t getpid(void);
- getppid - 得到当前进程的父进程的PID
pid_t getppid(void);
3.3 练习题
- 编写程序,循环创建多个子进程,要求如下:
- 多个子进程是兄弟关系。
- 判断子进程是第几个子进程
画图讲解创建多个子进程遇到的问题
注意:若让多个子进程都是兄弟进程,必须不能让子进程再去创建新的子进程。
循环创建n个子进程
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main(int argc,char* argv[]) { int i = 0; for(i = 0 ;i < 3; i++) { // pid_t fork(void); pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid()); // pid_t getpid(void); // pid_t getppid(void); // sleep(1); } else { printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); } } sleep(10); return 0; } [holo@holocom 0406]$ ./fork1 father:pid == [25337],fpid == [8655] father:pid == [25337],fpid == [8655] father:pid == [25337],fpid == [8655] son:pid == [25339], fpid == [25337] father:pid == [25339],fpid == [25337] son:pid == [25340], fpid == [25337] son:pid == [25338], fpid == [25337] father:pid == [25338],fpid == [25337] father:pid == [25338],fpid == [25337] son:pid == [25341], fpid == [25339] son:pid == [25342], fpid == [25338] father:pid == [25342],fpid == [25338] son:pid == [25343], fpid == [25338] son:pid == [25344], fpid == [25342] 分析:创建了几个son 就是有几个子进程 |
在子进程代码处, 添加break , 就会break,不会继续执行循环 ,子进程就不会创建子进程了. 这样就能保证父进程创建来的子进程全是兄弟关系 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main(int argc,char* argv[]) { int i = 0; for(i = 0 ;i < 3; i++) { // pid_t fork(void); pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid()); // pid_t getpid(void); // pid_t getppid(void); // sleep(1); } else { printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); break; } } sleep(10); return 0; } [holo@holocom 0406]$ ./fork1 father:pid == [26549],fpid == [8655] father:pid == [26549],fpid == [8655] father:pid == [26549],fpid == [8655] son:pid == [26550], fpid == [26549] son:pid == [26551], fpid == [26549] son:pid == [26552], fpid == [26549] |
打印出 是第几个子进程的pid #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main(int argc,char* argv[]) { int i = 0; for(i = 0 ;i < 3; i++) { // pid_t fork(void); pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid()); // pid_t getpid(void); // pid_t getppid(void); // sleep(1); } else { printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); break; } } //第一个子进程 if(i==0) { printf("[%d]---[%d]:child\n",i,getpid()); //第i个进程的pid } //第二个子进程 if(i==1) { printf("[%d]---[%d]:child\n",i,getpid()); //第i个进程的pid } //第三子进程 if(i==2) { printf("[%d]---[%d]:child\n",i,getpid()); //第i个进程的pid } //父进程 if(i==3) { printf("[%d]---[%d]:child\n",i,getpid()); //第i个进程的pid } sleep(10); return 0; } [holo@holocom 0406]$ ./fork1 father:pid == [39900],fpid == [8655] father:pid == [39900],fpid == [8655] father:pid == [39900],fpid == [8655] [3]---[39900]:child son:pid == [39902], fpid == [39900] [1]---[39902]:child son:pid == [39903], fpid == [39900] [2]---[39903]:child son:pid == [39901], fpid == [39900] [0]---[39901]:child |
- 编写程序,测试父子进程是否能够共享全局变量
重点通过这个案例讲解 读时共享,写时复制
可以节省资源, 在某个进程即将修改时, 在物理内存中拷贝出一份资源,修改后再映射回去.
父子进程不可以使用全局变量进行通信.
由系统的内存管理单元MMU 执行: 从虚拟内存映射到物理内存, 再映射回去
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int g_var = 99; int main(int argc,char* argv[]) { // pid_t fork(void); pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { g_var++; printf("add:[%p]",&g_var); //访问的是虚拟地址,因为访问不了物理地址 printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid()); // pid_t getpid(void); // pid_t getppid(void); // sleep(1); } else { printf("add:[%p]",&g_var); sleep(1); printf("child : g_var == [%d]\n",g_var); printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); } return 0; } [holo@holocom 0406]$ make fork2 cc fork2.c -o fork2 [holo@holocom 0406]$ ./fork2 add:[0x601054]father:pid == [8914],fpid == [8799] [holo@holocom 0406]$ add:[0x601054]child : g_var == [99] son:pid == [8915], fpid == [1] |
4 exec函数族
4.1 函数作用和函数介绍
有的时候需要在一个进程内部执行系统命令或用户自定义的应用程序,用exec函数族可以实现 , 使用方法一般都是在父进程里面调用fork创建处子进程,然后在子进程里面调用exec函数。
pid = fork();
if(pid==0)
{
execl(...);
}
Arg参数最好写:程序的名字,以便自己知道在执行这个程序.除非让别人故意看不到采写别的名字
- execl函数
" 换核不换壳 " : 堆, 栈 , .txt代码段都替换了
函数原型: int execl(const char *path, const char *arg, ... /* (char *) NULL */);
参数介绍:
- path: 要执行的程序的绝对路径/相对路径
- 变参arg: 要执行的程序的需要的参数
- arg:占位,通常写应用程序的名字
- arg后面的: 命令的参数
- 参数写完之后, 以NULL结尾(告诉函数 完了)
返回值:若是成功,则不返回,不会再执行exec函数后面的代码;若是失败,会执行execl后面的代码,可以用perror打印错误原因。
execl函数一般执行自己写的程序。
- execlp函数
函数原型: int execlp(const char *file, const char *arg, .../* (char *) NULL */);
参数介绍:
- file: 执行命令的名字, 不带路径, 根据PATH环境变量来搜索该命令(eg:ls -l)
- arg:占位
- arg后面的: 命令的参数
- 参数写完之后: NULL
返回值:若是成功,则不返回,不会再执行exec函数后面的代码;若是失败,会执行execlp后面的代码,可以用perror打印错误原因。
execlp函数一般是执行系统自带的程序或者是命令.
execl和execlp函数的区别: execl用于打开我们自己的应用程序 , execlp用于打开系统自带的应用程序或自己的应用程序 , execl必须加路径, execlp执行系统命令时不需要加路径
4.2 exec函数族原理介绍
exec族函数的实现原理图:
如:execlp(“ls”, “ls”, “-l”, NULL);
总结:
exec函数是用一个新程序替换了当前进程的代码段、数据段、堆和栈;原有的进程空间没有发生变化,并没有创建新的进程,进程PID没有发生变化。
4.3 exec函数练习
- 使用execl函数执行一个用户自定义的应用程序
- 使用execlp函数执行一个linux系统命令
注意:当execl和execlp函数执行成功后,不返回,并且不会执行execl后面的代码逻辑,原因是调用execl函数成功以后,exec函数指定的代码段已经将原有的代码段替换了。
test.c #include <stdio.h> int main(int argc,char * argv[]) { int i=0; for(i=0; i<argc; i++) { printf("[%d]:[%s]\n",i,argv[i]); } return 0; } execl.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main(int argc,char* argv[]) { // pid_t fork(void); pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid()); } else { printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); //execl("/usr/bin/ls" , "ls" , "-l" , NULL); //路径(which ls可查) , 命令名字(占位符) , 命令参数 //execl("./test","test","hello","world ","ni","hao","ya ", NULL); //execlp("ls","ls","-l",NULL); execlp("./test","test","hello","world","ni","hao","ya",NULL); //可加路径也可不加 //第二个参数占位符务必写上, 可以看出执行的是哪个exe, 如果不想让别人看见 可不写 printf("execl error"); } return 0; } [holo@holocom 0406]$ ./execl father:pid == [48819],fpid == [10309] [holo@holocom 0406]$ son:pid == [48820], fpid == [1] [0]:[hello] [1]:[world] [2]:[ni] [3]:[hao] [4]:[ya] |
5 进程回收
5.1 为什么要进行进程资源的回收
当一个进程退出之后,进程能够回收自己的用户区的资源,但是不能回收内核空间的PCB资源,必须由它的父进程调用wait或者waitpid函数完成对子进程的回收,避免造成系统资源的浪费。
5.2 孤儿进程
- 孤儿进程的概念:
若子进程的父进程已经死掉,而子进程还存活着,这个进程就成了孤儿进程。
- 为了保证每个进程都有一个父进程,孤儿进程会被init进程领养,init进程成为了孤儿进程的养父进程,当孤儿进程退出之后,由init进程完成对孤儿进程的回收。
- 模拟孤儿进程的案例
编写模拟孤儿进程的代码讲解孤儿进程,验证孤儿进程的父进程是否由原来的父进程变成了init进程。
如果进程只创建不回收,linux系统资源就会消耗殆尽,死机了.
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <string.h> #include <unistd.h> int main(int argc,char * argv[]) { pid_t pid = fork(); while(1){ pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("father:pid = [%d],fpid = [%d]\n",getpid(),getppid()); } else if(pid == 0) { printf("son:pid = [%d],fpid = [%d]\n",getpid(),getppid()); } } return 0; } 开始执行! resource temporarily unavailable 资源暂时不可用 |
进程活着才可以被回收
//孤儿进程 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main(int argc,char* argv[]) { pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { sleep(2); printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid()); } else { printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); sleep(20); //保证让父进程先死,子进程后死. printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); } return 0; } [holo@holocom 0406]$ make orphan cc orphan.c -o orphan [holo@holocom 0406]$ ./orphan son:pid == [58529], fpid == [58525] father:pid == [58525],fpid == [8050] [holo@holocom 0406]$ son:pid == [58529], fpid == [1] |
5.3 僵尸进程
- 僵尸进程的概念:
若子进程死了,父进程还活着, 但是父进程没有调用wait或waitpid函数完成对子进程的回收(回收的是子进程的内核资源),则该子进程就成了僵尸进程。
- 如何解决僵尸进程
- 由于僵尸进程是一个已经死亡的进程,所以不能使用kill命令将其杀死
- 通过杀死其父进程的方法可以消除僵尸进程。
杀死其父进程后,这个僵尸进程会被init进程领养,由init进程完成对僵尸进程的回收。
- 模拟僵尸进程的案例
编写模拟僵尸进程的代码讲解僵尸进程, 验证若子进程先于父进程退出, 而父进程没有调用wait或者waitpid函数进行回收, 从而使子进程成为了僵尸进程.
//僵尸进程 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main(int argc,char* argv[]) { pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { sleep(100); printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid()); } else { printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); } return 0; } |
Defunct表明此进程是僵尸进程 , 不可以被kill杀死 holo 113269 8050 0 15:55 pts/0 00:00:00 ./zombie holo 113270 113269 0 15:55 pts/0 00:00:00 [zombie] <defunct> [省略其他进程描述] [holo@holocom ~]$ kill -9 113270 [holo@holocom ~]$ ps –ef holo 113269 8050 0 15:55 pts/0 00:00:00 ./zombie holo 113270 113269 0 15:55 pts/0 00:00:00 [zombie] <defunct> [holo@holocom ~]$ kill -9 113269 [holo@holocom ~]$ ps –ef root 81821 2 0 15:46 ? 00:00:00 [kworker/0:1] root 94623 2 0 15:50 ? 00:00:00 [kworker/u256:1] root 98837 2 0 15:51 ? 00:00:00 [kworker/0:0] root 109028 2 0 15:54 ? 00:00:00 [kworker/0:3] root 112734 6542 0 15:55 ? 00:00:00 sleep 60 holo 114947 8043 0 15:56 ? 00:00:00 bash -c export LANG="en_US";export LANGUAGE="en_US";export LC_ALL="en_US";fre holo 114954 8044 0 15:56 ? 00:00:00 bash -c export LANG="en_US";export LANGUAGE="en_US";export LC_ALL="en_US";fre holo 114969 114947 0 15:56 ? 00:00:00 sleep 1 holo 114976 114954 0 15:56 ? 00:00:00 sleep 1 holo 114977 8049 0 15:56 pts/1 00:00:00 ps –ef (此时僵尸进程的父进程被杀死了,所以僵尸进程也不复存在了) [holo@holocom 0406]$ ./zombie son:pid == [113270], fpid == [113269] 已杀死 |
5.4 进程回收函数
-
wait函数
- 在父进程中调用,这是一个阻塞函数,子进程退出后,立即返回
- 函数原型:
pid_t wait(int *status); //status可以传入NULL
- 函数作用:
- 阻塞并等待子进程退出
- 回收子进程残留资源
- 获取子进程结束状态(退出原因)。
- 返回值:
- 成功:清理掉的子进程ID;
- 失败:-1 (没有子进程)
- status参数:子进程的退出状态(是否正常退出) -- 传出参数
如果不关心状态,status可以设为NULL
如果关心状态,就定义一个status, 然后等待传出值(这个值不是子进程退出return的值, 使用下面的函数获取子进程退出的值)
status
变量是通过wait()
函数返回的子进程退出状态。它是一个整数类型的变量,包含了有关子进程退出的一些信息。
其中,低字节(最低8位)存储了子进程的退出状态码,即子进程调用exit()
函数时传递的返回值。这个状态码可以通过WEXITSTATUS(status)
宏来提取。
int exit_status = WEXITSTATUS(status);
printf("子进程的退出状态码: %d\n", exit_status);
- WIFEXITED(status):为非0 true → 子进程正常结束
WEXITSTATUS(status):获取子进程退出return的值
- WIFSIGNALED(status):为非0 true→ 子进程异常终止
WTERMSIG(status):取得子进程终止的信号编号。
- wait函数练习
在父进程中使用wait函数完成父进程对子进程的回收
//无退出状态 参数为NULL //父进程调用wait函数,回收子进程 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main(int argc,char* argv[]) { pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid()); pid_t spid = wait(NULL); printf("pid : [%d]\n ",spid); } else { printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); sleep(5); } return 0; } [holo@holocom 0406]$ ./wait father:pid == [68367],fpid == [8050] son:pid == [68368], fpid == [68367] pid : [68368] |
//参数status有退出状态 //父进程调用wait函数,回收子进程 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main(int argc,char* argv[]) { pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid()); int status; pid_t spid = wait(&status); //spid存储 退出的子进程id printf("pid : [%d]\n ",spid); if(WIFEXITED(status)) //正常退出了 { printf("son normal exit, status is [%d]\n", WEXITSTATUS(status)); } else if(WIFSIGNALED(status)) //被信号杀死了 { printf("son is killed by signal , signo is [%d]\n", WTERMSIG(status)); } } else { printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid()); sleep(20); return 3; //子进程休眠20秒退出,返回3 } return 0; } [holo@holocom 0406]$ ./wait father:pid == [18092],fpid == [8050] son:pid == [18093], fpid == [18092] pid : [18093] son normal exit, status is [3] 问题:执行./wait后, ps –ef 查不到正在执行的这个进程,所以无法杀掉这个进程,son is killed by signal 的情况无法测试,待解决. 解答:已经解决,原来这个进程pid,往上滑可以看到 补充 : ps -ef | grep 程序名也可以查到 [holo@holocom 0406]$ ./wait father:pid == [34399],fpid == [8050] son:pid == [34400], fpid == [34399] pid : [34400] son is killed by signal , signo is [9] [holo@holocom ~]$ ps -ef holo 34399 8050 0 16:48 pts/0 00:00:00 ./wait holo 34400 34399 0 16:48 pts/0 00:00:00 ./wait [holo@holocom ~]$ kill -9 34400 |
-
waitpid函数
- 函数原型:
pid_t waitpid(pid_t pid, int *status, in options);
- 函数作用
同wait函数
- 函数参数
参数:
pid:
pid = -1 等待所有的子进程退出。与wait等效。
pid > 0 等待回收指定子进程。
pid = 0 等待回收同一个进程组里的子进程
pid < -1 等待回收其他组的子进程
status: 子进程的退出状态,用法同wait函数。
options:设置为WNOHANG,函数非阻塞; 即 Wait no hang : 等待不挂起
设置为0,函数阻塞。
- 函数返回值
>0:返回回收掉的子进程ID;
-1:无子进程
=0:参3为WNOHANG,且子进程正在运行。
- waitpid函数练习
使用waitpid函数完成对子进程的回收
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <string.h> #include <unistd.h> int main(int argc,char * argv[]) { pid_t fork_pid = fork(); if(fork_pid < 0) { perror("fork error"); return -1; } else if(fork_pid > 0) //fork() : 父进程返回子进程的PID,子进程返回0 { printf("father:pid == [%d],fpid == [%d]\n",getpid(),getppid()); //getpid得到当前进程的pid,getppid得到当前进程父进程的pid int status; //子进程还活着, 再回收一次,循环回收 直到子进程死去 停止回收 while(1) //由于非阻塞,需要保证父进程晚于子进程退出,不存在僵尸进程问题 { pid_t child_pid = waitpid(-1,&status,WNOHANG); //waitpid() 返回清理掉的子进程pid -1:等待所有子进程 0:函数阻塞 //WNOHANG:函数不阻塞 // printf("child_pid:[%d]\n",child_pid); if(child_pid > 0){ //>0说明回收了子进程 if(WIFEXITED(status)) { printf("进程正常退出,值为:[%d]\n",WEXITSTATUS(status)); } else if(WIFSIGNALED(status)) { printf("进程异常终止,被[%d]杀死\n",WTERMSIG(status)); } //break;能写到这里嘛 } else if(child_pid == 0) //说明子进程还活着 { // printf("子进程还活着,child_pid == [%d]\n",child_pid); } else if(child_pid == -1) //子进程全部退出了 waitpid返回值为-1说明没有子进程 { printf("子进程全部退出,child_pid == [%d]\n",child_pid); break; //没有子进程的时候结束while循环 父进程跳出 } } //僵尸进程:子进程死了,父进程还活着,但是父进程没有调用wait或waitpid函数完成对子进程内核资源的回收,所以子进程就变成了僵尸进程 // sleep(20); //让子进程变成僵尸进程,僵尸进程产生的原因是只做了一次waitpid } else if(fork_pid == 0) //fork():子进程返回0 { printf("child:pid == [%d] , fpid == [%d]\n",getpid(),getppid()); //子进程PID 父进程PID sleep(2); return 3; //子进程如果正常退出,返回3 } return 0; } |
[holo@holocom 0406]$ ./waitpid1 father:pid == [27661],fpid == [7666] child:pid == [27662] , fpid == [27661] 进程正常退出,值为:[3] 子进程全部退出,child_pid == [-1] |
6 作业
6.1 作业1
测试父子进程之间是否共享文件
#include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(char argc , char * argv[]) { int fd = open(argv[1] , O_RDWR | O_TRUNC); if(fd < 0) { perror("open error"); } pid_t pid = fork(); //open之后才fork, 这样子进程有了和父进程相同的文件描述符表 if(pid<0) { perror("fork error"); } else if(pid>0) //父进程 { printf("father:[%d]",getpid()); write(fd , "nihaoa" , sizeof("nihaoa")); wait(0); //肯定能回收成功吗 } else //子进程 { char buf[64]; memset(buf,0x00,sizeof(buf)); sleep(3); lseek(fd , 0, SEEK_SET); read(fd, buf , sizeof(buf)); printf("son:[%d] , buf:[%s]\n",getpid() , buf); }
close(fd); return 0; } |
holo@holo:~/test$ ./gongxiang 1.txt son:[48608] , buf:[nihaoa] |
6.2 作业2
父进程fork三个子进程:
其中一个调用ps命令;
一个调用自定义应用程序;
一个调用会出现段错误的程序。
父进程回收三个子进程(waitpid),并且打印三个子进程的退出状态。
=== 段错误 ===
1>. 访问了非法内存
2>. 访问了不可写的区域进行写操作
3>. 栈空间溢出
char* p = “hello,world”
p【0】=‘a’;
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <string.h> #include <unistd.h> #include <string.h> #include <errno.h> int main(int argc, char * argv[]) { int i; for(i=0; i<3; i++) { pid_t pid = fork(); if(pid<0) { perror("fork error"); return -1; } else if(pid >0) //父进程逻辑 { printf("father:pid = [%d]\n",getpid()); sleep(1); } else //子进程的逻辑 { if(i==0) //调用ps命令 { execlp("ls" , "ls" , "-l", NULL); fprintf(stderr , "[%d],[%s]",i,strerror(errno)); return -1; } else if(i==1) //调用自定义程序 { execlp("./testt" , "testt" , "ni" , "hao", NULL); fprintf(stderr , "[%d],[%s]",i,strerror(errno)); return -1; }else if(i==2) //调用会出现段错误的程序 { execlp("./testt" , "testt" , "core" , "core", NULL); fprintf(stderr , "[%d],[%s]",i,strerror(errno)); return -1; } break; } } while(1) //只有父进程会走这里的代码 , 因为子进程execlp会替换掉后续代码 { int status; int wait_pid = waitpid(-1 , &status, 0); sleep(2); if(wait_pid == -1) //==-1说明没子进程了 { printf("子进程已经全部退出了"); break; } else if(wait_pid>0) //指定子进程退出了 返回pid { if(WIFEXITED(status)) //正常退出了 { printf("son normal exit, status is [%d]\n", WEXITSTATUS(status)); } else if(WIFSIGNALED(status)) //被信号杀死 { printf("son is killed by signal , signo is [%d]\n", WTERMSIG(status)); } } else if(wait_pid==0) { continue; } } return 66; } |
holo@holo:~/test$ ./gongxiang 1.txt son:[48608] , buf:[nihaoa] father:[48607]holo@holo:~/test$ ./waitpid father:pid = [49054] 总用量 252 -r---w---T 1 holo holo 110 10月 4 04:07 11111.tvsc -rwxrwxr-x 1 holo holo 110 10月 4 04:07 112.asda -rwxrwxr-x 1 holo holo 0 10月 4 08:26 11.log -rw-rw-r-- 1 holo holo 7 10月 7 22:06 1.txt -rwxrwxr-x 1 holo holo 110 10月 4 04:07 1.txtad -rwxrwxr-x 1 holo holo 0 10月 4 07:49 2.txt -rwxrwxr-x 1 holo holo 8681 10月 4 05:55 a.out ---xrw---- 1 holo holo 121 10月 4 04:08 asdae ----r-S--- 1 holo holo 121 10月 4 04:08 asd.tt -rwxrwxr-x 1 holo holo 9085 10月 4 13:14 dir -rw-rw-r-- 1 holo holo 1120 10月 4 13:15 dir.c -rwxrwxr-x 1 holo holo 8723 10月 4 08:36 dup2 -rw-rw-r-- 1 holo holo 456 10月 4 08:36 dup2.c -rw-r--r-- 1 holo holo 19475 10月 4 06:49 eset nu -rwxrwxr-x 1 holo holo 8726 10月 4 18:32 execl -rw-rw-r-- 1 holo holo 389 10月 4 18:29 execl.c -rwxrwxr-x 1 holo holo 9145 10月 7 22:05 gongxiang -rw-rw-r-- 1 holo holo 743 10月 7 22:05 gongxiang.c -rw-rw-r-- 1 holo holo 436 10月 4 04:08 lseek.c drwxrwxr-x 2 holo holo 4096 10月 5 19:35 pipe -rwxrwxr-x 1 holo holo 8825 10月 4 04:01 read -rw-rw-r-- 1 holo holo 501 10月 4 04:07 read.c lrwxrwxrwx 1 holo holo 6 10月 4 06:54 read.c.s -> read.c -rwxrwxr-x 1 holo holo 14 10月 4 08:36 see.log -rwxrwxr-x 1 holo holo 8633 10月 4 06:14 stat -rw-rw-r-- 1 holo holo 287 10月 4 06:14 stat.c -rwxrwxr-x 1 holo holo 8572 10月 4 18:31 testt -rw-rw-r-- 1 holo holo 164 10月 4 18:31 testt.c -rwxrwxr-x 1 holo holo 8834 10月 4 05:46 tty -rw-rw-r-- 1 holo holo 427 10月 4 05:55 tty.c -rwxrwxr-x 1 holo holo 110 10月 4 04:06 vim -rwxrwxr-x 1 holo holo 8828 10月 4 19:23 wait -rw-rw-r-- 1 holo holo 953 10月 4 19:22 wait.c -rwxrwxr-x 1 holo holo 9046 10月 7 18:16 waitpid -rwxrwxr-x 1 holo holo 9097 10月 7 17:48 waitpid1 -rwxrw-r-- 1 holo holo 2353 10月 7 17:47 waitpid1.c -rwxrw-r-- 1 holo holo 1586 10月 7 18:16 waitpid.c -r--rwS--- 1 holo holo 121 10月 4 05:28 xxx father:pid = [49054] [testt] [ni] [hao] father:pid = [49054] [testt] [core] [core] son normal exit, status is [0] |