1.消息队列,信号量共享内存的底层原理 (最开始的25分钟)
信号量集
第一个成员*sem.base:起始信号量
可以malloc多个信号量sem_nsems:有多少个信号量
Linux:进程信号
1.引入信号
信号vs信号量 ,没有任何关系
1.信号:闹钟,红绿灯,上课铃,狼烟
2.什么叫信号:中断我们正在做的事情,是一种时间的异步通知机制
信号是一种发给进程的,用来进行异步通知的机制!信号的产生,相对于进程的运行,是异步的。信号是发给进程的
基本结论
基本结论:
1.信号处理,我在信号没有产生的时候,就知道该如何处理了。
2.信号的处理不是立即处理,而是可以等一会在进行处理,可以等到合适的时候,再进行处理。
3.进程早已经内置了对于信号的识别和处理方式。
4.信号源是非常多的->给进程产生信号的,信号源也非常多
2.信号产生
产生信号的方式非常多:
1.键盘产生信号
ctrl + c是给目标进程发送信号的。相当一部分信号的处理动作,就是让信号自己终止。
a.信号都有哪些 --- ctrl+c就是给进程发送信号
kill -l:查看信号列表
信号:宏替代
34~64实时信号
1~31普通信号:可以不立即处理
32 33 没有
b.怎么证明“ctrl + c:给进程发送二号信号”
收到信号,处理信号的三种方式:
1.默认处理动作,ctrl + c
2.自定义信号处理动作
3.忽略处理
更改进程默认 信号处理方式的系统调用函数:
signal:系统调用,更改进程的默认信号处理动作
signum:信号
handler:自定义函数指针指向的方法
signal:代码例子,更改ctrl + c的默认信号处理动作
Term:终止
Core:终止,不过有区别
Ign:忽略
Stop:暂停
Cont:continue继续
前后台进程
ctrl + /:也可以暂停
ctrl + c:给目标进程发送信号
是呢嘛是目标进程:??
前台和后台进程。
./XXX: --- 前台进程,键盘产生的信号,只能发给前台进程,crrl + c可以暂停
./XXX &: ---后台进程,对于ctrl +c 不做处理
命令行shell进程 --- 前台
kill -9 + PID:给目标进程发信号,终止对应的进程,杀掉后台进程
为什么前台进程可以获取键盘输入?
给一个确定的进程,而后台可以有多个,不确定,多以只能是前台
前台进程的本质,就是要从键盘获取数据
./test:
前台进程进行,bash进程进入后台
键盘输入只能打给前台,所以对ls 等命令不响应
./test &:
./test&编程后台进程,bash变成前台进程,输入命名的话,bash作为前台可以对命令有响应
解释
补充命令
jobs:查看所有的后台任务
fg + 任务号:把后台进程提到前台
ctrl + Z : 暂停,提到后台
bg 1:back ground
什么叫做给进程发信号?
什么叫做给进程发信号?
信号产生后不是立即处理,多以要求,进程必须把信号记录下来。记录在哪?
记录在
struct task_struct
{
unsigned int sigs;--->当做一个位图结构 ,32个比特位,比特位的位置表示信号编号,比特位的内容表示是否收到该信号。
“收到2号信号,记录在sigs里面,设置为1”
}
给一个进程发送信号的本质就是想目标进程写信号
----需要进程PID + 信号由于task_struct属于内核的数据结构,所以修改位图的本质就是操作系统自己提供发送信号(修改signs的位图)的系统调用
2.系统调用
系统调用产生信号
系统调用发送信号
1.kill函数
2.stoi
3.argv
全部都捕捉了的代码
当对信号进行自定义捕捉的时候,9号新号,无法被自定义捕捉
可以用就好进行删除
系统调用函数:
raise:自己给自己发信号
abort
abort:给自己发信号,固定的6号信号,用来终止进程。
要求进程必须处理进行终止
3.产生信号的方式调用系统命令kill
4.产生信号的方式 -- 【硬件 】异常
崩掉的两种情况:/0 || 野指针
![]()
除零错误
CPU上运算,有寄存器,有一个状态寄存器-->
EFLAGS:有个比特位,表示CPU当前计算式是否溢出
CPU属于硬件,操作系统属于软硬件资源的管理者,
寄存器保存当前进程的上下文,当前进程出错,操作系统找到当前进程,发现是硬件错误,诶,找打了。然后OS发送8号信号
野指针:
野指针访问0号地址,再也表中不在,
CPU拿着0号地址,CPU拿的都是虚拟地址
CPU内有寄存器CR3,保存页表的起始地址,
MMU硬件单元,拿着虚拟地址,拿着CR3的物理地址,进行页表映射,映射失败了,还想往0号地址里面写,硬件报错,被OS知道是当前进程硬件报错。返回11号信号
一旦崩掉,进程就会收到错误信号
5.产生信号的方式软件条件
alarm
管道只有认读,没有写,bug,错误发型signalpip信号。
alarm:
调用alarm,设定一个闹钟,要求OS给当前进程一个信号,
alarm(0):取消闹钟
返回值:
6.验证闹钟实验
1.验证闹钟
疯狂打印cnt;
设置闹钟,终止进程,发送信号
捕捉信号
14号SIGALRM信号
2.不要IO ,设置成全局,只打印一次。
![]()
相对于5万多,是数量级别的差距。
纯计算和IO有数量级的差距
3.每隔一秒,发送一个sigalarm信号一次性闹钟
一次性闹钟
4.一秒一个闹钟
pause :等待信号,再唤醒
1.每个一秒完成一个任务
信号来,每隔一秒执行一个任务
OS处于死循环,操作系统再别人的催租下
OSpause的时间很短,并且一直在等待信号来执行任务
7. 如何快速理解系统闹钟
每调度一次,到0的时候,切换进程
alarm设定闹钟
OS内会同时存在很多闹钟 -> OS要对闹钟进行管理 ->先描述再组织
闹钟结构体,对闹钟的管理编程对链表的增删查改
tiemstmp,执行一次,timestmp++,timestmp * 记录一次的时间 -》记录时间
计数器:计数器是几,那么就可以算出时间
堆:最小堆 -> 记录最短时间的闹钟,超时,闹钟响了,执行function方法,发送SIGALRM信号给进程
都转化成OS发送信号->修改比特位。
3.信号的保存
1.实际执行信号的处理动作称为信号递达->三种处理方式
2.信号从产生到递达之间的状态,称为信号未决->信号在位图中,还没来得及处理
3.进程可以选择阻塞某个信号
4.解除阻塞,才会递达
5.阻塞和忽略,不同,只要信号被阻塞就不递达,而忽略是递达之后可以选择的一种处理动作
三张表
SIG_DEL:默认
SIG_IGN:忽略
信号递达,未决在内核中的具体表现:
进程中与信号相关的有三张表:
pending表:unsigned int oending ->保存信号的位图
比特位的位置:表示的是第几个信号
比特位的内容:表示是否收到
block表:unsigned int block本质也是一张位图
比特位的位置:表示第几个信号
比特位的内容:表示是否阻塞
handler表:sighandler_t handler[31]->函数指针数组,数组下标:信号编号
singal:的本质就是修改handler表
代码验证
自定义捕捉
忽略 ,不做处理
自定义后回复默认 ,执行一次回复默认
验证信号保存的话题:
Linux提供信号操作
1.一定是围绕着这三张表展开
sigset_t:是一个结构体
sigset_t:
阻塞信号集也叫信号屏蔽字。
信号集操作函数
sigprocmask:
谁调用,就获取,设置,更新谁的block表
调⽤函数 sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
返回值:若成功则为0,若出错则为-1
对应how里面的操作
sigset_t* set 输入型参数
oldset:输出型参数,输出型参数,改后悔了,返回初始的。
返回值:成功 0 ,失败 - 1
sigpending:获取pending信号集
sigpending:获取pending信号集
修改pending位图?不同关心,设置有这几种用法
整合lesson27(代码)
实验初步思路:
首先,给二号进行堵塞(block),获取pending,应该全部都是0,
给二号发送一个信号,再次获取pending,由于堵塞,实际上也就是处于未决状态,会看到有一个0 变成 1 。
第一步:屏蔽二号信号。
1.现将block全部清空,记录老的oblock。
2.sigaddset,添加2号信号,初始化屏蔽信号
3.将当前block信号集,修改成我们设置的blcok.
第二步:获取pending信号集合
第三步:打印
第四步:
结果:
发送什么信号,就会在什么位置出现
全部屏蔽会怎么样?
其他信号,发一个,出现一个
9好信号不可被捕捉,不可被阻塞
2号新号被递达的过程:解除屏蔽
+一个计数器每一次获取打印
计数器 == 10,取消对2号新号的阻塞状态解除2号信号的阻塞,2号信号被递达:
但是刚开始的时候,是看不到的,因为2号信号一旦被递达,然后就执行默认捕捉,直接终止。
方法:修改默认捕捉,为自定义捕捉
解除屏蔽
看不见解除屏蔽后的pending,是因为原因如上
自定义捕捉
-----------------------------------------------------
pending 是在递达前 1 - > 0 ?√
还是在递达后,处理完 在 1-> 0?
4.信号捕捉
常规信号产生多次,只记录一次。
实时信号在递达之前产生多次可以一次放在一个队列里。
Core:
核心,会在当前路径下,形成一个文件,进程异常退出的时候,进程在内存中的核心数据从内存拷贝到磁盘,形成一个文件
“核心转储”---支持DebugTerm:进程直接退出。
云服务器上,core dump功能是被禁止掉的。
1.为什么被禁掉?2.怎么查看?
core file size - > 0 ,关掉ulimit -c 40960,打开
-g选项->调试
#include <iostream>
#include <cstdio>
#include <vector>
#include <functional>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
sleep(2);
printf("hello bit\n");
printf("hello bit\n");
printf("hello bit\n");
printf("hello bit\n");
printf("hello bit\n");
int a = 10;
a /= 0;
printf("hello bit\n");
exit(1);
}
int status = 0;
waitpid(id, &status, 0);
printf("signal: %d, exit code: %d, core dump: %d\n",
(status & 0x7F), (status >> 8) & 0xFF, (status >> 7) & 0x1);
}
ulimit -a:查看
ulimit -c size:开多大的core
size 置为0 ,关掉core.
信号值和退出码是通过
status
变量的位操作获取的:
(status & 0x7F)
:获取低7位,表示信号值。
(status >> 8) & 0xFF
:获取高8位,表示退出码。
(status >> 7) & 0x1
:第7位表示是否产生核心转储。