信号概述
信号是在软件层次上怼中断机制的一种模拟。原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的:一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号什么时候到达。进程之间可以互相通过信号相互交流。内核进程也可以利用它来通知用户空间进程发生了那些系统事件。它可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程当前并未处于执行状态,则该信号就有内核保存起来,直到该进程恢复工作在传递给它;如果一个信号被进程设置为阻塞,这该信号的传递被延迟,直到其阻塞被取消时才被进程。信号是进程间通信机制中唯一的异步通信机制。
信号列表
信号一共有64种:
列表中,编号为1 ~31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~63的信号是后来扩充的,称做可靠信号(实时信号) 。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会 。
-
1) SIGHUP
本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联 。
登录Linux时,系统会分配给登录用户一个终端(Session) 。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个 Session 。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号 。这个信号的默认操作为终止进程,因此前台进 程组和后台有终端输出的进程就会中止 。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也 能继续下载 。
此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件 。 -
2) SIGINT
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程 。 -
3) SIGQUIT
和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号 。 -
4) SIGILL
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号 。 -
5) SIGTRAP
由断点指令或其它trap指令产生. 由debugger使用 。 -
6) SIGABRT
调用abort函数生成的信号 。 -
7) SIGBUS
非法地址, 包括内存地址对齐(alignment)出错 。比如访问一个四个字长的整数, 但其地址不是4的倍数 。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间) 。 -
8) SIGFPE
在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误 。 -
9) SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略 。如果管理员发现某个进程终止不了,可尝试发送这个信号 -
10) SIGUSR1
用户自定义留给用户使用 -
11) SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据. -
12) SIGUSR2
用户自定义留给用户使用 -
13) SIGPIPE
管道破裂 。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号 。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止 。 -
14) SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号. -
15) SIGTERM
程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理 。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号 。如果进程终止不了,我们才会尝试SIGKILL 。 -
17) SIGCHLD
子进程结束时, 父进程会收到这个信号 。如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程 。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管) 。 -
18) SIGCONT
让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符 -
19) SIGSTOP
停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略. -
20) SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号 -
21) SIGTTIN
当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行. -
22) SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到. -
23) SIGURG
有紧急数据或out-of-band数据到达socket时产生. -
24) SIGXCPU
超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变 。 -
25) SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制 。 -
26) SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间. -
27) SIGPROF
类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间. -
28) SIGWINCH
窗口大小改变时发出. -
29) SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作. -
30) SIGPWR
Power failure -
31) SIGSYS
非法的系统调用 。
注意:
在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
不能恢复至默认动作的信号有:SIGILL,SIGTRAP
默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1
SIGUSR2,SIGVTALRM
默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞 。
信号分类
可靠信号与不可靠信号
Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是信号可能丢失。
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
可靠信号与不可靠信号的区别:
- 这里的不可靠主要是不支持信号队列,就是当多个信号发生在进程中的时候(收到信号的速度超过进程处理的速度的时候),这些没来的及处理的信号就会被丢掉,仅仅留下一个信号。
- 可靠信号是多个信号发送到进程的时候(收到信号的速度超过进程处理信号的速度的时候),这些没来的及处理的信号就会排入进程的队列。等进程有机会来处理的时候,依次再处理,信号不丢失。
实时信号与非实时信号
早期Unix系统只定义了32种信号,前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
信号的处理流程
总共分为三个阶段:
- 信号诞生
- 信号在进程中注册
- 信号在进程中的注销
- 信号处理函数执行
信号诞生
信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。
这里按发出信号的原因简单分类,以了解各种信号:
(1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。
(2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。
(3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。
(4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。
(5) 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。
(6) 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。
(7) 跟踪进程执行的信号。
信号注册与安装
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可。
进程的task_struct结构中有关于本进程中未决信号的数据成员: struct sigpending pending:
struct sigpending{
struct sigqueue *head, *tail;
sigset_t signal;
};
head:指向的是sigqueue类型的链表头
taill:指向的是sigqueue类型的链表尾
signal:是进程中所有未决信号集
struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
next:指向下一个sigqueue类型结构体地址
info:信号所携带的信息
信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册)。
当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册(通过sigset_t signal指示),则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。
总之信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)
信号安装相关函数:
如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作
signal:注册信号
所需头文件 | #include<signal.h> | |
函数原型 | typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); |
|
函数传入值 | signum:指定信号代码 | |
handler | SIG_IGN:忽略该信号 | |
SIG_DFL:采用系统默认方式处理该信号 | ||
自定义信号函数 | ||
返回值 | 成功:以前的的信号处理函数 | |
错误: -1 |
sigaction:注册信号
所需头文件 | #include<signal.h> | |
函数原型 | int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); |
|
函数传入值 | signum:指定信号类型,除SIGKILL及SIGSTOP之外的任何一个信号 | |
act:指向sigaction结构体的指针,包含对特定信号的处理 | ||
oldact:保存信号原来的处理方式 | ||
返回值 | 成功:0 | |
错误: -1 |
struct sigaction定义:
struct sigaction{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flag;
void (*sa_sigaction)(int,siginfo_t*,void*);
};
sa_handler:字段包含一个信号捕捉函数的地址
sa_mask:字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加进进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。
sa_flag:包含许多标志位,都是和信号处理相关的选项,常见的有:
SA_NODEFER: 当信号处理函数正在进行时,不堵塞对于信号处理函数自身信号功能。
SA_RESETHAND: 当用户注册的信号处理函数被执行过一次后,该信号的处理函数被设为系统默认的处理函数。
SA_SIGINFO: 提供附加信息,一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针
最后一个参数是一个替代的信号处理程序,当设置SA_SIGINFO时才会用他。
信号的执行和注销
内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。当其由于被信号唤醒或者正常调度重新获得CPU时,在其从内核空间返回到用户空间时会检测是否有信号等待处理。如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。
对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完毕)。否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号。
当所有未被屏蔽的信号都处理完毕后,即可返回用户空间。对于被屏蔽的信号,当取消屏蔽后,在返回到用户空间时会再次执行上述检查处理的一套流程。
内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。
处理信号有三种类型:进程接收到信号后退出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似的继续运行。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)。
信号发送
kill:函数和kil系统命令一样,可以发送信号给进程或进程组
所需头文件 | #include<signal.h> #include<sys/types.h> |
|
函数原型 | int kill(pid_t pid ,int sig) | |
函数传入值 | pid | 正数:发送信号给进程号为pid的进程 |
0:信号发送到所有和当前进程在同一个进程组的进程 | ||
-1:信号发给所有的进程表中进程(除了进程号最大的进程) | ||
sig:信号类型 | ||
返回值 | 成功:0 | |
错误: -1 |
sigqueue:是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。
注意:
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
所需头文件 | #include <sys/types.h> #include <signal.h> |
函数原型 | int sigqueue(pid_t pid, int sig, const union sigval val) |
函数传入值 | pid:接受进程ID 只能是单个进程不能是进程组 |
sig:要发送的信号 | |
val:联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。 | |
返回值 | 成功:0 |
错误: -1 |
raise:只允许给自身发送信号
所需头文件 | #include <unistd.h> |
函数原型 | unsigned int alarm(unsigned int seconds) |
函数传入值 | sig:指定信号类型,除SIGKILL及SIGSTOP之外的任何一个信号 |
返回值 | 成功:0 |
错误: -1 |
pause:用于将调用的进程挂起直到收到信号为止
所需头文件 | #include <unistd.h> |
函数原型 | int pause(void); |
函数返回值 | -1,并且把errno 值设为EINTR |
alarm:称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。可以设置忽略或者不捕获此信号,如果采用默认方式其动作是终止调用该alarm函数的进程。
所需头文件 | #include <unistd.h> |
函数原型 | int getitimer(int which, struct itimerval *value); |
函数传入值 | seconds:指定秒数,系统经过seconds秒之后向该进程发送SIGALRM信号 |
返回值 | 成功:如果调用此alarm()之前,进程中已经设置了闹钟时间,则返回到上一个闹钟剩余的时间,否则0 |
错误: -1 |
现在的系统中很多程序不再使用alarm调用,而是使用setitimer调用来设置定时器,用getitimer来得到定时器的状态
所用结构体:
struct itimerval {
struct timeval it_interval; /* 计时间隔*/
struct timeval it_value; /* 延时时长 */
};
it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。
struct timeval {
time_t tv_sec; /* 秒*/
suseconds_t tv_usec; /* 微妙*/
};
setitimer:调设置定时器,settimer工作机制是,先对it_value倒计时,当it_value为零时触发信号,然后重置为it_interval,继续对it_value倒计时,一直这样循环下去。
所需头文件 | #include <sys/time.h> | |
函数原型 | int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); |
|
函数传入值 | which:为定时器类型,setitimer支持3种类型的定时器: | TIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。 |
ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送SIGVTALRM信号给进程。 | ||
ITIMER_PROF:当进程执行时和系统为该进程执行动作时都计时。与ITIMER_VIR-TUAL是一对,该定时器经常用来统计进程在用户态和内核态花费的时间。计时到达将发送SIGPROF信号给进程。 | ||
new_value | 用来对计时器进行设置 | |
old_value | 它是用来存储上一次setitimer调用时设置的new_value值通常设为空 | |
返回值 | 成功:0 | |
错误: -1 |
getitimer:用计时器的当前值填写value指向的结构体。
所需头文件 | #include <sys/time.h> | |
函数原型 | int getitimer(int which, struct itimerval *value); | |
函数传入值 | which:为定时器类型,setitimer支持3种类型的定时器: | TIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。 |
ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送SIGVTALRM信号给进程。 | ||
ITIMER_PROF:当进程执行时和系统为该进程执行动作时都计时。与ITIMER_VIR-TUAL是一对,该定时器经常用来统计进程在用户态和内核态花费的时间。计时到达将发送SIGPROF信号给进程。 | ||
value | 设置为当前定时器的值 | |
返回值 | 成功:0 | |
错误:-1 |
两个函数返回的错误代码:
EFAULT:参数value或ovalue是无效的指针。
EINVAL:参数which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一个。
信号集
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t
信号集用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:
头文件 #include <signal.h>
int sigemptyset(sigset_t *set);
初始化由set指定的信号集,信号集里面的所有信号被清空;
int sigfillset(sigset_t *set);
调用该函数后,set指向的信号集中将包含linux支持的64种信号;
int sigaddset(sigset_t *set, int signum)
在set指向的信号集中加入signum信号;
int sigdelset(sigset_t *set, int signum);
在set指向的信号集中删除signum信号;
int sigismember(const sigset_t *set, int signum);
判定信号signum是否在set指向的信号集中。
int sigaction( int sig, const struct sigaction *act,struct sigaction *oact );
检查、修改和指定信号相关联的信号响应。
信号阻塞于信号未决
阻塞信号:
信号的阻塞就是让系统暂时保留信号待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。
当需要修改某些全局变量时,可以通过sigprocmask()函数阻塞处理函数中也使用该变量的信号。
在某些信号处理函数中,为了防止同类信号的到来,可以使用sigaction()函数的sa_mask阻塞特定的信号。
作用:
使用函数sigprocmask()阻塞信号的传递,只是延迟信号的到达。信号会在解除阻塞后继续传递。这种情况往往需要在信号程序和其它程序共享全局变量时,如果全局变量的类型不是sig_atomic_t类型,当一部分程序恰好读、写到变量过程中,产生某个信号,而信号程序里会改变该变量,那么就会产生混乱。为了避免这种混乱,提供程序的可靠性,你必须在操作这类变量前阻塞信号,操作完成后恢复信号的传递。
信号未决:
执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));
int sigpending(sigset_t *set));
int sigsuspend(const sigset_t *mask));
sigprocmask()函数能够根据参数how来实现对信号集的操作,操作主要有三种:
SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号
SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞
SIG_SETMASK 更新进程阻塞信号集为set指向的信号集
sigpending(sigset_t *set))获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。
sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂
停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程
将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。
实例
司机,管理台,售票处
司机收到售票处发送SIGUSR1信息 表示接受到一名乘客 当乘客满30人的时候发送给管理台SIGINT回复一个消息表示可以发车并且发送消息给售票处停止售票然后将信号发送给管理台,售票给司机发送消息和处理管理台的消息
head.h
#include<stdio.h>
#include<string.h>
#include<time.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<sys/time.h>
control.c
#include"head.h"
pid_t ser;
int sig_fid(int op)
{
if(op==1)
{
return SIGINT;
}
else if(op==2)
{
return SIGQUIT;
}
else if(op==3)
{
return SIGUSR1;
}
else if(op==4)
{
return SIGALRM;
}
else
{
return -1;
}
}
int main(int argc,char *argv[])
{
if(argc<2)
{
perror("argc is not engouht\n");
exit(-1);
}
ser=(pid_t)atoi(argv[1]);
char buf[1024];
puts("please chosse this operation and add your number\n");
puts("1 : go");
puts("2 : stop");
puts("3 : buy a ticket");
puts("4 : quit");
int op,sig,num;
union sigval mysigval;
while(1)
{
scanf("%d",&op);
sig=sig_fid(op);
if(sig==-1)
{
puts("please chosse right operation\n");
continue;
}
else if(sig==SIGALRM)
{
if(sigqueue(ser,sig,mysigval) == -1)
{
perror("sigqueue error");
exit(EXIT_FAILURE);
}
puts("quit");
sleep(2);
exit(0);
}
else
{
puts("please write you numbers:");
scanf("%d",&num);
mysigval.sival_int = num;
if(sigqueue(ser,sig,mysigval) == -1)
{
perror("sigqueue error");
exit(EXIT_FAILURE);
}
puts("ok");
}
}
return 0;
}
service.c
#include"head.h"
pid_t der;
void sig_fun(int signum,siginfo_t *info,void *myact)
{
printf("recve a sig %d\n", signum);
switch(signum)
{
case SIGINT:
{
if(sigqueue(der,SIGINT,info->si_value)==-1)
{
printf("send error\n");
exit(0);
}
puts("send to driver go");
break;
}
case SIGUSR1:
{
if(sigqueue(der,SIGUSR1,info->si_value)==-1)
{
printf("send error\n");
exit(0);
}
printf("name:%d passenger bought a ticket\n",info->si_value.sival_int);
break;
}
case SIGUSR2:
{
printf("please get off buss\n");
break;
}
case SIGQUIT:
{
if(sigqueue(der,SIGUSR2,info->si_value)==-1)
{
printf("send error\n");
exit(0);
}
puts("send to driver stop");
break;
}
case SIGALRM:
{
if(sigqueue(der,SIGALRM,info->si_value)==-1)
{
printf("send error\n");
exit(0);
}
puts("quit");
sleep(2);
exit(0);
break;
}
deafult:
break;
}
return ;
}
void sig_res()
{
struct sigaction act;//
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=sig_fun;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGINT);
sigaddset(&act.sa_mask, SIGUSR1);
sigaddset(&act.sa_mask, SIGUSR2);
sigaddset(&act.sa_mask, SIGQUIT);
sigaddset(&act.sa_mask, SIGALRM);
if(sigaction(SIGALRM,&act,NULL)<0)
{
perror("install SIGALRM is error");
exit(-1);
}
if(sigaction(SIGINT,&act,NULL)<0)
{
perror("install SIGINT is error");
exit(-1);
}
if(sigaction(SIGUSR1,&act,NULL)<0)
{
perror("install SIGUSR1 is error");
exit(-1);
}
if(sigaction(SIGUSR2,&act,NULL)<0)
{
perror("install SIGUSR2 is error");
exit(-1);
}
if(sigaction(SIGQUIT,&act,NULL)<0)
{
perror("install SIGQUIT is error");
exit(-1);
}
return ;
}
int main(int argc,char*argv[])
{
if(argc<2)
{
perror("argc is not engouht\n");
exit(-1);
}
der=(pid_t)atoi(argv[1]);
sig_res();
printf("this pid is %d\n",getpid());
while(1){}
return 0;
}
driver.c
#include"head.h"
pid_t ser;
int sum=1;
void sig_fun(int signum,siginfo_t *info,void *myact)
{
printf("recve a sig %d\n", signum);
switch(signum)
{
case SIGUSR1:
{
printf("the %d Passenger %d has boarded\n",sum++,info->si_value.sival_int);
if(sum%30==0)
{
if(sigqueue(ser,SIGUSR2,info->si_value)==-1)
{
printf("send error\n");
exit(0);
}
}
break;
}
case SIGUSR2:
{
printf("stop the bus\n");
break;
}
case SIGINT:
{
printf("Passenger %d sed go go\n",info->si_value.sival_int);
break;
}
case SIGALRM:
{
puts("quit");
sleep(2);
exit(0);
break;
}
deafult:
break;
}
return ;
}
void sig_res()
{
struct sigaction act;//
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=sig_fun;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGUSR1);
sigaddset(&act.sa_mask, SIGUSR2);
sigaddset(&act.sa_mask, SIGTSTP);
sigaddset(&act.sa_mask, SIGINT);
sigaddset(&act.sa_mask, SIGALRM);
if(sigaction(SIGUSR1,&act,NULL)<0)
{
perror("install SIGUSR1 is error");
exit(-1);
}
if(sigaction(SIGUSR2,&act,NULL)<0)
{
perror("install SIGUSR1 is error");
exit(-1);
}
if(sigaction(SIGTSTP,&act,NULL)<0)
{
perror("install SIGTSTP is error");
exit(-1);
}
if(sigaction(SIGINT,&act,NULL)<0)
{
perror("install SIGINT is error");
exit(-1);
}
if(sigaction(SIGALRM,&act,NULL)<0)
{
perror("install SIGINT is error");
exit(-1);
}
return ;
}
int main(int argc,char*argv[])
{
sig_res();
printf("this pid is %d\n piealse write service pid:",getpid());
scanf("%d",&ser);
while(1) {}
return 0;
}
makefile
SOURCE = $(wildcard *.c)
TARGETS = $(patsubst %.c, %, $(SOURCE))
CC = gcc
CFLAGS = -Wall -g
all:$(TARGETS)
$(TARGETS):%:%.c
$(CC) $< $(CFLAGS) -o $@
.PHONY:clean all
clean:
-rm -rf $(TARGETS)