从入门到精通:进程信号

引言

在操作系统的世界里,信号是一种用于进程间通信和控制的重要机制。信号能够在不同的进程之间传递异步事件,通知进程发生了某种情况。在Linux系统中,信号的使用是非常普遍且重要的,尤其是在处理进程控制、异常处理和进程间通信时。本文将带你深入了解Linux系统中的信号机制,从基本概念到高级应用,全面覆盖信号的生成、阻塞、捕捉和处理。通过对信号的深入理解和实际操作,你将能够更好地控制和管理进程,提高程序的健壮性和可靠性。

1. 信号入门

1.1 生活角度的信号

为了更好地理解信号,我们可以从生活中的例子开始。假设你在网上购买了很多商品,并且正在等待快递的到来。你知道快递员什么时候到达,但你不需要时刻守在门口。相反,当快递员到达时,他会给你打电话或者发送短信通知你。这时候,你可能正在做其他事情,比如玩游戏。你会在方便的时候去取快递,而不是立即去取。这就像信号处理一样,信号的产生是异步的,你可以在适当的时候处理它们。

  1. 快递通知:当快递员到达时,他会通知你,这就像操作系统向进程发送信号。信号是操作系统提供的一种异步通知机制,可以在任何时刻通知进程发生了某种事件。
  2. 延迟处理:你可以在适当的时候处理快递,而不是立即去取,就像信号可以被阻塞,直到进程准备好处理它们。信号的处理可以延迟到进程空闲时,保证进程的重要任务不被中断。
  3. 处理快递:你可以选择不同的处理方式,比如打开快递(默认处理)、转送给别人(自定义处理)或者忽略它(忽略信号)。同样地,信号的处理也有多种方式,可以执行默认动作、捕捉处理或忽略信号。

1.2 技术应用角度的信号

在技术应用中,信号是一种软中断机制,用于通知进程发生了特定的事件。例如,当你在Shell中运行一个前台进程并按下Ctrl-C时,会产生一个SIGINT信号,通知该进程应该终止。

信号的技术应用主要包括以下几个方面:

  1. 进程控制:信号可以用于控制进程的执行,例如终止进程、暂停进程和恢复进程等。常见的信号包括SIGINT(中断进程)、SIGTERM(终止进程)和SIGSTOP(暂停进程)。
  2. 异常处理:信号可以用于处理进程运行时的异常情况,例如内存访问错误、除零错误和非法指令等。常见的信号包括SIGSEGV(段错误)、SIGFPE(浮点异常)和SIGILL(非法指令)。
  3. 进程间通信:信号可以用于进程间的简单通信,例如通知子进程或父进程完成某个任务。常见的信号包括SIGCHLD(子进程状态变化)和SIGUSR1、SIGUSR2(用户定义信号)。

以下是一个简单的程序示例,它会一直等待信号:

#include <stdio.h>
#include <unistd.h>int main() {while (1) {printf("I am a process, I am waiting for a signal!\n");sleep(1);}return 0;
}

当你在终端中运行这个程序,并按下Ctrl-C时,程序会收到一个SIGINT信号并终止。信号的产生和处理是异步的,这意味着进程在运行过程中可以随时接收到信号,并根据设定的处理方式进行处理。

1.3 注意事项

在使用信号时,有一些重要的注意事项需要牢记:

  1. 前台与后台进程Ctrl-C产生的信号只能发送给前台进程。如果你将命令放到后台运行,Shell不会等待进程结束。
  2. 异步性:信号相对于进程的控制流程是异步的,进程在运行时随时可能接收到信号。
  3. 信号丢失:如果多个相同的信号在进程未处理完第一个信号时到达,除实时信号外,其他信号可能会丢失。
  4. 信号处理的可重入性:信号处理函数应该是可重入的,即信号处理函数不应该调用不可重入的函数,如mallocprintf等。

1.4 信号的概念

