Linux学习条记:
https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482
媒介:
在前面我们已经学习了有关信号的一些根本的知识点,包括:信号的概念、信号产生和信号处理等,今天我们重点来讲解一下信号在内核中的处理以及信号捕捉的相关知识点
在这篇文章中,我们将深入探讨 Linux 信号在内核中的处理流程,详细讲解信号递达、信号阻塞、未决信号、信号集操纵、信号捕捉等内容,并通过大量的代码示例和现实场景来展示信号如安在 Linux 中运作。
与信号有关的还有一个很紧张的知识点是有关用户态、内核态和状态切换的知识,本篇没有进行讲解,需要自己再去了解一下
目次
1. 信号在内核中的处理流程
1.1 信号在内核中的表示
1.2 信号的递达机制
信号递达的条件
信号递达过程
示例代码:信号递达
1.3 信号未决状态
信号未决队列的管理
示例代码:查察未决信号
1.4 信号集与 sigset_t
信号集的操纵
示例代码:操纵信号集
编辑
1.5 信号屏蔽与 sigprocmask
示例代码:利用 sigprocmask() 阻塞和解除阻塞信号
1.6 获取未决信号:sigpending()
示例代码:利用 sigpending() 查察未决信号
2. 信号捕捉与处理
2.1 利用 signal() 捕捉信号
示例代码:利用 signal() 捕捉信号
2.2 利用 sigaction() 捕捉信号
示例代码:利用 sigaction() 捕捉信号
3. 总结
1. 信号在内核中的处理流程
信号是由内核或其他进程通过系统调用发送给目标进程的。当进程正在执行时,信号可以或许在不干扰进程当前操纵的环境下打断它的执行,触发某种特定的举动。信号的处理流程在 Linux 内核中被设计得非常灵活,既支持异步信号处理,又能通过进程的信号屏蔽机制来控制信号的递达。
1.1 信号在内核中的表示
信号在内核中的表示表示图:
- 每个信号都有两个标记位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标记,直到信号递达才清除该标记。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
- SIGINT信号产生过,但正在被阻塞,以是暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,由于进程仍有机会改变处理动作之后再解除阻塞。
- SIGOUIT信号未产生过,一旦产生SIGOUIT信号将被阻塞,它的处理动作是用户自界说函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将怎样处理?POSIX1答应系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。
1.2 信号的递达机制
信号递达是信号机制中的焦点概念,它是信号从信号源发送到目标进程的过程。信号递达的实现依靠于内核的进程调度机制。在进程执行过程中,内核需要判断该进程是否有需要处理的未决信号,信号的递达会在进程的上下文切换时被触发。
信号递达的条件
信号的递达取决于以下几个因素:
- 信号是否被屏蔽:每个进程都可以选择性地阻塞某些信号,当信号被阻塞时,它们会进入未决状态,直到信号被解除阻塞。
- 进程的当前状态:信号递达的时机还受进程状态的影响。如果进程处于不可停止的状态(例如执行系统调用),它大概无法立刻处理信号,这时信号会被推迟递达,直到进程可以或许响应信号为止。
- 信号范例:标准信号和实时信号在递达的优先级上大概存在差异。实时信号(编号从 SIGRTMIN 开始)通常会比标准信号更快地递达,并且可以或许提供更多的控制选项。
信号递达过程
信号的递达过程通常包括以下几个步骤:
- 信号的发送:信号可以通过内核发送(例如内核变乱或系统调用)或通过其他进程调用 kill() 函数发送。
- 信号的处理查抄:当一个进程正在被调度执行时,内核会查抄该进程是否有未决的信号。如果存在未决信号,内核会查察进程的信号屏蔽字,以决定这些信号是否可以递达。
- 信号的递送:如果信号未被屏蔽且可以或许递达,内核会根据进程的信号处理方式来决定是执行默认动作还是调用信号处理函数。
示例代码:信号递达
- #include <stdio.h>
- #include <signal.h>
- #include <unistd.h>
- void signal_handler(int sig) {
- printf("Received signal: %d\n", sig);
- }
- int main() {
- signal(SIGINT, signal_handler); // 捕捉 SIGINT 信号
- printf("Waiting for SIGINT...\n");
- while(1) {
- sleep(1); // 进入等待状态,直到接收到信号
- }
- return 0;
- }
复制代码 在上面的代码中,进程会不停运行并等候 SIGINT 信号(通常由按下 Ctrl+C 触发)。一旦进程吸取到 SIGINT 信号,内核会将其递送到进程,并触发信号处理函数 signal_handler
1.3 信号未决状态
当信号发送给进程时,如果该信号被进程的信号屏蔽字阻塞,那么该信号就会进入未决状态。未决信号是那些已经被发送但尚未被递达的信号。内核维护了每个进程的未决信号队列,并会在进程解除对该信号的阻塞时按次序递送这些信号。
信号未决队列的管理
在 Linux 内核中,每个进程都有一个 task_struct 结构体,其中包含了当进步程的未决信号聚集。每当一个信号发送给一个进程时,如果该信号被阻塞,内核不会立刻递送它,而是将其存放在进程的未决信号队列中,直到进程解除对该信号的阻塞。
未决信号通常在进程解除信号屏蔽字后,由内核递送。递送次序通常与信号发送次序一致,且会按照优先级递送实时信号和标准信号。
示例代码:查察未决信号
- #include <stdio.h>
- #include <signal.h>
- #include <unistd.h>
- int main() {
- sigset_t pending;
- sigpending(&pending); // 获取当前进程的未决信号
-
- if (sigismember(&pending, SIGINT)) {
- printf("SIGINT is pending.\n");
- } else {
- printf("No SIGINT pending.\n");
- }
- sleep(10); // 稍作停顿,方便查看信号状态
- return 0;
- }
复制代码 通过利用 sigpending() 函数,我们可以查察当进步程的未决信号集。如果进程没有处理 SIGINT 信号,且信号被阻塞,则该信号会处于未决状态。
1.4 信号集与 sigset_t
信号集(sigset_t)是一个用于表示信号聚集的数据结构,它通过位掩码的方式表示进程当前可以接受的信号聚集。sigset_t 通常是一个整数或更大的数据范例,每一位对应一个信号。
信号集的操纵
在 Linux 中,常用的信号集操纵函数包括:
- sigemptyset():初始化信号集为空集。
- sigaddset():将某个信号添加到信号集中。
- sigdelset():将某个信号从信号集中删除。
- sigismember():判断某个信号是否在信号集中。
示例代码:操纵信号集
- #include <signal.h>
- #include <stdio.h>
- int main() {
- sigset_t set;
- sigemptyset(&set); // 初始化为空集
- sigaddset(&set, SIGINT); // 将 SIGINT 添加到信号集中
- sigaddset(&set, SIGTERM); // 将 SIGTERM 添加到信号集中
- if (sigismember(&set, SIGINT)) {
- printf("SIGINT is in the set.\n");
- }
- sigdelset(&set, SIGINT); // 从信号集中删除 SIGINT
- if (!sigismember(&set, SIGINT)) {
- printf("SIGINT is no longer in the set.\n");
- }
- return 0;
- }
复制代码
1.5 信号屏蔽与 sigprocmask
sigprocmask() 是一个用于修改进程信号屏蔽字的系统调用,它可以用来阻塞、解除阻塞或查询进程的信号屏蔽字。信号屏蔽字界说了哪些信号是被阻塞的,从而影响信号递达的时机。
sigprocmask() 具有以下操纵模式:
- SIG_BLOCK:将指定的信号添加到信号屏蔽字中,阻塞这些信号。
- SIG_UNBLOCK:从信号屏蔽字中删除指定信号,解除阻塞。
- SIG_SETMASK:将信号屏蔽字设置为指定值,替换当前的信号屏蔽字。
示例代码:利用 sigprocmask() 阻塞和解除阻塞信号
- #include <signal.h>
- #include <stdio.h>
- #include<unistd.h>
- int main() {
- sigset_t new_mask, old_mask;
- sigemptyset(&new_mask);
- sigaddset(&new_mask, SIGINT); // 阻塞 SIGINT
- sigprocmask(SIG_BLOCK, &new_mask, &old_mask); // 阻塞 SIGINT
- // 信号屏蔽后,可以进行一些操作
- printf("SIGINT is blocked. Press Ctrl+C to send SIGINT.\n");
- sleep(10); // 暂停,等待 Ctrl+C 输入
- // 恢复信号屏蔽字
- sigprocmask(SIG_SETMASK, &old_mask, NULL); // 恢复原信号屏蔽字
- printf("SIGINT is unblocked.\n");
- sleep(10); // 等待信号递达
- return 0;
- }
复制代码 在这段代码中,SIGINT 信号在前 10 秒内被阻塞,用户按下 Ctrl+C 时信号不会立刻递达。10 秒后,信号屏蔽被解除,SIGINT 信号会被递送并触发相应的处理。
1.6 获取未决信号:sigpending()
sigpending() 函数用于获取当进步程的未决信号,它返回一个信号集,表示该进程尚未处理的信号聚集。sigpending() 的实现依靠于进程的信号队列,它可以用于调试和监控进程的信号处理状态。
示例代码:利用 sigpending() 查察未决信号
- #include <signal.h>
- #include <stdio.h>
- #include <unistd.h>
- int main() {
- sigset_t pending;
- sigemptyset(&pending);
-
- // 阻塞 SIGINT 信号
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGINT);
- sigprocmask(SIG_BLOCK, &mask, NULL);
- // 发送 SIGINT 信号,但它会被阻塞
- kill(getpid(), SIGINT);
- // 获取未决信号
- sigpending(&pending);
- if (sigismember(&pending, SIGINT)) {
- printf("SIGINT is pending.\n");
- } else {
- printf("No SIGINT pending.\n");
- }
- sleep(5); // 稍作等待,防止信号丢失
- return 0;
- }
复制代码 此步伐模拟了阻塞 SIGINT 信号并通过 sigpending() 查察进程的未决信号状态。如果信号被阻塞,它将在信号屏蔽字解除后递达。
2. 信号捕捉与处理
信号捕捉是指进程通过自界说信号处理函数来响应特定的信号。Linux 提供了 signal() 和 sigaction() 两种方式来捕捉信号。signal() 是一种简单的接口,而 sigaction() 提供了更为复杂的配置选项,使得开发者可以或许在处理信号时得到更多的控制权。
2.1 利用 signal() 捕捉信号
signal() 是最基础的信号捕捉方式,它答应开发者指定一个信号处理函数来响应特定信号。signal() 的利用非常简单,但它并不支持所有高级功能,如信号的重入处理或复杂的信号控制。
示例代码:利用 signal() 捕捉信号
- #include <signal.h>
- #include <stdio.h>
- #include<unistd.h>
- void signal_handler(int sig) {
- printf("Received signal: %d\n", sig);
- }
- int main() {
- signal(SIGINT, signal_handler); // 捕捉 SIGINT 信号
- printf("Waiting for SIGINT...\n");
- while (1) {
- sleep(1); // 程序将一直运行,直到接收到信号
- }
- return 0;
- }
复制代码 在这个示例中,signal_handler 函数会在吸取到 SIGINT 信号时被调用。
2.2 利用 sigaction() 捕捉信号
sigaction() 提供了比 signal() 更灵活的方式来处理信号。它答应开发者在捕捉信号时设定更多的参数,比如怎样处理重入信号、是否需要恢复默认举动等。
sigaction() 的结构体界说如下:
- struct sigaction {
- void (*sa_handler)(int);
- sigset_t sa_mask;
- int sa_flags;
- void (*sa_restorer)(void);
- };
复制代码
- sa_handler:指定信号处理函数。
- sa_mask:指定在信号处理期间需要阻塞的信号集。
- sa_flags:设定信号处理的举动。
示例代码:利用 sigaction() 捕捉信号
- #include <signal.h>
- #include <stdio.h>
- #include<unistd.h>
- void signal_handler(int sig) {
- printf("Received signal: %d\n", sig);
- }
- int main() {
- struct sigaction sa;
- sa.sa_handler = signal_handler; // 指定信号处理函数
- sa.sa_flags = 0;
- sigemptyset(&sa.sa_mask); // 初始化信号集
- sigaction(SIGINT, &sa, NULL); // 捕捉 SIGINT 信号
- printf("Waiting for SIGINT...\n");
-
- while (1) {
- sleep(1); // 程序将一直运行,直到接收到信号
- }
- return 0;
- }
复制代码 通过 sigaction(),步伐可以或许灵活地处理信号,并控制信号捕捉的举动,甚至答应在处理信号时阻塞其他信号。
3. 总结
本文我们讲解了信号的处理机制,并且对信号捕捉进行了更详细的增补,结合上篇内容,根本上将信号部门的内容进行了大概的讲解,认真看一下相信会对你有所帮助
感谢各位大佬观看,创作不易,还望各位大佬点赞支持!!!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |