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

中文网站建设小组网站建设7

中文网站建设小组,网站建设7,查询网站信息,海外 国内网站建设信号 前言1. 引入2. 概念3. 初步认识ctrlc信号4. 硬件中断 一、信号的产生1. 键盘组合键2. kill命令3. 系统调用①kill②raise③ abort 4. 异常①异常产生信号②原理 5. 软件条件6. 小结 二、信号的保存1. 引入2. 原理3. 接口①信号集——sigset_t②sigprocmask③sigpending④使… 信号 前言1. 引入2. 概念3. 初步认识ctrlc信号4. 硬件中断 一、信号的产生1. 键盘组合键2. kill命令3. 系统调用①kill②raise③ abort 4. 异常①异常产生信号②原理 5. 软件条件6. 小结 二、信号的保存1. 引入2. 原理3. 接口①信号集——sigset_t②sigprocmask③sigpending④使用接口 三、信号的处理1. 信号什么时候处理和怎么处理2. 接口——sigaction 四、拓展1. 可重入函数2. volatile3. SIGCHLD——17 前言 1. 引入 在生活中处处有信号的身影。eg红绿灯闹钟外卖信息提示等…。即便现在没有信号产生但是我们知道信号产生之后我们应该怎么做。—— 我们知道信号产生后的对应动作信号产生了我们可能不立即处理这个信号对应的动作例如外卖信息提示了但是现在正在和父母打电话很明显和父母打电话更重要等打完电话再去拿外卖。—— 而这期间就有个时间窗口 所以—— 进程用户 信号处理功能在进程中内置 进程必须识别并且可以处理信号哪怕没有产生信号也有具备相关信号处理的能力时间窗口 进程可能不会第一时间收到信号就去处理可能他要先处理更重要的事情。—— 所以进程就要有保存信号的能力信号的处理方式例如绿灯亮了默认就是过马路也有可能我们没有过马路的需求所以就是忽略这个绿灯还有就是旁边有个年迈的老奶奶我们扶老奶奶过马路。 默认动作忽略自定义动作 2. 概念 是一种异步的通知机制用来提醒进程一个事件已经发生属于软中断等下介绍硬件中断类似。 异步 接收者不知道发送者什么时候发送信息也不需要知道只需要跑自己的流程在后续的某个时间点产生交集 Linux中的信号 [1 - 31]这个范围的信号为普通信号需要掌握[34 - 64]这个范围的信号为实时信号了解即可。 实时信号常用的是车载系统之类的这边都要撞车了那边还要听歌肯定不行所以要及时处理。而我们学的Linux是基于时间片轮转的操作系统是相对公平的。 3. 初步认识ctrlc信号 代码 #include iostream #include unistd.h using namespace std;int main() {while(true){cout I am a process, pid: getpid() endl;sleep(1);}return 0; }运行结果 ./mykill的方式执行代码使用ctrlc能终止该进程。本质是被进程解释成收到了信号——2号信号可以在上面找一下2) SIGINT(interrupt —— 中断)。在讲信号产生时会进行验证./mykill 的方式执行的代码不能被ctrlc原因是因为ctrlc只能被前台进程接收而./mykill 的方式是以后台进程的方式启动该程序。所以实际上ctrlc被发送给了bash进程bash既然又是进程又是前台进程为什么不受到ctrlc的影响呢很明显bash内部进行了设置可能就是该信号接收的处理方式改为自定义动作在Linux中一次登录一个终端一般会配上一个bash每一次登陆只允许一个前台进程但是可以允许多个后台进程。只有前台进程能获取键盘输入 注 ll在显示屏上错乱显示为什么都可以执行 在一切皆文件部分我们也说了外设也被映射成为文件。 简化图解 认识 pidof proc | xargs kill -9 查看proc指定进程的PID然后杀死该进程。 pidof procpidof命令可以用来查找指定进程的PID。proc是要查找的进程名。pidof会返回一个或多个PID。如果使用proc生成多个后台进程可以返回多个PIDxargs 传递命令行参数的工具。在这个命令中xargs会将上一个命令pidof的输出作为参数传递给后面的命令killkill -9杀死指定进程 示例 ps -axj | grep proc | awk print $2 | xargs kill -9 找到包含proc关键字的所有进程并强制杀死这些进程。 ps -axj | grep proc过滤proc的进程信息awk ‘print $2’使用awk命令获取第二列即PID的信息 awk 文本处理工具。可以逐行读取文本文件、提取数据、对数据进行处理和格式化输出。 awk pattern { action } file pattern表示匹配条件若省略则适用于所有行action表示对匹配行执行的操作。常见的操作包括打印特定列、计算求和、使用条件语句等打印列awk { print $1, $3 } file.txt 4. 硬件中断 键盘的数据时如何输入给内核的ctrlc又是如何变成信号的 OS怎么知道键盘缓冲区有数据了难道是一次次遍历——肯定不是毕竟硬件那么多。 图解 硬件发送的信号是高低电平即高电平代表1低电平代表0。这种信号在中断控制器中会被解释成二进制信号然后发送给CPU。 我们学习的信号就是模拟的硬件中断所以称为软中断 注 中断的处理是串性的不会多个硬件同时发生中断 一、信号的产生 无论信号如何产生最终都是OS发送给进程OS是进程的管理者 先认识一个系统调用方便做测试 signal设置一个函数处理对应的信号 头文件#include signal.h函数声明sighandler_t signal(int signum, sighandler_t handler);参数signum要捕获信号的编号是SIGINT这类也可以handler表示信号处理函数的指针。参数类型typedef void (*sighandler_t)(int)。可以设置成SIG_IGN:忽略该信号SIG_DFL:使用系统默认处理方式自定义用户自定义信号处理函数返回值1. 成功返回一个函数指针指向先前的signum对应的信号处理函数2. 错误返回SIG_ERR后续实验 1. 键盘组合键 测试Ctrl c是2号信号——中断进程 使用signal测试 #include iostream #include unistd.h #include signal.h using namespace std;void handler(int signo) {cout process get a signal: signo endl; }int main() {//对2号信号捕获自定义函数处理该信号signal(SIGINT, handler);while(true){cout I am a process, pid: getpid() endl;sleep(1);}return 0; }运行结果 Ctrl\是3号信号——退出进程 这就不测试了和测试2号信号同样的方式 CtrlZ是19号信号——暂停进程 测试一下是所有进程都可以被捕获吗 #include iostream #include unistd.h #include signal.husing namespace std;void handler(int signo) {//signo收到的信号cout process get a signal: signo endl; }int main() {//普通信号就31个for(int i 0; i 31; i){//那个信号被触发都会执行我们自定义的处理方式signal(i, handler);}while(true){cout I am a process, pid: getpid() endl;sleep(1);}return 0; }测试结果测试方法使用kill命令一次次的给我们测试的进程发送信号9号和19号进程没有被捕获 其余的都被捕获了。 2. kill命令 方式kill -signo [PID] 在上面测试信号能不能被全部捕获时就是通过另一个终端使用kill命令向测试进程发送信号。 示例 3. 系统调用 ①kill 向指定进程发送信号 头文件#include sys/types.h#include signal.h函数声明int kill(pid_t pid, int sig);参数1. pid:要发送信号目标进程的PIDpid0,表示给调用进程同一个组的所有进程发送信号pid为负数,表示给某个进程组的所有进程发送信号2. sig表示要发送的信号编号可以是宏返回值1. 成功返回02. 失败返回-1错误码被设置测试代码1使用kill命令终止另一个启动的进程 #include iostream #include string #include cstdio #include cerrno #include cstdlib #include unistd.h #include signal.h #include sys/types.h using namespace std;void Usage(string proc) {cout Usage:\n\t proc signum pid\n\n; }int main(int argc, char *argv[]) {if(argc ! 3){Usage(argv[0]);exit(errno);}int signum stoi(argv[1]);pid_t pid stoi(argv[2]);int n kill(pid, signum);if(n -1){perror(kill);exit(errno);}return 0; }被终止的进程 #include iostream #include unistd.h using namespace std;int main() {while(true){cout I am a pro, pid: getpid() endl;sleep(1);}return 0; }运行结果终止成功 测试代码2使用kill命令终止自己 #include iostream #include unistd.h #include signal.h #include sys/types.husing namespace std;int main() {int cnt 10;while(cnt--){cout I am a proc, pid: getpid() endl;sleep(1);if(cnt 5){kill(getpid(), 2);}}return 0; }运行结果 ②raise 向调用者进程发送信号 头文件#include signal.h函数声明int raise(int sig);参数sig:要发送的信号可以是宏返回值1. 成功返回02. 失败返回非0测试代码 代码描述五秒之后给自己发送2号终止信号 #include iostream #include unistd.h #include signal.husing namespace std;int main() {int cnt 10;while(cnt--){cout I am a proc, pid: getpid() endl;sleep(1);if(cnt 5){raise(2);}}return 0; }运行结果 注也可以使用signal捕获一下看看是不是收到了2号信号 ③ abort 使进程异常终止。实际就是给调用进程发送6号信号 头文件#include stdlib.h函数声明void abort(void); 无参无返回值测试代码1正常调用 代码描述5秒后给自己发送6号信号 #include iostream #include unistd.h #include cstdlibusing namespace std;int main() {int cnt 10;while(cnt--){cout I am a proc, pid: getpid() endl;sleep(1);if(cnt 5){abort();}}return 0; }运行结果 测试代码2捕获6号信号 #include iostream #include unistd.h #include cstdlib #include signal.husing namespace std;void handler(int signo) {//signo收到的信号cout process get a signal: signo endl; }int main() {//两种方式都可以// signal(6, handler);signal(SIGABRT, handler);int cnt 5;while(cnt--){cout I am a proc, pid: getpid() endl;sleep(1);if(cnt 2){abort();}}return 0; }运行结果 发现6号信号被我们捕获后我们在自定义处理方法中并没有终止进程但是调用abort还是进行了终止 注使用kill -6 [PID]可以执行正常的自定义 4. 异常 ①异常产生信号 1. 除0异常——SIGFPE 测试代码1 #include iostream using namespace std;int main() {cout div zero before! endl;int a 10;a / 0;cout div zero after! endl;return 0; }运行结果打印出浮点数异常就退出了 解释结果除0异常其实就是向进程发送8号信号——8) SIGFPE 测试代码2看看除0异常是不是进程收到了8号信号 #include iostream #include signal.h #include unistd.h using namespace std;int main() {while(true){cout I am a process, pid: getpid() endl;sleep(1);}return 0; }运行结果 解释结果所以浮点数异常确实是因为收到了8号信号 测试代码3如果对8号信号捕获会发送什么 #include iostream #include unistd.h #include signal.husing namespace std;void handler(int signo) {cout process get a signal: signo endl;sleep(1); }int main() {// 对8号捕获自定义函数处理该信号signal(SIGFPE, handler);cout div zero before! endl;int a 10;a / 0;cout div zero after! endl;return 0; }运行结果 结果一直执行这个自定义的8号信号处理方法。下面原理讲原因 2. 野指针异常——SIGSEGV 测试代码1 #include iostreamusing namespace std;int main() {cout wild pointers before! endl;int* ptr nullptr;*ptr 10;cout wild pointers after! endl;return 0; }运行结果段错误 解释结果野指针本质就是向进程发送了11号信号——11) SIGSEGV 测试代码2直接对11号信号捕获 #include iostream #include unistd.h #include signal.husing namespace std;void handler(int signo) {cout process get a signal: signo endl;sleep(1); }int main() {signal(SIGSEGV, handler);cout wild pointers before! endl;int* ptr nullptr;*ptr 10;cout wild pointers after! endl;return 0; }运行结果 结果一直执行这个自定义的11号信号处理方法。下面原理讲原因 ②原理 除0错误的原理 一般理解除0错误会让进程崩溃 —— 因为OS给进程发送信号了——进程出问题了OS检测到了所以要给进程发信号 问题OS怎么知道进程出现异常了 简化的图解主要是CPU其它弱化 野指针错误的原理 简化的图解 注我们没有资格修改寄存器的值所以不要妄想修改寄存器的值从而让进程继续跑没有意义毕竟已经出错了 5. 软件条件 上述讲的除0和野指针这类异常时基于硬件的然后触发相应处理机制。在之前学习管道时有一种情况读端关闭写端继续写进程会收到13号信号——SIGPIPE。OS认为这浪费资源的操作所以直接终止正在写入的进程这就是基于软件的异常。本节内容主要介绍的是alarm接口和SIGALRM——基于软件条件产生的信号 alarm接口和SIGALRM信号 认识alarm 作用用于设置一个定时器在指定时间后发送SIGALRM信号给当前进程该信号的默认处理动作时终止当前进程 头文件#include unistd.h函数声明unsigned int alarm(unsigned int seconds);参数要设置闹钟的秒数返回值1. 上一个闹钟剩下的秒数2. 没有前一个闹钟返回0测试代码1 #include iostream #include unistd.h using namespace std;int main() {//设置一个两秒的闹钟alarm(2);while(true){cout I am a proc, pid: getpid() endl;sleep(1);}return 0; }运行结果 测试代码2 代码描述在开始定义一个15秒的闹钟然后我在代码开始运行5秒后使用kill命令直接向该进程发送14号信号该信号的处理方法被我们自定义了所以会执行我们自定义的信号处理方法。执行完之后直接退出此时获取了上一个设置闹钟时间还余下的秒数。 注sleep影响闹钟的触发所以自定义方法中的闹钟没有被触发 #include iostream #include unistd.h #include signal.h using namespace std;void handler(int signo) {cout process get a signal: signo endl;sleep(1);int val alarm(2);cout return val: val endl;sleep(5);exit(0); }int main() {signal(14, handler);alarm(15);while(true){cout I am a proc, pid: getpid() endl;sleep(1);}return 0; }运行结果和预期结果相符 注alarm——先描述再组织 先描述系统中不会就一个alarm闹钟所以必然会被结构体描述起来等alarm时间到达之后再发送信号再组织将alarm对象用优先级队列维护堆顶为最近闹钟触发时间所以OS只需要判断堆顶元素即可 6. 小结 Core Dump: 在讲到进程控制的进程等待时status输出型参数低16位中有一个core dump——核心转储标志位。 图解core dump: 测试core dump代码 #include iostream #include unistd.h #include sys/types.h #include sys/wait.husing namespace std;int main() {pid_t pid fork();if (pid 0){// childwhile (true){cout I am a child process, pid: getpid() endl;sleep(1);}exit(0);}// fatherint status 0;pid_t rid waitpid(pid, status, 0);if(rid pid){cout wait child proc success!, rid: rid endl;cout exit code: ((status8)0xFF) exit signal: (status0x7F) endl;cout core dump: ((status7)1) endl;}return 0; }运行结果分析 core文件分析 打开系统的core dump功能一旦进程出现异常OS会讲进程在内存中的运行信息给我dump(转储)到进程的当前目录磁盘形成core.pid文件——核心转储core dump运行时错误可以直接定位到出错行先运行然后core-file调试。 示例 终端信号和内核信号 在Linux系统中信号可以分为两类终端信号Terminal Signal和内核信号Kernel Signal 终端信号 由进程之间交互或控制终端发出的信号由用户或其他进程直接发送给目标进程。终端信号通常用来通知目标进程某种事件的发生内核信号由内核直接向进程发送的信号表示某种系统事件的发生或错误的发生。由内核管理和发送的用于处理系统级事件或错误情况。 二、信号的保存 1. 引入 信号保存的相关问题 信号为什么要保存 因为进程可能不会第一时间收到信号就去处理可能他要先处理更重要的事情—— 所以进程就要有保存信号的能力怎么保存 普通信号使用位图保存比特位的内容是两态的所以可以决定是否收到相对应的信号OS在这里扮演什么作用信号的发送 OS对进程发送信号本质就是OS修改task_struct属性中信号位图对应的比特位。因为OS是进程的管理者只有OS才有资格修改进程PCB的属性信息 为介绍下面内容引入一些信号的常见概念 信号递达(Delivery)实际执行信号的处理动作信号未决(Pending)信号从产生到递达中间的状态阻塞信号进程可以选择指定信号阻塞就是让该信号不能递达 被阻塞的信号会保持在未决状态直到进程解除阻塞才会执行递达。 注有些内容在信号处理中讲这里主要是介绍三张表 在上面我们也说了信号肯定是保存在位图中实际上也确实这样哈哈哈哈哈但是突如其来的转折——要注意了内核里有三张相关的表。block、pending和handler前两张都是位图handler是函数指针数组。 画图了画图了图里介绍。 2. 原理 图解 文字描述 对于每个信号block和pending表都有一个标志位handler表中的一个函数指针表示处理动作。信号产生时如果没有阻塞内核会设置该信号的未决标志直到该信号递达才清除执行处理信号函数前对应的未决标志就会被置0了同时对应的阻塞信号也被置1信号处理时再实验递达时如果判断handler表中是0就执行默认动作是1就执行忽略动作此外是自定义动作如果指定的信号被阻塞暂时不能被递达就算对应的处理动作是忽略但是没有解除阻塞前不能忽略该信号因为进程在这个过程中有机会改变处理动作然后解除阻塞。handler表中有一个SIG_IGN默认处理动作是忽略block表是阻塞前者是处理动作后者是对应阻塞的信号不能递达。某个信号被阻塞了也可以向该进程发送信号也会pending只不过不会递达。 注 问题进程解除对某信号的阻塞之前该信号产生多次的处理方法 答普通信号在递达前产生多次只记一次实时信号在递达前产生多次可以一次放在队列中维护后续依次执行 3. 接口 ①信号集——sigset_t sigset_t也称为信号集该类型是给用户提供的。包含在头文件#include signal.h中 提供该类型的原因 block和pending两张表都是用相同的数据类型sigset_t来存储虽然也可以使用long这种类型但是考虑到扩展性等方面提供了sigset_t类型本质就是位图每个位代表一个信息有效和无效。 注阻塞信号集也叫做当前进程的信号屏蔽字signal mask 信号集操作函数 与该类型一起出现的还有相关的位图操作的接口用来操作sigset_t类型的变量 头文件#include signal.h相关操作函数声明int sigemptyset(sigset_t *set); //用于清空信号集合int sigfillset(sigset_t *set); //用于填满信号集合int sigaddset(sigset_t *set, int signum); //向信号集合中添加指定的信号signumint sigdelset(sigset_t *set, int signum); //向信号集合中删除指定的信号signumint sigismember(const sigset_t *set, int signum); //判断signum信号是否在信号集合中参数1. set:被操作的sigset_t类型对象2. signum向set对象中添加、删除几号信号或者判断相应的信号是否在set指向的信号集中返回值:1. 前四个函数成功返回0出错返回-12. sigismember函数的返回值判断一个信号集中的有效信号是否包含指定的信号包含返回1、不包含返回0出错返回-1注 使用sigset_t类型的变量之前要先调用sigemptyset或sigfillset做初始化使信号集和处于确定的状态。 ②sigprocmask sigprocmask用于修改当前进程信号屏蔽字signal mask的系统调用。可以在程序中动态地控制进程对信号的阻塞和解除阻塞 头文件#include signal.h函数声明int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);参数1. how:指定信号屏蔽字的操作方式取值有以下几种SIG_BLOCK:将set中的信号添加到进程的信号屏蔽字中这些信号将被阻塞。相当于maskmask|setSIG_UNBLOCK:从进程的信号屏蔽字中移除set中的信号这些信号不再被阻塞。相当于maskmask~setSIG_SETMASK:将进程的信号屏蔽字设置为set中的值。相当于maskset2. set(输入型参数):表示要设置的信号集合。3. oldset(输出型参数):用于存储之前的信号屏蔽字。如果不为NULL则会把之前的信号屏蔽字存放在oldset中。返回值成功返回0出错返回-1错误码被设置③sigpending sigpending读取当前进程未决信号集。可以用来查询有哪些信号被阻塞而尚未处理以便进一步处理。 头文件#include signal.h函数声明int sigpending(sigset_t *set);参数set:输出型参数把pending表带出来返回值成功返回0出错返回-1错误码被设置④使用接口 测试代码1 代码描述对2号信号捕获阻塞2号信号然后10秒后解除阻塞在这十秒钟向进程发送2号信号。结果可以发现2号信号处于未决状态 #include iostream #include signal.h #include unistd.husing namespace std;void PrintPending(const sigset_t pending) {//发送2号信号之后我们想要看到的结果是000000...00000000010for(int signo 31; signo 0; signo--){if(sigismember(pending, signo)){cout 1;}else{cout 0;}}cout endl; }void handler(int signo) {cout catch a signo: signo endl; }int main() {// 1. 对2号信号做自定义捕获signal(2, handler);// 2. 先对2号信号进行屏蔽sigset_t set, oset;sigemptyset(set); // 初始化操作sigemptyset(oset);sigaddset(set, 2); // 对2号信号屏蔽// 3. 系统调用把set的数据设置到内核sigprocmask(SIG_SETMASK, set, oset); // 屏蔽了2号信号// 4. 打印当前进程pendingsigset_t pending;int cnt 0;while (true){// 获取pending表int n sigpending(pending);if (n 0)continue;PrintPending(pending);sleep(1);cnt;if (cnt 10){cout unblock 2 signo endl;sigprocmask(SIG_SETMASK, oset, nullptr);}}return 0; }运行结果 测试代码2 代码描述试试把所有的信号都屏蔽 #include iostream #include signal.h #include unistd.husing namespace std;void PrintBlock(const sigset_t block) {for (int signo 31; signo 0; signo--){if (sigismember(block, signo)){cout 1;}else{cout 0;}}cout endl; }int main() {sigset_t set, oset;sigfillset(set); // 初始化操作sigemptyset(oset);cout set before:;PrintBlock(set);// 设置进task_structsigprocmask(SIG_SETMASK, set, nullptr);// 看一看有哪些信号被屏蔽了sigprocmask(0, nullptr, oset);cout set after:;PrintBlock(oset);return 0; }运行结果 ok了牢底9和19号信号不能被阻塞之前也说过9和19号信号不能被捕获 三、信号的处理 信号递达实际执行信号的处理动作。 在文章的开头也说过信号的处理方式有三种 默认动作即系统对相应的信号默认的处理动作忽略处理动作就是忽略该信号自定义动作即用户自己实现的信号处理动作 1. 信号什么时候处理和怎么处理 在谈这个之前就要再谈进程地址空间了 再分析信号是什么时候处理的和怎么处理的 答当我们的进程从内核态返回用户态的时候进行信号检测和处理 用户态允许访问用户自己的代码和数据内核态允许访问OS的代码和数据 图解 注 从③返回到用户态执行自定义信号处理方法其实内核态也可以执行但是群众里有坏人使用内核执行内核的代码用户执行用户的代码。sighandler和main函数使用不同的堆栈空间所有的系统调用也是在一张表中是函数指针数组调用系统调用本质是把其对应的系统调用号数组下标写到寄存器中用户层看是sigreturn在内核看sys_sigreturn 2. 接口——sigaction 捕捉信号如果信号的处理动作是用户自定义函数在信号递达时就调用这个函数。 捕捉信号在上面我们已经学习并使用了一个接口signal在上面测试时我们也发现9和19号信号是不能被捕捉的。 学习另一个捕捉方法sigaction——用于设置信号处理方法 头文件#include signal.h函数声明int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);参数1. signum:要设置信号处理函数的信号2. act(输入型参数):一个结构体指针。包含了信号的处理方法和标志位3. oldact(输出型参数)传出该信号原来的处理动作struct sigaction {void (*sa_handler)(int); //向内核注册信号处理函数sigset_t sa_mask; };sa_mask:当调用信号处理函数时除了当前信号被自动屏蔽之外屏蔽别的信号用sa_mask,信号处理函数返回时自动恢复原来的屏蔽字返回值成功返回0失败返回-1错误码被设置sigaction使用 #include iostream #include unistd.h #include signal.h #include cstringusing namespace std;void PrintPending() {sigset_t set;sigpending(set); //把pending位图带出来for(int signo 1; signo 31; signo){if(sigismember(set, signo)){cout 1;}else{cout 0;}}cout endl; }void handler(int signo) {cout catch a signal, signal num: signo endl;while(true){PrintPending();sleep(1);} }int main() {struct sigaction act, oact;//对结构体初始化memset(act, 0, sizeof(act));memset(oact, 0, sizeof(oact));//为信号设置处理方法act.sa_handler handler; //自定义// act.sa_handler SIG_IGN; //忽略// act.sa_handler SIG_DFL; //默认//阻塞指定信号sigemptyset(act.sa_mask); //初始化工作sigaddset(act.sa_mask, 3); //阻塞3号信号sigaddset(act.sa_mask, 4); //阻塞4号信号sigaddset(act.sa_mask, 5); //阻塞5号信号//捕获2号信号sigaction(2, act, oact);while(true){cout I am a process! pid: getpid() endl;sleep(1);}return 0; }测试结果 结论 pending位图由1置0的时间是执行信号捕捉方法前先清0再调用信号被处理时对应的信号会被自动添加到block表中防止嵌套调用 eg:正在处理自定义信号处理函数时完全有可能在自定义函数中再次陷入内核这时再收到一样的信号就嵌套了。也证明了前面所说普通信号在递达前产生多次只记一次 四、拓展 1. 可重入函数 1. 引入问题 不同执行流访问一个链表的插入函数 图解 信号可以理解为假执行流可以影响到可重入函数问题 main函数调用insert插入节点插入操作分为两步刚执行完第一步因为硬件中断切换到内核完成相应的处理之后返回用户态返回用户态要检测信号有没有可以递达的有执行信号处理函数信号处理函数也调用了同一个链表执行了插入节点操作执行完信号处理函数返回从主控流程上次被中断的地方继续执行执行插入操作第二步 结果只有一个节点插入到链表另一个节点丢失 2. 概念 上述问题中insert函数被main和handler假执行流执行流重复进入导致节点丢失内存泄漏。 不可重入函数如果一个函数被重复进入导致可能出错。eginsert 反之就是可重入函数 3. 局部变量 上述的insert函数访问的是一个全局的链表有可能因为重入造成错乱如果一个函数只访问自己的局部变量或参数就不会错乱。原因是每个控制流程调用函数时都会创建一个新的栈帧它们实际上操作的是各自栈帧中的不同副本不会相互影响从而避免了数据错乱。 2. volatile volatile:保持内存的可见性。告诉编译器该关键字修饰的变量可以在程序的其他部分改变不应该对其进行优化处理。每次访问“volatile”变量时都应该从内存读取数据而不是从寄存器中获取。 验证代码 代码描述在main函数中写个死循环当收到2号信号时程序结束 #include iostream #include signal.husing namespace std;int flag 0;void handler(int signo) {cout catch a signal, signal num: signo endl;flag 1; }int main() {signal(2, handler);while(!flag);cout process quit normal endl;return 0; }运行结果 不进行优化编译g -o $ $^ -stdc11 符合我们的预期结果 进行优化编译 g -o $ $^ -O3 -stdc11 进程不终止了 解释优化编译的结果 代码在代码中定义的flag变量并被设置为循环条件。 优化flag符合下面两个条件所以被编译器优化到CPU寄存器中。 flag在代码中没有被更改如果信号不被触发那将会一直不被修改!flag是一种计算逻辑计算。计算中有两种计算一种算术计算一种逻辑计算 解决办法 volatile int flag 0; //在定义变量flag时使用volatile关键字修饰运行结果 3. SIGCHLD——17 子进程在退出的时候不是直接就退出了而是会主动向父进程发送17号信号——SIGCHLD 测试代码1 看看是不是子进程退出会给父进程发送17号信号 代码描述父进程死循环子进程执行五秒自动退出对17号信号捕捉 #include iostream #include signal.h #include unistd.h #include cstdlibusing namespace std;void handler(int signo) {cout catch a signal, signum: signo endl; }int main() {signal(17, handler);pid_t id fork();if(id 0){//childwhile(true){cout I am child proc, pid: getpid() ppid: getppid() endl;sleep(5);break;}exit(0);}while(true){cout I am father proc, pid: getpid() endl;sleep(1);}return 0; }运行结果符合预期结果也是17号信号 所以我们也就可以在信号处理方法哪里进行等待。 测试代码2 多个子进程分批退出或者一起退出的情况 问题1同时退出可能会出现多个信号同时发送给父进程只执行一次 解决方式采用循环的形式直到这一批子进程全部退出 问题2一部分一部分的退出 解决方式非阻塞轮询的方式 #include iostream #include signal.h #include unistd.h #include sys/types.h #include sys/wait.h#include cstdlibusing namespace std;void handler(int signo) {cout catch a signal, signum: signo endl;int id -1;while ((id waitpid(-1, nullptr, WNOHANG)) 0) //非阻塞轮询的方式{cout I am proc, pid: getpid() wait success! pid: id endl;} }int main() {signal(17, handler);for (int i 0; i 10; i){pid_t id fork();if (id 0){// childwhile (true){cout I am child proc, pid: getpid() ppid: getppid() endl;sleep(5);break;}exit(0);}//创建的慢一点sleep(1);}while (true){cout I am father proc, pid: getpid() endl;sleep(1);}return 0; }测试结果所有子进程无论一起退出还是分批退出都被正常等待成功。 测试代码3 父进程必须要等待吗不是 int main() {signal(17, SIG_IGN);for (int i 0; i 10; i){pid_t id fork();if (id 0){// childwhile (true){cout I am child proc, pid: getpid() ppid: getppid() endl;sleep(5);break;}exit(0);}//创建的慢一点//sleep(1);}while (true){cout I am father proc, pid: getpid() endl;sleep(1);}return 0; }运行结果 结论讲SIGCHLD信号设置为SIG_IGN(忽略)这样fork出来的子进程在终止时会自动清理掉不会产生僵尸进程也不会通知父进程 注 signal(17, SIG_DFL);更改这一句代码的运行结果信号默认处理动作还是会产生僵尸进程 系统默认的做法 系统默认的忽略动作和用户用sigaction函数自定义的忽略一般没有区别SIGCHLD这个信号属于特例。
http://www.pierceye.com/news/195546/

