目录
- 1. 认识信号
- 2. 信号的产生方式
- 2.1 通过键盘给终端发送信号
- 2.1.1 信号都有哪些
- 2.1.2 自定义信号捕捉singal()函数(证明ctrl+c是2号信号编号)
- 2.1.3 前台进程(目标进程)&后台进程
- 2.1.补:前后台相关命令
- 2.1.4 给进程发送信号
- 2.2 系统调用发送信号
- 2.2.1 kill系统调用(给指定进程发送信号)
- 2.2.2 raise系统调用(给自己发送信号)
- 2.2.3 abort函数(给自己发送6号信号)
- 2.3 使用命令行kill向进程发信号
- 2.4 硬件异常产生信号
- 补:core VS term
- 2.5 软件条件产生信号
- 2.5.1 alarm定时器系统调用
- 2.5.2 基本alarm验证-体会IO效率问题
- 2.5.3 设置重复闹钟+pause函数
- 2.5.4 如何理解软件条件
1. 认识信号
- 信号和信号量没有任何关系
信号举例:闹钟,红绿灯,上课铃声,狼烟,电话铃声,肚子叫,敲门声,脸色不好…
信号概念:中断我们正在做的事情,是一种事件的异步通知机制。
对计算机系统来讲:信号是一种给进程发送的,用来进行事件异步通知的机制
信号举例:
你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”
当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取”。
在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”
当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:1. 执行默认动作(幸福的打开快递,使用商品)2. 执行自定义动作(快递是零食,你要送给你你的女朋友)3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)
- 快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话
关于信号的基本结论:
- 如何产生:信号源:给进程产生信号的事件。
- 如何识别:识别信号是内置的,进程识别信号,是内核程序员写的内置特性。
- 处理逻辑(事前已知):信号产生之后,你知道怎么处理吗?知道。如果信号没有产生,你知道怎么处理信号吗?知道。所以,信号的处理方法,在信号产生之前,已经准备好了。
- 处理时机:处理信号,立即处理吗?我可能正在做优先级更高的事情,不会立即处理?什么时候?合适的时候
- 处理流程:信号到来 | 信号保存 | 信号处理
- 处理方式:怎么进行信号处理啊?a.默认 b.忽略 c.自定义, 后续都叫做信号捕捉。
相当一部分进程的默认信号处理方式就是:进程终止
2. 信号的产生方式
2.1 通过键盘给终端发送信号
2.1.1 信号都有哪些
- ctrl+c:给目标进程发送信号,让进程终止(目标进程指的就是前台进程)
- ctrl+\:终止进程
$kill-l# 列出各种信号| 信号编号 | ||||
|---|---|---|---|---|
| 1) SIGHUP | 2) SIGINT | 3) SIGQUIT | 4) SIGILL | 5) SIGTRAP |
| 6) SIGABRT | 7) SIGBUS | 8) SIGFPE | 9) SIGKILL | 10) SIGUSR1 |
| 11) SIGSEGV | 12) SIGUSR2 | 13) SIGPIPE | 14) SIGALRM | 15) SIGTERM |
| 16) SIGSTKFLT | 17) SIGCHLD | 18) SIGCONT | 19) SIGSTOP | 20) SIGTSTP |
| 21) SIGTTIN | 22) SIGTTOU | 23) SIGURG | 24) SIGXCPU | 25) SIGXFSZ |
| 26) SIGVTALRM | 27) SIGPROF | 28) SIGWINCH | 29) SIGIO | 30) SIGPWR |
| 31) SIGSYS | 34) SIGRTMIN | 35) SIGRTMIN+1 | 36) SIGRTMIN+2 | 37) SIGRTMIN+3 |
| 38) SIGRTMIN+4 | 39) SIGRTMIN+5 | 40) SIGRTMIN+6 | 41) SIGRTMIN+7 | 42) SIGRTMIN+8 |
| 43) SIGRTMIN+9 | 44) SIGRTMIN+10 | 45) SIGRTMIN+11 | 46) SIGRTMIN+12 | 47) SIGRTMIN+13 |
| 48) SIGRTMIN+14 | 49) SIGRTMIN+15 | 50) SIGRTMAX-14 | 51) SIGRTMAX-13 | 52) SIGRTMAX-12 |
| 43) SIGRTMAX-11 | 54) SIGRTMAX-10 | 55) SIGRTMAX-9 | 56) SIGRTMAX-8 | 57) SIGRTMAX-7 |
| 58) SIGRTMAX-6 | 59) SIGRTMAX-5 | 60) SIGRTMAX-4 | 61) SIGRTMAX-3 | 62) SIGRTMAX-2 |
| 63) SIGRTMAX-1 | 64) SIGRTMAX |
2.1.2 自定义信号捕捉singal()函数(证明ctrl+c是2号信号编号)
相关函数
#include<signal.h>// 函数指针类型重定义typedefvoid(*sighandler_t)(int);// 功能:修改信号编号执行指定的功能sighandler_tsignal(intsignum,sighandler_t handler);// 参数signum 信号编号【9、19号信号无法自定义!!!证明见2.2.2的使用示例】 handler 函数指针,修改后的执行方法示例:testsig.cc(测试自定义信号捕捉)
#include<iostream>#include<unistd.h>#include<signal.h>#include<sys/types.h>voidhandleSig(intsig){std::cout<<"获得了一个信号:"<<sig<<std::endl;}intmain(){signal(SIGINT,handleSig);intcnt=0;while(true){std::cout<<"hello world, "<<cnt++<<" ,pid: "<<getpid()<<std::endl;sleep(1);}return0;}Makefile
.PHONY:all all:testsig testsig:testSig.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f testsig mykill2.1.3 前台进程(目标进程)&后台进程
前台进程:
$ ./XXX在命令行直接运行某进程,则默认为前台进程。- 前台进程可以从标准输入中获取内容【键盘输入即为标准输入】。命令行shell是前台进程。
- 前台进程只能有一个。
- 前台进程的本质目的:就是要从键盘获取数据的。
后台进程:
$ ./YYY &在命令行运行进程后加"&"符号,则为后台进程。- 后台进程无法从标准输入中获取内容【键盘输入即为标准输入】,所以ctrl+c对后台进程无效。
- 后台进程可以有多个。
共性:前后台进程都可以向标准输出打印
为什么前台进程可以处理键盘输入,后台进程不能处理键盘输入?答:键盘只有一个,只能向一个固定的进程输入数据。谁需要输入数据就把谁放在前台。
在前台执行2.1.1的代码之后,执行
ls pwd等命令行操作,bash没反应是为什么?答:./testsig运行之后,bash自动被切换成了后台进程,ls pwd等命令是给./testsig的,但是./testsig进程没有调用cin或者scanf命令处理工作,所以无法对ls pwd等命令作出反应。在后台之后2.1.1的代码之后,执行
ls pwd等命令行操作,bash可以反应是为什么?答:./testsig &把./testsig进程放在了后台运行,前台依然是bash进程,则bash收到ls pwd命令之后则做出对应的反应。为什么ctrl+c等通过键盘组合键产生的信号只能发给前台进程?答:因为键盘组合键也是键盘输入
# 查看运行中的进程psajx|head-1&&psajx|greptestsig# ps: 核心命令:Process Status 的缩写,用于查看系统中进程的运行状态。# ajxa(all):显示所有终端(tty)关联的进程(包括其他用户启动的进程,默认ps只显示当前用户当前终端的进程); j(jobs):显示作业控制相关字段(列),比如PPID(父进程 ID)、PGID(进程组 ID)、SID(会话 ID)、TTY(控制终端)、STAT(进程状态)等; x:显示无控制终端的进程(比如后台守护进程、无终端启动的进程,这类进程的 TTY 字段为 ?);# head: 工具命令:读取输入(文件 / 管道数据)的「前 N 行」,默认前 10 行。(此处读取第一行表头)# grep: 核心工具:Global Regular Expression Print 的缩写,用于在输入中搜索「匹配指定字符串 / 正则」的行并输出(文本过滤工具)。- 怎样杀死后台进程
- 使用 kill 命令:
kill -9 PID号(杀掉对应PID的进程)
- 使用 kill 命令:
2.1.补:前后台相关命令
补充说明:2.1.1的代码在后台运行时,使用ls pwd jobs等命令,则2.1.1代码的输出会继续,而ls pwd jobs命令也会有相应的输出,他们的输出混在一起了!其中,显示器此时就是共享资源,此问题为数据不一致问题
| 命令 | 功能 |
|---|---|
./YYY & | 后台运行进程,使用此命令后会输出[任务号] 进程PID |
jobs | 查看所有的后台任务 |
fg 任务号 | 将指定的后台任务切到前台【foreground 前台】 |
| ctrl+z | 暂停进程,同时将进程切到后台 |
bg 任务号 | 让指定的后台任务恢复运行(配合ctrl+z执行)【background 后台】 |
2.1.4 给进程发送信号
信号产生之后并不是立即处理的,所以要求进程必须把信号记录下来!
记录在哪?:
struct tast_struct{ unsigned int pending;//位图结构 }(比特位的位置:信号的编号。比特位的内容:是否收到)
属于操作系统的数据结构体对象(给目标进程发送信号的本质:向目标进程写信号,即修改位图(修改位图需要使用pid或者信号的编号)
- 修改位图本质是修改内核的数据!
- 只有操作系统可以修改内核的数据(不管信号怎么产生,发送信号,在底层,必须让OS发送)。
- 用户要修改内核数据,则操作系统必须提供发送信号的系统调用,即
kill命令(kill就是使用操作系统发信号的系统调用)
- 用户要修改内核数据,则操作系统必须提供发送信号的系统调用,即
- 只有操作系统可以修改内核的数据(不管信号怎么产生,发送信号,在底层,必须让OS发送)。
- 修改位图本质是修改内核的数据!
补:信号 VS 通信IPC
狭义理解:
通信IPC:进程间通信,数据从用户到用户
信号:人通过操作系统给进程发信号
广义理解:信号和通信IPC都是通知某种事件
如何记录?:
2.2 系统调用发送信号
2.2.1 kill系统调用(给指定进程发送信号)
使用kill系统调用给进程发送信号
#include<iostream>#include<sys/types.h>#include<signal.h>// ./mykill signumber pidintmain(intargc,char*argv[]){if(argc!=3){std::cout<<"./mykill signumber pid"<<std::endl;return1;}intsignum=std::stoi(argv[1]);pid_t target=std::stoi(argv[2]);intn=kill(target,signum);if(n==0){std::cout<<"send "<<signum<<" to "<<target<<"success"<<std::endl;}return0;}2.2.2 raise系统调用(给自己发送信号)
raise系统调用给自己发送信号
NAME raise-send a signal to the caller SYNOPSIS#include<signal.h>intraise(intsig);使用示例:自定义1-32号信号之后,使用raise顺序给自己发送信号。(运行到9号信号之后会自动杀死自己)
#include<iostream>#include<unistd.h>#include<signal.h>#include<sys/types.h>voidhandleSig(intsig){std::cout<<"获得了一个信号:"<<sig<<std::endl;}intmain(){for(inti=1;i<32;i++)signal(i,handleSig);for(inti=1;i<32;i++){sleep(1);// if(i == 9 || i == 19) continue;raise(i);}intcnt=0;while(true){std::cout<<"hello world, "<<cnt++<<" ,pid: "<<getpid()<<std::endl;sleep(1);}return0;}2.2.3 abort函数(给自己发送6号信号)
abort固定给自己发送原始功能的6号信号,使用abort之后,自定义的6号信号函数不起作用。
NAME abort-cause abnormal process termination SYNOPSIS#include<stdlib.h>voidabort(void);使用示例
#include<iostream>#include<unistd.h>#include<signal.h>voidhandleSig(intsig){std::cout<<"获得了一个信号:"<<sig<<std::endl;}intmain(){for(inti=1;i<32;i++)signal(i,handleSig);// for(int i = 1; i < 32; i++)// {// sleep(1);// if(i == 9 || i == 19) continue;// raise(i);// }intcnt=0;while(true){std::cout<<"hello world, "<<cnt++<<" ,pid: "<<getpid()<<std::endl;abort();sleep(1);}return0;}输出6号信号并退出,即abort给自己发送6号信号。
2.3 使用命令行kill向进程发信号
- 使用 kill 命令:
kill -n PID号(n是第n个信号,PID是进程对应的PID号)
2.4 硬件异常产生信号
在C/C++中程序崩溃的常见的两种情况:除0,野指针
除0错误:进程收到8号信号
8) SIGFPE浮点数错误。野指针错误:进程收到11号信号
11) SIGSEGV(Segmentation Violation)段错误。
SIGFPE P1990 Core Floating-pointexception(浮点数异常)SIGSEGV P1990 Core Invalid memoryreference(无效的内存引用)总结:程序崩溃的原因是进程收到的某种信号导致进程结束。(这种信号是谁发送的?见2.1.4,操作系统发送信号)
以上两种方式触发OS给进程发送信号的流程:进程犯错了 → OS识别到进程出错并判断犯错的类型 → OS给进程发送对应信号
#include<iostream>#include<unistd.h>#include<signal.h>voidhandleSig(intsig){std::cout<<"获得了一个信号:"<<sig<<std::endl;exit(13);}intmain(){for(inti=1;i<32;i++)signal(i,handleSig);// for(int i = 1; i < 32; i++)// {// sleep(1);// if(i == 9 || i == 19) continue;// raise(i);// }intcnt=0;while(true){sleep(1);std::cout<<"hello world, "<<cnt++<<" ,pid: "<<getpid()<<std::endl;// int a = 10;// a /= 0; // 除0错误/* 验证除0错误解除上面2行代码,验证野指针错误解除下面2行代码 */int*p=nullptr;*p=100;// 野指针错误}return0;}除0错误中操作系统如何知道程序犯错了?
答:CPU完成计算,CPU有各种寄存器,状态(标志)寄存器,有一个bit位标识CPU当前计算是否溢出。
CPU是硬件,OS是软硬件资源的管理者,当程序出错,OS会识别到硬件出错即发现计算溢出,而CPU中的寄存器保存当前进程的上下文(包括
current->task_struct),所以OS给此目标进程发送对应的8号信号。野指针错误
补:core VS term
- term:没有类似core的中间过程,进程直接退出
SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump。
- Core Dump:当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。即核心转储—支持debug。然后进程才退出。但是:在云服务器上,core dump功能是被禁止掉的!!
- 为什么要禁止掉?(了解)
云服务器是生产环境,core要在测试环境中使用。
- 如何查看?如何打开?
ulimit -a:查看用户层对应的一些设置,其中core file size为0,表示core功能是关掉的。
ulimit -c 大小:可以打开core file size(临时打开的方案,如果要彻底打开要修改配置文件或者内核)【例如:ulimit -c 40960,打开core功能并设置其生成文件大小为40960KB】此时再次运行除0错误的文件,则会生成一个名为core的文件。
- 为什么要进行核心转储?
进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug (事后调试)。
- 一个进程允许 产生多大的core 文件?
一个进程允许 产生多大的core 文件取决于进程的Resource Limit (这个信息保存 在PCB中)。默认是不允许产生 core 文件的, 因为core 文件中可能包含用户密码等敏感信息,不安全。
- core文件的使用
将异常终止的程序使用debug方式进行编译,之后进入gdb,通过
core-file core命令来直接定位文件出错的位置!
- 回看07博客,3.2.2小节的子进程状态status,core dump标志!表示是否core dump
验证子进程status
Makefile文件
testsig:testSig.cc g++ -o $@ $^ -g -std=c++11 .PHONY:clean clean: rm -f testsigtestSig.cc文件
#include<iostream>#include<cstdio>#include<vector>#include<functional>#include<unistd.h>#include<signal.h>#include<sys/types.h>#include<sys/wait.h>intmain(){pid_t id=fork();if(id==0){sleep(2);printf("子进程\n");inta=10;a/=0;exit(1);}intstatus=0;waitpid(id,&status,0);printf("signal: %d, exit code: %d, core dump: %d\n",(status&0x7F),(status>>8)&0xFF,(status>>7)&0x1);return0;}终端指令:
$ulimit-c40960# 打开core dump功能$make# debug编译testSig.cc程序$ ./testsig# 运行程序子进程 signal:8,exitcode:0, core dump:1# 由输出结果看到,core dump为1。查看当前目录,有一个core文件补:没有出现core文件?并且我将
ulimit -c 0执行之后,coredump仍然为“1”?同一个原因!如下:回到home目录,
sysctl kernel.core_pattern使用此指令查看core 文件的生成路径和命名规则(kernel.core_pattern变量的含义即为core文件的生成路径和命名规则)我的kernel.core_pattern显示如下内容
kernel.core_pattern = |/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -F%F -- %E。核心意思是:Linux 内核不会直接生成传统的core.xxx原始核心文件,而是将程序崩溃的核心转储(core dump)数据,通过管道(|)交给/usr/share/apport/apport程序处理。✔总结这个配置的实际效果
当你的程序崩溃时(触发 core dump),系统会:
- 内核捕获到 core dump 事件,不生成原始 core 文件;
- 把崩溃的关键信息(PID、信号、程序路径等,通过
%xx占位符替换)和核心数据,通过管道传给apport;apport接收数据后,在/var/crash/目录生成一个崩溃报告文件(文件名类似_home_user_test.1000.crash);- 如果你开启了 apport 的自动提交功能,它会询问是否向 Ubuntu 官方发送该报告。
✔如果你需要「传统的原始 core 文件」(而非 apport 报告)
如果你的目的是用
gdb调试原始 core 文件(比如gdb ./程序 core.1234),需要修改kernel.core_pattern配置,关闭 apport 接管,让内核直接生成 core 文件:方法 1:临时生效(重启系统失效)
sudosysctl -w kernel.core_pattern=core.%p.%t# 生成 core.进程号.时间戳 的原始文件方法 2:永久生效(重启后仍有效)
编辑内核配置文件:
sudovi/etc/sysctl.conf添加 / 修改以下行(替换原来的 apport 配置):
kernel.core_pattern = core.%p.%t # 自定义命名格式,%p=PID,%t=时间戳(避免覆盖)生效配置:
sudosysctl -p✔验证:重新执行之前的指令即可
修改后执行
sysctl kernel.core_pattern,输出core.%p.%t即成功。之后程序崩溃时,会在工作目录生成原始 core 文件(如core.1234.1699999999)。
$ulimit-c0# 关闭core dump功能$make# debug编译testSig.cc程序$ ./testsig# 运行程序子进程 signal:8,exitcode:0, core dump:0# 由输出结果看到,core dump为0。查看当前目录,没有core文件2.5 软件条件产生信号
软件条件产生信号例子:
写进程 → 管道 → 读进程。其中读进程关闭时,写进程会收到SIGPIPE信号,自动结束。
管道是软件实现的,基于文件的,管道的读进程被关闭,即软件的条件不满足则终止进程。
2.5.1 alarm定时器系统调用
alarm定时器系统调用
NAME alarm-set an alarm clockfordelivery of a signal SYNOPSIS#include<unistd.h>unsignedintalarm(unsignedintseconds);// 参数alarm(x),在x秒之后,要求OS给进程发送信号alarm(0),取消闹钟// 返回值返回值为上一个闹钟剩余时间,例: 调用alarm(5)->闹钟响之后->返回值为0调用alarm(5)->过了3秒->第二次调用alarm(10)【重置闹钟】->返回值为2(表示上一个闹钟剩余时间为2秒)调用 alarm 函数可以设定一个闹钟,也就是告诉内核在seconds 秒之后给当前进程发SIGALRM 信号,该信号的默认处理动作是终止当前进程。
这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。
使用示例:
#include<iostream>#include<unistd.h>#include<signal.h>#include<sys/types.h>voidhandleSig(intsig){std::cout<<"获得了一个信号:"<<sig<<std::endl;exit(13);}intmain(){alarm(1);// 设定1秒的闹钟for(inti=1;i<32;i++)signal(i,handleSig);intcnt=0;while(true){std::cout<<"count: "<<cnt++<<std::endl;}return0;}testsig:testSig.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f testsig运行上述程序,打印14号14) SIGALRM信号。
最后输出为
count:1128372.5.2 基本alarm验证-体会IO效率问题
修改代码后重新运行(将IO操作去掉,只进行数量++)
#include<iostream>#include<unistd.h>#include<signal.h>#include<sys/types.h>intcnt=0;voidhandleSig(intsig){std::cout<<"获得了一个信号:"<<sig<<" cnt: "<<cnt<<std::endl;exit(13);}intmain(){alarm(1);// 设定1秒的闹钟// for (int i = 1; i < 32; i++)// signal(i, handleSig);signal(SIGALRM,handleSig);// int cnt = 0;while(true){// cout本质是IO xshell获取命令./testSig->在云服务器运行->通过网络->我们看到// std::cout << "count: " << cnt++ << std::endl;cnt++;}return0;}输出结果:(可见和有IO相比,有数量级上的差异。++是cpu操作,IO访问的是外设,根据冯诺依曼结构木桶效用)
获得了一个信号:14 cnt:562508419📌 结论:
- 闹钟会响一次,默认终止进程
- 有IO效率低
2.5.3 设置重复闹钟+pause函数
- 设置重复闹钟
#include<iostream>#include<unistd.h>#include<signal.h>#include<sys/types.h>voidhandleSig(intsig){std::cout<<"获得了一个信号:"<<sig<<" pid: "<<getpid()<<std::endl;alarm(1);// 每次闹钟倒计时终止并发送信号之后,重设闹钟。}intmain(){signal(SIGALRM,handleSig);alarm(1);while(true){std::cout<<"., "<<"pid: "<<getpid()<<std::endl;sleep(1);}return0;}- pause函数及使用
NAME pause-waitforsignal SYNOPSIS#include<unistd.h>intpause(void);DESCRIPTIONpause()causes the callingprocess(orthread)to sleep until a signal is delivered that either terminates the processorcauses the invocation of a signal-catching function.pause()使调用进程(或线程)休眠,直到信号被传递终止进程或导致调用信号捕获函数。 RETURN VALUEpause()returns only when a signal was caughtandthe signal-catching function returned.Inthiscase,pause()returns-1,anderrno is set to EINTR.使用示例1:通过pause()函数实现,通过信号来控制程序运行。每次收到信号执行handleSig函数,执行完暂停。
#include<iostream>#include<unistd.h>#include<signal.h>#include<sys/types.h>voidhandleSig(intsig){std::cout<<"获得了一个信号:"<<sig<<" pid: "<<getpid()<<std::endl;intn=alarm(1);std::cout<<"n: "<<n<<std::endl;}intmain(){signal(SIGALRM,handleSig);alarm(1);while(true){pause();}return0;}使用示例2:模拟操作系统的运行方式
#include<iostream>#include<vector>#include<functional>#include<unistd.h>#include<signal.h>#include<sys/types.h>////////////func/////////////voidSched(){std::cout<<"我是进程调度"<<std::endl;}voidMemManger(){std::cout<<"我是周期性的内存管理,正在检查有没有内存问题"<<std::endl;}voidFflush(){std::cout<<"我是刷新程序,我在定期刷新内存数据到磁盘"<<std::endl;}/////////////////////////////usingfunc_t=std::function<void()>;std::vector<func_t>funcs;// 每隔一秒,完成一些任务voidhandleSig(intsig){std::cout<<"#########################"<<std::endl;for(autof:funcs)f();std::cout<<"#########################"<<std::endl;intn=alarm(1);}intmain(){funcs.push_back(Sched);funcs.push_back(MemManger);funcs.push_back(Fflush);signal(SIGALRM,handleSig);alarm(1);while(true)// 操作系统运行方式!!!{pause();}return0;}2.5.4 如何理解软件条件
在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产生机制。这些条件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据产生的SIGPIPE信号)等。当这些软件条件满足时,操作系统会向相关进程发送相应的信号,以通知进程进行相应的处理。简而言之,软件条件是因操作系统内部或外部软件操作而触发的信号产生。
最小堆,alarm的数据结构为最小堆。设定闹钟就是在最小堆中插入一个对象,最小堆自动调整,把超时时间最小的节点调整到堆顶,OS扫描只查看堆顶,堆顶时间到之后执行对应的函数并删除堆顶节点,再次调整。
OS内堆闹钟的管理,即先描述,再组织,即转换成对堆的管理。
软件条件:软件(用软件设计),条件(超时)。
本章总结:
示例:收到第一次2号信号之后,将2号信号还原默认功能。
#include<iostream>#include<vector>#include<functional>#include<unistd.h>#include<signal.h>#include<sys/types.h>voidhandler(intsig){std::cout<<"hello sig: "<<sig<<std::endl;signal(2,SIG_DFL);// 2号信号默认动作是终止std::cout<<"恢复处理动作"<<std::endl;}intmain(){signal(2,handler);// 自定义捕捉// signal(2, SIG_IGN); // 忽略信号while(true){sleep(1);std::cout<<"."<<std::endl;}return0;}