Linux系统----信号(万字文章超级详细并且简单易学附有实操shell指令图及注释!)

news2024/12/28 20:18:43

绪论​
“Do one thing at a time, and do well.”,本章开始Linux系统其中信号是学习操作系统的基本下面将会讲到什么是信号、信号的多种产生方式、信号如何保存的、信号如何处理的、以及一些信号的细节。请添加图片描述话不多说安全带系好,发车啦(建议电脑观看)。


1.信号的概念

日常生活中有很多类似于信号的东西,如红绿灯、下课铃声、闹钟声音等
他相当于是一种信息,当在传递给你时你需要对他有认识并且知道其信号的意义是什么。这也表明了我们需要提前存储这些信号的概念,对传来的信号能进行识别处理。
信号就是一种向目标进程发送通知消息的一种机制。

所以一个进程处理信号需要:

  1. 当信号没有产生时,就已经知道该怎么处理这个信号
  2. 信号的到来我们并不清楚,所以信号到来时我可能正在进行的代码,所信号的产生和进程是异步
    1. 异步:可以理解为有两个执行流,各自执行自己的执行流(他跑他的、你跑你的)
  3. 进程能暂时保存好到来的信号

kill命令:

  1. 向指定进程发信号:kill signum(信号编号) pid(进程的pid)
    在这里插入图片描述
    具体serce的实现过程见 2.3信号能通过键盘输入产生的第一处代码!
  2. 查看所有信号:kill -l在这里插入图片描述
    1. 其中没有0号信号!(一个进程就两种退出信息,包括信号和退出码(0/非0)),因为0号信号表示没有收到信号,为了标识进程的正常结束。
    2. 没有32、33信号,普通信号:1 ~ 31、实时信号:34 ~ 64(收到信号时立即处理,不会出现信号丢失)。
    3. 所有信号都是一个个宏,所以信号可以用数字和字符串表示。
    下面对所有普通信号我们边学边认识

2.信号的产生

进程运行时分为:

  1. 前台(==./xxx ==在前台执行进程)
    1. 前台进程只能有一个(键盘只有一个)
    2. 能接受键盘用户输入的指令
  2. 后台(./xxx & 在后台启动该进程)
    1. 后台可以有多个进程
    2. 一般放耗时较长的任务
    3. 反之不能接受来自键盘的指令在这里插入图片描述
    其中:[1]表示后台任务编号;20103是进程的pid

通过指令查看后台任务:jobs在这里插入图片描述
后台任务不影响前台任务。
把进程从后台放回前台:fg 后台任务编号(jobs第一列)
在这里插入图片描述
通过上图分析得:
3. 把编号1的任务放到前台时发现pwd指令用不成了,说明原本的shell是一个前台进程,也证明前台进程只能有一个(1号任务代替了shell进程所以shell指令也随之用不了了)
4. ^ C表示的是:ctrl + c 能终止前台的进程
5. 当终止掉当前的前台进程后发现shell自动启动了,所以shell是能被自动的提到前台的进程!


2.1OS接收键盘的数据?通过中断技术

在这里插入图片描述
中断技术:很多外设都能对CPU(CPU的针脚)发送中断信息(光电信号)表示数据就绪,发送来的光电信息就会被8259(一个板子)给到CPU的针脚(有编号的,又称中断号),发送就绪信息保存到寄存器就能被程序读取,就从硬件到了软件就能去读取信息了。

所以每个进程都有中断向量表,数组的下标就和信号的编号是强相关的在这里插入图片描述


2.2处理信号方法有

  1. 忽略处理
  2. 默认处理
  3. 自定义捕捉处理

