当前位置: 首页> 科技> 能源 > 官网静态html模板_忂州网站建设_电商平台怎么加入_私域运营软件

官网静态html模板_忂州网站建设_电商平台怎么加入_私域运营软件

时间:2025/7/10 17:41:33来源:https://blog.csdn.net/m0_74126249/article/details/140643952 浏览次数:0次
官网静态html模板_忂州网站建设_电商平台怎么加入_私域运营软件

目录

生活中的信号

Linux中的信号

前台进程与后台进程

信号的产生

核心转储 core dump

​编辑信号的其他相关概念

信号处理的三种方式

信号在内核中的表示示意图

sigset_t 类型

信号集操作函数

sigprocmask

sigpending

综合练习

用户态与内核态

信号的捕捉过程

sigaction函数

可重入函数

volatile关键字

SIGCHLD信号


生活中的信号

● 信号没有产生时,我们就知道如何处理对应的信号

比如你现在在家里,但你仍然知道红灯停,绿灯行

● 信号的产生是异步的,也就是我们不知道信号什么时候到来

比如你点了外卖后开始打游戏,打游戏期间你也不知道什么时候外卖员会给你打电话

● 信号产生了,可以不立即处理,而是在合适的时候处理

比如电话通知你外卖到了,你可以暂时不取,等打完游戏再取外卖

● 信号产生后可以稍后处理,这就要求你必须要有暂时保存信号的能力

比如电话通知你外卖到了,你可以暂时不取,你的大脑中已经保存了外卖到了的信息,你是可以稍后取外卖的

Linux中的信号

在OS中,上述的"我,你,我们"本质都是进程,信号在OS中就是一种向目标进程发送通知消息的一种机制!

在进程被设计的时候,源代码中就内置了很多对信号的识别和处理动作,因此进程没有收到任何信号之前,进程就认识所有信号,并且知道当信号到来时应该如何处理对应的信号

● kill -l 查看系统中的信号

其中,每个信号都有数字和大写的英文单词,显然是宏定义,数字就是信号的编号!

普通信号从 1-31,  实时信号从34-64, 没有0号信号,可以理解为0表示进程没有收到任何信号,也就是进程代码是正常执行完毕的!本篇博客只介绍普通信号!

● 系统中每个进程都维护了一张函数指针数组表,数组下标和信号编号强相关,数组内容就是相应信号的处理方法,因此进程知道如何处理相应的信号!

前台进程与后台进程

前台进程只能有1个,因为键盘只有1个,能接收键盘输入的必然是前台进程

后台进程可以有多个,一般耗时任务比较长的任务会放到后台执行,比如下载任务

前台进程可以接收键盘输入,可以被ctrl + c 终止掉,此时bash是后台进程,无法执行其他指令

后台进程不能接收键盘输入,无法被ctrl + c 终止掉,此时bash是前台进程,可以执行其他指令

● 当前台进程(bash除外)被杀掉之后, bash会自动把自己提到前台

● 前台进程如果被暂停(ctrl+z),会立刻变为后台进程

启动前台进程:  ./process, 启动后台进程: ./process &

● jobs指令: 查看系统中已经启动的任务

● fg 任务编号,将后台任务提为前台任务

● bg 任务编号,启动后台暂停的任务

信号的产生

OS如何知道键盘有数据了?

● OS是硬件的管理者,硬件有很多,如果OS一直轮询(比如每隔一段时间)检测硬件的状态,时间间隔长了OS不能及时得到有效信息,时间间隔短了OS太忙了,显然不现实!

● OS在设定时,就用到了中断技术: cpu内部除了有运算器,还有控制器,OS不直接和外设在数据层面上打交道,但是要和外设在控制信息层面上打交道!

● cpu上有针脚(有编号), 和主板的硬件电路直接来连接,主板和外设直接连,外设可以向cpu发送光电信号,这种信号就叫做中断信号,而针脚编号叫做中断号!

● 外设向cpu发送的中断信号会被存放在cpu寄存器中,就可以被程序读取了,就把硬件信息转化成了软件信息

