APUE--信号1(自己的补充)

信号是软件中断。信号提供了一种处理一步事件的方法。

1、信号的概念

产生信号的条件:

(1)当用户按某些终端键时,引发终端缠身的信号。(如ctrl+c通常产生中断信号(SIGINT)  ctrl+\通常产生中断信号(SIGQUIT))。

(2)硬件异常产生信号。除数为0、无效的内存引用等等。这些条件通常由硬件检测到,并将其通知内核,然后内核为该条件发生时正在运行的进程产生适当的信号。

  (3)进程调用kill(2)函数可将信号发送给另一个进程或进程组。

    (4) 用户可用kill(1)命令将信号发送给其他进程。(常用此命令终止一个失控的后台进程)。

    (5)当检测到某种软件条件已经发生,并应将其通知有关进程是也将发生信号。L例如SIGURG(在网络连接上传来带外数据时产生)、SIGALRM(进程所设置的闹钟时钟超时时产生)。

信号的处理:

当一个信号出现时,要求内核对该信号进行处理。

(1)忽略此信号。大多数的信号都可以使用这种处理方式,但SIGKILL和SIGSTOP这两种信号是绝不能被忽略的。

(2)捕捉信号。内核调用用户自定义函数来处理这种信号(这种行为称为注册一个信号处理函数)。

(3)执行系统默认动作(APUE表10-1)。

2、signal函数

typedef void (*sighandler_t)(int);  

sighandler_t  signal(int  signo, sighandler_t handler); 

先从参数看起,第一个参数是一个整型的信号编号,是为哪一个信号设置处理动作;第一个参数的类型是一个指向参数为int无返回值的函数的函数指针;这个就是我们要为signo信号注册动作,三种处理方式分别对应的是SIG_DFL(表示接到此信号后的动作是系统默认动作) SIG_IGN(向内核表示忽略此信号) 和 一个自定义的函数地址(在信号发生时调用此函数,称为这种处理为“捕捉”该信号)。这样我们就改变了一个信号的处理动作,再看返回值,类型是一个相同类型的函数指针,可以很容易推测出应该是signo信号更改之前的处理动作,也就是说可以通过返回值来保存更改之前的动作,当然如果函数执行失败,讲返回SIG_ERR。

这里需要注意的是,signal()仅仅是一个注册功能的函数,除了注册意外不做任何其他的事情,注册后,当信号产生时进程会自动执行注册的动作。整个过程是这样的:当信号到来时,程序从执行位置暂停,去执行该信号的处理动作,处理动作执行完毕后(如果动作不会使进程退出)返回暂停位置继续运行。

信号的产生条件之前说过,那如何让进程等待一个信号呢?可以使用sleep()函数,但问题是在sleep()时如果到来一个信号,sleep()函数就会被打断并返回剩余的时间,解决方法是使用循环,当sleep()的返回值为0时跳出循环 while ( (t=sleep(t)) != 0 ) 。当然这样做也无法做到让程序一直等待,替换函数式pause(),查看这个函数的帮助文档可以看到这个函数式专门用来等待一个信号的。

下面的几点需要清楚:

(1).多个信号是可以注册相同的处理函数,处理函数的整型参数就是引起该函数执行的信号编号。

(2).在信号处理函数执行过程中,接收到另一个信号,则仍会中断去执行另一个信号的处理动作,执行后回来继续执行。

(3).在信号处理函数执行过程中,再次接受到相同的信号,并不会立刻去执行,而是信号处理函数执行结束后,再次执行处理函数。

(4).在信号处理函数执行过程中,N次接受到相同的信号,在信号处理函数执行结束后情况分为两类:如果是非实时信号则只再执行一遍处理函数,如果是实时信号则执行N次处理函数。(实时信号讲会在后面详细介绍)

(5).子进程会继承父进程的信号处理方式,之后父子进程中一个改变信号的处理方式不再影响另一个

(6).来自终端的信号(如ctrl+c)将会发送给前台进程组中的所有进程

(7).执行exec()后,信号处理动作不会被新的程序所进程,所有的信号的处理动作恢复为默认。(地址空间变了)


3、SIGCHLD信号,

进程中说到过子进程退出后需要父进程对其收尸,父进程就是通过子进程在退出时向父进程发送的SIGCHLD信号得知子进程已经退出(wait()接触阻塞)。那思考一个问题,在进程部分编写程序时,如果想收尸就需要使用wait()函数,但问题时 wait()会使进程阻塞而不能做其他事情,解决办法就是在SIGCHLD信号上做些文章,我们可以将wait()函数放到SIGCHLD的信号处理函数中,这样当一个子进程退出时父进程就会自动执行wait(),而不用再wait()处阻塞着以至于父进程无法去做别的事情。这样做很简单呢,但心细的同学还会发现一个问题是:如果说SIGCHLD的信号处理函数在执行过程中如果又接受到了2个SIGCHLD(又有两个子进程退出),那只会再执行一遍信号处理函数(即只执行一遍wait()),这样并不能保证所有子进程都会被收尸。既然wait()不行我们就想到了waitpid()这个函数,这个函数提供了很多wait()做不到的方法,还记得WNOHANG参数么,提供了非阻塞版本。那好先回过来再思考下出现的问题,在执行一次处理函数的过程有很多子进程需要收尸(但有一些SIGCHLD已经丢失),既然是这样,我们就用waitpid()循环收尸将这些尸体全部收了,并且WNOHANG可以使waitpid()非阻塞,这样在收尸完毕后waitpid()会返回0,我们就可以根据这个条件退出循环了。

[cpp]  view plain copy
void  sig_chld( int  signo)  
  1. {  
  2. <span style="white-space:pre">  </span>while (waitpid(0, NULL, WNOHANG)>0) {  
  3. <span style="white-space:pre">      </span>;  
  4. <span style="white-space:pre">  </span>}  
  5. }  
我们来总结下,避免出现僵尸进程的三种方法:

(1).使用wait() waitpid()这样的函数来手动收尸。包括上面提到的waitpid()放到处理函数中。

(2).更改SIGCHLD信号的处理方式为忽略,这样就不会产生僵尸进程。还可以在sigaction()中设置SA_NOCLDWAIT标志位。

(3).利用两次fork()的方法,子进程在第二次fork()后立即退出,这样孙进程就成为了孤儿进程,会由init进程自动收尸。

  4、可重入函数

这个概念是针对信号的(线程安全是针对线程的,容易弄混)。函数A出现在信号处理中时,如果处理函数为结束,又被打断去执行另一个处理函数,该处理函数中也调用了函数A,如果不会出现问题,则该函数是可重入的。不可重入的函数特点,使用了全局变量、静态变量等,如getpwnam()这样的函数返回一个静态字符串的地址就是不可重入的,这样的函数往往提供了可重入版本getpwname_r()



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值