信号的传递与处理
当一个信号被发送时,他将会被发送到目标进程的内核层中,在内核层中有未决信号集和屏蔽集。未决信号集中的信号是指已经发送到进程但还未被处理的信号,而“未决”这个词则表示该信号正在等待处理,这是对应信号设置为1时的情况。当接收到信号但未决信号集对应的位是0,那么系统就会将该位置1,等到信号开始处理再变回0。
当未决信号集是1时,如果是标准信号,不会进行排队,后面的信号将会被丢弃。如果是实时信号,将会按照顺序存入一个队列,当前面的那个信号处理完了,队列首部的信号将会被执行。依次直到全部执行完,这也是为什么被称为可靠信号的原因。
因为标准信号的行为一般都是终止之类的,所以也不需要重复执行好几个,而实时信号一般是自定义的行为,根据需要可能得执行多次。
一种特殊情况,当第一个标准信号走过屏蔽集后,变为递达态开始处理,此时对应的未决信号集变为0,第二个标准信号这个时候过来,这个时候系统会将屏屏蔽集临时置1,暂时屏蔽第二个信号,使其进入到阻塞状态,等到第一个信号处理完毕后,在开始处理第二个信号。也产生了排队的假象。
信号的捕捉函数的执行
首先用户层的代码中执行发送信号的操作,那么信号将会从用户发送到内核层,在这之中发生了层级转换。
信号也是中断,那么内核层可能就会出现中断,内核层就需要处理中断或者处理异常。
当系统的事情都处理完了,先检测是否有未递送的信号。
检测完毕后,开始处理信号。
为了不那么频繁切换层级,系统保留高CPU权限,执行用户层捕捉函数(此时仍是内核层)
处理完捕捉函数后,返回到函数调用位置。
然后发生层级转换,回到用户空间。
继续执行信号后面的程序。
信号处理僵尸进程
当子进程结束的时候,将会告诉操作系统自己结束了,然后操作系统给父进程发送一个SIGCHLD信号,代表子进程已经结束(默认动作是忽略),我们可以将这个信号的捕捉函数设置为回收子进程的函数,这样就可以在子进程结束的时候及时回收了。
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
void signalHandler(int signum) {
if (signum == SIGCHLD) {
int status;
pid_t pid;
// 等待子进程退出,并获取退出状态
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status)) {
std::cout << "Child process " << pid
<< " exited with status: " << WEXITSTATUS(status) << std::endl;
} else if (WIFSIGNALED(status)) {
std::cout << "Child process " << pid
<< " terminated by signal: " << WTERMSIG(status) << std::endl;
}
}
}
}
int main() {
pid_t childPID;
// 设置SIGCHLD信号处理函数
signal(SIGCHLD, signalHandler);
// 创建子进程
childPID = fork();
if (childPID >= 0) { // fork成功
if (childPID == 0) { // 子进程
std::cout << "Child process" << std::endl;
sleep(2);
std::cout << "Child process exiting" << std::endl;
return 0;
} else { // 父进程
std::cout << "Parent process" << std::endl;
while (true) {
sleep(1);
}
}
} else { // fork失败
std::cerr << "Fork failed" << std::endl;
return 1;
}
return 0;
}