第四章 进程的控制与终止

来源:互联网 发布:2016淘宝官方活动 编辑:程序博客网 时间:2024/05/20 20:47

一、进程的控制

如果父进程先于子进程退出,则子进程成为孤儿进程,此时将自动被PID为1的进程(即init)接管。孤儿进程退出后,它的清理工作有祖先进程init自动处理。但在init进程清理子进程之前,它一直消耗系统的资源,所以要尽量避免。

孤儿进程示例:
#include <stdio.h>#include <unistd.h>#include <stdlib.h>int main(){if(!fork()){printf("I am child\n");while(1);}else{exit(0);}return 0;}

进程:父进程ID为1


如果子进程先退出,系统不会自动清理掉子进程的环境,而必须由父进程调用wait或waitpid函数来完成清理工作,如果父进程不做清理工作,则已经退出的子进程将成为僵尸进程(defunct),在系统中如果存在的僵尸(zombie)进程过多,将会影响系统的性能,所以必须对僵尸进程进行处理。简单来讲,子进程先退出,父进程仍在运行,子进程就会变为僵尸进程。

原型:
#include <sys/types.h>#include <sys/wait.h>pid_t wait(int *status);pid_t waitpid(pid_t pid, int *status, int options);

wait和waitpid都将暂停父进程,等待一个已经退出的子进程,并进行清理工作;
wait函数随机地等待一个已经退出的子进程,并返回该子进程的pid;子进程结束,向父进程发送一个SIGCHLD信号,父进程被唤醒,得到子进程pid,若子进程已经退出,则回收资源,若子进程仍在运行,那么父进程进入S状态
waitpid等待指定pid的子进程;如果为-1表示等待所有子进程。
status参数是传出参数,存放子进程的退出状态;通常用下面的两个宏(函数)来获取状态信息:
WIFEXITED(status) 如果子进程正常结束,它就取一个非0值。传入整型值,非地址。
WEXITSTATUS(status) 如果WIFEXITED非零,它返回子进程的退出码
options用于改变waitpid的行为,其中最常用的是WNOHANG,它表示无论子进程是否退出都将立即返回,不会将调用者的执行挂起。


创建僵尸进程:
#include <stdio.h>#include <unistd.h>#include <stdlib.h>int main(){if(!fork()){printf("I am child\n");exit(0);}else{while(1);}return 0;}

进程:


wait()示例:
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/types.h>#include <sys/wait.h>int main(){if(!fork()){printf("I am child\n");exit(0);}else{int status,ret;ret=wait(&status);printf("wait is %d\n",ret);ret=WIFEXITED(status);printf("WIFEXITED is %d\n",ret);if(WIFEXITED(status)){printf("the exit value is %d\n",WEXITSTATUS(status));}return 0;}}

运行结果:


waitpid()示例:
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/types.h>#include <sys/wait.h>int main(){pid_t pid;pid=fork();if(pid==0){printf("I am child\n");exit(0);}else{int status,ret;ret=waitpid(pid,&status,0);printf("waitpid is %d\n",ret);ret=WIFEXITED(status);printf("WIFEXITED is %d\n",ret);if(WIFEXITED(status)){printf("the exit value is %d\n",WEXITSTATUS(status));}return 0;}}

运行结果:


#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/types.h>#include <sys/wait.h>int main(){pid_t pid;pid=fork();if(pid==0){printf("I am child\n");while(1);}else{int status,ret;ret=waitpid(pid,&status,WNOHANG);printf("waitpid is %d\n",ret);//查看WNOHANG情况下的返回值ret=WIFEXITED(status);printf("WIFEXITED is %d\n",ret);if(WIFEXITED(status)){printf("the exit value is %d\n",WEXITSTATUS(status));}return 0;}}

运行结果:


进程:

二、进程的终止

进程的终止有5种方式:
1、main函数的自然返回:return跳出当前函数
2、调用exit函数:功能:结束当前进程。exit(0)表示正常退出,exit(x)(x不为0)表示异常退出
3、调用_exit函数
4、调用abort函数
5、接收到能导致进程终止的信号ctrl+c SIGINT  ctrl+\ SIGQUIT

前3种方式为正常的终止,后2种为非正常终止。但是无论哪种方式,进程终止时都将执行相同的关闭打开的文件,释放占用的内存等资源。只是后两种终止会导致程序有些代码不会正常的执行比如对象的析构、atexit函数的执行等。
exit和_exit函数都是用来终止进程的。当程序执行到exit和_exit时,进程会无条件的停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本程序的运行。但是它们是有区别的,exit和_exit的区别如图所示:



exit函数和_exit函数的最大区别在于exit函数在退出之前会检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的“清理I/O缓冲”。
由于linux的标准函数库中,有一种被称作“缓冲I/O”操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区中读取;同样,每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足一定的条件(如达到一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也为编程带来了麻烦。比如有一些数据,认为已经写入文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit函数直接将进程关闭,缓冲区中的数据就会丢失。因此,如想保证数据的完整性,建议使用exit函数。

exit()和_exit()不加'\n'的区别:
//_exit()
#include <stdio.h>#include <unistd.h>int main(){printf("I am here");_exit(0);}



//exit()
#include <stdio.h>#include <stdlib.h>int main(){printf("I am here");exit(0);}



_exit 不会flush 任何标准输入输出 (stdio)流(标准输出是按行缓冲的),即通过printf/fprintf 这类调用产生的尚未写入设备仍在缓冲中的数据都会丢弃。
(对于此函数return的运行结果和exit()的一样)

PS:大多数情况下只用前两种方式
0 0
原创粉丝点击