🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12625432.html
目录
前言
信号和信号量
二者之间没有任何关系。
信号
信号的处理
信号处理有三种情况:
- 默认动作
- 忽略动作
- 自定义处理--信号的捕捉
信号捕捉
signal
信号的产生
信号的产生方式:
- 通过kill命令,向指定进程发送指定信号
- 键盘可以产生信号。ctrl+c(SIGINT)、ctrl+\(SIGQUIT)
- 系统调用
- 软件条件
- 异常
系统调用
kill
raise
abort
由软件条件产生信号
SIGPIPE是一种由软件条件产生的信号。下面介绍alarm函数和SIGALRM信号。
alarm
我们修改代码成如下:
硬件异常产生信号
Core、Term
阻塞信号
信号其他相关常见概念
- 实际执行信号的处理动作称为信号递达 (Delivery)
- 信号从产生到递达之间的状态,称为信号未决(Pending)。
- 进程可以选择阻塞 (Block )某个信号。阻塞和有没有未决,二者没有关系
- 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
- 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
在内核中的表示
信号在内核中的表示示意图
sigset_t
信号集操作函数
sigset_t类型内部如何存储这些bit依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量
sigprocmask
sigpending
完整代码
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>
void PrintPending(sigset_t &pending)
{
std::cout << "curr process[" << getpid() << "]pending: ";
for (int signo = 31; signo >= 1; signo--)
{
if (sigismember(&pending, signo))
{
std::cout << 1;
}
else
{
std::cout << 0;
}
}
std::cout << "\n";
}
void handler(int signo)
{
std::cout << signo << " 号信号被递达!!!" << std::endl;
std::cout << "-------------------------------" << std::endl;
sigset_t pending;
sigpending(&pending);
PrintPending(pending);
std::cout << "-------------------------------" << std::endl;
}
int main()
{
// 0. 捕捉2号信号
signal(2, handler); // 自定义捕捉
signal(2, SIG_IGN); // 忽略一个信号
signal(2, SIG_DFL); // 信号的默认处理动作
// 1. 屏蔽2号信号
sigset_t block_set, old_set;
sigemptyset(&block_set);
sigemptyset(&old_set);
sigaddset(&block_set, SIGINT);
// 1.1 设置进入进程的Block表中
sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!
int cnt = 15;
while (true)
{
// 2. 获取当前进程的pending信号集
sigset_t pending;
sigpending(&pending);
// 3. 打印pending信号集
PrintPending(pending);
cnt--;
// 4. 解除对2号信号的屏蔽
if (cnt == 0)
{
std::cout << "解除对2号信号的屏蔽!!!" << std::endl;
sigprocmask(SIG_SETMASK, &old_set, &block_set);
}
sleep(1);
}
}
捕捉信号
内核如何实现信号的捕捉
再谈地址空间
sigaction
#include<iostream>
#include<signal.h>
#include<unistd.h>
void Print(sigset_t &pending)
{
for(int sig = 31; sig > 0; sig--)
{
if(sigismember(&pending, sig))
{
std::cout << 1;
}
else
{
std::cout << 0;
}
}
std::cout << std::endl;
}
void handler(int signum)
{
std::cout << "get a sig: " << signum << std::endl;
while(true)
{
sigset_t pending;
sigpending(&pending);
Print(pending);
sleep(1);
// sleep(30);
// break;
}
// exit(1);
}
int main()
{
struct sigaction act, oact;
act.sa_handler = handler;
sigemptyset(&act.sa_mask); // 如果你想在处理2号时(OS对2号自动屏蔽),同时对其他信号也进行屏蔽
sigaddset(&act.sa_mask,3);
act.sa_flags = 0;
sigaction(2, &act, &oact);
while(true)
{
std::cout << "I am a process, pid: " << getpid() << std::endl;
sleep(1);
}
return 0;
}
可重入函数
volatile
SIGCHLD信号
子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。
wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进 程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一下,程序实现复杂。
要想不产生僵尸进程还有另外一种办法:父进程调用signal将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。