目录
3、“signal_struct结构体”与“信号集sigset_t”
一、阻塞信号
1、信号的状态
信号递达(Delivery): 实际执行信号所携带指示的操作或行为的过程被称为信号的递达。这意味着当一个信号到达其目标进程时,操作系统会采取相应行动,可能是调用预设的信号处理函数,或者按照默认行为处理信号。
信号未决(Pending): 信号从生成时刻至其被实际处理(即递达)之前的这段时间状态,被称为信号未决状态。在这期间,信号已经产生但尚未对目标进程产生实际影响。
信号阻塞(Block): 进程拥有主动选择暂时不让某些信号生效的能力,这一过程称为阻塞信号。当一个进程决定阻塞某个信号时,即使该信号在此期间产生,也不会立即被执行,而是被置于待处理队列中,继续保持未决状态。
信号阻塞与忽略的区别:
- 阻塞: 当进程阻塞一个信号时,操作系统不会将该信号立即交付给进程,而是将其保存起来。直到进程取消对该信号的阻塞,信号才会从未决状态变为递达状态,并得到执行。
- 忽略: 忽略信号则是在信号递达后的一种处理策略。即使信号已经送达进程,进程也可以选择忽略该信号,即不执行任何特殊的处理动作。相比之下,阻塞侧重于延迟信号的处理,而忽略则是在信号实际递达之后明确地弃置信号,不采取任何应对措施。
2、内核中的信号
信号集(Signal Set)
Linux内核使用sigset_t
数据结构来表示一组信号。这是一个位图结构,其中每一位代表一种信号。例如,如果某个信号编号对应的位被设置为1,则表示该信号处于有效或未屏蔽状态;如果为0,则表示该信号被进程屏蔽或忽略。
task_struct 结构体
对于每个运行在内核中的进程,内核都有一个对应的task_struct
结构体,它是进程控制块(PCB)。在这个结构体中,有几个字段与信号处理相关:
-
sighand
: 这是一个指向sighand_struct
结构体的指针,用于存放信号处理函数。sighand_struct
结构体中包含了一个action[]
数组,用于存放信号对应的处理函数。这里的action[0]
和action[1]
分别代表两种不同的信号处理函数。 -
signal
: 这是一个指向signal_struct
结构体的指针,signal_struct是一个嵌套在task_struct
中的结构体,包含了一系列信号相关的字段,比如sig_blocked
(信号屏蔽集),用于记录当前进程中被阻塞的信号集合,即进程不想立即接收的信号。// kernel/signal.c 或类似的文件中(简化的伪代码) // 定义信号结构体 struct signal_struct { // ... sigset_t blocked; // 阻塞信号集 sigset_t pending; // 未决信号集 struct sigaction sig[NSIG]; // 信号处理动作数组 // ... };
-
sigset_t blocked: 这是一个信号集数据结构,它代表了当前进程中被阻塞(屏蔽)的信号集合。在信号处理机制中,如果一个信号被加入到
blocked
集合中,那么即便该信号已被发送给进程,进程也不会立即对其进行处理,直至后来进程取消对它的阻塞。 -
sigset_t pending: 这也是一个信号集,但它表示的是当前进程中所有已到达但尚未被处理的信号,即未决信号集。当一个信号被发送给进程,且该信号不在进程的阻塞信号集中时,该信号将被添加到
pending
集合中。进程在适当的时候(比如从系统调用返回用户空间时)会检查并处理这些未决信号。 -
struct sigaction sig[NSIG]: 这是一个数组,数组的每个元素对应一种信号,数组的索引号即信号编号。每个
struct sigaction
结构体包含了该信号的处理动作,包括信号处理函数的地址、信号处理模式以及其他与信号处理相关的属性。通过设置这个数组,进程可以为不同的信号指定不同的处理方式。
-
-
pending:
task_struct
中包含一个名为pending
的struct sigpending
类型的成员,它表示所有已经到达但尚未由进程实际处理的信号。pending.signal
是一个未决信号集,记录了所有未决信号。
pending.signal与signal->shared_pending的区别
-
pending.signal:
这个字段位于task_struct
中的struct sigpending pending
结构体内,它表示当前线程(或进程)私有的未决信号集。当一个信号被发送给线程且该信号不在线程的信号屏蔽集中时,并且该进程当前处于某种状态(如被阻塞)而无法立即处理该信号时,该信号会被放入该进程的私有未决信号集pending.signal
中。只有当线程有机会(比如从系统调用返回到用户空间)并检查自己的未决信号时,才会处理这些信号。 - 产生私有未决信号集的情况:
-
进程被阻塞或处于等待状态:
- 当进程处于睡眠状态(例如通过
sleep
、pause
或select
等系统调用)时,发送给它的信号会被放入私有未决信号集中,直到它解除阻塞状态。 - 当进程处于信号处理函数中时,其他信号也可能被放入私有未决信号集中,直到当前信号处理函数执行完毕。
- 当进程处于睡眠状态(例如通过
-
信号被屏蔽:如果进程通过
sigprocmask
设置了信号掩码,使得某些信号被屏蔽,则这些信号会被放入私有未决信号集中,直到信号掩码被更改。
-
-
signal->shared_pending:
这个字段位于task_struct
中的signal_struct
结构体中,它表示与整个进程相关的、所有线程共享的未决信号集。共享未决信号集是指多个进程共享的未决信号集。这种情况通常发生在进程组(Process Group)中,尤其是当信号是针对整个进程组而非单个进程时。当一个信号被发送给进程中的任何一个线程时,如果该信号可以送达,则会被添加到shared_pending
中。这个信号集对于进程中的所有线程都是可见的,并且当任何线程有机会处理信号时,都会考虑到这些共享的未决信号。 -
产生共享未决信号集的情况:
-
进程组:当一个信号发送给一个进程组中的所有进程时,这些信号会被放入共享未决信号集中。例如,当使用
kill -15
向一个进程组发送SIGTERM
信号时,这些信号会被放入进程组的共享未决信号集中。 -
会话首进程(Session Leader):会话首进程(通常是一个终端的前台进程)会接收发送给整个会话的信号。这些信号会被放入共享未决信号集中。
-
struct sigaction
管理信号处理
每个信号还可以关联一个信号处理函数,这个信息通常不是直接在task_struct
中表示,而是通过另一个结构struct sigaction
来管理。sigaction
结构体包含了信号处理函数的地址以及信号处理的一系列属性,如信号处理程序、信号的默认行为、以及是否应重置信号掩码等。
#include <signal.h>
struct sigaction {
void (*sa_handler)(int); // 信号处理函数
sigset_t sa_mask; // 在调用信号处理函数前临时阻塞的信号集
int sa_flags; // 标志位,用于控制信号行为
// 下面这两个字段在Linux内核中可能不存在,但在一些POSIX标准库实现中有定义,
// 它们与`siginfo_t`结构一起提供了更多关于信号的上下文信息。
void (*sa_sigaction)(int, siginfo_t *, void *); // 支持siginfo的信号处理函数
sigset_t sa_restorer; // 在某些架构上用于恢复信号上下文(现已废弃)
};
信号传递与调度
在Linux内核中,信号的传递和调度遵循一套精密的规则。当内核需要向一个进程发送一个信号时,它首先会检查该信号是否出现在进程的信号屏蔽集中。如果信号不在屏蔽范围之内:
-
内核会立即将信号添加至进程的未决信号列表。此时,信号并不会立即触发进程做出反应,而是等待合适的时机处理。
-
根据信号的具体属性,内核会据此调整进程的状态。例如,某些信号可能会要求中断进程正在进行的系统调用,使其提前返回到用户态进行信号处理。
相反,如果信号正处于进程的信号屏蔽集中:
- 内核并不会立即传递此信号,而是选择暂时储存起来。该信号会等待进程解除对该信号的屏蔽状态后,才得以传递并处理。
与此同时,在多线程环境下,信号的处理更为细致:
-
当一个信号被发送给进程时,它首先会被纳入到进程共享的
shared_pending
链表中,意味着所有隶属于该进程的线程都能感知到此信号的存在。 -
更进一步,如果该信号已被进程中的某个特定线程注册了特定的处理函数,那么除了加入到共享链表外,该信号还会被添加至该线程的私有未决信号列表(即
pending
链表)。
这样一来,当线程进入到可以安全处理信号的状态(如从繁忙状态转为空闲状态),它会主动检查并处理自己pending
链表中的所有未决信号,从而确保信号得到了及时有效的响应和处理。
3、“signal_struct
结构体”与“信号集sigset_t”
在Linux内核的实现中,信号集通常由
sigset_t
类型表示,该类型内部就是一个位图结构。通过位运算,可以方便地查询、设置和清除信号集中的某一位,以此来表示信号是否被阻塞或是否处于未决状态。
在系统层面,每个信号还各自配备了用于表示其未决状态和阻塞状态的独立比特位,这两种状态只有“开启”(值为1)或“关闭”(值为0)