当前位置:首页 > 系统教程 > 正文

深入理解Linux进程信号机制(从零掌握信号:产生、捕获与阻塞)

深入理解Linux进程信号机制(从零掌握信号:产生、捕获与阻塞)

深入理解Linux进程信号机制(从零掌握信号:产生、捕获与阻塞) 信号机制 信号捕获 信号阻塞 进程通信 第1张

在Linux系统中,信号机制是一种古老的进程间通信方式,它用于通知进程发生了异步事件。可以把信号想象成现实生活中的电话铃声:当你专注于工作时,电话铃响起(信号产生),你可以选择忽略(阻塞)、接听(捕获并处理)或者让系统默认处理(默认动作)。本文将带你从零开始,全面理解进程信号的产生、捕获与阻塞,并用通俗的语言和代码示例让你彻底掌握。

1. 信号的产生:谁在“打电话”?

信号可以由多种方式产生:

  • 硬件异常:例如除零操作产生SIGFPE(浮点异常),非法内存访问产生SIGSEGV(段错误)。
  • 终端输入:按下Ctrl+C产生SIGINT,Ctrl+\产生SIGQUIT。
  • 软件条件:如alarm定时器到期产生SIGALRM,或通过kill函数显式发送信号。
  • 进程通信:父进程可以通过信号通知子进程事件,这也是进程通信的一种简单形式。

例如,在Shell中运行一个前台程序,按下Ctrl+C,内核会向前台进程组的所有进程发送SIGINT信号。

2. 信号的默认处理:挂断?忽略?还是停止?

每个信号都有一个默认动作,常见的有:

  • Term:终止进程(如SIGINT)。
  • Ign:忽略信号(如SIGCHLD)。
  • Core:终止进程并生成core dump(如SIGSEGV)。
  • Stop:暂停进程(如SIGSTOP)。
  • Cont:继续进程(如SIGCONT)。

你可以通过man 7 signal查看所有信号的默认行为。

3. 信号的捕获:自定义“接电话”的方式

如果我们不想让进程按默认方式处理信号,可以自己编写处理函数,这就是信号捕获。主要使用signal()sigaction()函数。sigaction()更强大、更可移植,推荐使用。

    #include #include #include void handler(int sig) {    printf("捕获到信号 %d,执行自定义处理", sig);}int main() {    struct sigaction sa;    sa.sa_handler = handler;    sigemptyset(&sa.sa_mask);    sa.sa_flags = 0;    sigaction(SIGINT, &sa, NULL);  // 捕获SIGINT    while(1) {        printf("进程运行中... PID=%d", getpid());        sleep(2);    }    return 0;}  

运行后,按下Ctrl+C不会终止进程,而是打印消息。注意:SIGKILL和SIGSTOP不能被捕获或忽略。

4. 信号的阻塞:暂时“静音”

有时候我们希望暂时不处理某些信号,等关键操作完成后再处理,这时可以使用信号阻塞。阻塞通过设置信号屏蔽字(signal mask)实现,被阻塞的信号会变为未决状态(pending),直到解除阻塞才递达。

主要函数:sigprocmask()。例如,阻塞SIGINT:

    sigset_t newmask, oldmask;sigemptyset(&newmask);sigaddset(&newmask, SIGINT);sigprocmask(SIG_BLOCK, &newmask, &oldmask);  // 阻塞SIGINT// 执行关键代码,此时SIGINT被阻塞,不会递达sigprocmask(SIG_SETMASK, &oldmask, NULL);   // 解除阻塞  

解除阻塞后,如果有未决的SIGINT,会立即递达并执行处理函数。

5. 阻塞 vs 忽略:有什么区别?

阻塞只是将信号挂起,并未丢弃,解除阻塞后信号还会处理;而忽略是信号递达时直接丢弃,不产生任何动作。此外,阻塞是进程级的,可以针对每个线程(但传统Linux信号是进程级的)。

6. 综合示例:信号捕获与阻塞实战

下面是一个完整的示例,演示了信号捕获和阻塞配合使用,避免在临界区被中断。

    #include #include #include int flag = 0;void handler(int sig) {    flag = 1;    printf("信号处理函数执行,设置flag=1");}int main() {    struct sigaction sa;    sa.sa_handler = handler;    sigemptyset(&sa.sa_mask);    sa.sa_flags = 0;    sigaction(SIGUSR1, &sa, NULL);    sigset_t newmask, oldmask;    sigemptyset(&newmask);    sigaddset(&newmask, SIGUSR1);    // 进入临界区前阻塞SIGUSR1    sigprocmask(SIG_BLOCK, &newmask, &oldmask);    printf("临界区开始,SIGUSR1被阻塞");    sleep(5);  // 模拟临界区操作,此时发送SIGUSR1会被挂起    printf("临界区结束,解除阻塞");    sigprocmask(SIG_SETMASK, &oldmask, NULL);  // 解除阻塞,挂起的SIGUSR1立即递达    while(!flag) pause();  // 等待信号处理完成    printf("主程序退出");    return 0;}  

在另一个终端用kill -USR1 PID发送信号,观察输出。

总结

本文详细讲解了Linux信号机制的三大核心:信号产生(硬件、软件、终端)、信号捕获(signal/sigaction)和信号阻塞(sigprocmask)。掌握这些内容,你就能利用信号进行简单的进程通信,并编写出更健壮的程序。信号机制是Linux系统编程的基石之一,希望本文能帮助你彻底弄懂它。

(完)