2.3通过系统调用发送信号

  1. 捕获信号的函数:
    sighandler_t signal(int signum, sighandler_t handler);
    头文件:#include <signal.h>
    参数:
    1. signum:是信号编号,每个信号都有对应的编号
    2. handler:是一个函数指针 typedef void (* sighandler_t)(int)
    1. 自定义的方法,这个函数的类型就是:void ( * )(int)
    其中我们需要知道该进程是否需要被终止掉,当需要终止时我们需要终止(exit(1 ))进程,否则进程就不会被终止,将导致进程一直运行
    2. 默认方法:SIG_DFL
    3. 忽略方法:SIG_IGN
    其中SIG_DFL、SIG_IGN本质就是宏(本质也是函数指针):
    把0强转为SIG_DFL,把1强转为SIG_IGN,最终在handler内部再转为int判断是否为0/1再分别处理。
    在这里插入图片描述

  2. 给指定进发送指定信号:
    int kill(pid_t pid, int sig);
    头文件: #include <sys/types.h>、#include <signal.h>
    参数:
    1. pid:进程pid
    2. sig:信号编号

  3. 给自己发送指定信号:
    1. int raise(int sig);
    头文件: #include <signal.h>
    参数:
    1. sig:信号编号在这里插入图片描述
    代码:

void handler(int signo)
{
    cout << "捕获到信号第: "<< signo << "号信号"<< endl;
    sleep(1);
    exit(1);
}

int main(int argc,char* argv[])
{
    signal(2,handler);

    raise(2);//给自己发送2号信号

    sleep(10);//若没有被终止则会休眠10s
}
  1. 正常终止当前进程:
    void abort(void);
    原理其实是:向自己发送六号信号(SIGABRT)
    头文件:#include <stdlib.h>

2.4信号能通过键盘输入产生

键盘的输入的数据可能是数据也可能是组合键表示信号

  1. ctrl+c:发送二号信号(SIGINT)终止进程自己,从键盘输入后通过中断技术在中断向量表中找到直接所要执行的方法,所以也表明二号信号会终止进程自己。
  2. 1.ctrl z:发送20号信号默认停止进程自己(放回到后台暂停,后台任务继续运行:bg 后台编号)
  3. == ctrl \:发送3号信号默认终止进程==

当我们从键盘输入就能产生信号给前台进程
证明:当键盘输入ctrl+c时就会产生二号信号给当前前台进程(图中^C就是从键盘输入的ctrl c
在这里插入图片描述
代码:

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;

void handler(int signo)//自定义处理函数handler
{
    cout << "接收到" << signo << "号信号" << endl;//打印是几号信号
}

int main()
{
    signal(2, handler);//捕获信号的函数,捕获到产生的信号并进行自定义处理函数handler
    while (true)
    {
        std::cout << "runing ... ,pid:" <<  getpid() <<std::endl;
        sleep(1);
    }
    return 0;
}

2.4.1信号的存储

操作系统向目标进程发送信号,实际上就是对pcb结构体中的位图成员进行改写
也就是pcb结构体中有一个位图的成员变量

struct task_struct
{
	//其余成员变量...
	uint32_t sigmap;//信号位图
}

进程中会有一个信号的位图的成员,存着接收到的信号!
位图:0000 0000 … 0000 (1 ~ 31普通信号31位)

所以实际上发信号其实就是OS找到进程pcb修改位图(也就是写入信号:如1号信号的写入0000 … 0001)!

所以总结来说每个进程都会有

  1. 函数指针数组(中断向量表)
  2. 信号位图
    有了这两个东西才能让进称对信号正常的接收和处理
    所以我们从键盘到信号生效,也就明了了:
    接收到中断信息后存储产生的信号,然后要再通过存储的信号在中断向量表中找到对应的方法就能让信号产生对进程的对应效果!
    而只有OS能发送信号(因为只有OS才能修改进程内的成员)

查看信号的默认处理方式:man 7 signal(处理方法只有三种:默认,忽略,自定义)
打开后往下翻找到
其中默认处理方法就是Action一列:
在这里插入图片描述
只有少量的信号不能被自定义捕捉,如:9号信号,即使写了自定义方法也仍然会执行默认的Term(终止进程)在这里插入图片描述
写一个通过中终端控制向目标进程发送信号的程序:
在这里插入图片描述

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string>
using namespace std;

int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        cout << "格式错误,因为为:./process signal processpid" << endl;
        exit(0);
    }
    int signum = stoi(argv[1]+1);//传进来的是-9
    int processpid= stoi(argv[2]);

    kill(processpid,signum);//+1跳过-得到数字
    return 0;
}
//编译时加上-std=c++11

SIGCONT 18:从后台放回前台继续运行

2.5异常产生信号:

  1. 当是除0错误时,会向该进程发送8号信号(SIGFPE)。在这里插入图片描述
    从虚拟地址中获取数据给到在内存中的进程,在通过进程给到CPU处理,CPU处理的过程中会有一个溢出标记位,记录是否出现了错误,若出现错误了OS就会处理该错误,也就是发送信号给当前进程。

2.访问野指针错误(段错误),会向进程发送11号信号(SIGSEGV)。在这里插入图片描述
附:我们在学异常时实质上:抛出异常的主要目的并不是处理异常,而是让程序执行流正常结束(还能打印错误)。

2.6软件条件产生信号:

  1. 管道软件产生信号:
    匿名管道,当读关闭掉时,就会向写端发送信号(SIGPIPE)终止。
    为什么说管道是一个软件:因为他并没有涉及到底层硬件的处理,管道空间属于是我打开的文件而文件的本质就是软件,通过OS判断管道文件的数据结构,当OS识别内核数据结构不满足条件时就会杀死掉进程。
    此处:判断管道软件的读端被关闭了(底层是通过读文件描述符查看,并没有查看CPU寄存器的硬件,所以也就是软件问题),当OS发现读端被关闭(管道引用计数为1了)所以软件条件不满足,所以就会OS就会发送SIGPIPE给进程终止。
  2. 闹钟软件产生信号:
    unsigned int alarm(unsigned int seconds);//闹钟函数
    功能:在second秒后给当前进程发送SIGALRM信号(14号信号),默认动作是终止进程。
    头文件:#include<unistd.h>
    闹钟本质上就是OS中描述的一个结构体(也就是一个软件),其中包含了许多成员变量(信息),结构体成员中肯定有一个存着时间的成员记录着当前闹钟的是否超时,超时了就会响(也就是发送信号给指向的进程)。(而这些闹钟的时间的存储就可能是在一个小堆中,通过不断查看堆顶元素是否超时,超时就pop出堆来进行闹钟的运行)。
    当这个时间一超时就会先闹钟中结构体存的成员:进程的pid发送信号。

alarm(0)是取消上一个闹钟,并返回他剩余的时间。

为什么软件也能产生信号:

本质上是因为OS是软硬件资源的管理者,软件出问题了时,软件也能产生信号给OS让他处理进程。