相关文章:

  • 九九9九九9视频在线观看优化网站结构一般包括
  • 网站缺点国外网站素材
  • 网站域名到期如何续费淘宝客绑定网站备案号
  • 什么是自建站常州 网站制作
  • 网站开发基础培训网站做推广有用吗
  • 音乐网站开发模板网页设计师用什么软件
  • 烟台优化网站重庆酉阳网站设计公司
  • 网站维护工作太原公司网站建设
  • 个性化网站建设报价案例查询网站
  • 淘宝网站框架项目管理软件下载
  • 网站建设课程内容如何优化关键词
  • 龙口网站建设公司电子商务网站建设 课件
  • 权威做网站的公司网站织梦程序改成wordpress
  • 用cms建网站容易吗平面设计网课平台哪个好
  • 网站设计怎么做好什么视频直播网站做挣钱
  • 西安 网站开发 招聘app开发难吗
  • 富阳网站建设 优帮云邯郸市商标设计品牌策划公司
  • 整站优化费用中国网新重庆
  • 找别人做网站wordpress怎么更改栏目权限
  • 珠海市建设工程质量监督检测站网站在小网站上做点击广告
  • 网站拉圈圈接口怎么做传媒网站设计
  • 淘宝客做的最好的网站盐山建网站
  • 西城企业网站建设深圳设计网站多少钱
  • 电子商务网站建设a卷网站建设厘金手指排名二一
  • 网站空间便宜网站的信息管理建设的必要性
  • 校级特色专业建设网站博达站群网站建设教程
  • 有没有做任务的网站吗网站首页开发
  • 公司名字变了网站备案济南网站建设公司哪个好点呢
  • 图书馆网站建设的规章制度企业免费招聘网站
  • 效果图网站大全系统优化的例子