当前位置: 首页 > news >正文

Linux操作系统学习之---进程信号的产生和保存

一个辨析 :

[!信号和信号量 , 一字之差 , 有啥联系吗???]

  • 就如同老婆和老婆饼 , 没有半毛钱关系!!!
  • 信号量是在进程间通信中实现互斥同步机制的关键之一.
  • 信号则是操作系统用来终止程序的一个手段.

一.初步理解信号量:

信号的处理:

1.默认行为:

系统内定义有许多信号 , 其中大多数信号的默认行为都是终止进程
![[信号列表.png]]

2.自定义行为:

  • sighandler_t signal(int signum, sighandler_t handler)
  • 传入自定义的函数指针 , 来修改信号signum的默认行为
  • 这种操作也叫做``信号捕捉
自定义捕捉2号信号:

下面对信号2 SIGINT进行捕捉 , 让他原有的停止进程行为变成打印信息

	#include<iostream>#include<unistd.h>#include<signal.h>using namespace std;typedef void (*handler)(int signal);void func(int signal){cout << "收到信号:" << signal << endl;}int main(){sighandler_t s = signal(2,func);while(true){cout << "程序运行中" << endl;sleep(1);}return 0;}

运行起来会发现 , 按下Ctrl+c无法终止进程了,而是执行了我们自己func函数里的逻辑:
在这里插入图片描述

[!无法被捕捉的信号]-

  • 如果让能让进程终止的信号全部被自定义捕捉 , 岂不是恶意软件可以放飞自我啦?
  • 但道高一尺魔高一丈 , 操作系统在设计时就规定了 9号 SIGKILL19号 SIGSTOP无法被捕获
    #Linux/进程信号/两个东方不败的信号
无法捕捉的信号:
	//自定义函数void handler(int single){cout << "信号: " << single << "被自定义捕捉啦!!!" << endl;}//验证逻辑for (int i = 1; i <= 31; i++){sighandler_t ret = signal(i, handler);if (ret == SIG_ERR) //如果捕获失败,判断成立cout << "信号 : " << i << "捕捉失败!!" << endl;}while (true){cout << "哈哈哈哈" << endl;sleep(1);}

![[信号捕捉的漏网之鱼.png]]

3.忽略

  • 这个就比较简单 , 一般是进程通过函数sigignore将处理方式设置为SIG_IGN

二.信号产生:

[!信号的产生方式]

  • 信号产生方式有五中 : 终端按键 / 系统命令 / 函数调用 / 软件条件 / 硬件异常

1. 终端按键(键盘):

  • 按下键盘上的Ctrl + c , 会向前台进程发送2号信号SIGINT, 终止前台进程.
  • 按下键盘上的Ctrl + z , 会向前台进程发送20号信号SIGTSTP , 暂停前台进程.
  • 按下键盘上的Ctrl + \ , 会向前台进程发送3号进程SIGQUIT , 退出当前进程.

[!前台进程的本质]-
键盘发送的信号只能由前台进程接受 , 所以要明晰什么是前台进程!!!

  1. 前台进程的本质是能够接受键盘输入!!!同一时间只有一个前台进程 . 登陆终端时,shell是前台进程 , 当运行自己的可执行程序 , shell退居二线 .
  2. 后台进程是不能接受键盘输入的!!! 同一时间可以有很多个后台进程 .
    键盘是独占资源 , 一次只能有一个进程来接收他的信号 . 系统规定,这个接受者只能是前台进程.

2.系统命令:

常用的就是kill命令 , 使用kill -l可以看到所有信号对应的编号.

  • kill -9 [pid] : 发送9信号SIGKILL杀死进程
  • kill -2 [pid] : 发送2信号SIGINT进程 , 等价于键盘输入Ctrl+c.

3.函数调用:

kill()

  • int kill(pid_t pid, int sig)
  • 调用时向任意进程发送指定信号sig
  • 命令行里的kill指令的底层实现就涉及这个函数.

raise()

  • int raise(int sig)
  • 相比kill函数适用范围更窄 , 只能向当前进程发送信号

abort()

  • [[noreturn]] void abort(void)
  • 执行时直接退出当前进程,等价于raise(3),发送信号3 SIGQUIT
  • 由于执行后直接退出进程 , 所以不存在返回值.