● 为了对外部的中断信号做出相应,系统中存在一张中断向量表,本质是函数指针数组,数组下标是中断号,数组内容是相应硬件的读取方法

● 硬件中断信号的发出对于OS是异步的,而信号本质其实就是用软件来模拟中断的行为

如何理解向目标进程发送信号?

● 一个进程可能会收到多个信号,因此进程要对多个信号进行管理

● 管理信号的两个核心问题:是否收到了信号,收到了几号信号

● 显然task_struct中只需要维护一张位图即可,比特位的位置表示信号编号,比特位的内容表示是否收到了对应信号,由于只有31个普通信号,只需要一个32位的整数充当位图即可

● 发送信号本质是将位图对应的比特位由0改1,因此发信号本质是写信号

● OS是进程的管理者,无论是何种方式产生的信号,最终都是OS向目标进程发信号

信号产生的三种方式

1.键盘产生

● 键盘输入的数据分两种,一种是常规输入数据,一种是控制数据(如组合键,表示向进程发信号)

● 组合键表示向进程发送对应的信号,如 ctrl + c == 2号信号,ctrl + z == 19号信号,ctrl + \ == 3号信号

● 我们可以使用signal函数修改信号的默认处理行为,同时可以验证键盘组合键等同于发信号

sighandler_t signal(int signum, sighandler_t handler);

功能: 将编号为signum的信号的默认处理动作改为自定义处理行为(又叫信号捕捉,handler方法)

参数: signum表示信号编号,handler是信号的自定义处理行为

返回值: 返回的是本次修改为自定义处理行为前的信号处理方式

#include <iostream>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
using namespace std;void func(int signo)
{cout << "收到了2号新号" << endl;exit(1);
}int main()
{signal(2, func); while(true){cout << "I am running..., pid :" << getpid() << endl;sleep(1);}return 0;
}

● 9号信号被称为管理员信号,是不能被自定义捕捉的,否则一个进程对所有信号自定义捕捉后就永远无法被终止了!

#include <iostream>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
using namespace std;void func(int signo)
{cout << "收到了9号信号" << endl;exit(1);
}int main()
{signal(9, func); while(true){cout << "I am running..., pid :" << getpid() << endl;sleep(1);}return 0;
}

● man 7 signal 查看信号的默认处理动作

2.系统调用/函数产生

kill系统调用:

int kill(pid_t pid, int sig);

功能: 向pid进程发送sig信号

参数: 进程pid, 信号编号sig

返回值: 成功返回0,失败返回-1

#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#include <cstring>
#include <unistd.h>
using namespace std;static void Usage(const string& proc)
{cout << "\nUsage: " << proc << " -sigNumber processId" << endl << endl;
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}int sigNumber = stoi(argv[1] + 1);int processId = stoi(argv[2]);kill(processId, sigNumber);return 0;
}

raise函数:

int raise(int sig);

功能: 哪个进程调用raise,就向哪个进程发送sig信号

参数: 信号编号sig

返回值: 成功返回0,失败返回非0

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void handler(int signo)
{cout << "get a signo : " << signo << endl;
}int main()
{signal(2, handler);while(1){raise(2);sleep(1);}return 0;
}

abort函数:

void abort(void);

功能: 哪个进程调用abort函数,直接收到 SIGABRT 信号,直接终止当前进程

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
using namespace std;void handler(int signo)
{cout << "get a signo : " << signo << endl;
}int main()
{signal(6, handler); abort(); //即使6号信号被捕捉了,进程也会被abort直接终止掉!while(1){cout << "I am running" << endl;sleep(1);}return 0;
}

 

3.异常

● cpu中有一个状态寄存器,当发生除零异常,状态寄存器中的溢出标志位从0变1,cpu显示异常

● os是硬件的管理者,当os发现cpu异常,会立即发信号杀掉引起异常的进程

● 杀掉进程后,状态寄存器的溢出标志位就会从1变0,cpu恢复正常了

验证除零异常会收到8号信号

