linux信号问题网络FAQ整理

1.信号“未决”与“阻塞” 

信号状态:
    信号的”未决“是一种状态,指的是从信号的产生到信号被处理前的这一段时间;信号的”阻塞“是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
    APUE例题在sleep前用sigprocmask阻塞了退出信号,然后sleep,然后在sleep的过程中产生一个退出信号,但是此时退出信号被阻塞过,(中文的”阻塞”在这里容易被误解为一种状态,实际上是一种类似于开关的动作,所以说“被阻塞过”,而不是“被阻塞”)所以处于“未决”状态,在 sleep后又用sigprocmask关掉退出信号的阻塞开关,因为之前产生的退出信号一直处于未决状态,当关上阻塞开关后,马上退出“未决”状态,得到处理,这一切发生在sigprocmask返回之前。

信号生命周期:
    对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:1.信号诞生;2. 信号在进程中注册完毕;3.信号在进程中的注销完毕;4.信号处理函数执行完毕。相邻两个事件的时间间隔构成信号生命周期的一个阶段。
    下面阐述四个事件的实际意义:
1.信号"诞生"。信号的诞生指的是触发信号的事件发生(如检测到硬件异常、定时器超时以及调用信号发送函数kill()或sigqueue()等)。

2.信号在目标进程中"注册";
    进程的task_struct结构中有关于本进程中未决信号的数据成员:
struct sigpending pending;
struct sigpending
{
    struct sigqueue *head, **tail;
    sigset_t signal;
};
第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,第三个成员是进程中所有未决信号集,信息链中的每个sigqueue结构体刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:
struct sigqueue
{
    struct sigqueue *next;
    siginfo_t info;
};
    信号在进程中注册指的就是信号值加入到进程的未决信号集中(sigpending结构的第二个成员sigset_t signal),并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
注:
    当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册);
当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册,则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构(一个非实时信号诞生后,(1)、如果发现相同的信号已经在目标结构中注册,则不再注册,对于进程来说,相当于不知道本次信号发生,信号丢失;(2)、如果进程的未决信号中没有相同信号,则在进程中注册自己)。

3.信号在进程中的注销。在目标进程执行过程中,会检测是否有信号等待处理(每次从系统空间返回到用户空间时都做这样的检查)。(“sigprocmask返回前,也至少会将其中一个未决且未阻塞的信号递送给进程”???)如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集中删除对于实时与非实时信号是不同的。对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用gqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则应该把信号在进程的未决信号集中删除(信号注销完毕)。否则,不在进程的未决信号集中删除该信号(信号注销完毕)。进程在执行信号相应处理函数之前,首先要把信号在进程中注销。

4.信号生命终止。进程注销信号后,立即执行相应的信号处理函数,执行完毕后,信号的本次发送对进程的影响彻底结束。
注:
1)信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)。
2)在信号被注销到相应的信号处理函数执行完毕这段时间内,如果进程又收到同一信号多次,则对实时信号来说,每一次都会在进程中注册;而对于非实时信号来说,无论收到多少次信号,都会视为只收到一个信号,只在进程中注册一次。

 

不存在编号为0 的信号

普通信号不排队,实时信号排队( 但一般我们用不着)

异种信号允许嵌套,即一个未完也可嵌入另一个;

同种信号不允许嵌套,同种信号不排队,所以为了防止信号丢失,信号处理一定要快。

 

 

 

2.信号处理嵌套问题

现在我又三个进程   A   B   C
其中   A   进程设置了对   SIGUSR1   和   SIGUSR2   的处理:
sigset(SIGUSR1,   a1);        
sigset(SIGUSR2,   a2);    
如果   B   进程首先向   A   进程发送了一个   SIGUSR1   ,A进程会进入a1()函数,
但这时   C   进程   又向   A进程   发送了一个SIGUSER2,   那么:
1。A进程会中断a1()的处理然后进入b1()的处理?
2。A进程会先完成a1()   然后再响应SIGUSR2   进入b1()?

回复:

当A进入SIGUSR1信号处理程序的时候,如果SIGUSR2来了
会立即中断SIGUSR1的处理,嵌套进入SIGUSR2的处理函数。等SIGUSR2处理完
了才会回头接着处理SIGUSR1。

