进程之信号通信

信号通信

1、信号的产生:

信号是进程间通信中最为长久的方式,很多条件可以产生信号,例如:

① 当用户按某些按键时,产生信号
② 硬件异常产生信号:除数为0、无效的存储访问等等。这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,例如,内核对正访问一个无效存储区的进程产生一个SIGSEGV信号
③ 进程用kill函数将信号发送给另一个进程
④ 用户可用kill命令将信号发送给其他进程

2、常见的几种信号

SIGHUP: 从终端上发出的结束信号

SIGINT: 来自键盘的中断信号(Ctrl-C)

SIGKILL:该信号结束接收信号的进程,杀死进程

SIGTERM:kill 命令发出的信号

SIGCHLD:子进程停止或结束时通知父进程

SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号,暂停进程

3(内核对信号的一般处理方法)

内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。这里要补充的是,如果信号发送给一个正在睡眠的进程,那么要看该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。这一点比较重要,因为进程检查是否收到信号的实际是:一个进程在即将从内核态返回到用户态时,或者在一个进程要进入或离开一个适当的低调度优先级睡眠状态时。
内核处理一个进程收到的信号实际是在一个进程从内核态返回用户态时,所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只用处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。内 核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。前面介绍概念的时候讲过,处理信号有三种类型:进程接收到信号后退出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似的继续运行。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创 建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权 限)。
在信号的处理方法中有几点特别要引起注意。第一,在一些系统中,当一个进程处理完中断信号返回用户态之前,内核清除用户区中设 定的对该信号的处理例程的地址,即下一次进程对该信号的处理方法又改为默认值,除非在下一次信号到来之前再次使用signal系统调用。这可能会使得进程 在调用signal之前又得到该信号而导致退出。在BSD中,内核不再清除该地址。但不清除该地址可能使得进程因为过多过快的得到某个信号而导致堆栈溢 出。为了避免出现上述情况。在BSD系统中,内核模拟了对硬件中断的处理方法,即在处理某个中断时,阻止接收新的该类中断。 
第二个要 引起注意的是,如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上,这时该信号引起进程作一次longjmp,跳出睡眠 状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回一样,但返回了一个错误代码,指出该次系统调用曾经被中断。这要注意的是,BSD系统中内核可以自动地重新开始系统调用。 
第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不做longjmp,一般是继续睡眠。但用户感觉不到进程曾经被唤醒,而是象没有发生过该信号一样。 
第 四个要注意的地方:内核对子进程终止(SIGCLD)信号的处理方法与其他信号有所区别。当进程检查出收到了一个子进程终止的信号时,缺省情况下,该进程就象没有收到该信号似的,如果父进程执行了系统调用wait,进程将从系统调用wait中醒来并返回wait调用,执行一系列wait调用的后续操作(找 出僵死的子进程,释放子进程的进程表项),然后从wait中返回。SIGCLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果该进程捕捉了这个信号,就象普通信号处理一样转到处理例程。如果进程忽略该信号,那么系统调用wait的动作就有所不同,因为SIGCLD的作用仅仅是唤醒一个睡眠在可被 中断优先级上的进程,那么执行wait调用的父进程被唤醒继续执行wait调用的后续操作,然后等待其他的子进程。 
如果一个进程调用signal系统调用,并设置了SIGCLD的处理方法,并且该进程有子进程处于僵死状态,则内核将向该进程发一个SIGCLD信号。

                                  

4、信号的处理方式:

① 忽略此信号
大多数信号都按照这种方式进行处理,但有两种信号决不能被忽略,它们是:SIGKILL\SIGSTOP。
这两种信号不能被忽略的原因是:它们向超级用户提供了一种终止或停止进程的方法
② 执行用户希望的动作
通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理
③ 执行系统默认动作

对大多数信号的系统默认动作是终止该进程

 

5、信号的发送方式

(1)用kill函数发送信号给指定进程

函数原型:int  kill(pid_t  pid,  int  sig);

头文件:#include <sys/types.h>

               #include <signal.h>

参数:

pid:

① pid > 0,发送信号给进程号指定为pid的进程

② pid = 0,发送信号给和当前进程号的进程同组的所有进程

③ pid = -1,广播信号给同系统内的所有进程

④ pid < 0,发送信号给进程组ID为pid绝对值的所有进程

sig:相应的信号

返回值:执行成功,返回0;执行失败,返回-1。

 

(2)用raise函数发送信号给自身

函数原型:int  raise(int  sig);

头文件:#include <signal.h>

参数:sig:相应的信号

返回值:执行成功,返回0;执行失败,返回-1。

7、其他信号
(1)alarm函数
使用alarm函数可以设置一个时间值(闹钟时间),当所设置的时间到了,产生SIGALRM信号。如果不捕捉此信号,则默认动作是终止该进程
函数原型:unsigned  int  alarm(unsigned  int  seconds);
头文件:#include <unistd.h>
参数:设置的时间秒数
返回值:返回之前闹钟的剩余秒数,如果之前未设闹钟则返回0.
注意:每个进程只能有一个闹钟时间.如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,以前登记的闹钟时间则被新值代换。
如果有以前登记的尚未超过的闹钟时间,而这次seconds值是0,则表示取消以前的闹钟。

(2)pause函数
作用:使调用进程挂起直至捕捉到一个信号
函数原型:int  pause(void);
头文件:#include<unistd.h>
返回值:只返回-1.
 
8、信号的处理实现
当系统捕捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式
(1)signal函数
函数原型:void (*signal(int  signum,void(*handler)(int)))(int);
简化原型:typedef void (*sighandler_t)(int)    sighandler_t;
                    原型变为: sighandler_t signal(int signum, sighandler_t handler);
作用:依照参数signum指定的信号编号来设置该信号的处理函数,当指定的信号到达时就跳转到参数handler指定的函数执行
头文件:#include <signal.h>
返回值:返回先前信号处理函数指针。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值