人们很容易高估某个决定性时刻的重要性,也很容易低估每天进行微小改进的价值。以前我也以为大规模的成功需要大规模的行动,现在我不这么认为了。长期来看,由于复利效果,一点小小的改进就能产生惊人的变化。
还有一点值得注意的情况,大多数人有了家庭和子女后,并且现在国内盛行加班文化,很难再集中精力能抽出大块的时间进行学习了,部分还能坚持学习的人几乎都是以牺牲睡眠时间为代价的,我个人不太认为这种做法,我始终认为有更合理健康的方法能形成一个工作、生活、学习、娱乐的有效循环,或许认识到 微进步 的重要性就是一个很好的开始吧。
本文就是我的微进步,欢迎阅读。
信号有时被称为提供处理异步事件机制的软件中断,与硬件中断的相似之处在于打断了程序执行的正常流程,很多比较重要的应用程序都需处理信号。事件可以来自于系统外部,例如用户按下 Ctrl+C,或者来自程序或者内核的某些操作。作为一种进程间通信 (IPC) 的基本形式,进行可以给另一个进程发送信号。
信号很早就是 Unix 的一部分。随着时间的推移,信号有了很大的改进。比如在可靠性方面,之前的信号可能会出现丢失的情况。在功能方面,现在信号可以携带用户定义的附加信息。最初,不同的 Unix 系统对信号的修改,后来,POSIX 标准的到来挽救并且标准化了信号机制。
用术语 raise 表示一个信号的产生,catch 表示接收到一个信号。
事件的发生是异步的,程序对信号的处理也是异步的。
信号可以被生成、捕获、响应或忽略。有两种信号不能被忽略:SIGKILL 和 SIGSTOP。不能被忽略的原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。
信号类型:
$ man 7 signal
DESCRIPTION
Standard signals
First the signals described in the original POSIX.1-1990 standard.
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
SIGABRT 6 Core Abort signal from abort(3)
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.
Next the signals not in the POSIX.1-1990 standard but described in SUSv2 and POSIX.1-2001.
Signal Value Action Comment
────────────────────────────────────────────────────────────────────
SIGBUS 10,7,10 Core Bus error (bad memory access)
SIGPOLL Term Pollable event (Sys V).
Synonym for SIGIO
SIGPROF 27,27,29 Term Profiling timer expired
SIGSYS 12,31,12 Core Bad argument to routine (SVr4)
SIGTRAP 5 Core Trace/breakpoint trap
SIGURG 16,23,21 Ign Urgent condition on socket (4.2BSD)
SIGVTALRM 26,26,28 Term Virtual alarm clock (4.2BSD)
SIGXCPU 24,24,30 Core CPU time limit exceeded (4.2BSD)
SIGXFSZ 25,25,31 Core File size limit exceeded (4.2BSD)
...
Next various other signals.
Signal Value Action Comment
────────────────────────────────────────────────────────────────────
SIGIOT 6 Core IOT trap. A synonym for SIGABRT
SIGEMT 7,-,7 Term
SIGSTKFLT -,16,- Term Stack fault on coprocessor (unused)
SIGIO 23,29,22 Term I/O now possible (4.2BSD)
SIGCLD -,-,18 Ign A synonym for SIGCHLD
SIGPWR 29,30,19 Term Power failure (System V)
SIGINFO 29,-,- A synonym for SIGPWR
SIGLOST -,-,- Term File lock lost (unused)
SIGWINCH 28,28,20 Ign Window resize signal (4.3BSD, Sun)
SIGUNUSED -,31,- Core Synonymous with SIGSYS
(Signal 29 is SIGINFO / SIGPWR on an alpha but SIGLOST on a sparc.)
发送信号:
如果想发送一个信号给进程,而该进程并不是当前的前台进程,就需要使用kill 命令。
kill 命令有一个有用的变体叫 killall,它可以给运行着某一命令的所有进程发送信号。
处理信号:
Unix 系统提供了两种方法来改变信号处置:signal() 和 sigaction()。signal()系统调用是设置信号处置的原始 API,所提供的接口比sigaction() 简单。另一方面,sigaction() 提供了 signal() 所不具备的功能。进一步而言,signal() 的行为在不同 Unix 实现间存在差异,这意味着对可移植性有所追求的程序绝不能使用此调用来建立信号处理函数 (signal handler)。故此,sigaction()是建立信号处理器的首选API。
由于可能会在许多老程序中看到 signal() 的应用,我们先了解如何用 signal() 函数来处理信号。
signal() 的定义:
$ man 2 signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数1 signum 指定希望修改 handler 的信号编号,参数2 handler,则指定信号抵达时所调用的 signal handler 函数的地址。
成功,返回以前的信号处理函数;出错,返回 SIG_ERR;
简单试用 signal()。
分解代码:
static void ouch(int sig) {
printf("OUCH! - I got signal %d\n", sig);
(void) signal(SIGINT, SIG_DFL);
}
int main() {
(void) signal(SIGINT, ouch);
while(1) {
printf("Hello World!\n");
sleep(1);
}
}
运行效果:
$ ./ctrlc1
Hello World!
Hello World!
^COUCH! - I got signal 2
Hello World!
Hello World!
相关要点:
在信号处理函数中,调用如 printf 这样的函数是不安全的。一般的做法是:在信号处理函数中设置一个标志,然后在主程序中检查该标志,如需要就打印一条消息。
如果想保留信号处理函数,让它继续响应用户的 Ctrl+C 组合键,我们就需要再次调用 signal 函数来重新建立它。这会使信号在一段时间内无法得到处理,这段时间从调用中断函数开始,到信号处理函数的重建为止。如果在这段时间内程序接收到第二个信号,它就会违背我们的意愿终止程序的运行。
不推荐使用 signal 接口。之所以介绍它,是因为可能会在许多老程序中看到它的应用。更清晰、执行更可靠的函数: sigaction(),在所有的新程序中都应该使用这个函数,暂不做深入介绍。
进程可以通过调用 kill 函数向包括它本身在内的其他进程发送一个信号。
kill():
$ man 2 kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
把参数 sig 给指定的信号发送给由参数 pid 指定的进程号所指定的进程。
kill 调用会在失败时返回 -1 并设置 errno 变量,失败的原因:
给定的信号无效(errno设置为EINVAL);
发送进程权限不够(errno设置为EPERM);
目标进程不存在(errno设置为ESRCH);
关于权限:
要想发送一个信号,发送进程必须拥有相应的权限,包括2种情况:
两个进程必须拥有相同的用户 ID,即你只能发送信号给属于自己的进程;
超级用户可以发送信号给任何进程;
进程可以通过调用 alarm() 函数在经过预定时间后发送一个 SIGALRM 信号。
alarm():
$ man 2 alarm
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
在 seconds 秒之后发送一个 SIGALRM 信号。
返回值是以前设置的闹钟时间的余留秒数,如果调用失败则返回 -1。
相关要点:
由于处理的延时和时间调度的不确定性,实际闹钟时间将比预先安排的要稍微拖后一点儿。
把参数 seconds 设置为 0 将取消所有已设置的闹钟请求。
如果在接收到 SIGALRM 信号之前再次调用 alarm() 函数,则闹钟重新开始计时
每个进程只能有一个闹钟时间。
用 kill() 模拟闹钟。
分解代码:
设置 signal handler:
int main()
{
pid_t pid;
printf("alarm application starting\n");
pid = fork();
switch(pid) {
case -1:
/* Failure */
perror("fork failed");
exit(1);
case 0:
/* child */
sleep(5);
kill(getppid(), SIGALRM);
exit(0);
}
/* parent */
printf("waiting for alarm to go off\n");
(void) signal(SIGALRM, ding);
pause();
if (alarm_fired)
printf("Ding!\n");
printf("done\n");
exit(0);
}
定义 signal handler:
static int alarm_fired = 0;
static void ding(int sig)
{
alarm_fired = 1;
}
通过 fork 调用启动新的进程:子进程休眠 5 秒后向其父进程发送一个 SIGALRM 信号。父进程在安排好捕获 SIGALRM 信号后暂停运行,直到接收到一个信号为止。
运行效果:
$ ./alarm
alarm application starting
waiting for alarm to go off
<等待5 秒钟>
Ding!
done
相关要点:
pause() 把程序的执行挂起直到有一个信号出现为止。使用信号并挂起程序的执行是 Unix 程序设计中的一个重要部分。
$ man 2 pause
#include <unistd.h>
int pause(void);
当它被一个信号中断时,将返回 -1(如果下一个接收到的信号没有导致程序终止的话)并把 errno 设置为 EINTR。
更常见的方法是使用 sigsuspend() 函数,暂不做介绍。
在信号处理函数中没有调用 printf,而是通过设置标志,然后在main函数中检查该标志来完成消息的输出。
如果信号出现在系统调用的执行过程中会怎么样?
$ man 3 errno
EINTR
Interrupted function call (POSIX.1); see signal(7).
如果你开始在自己的程序中使用信号,就需要注意一些系统调用会因为接收到了一个信号而失败。
我们需要更健壮的信号接口:
在编写程序中处理信号部分的代码时必须非常小心,因为在使用信号的程序中会出现各种各样的“竞态条件”。例如,如果想调用pause等待一个信号,可信号却出现在调用 pause() 之前,就会使程序无限期地等待一个不会发生的事件。
POSIX 标准推荐了一个更新和更健壮的信号编程接口:sigaction。
三、信号集 (Signal Set)
多个信号可使用一个称之为信号集的数据结构来表示,POSIX.1 定义了数据类型 sigset_t 以表示一个信号集,并且定义了下列 5 个处理信号集的函数:
$ man 3 sigemptyset
NAME
sigemptyset, sigfillset, sigaddset, sigdelset, sigismember - POSIX signal set operations
SYNOPSIS
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
函数 sigemptyset() 初始化由参数 set 指向的信号集,清除其中所有信号。
函数 sigfillset() 初始化由参数 set 指向的信号集,使其包括所有信号。
必须使用 sigemptyset() 或者 sigfillset() 来初始化信号集。这是因为 C 语言不会对自动变量进行初始化,并且,借助于将静态变量初始化为 0 的机制来表示空信号集的作法在可移植性上存在问题,因为有可能使用位掩码之外的结构来实现信号集。
函数 sigaddset() 将一个信号添加到已有的信号集中,sigdelset() 则从信号集中删除一个信号。
sigismember() 函数用来测试信号 sig 是否是信号集 set 的成员。
每个进程都有一个信号屏蔽字(或称信号掩码,signal mask),它规定了当前要阻塞递送到该进程的信号集。对于每种信号,屏蔽字中都有一位与之对应。对于某种信号,若其对应位被设置,则它当前是被阻塞的。进程可以调用 sigprocmask() 检测或更改,或同时进行检测和更改进程的信号屏蔽字。
向信号屏蔽字中添加信号的3种方式:
当调用信号处理器 (signal handler) 时,可能会引发信号自动添加到信号屏蔽字中的行为,暂不作深入介绍。
使用 sigaction() 函数建立信号处理器时,可以指定一组信号集,当调用该处理器时会将该信号集里的信号阻塞,暂不作深入介绍。
使用sigprocmask()系统调用,可以随时显式地向信号屏蔽字中添加或移除信号。
先来了解 sigprocmask():
$ man 2 sigprocmask
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
相关知识点:
sigprocmask() 既可用于修改 进程的信号屏蔽字,也可用于获取现有的屏蔽字,或者同时执行这2个操作。
参数 how 指定了 sigprocmask() 该如何操作信号屏蔽字。
若 set 参数不为空,则其指向一个 sigset_t 缓冲区,用于返回之前的信号屏蔽字。
SUSv3 规定,如果有任何正在等待的信号 (pending signals) 因调用了 sigprocmask() 解除了锁定,那么在此调用返回前至少会传递一次这些信号。
系统将忽略试图阻塞 SIGKILL 和 SIGSTOP 信号的请求。如果试图阻塞这些信号,sigprocmask() 既不会予以关注,也不会产生错误。
常见的使用方法:
sigset_t blockSet, prevMask;
sigemptyset(&blockSet);
/* 1. Block SIGINT, save previous signal mask */
sigaddset(&blockSet, SIGINT);
if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1)
errExit("sigprocmask1");
/* 2. Code that should not be interrupted by SIGINT */
/* 3. Restore previous signal mask, unblocking SIGINT */
if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
errExit("sigprocmask2");
main() 函数:
1> 为所有信号注册同一个信号处理函数,用于验证信号集是否被成功屏蔽:
static void handler(int sig)
{
if (sig == SIGINT)
gotSigint = 1;
else
sigCnt[sig]++;
}
int main(int argc, char *argv[])
{
int n, numSecs;
sigset_t fullMask, emptyMask;
printf("%s: PID is %ld\n", argv[0], (long) getpid());
for (n = 1; n < NSIG; n++)
(void) signal(n, handler); // UNSAFE
...
}
注意:siganl() 是不可靠的,这里为了简化程序而采用该接口。
2> 初始化信号集,然后屏蔽所有信号:
sigfillset(&fullMask);
if (sigprocmask(SIG_SETMASK, &fullMask, NULL) == -1) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
printf("%s: sleeping for %d seconds\n", argv[0], numSecs);
sleep(numSecs);
先屏蔽所有的信号,然后睡眠。睡眠期间,进程无法响应除 SIGSTOP 和 SIGKILL 之外的任何信号。
3> 睡眠结束后,用空信号集来解除所有的信号屏蔽:
sigemptyset(&emptyMask); /* Unblock all signals */
if (sigprocmask(SIG_SETMASK, &emptyMask, NULL) == -1) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
while (!gotSigint) /* Loop until SIGINT caught */
continue;
for (n = 1; n < NSIG; n++)
if (sigCnt[n] != 0)
printf("%s: signal %d caught %d time%s\n", argv[0], n,
sigCnt[n], (sigCnt[n] == 1) ? "" : "s");
exit(EXIT_SUCCESS);
}
解除了对某个等待信号的屏蔽后,系统会立刻将该信号传递一次给进程。
打印信号集 printSigset():
void printSigset(FILE *of, const char *prefix, const sigset_t *sigset)
{
int sig, cnt;
cnt = 0;
for (sig = 1; sig < NSIG; sig++) {
if (sigismember(sigset, sig)) {
cnt++;
fprintf(of, "%s%d (%s)\n", prefix, sig, strsignal(sig));
}
}
if (cnt == 0)
fprintf(of, "%s<empty signal set>\n", prefix);
}
3. 运行效果:
屏蔽期间多次按下 ctrl + c (发送 SIGINT):
$ ./signal_set 5
./signal_set: PID is 18375
blocked:1 (Hangup)
blocked:2 (Interrupt)
blocked:3 (Quit)
...
blocked:64 (Real-time signal 30)
./signal_set: sleeping for 5 seconds
^C^C^Cblocked:<empty signal set>
./signal_set: signal 2 caught 1 time
在信号被屏蔽的 5 秒期间,连续按下 3 次 ctrl + c,所有信号都不会被处理。当过了 5 秒后,解除信号屏蔽,仅仅有一次 SIGINT 信号被成功地传递并处理。
如果某进程接受了一个该进程正在阻塞的信号,那么会将该信号填加到进程的等待信号集中。当解除对该信号的锁定时,会随之将信号传递给此进程。为了确定进程中处于等待状态的是哪些信号,可以使用 sigpending()。
$ man 2 sigpending
NAME
sigpending, rt_sigpending - examine pending signals
SYNOPSIS
#include <signal.h>
int sigpending(sigset_t *set);
DESCRIPTION
sigpending() returns the set of signals that are pending for delivery to the calling thread (i.e., the signals
which have been raised while blocked). The mask of pending signals is returned in set.
sigpending() 为调用进程返回处于等待状态的信号集,并将其置于 set 指向的sigset_t 中。
相关知识点:
如果某进程接受了一个该进程正在阻塞 (blocking) 的信号,那么会将该信号填加到进程的 等待信号集 (set of pending signals) 中。当解除对该信号的阻塞时,会随之将信号传递给此进程。可以使用 sigpending() 确定进程中处于等待状态的是哪些信号。
$ man 2 sigpending
#include <signal.h>
int sigpending(sigset_t *set);
sigpending() 为调用进程返回处于等待状态的信号集,并将其置于参数 set 指向的 sigset_t 中。
1) 分解代码:
1> main():
int main(void)
{
sigset_t newmask, oldmask, pendmask;
if (signal(SIGQUIT, sig_quit) == SIG_ERR)
err_sys("can't catch SIGQUIT");
/* Block SIGQUIT and save current signal mask. */
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
/* SIGQUIT here will remain pending */
sleep(5);
if (sigpending(&pendmask) < 0)
err_sys("sigpending error");
if (sigismember(&pendmask, SIGQUIT))
printf("\nSIGQUIT pending\n");
/* Restore signal mask which unblocks SIGQUIT. */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
printf("SIGQUIT unblocked\n");
/* SIGQUIT here will terminate with core file */
sleep(5);
exit(0);
}
main() 做了 5 件事:
注意:在设置 SIGQUIT 为阻塞时,我们保存了老的屏蔽字。为了解除对该信号的阻塞,用老的屏蔽字重新设置了进程信号屏蔽字。另一种方法是用 SIG_UNBLOCK 使阻塞的信号不再阻塞。如果编写一个可能由其他人使用的函数,而且需要在函数中阻塞一个信号,则不能用 SIG_UNBLOCK 简单地解除对此信号的阻塞,这是因为此函数的调用者在调用本函数之前可能也阻塞了此信号。
2> 信号处理函数 sig_quit():
static void sig_quit(int signo)
{
printf("caught SIGQUIT\n");
if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
err_sys("can't reset SIGQUIT");
}
2) 运行效果:
$ ./sig_pending
^\ // 按下 1 次 ctrl + \ (在5s之内)
SIGQUIT pending // 从 sleep(5) 返回后
caught SIGQUIT // 在信号处理程序中
SIGQUIT unblocked // 从sigprocmask() 返回
^\Quit (core dumped)
2 个值得注意的点:
信号处理函数是在 sigprocmask() unblock 信号返回之前被调用;
用 signal() 设置信号处理函数,信号被处理时,会将信号处置重置为其默认行为。要想在同一信号“再度光临”时再次调用该信号处理器函数,程序员必须在信号处理器内部调用signal(),以显式重建处理器函数,但是这种处理方式是不安全的,真实的项目里应使用 sigaction(),后续的文章会举例讲解。
七、不对待处理的信号进行排队处理
等待信号集只是一个掩码,仅表明一个信号是否发生,而未表明其发生的次数。换言之,如果同一信号在阻塞状态下产生多次,那么会将该信号记录在等待信号集中,并在稍后仅传递一次。后面会介绍实时信号,对实时信号所采取的是队列化管理。如果将某一实时信号的多个实例发送给一进程,那么将会多次传递该实时信号,暂不做深入介绍。
为了降低学习难度,跟前面的 Pending Signals 章节使用同一个例子,修改一下测试步骤:
$ ./sig_pending
^\^\^\ // 按下 3 次 ctrl + \ (在5s之内)
SIGQUIT pending // 从 sleep(5) 返回后
caught SIGQUIT // 只调用了一次信号处理程序
SIGQUIT unblocked // 从sigprocmask() 返回
^\Quit (core dumped)
第二次运行该程序时,在进程休眠期间产生了 3 次 SIGQUIT 信号,但是取消对该信号的阻塞后,系统只向进程传送了一次 SIGQUIT,从中可以看出在 Linux 系统上没有对信号进行排队处理。
1) 相关数据结构
内核用 struct task_struct 来描述一个进程,struct task_struct 中信号相关的成员 (Linux-4.14):
<sched.h>
struct task_struct {
...
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked;
sigset_t real_blocked;
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
...
};
我们将注意力集中中 struct sigpending pending 上。struct sigpending pending 建立了一个链表,该链表包含了所有已经产生、且有待内核处理的信号,其定义如下:
struct sigpending {
struct list_head list;
sigset_t signal;
};
成员 struct list_head list 通过双向链表管理所有待处理信号,每一种待处理的信号对应双向链表中的 1 个 struct sigqueue 节点。
成员 sigset_t signal 是位图 (bit mask,或称位掩码),它指定了仍然有待处理的所有信号的编号。某 1 bit = 1 表示该 bit 对应的信号待处理。sigset_t 所包含的比特位数目要 >= 所支持的信号数目。因此,内核使用了 unsigned long 数组来定义该数据类型:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t;
struct sigqueue {
struct list_head list;
int flags;
siginfo_t info;
...
};
注意:在 struct sigpending 链表中,struct sigqueue 对应的是一种类型的待处理信号,而不是某一个具体的信号。
示意图:
2) 信号的产生
当给进程发送一个信号时,这个信号可能来自内核,也可能来自另外一个进程。
内核里有多个 API 能产生信号,这些 API 最终都会调用 send_signal()。我们重点关注信号是何时被设置为 pending 状态的。
linux/kernel/signal.c:
send_signal()
__send_signal()
struct sigqueue *q = __sigqueue_alloc();
list_add_tail(&q->list, &pending->list); // 将待处理信号添加到 pending 链表中
sigaddset(&pending->signal, sig); // 在位图中将信号对应的 bit 置 1
complete_signal(sig, t, group);
signal_wake_up();
send_signal() 会分配一个新的 struct sigqueue 实例,然后为其填充信号的额外信息,并添加到目标进程的 sigpending 链表且设置位图。
如果信号成功发送,没有被阻塞,就可以用 signal_wake_up() 唤醒目标进程,使得调度器可以选择目标进程运行。
3) 信号的传递:
这些知识放在这篇文章里已经完全超纲了,如果将所有的细节都暴露出来会让初学者感到极度的困惑。
所以,我们只迈出一小步,将仅剩的一点注意力集中在内核在执行信号处理函数前是如何处理 pending 信号的。
在每次由内核态切换到用户态时,内核都会进行信号处理,最终的效果就是调用 do_signal() 函数。
linux/kernel/signal.c:
do_signal()
get_signal()
dequeue_signal(current, ¤t->blocked, &ksig->info);
handle_signal()
signal_setup_done();
signal_delivered();
dequeue_signal()
int sig = next_signal(pending, mask);
collect_signal(sig, pending, info, resched_timer);
sigdelset(&list->signal, sig); // 取消信号的 pending 状态
list_del_init(&first->list); // 删除 pending 链表中的 struct sigqueue 节点
copy_siginfo(info, &first->info);
handle_signal() 会操作进程在用户态下的栈,使得在从内核态切换到用户态之后运行信号处理程序,而不是正常的程序代码。
do_signal() 返回时,信号处理函数就会被执行。
七、相关参考
你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。如果你也对 嵌入式系统和开源软件 感兴趣,并且想和更多人互相交流学习的话,请关注我的公众号:嵌入式系统砖家,一起来学习吧,无论是 关注或转发 ,还是赏赐,都是对作者莫大的支持,谢谢 各位的大拇指 ,祝工作顺利,家庭和睦~
-END-
推荐阅读