进程处理信号有三种行为,在manpage里信号3种处理方式:
1.SIG_DFL 默认信号处理
2.SIG_IGN 忽略信号
3.a signal handling function 自定义信号处理行为(通过自定义函数来反馈信号)
即如下:进程处理信号的行为
1.默认处理动作(每个信号都有一个默认处理动作,如果当前进程没有设置忽略或者捕捉信号,则当收到信号时进行默认处理动作)
默认处理有5种动作(Action)情况:
Term 终止进程(terminate)
Core 终止进程,并且生成core文件,转储核心(储存终止进程之前其内存情况,验尸),如下
gcc -g file.c
ulimit -c 1024
gdb a.out core
进程死之前的内存情况,死后验尸
Ign 忽略该信号,即什么都不做,其与下面的忽略行为效果一样,但是层级不一样。
Stop 暂停进程
Cont 继续进程执行
2.忽略
3.捕捉(用户自定义信号处理函数)有点类似中断,当中断触发时会去执行中断处理函数
现在5种默认信号处理行为改为了A、B、C、D、E、F,代表含义一样
"动作(Action)"栏 的 字母 有 下列 含义:
A 缺省动作是结束(terminate终止)进程.
B 缺省动作是忽略这个信号.
C 缺省动作是结束进程, 并且核心转储.
D 缺省动作是停止(stop暂停并非终止)进程.
E 信号不能被捕获.
F 信号不能被忽略.
(译注: 这里 "结束" 指 进程 终止 并 释放资源, "停止" 指 进程 停止 运行, 但是 资源
没有 释放, 有可能 继续 运行.)
如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。
阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。
如图,当进程接收到信号后,进程pcb内维护着两个信号集,未决信号集、阻塞信号集。
未决信号集:每一个信号编号对应一个位,默认时候都为1,当进程接收到信号,进程pcb将信号传给未决信号集,对应信号位置1,表示进程此刻接收到了信号,但没有进行响应,此刻标记该信号为未决态。
阻塞信号集(也叫信号屏蔽字):当进程接收到信号,标记信号为未决态后,未决信号集将信号传给阻塞信号集,如果其阻塞位为1,表示该进程阻塞该信号,拒绝进行响应,不做出行为活动,此时,该信号一直被标记为未决态;如若阻塞位为0,则该信号通过阻塞信号集,并根据设定进行响应,并且内核将前面该信号的未决位由1该为0,此刻信号为递达态。
需要注意的是:
未决信号集是内核控制的,进程接到信号就对应未决位置1(未响应),当进程对该信号进行响应后,其对应未决位由内核自动置0,用户不能设置,但可以查询;阻塞信号集用户可以设置,用来控制进程对相应信号进行阻塞不响应。
未决位当该信号通过阻塞,进入行为的同时,信号就已经从未决态变为递达态(递达即递达到执行行为阶段,信号一通过屏蔽字,其未决位就被内核置0了,此时捕捉函数或者动作刚刚开始),故如果标准函数或者行为用时长比如5秒时间,那么在这5秒开始的那一刻,该信号就变为递达态,进程的该信号未决位置0,迎接下一个信号,所以在上一个信号进入捕捉函数执行阶段(5秒内),进程可以接收信号。但是前32位信号未决位只能记录一个,多个重复信号都是只记录一个。
handler为前面讲的行为:1.默认;2.忽略;3.捕捉。每一个信号都对应一个handler。
为了保证系统安全,并不是所有信号都可以阻塞的,系统会保留几个必要信号(SIGQUIT、SIGTSTP)不可阻塞,留给系统管理使用,如果不这样设定的话,那么用户将所有信号都阻塞了,那内核岂不是不能控制该进程了。
前32个信号为Linux经典信号,不支持排队;后32个信号为实时信号,支持排队;
sigset_t为信号集,可sizeof(sigset_t)察看
int sigemptyset(sigset_t *set) 清空信号集(模板)
int sigfillset(sigset_t *set) 全部置1
int sigaddset(sigset_t *set, int signo) 把该信号集(模板)某一个信号位置1
int sigdelset(sigset_t *set, int signo) 把该信号集(模板)某一个信号位置0
int sigismember(const sigset_t *set, int signo) 判断该信号集(模板)的某一个信号位是否为1
参数:
sigset_t表示信号集(模板),signo表示信号编号
return:
sigemptyset(),sigfillset(),sigaddset(),sigselset()成功返回0,失败返回-1
sigismember()是查询信号集某位是否为1,如若为1则返回1,如若不为1返回0;
信号集处理函数处理的对象不是pcb内的具体信号集,而是先自己构建一个信号集sigset_t作为模板,运用上述信号集函数将需设定的信号集内容填入到模板内,再将完成的模板粘贴应用到相应进程PCB内。
sigset_t信号集:信号集被定义为一种数据类型:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t
调用函数sigprocmask可以读取或更改进程的信号屏蔽字。
#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
参数:
1.how:选择执行操作类型,三种执行情况
2.set:信号集指针,指向自己构建的信号集模板,作为传入参数
3.oset:信号集指针,指向信号集指针buf,作为传出参数,函数将此刻屏蔽字(设置前)传出来,保存到oset内。若不关心原屏蔽字直接NULL
return:若成功则为0,若出错则为-1
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
how参数的含义:
SIG_BLOCK 信号集(模板)set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set,将模板与其原有的屏蔽字进行|操作
SIG_UNBLOCK 信号集(模板)set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set,将模板与原有屏蔽字进行&~操作
SIG_SETMASK 设置当前信号屏蔽字为set所指向的值,相当于mask=set,将模板替换原有屏蔽字
如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
#include
int sigpending(sigset_t *set);
参数:
set:为信号集(模板)指针,传出接收参数,为传出当前进程的未决信号集储存位置,接收当前进程的未决信号集。
sigpending读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
#include
#include
void printsigset(const sigset_t *set)
{
int i;
for (i = 1; i < 32; i++)
if (sigismember(set, i) == 1)
putchar('1');
else
putchar('0');
puts("");
}
int main(void)
{
sigset_t s, p;
sigemptyset(&s);
sigaddset(&s, SIGINT);
sigprocmask(SIG_BLOCK, &s, NULL);
while (1)
{
sigpending(&p);
printsigset(&p);
sleep(1);
}
return 0;
}
程序运行时,每秒钟把各信号的未决状态打印一遍,由于我们阻塞了SIGINT信号,按Ctrl-C将会使SIGINT信号处于未决状态,按Ctrl-\仍然可以终止程序,因为SIGQUIT信号没有阻塞。
xingwenpeng@ubuntu:~$ ./a.out
0000000000000000000000000000000
0000000000000000000000000000000(这时按Ctrl-C)
0100000000000000000000000000000
0100000000000000000000000000000(这时按Ctrl-\)
Quit (core dumped)
#include
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);不再使用
};
成员:
sa_handler : 早期的捕捉函数
sa_sigaction : 新添加的捕捉函数,可以传参 , 和sa_handler互斥,两者通过sa_flags选择采用哪种捕捉函数
sa_mask : 在执行捕捉函数时,设置阻塞其它信号,sa_mask | 进程阻塞信号集,退出捕捉函数后,还原回原有的
阻塞信号集
sa_flags : SA_SIGINFO(第二个函数原型) 或者 0(默认函数即第一个函数)
sa_restorer : 保留,已过时
参数:
signum:信号编号,需要设置捕捉函数的信号编号或宏名
act:信号动作函数结构体,输入参数,将自己构建的信号动作结构体传给捕捉设定函数,
oldact:信号动作函数结构体,输出函数,将原本信号捕捉函数结构体输出出来,若不关心原本捕捉函数则直接用NULL。
SIG_DFL、SIG_IGN
需要注意的是:
上图体现的是内核处理信号的流程,并不是信号一来就响应,而是在一定情况下去处理信号,当然对于高速运行的计算机,这种情况(系统转变为内核态处理异常或者中断)几乎时时在发生,所以在宏观上给人的感觉就是立即响应信号,其实并不是。比如主函数在用户空间执行一条语句如i++,它花费10MS,在这10MS内进程接收到了一个信号,进程不会中断i++去响应信号,而是进行执行i++完毕,再执行下一个(用户空间)语句,只有当进程调用系统调用、处理中断、异常时,操作系统进入内核态,比如进程A执行完一个时间片后,系统发出定时中断,进程A由于已经运行了一个时间片,内核剥夺进程A的cpu资源操作系统回归到内核态,发现进程A有一个未决信号,此刻也不会去立即响应进程A的信号,因为进程A 已经已完了当前的时间片,根据进程调度,操作系统将cpu资源分配给其他进程,当下一次轮到进程A使用CPU资源,此刻系统在内核态,准备进入进程A(用户空间的主程序时)发现上次的信号还在未决态,然后内核就直接进入信号处理,处理完后才进入用户空间的进程A主函数执行主函数。这里强调的是,进程和内核不是第一时间就响应接收到的信号的,而是在进程运行或者操作系统运行过程中,有无数个进入内核态再进入用户态是事件,信号统一在这个事件发生时处理。
信号与中断很像,但是不是中断,宏观处理方式很像,但是本质不同,中断是立即响应,信号并不是立即响应,是在特定时间才响应。
信号的具体响应:
主函数:
int main()
{
struct sigaction act;
int i;
act.sa_handler = act_i;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
while(1)
{
write(STDOUT_FILENO,“* * * * * * * * * *\n",20);
sleep(1);
}
return 0;
}
捕捉函数1:
void act01(int num)
{
int i = 1;
printf ("i am act01\n");
while(i){
printf("num = %d\n",num);
sleep(1);
i--;
}
}
捕捉函数2:
void act02(int ac)
{
printf("ac = %d\n", ac);
}
结果1:
jiaojian@KyLin:~/Linux/APUE/signal$ ./act02
* * * * * * * * * *
* * * * * * * * * *
* * * * * * * * * *
^Ci am act01
num = 2
^C^Ci am act01
num = 2
^C^C^C^Ci am act01
num = 2
^C^C^C^C^C^Ci am act01
num = 2
^C^C^C^Ci am act01
num = 2
* * * * * * * * * *
* * * * * * * * * *
^Ci am act01
num = 2
* * * * * * * * * *
* * * * * * * * * *
* * * * * * * * * *
^\退出 (核心已转储)
结果2:
jiaojian@KyLin:~/Linux/APUE/signal$ ./act
* * * * * * * * * *
* * * * * * * * * *
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^Cac = 2
* * * * * * * * * *
^\退出 (核心已转储)
从上面的两种结果可以发现。第二种捕捉函数,不管你多快的发送信号都不可连续两次及以上的执行捕捉函数(即接收一连串的信号)。
函数总结:
sigset_t模板编辑函数:
sigset_t模板写入与查询函数:
需要注意的是以上所有函数参数中都是用的模板指针sigset_t*和sigsction*
10:SIGUSR1和12:SIGUSR2内核没有定义其功能,让用户自己去定义他们的功能,称为用户自定义信号,系统设置的默认动作为终止,用户可以根据自己需求修改这两个信号的捕捉函数动作。
kill -SIGUSR1 6978,这段命令是向pid为6978的进程发送SIGUSR1信号。
注意:子进程继承了父进程的信号屏蔽字和信号处理动作,因为子进程是完全继承了父进程的pcb(除了pid)
前面介绍的sigaction()函数是Linux提供的信号处理函数,只能在Linux系统下使用,c标准函数可以在win和Linux下都可以使用。
typedef void (*sighandler_t)(int) 定义sighandler_t,为函数指针类型
sighandler_t signal(int signum, sighandler_t handler)
[参数]
signum为信号编号,handler为函数指针,与sigaction内的sa_hansler一模一样,signal函数比sigaction()函数简单,功能小,不可设定捕捉函数执行时的屏蔽字。
int system(const char *command)
集合fork,exec,wait一体
[参数]
command为指令的字符串,如“ls /home”
如:设捕捉函数为do_sig(),那么在函数中用signal()函数设定SIGINT信号捕捉函数
signal(SIGINT, do_sig);比sigaction简单很多。
如图为队列插入node1,insert()函数为插入函数,即将node1的地址指向原有队列的head,head指向node1这样完成插入,但是如果在insert()函数执行一半,node1尾部指向原有队列后,在head指向node前,突然来了一个信号,信号的捕捉函数为插入一个node2到原有的队列,insert(node2),这样就会导致在捕捉函数内node2插入后,head指向node2,捕捉函数完成,回到主函数,主函数此时为插入node1的第二步骤,将head指向node1,这样就要会有bug,node2没有真正的插入到队列,所以insert()函数为不可重入函数。
例如:strtok就是一个不可重入函数,因为strtok内部维护了一个内部静态指针,保存上一次切割到的位置,如果信号的捕捉函数中也去调用strtok函数,则会造成切割字符串混乱,应用strtok_r版本,r表示可重入。
多进程可以用不可重入函数,因为各进程之间资源不共享,内存空间是互相不可见的。对线程则不行,要用可重入函数,因为多线程之间资源是共享可见的。
int pause(void)
使调用进程挂起,直到有信号递达,如果递达信号动作是忽略,则继续挂起。
调用该函数可以造成进程主动挂起,等待信号唤醒。调用该系统调用的进程将处于阻塞状态(主动放弃 CPU)直到有信号递达将其唤醒
[返回值]
如果信号的默认处理动作是终止进程,则进程终止,pause 函数没有机会返回
如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause 函数不返回
如果信号的处理动作是捕捉,则调用完信号处理函数之后,pause 函数返回-1,errno 设置为 EINTR,表示 “被信号中断”。想想我们哪个函数只有出错返回值(exec)
pause 收到的信号不能被屏蔽,如果被屏蔽,那么 pause 就不能被唤醒。
时序竞态指的是在进程中如果有计数操作的函数或者模块,比如进程A内有一句alarm(1)计时一秒,当进程A执行完这一句后,CPU资源被其他进程夺去,过了2S才轮到进程A使用CPU,那么此时执行alarm(1)的下一句pause(),此时alarm(1)已经过去2S了,SIGALRM信号在一秒前已经传给进程A了,但那时进程A在就绪态,pause()没有收到信号,导致进程A一直挂起。这种由于进程内含有时序和进程竞争导致的执行错误现象就是时序竞态。
为了解决上面的问题,引入通过信号屏蔽来实现SIGALRM信号返回时如果此刻pause()没有在执行,则该信号屏蔽;当pause()执行时信号没有阻塞。即
1.阻塞SIGALRM
2.执行alarm(n)函数
3.执行pause(),并且放开SIGALRM的阻塞,sigsuspend()函数就是这两步的封装
上面的三步可解决pause()函数的时序竞态问题,保证pause()可以接收到信号(通过阻塞)
int sigsuspend(const sigset_t *mask)
sigsuapend()执行的步骤:
1.以通过指定mask来临时解除对某个信号的屏蔽,这里需要在sigsuspend()函数执行前,该信号就被屏蔽了。
2.然后挂起等待
3.当被信号唤醒sigsuspend返回时,进程的信号屏蔽字恢复为原来的值
注意;sigsuspend()函数只是解除阻塞和pause()的封装,在执行sigsuspend()前就需要完成相应信号的阻塞
使用pause()函数导致出现问题的版本:
#include
#include
#include
void sig_alrm(int signo)
{
/* nothing to do */
}
unsigned int mysleep(unsigned int nsecs)
{
struct sigaction newact, oldact;
unsigned int unslept;
newact.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM, &newact, &oldact);
alarm(nsecs);
pause();
unslept = alarm(0);
sigaction(SIGALRM, &oldact, NULL);
return unslept;
}
int main(void)
{
while(1){
mysleep(2);
printf("Two seconds passed\n");
}
return 0;
}
mysleep改进版,通过屏蔽SIGSLRM信号,然后在挂起的同时解除屏蔽(即sigsuspend()函数是将pause()函数和解除屏蔽字封装成一个原子操作),这样可以保证alarm的信号在到达sigsuspend()前是未决的。
unsigned int mysleep(unsigned int nsecs) //该函数的返回值为未睡够的时间
{
struct sigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept;
/* set our handler, save previous information */
newact.sa_handler = sig_alrm;//这里必须设置捕捉函数,
//如果是SIG_DLF(默认行为为终止)那么直接就终止进程了,如果是SIG_IGN,后面的挂起就不会停止,继续挂起
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM, &newact, &oldact);
/* block SIGALRM and save current signal mask */
//第一步:这里先阻塞SIGALRM
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
//第二步:阻塞SIGALRM设置完后再计时
alarm(nsecs);
suspmask = oldmask;
sigdelset(&suspmask, SIGALRM); /* make sure SIGALRM isn't blocked */
//第三步:通过sigsuspend()函数,进入pause()挂起,并且同时解除阻塞,即将这两步和成一步
sigsuspend(&suspmask); /* wait for any signal to be caught */
/* some signal has been caught, SIGALRM is now blocked */
unslept = alarm(0);
sigaction(SIGALRM, &oldact, NULL); /* reset previous action */
/* reset signal mask, which unblocks SIGALRM */
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return(unslept);
}
sigsuspend()函数通过整合pause和信号屏蔽来解决了时序竞态问题,保证了信号会传递给sigsuspend()函数,解除挂起。
sig_atomic_t
平台下的原子类型,即为了避免进程运行操作数据时,信号插入导致数据处理一半,信号捕捉函数内又对该数据进行处理,导致异步
如在32位机上的long long类型,
如在32位机上的long long类型,就不是一个原子类型,因为32位机一次能处理的最大位数为32位4个字节,long long 为8个字节,所以需要两步汇编才能对其进行操作,所以long long 对于这台机器就不是一个原子类型,而int才是一次可以操作的类型。
如:long long n = ?,给全局变量n赋值,然后进程A对n进行操作,操作在汇编机器层面分为前4个字节和后4个字节,如果进程A在操作完前4个字节后,收到一个信号sig,转而去处理sig捕捉函数,而捕捉函数也是操作n(全局变量),操作完后,回到主函数,接着处理前面没处理完的后4位,这样就导致异步,所以引入原子类型概念来避免该现象(通过typedef实现,可以具备跨平台能力)。
volatile
防止编译器开启优化选项时,优化对内存的读写
volatile与const一样都是修饰变量的,const修饰的变量为只读变量,volatile修饰的变量意思是防止编译器对该变量的优化操作,即编译器在编译源码时会进行优化操作,内存中的n只读一次,后面代码使用到变量n直接使用第一次读取到的值,这样可以避免每次用到n变量都去n的地址读取值,但是在当异步发生的时候,如果还是用优化操作,那么如果中间信号捕捉函数修改了n值,进程所看见的n值还是第一次读取的值没有跟随改变,就造成了异步错误,所以使用volatile修饰变量n,告诉编译器,不要对变量n进行内存读写优化,进程每次使用到n都去其内存地址读取,这样有效避免了异步错误。
反汇编命令:objdump
jiaojian@KyLin:~/Linux/APUE/signal$ gcc fanhui.c -g
jiaojian@KyLin:~/Linux/APUE/signal$ objdump a.out -dSsx > file
父进程回收子进程:
pid_t waitpid(pid_t pid, int *status, int options)
[参数]
options:
WNOHANG
没有子进程结束,立即返回,即不阻塞
WUNTRACED
如果子进程由于被停止产生的SIGCHLD, waitpid则立即返回
WCONTINUED
如果子进程由于被SIGCONT唤醒而产生的SIGCHLD, waitpid则立即返回
获取status(通过以下宏函数来解析status):
WIFEXITED(status)
子进程正常exit终止,返回真
WEXITSTATUS(status)返回子进程正常退出值
WIFSIGNALED(status)
子进程被信号终止,返回真
WTERMSIG(status)返回终止子进程的信号值
WIFSTOPPED(status)
子进程被停止(暂停),返回真
WSTOPSIG(status)返回停止子进程的信号值
WIFCONTINUED(status)
子进程由停止态转为就绪态,返回真
[返回值]
回收成功返回子进程pid,设置非阻塞时,若无子进程结束,返回0
回收失败返回-1
如下代码:
#include
#include
#include
#include
#include
#include
#include
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))
printf("child %d exit %d\n", pid, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("child %d cancel signal %d\n", pid, WTERMSIG(status));
}
}
int main(void)
{
pid_t pid;
int i;
//阻塞SIGCHLD
for (i = 0; i < 10; i++) {
if ((pid = fork()) == 0)
break;
else if (pid < 0)
sys_err("fork");
}
if (pid == 0) {
int n = 18;
while (n--) {
printf("child ID %d\n", getpid());
sleep(1);
}
return i;
}
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);
while (1) {
printf("Parent ID %d\n", getpid());
sleep(1);
}
}
return 0;
}
int sigqueue(pid_t pid, int sig, const union sigval value)
union sigval {
int sival_int;
void *sival_ptr;
};
sigqueue()函数是给指定进程传信号,并且给该信号捕捉函数传参,传的参数为联合体sigval,所以使用该函数给捕捉函数传参时,捕捉函数使用sigaction结构体内的第二个函数原型,即sa_sigaction = act_i,sa_flags = SA_SIGINFO,还有注意一下两点:
进程自己收发信号,在同一地址空间
不同进程间收发信号,不在同一地址空间,不适合传地址
即进程A用sigqueue()函数给另外一个进程B发送信号和捕捉函数传参,传去了一个指针,如果A进程B进程是两个无关的进程,那么该指针无意义,因为AB之间内存空间不共享;如果AB有血缘关系,那么地址空间有意义。这里需要注意。
void (*sa_sigaction)(int, siginfo_t *, void *)
siginfo_t {
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
sigval_t si_value; /* Signal value */
...
}
sa_flags = SA_SIGINFO
read阻塞读数据时,信号中断系统调用,则read函数返回:
第一种可能:返回部分读到的数据
第二种可能:read调用失败,errno设成EINTER
根据不同系统返回不一样的情况。