您的位置:首页 > 大数据 > 人工智能

进程等待和进程终止

2018-01-31 15:47 316 查看
进程终止

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  exit wait waitpid system