信号共性:1. 简单 2. 不能携带大量信息 3. 满足某个特设条件才发送。
Unix早期版本就提供了信号机制,但不可靠,信号可能丢失。Berkeley 和 AT&T都对信号模型做了更改,增加了可靠信号机制。但彼此不兼容。POSIX.1对可靠信号例程进行了标准化。
A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。
信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。
每个进程收到的所有信号,都是由内核负责发送的,内核处理。
产生信号:
递达:递送并且到达进程。
未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。
信号的处理方式:
Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
阻塞信号集(信号屏蔽字):
将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)
未决信号集:
不存在编号为0的信号。**其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关。**名字上区别不大。而前32个名字各不相同。
与变量三要素类似的,每个信号也有其必备4要素,分别是:1. 编号 2. 名称 3. 事件 4. 默认处理动作
可通过man 7 signal
查看帮助文档获取。也可查看/usr/src/linux-headers-3.16.0-30/arch/s390/include/uapi/asm/signal.h
Signal | Value | Action | Comment |
---|---|---|---|
SIGHUP | 1 | Term | Hangup detected on controlling terminal or death of controlling process |
SIGINT | 2 | Term | Interrupt from keyboard |
SIGQUIT | 3 | Core | Quit from keyboard |
SIGILL | 4 | Core | Illegal Instruction |
SIGFPE | 8 | Core | Floating point exception |
SIGKILL | 9 | Term | Kill signal |
SIGSEGV | 11 | Core | Invalid memory reference |
SIGPIPE | 13 | Term | Broken pipe: write to pipe with no readers |
SIGALRM | 14 | Term | Timer signal from alarm(2) |
SIGTERM | 15 | Term | Termination signal |
SIGUSR1 | 30,10,16 | Term | User-defined signal 1 |
SIGUSR2 | 31,12,17 | Term | User-defined signal 2 |
SIGCHLD | 20,17,18 | Ign | Child stopped or terminated |
SIGCONT | 19,18,25 | Cont | Continue if stopped |
SIGSTOP | 17,19,23 | Stop | Stop process |
SIGTSTP | 18,20,24 | Stop | Stop typed at terminal |
SIGTTIN | 21,21,26 | Stop | Terminal input for background process |
SIGTTOU | 22,22,27 | Stop | Terminal output for background process |
The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
在标准信号中,有一些信号是有三个“Value”,第一个值通常对alpha和sparc架构有效,中间值针对x86、arm和其他架构,最后一个应用于mips架构。一个‘-’表示在对应架构上尚未定义该信号。
不同的操作系统定义了不同的系统信号。因此有些信号出现在Unix系统内,也出现在Linux中,而有的信号出现在FreeBSD或Mac OS中却没有出现在Linux下。这里我们只研究Linux系统中的信号。
默认动作:
注意从man 7 signal帮助文档中可看到 : The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
这里特别强调了**9) SIGKILL 和19) SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将其设置为阻塞。**另外需清楚,只有每个信号所对应的事件发生了,该信号才会被递送(但不一定递达),不应乱发信号!!
Ctrl + C → 2) SIGINT(终止/中断) "INT" ----Interrupt
Ctrl + Z → 20) SIGTSTP(暂停/停止) "T" ----Terminal 终端。
Ctrl + \ → 3) SIGQUIT(退出)
除0操作 → 8) SIGFPE (浮点数例外) "F" -----float 浮点数。
非法访问内存 → 11) SIGSEGV (段错误)
总线错误 → 7) SIGBUS
kill命令产生信号:kill -SIGKILL pid
kill函数:int kill(pid_t pid, int sig);
给指定进程发送指定信号(不一定杀死)
进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同。
权限保护:super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。 kill -9 (root用户的pid) 是不可以的。同样,普通用户也不能向其他普通用户发送信号,终止其进程。 只能向自己创建的进程发送信号。普通用户基本规则是:发送者实际或有效用户ID == 接收者实际或有效用户ID
练习:循环创建5个子进程,任一子进程用kill函数终止其父进程。
#include
#include
#include
#include
using namespace std;
int main(){
//循环建立五个子进程
int i;
pid_t pid,pidVal;
for(i = 0;i < 5;i++){
pid = fork();
if(pid == 0){
break;
}
if(i == 2){
pidVal = pid;
}
}
if(i < 5){//子进程
while(1){
cout << "I am " << i+1 << " child! pid = " << getpid() <<endl;
sleep(1);
}
}else{//父进程
sleep(1);
cout << "I am father!" << endl;
kill(pidVal,SIGKILL);
while(1);
}
//int ret = kill(pidVal,SIGKILL);
//if(ret == -1){
// perror("kill error");
// exit(1);
//}
return 0;
}
raise 函数:给当前进程发送指定信号(自己给自己发) raise(signo) == kill(getpid(), signo);
int raise(int sig);
成功:0,失败非0值
abort 函数:给自己发送异常终止信号 6) SIGABRT 信号,终止并产生core文件
void abort(void);
该函数无返回
unsigned int alarm(unsigned int seconds);
返回0或剩余的秒数,无失败。常用:取消定时器alarm(0),返回旧闹钟余下秒数。
例:alarm(5) → 3sec → alarm(4) → 5sec → alarm(5) → alarm(0)
返回0 ————————————>返回2 ————————————>返回0——————>返回5
;
定时,与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、终止、僵尸…无论进程处于何种状态,alarm都计时。
练习:编写程序,测试你使用的计算机1秒钟能数多少个数。
#include
#include
using namespace std;
int main(){
int i;
alarm(1);
for(i = 0;;i++){
cout << i << " ";
}
return 0;
}
使用time命令:time ./a.out
查看程序执行的时间。 程序运行的瓶颈在于IO,优化程序,首选优化IO。
实际执行时间 = 系统时间 + 用户时间 + 等待时间 (等待其他系统资源所消耗的时间)
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
//参数
struct itimerval {
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value; /* Time until next expiration */
};
struct timeval {
time_t tv_sec; /* seconds */ //秒
suseconds_t tv_usec; /* microseconds */ //微秒
};
//相当于以下结构体
struct itimerval{
struct timeval it_interval{
it_terval.tv_sec;
it_interval.tv_usec;
}it_interval;
struct timeval it_interval{
it_value.tv_sec;
it_value.tv_usec;
}it_value;
}it,oldit;
练习: 使用setitimer函数实现alarm函数,重复计算机1秒数数程序。
#include
#include
#include
using namespace std;
unsigned int my_alarm(unsigned int sec){
struct itimerval it,oldit;
int ret;
//都是it,value为秒数,interval为间隔时间;oldit为传出参数
it.it_value.tv_sec = sec;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 0;
ret = setitimer(ITIMER_REAL,&it,&oldit);
if(ret == -1){
perror("setitimer error");
exit(1);
}
return oldit.it_value.tv_sec;
}
int main(){
int i;
my_alarm(1);//相当于alarm(1)
for(i = 0;;i++){
cout << i <<" ";
}
return 0;
}
#include
#include
#include
using namespace std;
void myfunc(int sig){
cout << "hello signal!" << endl;
}
int main(){
struct itimerval it,oldit;
signal(SIGALRM,myfunc);//注册SIGALRM信号的捕捉处理函数
it.it_value.tv_sec = 5;//定时5s
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 3;//第一个闹钟和第二个闹钟之间间隔3s
it.it_interval.tv_usec = 0;
if(setitimer(ITIMER_REAL,&it,&oldit) == -1){
perror("setitimer error");
return -1;
}
while(1);
return 0;
}
第一次打印是5s,之后打印是间隔3s
内核通过读取未决信号集来判断信号是否应被处理。(阻塞信号集)信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。
函数 | 功能 | 返回值 |
---|---|---|
sigset_t set; |
typedef unsigned long sigset_t; | |
int sigemptyset(sigset_t *set); |
将某个信号集清0 | 成功:0;失败:-1 |
int sigfillset(sigset_t *set); |
将某个信号集置1 | 成功:0;失败:-1 |
int sigaddset(sigset_t *set, int signum); |
将某个信号加入信号集 | 成功:0;失败:-1 |
int sigdelset(sigset_t *set, int signum); |
将某个信号清出信号集 | 成功:0;失败:-1 |
int sigismember(const sigset_t *set, int signum); |
判断某个信号是否在信号集中 | 返回值:在集合:1;不在:0;出错:-1 |
sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
练习:把所有常规信号的未决状态打印至屏幕。
#include
#include
#include
using namespace std;
void print_pend(sigset_t *pend){
for(int i = 0;i < 32;i++){
//判断信号集中哪个位为1,如果为0,在屏幕上输出‘0’,反之输出‘1’
if(sigismember(pend,i)== 1){
putchar('1');
}else{
putchar('0');
}
}
cout << endl;
}
int main(){
/*用自己设置的信号集去影响阻塞信号集,然后由阻塞信号集影响未决信号集*/
sigset_t myset,oldset,pend;
sigemptyset(&myset);//把信号集全都设置为0
sigaddset(&myset,SIGQUIT);//把3号信号设置屏蔽,此信号用ctrl+\产生
//sigaddset(&myset,SIGINT);//把2号信号设置屏蔽,此信号用ctrl+c产生
sigaddset(&myset,SIGTSTP);//把20信号设置屏蔽,此信号用ctrl+z产生
int ret = sigprocmask(SIG_BLOCK,&myset,&oldset);
if(ret == -1){
perror("sigprocmask error");
exit(1);
}
while(1){//循环打印未决信号集
sigpending(&pend);//pend为传出参数
print_pend(&pend);
sleep(1);
}
return 0;
}
注册一个信号捕捉函数:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。
void (*signal(int signum, void (*sighandler_t)(int))) (int);
能看出这个函数代表什么意思吗? 注意多在复杂结构中使用typedef。
#include
#include
#include
using namespace std;
void func(int sig){
cout << "signal function" << endl;
}
int main(){
sighandler_t ret = signal(SIGINT,func);//捕捉ctrl+c
if(ret == SIG_ERR){
perror("signal error");
exit(1);
}
while(1);
return 0;
}
修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction结构体
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
为某个信号(ctrl+c)设置捕捉函数
#include
#include
#include
using namespace std;
void func(int sig){
cout << "signal is catched " << endl;
}
int main(){
struct sigaction act;
act.sa_handler = func;
sigemptyset(&act.sa_mask);//sa_mask为所要屏蔽的信号集合,此步先把act.sa_mask集合清空
sigaddset(&act.sa_mask,SIGQUIT);//此步设置SIGQUIT(ctrl + c)信号为要屏蔽的信号
act.sa_flags = 0;//默认属性,信号捕捉函数执行期间,自动屏蔽本信号
int ret = sigaction(SIGINT,&act,NULL);
if(ret < 0){
perror("sigaction error");
exit(1);
}
while(1);
return 0;
}
验证在信号处理函数执行期间,该信号多次递送,那么只在处理函数之行结束后,处理一次。
验证sa_mask在捕捉函数执行期间的屏蔽作用。
#include
#include
#include
#include
using namespace std;
void func(int sig){
cout << "signal is catched!!!" << endl;
sleep(10);
cout << "finished!!!" << endl;
}
int main(){
struct sigaction act;
act.sa_handler = func;
sigemptyset(&act.sa_mask);//sa_mask为所要屏蔽的信号集合,此步先把act.sa_mask集合清空
sigaddset(&act.sa_mask,SIGQUIT);//此步设置SIGQUIT信号为要屏蔽的信号ctrl+\
act.sa_flags = 0;//默认属性,信号捕捉函数执行期间,自动屏蔽本信号
int ret = sigaction(SIGINT,&act,NULL);
if(ret < 0){
perror("sigaction error");
exit(1);
}
while(1);
return 0;
}
第一次执行ctrl+c时,此信号被捕捉,然后执行信号捕捉函数。执行捕捉函数期间,信号ctrl+c多次递送,但是只在处理函数结束后处理一次。捕捉函数执行期间递送屏蔽信号ctrl+\,并不执行该信号,只在函数结束后执行默认动作。
使用pause和alarm来实现sleep函数。
#include
#include
#include
#include
using namespace std;
//捕捉函数
void catch_sigalarm(int signo){
;
}
unsigned int mysleep(unsigned int seconds){
struct sigaction act,oldact;
act.sa_handler = catch_sigalarm;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
int ret = sigaction(SIGALRM,&act,&oldact);
if(ret == -1){
perror("sigaction error");
exit(1);
}
alarm(seconds);
ret = pause();//主动挂起,等待信号
if(ret == -1 && errno == EINTR){
cout << "pause sucess!!!" << endl;
}
ret = alarm(0);//
sigaction(SIGALRM,&oldact,NULL);//恢复SIGALRM信号旧有的处理方式
return ret;
}
int main(){
mysleep(3);
cout << "-------------" << endl;
return 0;
}
注意,unslept = alarm(0)的用法。清除闹钟,提高程序健壮性
例如:睡觉,alarm(10)闹铃。
正常: 10后闹铃将我唤醒,这时额外设置alarm(0)取消闹铃,不会出错。
异常: 5分钟,被其他事物吵醒,alarm(0)取消闹铃防止打扰。
设想如下场景:
欲睡觉,定闹钟10分钟,希望10分钟后闹铃将自己唤醒。
正常:定时,睡觉,10分钟后被闹钟唤醒。
异常:闹钟定好后,被唤走,外出劳动,20分钟后劳动结束。回来继续睡觉计划,但劳动期间闹钟已经响过,不会再将我唤醒。
回顾,借助pause和alarm实现的mysleep函数。设想如下时序:
1. 注册SIGALRM信号处理函数 (sigaction…)
2. 调用alarm(1) 函数设定闹钟1秒。
3. 函数调用刚结束,开始倒计时1秒。当前进程失去cpu,内核调度优先级高的进程(有多个)取代当前进程。当前进程无法获得cpu,进入就绪态等待cpu。
4. 1秒后,闹钟超时,内核向当前进程发送SIGALRM信号(自然定时法,与进程状态无关),高优先级进程尚未执行完,当前进程仍处于就绪态,信号无法处理(未决)
5. 优先级高的进程执行完,当前进程获得cpu资源,内核调度回当前进程执行。SIGALRM信号递达,信号设置捕捉,执行处理函数sig_alarm。
6. 信号处理函数执行结束,返回当前进程主控流程,pause()被调用挂起等待。(欲等待alarm函数发送的SIGALRM信号将自己唤醒)
7. SIGALRM信号已经处理完毕,pause不会等到。
可以通过设置屏蔽SIGALRM的方法来控制程序执行逻辑,但无论如何设置,程序都有可能在“解除信号屏蔽”与“挂起等待信号”这个两个操作间隙失去cpu资源。除非将这两步骤合并成一个“原子操作(不可再分的:系统调用)”。sigsuspend函数具备这个功能。在对时序要求严格的场合下都应该使用sigsuspend替换pause。
int sigsuspend(const sigset_t *mask);
挂起等待信号。
sigsuspend函数调用期间,进程信号屏蔽字由其参数mask指定。
可将某个信号(如SIGALRM)从临时信号屏蔽字mask中删除,这样在调用sigsuspend时将解除对该信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值。如果原来对该信号是屏蔽态,sigsuspend函数返回后仍然屏蔽该信号。
改进版mysleep【sigsuspend】
#include
#include
#include
#include
using namespace std;
//捕捉函数
void catch_sigalarm(int signo){
;
}
unsigned int mysleep(unsigned int seconds){
struct sigaction act,oldact;
sigset_t newmask,oldmask,suspmask;//设置信号集
//1.为SIGALRM设置捕捉函数,一个空函数
act.sa_handler = catch_sigalarm;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
int ret = sigaction(SIGALRM,&act,&oldact);
if(ret == -1){
perror("sigaction error");
exit(1);
}
//2. 设置阻塞信号集,阻塞信号SIGALRM信号
sigemptyset(&newmask);
sigaddset(&newmask,SIGALRM);
sigprocmask(SIG_BLOCK,&newmask,&oldmask);//设置屏蔽信号,此时newmask中SIGALRM信号为屏蔽状态
//3.定时n秒,定时结束后产生SIGALRM信号
alarm(seconds);
//4. 构造一个调用sigsuspend临时有效的阻塞信号集,
//在临时阻塞信号集里解除SIGALRM信号
suspmask = oldmask;//sigprocmask的传出参数
sigdelset(&suspmask,SIGALRM);//保证信号集suspmask中SIGALRM信号为解除状态
/*
5. sigsuspend调用期间,采用临时阻塞信号集suspmask替换原有阻塞信号集
这个信号集中不包含SIGALRM信号
同时挂起等待,当sigsuspend被信号唤醒返回时,恢复原有的阻塞信号集
· */
sigsuspend(&suspmask);
unsigned int unret = alarm(0);//
sigaction(SIGALRM,&oldact,NULL);//恢复SIGALRM信号旧有的处理方式
//7. 解除对SIGALRM的阻塞
sigprocmask(SIG_SETMASK,&oldmask,NULL);
return unret;
}
int main(){
mysleep(3);
cout << "-------------" << endl;
return 0;
}
竞态条件,跟系统负载有很紧密的关系,体现出信号的不可靠性。系统负载越严重,信号不可靠性越强。
不可靠由其实现原理所致。信号是通过软件方式实现(跟内核调度高度依赖,延时性强),每次系统调用结束后,或中断处理处理结束后,需通过扫描PCB中的未决信号集,来判断是否应处理某个信号。当系统负载过重时,会出现时序混乱。
这种意外情况只能在编写程序过程中,提早预见,主动规避,而无法通过gdb程序调试等其他手段弥补。且由于该错误不具规律性,后期捕捉和重现十分困难。
分析如下父子进程交替数数程序。当捕捉函数里面的sleep取消,程序即会出现问题。请分析原因。
#include
#include
#include
#include
int n = 0, flag = 0;
void sys_err(char *str)
{
perror(str);
exit(1);
}
void do_sig_child(int num)
{
printf("I am child %d\t%d\n", getpid(), n);
n += 2;
flag = 1;
sleep(1);
}
void do_sig_parent(int num)
{
printf("I am parent %d\t%d\n", getpid(), n);
n += 2;
flag = 1;
sleep(1);
}
int main(void)
{
pid_t pid;
struct sigaction act;
if ((pid = fork()) < 0)
sys_err("fork");
else if (pid > 0) { //父进程
n = 1;
sleep(1);//父进程沉睡1s,让子进程完成捕捉函数注册
act.sa_handler = do_sig_parent;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR2, &act, NULL); //注册自己的信号捕捉函数 父使用SIGUSR2信号
do_sig_parent(0);
while (1) {
/* wait for signal */;
if (flag == 1) { //父进程数数完成
kill(pid, SIGUSR1);
flag = 0; //标志已经给子进程发送完信号
}
}
} else if (pid == 0) {
n = 2;
act.sa_handler = do_sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR1, &act, NULL);
while (1) {
/* waiting for a signal */;
if (flag == 1) {
kill(getppid(), SIGUSR2);
flag = 0;
}
}
}
return 0;
}
示例中,通过flag变量标记程序实行进度。flag置1表示数数完成。flag置0表示给对方发送信号完成。
问题出现的位置,在父子进程kill函数之后需要紧接着调用 flag,将其置0,标记信号已经发送。但,在这期间很有可能被kernel调度,失去执行权利,而对方获取了执行时间,通过发送信号回调捕捉函数,从而修改了全局的flag。
如何解决该问题呢?可以使用后续课程讲到的“锁”机制。当操作全局变量的时候,通过加锁、解锁来解决该问题。
现阶段,我们在编程期间如若使用全局变量,应在主观上注意全局变量的异步IO可能造成的问题。
一个函数在被调用执行期间(尚未调用结束),由于某种时序又被重复调用,称之为“重入”。根据函数实现的方法可分为“可重入函数”和“不可重入函数”两种。看如下时序。
显然,insert函数是不可重入函数,重入调用,会导致意外结果呈现。究其原因,是该函数内部实现使用了全局变量。
注意事项:
子进程结束运行,其父进程会收到SIGCHLD信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。
#include
#include
#include
#include
#include
using namespace std;
void sys_err(char *str)
{
perror(str);
exit(1);
}
void do_sig_child(int signo)//信号捕捉函数
{
int status;
pid_t pid;
//回收子进程
while ((pid = waitpid(0, &status, WNOHANG)) > 0) {
if (WIFEXITED(status))
cout << "child " << pid << " exit " << WEXITSTATUS(status) << endl;
else if (WIFSIGNALED(status))
cout << "child " << pid << " cancle signal " << WTERMSIG(status) << endl;
}
}
int main(void)
{
pid_t pid; int i;
for (i = 0; i < 10; i++) {//循环创建n个子进程
if ((pid = fork()) == 0)
break;
else if (pid < 0)
sys_err("fork");
}
if (pid == 0) { //十个子进程
int n = 1;
while (n--) {
cout << "child ID " << getpid() << endl;//每个子进程打印一次
sleep(1);
}
return i+1;//子进程退出返回值1-10
} else if (pid > 0) {//父进程
//加上 SIGCHLD阻塞
//.....
struct sigaction act;
act.sa_handler = do_sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, NULL);
//加上 解除对SIGCHLD的阻塞
//.....
while (1) {
cout << "parent ID " << getpid() << endl;
sleep(1);
}
}
return 0;
}
结合 17)SIGCHLD 信号默认动作,掌握父使用捕捉函数回收子进程的方式。
如果每创建一个子进程后不使用sleep可以吗?可不可以将程序中,捕捉函数内部的while替换为if?为什么? if ((pid = waitpid(0, &status, WNOHANG)) > 0) { … }
不能替换为if ,因为内核是根据未决信号集来处理信号,如果一个信号重复发送,信号只会记录一次,可能其他信号不会被处理,所以会产生僵尸进程
思考:信号不支持排队,当正在执行SIGCHLD捕捉函数时,再过来一个或多个SIGCHLD信号怎么办?用while处理
pid_t waitpid(pid_t pid, int *status, int options)
options | 说明 |
---|---|
WNOHANG | 没有子进程结束,立即返回 |
WUNTRACED | 如果子进程由于被停止产生的SIGCHLD,waitpid则立即返回 |
WCONTINUED | 如果子进程由于被SIGCONT唤醒而产生的SIGCHLD,waitpid则立即返回 |
获取status | 说明 |
---|---|
WIFEXITED(status) | 子进程正常exit终止,返回真 |
WEXITSTATUS(status) | 返回子进程正常退出值 |
WIFSIGNALED(status) | 子进程被信号终止,返回真 |
WTERMSIG(status) | 返回终止子进程的信号值 |
WIFSTOPPED(status) | 子进程被停止,返回真 |
WSTOPSIG(status) | 返回停止子进程的信号值 |
WIFCONTINUED(status) | 子进程接收到SIGCONT信号继续执行 |
sigqueue函数对应kill函数,但可在向指定进程发送信号的同时携带参数
int sigqueue(pid_t pid, int sig, const union sigval value);
成功:0;失败:-1,设置errno
union sigval {
int sival_int;
void *sival_ptr;
};
向指定进程发送指定信号的同时,携带数据。但,如传地址,需注意,不同进程之间虚拟地址空间各自独立,将当前进程地址传递给另一进程没有实际意义。
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
当注册信号捕捉函数,希望获取更多信号相关信息,不应使用sa_handler而应该使用sa_sigaction。但此时的sa_flags必须指定为SA_SIGINFO。siginfo_t是一个成员十分丰富的结构体类型,可以携带各种与信号相关的数据。
系统调用可分为两类:慢速系统调用和其他系统调用。
结合pause,回顾慢速系统调用:
慢速系统调用被中断的相关行为,实际上就是pause的行为: 如,read
可修改sa_flags参数来设置被信号中断后系统调用是否重启。SA_INTERRURT不重启。 SA_RESTART重启。
扩展了解:
sa_flags还有很多可选参数,适用于不同情况。如:捕捉到信号后,在执行捕捉函数期间,不希望自动阻塞该信号,可将sa_flags设置为SA_NODEFER,除非sa_mask中包含该信号