#include <iostream>
#include <sys/types.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
using namespace std;void handler(int signo)
{cout << "get a signo : " << signo << endl;sleep(1);//exit(0);
}int main()
{signal(8, handler); //捕捉除0异常int a = 10;a /= 0; //不能直接除0,是数学的规定return 0;
}

● 我们对8号信号进行了自定义捕捉,因此发生除0错误时会执行自定义handler方法

● 如果handler中加了exit函数,执行handler方法后进程会立即退出,这是意料之中

● 但如果handler中没有exit函数,最后会死循环执行打印语句

● 当进程发生除0异常,cpu的状态寄存器的溢出标志位从0变1,会通知os自己出异常了,os会处理异常,而我们对信号处理方法进行了自定义捕捉,因此os在屏幕上打印了一句话就认为自己处理完了,实际上cpu的状态寄存器的溢出标志位仍然是1,当调度到该进程时,依然处于异常状态,循环往复

验证段错误异常会收到11号信号

● 段错误本质是一种越界行为,比如空指针解引用,0号虚拟地址空间对应的物理空间并没有分配给用户,当用户对空指针解引用时,会去访问0号虚拟地址空间对应的物理空间,此时MMU硬件单元会转化出错,通知os,os向进程发送信号

#include <iostream>
#include <sys/types.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
using namespace std;void handler(int signo)
{cout << "get a signo : " << signo << endl;sleep(1);//exit(0);
}int main()
{signal(11, handler); //捕捉段错误异常int* p = nullptr;*p = 2;return 0;
}

4.软件条件

● 匿名管道通信时,读端关闭,写端还在写,OS检测到读端文件描述符已经关闭了,就会直接向写端发送SIGPIPE信号杀掉写端进程,此时信号的产生的来源是软件条件!

● 不是所有的信号都是软硬件异常产生的!还有让进程暂停、继续等其他事情,说明还有非异常问题也会产生信号!

● Linux中的闹钟本质就是非异常问题产生的信号

OS中的时间

● 电脑一开机就知道时间,是因为纽扣电池在固定时间间隔给计算机硬件发信号,记录时间戳

● 电脑用上多年,时间可能不太准了,意味着纽扣电池寿命不久了

其他进程都是被OS调度的,那么OS开始是如何跑起来的呢?

● OS的本质就是一个死循环 while(1) { pause() }

● CMOS硬件周期性的,高频率的,向cpu发送时钟中断(也就是cpu的主频),os会去中断向量表中找到对应CMOS中断号的方法,也就是os的调度方法: while(1) { pause() }

● 所以说OS的执行是基于硬件中断的!

OS中的闹钟

● 闹钟在任何OS天然就是要被支持的,而OS内可以设置多个闹钟,  要把闹钟管理起来,先描述,再组织, 定闹钟,就创建一个内核数据结构对象 struct clock { ... }

● 结构体中有什么字段?  比如闹钟响的时间;要能够很好进行时间比对,比如时间戳! 闹钟id, 用户名, 用户id, 什么时间点设置的,闹钟响了向谁发信号等等

● 如何组织设置的闹钟, 如何知道哪些超时了?  只需要按时间为键值建立大/小堆,对闹钟的管理就变成了对数据结构堆的增删改查

alarm系统调用:

unsigned int alarm(unsigned int seconds);

功能: 设置一个闹钟,闹钟响之后,进程会收到SIGALRM信号,进程终止

参数: 闹钟在seconds秒之后响

返回值:  在本次调用alarm之前,没有设置过闹钟,则返回0;如果已经设置过闹钟,则旧的闹钟会被新的闹钟取代,并返回设置过的闹钟的剩余时间;如果参数传0,代表取消历史闹钟,并返回历史闹钟的剩余时间

对比访问外设和访问内存的速度差异

第一份代码访问了外设, 而第二份代码没有访问外设,因此速度差异非常明显!

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
using namespace std;int cnt = 0;int main()
{alarm(1);while(true){cout << "alarm : " << cnt++ << endl;}return 0;
}

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
using namespace std;int cnt = 0;void handler(int signo)
{cout << "get a signal : " << signo << ", alarm : " << cnt++ << endl;exit(0);
}int main()
{signal(14, handler);alarm(1);while(true){cnt++;}return 0;
}

定一个闹钟只响一次:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
using namespace std;int n = 0;void handler(int signo)
{n = alarm(0); //设置0就是将历史闹钟取消掉, 历史闹钟如果还没响,就返回历史闹钟剩余时间cout << "result: " << n << endl;exit(0);
}int main()
{signal(14, handler);cout << "pid : " << getpid() << endl;alarm(15); //闹钟15秒后响起while(true){sleep(1);cout << "running ..." << endl;}return 0;
}

定一个闹钟每隔2秒响一次:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
using namespace std;int n = 0;void handler(int signo)
{alarm(2); //2秒之后响cout << "result: " << n << endl;
}int main()
{signal(14, handler);cout << "pid : " << getpid() << endl;alarm(2); //闹钟2秒后响起while(true){sleep(1);cout << "running ..." << endl;}return 0;
}

核心转储 core dump

● 当进程收到信号终止运行后,会将当前进程的核心上下文数据写到一个磁盘上的临时文件,这个磁盘上的临时文件就是核心转储文件

● 核心转储文件的名字一般是 进程pid.core 或 core.进程pid

● 云服务器下默认是把core dump选项关闭的,否则进程一旦异常终止就会形成核心转储文件,会占据较大的磁盘空间

● gdb core.pid 可以很好的排查定位出错位置和出错原因

信号的其他相关概念

信号递达: 实际执行信号的处理动作

信号未决: 信号从产生到递达之间的状态

信号阻塞:  信号是允许被阻塞的,阻塞的意思是暂时不进行递达,直到解除对信号的阻塞才能抵达该信号,  信号被阻塞时处于未决状态!

ps1: 注意区分信号忽略和信号阻塞,忽略本身就是对信号做了处理,处理方式就是忽略,也就是忽略信号是递达了的!而信号阻塞是进程主动屏蔽了特定信号,所以信号产生后就无法被递达,直到解除屏蔽才能被递达

ps2: 信号是未决的,不一定被阻塞了,也可能是没来的及处理, 而信号被阻塞了,一定是未决的

信号处理的三种方式

1.默认动作

2.自定义捕捉

3.忽略处理

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
using namespace std;void handler(int signo)
{cout << "handler :" << signo << endl; 
}int main()
{cout << "getpid :" << getpid() << endl;cout << "自定义行为" << endl;signal(2, handler); //自定义sleep(5);cout << "恢复默认行为" << endl;signal(2, SIG_DFL); //恢复默认处理动作sleep(10);return 0;
}

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;void handler(int signo)
{cout << "handler " << signo << endl;
} int main()
{cout << "getpid: " << getpid() << endl;signal(2, handler); sigset_t block, oblock;sigemptyset(&block);sigemptyset(&oblock);sigaddset(&block, 2); //将2号信号添加到自己定义的block信号集中sigprocmask(SIG_BLOCK, &block, &block);while(true){sleep(1);}return 0;
}

总结

信号的默认处理和忽略动作,第二个参数都是大写的英文单词,本质就是宏,将0/1强制类型转化成函数指针类型,所以使用signal函数时,系统会先判断参数是否为0/1,如果为0/1,执行默认处理动作/对信号进行忽略,不为0/1,才去执行回调函数,进行自定义捕捉!

#define	SIG_DFL	 ((__sighandler_t)  0)	/* Default action.  */
#define	SIG_IGN	 ((__sighandler_t)  1)	/* Ignore signal.  */

信号在内核中的表示示意图

每个进程都维护了三张表,block表和pending表是位图表,handler表是函数指针数组,三张表的下标表示信号编号(下标从0开始,信号编号从1开始,对应加1减1即可)

● 三张表的含义:

pending位图表:发信号本质是修改pending位图,对应位置从0改1

handler表: 是一个函数指针数组,保存的是对对应信号的处理方法

block位图表: 是否对特定的信号进行屏蔽(阻塞)

