UNP卷一学习笔记:POSIX信号处理

Posted printfnothing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UNP卷一学习笔记:POSIX信号处理相关的知识,希望对你有一定的参考价值。

信号是告知某个进程发生了某个事件的通知,也叫软件中断,通常是异步发生的。

信号的传递:A进程→B进程或者内核→某个进程

当进程收到某一信号,需要有个相应的处置(disposition),一般通过调用sigaction函数来设定对某个特定信号的处置,并有三种选择:

(1)为信号提供一个信号处理函数,这种行为称为捕获信号。但是这种行为无法捕获SIGKILL和SIGSTOP信号。处理函数一般形式为:

void handler(int signo);函数名和参数名可以由自己指定。

(2)把某个信号的处置设为SIG_IGN来忽略它。SIGKILL和SIGSTOP无法忽略。

(3)把某个信号的处置设为SIG_DFL来启用它的默认处置。比如SIGCHLD和SIGURG的默认处置是忽略。


由于sigaction函数调用复杂,UNP卷一中将其进行改进,通过signal函数去调用完善它。

signal函数原型:void (*signal(int signo,void(*func)(int)))(int);

typedef简化:typedef void Sigfunc(int);

变为:Sigfunc *signal(int signo,Sigfunc *func);


signal函数对POSIX信号处理可以总结为以下几点:

(a)一旦设置了信号处理函数 Sigfunc*func,它便一直安装着。

(b)在一个信号处理函数运行期间,正被递交的信号是阻塞的。

(c)同一信号在递交时默认不排队,即使它在阻塞期间产生了多次,等到信号被解阻塞之后也只被递交一次。

(d)利用sigprocmask函数选择性地阻塞或解阻塞一组信号是可能的。


处理SIGCHLD信号:

当一个子进程退出后,内核释放该进程所有的资源,包括打开的文件,占用的内存等,并且会向它的父进程传递一个SIGCHLD信号。

子进程虽然退出了,但内核依然为其保留一些状态信息(进程ID,终止状态,运行时间等),父进程可以通过wait和waitpid函数去获取这些状态信息。

#include<sys/wait.h>
pid_t wait(int *statloc) ;
pid_t waitpid(pid_t pid,int *statloc,int options);
//均返回:成功返回进程ID,失败返回0或-1;
注意点:

wait和waitpid函数均返回两个值:已终止进程的ID,以及通过statloc指针返回的子进程终止状态。

子进程终止状态包括:正常终止,由某个信号杀死,作业控制停止

两个函数的区别是waitpid比wait提供我们更多的控制选项:

(a)当调用wait的进程没有已终止的子进程,有一个或多个子进程仍在执行,wait将阻塞到现有子进程第一个终止为止;而waitpid可以通过设置参数options来控制是否要阻塞,当options=WNOHANG时,waitpid不会在没有已终止子进程时候阻塞。

(b)waitpid的pid参数允许我们指定想等待的进程ID,值-1表示等待第一个终止的子程序。

有了wait和waitpid函数,就可以简单的编写SIGCHLD信号处理函数了

#include "unp.h"
void sig_chld(int signo)

  pid_t pid;
  int stat;
  pid = wait(&stat);
  //or pid=waitpid(-1,&stat,WNOHANG);
  return;

wait和waitpid函数存在不仅仅是为了捕获SIGCHLD信号,更是为了防止僵尸进程的产生。

来看两个概念:孤儿进程与僵尸进程

孤儿进程:故名思议,就是没了爹的进程。准确的说,在子进程终止前,父进程先一步终止,子进程没了父进程后,内核将其交由init进程,于是init进程变成了该子进程的新的父进程,这类子进程被称为孤儿进程。

僵尸进程:子进程终止后,它的父进程没有调用相应处置函数回收子进程的状态信息(进程ID,终止状态,运行时间等),该类子进程没有完全释放资源就成了僵尸进程,init进程也不会成为僵尸进程的父进程,僵尸进程就像孤魂野鬼一样存在内核中。

相比于孤儿进程,僵尸进程的危害更大。僵尸进程不仅浪费内存空间,而且会使可用的进程ID越来越少(系统中的进程ID是有限制的,僵尸进程保持自己的进程ID不放,那么该进程ID就不能被其他新进程使用)。因此,为了防止僵尸进程的产生,需要父进程调用wait和waitpid函数去获取子进程终止后的信息,让子进程彻底释放资源。

考虑这么一种情况:有多个子进程同时终止并且向父进程发送SIGCHLD信号,父进程收到SIGCHLD信号后仅仅调用一次signal函数,相当于仅执行一次sig_chld函数,如果采用上述sig_chld函数的做法,只能清理这些终止子进程中某一个进程,其他子进程就会变为僵尸进程,所以为了防止这类现象产生,应该在sig_chld使用循环

#include "unp.h"
void sig_chld(int signo)

  pid_t pid;
  int stat;
  while(pid=waitpid(-1,&stat,WNOHANG)>0)
      ...;
  return;
循环内不用wait而用waitpid的原因是,waitpid可以通过WNOHANG设置为非阻塞,而wait在子进程尚未终止前,会一直阻塞到第一个子进程终止为止,这样会浪费时间。

处理被中断的系统调用:

慢系统调用:可能永远阻塞的系统调用(调用有可能永远无法返回,比如多数网络支持函数)。当阻塞于某个系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。

例如处理accept函数:

for(;;)

  clilen = sizeof(cliaddr);
  if((connfd=accept(listenfd,(SA*)&cliaddr,&clilen))<0)
    if(errno==EINTR)
      continue;
    else
      err_sys("accept error");

重启被中断的系统调用适合:accept,read,write,select和open之类函数。不适合:connect函数。


最后总结下在网络编程过程中可能遇到的三个情况:

(1)当子进程终止时,必须捕获SIGCHLD信号;

(2)当捕获信号时,必须处理被中断的系统调用;

(3)SIGCHLD的信号处理函数必须正确编写,使用waitpid避免留下僵尸进程。

以上是关于UNP卷一学习笔记:POSIX信号处理的主要内容,如果未能解决你的问题,请参考以下文章

UNP卷一学习笔记:TCP服务器常见故障

UNP卷一学习笔记:TCP服务器常见故障

UNP卷一学习笔记:I/O模型

UNP卷一学习笔记:I/O模型

UNP卷一学习笔记:TCP状态

UNP卷一学习笔记:高级I/O函数