信号是进程之间事件异步通知的一种方式,属于软中断。每个信号都有一个编号和一个宏定义名称,如SIGINT、SIGTERM等。信号可以被进程捕捉并处理,也可以被进程忽略或采用默认处理动作。常见的信号包括:

  1. SIGINT(2):中断进程,通常由用户按Ctrl-C产生。
  2. SIGTERM(15):终止进程,可以由kill命令发送。
  3. SIGKILL(9):强制终止进程,无法被捕捉或忽略。
  4. SIGSTOP(19):暂停进程,无法被捕捉或忽略。
  5. SIGCONT(18):恢复暂停的进程。
  6. SIGSEGV(11):段错误,通常由于非法内存访问引起。
  7. SIGPIPE(13):写入一个没有读端的管道时产生。

2. 信号的产生

信号可以通过多种方式产生,包括终端按键、系统函数和软件条件等。

2.1 通过终端按键产生信号

在Shell中,用户可以通过按下特定的键来产生信号。例如,Ctrl-C会产生SIGINT信号,Ctrl-\会产生SIGQUIT信号。以下是一个简单的程序,验证这两个信号的默认处理动作:

#include <stdio.h>
#include <unistd.h>int main() {while (1) {printf("Running... Press Ctrl-C or Ctrl-\\ to test signal handling\n");sleep(1);}return 0;
}

运行这个程序并按下Ctrl-C,你会看到程序被终止。同样,按下Ctrl-\会使程序终止并生成一个核心转储文件(core dump)。

2.2 调用系统函数向进程发信号

你可以使用kill命令或kill函数向进程发送信号。以下是一个示例,展示如何在后台运行一个程序并使用kill命令发送信号:

$ ./sig &        # 将程序放到后台运行
$ kill -SIGTERM <pid>   # 向程序发送SIGTERM信号

kill命令实际上调用了kill函数,发送信号到指定的进程。使用kill函数可以在程序中实现同样的功能:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid == 0) {// 子进程while (1) {printf("Child process running...\n");sleep(1);}} else {// 父进程sleep(3);printf("Sending SIGTERM to child process\n");kill(pid, SIGTERM);wait(NULL);}return 0;
}

在这个示例中,父进程在3秒后发送SIGTERM信号给子进程,子进程接收到信号后终止。

2.3 由软件条件产生信号

软件条件也可以产生信号,例如管道破裂(SIGPIPE)和定时器超时(SIGALRM)。以下是一个示例,使用alarm函数设置一个定时器,触发SIGALRM信号:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>void alarm_handler(int sig) {printf("Alarm signal received\n");
}int main() {signal(SIGALRM, alarm_handler);alarm(2);  // 设置2秒的定时器while (1) {printf("Waiting for alarm...\n");sleep(1);}return 0;
}

在这个示例中,程序会在2秒后接收到SIGALRM信号,并调用自定义的信号处理函数。

2.4 硬件异常产生信号

硬件异常也会产生信号,例如除以零(SIGFPE)和非法内存访问(SIGSEGV)。以下是一个示

例,模拟非法内存访问并捕捉SIGSEGV信号:

#include <stdio.h>
#include <signal.h>void segv_handler(int sig) {printf("Segmentation fault captured\n");_exit(1);
}int main() {signal(SIGSEGV, segv_handler);int *p = NULL;*p = 100;  // 触发非法内存访问return 0;
}

在这个示例中,程序会触发一个段错误,并调用自定义的信号处理函数。

3. 信号捕捉

信号捕捉是指定义一个信号处理函数,在信号递达时执行该函数。通过信号捕捉,可以自定义信号的处理方式,例如记录日志、清理资源等。

3.1 使用signal函数捕捉信号

signal函数用于设置信号处理程序。以下是一个示例,展示如何使用signal函数捕捉SIGINT信号:

#include <stdio.h>
#include <signal.h>void sigint_handler(int sig) {printf("Caught SIGINT signal\n");
}int main() {signal(SIGINT, sigint_handler);while (1) {printf("Press Ctrl-C to generate SIGINT signal\n");sleep(1);}return 0;
}

在这个示例中,当你按下Ctrl-C时,程序会捕捉到SIGINT信号并调用自定义的信号处理函数。

3.2 使用sigaction函数捕捉信号

sigaction函数提供了更强大的信号捕捉功能,可以设置额外的选项,如信号屏蔽和实时信号处理。以下是一个示例,展示如何使用sigaction函数捕捉SIGINT信号:

#include <stdio.h>
#include <signal.h>
#include <string.h>void sigint_handler(int sig) {printf("Caught SIGINT signal\n");
}int main() {struct sigaction sa;memset(&sa, 0, sizeof(sa));sa.sa_handler = sigint_handler;sigaction(SIGINT, &sa, NULL);while (1) {printf("Press Ctrl-C to generate SIGINT signal\n");sleep(1);}return 0;
}

在这个示例中,我们使用sigaction函数设置SIGINT信号的处理程序,并且可以指定更多的选项,如信号屏蔽和处理标志。

3.3 可重入函数

在信号处理函数中,使用可重入函数是非常重要的。可重入函数是指可以在任何时刻中断的函数,通常不使用全局变量或静态变量。以下是一个示例,展示如何编写可重入的信号处理函数:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>volatile sig_atomic_t flag = 0;void sigint_handler(int sig) {flag = 1;
}int main() {signal(SIGINT, sigint_handler);while (!flag) {printf("Waiting for SIGINT signal\n");sleep(1);}printf("SIGINT signal received, exiting\n");return 0;
}

在这个示例中,我们使用volatilesig_atomic_t关键字确保信号处理函数是可重入的。

4. 信号阻塞与未决

4.1 信号阻塞

信号阻塞是指进程暂时不处理某些信号,而是将它们保持在未决状态,直到解除阻塞。通过信号阻塞,可以防止在关键代码段中断进程。

使用sigprocmask函数阻塞信号

以下是一个示例,展示如何使用sigprocmask函数阻塞SIGINT信号:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigint_handler(int sig) {printf("Caught SIGINT signal\n");
}int main() {sigset_t set;sigemptyset(&set);sigaddset(&set, SIGINT);signal(SIGINT, sigint_handler);// 阻塞SIGINT信号sigprocmask(SIG_BLOCK, &set, NULL);printf("SIGINT signal blocked\n");sleep(5);// 解除阻塞SIGINT信号sigprocmask(SIG_UNBLOCK, &set, NULL);printf("SIGINT signal unblocked\n");while (1) {printf("Running...\n");sleep(1);}return 0;
}

在这个示例中,程序会在开始时阻塞SIGINT信号,5秒后解除阻塞。期间按下Ctrl-C不会终止程序,而是在解除阻塞后立即处理该信号。

4.2 信号未决

信号未决是指信号已产生但未被递达的状态。当信号被阻塞时,进入未决状态。可以使用sigpending函数查看未决信号。

使用sigpending函数查看未决信号

以下是一个示例,展示如何使用sigpending函数查看未决信号:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigint_handler(int sig) {printf("Caught SIGINT signal\n");
}int main() {sigset_t set, pending;sigemptyset(&set);sigaddset(&set, SIGINT);signal(SIGINT, sigint_handler);// 阻塞SIGINT信号sigprocmask(SIG_BLOCK, &set, NULL);printf("SIGINT signal blocked\n");sleep(5);sigpending(&pending);if (sigismember(&pending, SIGINT)) {printf("SIGINT signal is pending\n");}// 解除阻塞SIGINT信号sigprocmask(SIG_UNBLOCK, &set, NULL);printf("SIGINT signal unblocked\n");while (1) {printf("Running...\n");sleep(1);}return 0;
}

在这个示例中,程序在阻塞SIGINT信号后查看未决信号,并在解除阻塞后处理该信号。

5. 信号的高级处理

5.1 信号的默认处理动作

每个信号都有一个默认的处理动作,包括终止进程、忽略信号和生成核心转储文件等。你可以通过自定义信号处理函数改变信号的处理方式。

信号的默认处理动作分为以下几种:

  1. 终止进程:如SIGINT、SIGTERM和SIGKILL信号,默认会终止进程。
  2. 生成核心转储文件:如SIGSEGV和SIGABRT信号,默认会终止进程并生成核心转储文件。
  3. 忽略信号:如SIGCHLD和SIGURG信号,默认会被忽略。
  4. 暂停进程:如SIGSTOP和SIGTSTP信号,默认会暂停进程。
  5. 恢复进程:如SIGCONT信号,默认会恢复暂停的进程。

5.2 核心转储文件