除非你在SIGUSR1的SA_MASK中把SIGUSR2屏蔽掉,
那样SIGUSR2来了也只是暂存在队列中,等SIGUSR1处理完了才有机会执行

 

 

 

3.“信号在进程中的注销。在目标进程执行过程中,会检测是否有信号等待处理(每次从系统空间返回到用户空间时都做这样的检查)。”?

        信号的处理是当前进程在中断时,从内核态返回到用户态时要处理的第一件事,进程在从内核态返回用户态时,先检查进程是否有需要处理的信号(就是判断当前进程的进程控制块中的signal信号位图,和blocked阻止位图的相与),如果信号位图与阻止位图相与后,发现有置位的位,就说明当前进程有信号需要处理。此时,先判断是32种信号中的哪种,然后内核会把这个信号的处理函数的地址,作为内核返回用户态时的EIP,因此,当返回用户态后,用户态的执行流程就从信号处理函数开始执行,当执行完了信号处理函数后,再转到发生中断时所保留的下一条语句开始执行。

而进程也许没有其他的中断,但是总会有时钟中断产生,因此,当有信号需要处理,而起进程得到执行时,都会在时钟中断返回时得到执行。

 

 

 

4. sigprocmask函数对不再阻塞信号的返回问题(zz)

< <UNIX环境高级编程>>上说:如果在调用s i g p r o c m a s k后有任何未决的、不再阻塞的信号,则在s i g p r o c m a s k返回前,至少将其中之一递送给该进程。

小弟用下列程序对这句话进行测试:先屏蔽掉SIGUSR1和SIGUSR2信号,然后用kill函数发送他们,再打开它们.
可运行结果却是:
SIGUSR is blocked
SIGUSR1 function
SIGUSR2 function
SIGUSR1 function
SIGUSR is unblocked

为什么会返回两次SIGUSR1函数呢?谢谢回答

程序如下:
#include <stdlib.h>
#include <signal.h>
static void sig_usr1(signo)
{
printf("SIGUSR1 function\n");
}
static void sig_usr2(signo)
{
printf("SIGUSR2 function\n");
}
main()
{
sigset_t newmask,oldmask;
if(signal(SIGUSR1,sig_usr1) <0|signal(SIGUSR2,sig_usr2) <0)
perror("signal\n");
sigemptyset(&newmask);
sigaddset(&newmask,SIGUSR1);
sigaddset(&newmask,SIGUSR2);
sigprocmask(SIG_BLOCK,&newmask,&oldmask);
printf("SIGUSR is blocked\n");
kill(getpid(),SIGUSR2);
kill(getpid(),SIGUSR1);

sigprocmask(SIG_SETMASK,&oldmask,NULL);
}

回复:-------------------------------------------------------------------------------------------

signal是不可靠信号函数,有个"时间窗",漏洞.
具体过程是这样的:

当调用sigprocmask函数放开信号屏蔽后,系统会按循序检查悬挂的的信号,注意,这个顺序不是信号到来的顺序,而是信号值的顺序,即1-64;
当检查到sigusr1时发现有信号,进入sigusr1的信号句柄,内核为这个句柄函数在用户空间建立函数栈,因为信号句柄函数在用户空间,所有当执行时可以被打断,进入sigusr2句柄函数,执行完后,清除sigusr2信号悬挂标志,再检查信号悬挂情况,发现sigusr1信号悬挂标志还有,就再为其建立先后句柄,执行(如果只是返回sigusr1被中断的地方,那么只会打印一次才对??),执行完后.清除标志,再接着,回到用户空间,按正常的函数情况执行,发现有函数栈没有执行,就再执行.



用sigaction设置函数句柄就没问题了.
struct sigaction act1,act2;
act1.sa_handler=sig_usr1;
sigemptyset(&act1.sa_mask);
act2.sa_handler=sig_usr2;
sigemptyset(&act2.sa_mask);
sigaction(SIGUSR1,&act1,NULL);
sigaction(SIGUSR2,&act2,NULL);
为什么用sigaction就没有这个问题??

我在这个问题上也有点糊涂,sigusr1虽然执行了两遍,但好象执行的是同一个栈.没仔细研究过.上述意见仅供参考.??????