4.软件条件

匿名管道通信

使用匿名管道实现进程池时 , 如果想让多个读端关闭(子进程) , 只需关闭一个父进程的写端

因为操作系统从来不做浪费资源的事 ! 当父进程关闭写端 , 子进程作为读端再也不会受到消息 , 因此留着他们也是浪费资源 , 会被操作系统叫停.

进程属于软件 , 匿名管道属于软件(由文件系统实现,不和磁盘交互) , 操作系统也是软件 , 因此这是软件条件—管道读端关闭导致的信号!!!

alarm函数

  • 函数声明
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
//返回值为剩下的时间
//参数表示倒计时
  • 函数原理
  • 计算机从开始运行就会存在一个不断增加的时间戳 , 可以理解为一个一直++的计数器 , 通过这个计数器可以算出当前的时间信息.
  • 假设当前时间戳为2000s , 那调用一次alarm(10) , 则会在时间戳增加到2010s时向当前进程发送信号.
  • 实践一 : 使用alarm函数

alarm函数发送的信号是14号SIGALRM , 默认行为是终止当前进程 , 这里将其自定义捕获

void sigcb(int single)
{cout << "信号 : " << single << "被捕捉啦\n"; alarm(1); //调用之后重置一秒的闹钟
}
int main()
{signal(SIGALRM,sigcb);alarm(1); //一秒后发送信号while(true){pause(); //让程序暂停,等待信号}
}

运行程序发现一秒钟后就开始不停的接受信号了

在这里插入图片描述


  • 实践二 : 结合alarm和pause,感受IO效率之慢:
int main()
{//感受IO效率之慢alarm(1); //alarm1秒后发送信号,默认行为终止程序int count = 0;while(true){cout << "count = " << count++ << endl;//看看一秒内可以执行多少词cout}
}

在这里插入图片描述


[!测试思路]

  • 全局变量count用来计数
  • 定义一个一秒钟的闹钟alarm(1)
  • while循环内的count在一秒内使劲++
  • 当一秒结束 , alarm发送信号 , 执行自定义捕捉函数,打印count最终结果
int count = 0; //全局变量,用作计数
void sigcb(int single)
{cout << "信号 : " << single << "被捕捉啦\n"; cout << count << endl;exit(1);
}
int main()
{//感受没有IO的飞快signal(SIGALRM,sigcb);alarm(1);while(true){count++;}
}

运行后发现一秒钟count++执行了五亿多次 , 相比于刚才一秒里七万多次cout打印简直是降维打击!!!
在这里插入图片描述

5.硬件异常

下面是两种最常见的硬件异常 , 一个涉及寄存器,另一个涉及MMU.

除0异常 :

当程序中出现 x/0 这样的表达式时 , 运行起来会出现异常 , 其实和硬件 寄存器 有关

  • 算术运算由CPU执行 , 当CPU执行 /0 操作时 , 会将错误信息以将EFLAGSS寄存器里的某个比特位置为 1 , 触发硬件异常
  • 随后由内核接管 , 检查到EFLAGS寄存器里的内容后向进程发送8号信号SIGFPE
  • 最后让进程按情况处理信号.

非法指针操作异常:

当程序试图修改cosnt char* str 字符串时(写入只读也行为.rodata) , 也会出现异常 , 此时和硬件 MMU (负责查询页表,完成虚拟到物理地址的转换)有关

  • 程序通过指针进行写入操作(如*str = ‘1’) , CPU将虚拟地址传输给MMU.
  • MMU执行虚拟地址到对应物理地址的转换(页表遍历), 发现目标地址映射到制度数据段
  • MMU检测到非法写入 , 出发异常 .
  • 随后内核接管 , 向进程发送11号进程SIGSEGV , 即段错误.
  • 最后进程按情况处理信号

core dump:

想象一下 , 一个已经上线运行的项目出现了错误 , 能够立即关停项目让程序员修bug吗?
答案是不能 , 所以就需要将这些错误以文件的形式存储下来 . 方便事后调试.

core dump就是用来标识是否有错误日志文件产生的.

**值得注意的是 , 一般在云服务器上 , 这个功能默认是关闭的 . **

  • 原因: 如果在云服务器运行时有一个带有bug的程序不停的出错/重启/崩溃 , 产生的错误文件会很容易将磁盘占满,影响使用.
  • 查看方式 : 使用 ulimit -a 可以查看是否开启.
  • 临时开启 : 使用 ulimit -c <size> 可以设置错误文件最大的size.
an@mycloud:...$ ulimit -a #查看的命令
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) 0 #最开始是0
#........
an@mycloud:~/code-in-linux/c++_linux$ ulimit -c 4096 #设置错误文件大小
an@mycloud:~/code-in-linux/c++_linux$ ulimit -a      #再次查看
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) 4096
#.........

当核心转储功能开启后 , 就可以测试了:
在这里插入图片描述

[!二进制与状态解析速记]-

一、十六进制 ↔ 二进制

  • 1位十六进制 = 4位二进制
    0xF11110xA10100x70111
  • 字母转换技巧
    B=118+2+11011
    C=128+41100

二、status位运算解析

// 结构:高8位exit_code | 1位core_dump | 低7位signal
exit_code  = (status >> 8) & 0xFF;  // 右移后取1字节
core_dump  = (status >> 7) & 0x1;   // 挪第7位到末尾
signal_num = status & 0x7F;         // 屏蔽高9位

宏等价

  • WEXITSTATUS(status) → 高8位
  • WCOREDUMP(status) → bit7
  • WTERMSIG(status) → 低7位

三、核心心法**

  • &操作:用1保留目标位,0屏蔽干扰(如&0x7F取低7位)
  • 人生映射:专注目标时,杂念自然被"屏蔽"
    #linux/进程信号/status全解析(进制转换/位运算)

三.信号保存

[!信号保存的关键在于三张表]

  1. 信号掩码表(Signal Mask)
  2. 未决信号集(Pending Signal Table)
  3. 信号处理函数表(Single Handler Table)
  • 而信号发送后又有四种情况
  1. 信号递达 : 进程接收到信号 , 进行默认行为or自定义行为or忽略
  2. 信号未决 : 描述的是信号还没有递达的状态.
  3. 信号阻塞 : 进程的信号掩码表里屏蔽了对应的信号,无法抵达
  4. 信号被忽略 : 进程收到信号,但没有任何行为执行
  • 可以这样粗略的理解三张表
  1. 信号掩码表就类似于文件权限里的umask , 一个位图 , 用于屏蔽指定的信号
  2. 未决信号集也类似于个位图 , 接收信号后,递达信号前就会将对应比特位置1
  3. 信号处理函数表可以理解为一个函数指针数组 , 对应每个信号的处理方式.

[!信号阻塞和信号忽略]-

  • 假如信号递达是人拿到了快递
  • 信号阻塞就是快递到了快递站 , 人因为各种原因没去拿.
  • 信号忽略就是快递到了快递站 , 但人有条件去拿却不拿,让快递在快递站吃灰
    阻塞是递达之前,忽略是递达的一种情况

1.未决信号集:

  • 暂时不考虑实时信号 , 就说那31个普通信号.

可以认为未决信号集类似一个有32个比特位的整形值 , 除了第一个比特位外(普通信号只有31个) , 每一个比特位都对应一个信号 . 比特位为0表示没有对应信号产生,为1则有.

//举例子...
00000000000000000000000000000000 //初始全零00000000000000000000000000000010 //一号信号到来 , 将第二个比特位置100000000000000000000000000000100 //二号信号到来 , 将第三个比特位置1

2.信号处理函数表:

  • 一个函数指针数组 , 下标对应信号编号.
  • 当信号递达时 , 直接访问这个数组 , 调用提前设定好的函数
    进程在信号递达之前就知道自己该干嘛 , 这就是信号处理函数表的意义

3.位信号掩码表:

  • 和未决信号集是一样的结构 , 只是比特位表达的含义不同 . 为0表示不对应信号递达 , 为1则相反
//举例子...
00000000000000000000000000000100 //(未决信号集)操作系统收到信号2
00000000000000000000000000000000 //(位信号掩码表)对应比特位为0,信号2可以递达00000000000000000000000000000100 //(未决信号集)操作系统收到信号2
00000000000000000000000000000100 //(位信号掩码表)对应比特位为1,直到这个比特位重新                                    置为0后信号2才能递达.

4. 三张表的联合工作:

  • 未决信号集决定信号是否产生.
  • 位信号掩码表决定这个信号能否递达.
  • 信号处理函数表决定递达方式.

5.一个小实验:从阻塞到递达

  • 通过sigaddset()将sigset_t类型的位图第二位设置为1 (让二号信号阻塞).
  • 使用sigprocmask()将位图从用户态更新到内核.
  • 使用alarm函数设置一个闹钟,时间到后执行自定义捕捉来接触二号信号的阻塞.
  • 运行时不停的通过sigpending()获取未决表并打印

预期运行效果 : 起初二号信号被阻塞,因此按下键盘上的Ctrl+c发送2号信号会阻塞,被记录在未决表里并显示 . 五秒后alarm结束,接触二号信号的阻塞 , 执行二号信号,终止进程.

void handler_alrm(int signal)
{cout << "捕捉信号SIGALRM , 接触SIGINT信号的阻塞" << endl;sigset_t set;sigdelset(&set,SIGINT);sigprocmask(SIG_SETMASK,&set,nullptr);
}void print_signal(sigset_t sig)
{for(int i = 31 ; i>= 1 ; i--){if(sigismember(&sig,i)){cout << 1;}elsecout << 0;}cout << endl;
}
int main()
{sigset_t set,old_set;sigaddset(&set,SIGINT);sigprocmask(SIG_SETMASK,&set,&old_set);alarm(5);signal(SIGALRM,handler_alrm);while(true){sigpending(&set);print_signal(set);sleep(1);}return 0;
}

6.小实验的运行结果:

  • 起初2号信号在位信号掩码表里 , 因此发送的信号会出现在未决信号集里(可以看到从右往左第二位比特位变为1) .
  • 当五秒结束,alarm函数触发信号 , 执行我们自己写的捕捉函数将2号信号解除屏蔽.
  • 然后2号成功抵达 , 执行默认动作 , 终止进程.
  • 虽然后面没有输出pending表 , 但是操作系统向进程递达信号之前会将pending表对应的比特位置0.
    在这里插入图片描述

四.Q&A

Q: 如果重复收到同一个信号 , 操作系系统会怎么做?

A :

  • 对于普通信号 , 操作系统在一个信号抵达前 , 会忽略随之而来的相同信号.
  • 对于实时信号 , 操作系统会将数量记录下来.

Q: 信号的默认行为里 , 有的是Term类型 , 有的是 Core .区别在哪?

A:

  • term类型就是正常递达信号的类型.
  • Core类型就是会在抵达信号的同时 , 产生核心转储文件的类型.
http://www.dtcms.com/a/487453.html

相关文章:

  • React Hooks 核心原理与开发技巧
  • 海南手机网站建设公司哪家好深圳营销型网站seo
  • 丽江市建设局网站深圳市城乡和建设局网站首页
  • 南昌做公司网站哪家好生物做实验的网站
  • RHCSA 基础练习
  • Learn C the Hardway学习笔记和拓展知识(一)
  • 算法10.0
  • 凡科网做的网站能直接用吗网站换服务器对排名有影响吗
  • 多层超表面革新 | 简化传统光学系统
  • 辽阳专业建设网站公司电话山东住房城乡建设厅网站首页
  • 数据结构2:线性表1-线性表类型及其特点
  • 网站外包如何报价做那种事的网站
  • 张家港做网站的推荐驻马店app和网站开发公司
  • 目标检测(一)
  • 石家庄免费做网站专做药材的网站有哪些
  • 基本功 | 一文讲清多线程和多线程同步
  • 360门户网站怎样做广州百度seo代理
  • C++蓝桥杯之函数与递归
  • Oracle AWR报告分析:诊断RAC Global cache log flush性能故障
  • python - 第四天
  • 领取流量网站药剂学教学网站的建设
  • 端端网站开发网络广告网站怎么做
  • threejs(五)纹理贴图、顶点UV坐标
  • debug - MDK - arm-none-eabi - 将MDK工程编译过程的所有命令行参数找出来
  • 网站怎么维护百度会收录双域名的网站么
  • Oracle数据库基本命令的8个模块
  • Vue3中的计算属性和监视属性【5】
  • Docker部署WordPress及相关配置
  • 大自然的网站设计营销型企业网站源码
  • 网站如何做线上支付功能免费刷推广链接的网站