进程终止
1》进程退出场景:
(a)程序运行完毕,结果正确
(b)程序运行完毕,结果不正确
(c)代码异常终止
2》进程退出方法:
(1)正常退出
(a)在main函数内执行return语句
(b)调用exit
(c)调用_exit或_Exit函数
(2)异常退出
(a) 调用abort。
(b)当进程接收到某些信号时。
(c) 最后一个线程对“取消”做出相应。
不管进程如何终止,最后都会执行进程内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
一般情况下,我们希望将终止进程的终止状态告知其父进程。对于以上exit,_exit,_Exit等函数,它将其退出状态作为参数传送给函数; 在异常终止情况,内核产生一个指示其异常终止原因的终止状态(在最后调用_exit时,内核将退出状态转换成终止状态。)
另一种情况,如果父进程在子进程之前终止,对于父进程已经终止的所有进程,它们的父进程都改变为init进程(孤儿状态之前已详述);如果子进程在父进程之前终止,内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包含进程ID,该进程终止状态以及该进程使用的CPU时间总量。
3》退出函数
(1)_exit
_exit是linux系统调用,关闭所有文件描述符,然后退出进程。
#include<unistd.h>
void _exit(int status);
参数status定义了进程的终止状态,父进程通过wait来获取该值。
(2)exit
exit是C语言的库函数,他最终也调用_exit。
#include<unistd.h>
void exit(int status);
工作流程
1.执行用户通过alexit或on_exit定义的清理函数。
2.关闭所有打开的流,所有的缓存数据均被写入。
3.调用_exit
对比演示
[a@localhost ~]$ vim exit.c
[a@localhost ~]$ cat exit.c
#include<stdio.h>
int main()
{
printf("hello");
exit(0);
}
[a@localhost ~]$ ./a.out
hello[a@localhost ~]$
[a@localhost ~]$ vim _exit.c
[a@localhost ~]$ cat _exit.c
#include<stdio.h>
int main()
{
printf("hello");
_exit(0);
}
[a@localhost ~]$ ./a.out
[a@localhost ~]$
(3)return
return退出是一种更常见的进程退出方法,等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做exit的参数。
进程等待
目的:父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息,避免子进程进入僵尸状态。
调用wait或waitpid的进程可能会发生的情况:
(1)如果其所有进程都在运行,则阻塞。
(2)如果一个子进程已终止,正等待其父进程获取其终止状态,则取得该子进程的终止状态立即返回。
(3)如果没有任何子进程,则立即出错返回。
wait和waitpid函数
pid_t wait(int* status);
返回值:成功返回被等待进程pid,失败返回-1.
参数:status是一个整形指针,若不为空则终止进程的终止状态就存放在它所指向的单元,不关心则可以设置为NULL
pid_t waitpid(pid_t pid,int *status,int options);
返回值:正常返回的时候waitpid返回收集到的子进程的进程ID;调用中出错返回-1.
参数:
(1)pid pid=-1:等待任一个子进程,与wait等效。 pid>0:等待其进程ID与pid相等的子进程
(2)status WIFEXITED(status):查看进程是否是正常退出 WEXITSTATUS(status):查看进程的退出码
(3)options WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID.
waitpid函数相比wait函数的优势:
(1)waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态。
(2)waitpid提供了wait的非阻塞版本。
(3)waitpid通过WUNTRACED 和WCONTINUED选项支持作业控制。
进程的阻塞等待方式:
#include<unistd.h>
#include<stdio.h>
#include<sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
printf("%s fork error\n");
}
else if(pid == 0)
{
printf("child is run,pid is:%d\n",getpid());
sleep(5);
exit(1);
}
else
{
int status = 0;
pid_t ret = waitpid(-1,&status,0);//阻塞式等待,等待5秒
printf("this is test for wait\n");
if(WIFEXITED(status) && ret == pid)
{
printf("wait child 5s success,child return code is:%d.\n",WEXITSTATUS(status));
}
else
{
printf("wait child failed,return.\n");
return 1;
}
}
return 0;
}
[a@localhost ~]$ ./a.out
child is run,pid is:4930
this is test for wait
wait child 5s success,child return code is:1.
[a@localhost ~]$
进程的非阻塞等待方式:
[a@localhost ~]$ cat test.c
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
pid_t pid=fork();
if(pid<0){
printf("fork is error\n");
return 1;
}else if(pid==0){//child
printf("child is run,pid is:%d\n",getpid());
sleep(5);
exit(1);
}
else{
int status=0;
pid_t ret=0;
do{
ret=waitpid(-1,&status,WNOHANG); //非阻塞式等待
if(ret==0){
printf("child is running\n");
}
sleep(1);
}while(ret==0);
if(WIFEXITED(status) && ret==pid){
printf("wait child 5s success,child return code is:%d\n",
WEXITSTATUS(status));
}else{
printf("wait child failed,return.\n");
return 1;
}
}
return 0;
}
[a@localhost ~]$ ./a.out
child is running
child is run,pid is:4988
child is running
child is running
child is running
child is running
wait child 5s success,child return code is:1
[a@localhost ~]$
system/popen
1》system在其实现中调用了fork,exec和waitpid因此有三种返回值。
(1)fork失败或者waitpid返回除EINTR之外的出错,则system返回-1,并设置errno以指示错误类型。
(2)如果exec失败,则其返回值如同shell执行了exit(127)一样。
(3)否则所有3个函数都成功,那么system的返回值是shell的终止状态。
源码:
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<sys/wait.h>
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL){
return (1);
}
if((pid = fork())<0){
status = -1;//fork失败,返回-1
}
else if(pid = 0){
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127); //execl执行失败返回127
}
else{
while(waitpid(pid, &status, 0) < 0){
if(errno != EINTR){
status = -1;
break;
}
}
}
return status;
}
system的原理: 当system接受的命令为NULL时直接返回,否则fork出一个子进程,因为fork在两个进程:父进程和子进程中都返回,这里要检查返回的pid,fork在子进程中返回0,在父进程中返回子进程的pid,父进程使用waitpid等待子进程结束,子进程则是调用execl来启动一个程序代替自己,execl(“/bin/sh”, “sh”, “-c”, cmdstring, (char*)0)是调用shell,这个shell的路径是/bin/sh,后面的字符串都是参数,然后子进程就变成了一个shell进程,这个shell的参数是cmdstring,就是system接受的参数。
对比fork原理:当一个进程A调用fork时,系统内核创建一个新的进程B,并将A的内存映像复制到B的进程空间中,因为A和B是一样的,那么他们怎么知道自己是父进程还是子进程呢,看fork的返回值就知道,上面也说了fork在子进程中返回0,在父进程中返回子进程的pid。
popen
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
2》popen() 函数用创建管道的方式启动一个 进程, 并调用 shell. 因为管道是被定义成单向的, 所以 type 参数只能定义成只读或者只写, 不能是两者同时, 结果流也相应的是只读或者只写. command 参数是一个字符串指针, 指向的是一个以 null 结束符结尾的字符串, 这个字符串包含一个 shell 命令. 这个命令被送到 /bin/sh 以 -c 参数执行, 即由 shell 来执行. type 参数也是一个指向以 null 结束符结尾的字符串的指针, 这个字符串必须是 ‘r’ 或者 ‘w’ 来指明是读还是写.
#include
int main(int argc, char *argv[])
{
char buf[128];
FILE *pp;
if( (pp = popen("ls -l", "r")) == NULL )
{
printf("popen() error!/n");
exit(1);
}
while(fgets(buf, sizeof buf, pp))
{
printf("%s", buf);
}
pclose(pp);
return 0;
}