Linux——进程信号1

信号和信号量是俩个东西,俩者无关系。

信号

信号本质是一种通知机制,用户or操作系统通过发送一定的信号,通知进程,某些事件已经发送,让进程进行后续处理。

结合进程,信号结论:

  1. 进程要处理信号,进程必须具备识别信号的能力。

  1. 凭什么进程能够识别信号呢?程序员让进程具备了识别信号的能力

  1. 信号是随机产生的,进程可能正在忙自己的事情,所以,信号的后续处理可能不是立即处理的

  1. 信号会被临时记录下来,方便后续处理

  1. 什么时候处理呢?合适的时候。

  1. 一般而言信号的产生,相对于进程而言是异步的(信号和进程各干各的,互不影响)。

信号如何产生

我们写一个死循环程序,按ctrl+c可直接退出

这是因为ctrl+c:本质是向进程发送二号信号,我们输入kill -2 PID值,效果跟ctrl+c一样

下图都是信号,左侧是信号编号,右侧是宏值

处理信号的方式

  1. 默认(进程自带的,程序员写好的逻辑)

  1. 忽略(也是信号处理的一种方式)

  1. 自定义动作(捕捉信号)

常见信号(kill -l可查看所有信号)

【1,31】是普通信号,后面的是实时信号,实时信号对信号处理要求比较高

也可输入man 7 signal,可查看信号的详细描述

如何理解组合键变成信号呢?

键盘的工作方式是通过中断方式进行的,操作系统可以识别组合键

如何理解信号被进程保存呢?

  1. 什么信号?

  1. 是否产生?

进程必须要有保存信号的相关数据结构(位图,unsigned int,用比特位的位置表示信号),PCB内部保存了信号位图字段

信号发送本质:信号位图是在task_struct内核数据结构中,只有操作系统去发送信号,即信号发送的本质:OS向目标进程写信号,OS直接修改PCB中指定的位图结构,完成“发送”信号的过程。

OS解释组合键->查找进程列表->前台运行的进程->OS写入对应的信号到进程内部的位图结构中

产生信号的方式

1. 通过终端按键产生信号

signal函数,作用:对特定的信号进行捕捉,这种方式属于自定义捕捉

第一个参数是要捕捉的信号,可传入[1,31]对应的数字,也可传入对应的字母如SIGINT

typedef void (*sighandler_t)(int) 是一个函数指针,因此第二个参数时一个函数指针

signal通过回调的方式,修改对应的信号捕捉方法。

返回值也是个函数指针,一般不需要关心返回值,当我们捕捉了一个新的方法时,该函数会返回老的方法

catchSig的参数含义是:将信号对应的编号以参数形式传到这里

运行程序后,我们ctrl+c,此时会捕捉到信号,但是程序还在运行,ctrl+c未能让进程终止

这是因为以前2号信号处理动作默认是终止进程,而现在我们把二号信号的处理动作给改了(改成了打印一句话),因此对应的进程就不再退出。

signal函数仅仅是修改进程对特定信号的后续处理动作,不是直接调用对应的处理动作。即:如果后续没有任何SIGINT信号产生,catchSig永远也不会被调用

就好比新颁布了一项法律,若没人触犯该法,该条法律则永远不会被使用。

信号也是如此,我们只是提前注册了一个捕捉到信号的方法,若后续未捕捉到信号,则不会使用该方法。

特定信号的处理动作一般只有一个。

我们此时发送别的信号,可使程序退出如ctrl+\,这其实是3号信号

我们对2号和3号信号使用同一个捕捉方法

再次运行

用kill发送信号

我们发送8号信号,可终止程序,8号信号是浮点数异常

man 7 signal,我们可以看到3号信号后面是Core

Core核心转储

在进程等待部分,当进程退出时,我们要获取它的退出结果,次低8位代表子进程退出时的退出码,最低的7个比特位保存退出时的退出信号,而core dump代表进程退出时是否具备core dump,core dump是核心转储标志,代表是否发生了核心转储。

一般而言,云服务器(生产环境)的核心转储功能是被关闭的

ulimit -a查看当前服务器相关资源配置

打开了当前的core file选项由0变为10240

此时程序跑起来,kill -8 ,我们发现此时多了一个core dump和一个临时文件

我们看到这个文件在所有文件中大小算是比较大的了

核心转储:当进程出现某种异常的时候,是否由OS将当前进程在内存种的相关核心数据,转存到磁盘中。保存到了如图所示的core文件。核心转储主要是为了调试。

此时我们不捕捉这俩个信号

运行程序,分别用2号和3号信号进程退出,3号信号退出时出现了core dump

此时又有了核心转储

我们可以看到里面还有Ign(忽略),cont(继续)

此时运行程序,程序直接终止

产生了core文件

GDB调试

我们先输入gdb mysignal,之后输入core -file core.26559,此时直接定位到出错的地方而且有出错的原因,我们就不需要像以前一样一行一行调试