核心转储文件(core dump)是指当进程异常终止时,将进程的内存状态保存到磁盘上的文件。核心转储文件用于调试进程异常终止时的情况,可以帮助开发者查找和修复程序中的错误。

可以使用ulimit命令设置允许生成核心转储文件的大小。例如,设置允许生成最大为1024KB的核心转储文件:

$ ulimit -c 1024

以下是一个示例,展示如何生成核心转储文件:

#include <stdio.h>
#include <signal.h>int main() {int *p = NULL;*p = 100;  // 触发段错误return 0;
}

运行这个程序会生成一个核心转储文件,可以使用调试器(如gdb)检查该文件以查找错误原因:

$ gdb ./a.out core
(gdb) bt

5.3 竞态条件和可重入函数

竞态条件是指多个进程或线程并发执行时,由于缺乏同步机制,导致程序的运行结果不确定的情况。在信号处理函数中,避免使用不可重入函数,如mallocfree和标准I/O库函数,因为它们可能会引发竞态条件。

以下是一个示例,展示竞态条件的发生:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>int count = 0;void handler(int sig) {count++;printf("Signal received, count = %d\n", count);
}int main() {signal(SIGINT, handler);while (count < 5) {printf("Running...\n");sleep(1);}return 0;
}

在这个示例中,如果信号

处理函数在count变量的修改过程中被中断,可能会导致count值不正确。

5.4 使用volatile关键字

volatile关键字用于告诉编译器变量的值可能随时发生变化,避免编译器对变量进行优化。在信号处理函数中,使用volatile关键字可以确保变量的正确性。

以下是一个示例,展示如何使用volatile关键字:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>volatile sig_atomic_t flag = 0;void sigint_handler(int sig) {flag = 1;
}int main() {signal(SIGINT, sigint_handler);while (!flag) {printf("Waiting for SIGINT signal\n");sleep(1);}printf("SIGINT signal received, exiting\n");return 0;
}

在这个示例中,我们使用volatile关键字确保flag变量的正确性。

6. SIGCHLD信号

当子进程终止时,会向父进程发送SIGCHLD信号。父进程可以通过捕捉SIGCHLD信号处理子进程的终止,避免产生僵尸进程。

6.1 使用SIGCHLD信号处理子进程终止

以下是一个示例,展示如何使用SIGCHLD信号处理子进程的终止:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>void sigchld_handler(int sig) {pid_t pid;int status;while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {printf("Child process %d terminated\n", pid);}
}int main() {signal(SIGCHLD, sigchld_handler);pid_t pid = fork();if (pid == -1) {perror("fork");exit(EXIT_FAILURE);}if (pid == 0) {printf("Child process %d\n", getpid());sleep(2);exit(EXIT_SUCCESS);} else {printf("Parent process %d\n", getpid());while (1) {printf("Parent process is doing something\n");sleep(1);}}return 0;
}

在这个示例中,父进程通过捕捉SIGCHLD信号处理子进程的终止,避免产生僵尸进程。

7. SIGCHLD信号的处理机制

7.1 wait和waitpid函数

在处理子进程终止时,父进程通常会使用waitwaitpid函数等待子进程的结束并获取其退出状态。以下是这两个函数的基本用法:

  1. wait函数wait函数会阻塞父进程,直到有子进程结束为止。
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *status);
  1. waitpid函数waitpid函数提供了更灵活的等待方式,可以指定等待特定的子进程,并且可以选择阻塞或非阻塞等待。
#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);

在信号处理函数中,通常使用waitpid函数并指定WNOHANG选项,以非阻塞方式等待所有结束的子进程。

7.2 自动清理子进程

除了使用SIGCHLD信号处理子进程的终止,还可以通过设置SIGCHLD信号的默认处理动作为忽略,自动清理子进程,避免产生僵尸进程。

以下是一个示例,展示如何通过设置SIGCHLD信号的默认处理动作为忽略,自动清理子进程:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>int main() {struct sigaction sa;sa.sa_handler = SIG_IGN;sigaction(SIGCHLD, &sa, NULL);pid_t pid = fork();if (pid == -1) {perror("fork");exit(EXIT_FAILURE);}if (pid == 0) {printf("Child process %d\n", getpid());sleep(2);exit(EXIT_SUCCESS);} else {printf("Parent process %d\n", getpid());while (1) {printf("Parent process is doing something\n");sleep(1);}}return 0;
}

在这个示例中,父进程通过设置SIGCHLD信号的默认处理动作为忽略,自动清理子进程,避免产生僵尸进程。

8. 阻塞信号

8.1 信号的阻塞与未决

信号阻塞是指进程暂时不处理某些信号,而是将它们保持在未决状态,直到解除阻塞。信号未决是指信号已产生但未被递达的状态。

8.2 使用sigprocmask函数

sigprocmask函数用于设置进程的信号屏蔽字,可以阻塞或解除阻塞信号。以下是一个示例,展示如何使用sigprocmask函数阻塞和解除阻塞信号:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigint_handler(int sig) {printf("Caught SIGINT signal\n");
}int main() {sigset_t set;sigemptyset(&set);sigaddset(&set, SIGINT);signal(SIGINT, sigint_handler);// 阻塞SIGINT信号sigprocmask(SIG_BLOCK, &set, NULL);printf("SIGINT signal blocked\n");sleep(5);// 解除阻塞SIGINT信号sigprocmask(SIG_UNBLOCK, &set, NULL);printf("SIGINT signal unblocked\n");while (1) {printf("Running...\n");sleep(1);}return 0;
}

在这个示例中,程序会在开始时阻塞SIGINT信号,5秒后解除阻塞。期间按下Ctrl-C不会终止程序,而是在解除阻塞后立即处理该信号。

8.3 使用sigpending函数查看未决信号

sigpending函数用于查看进程的未决信号。以下是一个示例,展示如何使用sigpending函数查看未决信号:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigint_handler(int sig) {printf("Caught SIGINT signal\n");
}int main() {sigset_t set, pending;sigemptyset(&set);sigaddset(&set, SIGINT);signal(SIGINT, sigint_handler);// 阻塞SIGINT信号sigprocmask(SIG_BLOCK, &set, NULL);printf("SIGINT signal blocked\n");sleep(5);sigpending(&pending);if (sigismember(&pending, SIGINT)) {printf("SIGINT signal is pending\n");}// 解除阻塞SIGINT信号sigprocmask(SIG_UNBLOCK, &set, NULL);printf("SIGINT signal unblocked\n");while (1) {printf("Running...\n");sleep(1);}return 0;
}

在这个示例中,程序在阻塞SIGINT信号后查看未决信号,并在解除阻塞后处理该信号。

9. 可重入函数

9.1 什么是可重入函数

可重入函数是指可以在任何时刻被中断,并且在中断后可以正确地继续执行的函数。可重入函数通常不使用全局变量或静态变量,不依赖于不可重入的函数,如mallocfree和标准I/O库函数。

9.2 编写可重入函数

在编写信号处理函数时,确保函数是可重入的非常重要。以下是一个示例,展示如何编写可重入的信号处理函数:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>volatile sig_atomic_t flag = 0;void sigint_handler(int sig) {flag = 1;
}int main() {signal(SIGINT, sigint_handler);while (!flag) {printf("Waiting for SIGINT signal\n");sleep(1);}printf("SIGINT signal received, exiting\n");return 0;
}

在这个示例中,我们使用volatilesig_atomic_t关键字确保信号处理函数是可重入的。

9.3 常见的不可重入函数

在信号处理函数中,应该避免使用以下不可重入函数:

  1. 动态内存分配函数:如malloccallocfree
    2

. 标准I/O库函数:如printfsprintffgets
3. 时间和日期函数:如ctimelocaltimegmtime
4. 环境变量函数:如getenvputenv

这些函数通常使用全局数据结构或静态数据结构,不适合在信号处理函数中使用。

10. 信号处理的高级话题

10.1 核心转储文件

核心转储文件(core dump)是指当进程异常终止时,将进程的内存状态保存到磁盘上的文件。核心转储文件用于调试进程异常终止时的情况,可以帮助开发者查找和修复程序中的错误。

以下是一个示例,展示如何生成核心转储文件:

#include <stdio.h>
#include <signal.h>int main() {int *p = NULL;*p = 100;  // 触发段错误return 0;
}

运行这个程序会生成一个核心转储文件,可以使用调试器(如gdb)检查该文件以查找错误原因:

$ gdb ./a.out core
(gdb) bt

10.2 竞态条件和信号处理

竞态条件是指多个进程或线程并发执行时,由于缺乏同步机制,导致程序的运行结果不确定的情况。在信号处理函数中,避免使用不可重入函数,如mallocfree和标准I/O库函数,因为它们可能会引发竞态条件。

以下是一个示例,展示竞态条件的发生:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>int count = 0;void handler(int sig) {count++;printf("Signal received, count = %d\n", count);
}int main() {signal(SIGINT, handler);while (count < 5) {printf("Running...\n");sleep(1);}return 0;
}

在这个示例中,如果信号处理函数在count变量的修改过程中被中断,可能会导致count值不正确。

10.3 使用volatile关键字

volatile关键字用于告诉编译器变量的值可能随时发生变化,避免编译器对变量进行优化。在信号处理函数中,使用volatile关键字可以确保变量的正确性。

以下是一个示例,展示如何使用volatile关键字:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>volatile sig_atomic_t flag = 0;void sigint_handler(int sig) {flag = 1;
}int main() {signal(SIGINT, sigint_handler);while (!flag) {printf("Waiting for SIGINT signal\n");sleep(1);}printf("SIGINT signal received, exiting\n");return 0;
}

在这个示例中,我们使用volatile关键字确保flag变量的正确性。

11. 信号的实际应用

11.1 使用信号实现定时任务

信号可以用于实现定时任务,例如定时器。以下是一个示例,展示如何使用SIGALRM信号实现定时任务:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void alarm_handler(int sig) {printf("Timer expired\n");alarm(2);  // 重新设置定时器
}int main() {signal(SIGALRM, alarm_handler);alarm(2);  // 设置2秒的定时器while (1) {printf("Waiting for timer...\n");sleep(1);}return 0;
}

在这个示例中,程序会每隔2秒打印一次"Timer expired",实现定时任务的功能。

11.2 使用信号处理进程间通信