● 进程启动时,pending表和handler表就被设置好了,利用pending表和handler表,我们就可以识别并且处理信号了

● 具体到1个信号,三张表应该横着看,比如:

第一行:  没有收到1号信号,对1号信号没有屏蔽,处理行为是默认动作

第二行:  收到了2号信号,对2号信号屏蔽了,尽管处理动作是忽略,但由于信号阻塞,不会处理

第三行:  没有收到3号信号,对3号信号屏蔽了,即便将来收到了3号信号,也不会进行处理

● 能不能执行handler表中对应的方法,最终还是要看block表中的信号是否被阻塞,而最终执行了handler表中的方法后,pending位图中的比特位会由1置为0;当进程收到了信号,但信号被阻塞了,不能执行方法,但是一旦解除阻塞了,信号就要立即被处理!

● 如果在进程解除对某信号的阻塞之前,这种信号产生过多次,将如何处理?

POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次, 而实时信号在递达之前产生多次可以依次放在一个队列里

● 所有对信号的操作都是围绕三张表展开的,而三张表都是内核数据结构,用户无法直接修改,OS要给我们提供系统调用来访问修改三张表

● 因为三张表都是内核数据结构,因此OS必须提供对应的系统调用来修改三张表, handler数组改直接可以拿着下标改,但是另外两张表要用户自己用位操作修改位图吗? 对用户要求太高了,因此OS不仅提供了系统调用,还提供了特定的数据类型(比如pid_t是OS提供的)

sigset_t 类型

typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;

● 在内核角度,三张表叫block位图表、pending位图表、handler表,在用户角度,block位图表又叫阻塞信号集(或信号屏蔽字), pending位图表又叫未决信号集

● sigset_t 类型本质就是信号集,该类型可以表示每个信号的"有效"或"无效"状态,比如在阻塞信号集中,“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态

● sigset_t 虽然是OS提供的,但是使用时就当成普通类型来用就行, 在底层本质是位图结构

● 对信号集的操作,我们也不必自己去用位操作修改,直接使用系统提供的信号集操作函数即可

信号集操作函数

sigprocmask

● 调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集), 成功返回0,失败返回-1

● how指的是我们要对信号屏蔽字做哪种操作,可以传递以下三个字段

● oldset是一个输出型参数,无论对block表进行何种操作,都是对block表的修改,把block表修改了,如果后续想恢复呢? 传入oldset参数就可以把旧的信号屏蔽字的内容带出来!

● 代码演示(屏蔽2号信号)

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;void handler(int signo)
{cout << "handler " << signo << endl;
} int main()
{cout << "getpid: " << getpid() << endl;signal(2, handler);sigset_t block, oblock;sigemptyset(&block);sigemptyset(&oblock);sigaddset(&block, 2); //将2号信号添加到自己定义的block信号集中sigprocmask(SIG_BLOCK, &block, &block);while(true){sleep(1);}return 0;
}

● 结论: 9号信号是管理员信号,无法被屏蔽

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;void handler(int signo)
{cout << "handler " << signo << endl;
} int main()
{cout << "getpid: " << getpid() << endl;signal(2, handler);sigset_t block, oblock;sigemptyset(&block);sigemptyset(&oblock);for(int signo = 1; signo <= 31; signo++){sigaddset(&block, signo);}sigprocmask(SIG_SETMASK, &block, &block);while(true){cout << "我已经屏蔽了所有的信号,来打我呀!" << endl;sleep(1);}return 0;
}

sigpending

sigpending用于获取三张表中的pending表,以输出型参数的方式带出来!

综合练习

1. 屏蔽2号信号,每隔1s循环打印pending位图

