怎么在建筑网站做翻译兼职,网页设计下载方式,进了网站的后台系统 怎么改公司的网站,深圳外发加工网信号是 Linux 操作系统中非常重要的进程控制机制#xff0c;用来异步通知进程发生某种事件。理解信号的产生、阻塞、递达、捕捉等概念#xff0c;可以帮助开发者更好地编写健壮的应用程序#xff0c;避免由于未处理的信号导致程序异常退出。本文将带你从基础概念开始#x…
信号是 Linux 操作系统中非常重要的进程控制机制用来异步通知进程发生某种事件。理解信号的产生、阻塞、递达、捕捉等概念可以帮助开发者更好地编写健壮的应用程序避免由于未处理的信号导致程序异常退出。本文将带你从基础概念开始深入探讨信号处理的常见方式。
1. 信号的基本概念
在 Linux 系统中信号是一种软中断机制主要用于进程之间的异步通信。信号的产生和递达并不会按照进程的执行顺序发生而是通过操作系统将某种事件如用户输入、硬件异常等通知进程。每个信号都有唯一的编号和宏定义名称。例如SIGINT编号2是一个常见的信号通常由按下 CtrlC 产生。 信号的产生的四种方式 终端输入产生信号例如当用户在终端中按下 CtrlC 时系统会发送 SIGINT 信号给当前前台进程进而终止进程。系统调用产生信号开发者可以通过调用 kill 函数向指定进程发送信号例如 kill -SIGKILL PID。软件条件产生信号某些软件事件会自动触发信号。例如SIGPIPE 信号在管道破裂时触发。硬件异常产生信号例如执行非法内存访问会触发 SIGSEGV 信号。 信号处理一般有三种方式 忽略信号进程忽略该信号。执行默认动作进程按照信号的默认处理方式处理例如 SIGKILL 会导致进程直接退出。捕捉信号进程通过自定义函数捕捉信号并进行处理。 2. 信号的自定义捕捉
Linux 提供了 signal 和 sigaction 系统调用允许开发者自定义信号的处理函数即捕捉信号。
a. 自定义捕捉终端产生的 SIGINT 2号信号
#include stdio.h
#include signal.hvoid handler(int sig) {printf(Received signal: %d\n, sig);
}int main() {signal(SIGINT, handler); // 捕捉SIGINT信号while (1) {printf(Waiting for signal...\n);sleep(1);}return 0;
}程序解释
handler 函数捕捉到 SIGINT 信号并打印信号编号。当用户在终端中按下 CtrlC进程不会立即终止而是调用自定义的 handler 函数。
b. 自定义捕捉由软件条件产生的 SIGALRM 信号
SIGALRM 信号通常由 alarm() 函数产生用于在设定的时间后通知进程。
alarm() 函数与 SIGALRM 信号 alarm() 函数的作用是设置一个闹钟指定经过若干秒后系统向进程发送 SIGALRM 信号。此信号的默认处理行为是终止进程但我们可以通过自定义信号处理函数来捕捉并处理 SIGALRM 信号。 alarm() 函数的定义如下
#include unistd.h
unsigned int alarm(unsigned int seconds);参数 seconds指定在多少秒后发送 SIGALRM 信号。如果参数为 0则取消先前设置的闹钟。返回值返回先前设置的闹钟还剩余的时间。如果没有设置过闹钟返回值为 0。例如设定闹钟为 30 秒闹钟执行了 20 秒后取消并重设为 15 秒之前的闹钟还剩下 10 秒这个时间会作为 alarm() 函数的返回值。
#include stdio.h
#include signal.h
#include unistd.h// 自定义的SIGALRM信号处理函数
void handle_alarm(int sig) {printf(Alarm signal received: %d. Times up!\n, sig);
}int main() {// 注册SIGALRM信号的处理函数signal(SIGALRM, handle_alarm);// 设置闹钟为1秒alarm(1);// 计数器在闹钟响起之前一直计数int count 0;while (1) {printf(Counting: %d\n, count);sleep(1); // 每秒计数一次}return 0;
}程序解释 信号处理函数 handle_alarm 当 SIGALRM 信号产生时操作系统会调用此函数。此函数接收信号编号作为参数并打印出提示信息。 设置闹钟 程序通过调用 alarm(1) 设置了一个 1 秒的闹钟。即 1 秒后系统会发送 SIGALRM 信号触发信号处理函数 handle_alarm。 计数器 程序进入一个无限循环每秒钟打印一次计数。在 1 秒内计数器会输出几次直到接收到 SIGALRM 信号并终止循环。
运行该程序时输出
Counting: 0
Alarm signal received: 14. Times up!程序开始计数并且在 1 秒后接收到 SIGALRM 信号调用信号处理函数输出“Alarm signal received”信息随后程序被信号终止。
拓展 重新设置闹钟如果在 SIGALRM 之前再次调用 alarm()会取消之前的闹钟并重新设置。例如假如 alarm(5) 在 alarm(1) 之后被调用系统将在 5 秒而非 1 秒后发送 SIGALRM 信号。 取消闹钟alarm(0) 可以取消先前设置的闹钟程序将不会再收到 SIGALRM 信号。
3. 信号阻塞与未决信号
信号的三种状态阻塞、未决和递达
阻塞Block
阻塞是指进程可以暂时不处理某些信号。当信号被阻塞时即使信号产生了也不会立即处理。信号会保持在阻塞状态直到解除阻塞。
未决Pending
未决是指信号已经产生但由于被阻塞无法递达。信号会处于未决状态等待解除阻塞。当信号解除阻塞后未决信号会递达给进程。
递达Delivery
递达是指信号从产生到被进程处理的过程。当信号未被阻塞或解除阻塞后信号会递达给进程触发默认处理动作或自定义的信号处理函数。
进程可以选择阻塞Block某个信号。被阻塞的信号在产生时会处于未决状态直到进程解除对该信号的阻塞后才会执行相关的处理动作。 注意阻塞和忽略是不同的概念。阻塞信号意味着信号不会被递达直到解除阻塞。而忽略则是在信号递达后选择不进行处理的一种方式。
C语言中的信号相关函数
sigset_t 是一个用于表示信号集的数据类型每个信号用一个位来表示它的状态。这个类型可以用来存储和操作进程的信号屏蔽字阻塞信号集以及未决信号集。 sigemptyset(sigset_t *set): 初始化一个信号集将其中的所有信号位清零即该信号集不包含任何信号。 sigfillset(sigset_t *set): 初始化一个信号集将其中的所有信号位设置为1即该信号集包含所有信号。 sigaddset(sigset_t *set, int signo): 在信号集中添加一个信号使其对应的位被设置为1。signo 是要添加的信号的编号。 sigdelset(sigset_t *set, int signo): 从信号集中删除一个信号使其对应的位被设置为0。signo 是要删除的信号的编号。 sigismember(const sigset_t *set, int signo): 检查信号集中的某个信号是否被设置为1。返回值为非零表示信号被设置即有效为0表示信号未被设置即无效。 sigprocmask(int how, const sigset_t *set, sigset_t *oset): 读取或更改进程的信号屏蔽字阻塞信号集。 sigpending(sigset_t *set): 读取当前进程的未决信号集。
使用 sigprocmask添加block阻塞集
在某些场景下进程可能不希望立即处理某个信号这时可以选择阻塞该信号。当信号被阻塞时信号会进入未决状态直到解除阻塞后才会递达。可以使用 sigprocmask 函数来设置信号的阻塞状态。
#include stdio.h
#include signal.hint main() {sigset_t set;sigemptyset(set);sigaddset(set, SIGINT); // 阻塞SIGINT信号sigprocmask(SIG_BLOCK, set, NULL);printf(SIGINT is blocked, press CtrlC...\n);sleep(10); // 期间按下CtrlC不会终止程序return 0;
}SIGINT 信号被阻塞按下 CtrlC 时进程不会立刻退出而是被阻塞不能被递达需要等到信号被解除阻塞后才能处理信号。
如果将所有信号都使用sigprocmask函数添加到block阻塞位图当中是不是就能产生一个无法被退出的无敌进程 答SIGKILL (9号信号)和 SIGSTOP(19号信号)是特殊的无法被阻塞、忽略或捕获。我们依然可以通过这些信号杀死进程所以这样操作并不能使进程成为“无敌”的进程。(操作系统设计者早就想到了~~)
4. 常见信号的递达过程和处理方式
信号的递达过程 信号抵达的检查: 当系统从内核态返回用户态时会首先检查当前进程的未决信号pending signals。这时系统处于内核态有权限检查进程的信号状态。 处理未决信号: 如果发现有未决信号并且该信号没有被阻塞系统会决定如何处理这些信号。对于默认处理动作或忽略的信号系统会执行默认动作或忽略信号然后清除对应的未决标志位。 指定信号动作: 如果信号的处理动作是用户自定义的系统会返回用户态执行用户定义的处理函数。执行完自定义处理函数后用户态的处理程序会通过 sigreturn 系统调用返回内核态清除对应的未决标志位。如果没有新的信号要处理系统会直接返回用户态从主控制流程中上次被中断的地方继续执行。 为什么执行自定义函数是需要由内核态切换到内核态 尽管内核态具有高权限但操作系统设计中不允许直接在内核态执行用户代码。原因是用户代码可能包含非法操作如清空数据库等这在用户态时权限不足但在内核态时可能会造成严重后果。操作系统必须确保用户代码的合法性以防止安全风险。因此操作系统会严格控制用户代码的执行确保系统安全和稳定。
信号的处理方式
信号编号宏定义名称默认动作说明1SIGHUP终止进程终端挂起时发送此信号2SIGINT终止进程用户按下 CtrlC9SIGKILL终止进程不可捕捉直接终止进程11SIGSEGV终止进程并生成 core段错误非法内存访问15SIGTERM终止进程请求进程终止