现在的位置: 首页 > 综合 > 正文

IPC–信号(1)–基本知识

2014年02月18日 ⁄ 综合 ⁄ 共 5821字 ⁄ 字号 评论关闭

信号本质

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

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

信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。

信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。

信号来源

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

硬件来源(比如我们按下了键盘或者其它硬件故障);

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

信号种类

    第一种:

    可靠信号与不可靠信号

    1. 信号值小于SIGRTMIN (Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63) 的信号都是不可靠信号;

    2. 不可靠信号的问题(主要只信号可能丢失):

       A. 进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,

           那么就要在信号   处理函数结尾再一次调用signal(),重新安装该信号。( 已经改善)

       B. 信号可能丢失,后面将对此详细阐述。

    3. 可靠信号支持排队,不会丢失。

    第二种:

    实时信号与非实时信号:

    1. 非实时信号都不支持排队,都是不可靠信号;

    2. 实时信号都支持排队,都是可靠信号。

 

进程对信号的响应

     1. 忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及 SIGSTOP;

     2. 执行缺省操作,Linux对每种信号都规定了默认操作。注意,进程对实时信号的缺省反应是进程终止。

     3. 捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;

信号阻塞与信号信号的未决

信号的“未决”是一种状态,指的是从信 号的产生到信号被处理前的这一段时间;
信号的“阻塞”是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

伪demo:

 进程捕获信号过程

1. 用户输入命令,在Shell下启动一个前台进程。
2. 用户按下Ctrl-C,这个键盘输入产生一个硬件中断。
3. 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断。
4. 终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。
5. 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

  内核如何实现信号的捕捉

    如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。

由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:
1. 用户程序注册了SIGQUIT信号的处理函数sighandler。
2. 当前正在执行main函数,这时发生中断或异常切换到内核态。
3. 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
4. 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
5. sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。
6. 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

 如下图:

信号在内核中的表示(refer to :linux c 一站式学习)

以上我们讨论了信号产生(Generation)的各种原因,而实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:

图 33.1. 信号在内核中的表示示意图

每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,
1. SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
2. SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
3. SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。阻塞信号集也叫做当前进程的信号屏蔽字(Signal
Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

3.2. 信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。

函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。注意,在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

3.3. sigprocmask
调用函数sigprocmask可以读取或更改进程的信号屏蔽字。

返回值:若成功则为0,若出错则为-1
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

表 33.1. how参数的含义

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
3.4. sigpending

sigpending读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。 

下面用刚学的几个函数做个实验。程序如下:

程序运行时,每秒钟把各信号的未决状态打印一遍,由于我们阻塞了SIGINT信号,按Ctrl-C将会使SIGINT信号处于未决状态,按Ctrl-\仍然可以终止程序,因为SIGQUIT信号没有阻塞。

sigsuspend 函数

sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。

注意:

sigsuspend的整个原子操作过程为:
(1) 设置新的mask阻塞当前进程,有些地方的讲解说恢复原先的mask在第三步
(2) 收到信号,恢复原先mask;执行,实际上按照输出结果,恢复原先的mask
(3) 调用该进程设置 的信号处理函数,应该在调用进程的信号处理函数之前进行。请注意
(4) 待信号处理函数返回后,sigsuspend 返回。
 

实例:

功能描述:
sigsuspend 函数将进程的信号屏蔽字设置为 sigmask 指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,在返回之前,将进程的信号屏蔽字设置为调用sigsuspend之前的值。

用 法:
# include < signal . h>
int sigsuspend ( const sigset_t * sigmask) ;

参 数:
sigmask:  指向信号集的指针,里面设置了屏蔽的信号

返回说明:
函数没有成功返回值。如果它返回到调用者,则总是返回-1,并将 errno 设置为EINTR(表示一个被中断的系统调用)。

demo:


  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <signal.h>  
  4. volatile sig_atomic_t quitflag; /* set nonzero by signal handler */  
  5.   
  6.  /* one signal handler for SIGINT and SIGQUIT */  
  7. static void sig_handler(int signo)  
  8. {  
  9.     if (signo == SIGINT)  
  10.                 printf("ninterruptn");  
  11.         else if (signo == SIGQUIT)  
  12.                 quitflag = 1; /* set flag for main loop */  
  13. int main(int argc, char *argv[])  
  14. {  
  15.         sigset_t newmask, oldmask, zeromask;  
  16.         if (signal(SIGINT, sig_handler) == SIG_ERR)  
  17.                 perror("signal(SIGINT) error");  
  18.         if (signal(SIGQUIT, sig_handler) == SIG_ERR)  
  19.                 perror("signal(SIGQUIT) error");  
  20.         sigemptyset(&newmask);  
  21.         sigemptyset(&zeromask);  
  22.         sigaddset(&newmask, SIGQUIT);  
  23.         /* Block SIGQUIT and save current signal mask */  
  24.         if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) != 0)  
  25.                 perror("sigprocmask SIG_BLOCK error");  
  26.         //while(1);   
  27.         while (quitflag == 0)  
  28.     {
  29.        printf(“start sigsuspend…\n”);
  30.                 sigsuspend(&zeromask);  
  31.        printf(“end sigsuspend…\n”);
  32.     }
  33.         quitflag = 0;  
  34.         /* Reset signal mask which unblocks SIGQUIT */  
  35.         if (sigprocmask(SIG_SETMASK, &oldmask, NULL) != 0)  
  36.                 perror("sigprocmask SIG_SETMASK error");  
  37.         exit(0);  
  38. }  

我们运行程序(注释while(1);版本):

输入: ctrl+c   :    输出 :  interrept

输入: ctrl+c   :    输出 :  interrept

输入: ctrl+c   :    输出 :  interrept

输入: ctrl+c   :    输出 :  interrept

输入: ctrl+\         程序终止

接着我们把程序中的while(1); 解除注释,运行程序

输入: ctrl+c   :    输出 :  interrept

输入: ctrl+c   :    输出 :  interrept

输入: ctrl+c   :    输出 :  interrept

输入: ctrl+c   :    输出 :  interrept

输入: ctrl+\         没有反应

分析:

程序停在while(1)的时候,这里屏蔽了sigquit的信号,而程序在sigsuspend阻塞时候,把屏蔽信号设置成zeromask,不阻塞任何信号,所以能接受到ctrl+\

抱歉!评论已关闭.