Linux进程间通信--信号通信之信号发送捕捉kill()、raise()、alarm()、pause()及其基础实验

来源:互联网 发布:如何修改知乎首页话题 编辑:程序博客网 时间:2024/05/22 00:06

信号概述

   ●  信号是在软件层次上对中断机制的一种模拟。在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。

   ●  信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上进程也不知道信号到底什么时候到达。

   ●  信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一个进程,而无需知道该进程的状态。如果该信号当前并未处于执行态(Running),则该信号由内核保存起来,直到该进程恢复执行再传递给它为止。如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

   ●  信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事件发生了。信号机制除了基本通知外,还可以传递附加信息。

信号来源

     信号事件发生的来源有两种:

     ① 硬件来源。如我们按下了键盘上的按钮 或者出现其他硬件故障;

     ② 软件来源。最常用发送信号的系统函数有kill()、raise()、alarm()、setitimer()和sigqueue()等,软件来源还包括一些非法运算等操作。

进程响应信号的方式

    ① 忽略信号。忽略信号即对信号不做处理,其中,有两个信号不能忽略:SIGKILL和SIGSTOP。

    ② 捕捉信号。定义信号处理函数,当信号发生时,执行响应的处理函数。

    ③ 执行默认操作。Linux对每种信号都规定了默认操作,如下表所示:

   


信号的生命周期

     一个完整的信号生命周期可以分为3个重要阶段,这3个阶段由4个重要事件来刻画的;信号产生、信号在进程中注册、信号在进程中注销、执行信号处理函数。这里信号的产生、注册、注销等是指信号的内部实现机制,而不是信号的函数实现(不受我们的掌控)。因此信号注册与否与后面讲到的发送信号函数(如 kill()等)及信号安装函数(如 signal()等)无关,只与信号值有关。

   相邻两个事件的时间间隔构成信号生命周期的一个阶段,如下图1.注意这里的信号处理有多种方式,一般是由内核完成的,当然也可以由用户进程来完成。

    

    信号的处理包括信号的发送、捕捉和处理,它们有各自相对应的常见函数:

    ●  发生信号的函数: kill()、raise()。 

    ●  捕捉信号的函数: alarm()、pause()。

    ●  处理信号的函数: signal()、sigaction()。

  本节主要讲信号的发送与捕捉,下一节再讲处理

信号发送函数 kill()和raise()

函数说明

   kill()函数同咱们的kill系统命令一样(但不能误以为kill()就是kill哈),可以发送信号给进程或进程组(实际上,kill系统命令只是kill()函数的一个用户接口)。这里需要注意的是,kill()函数不仅可以终止进程(实际上是通过发出SIGKILL信号终止),也可以向进程发送其他信号。

   与kill()函数不同的是,raise()函数允许进程向自身发送信号。

函数格式

   下表分别列出了kill()和raise()的格式

      

      


基础实验

   本实验首先使用 fork()创建了一个子进程,接着为了保证子进程不在父进程调用kill()之前退出,在子进程中使用raise()函数向自身发送 SIGSTOP信号,使子进程暂停。接下来在父进程中调用kill()向子进程发送信号,在该实验中使用的是SIGKILL。实验代码如下:

   kill_raise.c文件点此下载

     

   编译后执行的效果如下图

   

   你瞧瞧,多狠啊,都不让子进程输出第22行的话,直接啪的就给人拍那里了。

   另外,建议你去掉27行的代码再执行一遍试一试看看有什么不同。

信号捕捉函数: alarm()、pause()

函数说明

    alarm()也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送SIGALARM信号。要注意的是,一个进程只能有一个闹钟时间,如果在调用alarm()之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。

   pause()函数用于将调用进程挂起直至捕捉到信号为止。这个函数很常用,通常可以用于判断信号是否已到。

函数格式

   

    



基础实验

    本实验实际上是完成了一个简单的sleep()函数的功能,程序如下图

     

   编译执行后,结果如下图

   

  可以看到12行的语句根本就没执行,其实想想程序的执行流程就很清除,首先程序定时,执行到11行pause();时进程会被挂起,当计时到,发出信号SIGALARM,这时pause()捕捉到信号,进程直接被终止。

    现在屏蔽掉11行,如下

    

   再次编译执行,结果如下图

   

信号处理方法

   信号处理的方法主要有以下两种:

   ①  使用 signal() 函数;

   ②  使用信号集函数组。

使用signal()函数

函数说明

    使用signal()函数处理时,只需指出要处理的信号和处理函数即可。它主要用于前32种非实时信号的处理,不支持信号传递信息。Linux还支持一个更健壮更新的信号处理函数呢,它就是 sigaction(),推荐使用这个函数。

