Linux进程通信-信号

  1. 信号概念

信号是 Linux 进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号

可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

  1. 发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:

 对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C

通常会给进程发送一个中断信号。

 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给

相关进程。比如执行一条异常的机器语言指令,诸如被 0 除,或者引用了无法访问的

内存区域。

 系统状态变化,比如 alarm 定时器到期将引起 SIGALRM 信号,进程执行的 CPU

时间超限,或者该进程的某个子进程退出。

 运行 kill 命令或调用 kill 函数。

  1. 使用信号的两个主要目的是:

 让进程知道已经发生了一个特定的事情。

 强迫进程执行它自己代码中的信号处理程序。

  1. 信号的特点:

 简单

 不能携带大量信息

 满足某个特定条件才发送

 优先级比较高

查看系统定义的信号列表:kill –l

前 31 个信号为常规信号,其余为实时信号。
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson25$ kill -l1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

image-20240505220458667

image-20240505220510108

image-20240505220520571

信号的 5 种默认处理动作

查看信号的详细信息:man 7 signal

◼ 信号的 5 中默认处理动作

 Term 终止进程

 Ign 当前进程忽略掉这个信号

 Core 终止进程,并生成一个Core文件(保存进程异常退出的错误信息)

 Stop 暂停当前进程

 Cont 继续执行当前被暂停的进程

◼ 信号的几种状态:产生、未决、递达

◼ SIGKILL 和 SIGSTOP 信号不能被捕捉、阻塞或者忽略,只能执行默认动作

#include <stdio.h>
#include <string.h>int main() {char * buf; //指针没有指向合法内存strcpy(buf, "hello");return 0;
}

信号相关函数

kill

在编程中,kill 函数通常用于向进程发送信号,通知它进行某种操作,如终止或暂停。这个函数最常见于类 Unix 操作系统,如 Linux 和 macOS,它的功能是通过发送信号来控制进程的行为。

函数定义

在 C 语言中,kill 函数定义如下:

#include <signal.h>
int kill(pid_t pid, int sig);
  • pid_t pid: 指定要发送信号的进程 ID。如果 pid 大于 0,信号将发送给具有该 ID 的进程。如果 pid 等于 0,信号将发送给与发送者同一进程组的所有进程。
  • int sig: 要发送的信号。例如,SIGTERM 是请求终止进程的标准信号,SIGKILL 是强制终止进程的信号。
返回值
  • 成功时,kill 函数返回 0。
  • 失败时,返回 -1,并且 errno 被设置为错误的具体原因,比如 EINVAL 表示一个无效的信号,或 EPERM 表示没有足够的权限来发送信号。
示例

下面是一个使用 kill 函数的简单示例,它向进程发送一个 SIGTERM 信号:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>int main() {pid_t pid = 1234; // 假设进程ID为1234int result = kill(pid, SIGTERM);if (result == 0) {printf("Signal sent successfully\n");} else {perror("Failed to send signal");}return 0;
}

这个函数在系统编程中非常重要,用于进程间通信和进程控制,尤其是在需要清理僵尸进程或优雅地终止服务时非常有用。

在编程中,raise 函数通常用于在当前进程中生成一个信号。这个函数是C语言标准库中的一部分,定义在 <signal.h> 头文件中。它允许程序在其自身的执行流中发送信号,这可以用于各种目的,如触发信号处理程序或模拟外部事件的影响。

raise
函数定义

在 C 语言中,raise 函数的定义如下:

#include <signal.h>
int raise(int sig);
  • int sig: 要发送的信号代码。例如,SIGINT 通常用于表示中断信号(如Ctrl+C触发的),而 SIGTERM 用于请求终止程序。
返回值
  • 成功时,raise 函数返回 0。
  • 失败时,返回非0值。
示例

下面是一个使用 raise 函数的简单示例,它向自己发送一个 SIGTERM 信号:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>void signal_handler(int signal) {if (signal == SIGTERM) {printf("Received SIGTERM, exiting...\n");exit(0);}
}int main() {if (signal(SIGTERM, signal_handler) == SIG_ERR) {printf("Error: Cannot set signal handler.\n");return 1;}printf("Raising SIGTERM...\n");int result = raise(SIGTERM);if (result != 0) {perror("Failed to raise signal");return 1;}return 0; // 这行代码永远不会执行,因为signal_handler中已经调用了exit()
}

在这个例子中,raise 函数用于发送 SIGTERM 信号给程序自身,当信号被触发时,通过安装的信号处理程序 signal_handler 捕获并处理这个信号,随后程序退出。

使用场景

raise 函数常用于测试信号处理代码、在特定情况下自我终止程序、或者实现程序内部的通信机制。它提供了一种有效的方式来模拟信号发送,帮助开发者在单个程序内部测试和处理各种信号相关的逻辑。

abort

在编程中,abort 函数用于立即终止当前进程的执行,通常用于处理无法恢复的错误或异常情况。abort 是 C 语言标准库中的一个函数,定义在 <stdlib.h> 头文件中。当调用 abort 时,程序会生成一个 SIGABRT(abort signal,终止信号)信号,这通常会导致程序异常终止。

函数定义

在 C 语言中,abort 函数的定义如下:

#include <stdlib.h>
void abort(void);
  • 该函数没有参数。
  • 函数声明为 void 返回类型,意味着它不返回任何值。
行为描述
  • 终止进程abort 函数会立即终止调用它的进程。
  • 信号生成:调用 abort 通常会生成 SIGABRT 信号,该信号的默认行为是终止进程。
  • 内存和资源:由于进程是突然终止的,操作系统负责回收任何由进程使用的内存和系统资源。
  • 核心转储:在许多系统上,默认情况下,abort 的调用会产生一个核心转储文件,这可以用于后续的调试和分析。
示例

下面是一个使用 abort 函数的简单示例,展示了如何在检测到某些错误条件时终止程序:

#include <stdlib.h>
#include <stdio.h>int main() {printf("Something went horribly wrong!\n");abort(); // 终止程序// 下面的代码永远不会执行printf("This line will never be executed.\n");return 0;
}

在这个示例中,调用 abort() 会立即终止程序。由于 abort 导致程序异常终止,"This line will never be executed." 这一行将永远不会执行。

使用场景

abort 函数通常在以下情况下使用:

  • 错误处理:在检测到无法恢复的错误或严重异常情况时,使用 abort 来立即终止程序。
  • 调试:在开发过程中,当程序到达不应该到达的代码路径时,使用 abort 来强制停止程序并生成核心转储,便于开发者调试。
  • 安全性:在关键安全验证失败时,使用 abort 以防止潜在的安全漏洞被利用。

abort 是一个强大的工具,用于处理那些需要立即停止程序执行的情况,但它应该谨慎使用,因为它不提供进行任何清理操作的机会,如关闭文件句柄或释放动态分配的内存。

/*  #include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);- 功能:给任何的进程或者进程组pid, 发送任何的信号 sig- 参数:- pid :> 0 : 将信号发送给指定的进程= 0 : 将信号发送给当前的进程组= -1 : 将信号发送给每一个有权限接收这个信号的进程< -1 : 这个pid=某个进程组的ID取反 (-12345)- sig : 需要发送的信号的编号或者是宏值,0表示不发送任何信号kill(getppid(), 9);kill(getpid(), 9);int raise(int sig);- 功能:给当前进程发送信号- 参数:- sig : 要发送的信号- 返回值:- 成功 0- 失败 非0kill(getpid(), sig); 实现同样功能  void abort(void);- 功能: 发送SIGABRT信号给当前的进程,杀死当前进程kill(getpid(), SIGABRT);
*/#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>int main() {pid_t pid = fork();if(pid == 0) {// 子进程int i = 0;for(i = 0; i < 5; i++) {printf("child process\n");sleep(1);}} else if(pid > 0) {// 父进程printf("parent process\n");sleep(2);printf("kill child process now\n");kill(pid, SIGINT);}return 0;
}
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson26$ ./a.out 
parent process
child process
child process
kill child process now
alarm

在编程中,alarm 函数用于设置一个计时器(闹钟),在指定的秒数后发送 SIGALRM 信号到当前进程。这个函数是 Unix 系统的标准部分,主要用于在进程需要在一定时间后接收到通知时创建简单的时间延迟或超时。

函数定义

在 C 语言中,alarm 函数定义在 <unistd.h> 头文件中,其函数原型如下:

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
  • 参数:

    • unsigned int seconds: 指定在多少秒后产生 SIGALRM 信号。如果 seconds 为0,则取消任何之前设置的闹钟。
  • 返回值:

    • 返回值是之前设置的闹钟时间的剩余秒数,如果之前没有设置闹钟,则返回0。
行为描述
  • 信号发送:当 alarm 函数设置的计时器到期时,将向进程发送 SIGALRM 信号。如果进程没有捕获或忽略此信号,其默认行为是终止进程。
  • 闹钟取消:通过传递参数0可以取消之前设置的计时器。
  • 闹钟覆盖:如果在之前的 alarm 调用之后再次调用 alarm,新的调用将覆盖旧的设置。
示例

下面是一个使用 alarm 函数的示例,该示例设置一个5秒后的计时器,并捕获 SIGALRM 信号:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>void handle_sigalrm(int sig) {printf("Received SIGALRM - alarm went off!\n");
}int main() {// 设置 SIGALRM 的处理函数signal(SIGALRM, handle_sigalrm);// 设置 alarm 为5秒alarm(5);printf("Alarm set for 5 seconds...\n");// 等待信号到来,通常这里会做一些其他的工作pause();  // 使进程挂起直到捕获到一个信号printf("Continuing execution...\n");return 0;
}

在这个程序中,通过 alarm 函数设定一个5秒后的计时器,并通过 signal 函数设定 SIGALRM 的处理函数 handle_sigalrm。程序执行 pause() 函数后会挂起直到收到信号。一旦信号 SIGALRM 被接收,相应的处理函数将被调用,然后程序继续执行。

使用场景

alarm 函数常用于以下场景:

  • 实现超时:在执行可能长时间运行的操作时,用于强制超时。
  • 周期性任务:虽然 alarm 只能设置一个定时器,它可以用于触发周期性执行的任务。
  • 资源管理:在一段时间后自动释放或检查资源状态。

这个函数提供了一个简单但有效的机制来处理与时间相关的事件,尤其是在需要在指定时间后执行特定操作的场景中非常有用。

/*#include <unistd.h>unsigned int alarm(unsigned int seconds);- 功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,函数会给当前的进程发送一个信号:SIGALARM- 参数:seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。取消一个定时器,通过alarm(0)。- 返回值:- 之前没有定时器,返回0- 之前有定时器,返回之前的定时器剩余的时间- SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。alarm(10);  -> 返回0过了1秒alarm(5);   -> 返回9alarm(100) -> 该函数是不阻塞的
*/#include <stdio.h>
#include <unistd.h>int main() {int seconds = alarm(5);printf("seconds = %d\n", seconds);  // 0sleep(2);seconds = alarm(2);    // 不阻塞printf("seconds = %d\n", seconds);  // 3while(1) {}return 0;
}
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson26$ ./a.out 
seconds = 002multiPr
seconds = 3
Alarm clock
// 1秒钟电脑能数多少个数?
#include <stdio.h>
#include <unistd.h>/*实际的时间 = 内核时间 + 用户时间 + 消耗的时间进行文件IO操作的时候比较浪费时间定时器,与进程的状态无关(自然定时法)。无论进程处于什么状态,alarm都会计时。
*/int main() {    alarm(1);int i = 0;while(1) {printf("%i\n", i++);}return 0;
}
setitimer

setitimer 函数是 alarm 函数的更灵活和强大的版本,提供了对进程定时器的精细控制。它允许设置可以重复的定时器,并且具有更高的时间精度。setitimer 在 Unix-like 系统中使用广泛,通常定义在 <sys/time.h> 头文件中。

函数定义

在 C 语言中,setitimer 的定义如下:

#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
  • 参数:
    • int which: 定时器的类型。可选值包括 ITIMER_REAL(减少真实时间,到期发送 SIGALRM)、ITIMER_VIRTUAL(减少进程虚拟时间,到期发送 SIGVTALRM)、ITIMER_PROF(减少进程运行时间加上系统时间,到期发送 SIGPROF)。
    • const struct itimerval *new_value: 指向 itimerval 结构的指针,该结构指定新的定时器值。
    • struct itimerval *old_value: 可选,用于存储之前定时器的值。如果不关心旧值,可以设置为 NULL

struct itimerval 结构定义如下:

struct itimerval {struct timeval it_interval; // 下一次定时器触发的间隔时间struct timeval it_value;    // 定时器首次触发的延迟时间
};struct timeval {long tv_sec;  // 秒long tv_usec; // 微秒
};
  • it_interval 设置为非零值时,setitimer 会重复触发信号。
  • it_value 设置为零时,定时器被禁用。
返回值
  • 成功时,setitimer 返回 0
  • 失败时,返回 -1 并设置 errno 以指示错误原因(如 EINVAL 表示参数无效)。
示例

以下是一个使用 setitimer 来每隔一定时间发送 SIGALRM 的示例:

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>void timer_handler(int signum) {static int count = 0;printf("Timer expired %d times\n", ++count);
}int main() {struct sigaction sa;memset(&sa, 0, sizeof(sa));sa.sa_handler = &timer_handler;sigaction(SIGALRM, &sa, NULL);struct itimerval timer;timer.it_value.tv_sec = 1; // 初始延迟1秒timer.it_value.tv_usec = 0;timer.it_interval.tv_sec = 2; // 之后每2秒触发一次timer.it_interval.tv_usec = 0;setitimer(ITIMER_REAL, &timer, NULL);while (1) {sleep(1);  // 让程序保持运行以持续接收信号}return 0;
}

在这个程序中,定时器首次设置为1秒后触发,之后每2秒重复触发。信号处理函数 timer_handler 在每次信号到来时被调用,记录并打印触发次数。

使用场景

setitimer 常用于需要高精度定时或需要定时器重复触发的场合,如:

  • 性能监控:使用 ITIMER_PROF 来监测程序的性能。
  • 资源管理:定期检查和管理系统资源。
  • 实时应用:在需要精确控制时间的实时应用中,比如多媒体处理或游戏。

这个函数提供了对 Unix 系统中进程定时器的高级控制,但也需要注意使用 setitimer 可能会引入复杂性,特别是在涉及信号和多线程

时。

/*#include <sys/time.h>int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);- 功能:设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时- 参数:- which : 定时器以什么时间计时ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM   常用ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRMITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF- new_value: 设置定时器的属性struct itimerval {      // 定时器的结构体struct timeval it_interval;  // 每个阶段的时间,间隔时间struct timeval it_value;     // 延迟多长时间执行定时器};struct timeval {        // 时间的结构体time_t      tv_sec;     //  秒数     suseconds_t tv_usec;    //  微秒    };过10秒后,每个2秒定时一次- old_value :记录上一次的定时的时间参数,一般不使用,指定NULL- 返回值:成功 0失败 -1 并设置错误号
*/#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>// 过3秒以后,每隔2秒钟定时一次
int main() {struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定时器开始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0;
}
定时器开始了...
Alarm clock

信号捕捉函数

signal

在 C 语言中,signal 函 方式。它是 Unix 和类 Unix 系统中的标准部分,用于定义当特定信号被传递给进程时应执行的行为。signal 函数定义在 <signal.h> 头文件中。

函数定义

signal 函数的原型如下:

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
  • 参数:
    • int sig: 要处理的信号代码,如 SIGINT(中断信号,通常由 Ctrl+C 产生),SIGTERM(终止信号),SIGALRM(由 alarm 函数产生的定时信号),等等。
    • void (*func)(int): 指向信号处理函数的指针。该函数必须接受一个整型参数(即信号编号)。此参数可以是一个指向函数的指针,用于处理信号;也可以是 SIG_IGN 忽略信号,或 SIG_DFL 使用信号的默认处理方式。
返回值
  • 返回一个指向之前关联到指定信号的处理函数的指针。如果调用失败,返回 SIG_ERR 并设置 errno 来指示错误。
示例

下面是一个使用 signal 函数来设置信号处理程序的示例,该示例中处理 SIGINT 信号:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void  (int signal) {printf("Received SIGINT, exiting...\n");exit(0);
}int main() {// 设置 SIGINT 的处理函数if (signal(SIGINT, signal_handler) == SIG_ERR) {printf("Failed to set signal handler.\n");return 1;}printf("Waiting for SIGINT...\n");// 让程序保持运行,等待信号while(1) {sleep(1);}return 0;
}

在这个程序中,当用户按 Ctrl+C 时,将不会执行默认的终止操作,而是调用 signal_handler 函数,打印一条消息并退出程序。

注意事项和限制
  • 可移植性问题signal 函数的行为在不同的系统或库实现之间可能会有所不同。特别是在信号被处理时程序的行为(如信号是否自动重置为默认行为)可能不一致。
  • 推荐使用 sigaction:由于 signal 函数的这些可移植性问题,建议在需要精确控制信号处理行为的情况下使用更现代且功能更全的 sigaction 函数。sigaction 提供了更详细的控制,包括阻止在信号处理函数执行时送达的其他信号等功能。

signal 函数是处理 Unix 信号的基本工具之一,适用于简单的信号处理需求,但在复杂应用中,更稳定且功能丰富的 sigaction 往往是更好的选择。

/*#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);- 功能:设置某个信号的捕捉行为- 参数:- signum: 要捕捉的信号- handler: 捕捉到信号要如何处理- SIG_IGN : 忽略信号- SIG_DFL : 使用信号默认的行为- 回调函数 :  这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。回调函数:- 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义- 不是程序员调用,而是当信号产生,由内核调用- 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。- 返回值:成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL失败,返回SIG_ERR,设置错误号SIGKILL SIGSTOP不能被捕捉,不能被忽略。
*/#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>void myalarm(int num) {printf("捕捉到了信号的编号是:%d\n", num);printf("xxxxxxx\n");
}// 过3秒以后,每隔2秒钟定时一次
int main() {  // 注册信号捕捉// signal(SIGALRM, SIG_IGN);// signal(SIGALRM, SIG_DFL);// void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。signal(SIGALRM, myalarm);struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定时器开始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0;
}
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson26$ ./a.out 
定时器开始了...
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14

信号集

许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为

信号集的数据结构来表示,其系统数据类型为 sigset_t。

◼ 在 PCB 中有两个非常重要的信号集。一个称之为 “阻塞信号集” (阻塞信号被处理),另一个称之为

“未决信号集” 。这两个信号集都是内核使用位图(二进制位 )机制来实现的。但操作系统不允许我

们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数

来对 PCB 中的这两个信号集进行修改。

◼ 信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间。

◼ 信号的 “阻塞” 是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

◼ 信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,

所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。

image-20240507172721278

信号集相关函数

sigemptyset

在 C 语言中,sigemptyset 函数用于初始化一个信号集,将其清空,即从该集合中移除所有信号。这个函数是信号处理相关函数的一部分,定义在 <signal.h> 头文件中,并且通常与其他信号操作函数一起使用,以控制信号的阻塞、忽略或处理行为。

函数定义

sigemptyset 函数的定义如下:

#include <signal.h>
int sigemptyset(sigset_t *set);
  • 参数:
    • sigset_t *set: 指向 sigset_t 类型的指针,该类型用于存储信号集。
返回值
  • 成功时,sigemptyset 返回 0
  • 失败时,通常情况下,此函数不应失败,因此如果出现非零返回值,应检查代码其他部分是否有错误。
示例

以下是一个如何使用 sigemptyset 函数来初始化信号集的示例:

#include <stdio.h>
#include <signal.h>int main() {sigset_t signalSet;int result;// 初始化信号集为空result = sigemptyset(&signalSet);if (result == 0) {printf("Signal set successfully initialized as empty.\n");} else {printf("Failed to initialize signal set.\n");}// 现在可以向此空集中添加信号,或用它来设置信号掩码等操作return 0;
}

在这个示例中,首先声明一个 sigset_t 类型的变量 signalSet,然后使用 sigemptyset 函数初始化它,确保该信号集不包含任何信号。这是处理信号时设置信号集的通常做法,尤其是在设置新的信号掩码或在多线程应用中为特定线程配置信号之前。

使用场景

sigemptyset 通常在下列情况中使用:

  • 在设置新的信号处理或信号掩码之前,确保信号集中不含任何未预期的信号。
  • 清除现有信号集以重新配置程序的信号处理行为。
  • 在多线程程序中,为不同线程设置不同的信号掩码。

这个函数是信号处理编程中的基础,允许程序员精确地控制哪些信号应该被阻塞或处理,从而提高程序的可靠性和响应性。

sigfillset

在 C 语言中,sigfillset 函数用于初始化一个信号集,并将所有已定义的信号添加到这个集合中。这个函数是信号处理相关函数的一部分,定义在 <signal.h> 头文件中,并且通常与其他信号操作函数一起使用,以控制信号的阻塞、忽略或处理行为。

函数定义

sigfillset 函数的定义如下:

#include <signal.h>
int sigfillset(sigset_t *set);
  • 参数:
    • sigset_t *set: 指向 sigset_t 类型的指针,该类型用于存储信号集。
返回值
  • 成功时,sigfillset 返回 0
  • 失败时,通常情况下,此函数不应失败,因此如果出现非零返回值,应检查代码其他部分是否有错误。
示例

以下是一个如何使用 sigfillset 函数来初始化并填充信号集的示例:

#include <stdio.h>
#include <signal.h>int main() {sigset_t signalSet;int result;// 初始化信号集并添加所有信号result = sigfillset(&signalSet);if (result == 0) {printf("Signal set successfully initialized and filled with all signals.\n");} else {printf("Failed to initialize and fill the signal set.\n");}// 可以用此信号集来阻塞所有信号if (sigprocmask(SIG_SETMASK, &signalSet, NULL) == 0) {printf("All signals are now blocked.\n");} else {perror("sigprocmask");}return 0;
}

在这个示例中,首先使用 sigfillset 函数初始化并填充 sigset_t 类型的变量 signalSet,使其包含所有可用信号。然后使用 sigprocmask 函数设置信号掩码,阻塞所有信号,这在需要确保某代码段不被信号打断时非常有用。

使用场景

sigfillset 通常在下列情况中使用:

  • 当需要阻塞进程中的所有信号以执行关键代码段时,可以使用此函数填充信号集,并通过 sigprocmask 设置为当前的信号掩码。
  • 在需要清理或重置信号掩码到最初状态时,可以使用此函数以确保所有信号都被考虑到。

这个函数对于需要精细控制信号行为的程序是非常重要的,它允许开发者确保在特定时刻不会有任何外部信号干扰程序的执行。

sigdelset

在 C 语言中,sigdelset 函数用于从一个信号集中删除一个特定的信号。这个函数是 Unix 和类 Unix 系统中信号处理操作的一部分,定义在 <signal.h> 头文件中。使用 sigdelset 可以修改信号集,这对于精确控制哪些信号应该被阻塞或忽略是非常有用的。

函数定义

sigdelset 函数的定义如下:

#include <signal.h>
int sigdelset(sigset_t *set, int signum);
  • 参数:
    • sigset_t *set: 指向 sigset_t 类型的指针,该类型用于存储信号集。
    • int signum: 指定要从信号集中删除的信号。
返回值
  • 成功时,sigdelset 返回 0
  • 失败时,返回 -1 并设置 errno 来指示错误。常见的错误原因是 signum 不是有效的信号编号。
示例

以下是一个使用 sigdelset 函数来从信号集中删除一个信号的示例:

#include <stdio.h>
#include <signal.h>int main() {sigset_t signalSet;int result;// 初始化信号集并添加所有信号sigfillset(&signalSet);// 从信号集中删除 SIGINTresult = sigdelset(&signalSet, SIGINT);if (result == 0) {printf("SIGINT successfully removed from the signal set.\n");} else {perror("sigdelset");}// 现在可以使用修改后的信号集来阻塞其他所有信号,而不阻塞 SIGINTif (sigprocmask(SIG_SETMASK, &signalSet, NULL) == 0) {printf("All signals except SIGINT are now blocked.\n");} else {perror("sigprocmask");}return 0;
}

在这个示例中,首先使用 sigfillset 函数初始化并填充 sigset_t 类型的变量 signalSet,使其包含所有可用信号。然后使用 sigdelset 函数从信号集中删除 SIGINT(通常是 Ctrl+C 生成的中断信号)。之后,通过 sigprocmask 设置信号掩码,阻塞除 SIGINT 之外的所有信号。

使用场景

sigdelset 通常在以下情况中使用:

  • 在需要确保某些信号不被阻塞时,比如在执行关键任务时需要响应中断信号(如 SIGINT)。
  • 修改现有的信号集,以便在多线程应用中为特定线程配置信号处理或阻塞策略。
  • 在信号处理程序中动态调整信号掩码,以便在处理某些信号时允许或禁止其他信号的递送。

这个函数使得信号处理在程序中更加灵活,有助于提高程序对外部事件的响应能力和可控性。

sigismember

在 C 语言中,sigismember 函数用于检查特定信号是否为给定信号集的成员。这个函数是 Unix 类操作系统中 <signal.h> 头文件提供的信号处理函数集的一部分。它常用于确定特定信号是否已被添加到信号集中,这对于配置应用程序中的信号处理行为非常关键。

函数定义

sigismember 函数的定义如下:

#include <signal.h>
int sigismember(const sigset_t *set, int signum);
  • 参数
    • const sigset_t *set:指向之前通过 sigemptysetsigfillset 初始化或通过 sigaddsetsigdelset 修改过的信号集的指针。
    • int signum:要检测其成员资格的信号编号。
返回值
  • 0:该信号不是信号集的成员。
  • 1:该信号是信号集的成员。
  • -1:发生错误,此时 errno 被设置为具体的错误代码(例如,如果 signum 是无效的信号编号,可能会设置 EINVAL)。
示例

下面是使用 sigismember 函数的一个示例,该示例演示了如何检查信号 SIGINT 是否在信号集中:

#include <stdio.h>
#include <signal.h>int main() {sigset_t signalSet;sigemptyset(&signalSet);  // 初始化信号集为空sigaddset(&signalSet, SIGINT);  // 向信号集中添加 SIGINT// 检查 SIGINT 是否在集合中if (sigismember(&signalSet, SIGINT)) {printf("SIGINT is a member of the set.\n");} else {printf("SIGINT is not a member of the set.\n");}return 0;
}
使用场景

sigismember 函数通常在以下情况中使用:

  • 信号掩码管理:在设置或修改进程的信号掩码之前检查某个信号是否已经在信号集中,以决定是否需要添加或删除。
  • 信号处理逻辑:在信号处理函数中判断某个信号是否应被当前处理逻辑处理。
  • 多线程应用:在多线程程序中,为特定线程设置或检查信号掩码时确定某信号的状态。

通过这种方式,sigismember 功能使程序能够更精确地控制和响应信号,提高了程序对信号的管理能力和灵活性。

/*以下信号集相关的函数都是对自定义的信号集进行操作。int sigemptyset(sigset_t *set);- 功能:清空信号集中的数据,将信号集中的所有的标志位置为0- 参数:set,传出参数,需要操作的信号集- 返回值:成功返回0, 失败返回-1int sigfillset(sigset_t *set);- 功能:将信号集中的所有的标志位置为1- 参数:set,传出参数,需要操作的信号集- 返回值:成功返回0, 失败返回-1int sigaddset(sigset_t *set, int signum);- 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号- 参数:- set:传出参数,需要操作的信号集- signum:需要设置阻塞的那个信号- 返回值:成功返回0, 失败返回-1int sigdelset(sigset_t *set, int signum);- 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号- 参数:- set:传出参数,需要操作的信号集- signum:需要设置不阻塞的那个信号- 返回值:成功返回0, 失败返回-1int sigismember(const sigset_t *set, int signum);- 功能:判断某个信号是否阻塞- 参数:- set:需要操作的信号集- signum:需要判断的那个信号- 返回值:1 : signum被阻塞0 : signum不阻塞-1 : 失败*/#include <signal.h>
#include <stdio.h>int main() {// 创建一个信号集sigset_t set;// 清空信号集的内容sigemptyset(&set);// 判断 SIGINT 是否在信号集 set 里int ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 添加几个信号到信号集中sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);// 判断SIGINT是否在信号集中ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 判断SIGQUIT是否在信号集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}// 从信号集中删除一个信号sigdelset(&set, SIGQUIT);// 判断SIGQUIT是否在信号集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}return 0;
}
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson26$ ./a.out 
SIGINT 不阻塞
SIGINT 阻塞
SIGQUIT 阻塞
SIGQUIT 不阻塞

image-20240507193710572

sigprocmask

在 C 语言中,sigprocmask 函数用于检查和更改进程的信号掩码。信号掩码是指定哪些信号将被阻塞(即暂时不递送给进程)的信号集。sigprocmask 是 Unix 和类 Unix 系统中信号处理的一个重要工具,定义在 <signal.h> 头文件中。通过使用这个函数,可以在运行时动态地更改哪些信号被阻塞,从而控制程序的行为。

函数定义

sigprocmask 函数的原型如下:

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 参数:
    • int how: 指定如何修改当前信号掩码。可能的值有:
      • SIG_BLOCK: 将 set 指定的信号添加到当前信号掩码中,即阻塞这些信号。
      • SIG_UNBLOCK: 从当前信号掩码中移除 set 指定的信号,即不再阻塞这些信号。
      • SIG_SETMASK: 将当前信号掩码设置为 set 指定的信号集。
    • const sigset_t *set: 指向信号集的指针,这个信号集指定了要阻塞或解除阻塞的信号。如果为 NULL,则 how 参数没有效果,但如果 oldset 不是 NULL,当前信号掩码仍然可以被存储。
    • sigset_t *oldset: 如果不为 NULL,则当前信号掩码在函数修改前将被存储在 oldset 指定的位置。
返回值
  • 成功时,返回 0
  • 失败时,返回 -1 并设置 errno 以指示错误原因(例如,EINVAL 表示 how 参数无效)。
示例

以下是一个使用 sigprocmask 函数来修改信号掩码的示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>int main() {sigset_t newSet, oldSet;// 初始化新信号集,并添加 SIGINTsigemptyset(&newSet);sigaddset(&newSet, SIGINT);// 阻塞 SIGINT 信号if (sigprocmask(SIG_BLOCK, &newSet, &oldSet) < 0) {perror("sigprocmask");return 1;}printf("SIGINT is now blocked.\n");// 模拟一些工作,延时 5 秒sleep(5);// 恢复之前的信号掩码if (sigprocmask(SIG_SETMASK, &oldSet, NULL) < 0) {perror("sigprocmask");return 1;}printf("SIGINT is no longer blocked.\n");return 0;
}
使用场景

sigprocmask 函数常用于以下场景:

  • 控制信号的阻塞:在执行关键代码段时,可能需要阻塞某些信号,以防止信号处理函数中断这些操作。
  • 动态信号管理:根据程序的状态更改哪些信号应当被阻塞或接收。
  • 信号掩码的查询:通过传递 NULLset 参数,可以查询当前的信号掩码状态。

这个函数在多线程程序和复杂的信号处理逻辑中尤其有用,它允许程序精确控制在何时应该处理特定的信号。

sigpending

在 Unix 和类 Unix 系统中,sigpending 函数用于检查当前阻塞且未决的信号集,即那些已被发送到进程但由于当前的信号掩码而被阻止的信号。此函数是信号处理功能的一部分,定义在 <signal.h> 头文件中。

函数定义

sigpending 函数的定义如下:

#include <signal.h>
int sigpending(sigset_t *set);
  • 参数:
    • sigset_t *set: 指向 sigset_t 类型的指针,该指针用于输出当前进程的未决信号集。
返回值
  • 成功时,返回 0
  • 失败时,返回 -1 并设置 errno 以指示错误原因。
示例

以下是使用 sigpending 函数来检查和输出当前阻塞但未决的信号的示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>int main() {sigset_t newSet, pendingSet;// 初始化新信号集并添加 SIGINT 和 SIGTERMsigemptyset(&newSet);sigaddset(&newSet, SIGINT);sigaddset(&newSet, SIGTERM);// 阻塞 SIGINT 和 SIGTERM 信号if (sigprocmask(SIG_BLOCK, &newSet, NULL) < 0) {perror("sigprocmask");return 1;}// 发送 SIGINT 信号给自己raise(SIGINT);// 检查未决的信号if (sigpending(&pendingSet) < 0) {perror("sigpending");return 1;}// 检查 SIGINT 是否在未决信号集中if (sigismember(&pendingSet, SIGINT)) {printf("SIGINT is pending.\n");}// 检查 SIGTERM 是否在未决信号集中if (sigismember(&pendingSet, SIGTERM)) {printf("SIGTERM is pending.\n");} else {printf("SIGTERM is not pending.\n");}return 0;
}

在这个示例中,首先将 SIGINT 和 SIGTERM 添加到新的信号集中并阻塞这些信号。然后通过 raise 函数向进程自己发送 SIGINT 信号。由于 SIGINT 被阻塞,它变成未决状态。随后,使用 sigpending 检查哪些信号是未决的,并通过 sigismember 检查 SIGINT 和 SIGTERM 是否在未决信号集中。

使用场景

sigpending 函数通常在需要了解哪些信号正在等待被处理的情况下使用,特别是在:

  • 复杂的信号处理逻辑中,需要确定当前哪些信号由于阻塞掩码而未被处理。
  • 调试多线程应用程序中的信号处理问题时。
  • 在长时间运行的服务中,程序可能需要定期检查是否有重要信号等待处理。

这个函数帮助开发者更好地理解和控制程序的行为,尤其是在涉及信号阻塞和处理时。

/*int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);- 功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)- 参数:- how : 如何对内核阻塞信号集进行处理SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变假设内核中默认的阻塞信号集是mask, mask | setSIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞mask &= ~setSIG_SETMASK:覆盖内核中原来的值- set :已经初始化好的用户自定义的信号集- oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL- 返回值:成功:0失败:-1设置错误号:EFAULT、EINVALint sigpending(sigset_t *set);- 功能:获取内核中的未决信号集- 参数:set,传出参数,保存的是内核中的未决信号集中的信息。
*/// 编写一个程序,把所有的常规信号(1-31)的未决状态打印到屏幕
// 设置某些信号是阻塞的,通过键盘产生这些信号#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>int main() {// 设置2、3号信号阻塞sigset_t set;sigemptyset(&set);// 将2号和3号信号添加到信号集中sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);// 修改内核中的阻塞信号集sigprocmask(SIG_BLOCK, &set, NULL);int num = 0;while(1) {num++;// 获取当前的未决信号集的数据sigset_t pendingset;sigemptyset(&pendingset);sigpending(&pendingset);// 遍历前32位for(int i = 1; i <= 31; i++) {if(sigismember(&pendingset, i) == 1) {printf("1");}else if(sigismember(&pendingset, i) == 0) {printf("0");}else {perror("sigismember");exit(0);}}printf("\n");sleep(1);if(num == 10) {// 解除阻塞sigprocmask(SIG_UNBLOCK, &set, NULL);}}return 0;
}
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson26$ ./a.out 
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
^C0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
^\0110000000000000000000000000000
0110000000000000000000000000000
sigaction

在 Unix 和类 Unix 系统中,sigaction 函数用于检查和更改信号的处理方式。这是信号处理的一个高级工具,提供比 signal 函数更详细的控制,允许程序定义如何处理特定的信号,包括设置信号处理函数、指定信号处理过程中哪些信号应被阻塞等。

函数定义

sigaction 函数定义在 <signal.h> 头文件中,其原型如下:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • 参数:
    • int signum: 要操作的信号编号,除了 SIGKILLSIGSTOP,因为这两个信号不能被捕获或忽略。
    • const struct sigaction *act: 指向 sigaction 结构的指针,该结构指定了新的信号处理设置。
    • struct sigaction *oldact: 如果不为 NULL,则存储旧的信号处理设置。

struct sigaction 结构定义如下:

struct sigaction {void     (*sa_handler)(int);void     (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int      sa_flags;void     (*sa_restorer)(void);
};
  • sa_handler: 指向信号处理函数的指针,或者可以是 SIG_IGN(忽略信号)或 SIG_DFL(恢复默认行为)。
  • sa_sigaction: 一个可选的信号处理函数,用于接收额外的信号信息,只有在 sa_flags 中设置了 SA_SIGINFO 时才使用。
  • sa_mask: 在信号处理函数执行期间需要阻塞的附加信号。
  • sa_flags: 用于修改信号处理的行为,例如 SA_RESTART 使被信号中断的系统调用自动重启。
  • sa_restorer: 这是一个过时的、不再使用的字段,用于在早期的系统实现中恢复信号上下文。
返回值
  • 成功时,返回 0
  • 失败时,返回 -1 并设置 errno 以指示错误原因。
示例

以下是一个使用 sigaction 设置信号处理函数的示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void handle_sigint(int signum) {printf("Caught signal %d\n", signum);
}int main() {struct sigaction sa;sa.sa_handler = handle_sigint;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;if (sigaction(SIGINT, &sa, NULL) == -1) {perror("sigaction");return 1;}printf("Press Ctrl+C\n");sleep(10); // Wait for signalreturn 0;
}

在这个例子中,我们设置了 SIGINT 信号的处理函数为 handle_sigint。当用户按下 Ctrl+C(通常发送 SIGINT)时,程序将捕获并处理该信号。

使用场景

sigaction 是信号处理中非常强大的工具,常用于:

  • 定制复杂的信号处理逻辑。
  • 在处理信号时阻塞其他信号。
  • 处理需要从信号处理程序中获取额外信息的高级场景。

这个函数提供了对信号处理的全面控制,使得应用程序能够更稳定和安全地运行,尤其是在多线程环境和需要精细控制信号处理的应用中。

/*#include <signal.h>int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);- 功能:检查或者改变信号的处理。信号捕捉- 参数:- signum : 需要捕捉的信号的编号或者宏值(信号的名称)- act :捕捉到信号之后的处理动作- oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL- 返回值:成功 0失败 -1struct sigaction {// 函数指针,指向的函数就是信号捕捉到之后的处理函数void     (*sa_handler)(int);// 不常用void     (*sa_sigaction)(int, siginfo_t *, void *);// 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。sigset_t   sa_mask;// 使用哪一个信号处理对捕捉到的信号进行处理// 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigactionint        sa_flags;// 被废弃掉了void     (*sa_restorer)(void);};*/
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>void myalarm(int num) {printf("捕捉到了信号的编号是:%d\n", num);printf("xxxxxxx\n");
}// 过3秒以后,每隔2秒钟定时一次
int main() {struct sigaction act;act.sa_flags = 0;act.sa_handler = myalarm;sigemptyset(&act.sa_mask);  // 清空临时阻塞信号集// 注册信号捕捉sigaction(SIGALRM, &act, NULL);struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定时器开始了...\n");if(ret == -1) {perror("setitimer");exit(0);}// getchar();while(1);return 0;
}
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson26$ ./a.out 
定时器开始了...
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx
捕捉到了信号的编号是:14
xxxxxxx

内核实现信号捕捉的过程

在 Unix 和类 Unix 系统中,信号是操作系统用来通知进程某些事件已经发生的一种机制。信号可以由操作系统因为外部事件(比如硬件异常、终端用户操作)触发,或者由应用程序(如调用 killraise)显式生成。下面是操作系统内核实现信号捕捉的基本过程:

  1. 信号的生成

信号可以通过多种方式生成:

  • 外部事件:例如,用户按下 Ctrl+C 在终端生成 SIGINT 信号。
  • 程序错误:如除零操作产生 SIGFPE(浮点异常信号)。
  • 显式调用:程序调用 killraise 等函数直接发送信号。
  1. 信号的传递

一旦信号生成,内核将其添加到目标进程的信号队列中。每个进程都有一个信号队列和一个或多个信号掩码(决定哪些信号当前被阻塞)。如果信号未被阻塞,它被标记为“待处理”(pending)。

  1. 信号的捕捉和处理

当进程从内核模式返回到用户模式前,内核检查待处理的信号:

  • 信号掩码检查:内核检查进程的信号掩码,看是否有待处理的信号被阻塞。
  • 信号处理分配:如果信号未被阻塞,内核将查找与该信号关联的处理动作。这些动作可以是默认动作(比如终止进程)、忽略信号或者是一个指向用户定义函数的指针(信号处理函数)。
  1. 用户态信号处理函数的调用

如果为信号指定了用户定义的处理函数(通过如 sigaction 设置):

  • 上下文保存:在调用信号处理函数前,内核保存当前的处理上下文(如CPU寄存器状态),以便信号处理完成后能恢复执行。
  • 设置信号栈:如果指定了信号处理栈(通过 sigaltstack),内核会切换到这个栈。
  • 调用处理函数:内核修改程序计数器(PC),让其指向信号处理函数的入口,然后返回用户空间执行该函数。
  1. 信号处理完毕
  • 处理函数返回:信号处理函数执行完毕后,从处理函数返回。
  • 上下文恢复:内核恢复在信号处理前保存的上下文,进程继续执行信号到达前被中断的代码。
  1. 信号处理的后续动作

根据信号处理的结果,进程可能继续正常执行,或者因为某些信号的默认行为(如 SIGTERM)而终止。

这个过程涉及到复杂的内核机制,包括中断处理、上下文切换和用户空间与内核空间之间的交互。操作系统的信号处理机制是理解进程管理和操作系统响应外部事件能力的重要部分。在多任务和多用户环境中,这种机制尤为重要,因为它允许操作系统控制和协调不同进程的行为。

SIGCHLD信号

SIGCHLD信号产生的条件

 子进程终止时

 子进程接收到 SIGSTOP 信号停止时

 子进程处在停止态,接受到SIGCONT后唤醒时

◼ 以上三种条件都会给父进程发送 SIGCHLD 信号,父进程默认会忽略该信号

/*SIGCHLD信号产生的3个条件:1.子进程结束2.子进程暂停了3.子进程继续运行都会给父进程发送该信号,父进程默认忽略该信号。使用SIGCHLD信号解决僵尸进程的问题。
*/#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>void myFun(int num) {printf("捕捉到的信号 :%d\n", num);// 回收子进程PCB的资源,这样的话父进程没有办法做事情// while(1) {//     wait(NULL); // }while(1) {int ret = waitpid(-1, NULL, WNOHANG);//回收所有子进程,且非阻塞if(ret > 0) {printf("child die , pid = %d\n", ret);} else if(ret == 0) {// 说明还有子进程活着break;} else if(ret == -1) {// 没有子进程break;}}
}int main() {// 提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉sigset_t set;sigemptyset(&set);sigaddset(&set, SIGCHLD);sigprocmask(SIG_BLOCK, &set, NULL);// 创建一些子进程pid_t pid;for(int i = 0; i < 20; i++) {pid = fork();//避免子进程再生成子进程if(pid == 0) {break;}}if(pid > 0) {// 父进程// 捕捉子进程死亡时发送的SIGCHLD信号,进行回收struct sigaction act;act.sa_flags = 0;act.sa_handler = myFun; //指定回调函数sigemptyset(&act.sa_mask);sigaction(SIGCHLD, &act, NULL);// 注册完信号捕捉以后,解除阻塞sigprocmask(SIG_UNBLOCK, &set, NULL);while(1) {printf("parent process pid : %d\n", getpid());sleep(2);}} else if( pid == 0) {// 子进程printf("child process pid : %d\n", getpid());}return 0;
}
解决僵尸进程问题
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson26$ gcc sigchld.c 
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson26$ ./a.out 
child process pid : 8662
child process pid : 8663
child process pid : 8666
child process pid : 8665
child process pid : 8664
child process pid : 8667
child process pid : 8669
child process pid : 8668
child process pid : 8670
child process pid : 8673
child process pid : 8671
child process pid : 8674
child process pid : 8672
child process pid : 8675
child process pid : 8676
child process pid : 8677
捕捉到的信号 :17
child process pid : 8678
child process pid : 8681
child process pid : 8679
child process pid : 8680
child die , pid = 8662
child die , pid = 8663
child die , pid = 8664
child die , pid = 8665
child die , pid = 8666
child die , pid = 8667
child die , pid = 8668
child die , pid = 8669
child die , pid = 8670
child die , pid = 8671
child die , pid = 8672
child die , pid = 8673
child die , pid = 8674
child die , pid = 8675
child die , pid = 8676
child die , pid = 8677
child die , pid = 8678
child die , pid = 8679
child die , pid = 8680
child die , pid = 8681
捕捉到的信号 :17
parent process pid : 8661
parent process pid : 8661
parent process pid : 8661
parent process pid : 8661
parent process pid : 8661
parent process pid : 8661
parent process pid : 8661
parent process pid : 8661
parent process pid : 8661

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

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

相关文章

通过 Java 操作 redis -- String 基本命令

关于 redis String 类型的相关命令推荐看 Redis - String 字符串 要想通过 Java 操作 redis&#xff0c;首先要连接上 redis 服务器&#xff0c;推荐看通过 Java 操作 redis -- 连接 redis 本博客只介绍了一小部分常用的命令&#xff0c;其他的命令根据上面推荐的博客也能很简单…

Day 63:单调栈 LeedCode 84.柱状图中最大的矩形

84. 柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,2,3] 输出&#xff1a;10 解释&a…

论文精读-基于FPGA的卷积神经网络和视觉Transformer通用加速器

文章目录 论文精读-基于FPGA的卷积神经网络和视觉Transformer通用加速器概述研究背景卷积和注意力机制概述计算方式差异非线性与归一化操作差异 问题小结 加速器设计乘法单元探索非线性与归一化加速单元加速器架构 实验结果QA 论文精读-基于FPGA的卷积神经网络和视觉Transforme…

交易复盘-20240507

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 蔚蓝生物 (5)|[9:25]|[36187万]|4.86 百合花…

从0到1提审苹果商店(appstore)上线一款新APP

本篇主要复盘和介绍一款APP如何从0到1上线到苹果商店,将我自己项目遇到的坑跟大家分享,希望能为同样做开发或者运营的你提供经验,少走弯路。 如果你是24年1月1日之后开始首次提审APP,还需要先将自己的APP在工信部备案,苹果后台增加了工信部备案号的填写,备案方法和经验如…

SAP PP学习笔记12 - 评估MRP的运行结果

上一章讲了MRP的概念&#xff0c;参数&#xff0c;配置等内容。 SAP PP学习笔记11 - PP中的MRP相关概念&#xff0c;参数&#xff0c;配置-CSDN博客 本章来讲 MRP跑完之后呢&#xff0c;要怎么评估这个MRP的运行结果。 1&#xff0c;Stock/Requirements List and MRP List 在…

T型槽地轨承载力是如何连接整个制造过程的强力桥梁(北重公司设计)

T型槽地轨承载力的定义和计算 T型槽地轨是一种用于工业设备运输和装配的关键组件。它由世界上各行各业的生产商广泛采用&#xff0c;其有效的承载力使其成为连接整个制造过程的强力桥梁。本文将介绍T型槽地轨的承载力以及相关的设计要点和应用。 承载力的定义和计算 承载力是…

某制造公司屋顶分布式光伏发电案例分享--分布式光伏电力监控系统解决方案

安科瑞薛瑶瑶18701709087/17343930412 ★分布式光伏监控系统 分布式光伏监控电力系统遵循安全可靠、经济合理原则&#xff0c;满足电力系统自动化总体规划要求&#xff0c;且充分考虑光伏发电的因素&#xff0c;对分布式光伏发电、用电进行集中监控、统一调度、统一运维、满足…

vivado Zynq UltraScale+ MPSoC 比特流设置

Zynq UltraScale MPSoC 比特流设置 下表所示 Zynq UltraScale MPSoC 器件的器件配置设置可搭配 set_property <Setting> <Value> [current_design] Vivado 工具 Tcl 命令一起使用。

本地运行AI大模型简单示例

一、引言 大模型LLM英文全称是Large Language Model&#xff0c;是指包含超大规模参数&#xff08;通常在十亿个以上&#xff09;的神经网络模型。2022年11月底&#xff0c;人工智能对话聊天机器人ChatGPT一经推出&#xff0c;人们利用ChatGPT这样的大模型帮助解决很多事情&am…

【Qt 学习笔记】Qt常用控件 | 输入类控件 | Date/Time Edit的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 输入类控件 | Spin Box的使用及说明 文章编号&#xff1…

YOLOv5 V7.0 - rknn模型的验证 输出精度(P)、召回率(R)、mAP50、mAP50-95

1.简介 RKNN官方没有提供YOLOv5模型的验证工具&#xff0c;而YOLOv5自带的验证工具只能验证pytorch、ONNX等常见格式的模型性能&#xff0c;无法运行rknn格式。考虑到YOLOv5模型转换为rknn会有一定的精度损失&#xff0c;但是需要具体数值才能进行评估&#xff0c;所以需要一个…

通过三角形相似原理实现单目测距

根据三角形相似原理计算相机焦距&#xff0c;公式为&#xff1a;F (P * D) / W 其中&#xff1a; F: 待求的相机的焦距 P: 图像中目标的宽度&#xff0c;单位像素 D: 真实目标与相机的距离&#xff0c;单位厘米 W: 真实目标的宽度&#xff0c;单位厘米 计算焦距前&#xff0c;…

太原理工大学Python数据分析原理与应用(课外考题:8~11章)

这部分大概只考10分&#xff0c;且大部分出在选择题&#xff0c;填空最多一两个 (仅供参考) 第十章 (理解概念为主&#xff0c;无需看推导过程) 第十一章

QGraphicsView实现简易地图10『自适应窗口大小』

前文链接&#xff1a;QGraphicsView实现简易地图9『层级缩放显示底图』 自适应窗口大小 当地图窗口放大或缩小的时候&#xff0c;需要地图能够动态覆盖整个视口。 1、动态演示效果 2、核心代码 注&#xff1a;WHMapView继承自MapViewvoid WHMapView::resize() {if (m_curLev…

毕业就业信息|基于Springboot+vue的毕业就业信息管理系统的设计与实现(源码+数据库+文档)

毕业就业信息管理系统 目录 基于Springboot&#xff0b;vue的毕业就业信息管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1学生信息管理 2 公司信息管理 3公告类型管理 4公告信息管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设…

C语言----汉诺塔问题

1.什么是汉诺塔问题 简单来说&#xff0c;就是有三个柱子&#xff0c;分别为A柱&#xff0c;B柱&#xff0c;C柱。其中A柱从上往下存放着从小到大的圆盘&#xff0c;我们需要借助B柱和C柱&#xff0c;将A柱上的所有圆盘转移到C柱上&#xff0c;并且一次只能移动一个圆盘&#…

基于Springboot+Vue的Java项目-鲜牛奶订购系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

UE5自动生成地形二:自动生成插件

UE5自动生成地形二&#xff1a;自动生成插件 Polycam使用步骤 本篇主要讲解UE5的一些自动生成地形的插件 Polycam 此插件是通过现实的多角度照片自动建模生成地形数据&#xff0c;也是免费的。这里感谢B站up主古道兮峰的分享 Polycam网站 插件下载地址 插件网盘下载 提取码&a…

机器学习第二天(监督学习,无监督学习,强化学习,混合学习)

1.是什么 基于数据寻找规律从而建立关系&#xff0c;进行升级&#xff0c;如果是以前的固定算式那就是符号学习了 2.基本框架 3.监督学习和无监督式学习&#xff1a; 监督学习&#xff1a;根据正确结果进行数据的训练&#xff1b; 在监督式学习中&#xff0c;训练数据包括输…