-------------------------------补充:

如果调用 sigprocmask 放开某个信号屏蔽而导致任何悬挂的信号被解除阻塞,则在 sigprocmask 返回之前,这些信号中至少有一个被交付。


 

 

 

4.APUE2读书笔记(二):为什么有了wait函数族还需要SIGCHLD信号:

首先,在谈这个问题时,先说说unix中僵尸进程的含义,APUE2中如下定义:
In UNIX System terminology, a process that has terminated, but whose parent has not yet waited for it, is called a zombie.
也就是说,但凡是父进程没有调用wait函数获得子进程终止状态的子进程在终止之后都是僵尸进程,这个概念的关键一点就是父进程是否调用了wait函数.

而关于SIGCHLD信号,APUE2中又如是说:
Whenever a process terminates or stops, the SIGCHLD signal is sent to the parent. By default, this signal is ignored, so the parent must catch this signal if it wants to be notified whenever a child's status changes. The normal action in the signal-catching function is to call one of the wait functions to fetch the child's process ID and termination status.
简单的说,子进程退出时父进程会收到一个SIGCHLD信号,默认的处理是忽略这个信号,而常规的做法是在这个信号处理函数中调用wait函数获取子进程的退出状态.

这里存在一个疑问,既然在SIGCHLD信号的处理函数中要调用wait函数族,为什么有了wait函数族还需要使用SIGCHLD信号?

我们知道,unix中信号是异步处理某事的机制,好比说你准备去做某事,去之前跟邻居张三说如果李四来找你的话就通知他一声,这让你可以抽身出来去做这件事,而李四真正来访时会有人通知你,这个就是异步信号一个较为形象的比喻.

一般的,父进程在生成子进程之后会有两种情况,一种是父进程继续去做别的事情,类似上面举的例子,另一种是父进程啥都不做,一直在wait子进程退出.SIGCHLD信号就是为这第一种情况准备的,它让父进程去做别的事情,而只要父进程注册了处理该信号的函数,在子进程退出时就会调用该函数,在函数中wait子进程得到终止状态之后再继续做父进程的事情.

也就是说,明确以下几点:
1)凡父进程不调用wait函数族获得子进程终止状态的子进程在退出时都会变成僵尸进程.
2)SIGCHLD信号可以异步的通知父进程有子进程退出.

 

总结:

waitpid等不是依靠SIGCHLD是否到达来判断子进程是否退出,但是如果设置了SIGCHLD的处理函数,那么就需要等待SIGCHLD信号的发生并完成信号处理函数,waitpid才能接收到子进程的退出状态。在APUE中的system()实现中阻塞了SIGCHLD信号,但是并没有设置信号处理函数,所以waitpid在阻塞了SIGCHLD的情况下依然能正常返回,因为SIGCHLD在未设置信号处理函数的情况下不会影响到waitpid的工作。至于为什么要阻塞SIGCHLD信号呢?那就是为了防止其他程序(main除了会调用system还会使用其他程序)设置了SIGCHLD的信号处理函数,如果其他程序设置了SIGCHLD信号处理函数,在waitpid等待子程序的返回前,要去处理SIGCHLD信号处理程序,如果阻塞了该信号,就不会去处理该信号处理程序,防止多余信息在system()中的出现。

例如 while(waitpid(pid, &status,0)<0)这里不是通过SIGCHLD,而是父进程不断循环检测子进程pid是否终止来实现的,即不是异步信号机制子进程通知父进程,而是父进程不断循环查询。

 

5.总结:

       中断返回才检测信号是否需要处理;信号处理函数是在用户态执行;原有信号处理函数在执行,新信号产生并又中断(eg时钟中断),那么新信号将抢占嵌套原有信号处理函数(没有阻塞时)。

 

 

 

6.疑惑:

    pause()是只要有信号处理程序返回就返回,所以嵌套的信号处理函数返回pause亦返回,而不用等到最外层的信号处理函数返回才返回??  如果这样,用pause实现sleep,当内部嵌套的信号处理函数返回时,pause返回,sleep返回,调用sleep的函数接着执行,那么原来被嵌套的外部信号处理函数将永远无法执行??

 

 

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值