验证进程等待中的core dump标记位

这个标记为代表是否发生核心转储

我们可以看到发生了核心转储

多了一个core文件

我们sleep(100),之后运行程序 使用kill -2

此时core dump标记位为0

我们关闭核心转储,core dump标记位为0

为什么生产环境一般都是要关闭core dump?

core文件产生过多可能会把磁盘写满,造成无法预估的后果。

调用系统接口发送信号

kill

man 2 kill

向指定的进程发送指定的信号。

使用kill写一个程序

我们此时sleep(10000),然后运行我们的程序,进程被杀掉

raise

如果kill是向指定的进程发送指定的信号,kill就是向自己发送指定的信号

自己给自己发送8号信号

abort

abort让进程直接终止, 给自己发送abort信号(自己终止自己)。

输入ulimit -c 10240打开core,此时再运行程序

abort通常用来终止进程。

用户调用系统接口->执行OS对应的系统调用代码->OS提取参数或者设置特定的数值->OS向目标进程写信号->修改对应进程的信号标记位->进程后续会处理信号->执行对应的动作

由软件条件产生信号

管道,读端不光不读,而且关闭了,写端一直在写,会发生什么?

此时意味着写没有意义,OS会自动终止对应的写端进程,通过发送信号的方式,发送SIGPIPE(13号信号)。

SIGALARM是14号信号

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动
作是终止当前进程。

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后

响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就

是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

上面这段程序验证1s之内,能运算多少次count++;

为什么只计算到6w多呢?因为我们进行了cout,而且因为我们的Xshell在本地,云服务器在远端,cout还是通过网络发送的,因此会比较慢。

若单纯想计算算力,可这样做

我们每发送14号信号count值就会被发出来

我们设定了一个闹钟,这个闹钟一旦触发,就自动移除了(设置一次就只会触发一次)

我们在每次捕捉到信号时,再设置一个闹钟

每隔一秒打印一条消息

我们上面实现了类似的定时器功能

每隔一秒打印一下

#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>

using namespace std;

 typedef function<void ()> func;
 vector<func> callbacks;

 uint64_t count = 0;

 void showCount()
 {
     // cout << "进程捕捉到了一个信号,正在处理中: " << signum << " Pid: " << getpid() << endl;
     cout << "final count : " << count << endl;
 }
 void showLog()
 {
     cout << "这个是日志功能" << endl;
 }
 void logUser()
 {
     if(fork() == 0)
     {
         execl("/usr/bin/who", "who", nullptr);
         exit(1);
     }
     wait(nullptr);
 }
 void flushdata()
 {

 }

 // 定时器功能
 // sig: 
 void catchSig(int signum)
 {
     for(auto &f : callbacks)
     {
         f();
     }
     alarm(1);
 }
 static void Usage(string proc)
 {
     cout << "Usage:\r\n\t" << proc << " signumber processid" << endl;
 }

void handler(int signum)
{
    sleep(1);
    cout << "获得了一个信号: " << signum << endl;
     exit(1);
}
int main(int argc, char* argv[])
{
    signal(SIGFPE, handler);
    alarm(1);
    callbacks.push_back(showCount);
    callbacks.push_back(showLog);
    callbacks.push_back(logUser);
    while (true) count++;
    return 0;
}

我们可以发现IO的效率非常低,尤其是带上网络。

如何理解软件条件产生信号?

OS先识别到某种软件条件触发或者不满足,然后OS构建信号,发送给指定的进程

硬件异常产生信号

捕捉了一个信号,但为什么这里开始循环了?

如何理解除0?

  1. 进行计算的是CPU,这个硬件

  1. CPU内部是有寄存器的,其中有一个是状态寄存器(位图),里面保存每次计算的计算状态,状态寄存器有对应的标志位,有溢出标记为,OS会自动进行计算完毕之后的检测。如果溢出标记位是1,OS识别到有溢出问题,立即找到当前谁在运行提取PID,OS完成信号发送的过程,进程会在合适的时候,进行处理。

  1. 除0错误本质是硬件异常。

  1. 一旦出现硬件异常,进程不一定退出,一般默认是退出,但是即便不退出,我们也做不了什么

  1. 为什么会死循环?寄存器中的异常一直没有被解决,OS不断地重复发送信号

我们写一个段错误

段错误本质也是收到信号

此时又陷入了死循环。

如何理解野指针和越界问题?

  1. 都必须通过地址找到目标位置

  1. 语言上面的地址全部都是虚拟地址,

  1. 将虚拟地址转为物理地址

  1. 通过页表+MMU(Memory Manager Unit是硬件)找到物理内存

  1. 野指针,越界-》非法地址,MMU转换的时候一定会报错。错误会被OS转成信号发送给进程

所有的信号,都有它的来源。但最终全部是被OS识别,解释并发送的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

头发没有代码多

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值