Linux C编程--进程介绍3--进程终止和等待

时间:2021-04-01 14:53:46

进程结束

1.在Linux中任何让一个进程结束

进程退出表示进程即将结束。在Linux中进程退出分为了正常退出和异常退出两种。

1>正常退出

a. 在main()函数中执行return 。

b.调用exit()函数

c.调用_exit()函数

2>异常退出

a.调用about函数

b.进程收到某个信号,而该信号使程序终止。

不管 是哪种退出方式,系统最终都会执行内核中的同一代码。这段代码用来关闭进程所用已打开的文件描述符,释放它所占用的内存和其他资源。

 

 

3.比较以上几种退出方式的不同点

(1)exit和return 的区别:

a.exit是一个函数,有参数。exit执行完后把控制权交给系统

b.return是函数执行完后的返回。renturn执行完后把控制权交给调用函数。

(2)exit和abort的区别:

a.exit是正常终止进程

b.about是异常终止。


exit()和_exit()函数

exit()和_exit()的学习

1>exit和_exit函数都是用来终止进程的。

当程序执行到exit或_exit时,系统无条件的停止剩下所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。

2>exit在头文件stdlib.h中声明,而_exit()声明在头文件unistd.h中声明。 exit中的参数exit_code为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生。

3>exit()和_exit()的区别: 

a._exit()执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。

b. 调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin, stdout, stderr  ...).   exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新流。


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


几个要点:
1.不管进程如何终止,最后都会执行内核中的同一段代码:为相应进程关闭所有打开描述符,释放内存等等。
2.若父进程在子进程之前终止了,则子进程的父进程将变为init进程,其PID为1保证每个进程都有父进程。
3.当子进程先终止,父进程如何知道子进程的终止状态?事实上,内核为每个终止子进程保存了终止状态等信息,父进程调用wait等函数,就可获取该信息。
4.当父进程调用wait等函数后,内核将释放终止进程所使用的所有内存,关闭其打开的所有文件。
5.对于已经终止、但是其父进程尚未对其调用wait等函数的进程,被称为僵尸进程(即已经结束,但尚未释放资源的)。
6.对于父进程先终止,而被init领养的进程会是僵尸进程吗?init对每个终止的子进程,都会调用wait函数,获取其终止状态信息。
综上所述,子进程调用exit后,父进程必须调用wait。

进程等待

系统中的僵尸进程都要由wait系统调用来回收,下面就通过实战看一看wait的具体用法:

wait的函数原型是:

#include <sys/types.h> /* 提供类型pid_t的定义 */

#include <sys/wait.h>

pid_t wait(int *status);

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程, wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:

pid = wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。


下面给出两个示例

1.不同类型结束进程的状态字示例

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
/* 处理并打印状态字的子函数*/
void h_exit(int status)
{
if(WIFEXITED(status))
printf("normal termination, exit status=%d \n", WEXITSTATUS(status));
else if(WIFSIGNALED(status))
{

printf("abnormal termination, signal number =%d %s\n", WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status) ? " )" : "(core file generated)");
#else
") ");
#endif
}
}
/*主函数。示范三种结束进程的不同方式,并调用h_exit函数处理返回状态字*/
int main()
{
pid_t pid;
int status;
/*子程序正常退出 */
if((pid=fork())<0)
{
printf("fork error \n");
exit(0);
}
else if (pid==0)
exit(7);
if(wait(&status)!=pid) /*等待子进程*/
{
printf("wait error \n");
exit(0);
}
h_exit(status); /*打印状态 */
/*子进程abort终止 */
if((pid=fork())<0)
{
printf("fork errof\n");
exit(0);
}
else if(pid==0) /*子进程*/
abort(); /*产生信号SIGABRT终止进程*/
if(wait(&status)!=pid) /*等待子进程*/
{
printf("wait error.\n");
exit(0);
}
h_exit(status); /*打印状态*/
/* 子进程除零终止 */
if((pid=fork())<0)
{
printf("fork errof\n");
exit(0);
}
else if(pid==0) /*子进程 */
status /=0; /*除数为0产生SIGFPE */
if(wait(&status)!=pid)
{
printf("wait error.\n");
exit(0);
}
h_exit(status); /*打印状态*/

exit(0);
}


2.waitpid的使用

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
int main()
{
pid_t pid;

if((pid=fork())<0)
{
printf("fork error.\n");
exit(0);
}
else if(pid==0)
{
if((pid=fork())<0)
{
printf("fork error.\n");
exit(0);
}
else if(pid>0)
exit(0);
sleep(2);
printf("second child, parent pid=%d \n", getppid());
exit(0);
}

if(waitpid(pid, NULL, 0)!=pid)
{
printf("waitpid error.\n");
exit(0);
}
exit(0);
}

说明:

WIFEXITED(status)如果子进程正常结束则为非0 值。

WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。


WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。


WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。


wait函数

wait函数的原型为:pid_t wait(int *status)

  当进程退出时,它向父进程发送一个SIGCHLD信号,默认情况下总是忽略SIGCHLD信号,此时进程状态一直保留在内存中,直到父进程使用wait函数收集状态信息,才会清空这些信息.

  用wait来等待一个子进程终止运行称为回收进程.

  当父进程忘了用wait()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程.

  wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.

如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID 1)继承,当子进程终止时,init进程捕获这个状态.


waitpid函数

1)waitpid的概述:

  .waitpid函数的原型为pid_t waitpid(pid_t pid,int *status,int options)

  .从本质上讲,系统调用waitpid是wait的封装,waitpid只是多出了两个可由用户控制的参数pid和options,为编程提供了灵活性.

  2)waitpid的参数说明:

  参数pid的值有以下几种类型:

  pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去.

  pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样.

  pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬.

  pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值.

  参数options的值有以下几种类型:

  如果使用了WNOHANG参数,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去.

  如果使用了WUNTRACED参数,则子进程进入暂停则马上返回,但结束状态不予以理会.

  Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

  ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);

  如果我们不想使用它们,也可以把options设为0,如:ret=waitpid(-1,NULL,0);

  waitpid的返回值比wait稍微复杂一些,一共有3种情况:

3)waitpid的返回值:

  当正常返回的时候waitpid返回收集到的子进程的进程ID;

  如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

  如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

  当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD.