从Unix操作系统的早期开始,操作系统就具有向正在运行的进程发送信号的能力。当操作系统向进程发送信号时,操作系统将暂停该进程的正常操作,并给进程一个捕获和处理信号的机会。进程通过运行特殊的信号处理程序例程来捕获和处理信号。信号处理程序退出后,操作系统将在进程最初中断的位置重新启动该进程。
操作系统可以发送许多不同的信号。每种信号类型都由数字标识符标识。您可以在man7.org的信号概述页面上找到Linux操作系统信号类型的完整表以及关于Linux中信号的其他重要细节。
信号的严重程度不同,信号是否可以被捕获和处理也不同。在我链接到的页面上的表格中,您可以找到有关每个信号的默认操作的信息。默认操作是在赢博体育程序未捕获和处理信号时将发生的操作。许多信号类型的默认操作是立即终止进程。在少数情况下,默认操作是简单地忽略信号。此外,一些更严重的信号类型,如SIGKILL,不能被捕获和处理。相反,这些信号类型会导致赢博体育程序立即终止。
在许多情况下,操作系统将生成一个信号来响应错误条件。这类信号最明显的例子包括SIGSEGV(非法内存访问)和SIGFPE(除0或其他非法算术操作)。
用户也可以通过在终端会话中输入中断序列来产生信号。用户可以发送的信号示例包括SIGINT (control-c), SIGQUIT (control-\)和SIGTSTP (control-z)。
另一种向正在运行的赢博体育程序发送信号的方法是在终端中运行kill命令。例如,要将信号SIGINT发送到进程id为1706的进程,您将运行
杀死-SIGINT 1706
kill命令还接受信号的数字代码来代替信号名称。将SIGINT信号发送到进程1706的另一种方法是do
杀死-2 1706
我们还看到,一个赢博体育程序可以通过使用kill()系统调用向另一个赢博体育程序发送信号。例如,要向进程1706发送SIGINT信号,我们将这样做
#include <signal.h> kill(1706,SIGINT);
为了能够捕获和响应信号,赢博体育程序必须为该信号安装信号处理程序。下面的代码示例是一个赢博体育程序,它将捕获并响应SIGTSTP信号。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> int count = 0;void handler(int sig) {if(count == 2) {printf("太多的中断!我不干了! \ n”);退出(1);} else {count++;printf(“我被中断了%d次。\n”,count);}} int main(int argc, char *argv[]) {if (signal(SIGTSTP, handler) == SIG_ERR) {printf(“错误:无法安装handler.\n”);退出(1);} while(1) {printf("Hello!\n");睡眠(1);}}
信号处理函数必须是一个返回void并接受单个整型参数的函数。该参数是正在处理的信号的id号。
要安装信号处理程序,我们调用signal()函数。第一个参数是函数将处理的信号号,第二个参数是我们想要用作信号处理程序的函数。
信号处理程序的一个问题是,它们有时会在不方便的时间运行。在上面的示例中,当赢博体育程序正在打印“Hello!”消息时,信号到达的概率很小,但不是零。在打印“Hello!”消息的过程中运行信号处理程序并将其消息打印到控制台是很尴尬的,因此我们可能希望避免这种情况。
解决方案是暂时阻塞信号。我们通过设置一个信号掩码来实现这一点,该掩码列出了我们想要屏蔽的一组信号。只要设置了掩码,任何发送给赢博体育程序的掩码信号都将被操作系统保留。一旦掩码被移除,任何暂时被阻塞的挂起信号将立即传递给赢博体育程序。
下一个示例演示如何设置掩码以暂时阻止信号。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> int count = 0;void handler(int sig) {if(count == 2) {printf("太多的中断!我不干了! \ n”);退出(1);}计数+ +;printf(“我被中断了%d次。\n”,count);} int main(int argc, char *argv[]) {if (signal(SIGTSTP, handler) == SIG_ERR) {printf(“错误:无法安装handler.\n”);退出(1);} sigset_t prevMask, blockMask;sigemptyset (&blockMask);sigaddset (&blockMask SIGTSTP);while(1) {if (sigprocmask(SIG_SETMASK, &blockMask, &prevMask) == -1) {printf(“错误设置掩码。\n”);退出(1);} printf(“你好! \ n”);睡眠(3);if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1) {printf(“错误设置掩码。\n”);退出(1);}睡眠(3);}}
为了屏蔽一个或多个信号,我们首先创建一个信号集,它由sigset_t类型的结构体表示。要清空集合,然后向其中添加单个信号类型,可以使用sigemptyset()和sigaddset()函数的组合。
为了临时安装屏蔽集,我们调用sigprocmask()。sigprocmask()的第二个和第三个形参是指向两个掩码集的指针:我们要安装的新掩码集,以及用于保存先前掩码设置的第二个掩码集。通常遵循的惯例是,在执行任何需要暂时暂停信号传递的关键操作之前,安装自定义掩码集,同时保存旧的掩码集,然后在离开临界区时重新安装原始掩码集。
Unix信号最初是在赢博体育赢博体育程序都是单线程赢博体育程序的时候设计的。再加上一些线索,情况就复杂了。下面是一些关于线程和信号相互作用的事情:
下面是一个示例程序,演示了混合线程和信号的常用策略。示例程序启动一组设置为永远运行的线程。启动线程后,主程序进入等待状态,该状态将在赢博体育程序接收到SIGINT信号时终止。当程序接收到该信号时,主例程将关闭赢博体育线程并退出。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <pthread.h> static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;Void safe_print(char* message) {pthread_mutex_lock(&mutex);printf(“% s”,消息);pthread_mutex_unlock(互斥);} void* thread_print(void* arg) {int num = *((int*) arg);char味精[32];sprintf(味精,“线程% d \ n”,num);While (1) {safe_print(msg);睡眠(2);}} int main() {sigset_t set;pthread_t线程[3];int num [3];int n;sigemptyset(组);sigaddset (SIGINT组);pthread_sigmask(SIG_BLOCK, &set, NULL);For (n = 0;n < 3;n++) {nums[n] = n;pthread_create(&(threads[n]), NULL, thread_print, (void *) &(nums[n]));} int sig;sigwait(组、团体);safe_print(“退出! \ n”);pthread_mutex_lock(互斥);For (n = 0;n < 3; ++) pthread_cancel(threads[n]);返回0;}
为了确保SIGINT信号到达时没有线程接收到信号,我们使用pthread_sigmask()为赢博体育线程屏蔽该信号。向该函数传递一个只包含SIGINT信号类型的信号集。启动线程后,主程序用相同的信号集调用sigwait()。sigwait()函数将阻塞,直到掩码集中的信号到达。在接收到该信号后,赢博体育程序将在每个正在运行的线程上调用pthread_cancel()来关闭它们,然后退出。