25.Linux/Unix 系统编程手册(上) -- 进程的终止

1.进程的终止 : _exit() 和 exit()
	1. _exit(int status), status 定义了终止状态,父进程可调用 wait 获取。仅低8位可用,
	  调用 _exit() 总是成功的。
	2.程序一般不会调用 _exit(), 而是调用库函数 exit()。
	  exit() 执行如下动作:
	  	1.调用退出处理程序(通过 atexit() 和 on_exit() 注册的函数),其执行顺序与注册顺序相反。
	  	2.刷新 stdio 流缓冲区
	  	3.使用由 status 提供的值执行 _exit() 系统调用。
	与专属的Unix的 _exit 不同, exit() 则属于标准C语言函数库。


2.进程终止的细节
	无论进程是否正常终止,都会发生如下动作:
	1.关闭所有打开文件描述符,目录流,信息目录描述符,以及转换描述符
	2.作为文件描述符关闭的后果之一,将释放该进程所持有的任何文件锁
	3.分离任何已经连接的 System V 共享内存段,且对应于各段的 shm_nattch 计数器值减一
	4.进程为每个 System V 信号量所设置的 semadj 值将会被加到信号量值中。
	5.如果该进程是一个管理终端的管理进程,那么系统会向该终端前台进程组中的每个进程发送 sighup 信号,
	  接着终端会于会话脱离。
	6.将关闭该进程打开的任何 POSIX 有名信号量,类似调用 sem_close()
	7.将关闭该进程打开的任何 POSIX 消息队列,类似于调用 mq_close()
	8.作为进程退出的后果之一,如果某进程组称为孤儿,且该组中存在任何已停止进程,则组中所有进程都将收到 sighup 
	  信号,随着为 sigcont 信号。
	9.移除该进程通过 mlock() 和 mlockall() 锁建立的任何内存锁
	10.取消该进程调用 mmap() 所创建的任何内存映射。


3.退出处理程序
	退出处理程序是一个由程序设计者提供的函数,可于进程生命周期的任意时间点注册,并在该进程调用 exit() 正常终止时
  自动执行。如果程序直接调用 _exit() 或者因信号而异常终止,则不会调用退出处理程序。
    当程序收到信号而终止时,将不会调用退出处理程序。这一事实一定程序上限制了它们的效用。此时最佳的应对方式莫若为可能发送
  给进程的信号建立信号处理程序,并于其中设置标志位,领主程序据此来调用 exit()。因为 exit() 不属于异步信号安全函数,所有通常
  不能在信号处理程序中对其发起调用。
  
  atexit();
  on_exit(); 

  	通过 fork 创建的子进程会继承退出处理程序,而 exec 会清除退出处理程序。


4.fork,stdio缓冲区以及_exit 之间的交互

#include <stdio.h>

int main()
{
        printf("start:\n");
        if ( fork() == -1 ) {
                exit(1);
        }
        exit(0);
}

	printf() 信息输出了2次,是在进程的用户空间内存维护 stiod 缓冲区的。因此,通过 fork 创建的子进程会复制这些缓冲区。
  父子进程调用 exit() 时,会各自刷新 stdio 缓冲区,从而导致重复输出。

  可以采用以下2种方法避免:
  1.可以在调用 fork 之前,使用 fflush() 来刷新 stdio 缓冲区,作为另外一种选择,使用 setvbuf() 和 setbuf() 来关闭 stdio 流缓冲。
  2.子进程可以调用 _exit() 而非 exit(),以便不刷新 stdio 缓冲区。
  这一技术例证了更为通用的原则:在创建子进程的应用中,典型情况下仅一个进程(一般为父进程)应通过调用 exit() 终止,而其他进程应调用 _exit()终止,
 从而确保一个进程调用退出处理程序刷新 stdio 缓冲区。

  write 并未出现2次,是因为 write 会将数据直接传递给内核缓冲区,fork 不会复制这一缓冲区。

猜你喜欢

转载自blog.csdn.net/enlyhua/article/details/82919190