信号可以用于进程间的简单通信,例如通知子进程或父进程完成某个任务。以下是一个示例,展示如何使用SIGUSR1信号实现进程间通信:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigusr1_handler(int sig) {printf("Received SIGUSR1 signal\n");
}int main() {signal(SIGUSR1, sigusr1_handler);pid_t pid = fork();if (pid == 0) {// 子进程sleep(2);kill(getppid(), SIGUSR1);} else {// 父进程pause();  // 等待信号}return 0;
}

在这个示例中,子进程在2秒后发送SIGUSR1信号给父进程,父进程接收到信号后调用自定义的信号处理函数。

11.3 使用信号处理多线程程序

在多线程程序中,信号处理需要特别注意,因为信号默认会发送到整个进程,而不是特定的线程。以下是一个示例,展示如何在多线程程序中使用信号:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>void *thread_func(void *arg) {printf("Thread %d running\n", *(int *)arg);while (1) {sleep(1);}return NULL;
}void sigint_handler(int sig) {printf("Caught SIGINT signal\n");
}int main() {pthread_t threads[2];int thread_ids[2] = {1, 2};signal(SIGINT, sigint_handler);for (int i = 0; i < 2; i++) {if (pthread_create(&threads[i], NULL, thread_func, &thread_ids[i]) != 0) {perror("pthread_create");exit(EXIT_FAILURE);}}for (int i = 0; i < 2; i++) {pthread_join(threads[i], NULL);}return 0;
}

在这个示例中,主线程设置了SIGINT信号的处理函数,当按下Ctrl-C时,信号处理函数会被调用。信号处理函数在多线程程序中的使用需要小心,避免使用不可重入的函数。

结论

本文详细介绍了Linux系统中的信号机制,包括信号的生成、阻塞、捕捉和处理。通过具体的示例代码展示了信号的各种应用场景和处理方式。信号是进程间通信和控制的重要工具,理解和掌握信号机制可以帮助你更好地控制和管理进程,提高程序的健壮性和可靠性。希望通过本文的学习,你能更加深入地理解和掌握信号机制,为开发高效、可靠的程序打下坚实的基础。
嗯,就是这样啦,文章到这里就结束啦,真心感谢你花时间来读。
觉得有点收获的话,不妨给我点个赞吧!
如果发现文章有啥漏洞或错误的地方,欢迎私信我或者在评论里提醒一声~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/27450.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单通道触摸感应开关RH6016

1.简介 SOT23-6 RH6016 封装和丝印 RH6016 是一款内置稳压模块的单通道电容式触摸感应控制开关IC&#xff0c;可以替代传统的机械式开关。 RH6016可在有介质(如玻璃、亚克力、塑料、陶瓷等)隔离保护的情况下实现触摸功能&#xff0c;安全性高。 RH6016内置高精度稳压、上电复…

Requests —— 请求头设置!

前戏 在我们进行自动化测试的时候&#xff0c;很多网站都会都请求头做个校验&#xff0c;比如验证 User-Agent&#xff0c;看是不是浏览器发送的请求&#xff0c;如果我们不加请求头&#xff0c;使用脚本访问&#xff0c;默认User-Agent是python&#xff0c;这样服务器如果进行…

Superset二次开发之Git篇 git remote

背景:从GitHub clone Superset项目,基于3.0版本做二次开发,后续通过其他方式把3.0版本未做任何修改过的原始代码上传到企业GitLab库develop分支 任务:本地代码推送到GitLab库develop分支,但是两者似乎没有任何关联关系 操作步骤 克隆 Superset 3.0 版本的项目到本地: …

DeepSORT(目标跟踪算法)卡尔曼滤波中的贝叶斯定理

DeepSORT&#xff08;目标跟踪算法&#xff09;卡尔曼滤波中的贝叶斯定理 flyfish 从例子中介绍名词 假设我们有一个袋子&#xff0c;里面有5个红球和3个蓝球。我们从袋子里随机抽取一个球。 概率 (Probability) 我们想计算从袋子里抽到红球的概率 P ( R ) P(R) P(R)。 …

请解释Java中的线程池的作用和优势,以及如何合理地配置线程池参数。什么是Java中的原子操作?请举例说明其应用场景和优势。

请解释Java中的线程池的作用和优势&#xff0c;以及如何合理地配置线程池参数。 Java中的线程池的作用和优势 作用&#xff1a; Java线程池是Java多线程编程中的核心概念之一。它通过维护一组线程来执行任务&#xff0c;并提供了任务调度、线程重用和资源管理等功能。优势&am…

misc刷题记录(1)陇剑杯

[陇剑杯 2021]签到 题目内容&#xff1a;此时正在进行的可能是__________协议的网络攻击。&#xff08;如有字母请全部使用小写&#xff0c;填写样例&#xff1a;http、dns、ftp&#xff09;。得到的flag请使用NSSCTF{}格式提交。 打开统计&#xff0c;找到协议分级&#xff…

大模型应用之路:从提示词到通用人工智能(AGI)

前言 大模型在人工智能领域的应用正迅速扩展&#xff0c;从最初的提示词&#xff08;Prompt&#xff09;工程到追求通用人工智能&#xff08;AGI&#xff09;的宏伟目标&#xff0c;这一旅程充满了挑战与创新。本文将探索大模型在实际应用中的进展&#xff0c;以及它们如何为实…

Vue待学习

整个渲染过程了解 Vue实例&#xff1f;Vue模板&#xff1f;渲染函数render&#xff08;&#xff09;&#xff1f;虚拟DOM VNode?模板编译器&#xff1f;diff算法 CSS相关 CSS高级学习&#xff1f;过渡&#xff1f; 待熟悉掌握 Vue-router?VueX&#xff1f;Vue-Cli、Webpack和…

php实现一个简单的MySQL分页

一、案例演示&#xff1a; 二、php 代码 <?php $servername "localhost"; // MySQL服务器名称或IP地址 $username "root"; // MySQL用户名 $password "123456"; // MySQL密码 $dbname "test"; // 要连接…

CentOS7安装nginx【巨详细】

CentOS7安装nginx 安装依赖 1.安装gcc&#xff0c;nginx 编译时依赖 gcc 环境 # 安装c yum install gcc-c# 查看版本 gcc -v正常情况显示如下 2.安装openssl 安全套接字层密码库&#xff0c;用于通信加密 yum install -y openssl openssl-devel3.安装zlib,zlib 库 提供了很多…

java反序列化---cc6链

目录 Transformer[]数组分析 链条代码跟进 ChainedTransformer.transform() LazyMap.get() TiedMapEntry.getValue() TiedMapEntry.hashCode() HashMap.hash() HashMap.put()的意外触发 LazyMap.get()中key的包含问题 cc6的payload如下 import org.apache.commons.co…

【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【08】【商品服务】Object划分_批量删除

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【08】【商品服务】Object划分_批量删除 Object划分批量删除/添加参考 Object划分 数据库中对于一张表的数据&#xff0c;由于拥有隐私字段、多余字段、字段过少等原因&#xff0c;不应该直…

浅谈二刷链表的感受以及引发的思考

1.总括 链表这个东西&#xff0c;就是一个非连续的物理结构&#xff0c;也属于这个线性表的一种&#xff0c;但是不像顺序表那样在这个物理结构上面是连续的&#xff0c;因此我们没有办法通过下标对于这个链表进行遍历&#xff0c;而是需要一个next指针进行这个指向的说明&…

中文技术文档的写作规范(搬运)

阮一峰老师的《中文技术文档的写作规范》搬运。 链接指路&#xff1a; https://github.com/ruanyf/document-style-guide/tree/master 内容&#xff1a;对中文技术文档从标题、文本、段落、数值、标点符号、文档体系、参考链接等七大方面进行了简明扼要的介绍。

网络安全练气篇——PHP编程语言基础

目录 PHP基础 一、PHP简介与环境搭建 什么是PHP&#xff1f; PHP环境安装 代码编辑选择 二、基本语法 PHP基本语法操作 PHP变量与输出 啥是常量? PHP注释 PHP单引号双引号声明 三、PHP表单 PHP表单 四、登录界面搭建与讲解 构建登陆页面 登陆页面端 服务器端…

汽车油耗NEDC与WLTP有什么区别?以及MATLAB/Simulink的汽车行驶工况仿真

最近的热点新闻非比亚迪的秦L莫属&#xff0c;其油耗达到2.9L/100km&#xff0c;但其标注为NEDC也引起了讨论&#xff0c; NEDC与WLTP的区别 NEDC的全称为“New European Driving Cycle”&#xff0c;即“新欧洲驾驶循环”。这种油耗测试标准起源于上世纪80年代&#xff0c;主…

计算机类期刊含金量横纵向对比(一)

本文期刊横纵向对比数据均来源于知网、知乎、谷歌学术、博客网、百度百科、发表者论坛所综合提取的数据对比。&#xff08;经验分享&#xff09; 期刊 难度比较 含金量 发表领域&#xff08;侧重点&#xff09; 审稿速度 收录情况 费用 计算机应用 对投稿者学历工作要求…

近期笔记总结

都是最近项目中会用到的一些小方法 写个笔记整理一下 方便之后忘了找不到 1、相同字段的对象 直接赋值 主要是用到编辑的功能里面 拿到的查询字段和需要赋值的字段一模一样时 不用一个一个的去写等于了 assingTab(arrA,arrB){Object.keys(arrA).forEach(key > {arrA[key] a…

液晶拼接屏企业应该采取哪些措施来提升整体竞争力和市场地位呢?

步入智能科技时代以来&#xff0c;商显行业面对着各式各样的挑战&#xff0c;人工智能、AI大模型等整合中&#xff0c;液晶拼接屏企业应该采取哪些措施以提升整体竞争力和市场地位。下面小编个人观点简单说一下&#xff1b;下是一些关键的措施&#xff1a; 首先&#xff0c;加…

Java文件复制方法详解:原理、使用场景、优缺点及代码示例

1. 基本文件流 (FileInputStream 和 FileOutputStream) 原理 基本文件流通过逐字节读取和写入文件来实现文件复制。这种方法简单直接&#xff0c;但效率较低。 使用场景 适用于小文件或对性能要求不高的场景。 优缺点 优点&#xff1a; 实现简单&#xff0c;容易理解。适…