2. 10s之后解除对2号信号的屏蔽,每隔1s循环打印pending位图

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
using namespace std;void handler(int signo)
{cout << "handler " << signo << endl;
} void PrintPending(const sigset_t& pending)
{for(int signo = 31; signo > 0; signo--){if(sigismember(&pending, signo)){cout << "1";}      else{cout << "0";}}cout << endl;
}int main()
{signal(2, handler);cout << "getpid: " << getpid() << endl;//1.屏蔽2号信号sigset_t set, oset;sigemptyset(&set);sigemptyset(&oset);sigaddset(&set, 2); sigprocmask(SIG_BLOCK, &set, &oset);//2.让进程不断获取当前进程的pending表int cnt = 0;sigset_t pending;while(true){sigpending(&pending);PrintPending(pending);     sleep(1);cnt++;if(cnt == 10){cout << "解除对2号信号的屏蔽, 2号信号准备递达" << endl;sigprocmask(SIG_SETMASK, &oset, nullptr);}}return 0;
}

问题: 信号被递达,pending位图的对应位置会从1改为0,这个过程发生在什么时候? 是处理完信号之前还是处理完信号后呢??

结论: 信号在要被递达时,pending位图对应位置就已经从1改0了!

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
using namespace std;void PrintPending(const sigset_t& pending)
{for(int signo = 31; signo > 0; signo--){if(sigismember(&pending, signo)){cout << "1";}      else{cout << "0";}}cout << endl;
}void handler(int signo)
{    sigset_t pending;cout << "############################" << endl;sigpending(&pending);PrintPending(pending);     cout << "############################" << endl;cout << "handler " << signo << endl;
} int main()
{signal(2, handler);while(true){sleep(1);}return 0;
}

用户态与内核态

● 用户态是一种受控的状态, 比如操作上有权限约束,代码上会出现越界访问或野指针问题, 能够访问的资源是有限的,不能直接访问硬件资源,必须借助系统调用

● 内核态是一种OS的工作状态,能够访问大部分系统资源,系统调用背后,就包含了从用户态到内核态的身份转变

● 进程地址空间中, 用户态只能访问自己的[0, 3GB], 而内核态可以让用户以OS的身份访问[3, 4GB]

● 每一个进程都会有对应的进程地址空间,页表,而此处的页表指的是内存级页表,有n个进程就有n个用户级页表

● OS也是需要加载到内存的,OS代码和数据到内存的映射靠的是内核级页表,无论有多少个进程,内核级页表只有1个,因为OS的代码和数据只有1份

● 无论进程如何切换调度,cpu都能找到OS的代码运行,因为有内核级页表的存在

● 进程所有代码的执行,都可以在自己的地址空间内通过跳转的方式,进行调用和返回

● Linux操作系统中: cpu内部存在cs寄存器,有2个比特位,组合有4种,但是只使用2种,1表示内核态,3表示用户态,  用户态和内核态的切换都会修改两个比特位

 ● cpu内还有一套寄存器:CR寄存器,比如CR1(保存引发缺页中断/异常,比如野指针,越界访问的虚拟地址), CR3寄存器(保存当前进程页表信息, 物理地址!)

信号的捕捉过程

● 用户代码中调用了系统调用,需要从用户态转变为内核态,才有权限调用系统调用

● 调用完系统调用之后,会检测进程是否收到了信号,如果没有收到信号,会直接回到用户态;如果收到了信号,且处理动作为忽略,那么pending信号集对应位置由1改0,回到用户态如果处理动作为默认,则去执行默认行为,如直接杀掉进程,回到用户态

● 如果处理动作是自定义捕捉信号,则需要去回调处于用户态的代码,内核态当然可以直接执行用户态代码,但代码中可能会有恶意访问os的行为,因此回调用户态代码,需要从内核态转变到用户态

● 自定义捕捉完信号后再回到用户态调用系统调用的地方,继续向下执行代码

sigaction函数

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

功能: 对信号自定义捕捉

参数:  signum是信号编号, act和oldact都是输出型参数,可以修改对信号处理的自定义行为以及获取上次对信号处理的动作

返回值:成功返回0,失败返回-1

其中,sigaction结构体的定义如下:

struct sigaction {void     (*sa_handler)(int);        void     (*sa_sigaction)(int, siginfo_t *, void *);sigset_t   sa_mask;                 int        sa_flags;                void     (*sa_restorer)(void);     
};

