什么是进程?
进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程都好像在独占地使用硬件。而并发运行,则是说一个进程的指令和另一个进程的指令是交错执行的。
进程和程序的区别
进程为一个抽象概念,在内存中执行,占用系统资源;
程序为静态概念,属于二进制文件,存储在硬盘中,不属于系统资源;
程序可比作“剧本(纸)”,进程可比作“戏(舞台、演员、灯光、道具....)”;同一个剧本可以在多个舞台上同时上演。同样,同一个程序也可以加载为不同的进程(彼此互影响);如:同时打开两个终端,各自都有一个bash但彼此ID不同。
并发概念
并发,在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态。但,任一个时刻点上仍只有一个进程在运行。(任务、进程并行运行;)区别与并行
可以想下,我们使用计算机室可以边听音乐边聊天边上网。若笼统的将他们均看做一个进程的话,为什么可以同时运行呢,因为并发。
单道程序设计
所有进程任务一个一个排队执行。若A阻塞,B只能等待,即使CPU处于空闲状态。而在人机交互时阻塞的出现是必然的。该种模型在系统资源利用上及其不合理,也随之被淘汰。
多道程序设计
在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之下, 相互穿插的运行。多道程序设计必须有硬件基础作为保证。
时钟中断即为多道程序设计模型的基础理论。并发时,任意进程在执行期间都不希望放弃cpu。因此系统需要一种强制让进程让出cpu资源的手段。时钟中断有硬件基础作为保障,对进程而言不可抗拒。操作系统中的中断处理函数,来负责调度程序执行。
在多道程序设计模型中,多个进程轮流使用CPU(分时复用CPU资源)。而当下常见CPU资源为纳秒级,1秒可执行大约10亿条指令。由于人眼的反应速度是毫秒级,所以看似同时运行。
实质上,并发是宏观“并行”,微观串行!
CPU和MMU概念
CPU中存储介质有寄存器、cache(缓存)、内存、硬盘、网络(存储);
寄存器是用价格很高昂金属制成,存储空间较小(考虑节约成本),它分为32位和64位,分别对应存储单位4个字节和8字节?
-
预取器:预取指令
-
译码器:解析指令作用
-
ALU算数逻辑单元:完成运算,加法、左移
-
寄存器堆:指令运算及保存结果
MMU内存管理单元:
作用:
1、完整虚拟内存与物理内存之间映射
2、设置修改内存访问级别(给CPU设定,intel给CPU4中级别)访问从user空间到kernel空间
虚拟内存0-4G -》不是真实存在,真正放在物理内存条中。各部分功能:
- .text : ro只读权限
- .data: rw可读写
- Heap:栈 从低往高
- Stack:堆区 从高向低
- 环境变量/命令行参数
- Kernel:PCB进程控制块(进程描述符)
虚拟地址:可用的地址空间,有4G,但实际物理内存不一定有4G。
物理内存与虚拟内存之间的对应关系,MMU来做,例如实现虚拟地址 ox8043a0000 int a =10;物理地址 1000
注意:
- MMU划分内存最小单位为page 4K
- 进程彼此独立,统一程序起不同进程,占用不空内存空间
- 两个不同进程共用一个内核空间(物理内存)
结论:PCB位于内核空间中,但两个进程的PCB不同,但位于同一块物理内存(MMU来实现)
Liux中内存访问有0级(内核)和3级(用户)
进程控制块PCB
每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。
/usr/src/linux-headers-3.16.0-30/include/linux/sched.h文件中可以查看struct task_struct 结构体定义。其内部成员有很多,我们重点掌握以下部分即可:
进程id pid_t类型表示
进程状态 初始化 就绪 运行态 挂起态 终止态(面试描述关系)
进程切换是需要保存和恢复一些CPU寄存器;(当一个进程A挂起,进程B占用CPU资源。AB使用同一个寄存器空间,A先得保存数据到PCB中,B进程数据然后覆盖寄存器中A数据,执行)
描述虚拟地址空间的信息,MMU完成映射,会用到一个表
物理 | 虚拟地址 |
10000 | Ox80870000 |
描述控制终端的信息
当前工作目录:不同目录的进程不同,pcb 来保存该些进程信息;
Umask掩码 :保护创建文件修改的默认权限;
文件描述表:“ 数的集合”包含很多指向file结构体的指针,file struct 指向打开文件的描述信息;
和信号相关的信息:用户id和组id;
会话和进程组 : 进程组方便管理同一类型进程、会话 “统一管进程组”;
进程可以使用的资源上线:Linux栈大小:ulimit -a;
进程状态
进程状态 有5中,分别为初始化 就绪 运行态 挂起态 终止态。
进程先进图初始化,,等待CPU分配时间片,分配完成,占用CPU处于运行态;等待除CPU以外的其他资源主动放弃CPU,处于挂起态;任务完成或者中断处于终止态.;
环境变量
环境变量操作系统中用来指定操作系统运行环境的一些参数。
Linux多任务,多用户,环境配置是基于用户,不同用户之间环境互不干扰(用户为单位)
①字符串(本质)②有统一的格式:名= 值[:值]③值用来描述进程环境信息(进程为单位)
储存形式:与命令行参数类似。Char*[]数组,数组名environ,内部存储字符串,NULL作为哨兵结尾。
使用形式:与命令行参数类型;
加载位置:与命令行参数类型,位于用户区,高于stack的起始位置;
引入环境变量表 :须声明环境变量,extern ** environ
练习:打印当前进程的所有环境变量,environ.c
#include<stdio.h>
extern char **environ;
int main(){
int i;
for(i=0;environ[i];i++){
printf("%s\n",environ[i]);
}
return 0;
}
常见环境变量
环境变量字符串都是name=value这样的形式,大多数name由大写字母加下划线组成,一般把name的部分叫做环境变量,value的部分则是环境变量的值。环境变量定义了进程的运行环境,一些比较重要的环境变量的含义如下:
Date
Shell 查看环境变量路径PATH解析date,找/bin/date 并执行
PATH
可执行文件是搜索路径。ls命令也是一个程序,执行它不需要提供完整的路径名/bin/ls,然而通常我们执行当前目录下的程序a.out却需要提供完整的路径名./a.out,这是因为PATH环境变量的值里面包含了ls命令所在的目录/bin,却不包含a.out所在的目录。PATH环境变量的值可以包含多个目录,用:号隔开。在Shell中用echo命令可以查看这个环境变量的值:
$ echo $PATH
SHELL
记录当前使用的shell解释器,通常为/bin/bash
TERM
记录当前终端类型,通常为xterm 它决定了程序的输出显示方式
LANG
语言和locale,字符编码,时间 货币等信息显示格式
HOME
记录当前用户的目录路径
常用环境变量香瓜函数
man查看函数用法如man unsetenv
getenv函数
获取环境变量
- Char *getenv(const char *name);
- 成功:返回环境变量的值;失败:NULL(name不存在)
练习:编程实现getenv函数 getenv.c
setenv函数
设置环境变量值
- Int setenv(const char *name,const char *vlaue,int overwrite);
- 成功:0;失败:-1;
- 参数overwrite取值,1:覆盖原环境变量
- 0:不覆盖(该参数常用于设置新的环境变量,ABC=hahhah-daydayupup)
unsetenv函数
删除环境变量name定义
- Int unsetenv(const char *name);
- 成功:0;失败:-1;
- 注意事项:name不存在仍返回0(成功),当name命名为”ABC=”则会出错
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
char *val;
const char *name = "ABD";
val = getenv(name);
printf("1,%s = %s\n",name,val);
setenv(name,"hahahahah",1);
val = getenv(name);
printf("2,%s = %s \n");
#if 0
int ret = unsetenv("ASDF");
printf("ret = %d\n",ret);
val = getenv(name);
printf("3,%s = %s\n",val);
#else
int ret = unsetenv("ABD");
printf("ret = %d\n",ret);
val = getenv(name);
printf("3,%s = %s\n");
#endif
return 0;
}
进程控制
怎么创建一个进程?
运行一个可执行程序,创建一个进程;
fork函数
创建一个子进程
- pid_tfork(void);
- 失败返回-1;成功返回:(返回值有2个)
- 一个进程 --》2个进程 --》各自对fork做返回
- 1 返回子进程的pid(非负整数>0) (父进程)
- 2 返回0 (子进程)注:可以用来判断该进程为父进程还是子进程
创建一个进程,父进程返回子进程id(非负整数)和子进程返回0(表示调用成功)
Pid_t类型表示进程ID ,但为了表示-1,它是有符号整型。(0不是有效进程ID,init最小,为1)
注意返回值,不是fork函数返回两个值,而是fork后,fork函数变为两个,父子需(各自)返回一个。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
pid_t pid;
printf("xxxxxxxxxxxx\n");
pid = fork();
if(pid == -1){
perror("fork erro:");
exit(-1);
}else if(pid == 0){
printf("child,pid = %u,ppid = %u\n",getpid(),getppid());
}else{
printf("parnet,pid=%u,ppid=%u\n",getpid(),getppid());
sleep(1);
}
printf("yyyyyyyyyy\n");
return 0;
}
为什么xxxxxx只打印一遍,而yyyyyyy打印两遍?
打印xxxxx还未创建子进程,此处为父进程打印的xxxxxxx,而父进程打印一遍yyyyyyy,子进程也会打印一遍yyyyyy
练习:循环创建5个子进程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
int main(){
pid_t pid[5];
printf("xxxxxxxxxxxx\n");
int i;
for(i=0;i<5;i++){
pid[i] = fork();
if(pid[i] == -1){
perror("fork erro:");
exit(-1);
}else if(pid[i] == 0){
printf("child,pid = %u,ppid = %u\n",getpid(),getppid());
}else{
printf("parnet,pid=%u,ppid=%u\n",getpid(),getppid());
sleep(1);
}
}
printf(“yyyyyyy\n”);
return 0;
}
结果:循环5次,创建进程远远大于5个,创建了2^5-1个进程,父进程创建子进程之后,父进程会重新创建子进程而子进程也会创建它的“子进程”,如下图所示:
改进:不让创建出来的子进程在此创建新的进程,判断pid是否为父进程,若父进程则创建,若为子进程则不创建。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
int main(){
pid_t pid;
printf("xxxxxxxxxxxx\n");
int i;
for(i=0;i<5;i++){
pid = fork();
if(pid == -1){
perror("fork erro:");
exit(-1);
}else if(pid == 0){
break;
}
}
if(i<5){
printf("I am %d child,pid = %u\n",i+1,getpid());
}
//printf("yyyyyyyyyy\n");
return 0;
}
gcc fork_test_5.c -o fork_test_5 -Wall -g
思考:父进程创建子进程返回的pid是一个整形数,当父进程运行退出,进入子进程运行后,返回的pid为什么为0???
另
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
int main(){
pid_t pid;
printf("xxxxxxxxxxxx\n");
int i;
for(i=0;i<5;i++){
pid = fork();
if(pid == -1){
perror("fork erro:");
exit(-1);
}else if(pid == 0){
break;
}
}
if(i<5){
sleep(i);
printf("I am %d child,pid = %u\n",i+1,getpid());
}else{
sleep(i);
printf("I am parnet\n");
}
//printf("yyyyyyyyyy\n");
return 0;
}
getpid函数
获取当前进程ID
- pid_t getpid(void);
getppid函数
获取当前进程的父进程ID
- pid_t getppid(void);
- 区分一个函数是“系统函数”还是“库函数”依据:
- 是否访问内核数据结构
- ② 是否访问外部硬件资源 二者有任一 → 系统函数;二者均无 → 库函数
getuid函数
获取当前进程实际用户ID
- uid_t getuid(void);
- 获取当前进程有效用户ID
- uid_t geteuid(void);
getgid函数
获取当前进程使用用户组ID
- gid_t getgid(void);
- 获取当前进程有效用户组ID
- gid_t getegid(void);
进程共享
父子进程之间在fork后,有哪些异同?
- 父子相同处:全局变量(独享),.data、.Text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式
- 父子不同处:1进程id 2 fork返回值 3 父进程ID 4 进程运行时间 5 闹钟(定时器)6未决信号集
似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但pid不同。真的每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗?
结论是否定,父子进程间遵循读时共享写时复制原则(操作系统调用MMU作用物理内存完成的);这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销;
练习:测试程序,父子进程是否共享全局变量
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int var = 34;
int main(){
pid_t pid;
pid = fork();
if(pid == 1){
perror("fork");
exit(-1);
}else if(pid > 0){
sleep(2);
var = 55;
printf("I am parnet pid = %d,parnet ID = %d,var = %d\n",getpid(),getppid(),var);
}else if(pid == 0){
var = 100;
printf("I am child pid = %d,child ID = %d,var = %d\n",getpid(),getppid(),var);
}
printf("var == %d\n",var);
return 0;
}
重点注意:躲避父子进程共享全局变量的知识误区。
重点:父子进程共享:1 文件描述符(打开文件的结构体) 2mmap建立的映射区(进程间通信详解)
特别的,fork之后父进程先执行还是子进程先执行不确定。取决于内核所使用的调度算法
Gdb调试
使用gdb调试的时候,gdb只跟踪一个进程, 可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者跟踪子进程。默认跟踪父进程。
Set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程
Set follow-fork-mode parnet 设置跟踪父进程
注意:一定要在fork函数调用之前设置才有效
gcc fork_share.c -g
gdb a.out
开始 start\运行 run\下一步 n\结束 quit\设置断点 b line 代码。
进程创建之后,想跟踪第3个子进程,通设置断点来实现 b 17 i=3
exec函数族
fork创建子进程后执行和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec函数并不创建新进程,所以exec前后该进程的ID并未改变。
当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。
其中有六种以exec开头函数,统称为exec函数:
int execl(const char *path,const char *arg,...);
int execlp(const char *path,const char *arg,...);
int execle(const char *path,const char *arg,...,char *const envp[]);//先要引入环境变量表
int execv(const char *path,const char *argv[]);
char *argv[]={};exec("/bin/ls",argv);
int execvp(const char *file,char *const argv[]);
int execve(const char *path,char *const argv[],char *const envp[]);
execlp函数
用于加载一个进程,借助PATH环境变量;
int execlp(const char *file,const char *arg,....);
成功:无返回;失败:-1
参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1,则出错返回。该函数通常来调用系统程序,如:ls,date,cp,cat等命令。
execl函数
加载一个进程, 通过路径+程序名来加载。
int execl(const char *path,const char *arg,...);
成功:无返回值;失败:-1
对比execlp,如加载“ls”命令带有-l,-F参数
execlp(“ls”,“ls”,"-l","-F",NULL);//使用程序名在PATH中搜索
execl("/bin/ls","ls","-l","-F",NULL);//使用参数1给出的绝对路径搜索
execvp函数
加载一个进程,使用自定义环境变量env;
int execvp(const char *file,char *const argv[]);
- 变参形式:①... ②argv[] (main函数也是变参函数),形式上等同于int main(int argc,char *argv0,....)
- 变参终止条件:①NULL 结尾 ②固参指定
execvp与execlp参数形式不同,原理一致。
练习:将当前系统进程信息,打印到文件中。(exec_ps.c)
exec函数族一般规律
exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。
- l(list) 命令行参数列表
- p(path) 搜索file时使用path变量
- v(vector) 使用命令行参数数组
- e(environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
事实上,只有exec是真正的系统调用,其他五个函数最终都调用execve,所以execve在man手册第2节,其他函数在第3节。这些函数之间关系如下图所示:
例子
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(void){
pid_t pid;
pid = fork();
if(pid <0){
perror("erro");
exit(-1);
}else if(pid == 0){
printf("I am child pid = %u\n",getpid());
}else if(pid >0){
printf("I am parnet pid = %u\n",getppid());
execlp("ls","fasdf","-a",NULL);
}
return 0;
}
重定向函数 dup2()
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main(void){
int fd;
fd = open("ps.out",O_WRONLY|O_CREAT|O_TRUNC,0644);
if(fd < 0){
perror("open ps.out error");
exit(1);
}
dup2(fd,STDOUT_FILENO);//dup2(3,1); fd,stdout
execlp("ps","ps","ax",NULL);
return 0;
}
回收子进程
孤儿进程
孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,成为init进程领养的孤儿进程。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
int main(){
pid_t pid;
pid = fork();
if(pid == -1){
perror("fork erro:");
exit(-1);
}else if(pid > 0){
sleep(1);
printf("parnet,pid=%d,ppid=%d\n",getpid(),getppid());
}
else if(pid == 0){
//sleep(1);
printf("child,pid = %u,ppid = %u\n",getpid(),getppid());
sleep(3);
//此时的父进程已经结束,当只进程未结束,会那Init进程作为它的父进程
printf("child,pid = %u,ppid = %u\n",getpid(),getppid());
}
return 0;
}
僵尸进程
僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
值得注意:僵尸进程是不能使用kill命令清除掉的,因为kill命令只是用来终止进程的,而僵尸进程已经终止。思考:用什么办法可清除掉僵尸进程呢?
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
int main(){
pid_t pid;
pid = fork();
if(pid == 0){
printf("I am child,pid = %u,ppid = %u\n",getpid(),getppid());
sleep(9);
printf("--------child go to die-----------\n");
}else if(pid > 0){
while(1){
printf("I am parnet,pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
}else{
perror("fork");
return -1;
}
return 0;
}
~
父进程创建一个子进程之后,pid的返回值?? 父进程结束之后,pid 的值?子进程占用CPU资源,然后休眠9秒,此时进入循环语句 父进程占用CPU资源;
【zoom】是子进程退出之后,资源未被回收,处于僵尸进程状态,需要回收僵尸进程
wait函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或者waitpid获取这些信息,然后彻底清除掉这个进程。一个进程的退出状态可在shell中用特殊变量 $?查看,因为shell是它的父进程,当它终止时shell调用wait或waitpid得到它的退出状态,同时彻底清除掉这个进程。
父进程调用wait函数可以回收子进程终止信息,函数有三个功能:
- 阻塞等待子进程退出;
- 回收子进程残留资源,调用成功,没有僵尸进程,不占pcb资源
- 获取子进程结束状态(退出原因)
pid_t wait(int *status);
//成功:清理掉的子进程ID;失败:-1(没有子进程)死亡状态
当进程终止时,操作系统的隐式回收机制会:
- 关闭所有文件描述符;
- 释放用户空间分配的内存;
- 内核的PCB仍存在,其中保存该进程的退出状态。(正常终止->退出值;异常终止->终止信号)
可使用wait函数传出参数status来保存进程的退出状态,借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
WIFEXITED(status)为非0 --进程正常结束;
WEXITSTATUS(status)如上宏为真,使用此宏-- 获取进程退出状态(exit的参数)。
WIFSIGNALED(status)为非0 -- >进程异常终止;
WTERMSIG(status)如上宏为真,使用此宏 -->取得使进程终止的那个信号的编码。
WIFSTOPPED(status)为非0, -->进程处于暂停状态;
WSTOPSIG(status)如上宏为真,使用此宏-->取得使进程暂停的那个信号的编号;
WIFCONTINUED(status)为真-->进程暂停后已经继续运行。
粗略回收
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid_t wpid;
pid = fork();
if(pid == 0){
printf("I am child,pid = %u,ppid = %u\n",getpid(),getppid());
sleep(9);
printf("--------child go to die-----------\n");
}else if(pid > 0){
wpid = wait(NULL);
if(wpid<0){
perror("fail");
exit(1);
}
while(1){
printf("I am parnet,pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
}else{
perror("fork");
return -1;
}
return 0;
}
精细回收
查看子进程退出状态,及使得子进程终止信号的编号
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid_t wpid;
int status;
pid = fork();
if(pid == 0){
printf("I am child,pid = %u,ppid = %u\n",getpid(),getppid());
sleep(9);
printf("--------child go to die-----------\n");
return 100;
}else if(pid > 0){
wpid = wait(&status);
if(wpid == -1){
perror("wait error:");
exit(1);
}
if(WIFEXITED(status)){
printf("child exit with %d\n",WIFEXITED(status));
//status = WEXITSTATUS(status);
//printf("status %d\n",status);
}
if(WEXITSTATUS(status)){
printf("child kill by %d\n",WEXITSTATUS(status));
}
while(1){
printf("I am parnet,pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
}else{
perror("fork");
return -1;
}
return 0;
}
结果
子进程休眠9秒,父进程调用wait函数回收子进程终止信息,此时父进程阻塞等待子进程退出;
fork函数返回pid值问题
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
int main(){
pid_t pid;
pid = fork();
printf("1 fork pid :%d\n",pid);
if(pid == 0){
printf("I am child,pid = %u,ppid = %u\n",getpid(),getppid());
printf("2 pid :%d\n",pid);
sleep(9);
printf("--------child go to die-----------\n");
//printf("2 chile pid :%d\n",pid);
}else if(pid > 0){
printf("pid :%d\n",pid);
while(1){
printf("I am parnet,pid=%d,ppid=%d\n",getpid(),getppid());
printf("3 pid :%d\n",pid);
sleep(1);
}
}else{
perror("fork");
return -1;
}
return 0;
}
父进程使用fork函数创建子进程后,返回创建子进程的id,此时进程id为整形数大于0,执行父进程,父进程结束之后pid的值为什么会变为0,然后执行子进程???。
waitpid函数
作用同wait,但可指定pid进程清理,可不阻塞;
pid_t waitpid(pid_t pid,int *status,int options);
//成功:返回清理掉的子进程ID,失败:-1(无子进程)
参数pid:
- pid >0 回收指定ID的子进程;
- pid = -1 回收任意子进程(相当于wait);
- pid = 0 回收和当前调用waitpid一个组的所有子进程;
- pid < -1 回收指定进程组内的任意子进程;
返回值为0:参数3为WHONANG,且子进程正在运行。(回收子进程时轮询)
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。【waitpid.c】
回收指定进程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(void){
int flag = 0;
pid_t pid,pid_2,wpid;
pid = fork();
pid_2 = fork();
if(pid == -1){
printf("fork erro");
exit(1);
} else if(pid == 0){ //son
printf("I am child,pid = %d\n",getpid());
sleep(5);
exit(4);
} else{ //parnet
do{
wpid = waitpid(pid,NULL,WNOHANG);
//wpid = wait(NULL);
printf("---wpid = %d ----- %d\n",wpid,flag++);
if(wpid == 0){
printf("NO child exited\n");
sleep(1);
}
}while(wpid == 0);
if(wpid == pid){
printf("I am parnet,I catched child process,pid = %d\n",getpid());
}else{
printf("other ....\n");
}
}
return 0;
}
业:父进程fork3个子进程,三个子进程一个调用ps命令,一个调用自定义程序1(正常),一个调用自定义程序2(会出段错误)。父进程使用waitpid对进行回收。