函数格式

   

    这里 signal() 这个函数的原型我当时看了头有点大,还是先说明一下:首先该函数原型整体指向一个无返回值并且带一个整形参数的函数指针,也就是信号的原始配置函数;接着该原型又带有两个参数,其中第2个参数可以是用户自定义的信号处理函数的函数指针。不明白也没事,后边做实验就明白了,会用就行了。

   

  这里要说明的是 sigaction()函数中第2个和第3个参数用到的 sigaction 结构,下表为 siaction的定义:

  

   sa_handler 是一个函数指针,指定信号处理函数,这里除可以是咱们自定义的处理函数外,还可以为SIG_DFL(采用默认的处理方式)或SIG_IGN(忽略信号)。它的处理函数只有一个参数,即信号值。

   sa_mask 是一个信号集,它可以指定在信号处理程序执行过程中哪些信号应当被屏蔽,在调用信号捕获函数前,该信号集要加入到信号的信号屏蔽字中。

   sa_flags 中包含了很多标志位,是对信号进行处理的各个选择项。它的常见可选值如下表所示:

  

基础实验1

   本实验是表明如何使用 signal()函数捕捉相应信号,并做出给定的处理。这里,my_func就是信号处理的函数指针,咱们也可以将其改为SIG_IGN或SIG_DFL查看运行结果。实验代码如下:

   signal.c文件点此下载

   

  编译运行后出现如下的结果:

  

  此时程序被挂起,一直等待信号。

  如果在键盘上按下  Ctrl+c  组合键,结果如下

   

  如果在键盘上按下  Ctrl+\  组合键,结果如下

   

  可见进程收到相应的信号后,转去执行咱们自定义函数了。你可以将my_func换为SIG_IGN或者SIG_DFL,看看有什么不同。

基础实验2

   本实验实现的功能同实验1一样,只不过是换成了sigaction()函数,程序如下

    sigaction.c文件点此下载

  

    编译运行后结果如下

   

    第一次按组合键“Ctrl+c”,结果如下

           

    第二次按组合键“Ctrl+c”,结果如下

    

   我建议你把25行的换成SA_RESTARTHAND等试一试,会加深理解的

信号集函数组

函数说明

  使用信号集函数组处理信号时设计一系列的函数,这些函数按照先后的调用次序可分为以下几大模块:创建信号集、注册信号处理函数及检测信号。

  其中,创建信号集主要用于处理用户感兴趣的一些信号,其函数包括以下几个:

  ●  sigemptyset(): 将信号集初始化为空

  ●  sigfillset(): 将信号集初始化为包含所有已定义的信号集

  ●  sigaddset(): 将指定信号加入到信号集中

  ●  sigdelset(): 将指定信号从信号集中删除

  ●  sigismember(): 查询指定信号是否在信号集中

   注册信号处理函数主要用于决定进程如何处理信号。这里要注意的是,信号集里的信号并不是真正可以处理的信号,只有当信号的状态处于非阻塞状态时才会真正起作用。因此,首先使用 sigprocmask() 函数检测并更改信号屏蔽字(信号屏蔽字是用来指定当前被阻塞的一组信号,它们不会被进程接收),然后使用 sigaction()函数来定义进程接收到特定信号后的行为。

   检测信号是信号处理的后续步骤,因为被阻塞的信号不会传递给进程,所以这些信号就处于“未处理”状态(也就是进程不清除它的存在)。sigaction()函数允许进程检测“未处理”信号,并进一步决定对它们做何处理。

函数格式

    

   

    在sigprocmask()中,若set 是一个非空指针,则参数 how 表示函数的操作方式;若how为空,则表示忽略此操作。

    

    总之,在处理信号时,一般遵循下图所示的操作流程

   

基础实验3(强烈建议做这个实验啊)

   该实验首先把 SIGQUIT、SIGINT两个信号加入信号集,然后将该信号集设为阻塞状态,并进入用户输入状态。咱们只需要按任意键,就可以将信号集设置为非阻塞状态,再对这两个信号分别操作,其中SIGQUIT执行默认操作,而SIGINT执行用户自定义函数的操作。

   sigset.c文件点此下载  

   

   

   

   编译运行结果如下

   

   按任意键

   

  接着按组合键:Ctrl+c

  

  接着按组合键:Ctrl+\

   

  如果在运行时,先直接按组合键:Ctrl+\,结果如下

  

 对比结果可以看到,在新号处于阻塞状态时,所发出的信号对进程不起作用,并且该信号进入待处理状态。按下任意键,信号脱离了阻塞状态时,咱们发出的信号才能正常运行。这里SIGINT已经按照咱们自定义的函数运行。


转自请注明出处:http://blog.csdn.net/mybelief321/article/details/9079145

原创粉丝点击