什么是信号
信号是一种软件中断,用于通知进程系统中发生了某种特定事件。它是操作系统与进程之间,以及进程与进程之间进行异步通信的一种方式。在 Linux 系统中,信号是一种比较简单的进程间通信机制。当一个信号产生时,内核会通过某种方式通知相应的进程,进程接收到信号后,会根据信号的类型以及自身对该信号的处理方式来做出响应。例如,用户按下 Ctrl + C 组合键,系统会向当前前台进程发送一个 SIGINT 信号,通常情况下,进程接收到这个信号后会终止运行。
常见的信号
SIGINT
SIGINT:中断信号,通常由用户按下 Ctrl + C 组合键产生。它用于通知进程需要立即停止当前操作并退出。许多交互式程序,如命令行工具,在接收到 SIGINT 信号时会停止运行,清理资源并退出。
SIGTERM:终止信号。这是一个通用的终止信号,系统或其他进程可以发送给目标进程,请求其正常终止。与 SIGKILL 不同,SIGTERM 允许进程有机会在终止前进行清理操作,例如关闭打开的文件、释放内存等。许多服务器程序在接收到 SIGTERM 信号时,会停止接受新的连接,并逐步关闭当前正在处理的连接,然后安全退出。
SIGKILL
SIGKILL:强制终止信号。一旦进程接收到 SIGKILL 信号,内核会立即终止该进程,进程没有机会进行任何清理操作。这个信号主要用于处理那些陷入死锁或无法响应其他正常终止信号的进程。但由于它不允许进程进行清理,可能会导致资源没有正确释放等问题,所以一般作为最后的手段使用。
SIGALRM
SIGALRM:闹钟信号。进程可以使用 alarm 函数设置一个定时器,当定时器超时后,内核会向该进程发送 SIGALRM 信号。常用于实现定时任务,比如在一个网络请求中设置超时时间,如果在规定时间内没有得到响应,进程接收到 SIGALRM 信号后可以进行相应的错误处理。
SIGCHLD
SIGCHLD:子进程状态改变信号。当一个进程的子进程终止、暂停或继续运行时,内核会向该父进程发送 SIGCHLD 信号。父进程可以通过处理这个信号来回收子进程的资源,避免产生僵尸进程。
进程怎么处理信号
默认处理
默认处理:每个信号都有系统默认的处理方式。例如,对于 SIGINT 信号,默认处理方式是终止进程;对于 SIGQUIT 信号,默认处理方式是终止进程并生成核心转储文件(如果允许的话)。进程在没有对某个信号进行自定义处理时,就会按照系统默认方式来响应信号。
忽略信号
忽略信号:进程可以选择忽略某些信号,即接收到信号后不进行任何操作。但并不是所有信号都可以被忽略,例如 SIGKILL 和 SIGSTOP 信号不能被忽略,这是为了保证系统能够在必要时强制终止或暂停进程。一般情况下,进程可以通过调用 signal 函数来设置对某个信号的处理方式为忽略,例如signal(SIGINT, SIG_IGN); 这行代码会使进程忽略 SIGINT 信号,当用户按下 Ctrl + C 时,进程不会终止。
捕获信号并自定义处理
捕获信号并自定义处理:进程可以定义一个信号处理函数,当接收到特定信号时,内核会调用该函数,进程在函数中可以执行自定义的操作。首先需要定义信号处理函数,
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>int fd;
int* ptr;void sigint_handler(int signum) {printf("Received SIGINT. Cleaning up...\n");if (fd != -1) {close(fd);printf("File test.txt closed.\n");}if (ptr != NULL) {free(ptr);printf("Memory freed.\n");}exit(0);
}int main() {signal(SIGINT, sigint_handler);fd = open("test.txt", O_RDONLY);ptr = (int*)malloc(sizeof(int));while(1) {printf("Main process is working...\n");sleep(1);}return 0;
}
stdio.h:提供标准输入输出库函数,如 printf 用于输出信息。
signal.h:用于信号处理相关的函数和宏定义,这里会用到 signal 函数来设置信号处理程序。
unistd.h:包含了许多 Unix 标准库函数,例如 close 用于关闭文件描述符,sleep 用于使程序暂停执行。
fcntl.h:提供文件控制操作的函数和宏定义,代码中使用 open 函数来打开文件。
stdlib.h:包含通用工具函数,例如 malloc 用于动态分配内存,free 用于释放动态分配的内存,exit 用于终止程序。
fd:用于存储文件描述符,后续会通过 open 函数打开文件并将返回的文件描述符赋值给它。
ptr:是一个指向 int 类型的指针,用于存储动态分配的内存地址。
sigint_handler:这是一个信号处理函数,当进程接收到 SIGINT 信号时会被调用。
signum:是传递给信号处理函数的信号编号,在这个函数中,signum 的值为 SIGINT。
函数内部操作:
打印一条消息,表示接收到了 SIGINT 信号,开始进行清理工作。
检查 fd 是否有效(不等于 -1),如果有效则关闭文件并打印关闭文件的信息。
检查 ptr 是否为空指针,如果不为空则释放动态分配的内存并打印释放内存的信息。
调用 exit(0) 终止程序,返回状态码 0 表示正常退出。
signal(SIGINT, sigint_handler):将 SIGINT 信号的处理函数设置为 sigint_handler,这样当进程接收到 SIGINT 信号(通常是用户按下 Ctrl+C)时,会调用 sigint_handler 函数进行处理。
fd = open(“test.txt”, O_RDONLY);:以只读模式打开 test.txt 文件,并将返回的文件描述符赋值给 fd。
ptr = (int*)malloc(sizeof(int));:动态分配一个 int 类型大小的内存空间,并将其地址赋值给 ptr。
while(1):创建一个无限循环,模拟主程序持续执行任务。
printf(“Main process is working…\n”);:每秒打印一条消息,表示主程序正在运行。
sleep(1);:使程序暂停执行 1 秒,避免消息打印过快。
代码通过设置信号处理函数,确保在程序运行过程中接收到 SIGINT 信号时,能够正确地关闭打开的文件并释放动态分配的内存,然后正常终止程序,避免资源泄漏。
alarm 和pause函数
alarm 函数:alarm 函数用于设置一个定时器,在指定的秒数后,内核会向调用该函数的进程发送 SIGALRM 信号。函数原型为unsigned int alarm(unsigned int seconds);,参数 seconds 表示定时器的超时时间,单位为秒。如果在调用 alarm 函数时,之前已经设置过定时器且尚未超时,那么之前设置的定时器将被新的定时器覆盖,并且返回值是之前定时器剩余的时间。如果 seconds 为 0,则取消之前设置的定时器,并且返回值为之前定时器剩余的时间(如果之前没有设置定时器,则返回 0)。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void sigalrm_handler(int signum) {printf("Time's up!\n");
}int main() {signal(SIGALRM, sigalrm_handler);alarm(5); // 设置5秒后发送SIGALRM信号while(1) {// 主程序执行其他任务sleep(1);printf("Working...\n");}return 0;
}
设置了一个 5 秒的定时器,5 秒后进程接收到 SIGALRM 信号,会调用 sigalrm_handler 函数输出 “Time’s up!”。
pause 函数:pause 函数用于使调用它的进程暂停执行,直到该进程接收到一个信号。函数原型为int pause(void);。如果进程接收到的信号的处理方式是默认处理或忽略,那么 pause 函数返回后,进程会继续执行 pause 函数后面的代码;如果进程接收到的信号的处理方式是捕获信号并执行自定义处理函数,那么在自定义处理函数执行完毕后,pause 函数返回,进程继续执行 pause 函数后面的代码。pause 函数的返回值总是 - 1,并且会设置 errno 为 EINTR,表示被信号中断。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void sigalrm_handler(int signum) {printf("Time's up!\n");
}int main() {signal(SIGALRM, sigalrm_handler);alarm(5); // 设置5秒后发送SIGALRM信号while(1) {// 主程序执行其他任务sleep(1);printf("Working...\n");}return 0;
}
进程执行到 pause 函数时会暂停,当用户按下 Ctrl + C 发送 SIGINT 信号后,进程调用 sigint_handler 函数,然后 pause 函数返回,进程继续执行输出 “After pause”。