我们只用到了其中两个参数:

sa_handler:自定义的信号捕捉函数

sa_mask:希望屏蔽的信号添加到该信号集中

注:

1. 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。

2.如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

使用sigaction函数达到和signal函数一样的效果,比如对2号信号进行自定义捕捉

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void handler(int signo)
{cout << "get a signo : " << signo << endl;
}int main()
{struct sigaction act, oact;act.sa_handler = handler;sigaction(2, &act, &act); //对2号信号进行自定义捕捉while(true){sleep(1);}return 0;
} 

验证正在处理当前信号时,会自动屏蔽掉OS不断发来的相同的信号

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void PrintPending(const sigset_t& pending)
{for(int signo = 31; signo > 0; signo--){if(sigismember(&pending, signo)){cout << "1";}else{cout << "0";}}cout << endl;
}void handler(int signo)
{cout << "get a signo : " << signo << endl;while(true){sigset_t pending;sigpending(&pending);PrintPending(pending);sleep(1);}
}int main()
{cout << "getpid : " << getpid() << endl;struct sigaction act, oact;act.sa_handler = handler;sigaction(2, &act, &oact); //对2号信号进行自定义捕捉while(true) sleep(1);return 0;
}

使用sigaction函数屏蔽指定的一堆信号

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void PrintPending(const sigset_t& pending)
{for(int signo = 31; signo > 0; signo--){if(sigismember(&pending, signo)){cout << "1";}else{cout << "0";}}cout << endl;
}void handler(int signo)
{cout << "get a signo : " << signo << endl;while(true){sigset_t pending;sigpending(&pending);PrintPending(pending);sleep(1);}
}int main()
{cout << "getpid : " << getpid() << endl;struct sigaction act, oact;act.sa_handler = handler;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, 3); //将3号信号也屏蔽掉sigaction(2, &act, &oact); //对2号信号进行自定义捕捉while(true) sleep(1);return 0;
}

研究进程同时收到多个信号的处理情况

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void PrintPending(const sigset_t& pending)
{for(int signo = 31; signo > 0; signo--){if(sigismember(&pending, signo)){cout << "1";}else{cout << "0";}}cout << endl;
}void handler(int signo)
{cout << "get a signo : " << signo << endl;sleep(1);
}int main()
{signal(2, handler);signal(3, handler);signal(4, handler);signal(5, handler);sigset_t mask, omask;sigemptyset(&mask);sigemptyset(&omask);sigaddset(&mask, 2);sigaddset(&mask, 3);sigaddset(&mask, 4);sigaddset(&mask, 5);sigprocmask(SIG_SETMASK, &mask, &omask);cout << "getpid :" << getpid() << endl;int cnt = 20;while(true){sigset_t pending;sigpending(&pending);PrintPending(pending);cnt--;sleep(1);if(cnt == 0){sigprocmask(SIG_SETMASK, &omask, nullptr);cout << "cancel 2,3,4,5 block" << endl;}}return 0;
}

结论: 当信号从内核态返回到用户态时,会检测是否还有信号没有处理,如果还有信号没有处理,则会将剩余信号处理完之后返回,而处理其余信号的顺序是不固定的,主要依据的是信号优先级

可重入函数

● 一个函数被多个执行流同时进入而不会出现任何问题,则该函数称为可重入函数;否则称为不可重入函数

● 函数中用到了全局变量或全局数据,一般是不可重入的

● 可重入函数或不可重入函数描述的只是函数的特征,并不代表函数的好坏

● 大多数函数都是不可重入函数

volatile关键字

volatile是C语言中的关键字,作用是保持内存的可见性

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;int flag = 0;void handler(int signo)
{cout << "signo : " << signo << endl;flag = 1;cout << "change flag to " << flag << endl;
}int main()
{signal(2, handler);cout << "getpid : " << getpid() << endl;while(!flag);cout << "quit nomal!" << endl;return 0;
}

