信号
1.是什么
2.为什么
OS要求进程具有相应外部信号的能力
3.怎么办
分为 信号的产生 信号的保存 信号的处理
信号的产生
1.kill命令产生+键盘产生信号
1.认识信号
2.处理信号
signal函数会调用里面的handler函数指针,所以它是一个回调函数
调用signal后不会立即执行handler,而只有在未来收到相应信号,才会调用handler。
现在 2号信号(进程自己退出)只会让他打印一句话。
另外,ctrl + c 也是对应的2号信号:
ctrl + z 对应3号信号
忽略信号,第二个参数是一个宏(函数指针类型)
现在会忽略信号
2.系统调用
1.kill
对任意进程发送任意信号
2.raise
自己给自己发任意信号
3.abort
给自己发送指定信号
3.软件条件
1.管道文件关闭信号
13: SIGPIPE 对管道文件这个软件发送信号。
对管道文件写入,读取就是一种软件条件。
2.闹钟
1.一般用法
就是直接到点暂停:
2.自定义法让闹钟一直响应 + 取消闹钟
用signal自定义闹钟的行为,第一次响了以后,换成2秒响一回,并打印上一次闹钟的剩余时间。
而当循环里,count走到10的时候,取消闹钟。
3.闹钟是软件条件的原因
软件:结构体,堆
条件:有过期时间。
4.异常
1.除0错误
发8号信号终止的。
2.野指针
发11号信号终止的。
关于对各种产生方式的解释
1.进程如何接受不同的信号
2.键盘产生信号
来自:硬件中断处理。
最后那里解释为2号信号,发送给进程,进程接收到了信号——>做事情。
3.异常
解释:eax / ebx 的结果,如果发生错误,会存入标志寄存器中,修改寄存器中的标志,CPU再把标志信息返回给OS,OS检测到错误,给进程发送对应的信号。
解释:CR3存页表的起始地址,CR2存出错的地址,当eax处理计算时,发现野指针访问,0号区域(错误)
原因是:
- 0号区域是只读的
- 0号区域根本没参与页表映射,所以页表找不到
所以MMU转化时会出错,把错误返回给CR2,CR2告诉OS,OS就会判断,并给相应进程发送对应的信号。
补充前面的知识
从右往左数的第八位是core dump标志,是干啥的?
可以看到下面的信号,大部分都是Term . Core
其实,Term就是常规的终止进程,而Core则是可以定位到哪行代码退出的,并且退出时,将进程在内存中的核心数据(与调试有关的)转储到磁盘上,方便我们事后调试。
因此:core dump 就是核心转储的意思。
注意:core 在云服务器上默认关闭,需要手动开启。 ulimit -a (开启core)
信号的保存
1.要知道的概念
- 实际执行信号的处理动作称为信号递达(Delivery)
- 信号从产生到递达之间的状态,称为信号未决(Pending)。
- 进程可以选择阻塞 (Block )某个信号。
- 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
2.是什么
三张表:
3.怎么办
1.sigset_t
从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。
因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号
的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有
效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),
这里的“屏蔽”应该理解为阻塞而不是忽略。
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统
实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做
任何解释,比如用printf直接打印sigset_t变量是没有意义的。(不同平台有差异)
2.信号集操作函数
#include
int sigemptyset(sigset_t *set); //清空->0
int sigfillset(sigset_t *set); //填满->1
int sigaddset (sigset_t *set, int signo); //加入指定信号
int sigdelset(sigset_t *set, int signo); //删除指定信号
int sigismember(const sigset_t *set, int signo); //判断信号是否在位图中
上面都是对sigset_t类型位图的操作。
3.系统调用
sigprocmask
#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
sigpending
#include
int sigpending(sigset_t *set);
返回值:若成功则为0,若出错则为-1
读取当前进程的未决信号集(pending位图),通过set参数传出。
4.用一用上面的调用函数
void Printpending(sigset_t &pending)
{
cout << "pending bitmap: ";
for (int sig = 31; sig > 0; sig--)
{
int n = sigismember(&pending, sig);
if (n == 1)
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
int main()
{
sigset_t block, oblock;
sigemptyset(&block);
sigemptyset(&oblock);
sigaddset(&block, 2); // 此时,还没有设置进当前的block位图中
// 屏蔽2号信号,对内核进行操作了
int n = sigprocmask(SIG_SETMASK, &block, &oblock);
assert(n == 0);
while (1)
{
// 获取pending位图
sigset_t pending;
sigemptyset(&pending);
n = sigpending(&pending);
assert(n == 0);
// 看看pending位图
Printpending(pending);
sleep(1);
}
}
看现象:输入ctrl + c 后2号编号的位置一直是1,处于未决状态。
如果我屏蔽所有的信号呢?
看现象:9号,19号信号无法被屏蔽,18号信号可以解除部分信号的屏蔽。
解除对2号信号的屏蔽:
int count = 0;
while (1)
{
// 获取pending位图
sigset_t pending;
sigemptyset(&pending);
n = sigpending(&pending);
assert(n == 0);
// 看看pending位图
Printpending(pending);
count++;
//解除对二号信号的屏蔽 2好信号 1->0
if(count == 10)
{
cout<<"解除对二号信号的屏蔽"<<endl;
n = sigprocmask(SIG_UNBLOCK,&block,&oblock);
assert(n==0);
}
sleep(1);
}
现象:解除成功,因为左边进程停掉了。
问题:先递达还是先清0呢?
void handler(int signo)
{
sigset_t pending;
sigemptyset(&pending);
int n = sigpending(&pending); // 我正在处理2号信号哦!!
assert(n == 0);
// 3. 打印pending位图中的收到的信号
std::cout << "递达中...: ";
Printpending(pending); // 0: 递达之前,pending 2号已经被清0. 1: pending 2号被清0一定是递达之后
std::cout << signo << " 号信号被递达处理..." << std::endl;
}
int main()
{
// 对2号信号进行自定义捕捉 --- 不让进程因为2号信号而终止
signal(2, handler);
答案是:先清0,再递达。