2.7操作系统的一些更加深入的底层知识:

  1. 在计算机内部有个纽扣电池一直在给某些硬件(计数器)供电记录时间,所以最后开机时在计算处正确的时间。
  2. 所有用户的行为最终都是以进程的形式在OS中表现的(打开如何东西)。所以操作系统只用把进程调度好,就能完成所有的用户任务。
  3. CMOS,能向CPU周期性的,高频率的发送时钟中断。(CPU的主频概念的产生)
  4. 硬件CMOS通过向CPU内发送超高频中断信息才让操作系统运行起来并执行其内部的代码(调度方法),所以操作系统本质也是代码程序也是通过硬件发送中断信息才能被执行对应的程序的!。在这里插入图片描述
  5. OS的本质就是个死循环while(1) { pause();//暂停进程,只到来信号 },来保证操作系统在开机后一直运行。

总结:产生信号的方式可以有很多,但是发送信号只能由OS发送(写信号)


2.8Core Dump(核心转储)

core dump核心转储就是把进程退出的原因存储进硬盘,Core Dump会形成一个 以在运行时代码中的崩溃处的核心上下文数据 的文件(也就是问题存储进磁盘中),在文件下形成core.pid命名的文件(指定进程pid)。

查看基本配置项(包括了core dump产生的文件大小):
指令:ulimit -a
其中core file size项就表示了当前Core Dump产生文件的大小
ulimit -a 修改 core file size项(并且这个修改只针对当前的shell进程)
在这里插入图片描述
在gdb模式下查看core file文件内容
通过在gdb模式下:core-file + 对应的文件名直接查看转储错误的地方
在这里插入图片描述

core dump保存东西较多文件比较大,并且每当运行一次程序出现问题就会产生一个,所以为了防止一些特殊进程会不断重启(异常,重启循环不断的生成core file文件),所以云服务器就默认关闭了,所以若想启动的话就去修改基础配置中core file的文件大小即可。

而Action中:Term终止和Core终止的区别:
报Core错误的才是真正的异常错误,并且Core比较严重,用户还需自己进一步排查代码哪里错(Term反之),并且若有错误并且开启了core file size非0则会生成core file文件。


重要知识总结:
1. 信号的产生都是经过操作系统的,OS是进程的管理者
2. 信号并不是立即处理,在合适的时候处理
3. 信号需要被暂时记录下来(位图)
4. 进程还没收到信号就知道如何处理信号(维护一张函数指针数组表)
5. OS本质并不是发信号,而是写信号!


3.信号的保存(阻塞)

信号的常见名称(概念):

  1. 信息递达实际执行信号的处理动作(也就是合适的时候处理信号
    处理信号的方法:
    1. 信号的忽略
    2. 自定义捕捉
    3. 信号的默认
      其中可以用signal函数:
      sighandler_t signal(int signum, sighandler_t handler);(可见于2.3.1节处)。
  2. 信号未决信号从产生到递达之间的状态(也就回把信号保存在pcb的信号位图中,是还未被执行时)。(理解为:记录了老师布置的作业,此时只是记录还未处理)。
  3. 信号阻塞:阻塞时是表示一直处于未决状态,暂时不递达,直到解除对信号的阻塞。
  4. 忽略:本身没有阻塞,他是一种未决状态(属于递达的一种,没来得及处理),所以说信号未决,信号不一定是阻塞。

即使没有收到某个信号,也能设置信号的状态,

进程结构体中会有三张表
在这里插入图片描述
也就是上面阻塞、未决、递达三个概念对应的表

  1. pending(未决位图表):OS发送信号时本质就是在此处写信号
  2. handler(递达的函数指针数组):对应信号的处理方法,相当于signal的第二个参数。
  3. block(阻塞位图表):是否对特定的信号进行屏蔽(阻塞)

因为每个进程都提前有了上面的三个表,所以也就让进程能认识信号(处理信号) ,其中每一行就对应着一个信号的处理。
对于上面的表,每个信号当产生信号时OS修改pending位图为1,当处理后pending由1变回0,若block为1就被阻塞了无法执行(即使pending为1),一旦block阻塞解除了就需要立即递达!
允许OS向进程发送多次信号,但其实本质也只记录一次信号(普通信号,实时信号则相反是通过一个队列来处理每一次信号)。

3.1sigset_t(信号集)

sigset_t称为信号集。在未决表中每个信号只有一个bit的未决标志,非0即1,同理阻塞也是这样。因此未决和阻塞标志可以用相同的数据类型sigset_t来存储,这个类型由系统提供可以表示每个信号的有效或无效状态,所以还能把block表和pending表称为阻塞信号集和未决信号集,block表还能称为信号屏蔽字(Signal Mask)

3.2信号集操作函数

下面是对set信号集进行处理的函数

  1. 在信号集set中全置空0:int sigemptyset(sigset_t *set);
  2. 在信号集set中全充满1:int sigfillset(sigset_t *set);
  3. 在信号集set中添加信号signo(比特位处置为1):int sigaddset (sigset_t *set, int signo);
  4. 在信号集中删除指定信号signo(置为0):int sigdelset(sigset_t *set, int signo);
  5. 判断指定的signo信号是否在该set信号集中:int sigismember(const sigset_t *set, int signo);

头文件:#include <signal.h>

而其中sigset_t其实也就是个类型,由系统提供为了形参的一个结构体方便我们的用于系统调用。


3.3读取或修改信号屏蔽字(阻塞信号集)的函数:

int sigprocmask(int how, const sigset_t * set, sigset_t * oset);
头文件:#include <signal.h>
返回值:若成功则为0,若出错则为-1
参数:

  1. how有三个选项标识符
    1. SIG_BLOCK:set包含了我们希望添加到当前信号屏蔽字的信号,相当mask=mask|set
    2. SIG_UNBLOCK:set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set(把原本的位图mask按位与上传进来的set的取反,意味着在set中的就会从mask中去除
    3. SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask=set(也就是把传进来的位图set(参数二)替换掉原有的位图)
  2. set根据参数一,来自定义自己的set位图表传递进去
  3. oset是一个输出型参数,返回老的mask位图(老的block表的位图)

在信号屏蔽时,有两个信号时无法进行屏蔽的,他们称为管理员信号
分别是9号和19号信号(其余可自行测试是能被屏蔽的)
测试代码:

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string>
using namespace std;
void handler(int signo)
{
    cout << "signo:"<<signo << endl;
}
int main()
{
    cout << "getpid:" << getpid() << endl;
    signal(2,handler);
    sigset_t block,oblock;

    sigemptyset(&block);
    sigemptyset(&oblock);

    for(int signo = 1; signo < 32 ;signo++)
    {
        sigaddset(&block,signo);
    }

    sigprocmask(SIG_SETMASK,&block,&oblock);//此处才修改了系统的信号屏蔽字

    cout << "已经屏蔽所有信号" << endl;

    while(true)
    {
        sleep(1);
    }

    return 0;
}

指令操作以及结果:
在这里插入图片描述
在这里插入图片描述

3.3读取未决信号集的函数:

#include <signal.h>
sigpending(sigset_t set)
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1
set也就还是一个输出型参数
实操代码及结果:

void handler(int signo)
{
    cout << "signo:"<<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;
    sigset_t set,oset;
    sigemptyset(&set);
    sigemptyset(&oset);

    sigaddset(&set,2);

    sigprocmask(SIG_BLOCK,&set,&oset);

    sigset_t pending;
    int cnt = 0;
    while(true)
    {
        sigpending(&pending);
        PrintPending(pending);

        sleep(1);

        if(cnt == 5)
        {
            cout<< "解除屏蔽" <<endl;
            sigprocmask(SIG_SETMASK,&oset,nullptr);
        }
        cnt++;
    }

    return 0;
}

在这里插入图片描述
信号在要处理方法前pending位图就会先变成0后才执行调用对应方法


4.信号的处理(递达)

4.1信号在合适的时候处理

进程从内核态返回到用户态的时候,进行信号的检测和处理,也就是:进程从内核态返回用户态的时候,进行信号的检测和信号的处理
在这里插入图片描述

  1. 用户态是一种受控的状态,能够访问的资源时有限的
  2. 内核态是一种操作系统的工作状态,能够访问大部分系统资源
  3. 用户只能访问用户空间(0~3G)
  4. 内核态:可以让用户以OS的身份访问内核空间(3~4G)

其中系统调用就是身份的变化在底层工作原理如下图:

  1. OS共用一个内核页表,所以无论调度那个进程,CPU都能很好的找到操作系统。
  2. 所有的代码的执行都能在自己的进程地址空间内执行通过地址 跳转的方式,进行调用和返回!!
    在这里插入图片描述
    信号处理的流程:
    在这里插入图片描述
    它分为四步,能忽略细节的看成:
    在这里插入图片描述
    通过上图我们能总结出来:
    1. 会有四次状态的交换(进程从内核态返回用户态的时候,进行信号的检测和信号的处理
    2. 即使没有系统调用也同样会有状态的改变(进程切换过程中会从内核到用户来执行对应的代码)
      在这里插入图片描述

信号捕捉处理函数(针对于handler表):

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

  1. 要处理的信号:signum
  2. 新修改后的方法:act
  3. 输出型参数,返回修改前的act

返回值:调用成功则返回0,出错则返回- 1
头文件:#include<signal.h>

act类型是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);
};

成员:
1. sa_handler:就是对应的信号的handler方法
2. sa_mask:处理某个信号时,用来屏蔽额外的信号

3. 其他sa_sigaction、sa_flags、sa_restorer暂时不考虑

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。
总结就是:某一种信号在执行时,若再次产生该信号,产生的信号将被屏蔽 不被处理,他并不会多次处理。
如果要不止屏蔽执行的信号,而且还要屏蔽其他信号,那么就设置sa_mask同步来屏蔽其余的信号。

实践证明sa_handler就是改变handler表内的方法:

void handler(int signo)
{
    cout << "signo:"<<signo << endl;
}

int main()
{
    struct sigaction act,oact;

    act.sa_handler = handler;//修改act的sa_handler

    sigaction(2,&act,&oact);//对对应signum信号通过act修改handler表
    while(true)
    {
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
实践证明sa_mask的使用:当把三号信号加入sa_mask中后,在执行二号信号时,不仅会屏蔽执行着的二号信号,还会屏蔽sa_mask中的三号信号。
在这里插入图片描述
代码:

void Print(const sigset_t& pending)
{
    for(int signo = 31 ; signo > 0 ;signo--)
    {
        if(sigismember(&pending,signo))//查看signo信号是否在pending位图中
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

void handler(int signo)
{
    cout << "signo:" << signo << endl;
    while(true)
    {
        sigset_t pending;
        sigpending(&pending);//返回得到pending位图
        Print(pending);
        sleep(1);
    }
}


int main()
{
    cout << "getpid:" << getpid() << endl;

    struct sigaction act,oact;

    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask,3);//对3号信号进行了屏蔽
    
    act.sa_handler = handler;//修改act的sa_handler


    sigaction(2,&act,&oact);//对对应signum信号通过act修改handler表
    
    while(true) sleep(1);
    return 0;
}

若同时存在好几个信号被堵塞,那么当解除堵塞后,将会在一次信号检测过程中将多个信号进行处理。在这里插入图片描述
附:
在这里插入图片描述
系统调用的原理:将系统调用编号存在寄存器,通过这个编号后去函数调用的函数指针数组中去找对应方法。

5.信号的其他补充话题

5.1可重入函数

  1. 可重入函数:允许被多个执行流重复进入
  2. 不可重入函数:不允许被多个执行流重复进入(百分之90自定义的函数都是不可重入函数)

下图中出现的问题就是因为多执行流重入而导致头插错误(在执行完1后,收到一个信号后进行立即处理,并且里面也有插入函数从而导致错误,node2找不到内存泄漏),所以下面的函数就是不可重入函数。
在这里插入图片描述
一般而言只要函数用到了全局的变量或者容器数据结构,而我们调用的malloc/free他们都是通过全局链表来管理堆的,还有常用的标准I/O库函数(printf…),他们都是不可重入函数方式使用全局数据结构。

5.2volatile

通过下面代码产生的问题来解释volatile的用处:

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);
    return 0;
}

在这里插入图片描述
在这里插入图片描述
此处因为:在main函数中,flag只进行了读,所以当提升优化级别时,将会把flag的存进CPU寄存器的过程,只进行一次(优化),所以当我们产生信号时修改的仅仅只是内存里的,CPU内的将不会改变,所以将不能成功退出。
所以为了避免这种情况我们可以对变量加上volatile,让其内存可见性(这样当内存的变量改变时,CPU内部也会跟着改变)

volatile int flag = 0;//修改即可!!!

在这里插入图片描述

5.3SIGCHLD信号

子进程退出会僵尸需要父进程wait他,他退出时会向父进程发送信号(SIGCHLD信号)

证明代码:

#include<sys/wait.h>//wait的头文件

void handler(int signo)
{
    cout << "signo:" << signo << endl;
}

int main()
{
    signal(SIGCHLD,handler);
    
    pid_t id= fork();
    if(id == 0)
    {
        //子进程
        sleep(5);
        exit(1);
    }

    int cnt = 10;
    while(cnt--)
    {
        cout << cnt << endl;
        sleep(1);
    }
    wait(NULL);

    return 0;
}

在这里插入图片描述
附:
若要一次性退出多个进程:
==原理是在接收到信号后handler函数内进行子进程的循环等待,这样就能在接收到信号后处理。==但注意的是可能同时处理多个子进程,因为子进程可能同时结束并发送SIGCHLD信号,回顾前面信号当同时发来多个相同的信号时,他不会同时处理相同的信号(会被屏蔽),所以通过循环的方式来处理,让同时传进来的信号的子进程都被等待成功。

void handler(int signo)
{
    cout << "signo:" << signo << endl;

    pid_t id;
    while(id = waitpid(-1,nullptr,WNOHANG))//-1接收所有子进程,WNOHANG非阻塞,返回<=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)
        {
            //子进程
            sleep(1);
            exit(1);
        }
    }

    int cnt = 15;
    while(cnt--)
    {
        cout << cnt << endl;
        sleep(1);
    }
    wait(NULL);

    return 0;
}

在这里插入图片描述
但其实Linux操作系统上对SIGCHLD信号进行了特殊的处理,Linux支持手动忽略SIGCHLD,后来所有子进程都不要父进程等待了,退出会自动回收。
具体如下:


int main()
{
    signal(SIGCHLD,SIG_IGN);//Linux支持手动忽略SIGCHLD,后来所有子进程都不要父进程等待了,退出会自动回收
    
    for(int i = 0; i < 10;i++)
    {
        pid_t id= fork();
        if(id == 0)
        {
            //子进程
            // sleep(5);
            exit(1);
        }
    }

    int cnt = 10;
    // while(cnt--)
    // {
    //     cout << cnt << endl;
    //     sleep(1);
    // }
    while(true);
    wait(NULL);

    return 0;
}

在这里插入图片描述


本章完。预知后事如何,暂听下回分解。

如果有任何问题欢迎讨论哈!

如果觉得这篇文章对你有所帮助的话点点赞吧!

持续更新大量Linux细致内容,早关注不迷路。

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

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

相关文章

AS-V1000 视频监控平台,如何实现设备上线、下线时产生告警,及时通知管理人员

目录 一、客户需求 &#xff08;一&#xff09;客户需求 &#xff08;二&#xff09;掌握设备状况的意义 1、实时故障检测与预警 2、提升系统可靠性 3、优化资源配置 4、增强安全保障 5、提升管理效率 二、产品介绍 三、系统配置 &#xff08;一&#xff09;实现告警…

[网络安全] apt攻击是什么?

什么是APT攻击&#xff1a;APT攻击的主要特征包括&#xff1a;APT攻击的防御措施&#xff1a;零基础入门学习路线视频配套资料&国内外网安书籍、文档网络安全面试题 什么是APT攻击&#xff1a; APT&#xff08;Advanced Persistent Threat&#xff0c;高级持续性威胁&…

大小写不规范引起的LVS问题

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 往期文章链接: LVS常见问题解析 综合网表不规范,大小写混用常导致LVS问题,比如两个端口clk和CLK只有大小写区别,PR工具是可以识别为两个端口的,只不过Calibre LVS默认不区分大小写,会报错。 …

mac安装java

在 macOS 上配置 Java 环境变量是相对简单的。你需要做的是设置 JAVA_HOME 环境变量&#xff0c;并将 bin 目录添加到 PATH 变量中。本篇是最详细的教程&#xff0c;细化每个步骤过程&#xff0c;保姆级的教程&#xff01; 1. 下载JDK安装包 到oracle官网下载适合的JDK安装包…

外包干了4个月,技术退步明显

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01; 而我已经在一个企业干了四年的功能…

关于前后端一体项目SpringSecurity框架登陆失效,HTTPS重定向登陆页面异常的问题

现有环境是基于SpringBoot 2.6.8&#xff0c;然后是前后台一体化的项目。 安全框架使用的是内置版本的SpringSecurity。 场景&#xff1a;用户登陆&#xff0c;系统重启导致用户的session失效。但前端并没有跳转到对应的登录页&#xff0c;在HTTP的环境下可以正常跳转&#x…

Linux——DNS的配置和使用

一、DNS 域名服务器&#xff0c;实现IP和域名的转换 DNS 协议运行在 UDP 协议之上&#xff0c;使用端口号 53 2.结构 DNS 的命名空间的结构如下&#xff1a; 1. 根域名&#xff08; Root Domain &#xff09;&#xff1a; 根域名位于 DNS 命名空间的顶部&#xff0c;它表示…

[leetcode] 58. 最后一个单词的长度

文章目录 题目描述解题方法倒序遍历java代码复杂度分析 题目描述 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 1&#xff1a…

OpenHarmony实战开发—进程间通讯

版本&#xff1a;v3.2 Beta5 进程模型 OpenHarmony的进程模型如下图所示&#xff1a; 应用中&#xff08;同一包名&#xff09;的所有UIAbility、ServiceExtensionAbility、DataShareExtensionAbility运行在同一个独立进程中&#xff0c;即图中绿色部分的“Main Process”。…

leaftjs+turfjs+idw纯前端实现等值面绘图+九段线

最近有个绘制等值面图的需求。我们一般的实现路径是&#xff1a; 1.后台绘图&#xff0c;用surfer绘制好&#xff0c;给前端调用叠加到地图。 2.后台用python绘图&#xff0c;绘制好给前端调用&#xff0c;叠加到地图。 3.后台进行插值计算、地图裁剪、最终生成geojson文件或…

电商技术揭秘三十四:智能风控业务架构浅析

相关系列文章 电商技术揭秘相关系列文章合集&#xff08;1&#xff09; 电商技术揭秘相关系列文章合集&#xff08;2&#xff09; 电商技术揭秘二十八&#xff1a;安全与合规性保障 电商技术揭秘二十九&#xff1a;电商法律合规浅析 电商技术揭秘三十&#xff1a;知识产权保…

vue 3 —— 笔记(模板语法,响应式变量)

模板语法&#xff1a; Vue 使用一种基于 html 的模板语法&#xff0c;使我们能声明式将其组件实例绑定到呈现的 dom 上 文本插值 基础数据绑定形式 双大括号 会替换相应组件实例 msg 属性的值 原始html 双大括号会将数据解释为纯文本 不是html 想插入html 使用 v-html 指令 &…

短距离无线通信-Zigbee

阅读引言&#xff1a; 最近在复习之前做过的项目&#xff0c; 所以向将zigbee这个协议分享出来&#xff0c; 一方面可以给需要的人看到&#xff0c; 一方面也方便自己整理zigbee的知识。 目录 一、什么是 ZigBee? 二、Zigbee 通信频段和信道 三、Zigbee协议和Zigbee协议栈 …

ATFX汇市:日本央行并未连续加息,日元剧烈贬值

ATFX汇市&#xff1a;今日11:22&#xff0c;日本央行公布4月利率决议结果&#xff0c;宣布维持0~0.1%的基准利率不变。日元应声贬值&#xff0c;三十分钟内&#xff0c;USDJPY从155.42猛涨至155.89&#xff0c;涨幅47基点。今日14:30&#xff0c;日本央行行长植田和男召开货币政…

类与对象(四)

目录 1.构造函数 1.1初始化列表 1.2 隐式类型转换 2.静态成员 2.1 静态成员变量 2.2静态成员函数 3.友元 3.1 友元函数 3.2 友元类 4.内部类 5.匿名对象 6.拷贝对象时的一些编译器优化 1.构造函数 1.1初始化列表 我们在将构造函数的时候讲过构造函数是对一个对象整体的…

HarmonyOS ArkUI实战开发-NAPI方法扩展

在前 3 小结笔者简单介绍了 NAPI 工程并对生成的源码进行了简单介绍&#xff0c;本节笔者在前 3 小节的基础上对 NAPI 工程做个扩展&#xff0c;再额外添加一个计算 MD5 的方法 md5()。 声明md5方法 在 index.d.ts 文件中声明一个 md5() 方法&#xff0c;该方法接收一个 stri…

自己搭建的大疆无人机RTMP流媒体服务延迟太大

流程&#xff1a;无人机摄像头->图传->遥控器->流媒体服务器->取流播放&#xff0c;延迟有10秒来的&#xff0c;大家有没有什么好的方案。

思考(1)

思考&#xff1a; 1. windows登录的明文密码&#xff0c;存储过程是怎么样的&#xff0c;密文存在哪个文件下&#xff0c;该文件是否可以打开&#xff0c;并且查看到密文 Windows登录的明文密码&#xff1a;是通过LSA&#xff08;Local Security Authority&#xff09;进行存储…

用过最佳的wordpress模板

西瓜红&#xff0c;作为一种充满活力和激情的颜色&#xff0c;总是能给人留下深刻的印象。当这种鲜艳的色彩与经典的设计元素相结合时&#xff0c;就能打造出一款既时尚又实用的WordPress企业模板。今天&#xff0c;我们向您隆重推荐这款西瓜红经典配色WordPress企业模板。 这…

HTML批量文件上传方案——图像预览方式

作者:私语茶馆 1.HTML多文件上传的关键方案 多文件上传包括:文件有效性校验,文件预览、存储和进度展示多个方面,本章节介绍的是文件预览的实现方案。 2.文件上传前预览 2.1.效果 选择文件前: 选择文件后: 2.2.CSS文件代码 StorageCenter.css代码 html {font-family:…