一个重要且实用的signal---SIGCHLD

https://blog.csdn.net/lyztyycode/article/details/78150805

SIGCHLD(修改)

因为笔者之前的文章里面有错误,今天发现,立马做个修改。在下面我的一段关于sigchld信号相对于直接调用wait函数的好处时,我说调用wait函数要一直检测子进程是否执行完其实是错误的, wait是阻塞函数,当主进程调用wait函数的时,主进程处于阻塞状态,并没有一直检测的动作,他只是在等待,假设我们有三个子进程(编号1,2,3)假设2,3进程先执行完,然而1号子进程还没有执行完,那么主进程的wait就一直处于阻塞状态,这时候2,3可能产生僵尸进程。这里sigchld和wait的区别就很明显了。
先来看看信号的基本概念:
信号kill-l查看linux信号及其宏定义编号,其中1~31非实时编号(发送的信号可能丢失,不支持信号排队),31~64实时信号,发送的信号都会被接收(支持信号排队)
信号的定义在/usr/include/bits/signum.h
1.信号是软件中断
2.信号是异步信号
3.信号的来源:
(1)、硬件来源:主要是硬件的驱动产生,如键盘,鼠标等
(2)、软件来源:主要是一些信号函数、比如kill()、raise()、alarm()、setitimer()等函数,软件来源包括一些非法运算等操作,软件设置条件(gdb调试),信号由内核产生
#信号的处理
进程会采取三种方式响应和处理信号
1.忽略信号,sigkill和sigstop永远不被忽略,忽略硬件异常、进程启动时,sigusr1和sigusr2被忽略
2.执行默认操作
3.捕获信号。告诉内核信号出现时调用自己的处理函数,SIGKILL和SIGSTOP不能被捕获
#信号登记
void(*signal(int signo,void (*func)(int))(int)
signo--要登记的信号编号或者信号宏
func--信号处理函数指针、忽略信号(SIG_IGN)、默认信号(SIG_DEF)
今天我就不针对每个信号详细介绍了,你也没必要知道那么多信号,今天介绍一个很重要的信号,SIGCHLD这个信号的作用如下:
SIGCHLD 子进程状态发生变化产生该信号(子进程运行结束)父进程调用wait函数,回收子进程的进程表项,task_struct结构体。有了这个信号父进程不需要处于阻塞状态,任然可以干其他事情,当子进程结束时发送一个SIGCHLD信号给父进程,父进程调用wait回收子进程,避免僵尸进程的产生,提高了资源利用率。

再了解这个信号之前,先来简单了解一下wait()函数:

pid_t wait(int *status)//状态
<unistd.h>
<sys/wait.h>
等待子进程退出并回收,防止僵尸进程(子进程运行结束,但是内核中的task_struct没有释放)产生,凡是调用wait()就会阻塞,父进程等待,定时检查子进程是否执行完毕
返回子进程id
pid_t waitpid(pid_t pid, int *status, int options);
成功返回子进程id,这是wait的非阻塞版本。
wait和waitpid区别:
1.在一个子进程终止前,wait使调用者阻塞
2.waitpid有一个选择项,可以使调用者不阻塞
3。waitpid可以等待指定的一个子进程,wait等待所有的子进程,返回任意一个种植的子进程状态。
子进程在运行中有暂停信号如果想要显示暂停信号的信号码不能使用wait()要用waitpid()
waitpid的宏WNOHANG(非阻塞)WUNTRACED(监听信号)

我们处理僵尸进程有两种方式:
1.kill -9 父进程 让init进程回收僵尸进程
2.wait() 和 waitpid()让父进程等待回收子进程
下面我们来使用信号实现解决和避免僵尸进程的第三种的方式:
[cpp] view plaincopy
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<sys/wait.h>  
  4. #include<signal.h>  
  5. void sig_handler(int signo)  
  6. {  
  7.     printf("child process  %d stop\n", signo);  
  8.   
  9. }  
  10. void out(int n)  
  11. {  
  12.     for(int i = 0; i < n; ++i)  
  13.     {  
  14.         printf("%d out %d\n", getpid(), i);  
  15.         sleep(1);  
  16.     }  
  17. }  
  18. int main(void)  
  19. {  
  20.     if(signal(SIGCHLD, sig_handler) == SIG_ERR)  
  21.     {  
  22.         perror("sigchld error");  
  23.         exit(1);  
  24.     }  
  25.     pid_t pid = fork();  
  26.     if(pid < 0)  
  27.     {  
  28.         perror("fork error");  
  29.         exit(1);  
  30.     }  
  31.     else if(pid > 0)  
  32.     {  
  33.         out(100);  
  34.     }  
  35.     else  
  36.     {  
  37.         out(20);  
  38.     }  
  39.     return 0;  
  40. }  

这段代码使用signal系统调用来捕获信号,我们在signal里面注册了SIGCHLD信号,程序中我们让子进程现执行完,然后捕获子进程执行完毕的信号。
下面是运行结果部分截图:

这里进程pid3546为父进程3547为子进程
我们再次运行程序来观察程序的运行状态,把程序编译gcc signal_sigchld.c -o child
运行程序./child
使用命令ps -aux|grep child 来观察程序运行状态,下面是结果截图
你可以看到父子进程在子进程运行到20以前都处于S+即运行状态,当子进程到达20的时候,signal捕获到子进程退出的信号SIGCHLD
这时候子进程状态变为Z+即僵尸进程。

所以我们说了那么多,为什么SIGCHLD没有处理这个僵尸进程呢,这里我们要搞清楚,SIGCHLD只是子进程在运行结束的时候产生的一i个信号,我们要想处理这个僵尸状态,还是要用到上面说的两种方式。最好就是父进程调用wait(),你可能有要问,既然都要用到wait,那抹干吗多此一举使用信号呢?首先要知道父进程调用wait以后处于阻塞状态,父进程不能干其他事情,使用效率降低,资源利用率低下,增加了开销,而调用信号以后,当子进程执行完毕以后,自动产再生一个信号给父进程,父进程收到信号以后就调用wait挥手子进程没有释放的资源。这样我的感觉就是子进程化被动为主动。父进程的工作也轻松了不少,可以做自己想做的事情。

所以为了避免僵尸进程的产生我们修改上面的代码中的sig_handler函数如下:
[cpp] view plaincopy
  1. void sig_handler(int signo)  
  2. {  
  3.     printf("child process  %d stop\n", signo);  
  4.     wait(0);  
  5. }  
当父进程捕获到SIGCHLD后调用wait。
按照上述步骤重新编译,运行,用ps观察进程运行状态:
上面是子进程运行到20之前,下面看子进程运行完毕,父进程捕获到SIGCHLD以后

这里你发现子进程没有显示,是因为紫禁城已经被回收释放掉了。
这就是处理僵尸进程的第三种方式。
也是一种异步处理方式。



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383715.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python3函数和代码复用

函数的定义 def 函数名([参数列表]):注释函数体注意事项 函数形参不需要声明类型&#xff0c;可以使用return语句在结束函数执行的同时返回任意类型的值&#xff0c;函数返回值类型与return语句返回表达式i的类型一致 即使该函数不需要接受任何参数&#xff0c;也必须保留一堆…

一文说尽C++赋值运算符重载函数(operator=)

http://www.cnblogs.com/zpcdbky/p/5027481.html在前面&#xff1a;关于C的赋值运算符重载函数(operator)&#xff0c;网络以及各种教材上都有很多介绍&#xff0c;但可惜的是&#xff0c;内容大多雷同且不全面。面对这一局面&#xff0c;在下在整合各种资源及融入个人理解的基…

Python a和a[:]的区别

简单来讲a[:]是深复制&#xff0c;a是浅复制&#xff0c;相当于赋值a的话是赋值了指针&#xff0c;赋值a[:]相当于复制了a对应的那段空间 例如&#xff1a; a [1,1,1,1,1,1]for x in a:if x1:a.remove(x)print(a)运行结果&#xff1a; remove操作是移除序列中第一个x元素。…

Linux系统【二】exec族函数及应用

文件描述符 文件描述符表是一个指针数组&#xff0c;文件描述符是一个整数。 文件描述符表对应的指针是一个结构体&#xff0c;名字为file_struct&#xff0c;里面保存的是已经打开文件的信息 需要注意的是父子进程之间读时共享&#xff0c;写时复制的原则是针对物理地址而言…

白话C++系列(27) -- RTTI:运行时类型识别

http://www.cnblogs.com/kkdd-2013/p/5601783.htmlRTTI—运行时类型识别 RTTI&#xff1a;Run-Time Type Identification。 那么RTTI如何来体现呢&#xff1f;这就要涉及到typeid和dynamic_cast这两个知识点了。为了更好的去理解&#xff0c;那么我们就通过一个例子来说明。这个…

使用头文件的原因和规范

原因 通过头文件来调用库功能。在很多场合&#xff0c;源代码不便&#xff08;或不准&#xff09;向用户公布&#xff0c;只 要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库 功能&#xff0c;而不必关心接口怎么实现的。编译器会从库中提取相应…

转圈踢人问题

https://www.cnblogs.com/lanxuezaipiao/p/3339603.html 有N个人围一圈依次报数&#xff0c;数到3的倍数的人出列&#xff0c;问当只剩一个人时他原来的位子在哪里&#xff1f; 解答&#xff1a;经典的转圈踢人问题&#xff0c;好吧专业一点&#xff0c;约瑟夫环问题&#xff0…

Python3常用数据结构

Python3中有三种组合数据类型&#xff0c;分别为&#xff1a; 序列类型&#xff1a;字符串&#xff08;str&#xff09;、元组&#xff08;tuple&#xff09;、列表&#xff08;list&#xff09;集合类型&#xff1a;集合&#xff08;set&#xff09;映射类型&#xff1a;字典…

TCP第四次挥手为什么要等待2MSL

当客户端进入TIME-WAIT状态的时候(也就是第四次挥手的时候)&#xff0c;必须经过时间计数器设置的时间2MSL(最长报文段寿命)后&#xff0c;才能进入关闭状态&#xff0c;这时为什么呢&#xff1f;&#xff1f;&#xff1f; 这最主要是因为两个理由&#xff1a; 1、为了保证客户…

计算机网络【一】概述+OSI参考模型

网络概述 局域网:覆盖范围小(100m以内)&#xff0c;自己花钱买设备&#xff0c;带宽固定(10M,100M,1000M)&#xff0c;自己维护&#xff08;接入层交换机直接连接电脑、汇聚层交换机直接连接接入层交换机&#xff09; 广域网:距离远&#xff0c;花钱买服务&#xff0c;租带宽&…

单链表逆序的多种方式

https://www.cnblogs.com/eniac12/p/4860642.htmltemplate<class T> void List<T>::Inverse() {if(first NULL) return;LinkNode<T> *p, *prev, *latter; p first->link;   // 当前结点prev NULL;   // 前一结点l…

socket编程 -- epoll模型服务端/客户端通信的实现

https://blog.csdn.net/y396397735/article/details/50680359 本例实现如下功能&#xff1a; 支持多客户端与一个服务端进行通信&#xff0c;客户端给服务端发送字符串数据&#xff0c;服务端将字符串中小写转为大写后发送回客户端&#xff0c;客户端打印输出经转换后的字符串。…

Python3 面向对象程序设计

类的定义 Python使用class关键字来定义类 class Car:def infor(self):print("This is a car") car Car() car.infor()内置方法isinstance()来测试一个对象是否为某个类的实例 self参数 类的 所有实例方法都有一个默认的self参数&#xff0c;并且必须是方法的第一…

计算机网络【二】物理层基础知识

计算机网络的性能 速率&#xff1a;连接在计算机网络上的主机在数字信道上传送数据位数的速率&#xff0c;也成为data rate 或bit rate&#xff0c;单位是b/s,kb/s,Mb/s,Gb/s。 我们平时所讲的宽带的速度是以字为单位的&#xff0c;但是实际中应用一般显示的是字节 &#xff0…

Linux网络编程——tcp并发服务器(多进程)

https://blog.csdn.net/lianghe_work/article/details/46503895一、tcp并发服务器概述一个好的服务器,一般都是并发服务器&#xff08;同一时刻可以响应多个客户端的请求&#xff09;。并发服务器设计技术一般有&#xff1a;多进程服务器、多线程服务器、I/O复用服务器等。二、…

求序列第K大算法总结

参考博客&#xff1a;传送门 在上面的博客中介绍了求序列第K大的几种算法&#xff0c;感觉收益良多&#xff0c;其中最精巧的还是利用快速排序的思想O(n)查询的算法。仔细学习以后我将其中的几个实现了一下。 解法 1&#xff1a; 将乱序数组从大到小进行排序然后取出前K大&a…

Linux网络编程——tcp并发服务器(多线程)

https://blog.csdn.net/lianghe_work/article/details/46504243tcp多线程并发服务器多线程服务器是对多进程服务器的改进&#xff0c;由于多进程服务器在创建进程时要消耗较大的系统资源&#xff0c;所以用线程来取代进程&#xff0c;这样服务处理程序可以较快的创建。据统计&a…

计算机网络【三】物理层数据通信

物理层传输媒介 导向传输媒体&#xff0c;比如光纤和铜线 双绞线&#xff08;屏蔽双绞线STP 五屏蔽双绞线UTP&#xff09;电线扭曲在一起可以降低互相之间的电磁干扰 同轴电缆 (50欧姆的基带同轴电缆&#xff0c;75欧姆的宽带同轴电缆) 10M和100M网络只使用了四根线&#xf…

02_算法分析

02_算法分析 0.1 算法的时间复杂度分析0.1.1 函数渐近增长概念&#xff1a;输入规模n>2时&#xff0c;算法A1的渐近增长小于算法B1 的渐近增长随着输入规模的增大&#xff0c;算法的常数操作可以忽略不计测试二&#xff1a;随着输入规模的增大&#xff0c;与最高次项相乘的常…

Linux网络编程——I/O复用之select详解

https://blog.csdn.net/lianghe_work/article/details/46506143一、I/O复用概述I/O复用概念&#xff1a;解决进程或线程阻塞到某个 I/O 系统调用而出现的技术&#xff0c;使进程不阻塞于某个特定的 I/O 系统调I/O复用使用的场合&#xff1a;1.当客户处理多个描述符&#xff08;…