当编译代码时如果不优化以及 O0级别 的优化结果都在预期之内,但是当采用 O1 级别优化时,发现卡住了,分析代码就知道卡在了while(!flag)循环处,但是flag不是改成1了吗,为啥还在循环??

稍微修改了一下代码,在定义flag时使用volatile关键字进行修饰,此时尽管O1级别 优化也可以正常跑完代码了!

经过O1级别优化,形成的汇编代码会有所变化,因为main函数中flag是只读的,因此以后cpu检测flag值时,只有第一次会从内存中读取数据到cpu寄存器中,以后就只在cpu内部做检测即可,因此你发送2号信号将flag值改了,改的是物理内存的flag值,不会影响到cpu的判断了!

而volatile的作用就是保持内存的可见性, 意思是所有使用flag的操作,都要从内存中拿数据!

SIGCHLD信号

子进程在退出的时候,不是静悄悄退出的,会给父进程发送17号信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;void handler(int signo)
{cout << "子进程给父进程说他要退了!!!" << endl;
}int main()
{signal(SIGCHLD, handler);pid_t id = fork();if(0 == id){int cnt = 5;while(cnt--){cout << "I am child process" << endl;sleep(1);}exit(0);}sleep(10);return 0;
}

既然子进程退出时会向父进程发信号,而父进程因为一些客观原因(比如网络服务)一直在运行,因此父进程可以直接在handler函数中等待回收子进程

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
using namespace std;void handler(int signo)
{cout << "get a signo : " << signo << endl;//回收子进程waitpid(-1, nullptr, 0); //阻塞等待
}int main()
{signal(SIGCHLD, handler);pid_t id = fork();if(id == 0){//childcout << "child is running" << endl;sleep(5);exit(10);}while(1){sleep(1); //父进程一直在进行某种服务}return 0;
}

● fork可能创建了多个子进程,有可能这些子进程同时退出了,都同时向父进程发送17号信号,而根据上文提到的,父进程收到第一个17号信号之后就阻塞17号信号了,也就意味着其他子进程发送的17号信号就被丢弃了,父进程只能处理一个子进程,其他子进程都是僵尸了!因此我们要在handler中循环式等待处理子进程

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
using namespace std;void handler(int signo)
{cout << "get a signo : " << signo << endl;pid_t id = 0;while(id = waitpid(-1, nullptr, 0)){if(id < 0) break; //没有需要回收的子进程了!cout << "回收子进程 : " << id << endl;}
}int main()
{signal(SIGCHLD, handler);for (int i = 0; i < 10; i++){pid_t id = fork();if (id == 0){// childcout << "child is running" << endl;sleep(5);exit(10);}}while(true){sleep(1); // 父进程一直在进行某种服务}return 0;
}

● 不一定所有的进程都会退出,比如在有6个进程退出了,有4个进程不退出,handler函数中等待回收子进程就会阻塞住,父进程就做不了其他事情了! 因此在等待子进程时必须是非阻塞等待!

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
using namespace std;void handler(int signo)
{cout << "get a signo : " << signo << endl;pid_t id = 0;while(id = waitpid(-1, nullptr, WNOHANG)){if(id <= 0) break; //没有需要回收的子进程了!cout << "回收子进程 : " << id << endl;}
}int main()
{signal(SIGCHLD, handler);for (int i = 0; i < 10; i++){pid_t id = fork();if (id == 0){// childcout << "child is running" << endl;sleep(5);exit(10);}}while(true){sleep(1); // 父进程一直在进行某种服务}return 0;
}

● 如果要获取子进程的退出信息,那么父进程必须等待子进程,但如果只是为了回收子进程的僵尸状态,则等待不是必须的, Linux支持对SIGCHLD信号进行忽略处理,所有的子进程都不需要父进程进行等待了,自动回收!

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;int main()
{signal(SIGCHLD, SIG_IGN); for(int i = 0; i < 10; i++){pid_t id = fork();if(0 == id){cout << "child is running" << endl;sleep(5);exit(10);}}while(true) sleep(1);return 0; 
}

关键字:官网静态html模板_忂州网站建设_电商平台怎么加入_私域运营软件

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: