【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

这篇具有很好参考价值的文章主要介绍了【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🐱作者:一只大喵咪1201
🐱专栏:《Linux学习》
🔥格言:你只管努力,剩下的交给时间!
【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
信号的产生以及详细讲解了,有兴趣的小伙伴可以去看看,传送门。接下来介绍信号的保存和信号处理。

首先介绍几个新的概念:

  • 信号递达(Delivery):实际执行信号的处理动作。
  • 信号未决(Pending):信号从产生到递达之间的状态。
  • 信号阻塞(Block):被阻塞的信号产生时将保持在未决状态,直达解除对该信号的阻塞,才执行递达动作。

注意: 阻塞和忽略是不同的,只要信号被阻塞就不会被递达,但是忽略是在递达之后进行的一种处理动作。

👑信号保存

我们知道,信号是保存在内核数据结构中的,下面来看它具体的储存模型:

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • pending表:用来存放接收到的信号,操作系统向进程发送信号时,都会修改pending表中对应编号处的比特位。
  • block表:用来存放被阻塞的信号,当指定信号需要被阻塞时,操作系统会修改block表中对应编号处的比特位。
  • handler表:这是是一个数组,用来存放不同信号的处理方法,保存的是函数指针。

当我们使用signal注册一个自定义处理方式时,操作系统会将我们定义的函数指针放在handler表中,在信号递达后调用。如果是默认处理方式,会调用handler默认的初始函数指针所对应的函数。

  • 信号产生后,操作系统就会修改pending位图,使信号处于未决状态。

操作系统会按照一定的顺序来检查block表和pending表,然后去调用相应信号编号的处理方式来完成信号递达。大概逻辑(伪代码):

if(1<<(signo - 1) & pcb->block)
{
	//signo信号被阻塞,不会被递达
}
else
{
	if(1<<(signo - 1) & pcb->pending)
	{
		//信号递达,处理该信号
		handler[signo - 1];
	}
}

操作系统在对信号进行检测的时候,先检测的是信号的block位图,如果对应信号的比特位被置一,说明该信号被阻塞,就不再去检测pending位图。如果没有被阻塞,才会去检测pending位图,如果相应的位被置一,再去调用handler表中的处理函数。

结论: 如果一个信号没有产生,但是并不妨碍它被阻塞。

被阻塞的信号,在产生之后就会一直处于未决状态,不会被递达,只有当阻塞被解除后才会被递达。

  • 默认情况下,所有信号都是不被阻塞的,所有信号都没有产生,也就是block位图和pending位图都是0。

💎信号集操作

pending图,block图以及handler表是存放在内核数据结构中的,所以只能由操作系统来修改,我们用户如果要修改也能通过操作系统来实现,所以操作系统同样给我们提供了系统调用。

  • handler表中的函数指针可以通过系统调用signal来设置。

对于block位图和pending位图的修改,操作系统提供了一族系统调用,称为信号集操作函数

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • sigset_t set:信号集变量。
  • int signum:信号编号。
  • 返回值:成功返回0,失败返回-1。

信号集:

用户在设置pending位图和block位图的时候,并不能直接让系统调用将内核中对于的比特位置一或清0,而是需要预先在一个变量中表达出我们的意愿,然后将这个变量通过系统调用给到操作系统,再由操作系统去修改内核数据结构。

  • 操作系统给我们提供了一个sigset_t的变量类型,用户只需要对这个变量进行预设置,然后再交给操作系统。

系统提供的信号集操作函数操作的也是也是这个域先处理的变量,之所以也用系统调用来处理这个变量,是因为这个变量不单单是一个32位的整形变量,它的结构和内核是对应的,所以操作也要按照相应的规则。

  • 从使用者的角度不必关心具体是如何操作的,只需要使用信号集操作函数来操作sigset_t变量即可。
  • sigset_t变量用其他方式是无法操作的,比如用printf去打印,这是没有意义的。

具体操作:

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • sigemptyset:使所有信号对应的bit清零,表示该信号集不包含任何有效信号。
  • sigfillset:使所有信号对应的bit置位,表示该信号集的有效信号包括系统支持的所有信号。
  • sigaddset:使指定信号所对应的bit置位,表示该信号集中对应信号有效。
  • sigdetset:使指定信号所对应的bit清零,表示该信号集中对应信号无效。
  • sigismember:判断指定信号所对应的bit是否有效,返回类型是bool类型。
  • 在使用sigset_t类型的变量之前,一定要调用sigemptyset进行初始化,使信号集处于确定状态。

此时我们已经对sigset_t变量预处理好了,下一步就是把这个变量交给操作系统了,操作系统同样提供了对应的系统调用。

sigprocmask():

该系统调用是专门用来修改内核数据结构中的block位图的。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • int how:修改方式,有三个选项:
    SIG_BLOCK:在block原有位图基础上添加sigset_t变量中设置的比特位。
    SIG_UNBLICK:在bolck原有位图解除上删除sigset_t变量中设置的比特位。
    SIG_SETMASK:用sigset_t变量覆盖原有的block位图。一般使用这个。
  • set:我们设置好的sigset_t变量。
  • oldeset:这是一个输出型参数,将原本block位图输出到这个sigset_t变量中。
  • 返回值:设置成功返回0,失败返回-1。

sigpending():

这是专门用来获取内核数据结构中的pending位图的。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • set:这是一个输出型参数,用来返回从内核中获取的pending位图情况。
  • 返回值:成功返回0,失败返回-1。

此时我们可以利用上面的系统调用做一个小的实验,来验证某个信号被阻塞后,它的pengding位图会被置一,但是不会被递达。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
将编号为2号和3号的信号阻塞,并且用自定义处理方式来处理2号和3号信号,一旦递达就会被处理,打印出信号的编号,但是不退出。

循环打印内核数据结果中的pending位图,观察进程在接收到2号和3号信号后的位图变化。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • 在接收到2号信号以后,pending位图的第二个比特位置一,表明该信号处于未决状态。
  • 在接收到3号信号以后,pending位图的第三个比特位也置一,表明该信号也处于未决状态。
  • 无论哪个信号产生,都没有递达,因为没有执行自定义处理函数。

所以说,被阻塞的信号,即使产生也是处于未决状态,不会被递达。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
在10秒钟后,解除对指定信号的屏蔽。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • 原本2号和3号信号被阻塞,即使产生也处于未决状态,没有被递达。
  • 当解除阻塞以后,被阻塞的信号便递达了,自定义处理方式中打印出了信号编号。
  • 信号递达后,对应pending位图中的比特位被自动清零。

👑信号处理

现在我们知道,进程在接收到信号后并不是立刻处理的,而是在适当的时候,那这个适当的时候到底是什么时候呢?

  • 从内核态返回用户态的时候信号递达。

信号只是处理的话非常简单,就是在执行默认的处理方式或者自定义方式,再或者是忽略,最重要的是信号处理的时机,也就是信号的捕获。

💎捕捉信号

首先来看什么是内核态和用户态:

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • 用户为了访问内核或者硬件资源,必须通过系统调用才能完成访问。
  • 用户态:正在执行用户层的代码,此时CPU的状态是用户态。
  • 内核态:正在通过系统调用访问内核或者硬件资源时,此时CPU的状态是内核态。

虽然系统调用是在我们的代码中写的,也就是用户在使用,但是具体的执行者是内核,也就是操作系统。

现在是知道了什么是用户态,什么是内核态,但是操作系统是怎么知道当前进程的身份状态的呢?

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

CPU中的寄存器虽然只有一套,但是有很多,有可见寄存器,如eax,ebx等等,还有很多的不可见寄存器,凡是和当前进程强相关的,都属于当前进程的上下文数据。

如上图中:

  • 有专门用来存放当前进程PCB指针的寄存器。
  • 也有专门存放当前进程页表指针的寄存器。
  • CR3寄存器:专门用来表征当前进程的运行级别的。
    0:表示内核态,此时访问的是内核资源或者硬件。
    3:表示用户态,此时执行的是用户层的代码。

操作系统是一个进行软硬件资源管理的软件,它很容易就可以获取到CPU中CR3寄存器中是0还是3,从而知道当前是用户态还是内核态。

执行系统调用时,执行者是操作系统,而不是用户。那么又存在一个问题,一个进程是怎么跑到操作系统中执行代码的呢?

对进程地址空间进行一个补充介绍:

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • 我们之前一直所说的页表都是用户级页表,每个进程都有一个。
    进程地址空间的大小一共有4GB,我们之前谈论的只有0~3GB,这3GB的空间属于用户空间,用来存放用户的代码,数据等。为了保证进程的独立性,每个进程都有一个进程地址空间,都有一个用户级页表。
  • 还有一共内核级页表,所有进程共用一份。
    进程地址空间中的3~4GB空间,是不允许用户访问的,因为这1GB空间中的数据等,通过内核级页表和内存中的操作系统相映射,属于内核级别的。因为内存中只存在一份内核,所以所有进程的虚拟地址空间的这1GB空间都通过同一份内核级页表和内存中的内核相映射。
  • 每一个进程地址空间中的3~4GB的内容都是一样的,因为它们都通过同一个内核级页表和内存中的内核相映射。

还记得动态链接吗?通过代码段的位置无关码跳转到共享区从内存中映射过来的动态库来执行相应的方法。系统调用和它的原理一样:

  • 当执行到代码段中的系统调用时,会在跳转到当前进程虚拟地址空间中的内核空间中。
  • 系统调用的具体实现都放在这1GB的内核空间中。
  • 然后根据内核级页表和内存中内核的映射关系实现内核的访问。

此时又有一个问题,为什么我们的代码中不能访问这3~4GB的空间,而系统调用就跳转到这1GB的内核空间中进行访问了呢?我们都是用户的代码啊?

  • 因为从代码段跳转到内核空间中后,CPU中的CR3寄存器从3变成了0。
  • 意味着进程运行级别从用户态变成了内核态,也就是执行者从用户变成了操作系统,所以可以对这1GB的内核空间进行访问。
  • 系统调用接口的起始位置,会将CR3寄存器中的数据从3变成0,完成从用户态向内核态的转变。

所以说,系统调用前一部分是由用户在执行,其余部分由操作系执行

此时再来理解信号处理的时机—从内核态返回到用户态,这句话的含义:

  • 必然曾经进入到了内核态,而进入内核态的方式很多,比如进程切换,只有操作系统才有权力将进程从CPU上剥离下来换上另一个进程。还有系统调用,等等方式。

以我们最熟悉的系统调用为例:

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
以黑色长线为界,上面是用户态,下面是内核态。

  • 当执行到用户代码段中的系统调用时,会跳转掉虚拟地址空间中的内核空间去执行具体的方法,此时从用户态变成了内核态
  • 当系统调用被操作系统执行完毕以后,在返回之前(来一趟挺不容易的),操作系统会检测task_struct中block位图,pending位图,然后再根据handler中的处理方式去处理相应的信号。
  • 如是自定义处理方式,操作系统会拿着handler表中的函数地址,通过特定的系统调用去执行用户自定义的处理方式,此时从内核态变成了用户态
  • 在执行完自定义处理方式以后,再次回到内核中取系统调用得到的数据,此时再次从用户态变成了内核态
  • 拿上要取的数据以后,通过特定的系统调用返回到用户代码中系统调用的位置,再次从内核态变成了用户态

上面过程的伪代码形式:

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • 涉及到的系统调用无需详细了解,只需要知道是通过系统调用实现的即可。

上面过程中存在一个问题,在执行自定义处理方式的时候,为什么必须从内核态切换成用户态去执行用户定义的处理方式呢?不能直接以内核态的身份去执行吗?

  • 不可以。理论上是绝对可以实现的,因为内核态比用户态高,高级别去处理低级别肯定是可以的。
  • 但是操作系统不相信任何人,如果自定义处理方式中有用户的恶意代码,而此时又以操作系统身份去执行,那么就会导致问题。
  • 所以必须得切换到用户身份去执行自定义处理方式才能保证系统的安全。

两个独立的流程:

此时就存在了两个流程,一个是main函数所在的执行流程,一个是自定义处理方式的执行流程:

  • 在执行完系统调用后不是恢复main函数的上下文进行执行,而是执行用户自定义的处理方式。
  • 自定义处理方式函数和main函数使用不同的堆栈空间,并且不存在调用和被调用的关系,是两个独立的控制流程。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • 上面整个过程可以看成一个无穷大符号加一条线,线的上边是用户态,下边是内核态。
  • 每经过一次黑线就会发生一次身份状态的改变,一共改变了四次。

上面这种自定义处理方式是最复杂的情况,如果是SIG_DFL(默认处理方式)和SIG_IGN(忽略方式),以内核态身份就可以处理,然后就可以直接返回到用户代码中系统调用的位置,少了两次身份的转变。

  • 因为默认方式和忽略方式是被写入到操作系统中的,被操作系统所信任的方式。
  • 默认处理方式:所有信号的默认处理方式都是结束进程,只是不同信号表示不同的异常。
  • 忽略处理方式:忽略和阻塞不一样,忽略也是一种处理方式,它仅仅是将task_struct中的pending位图中对应信号的比特位清空,然后就直接返回到用户态了。

💎系统调用sigaction():

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • int signum:信号编号。
  • act:这是一个结构体变量,结构体中包括多个属性,sa_handler赋值自定义处理方式,暂时将sa_flags都设为0,其他暂时不用管。
  • oldact:是一个输出型的结构体变量,将原本的捕捉方式放入这个结构体变量中。
  • 返回值:成功返回0,失败返回-1。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • 将自定义处理方式赋值给结构体变量act中的sa_handler。
  • 使用系统调用sigaction注册自定义处理方式。
  • 在自定义处理函数中,打印捕捉到的信号编号,然后进行10s种延时。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
在进程开始运行后,我们在10s内发送了很多次2号信号,但是最终只捕获了两次。

  • 当递达第一个2号信号的时候,同类型的信号无法被递达。
  • 因当前信号在被捕捉的时候,系统会自动将当前信号加入到进程的信号屏蔽字,也就是将block对应的比特位置位,然后将pending表对应比特位清空,再去进行递达。
  • 但是第二个2号信号在第一个信号被捕捉的时候会将对应pending位图的比特位置位。
  • 所以当第一个2号信号处理完毕以后,解除对2号信号的屏蔽后,第二个2号信号就会被递达。
  • 除了这两个2号信号,其余的2号信号都被舍弃了。

注意: 进程处理信号的原则是串行的处理同类型的信号,不允许递归,所以同类型的多个信号同时产生,最多可以处理两个。

上面内容,系统调用signal也可以实现,那么sigaction相对于signal有什么优势呢?

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
刚刚代码中,由于在2号信号的自定义处理中没有结束进程,所以只能用其他信号来结束这个进程,如上图中使用的是3号信号。

  • 如果想要在捕获2号信号以后,将3号信号也屏蔽了呢?
  • 此时就需要设置结构体变量act中的sa_mask成员

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
还是使用上面的代码,只是在act结构体变量中sa_mask成员中增加了3号信号,并且给3号信号注册了自定义处理方式。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
在10s内,多次发送2号和3号信号。

  • 当第一次2号信号被捕获后,第二个2号信号虽然被阻塞了,但是它还是让pending位图置位了。
  • 当第一次2号信号被递达完成后,就会递达第二个2号信号。
  • 当第二次2号信号被递达完成后,2号信号的pending位图的比特位是0,所以才递达3号信号。
  • 虽然在捕获2号信号的同时会阻塞3号信号,但是3号的pending位图的比特位仍然被置位了。

在第一个2号信号被捕获的时候,同时阻塞了第二个2号信号和3号信号,此时pending位图的第二个和第三个比特位都是1,但是当第一个2号信号递达完成后,先处理的是第二个2号信号而不是3号信号。

  • 一般一个信号被解除屏蔽的时候,会自动递达这个信号,如果该信号pending位图的比特位是1的话就会递达,是0的话就不做任何处理。

👑不可重入函数

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
如上图所示链表,在插入节点的时候捕获到了信号,并且该信号的自定义处理方式中也调用了插入节点的函数。

  • 在main函数中,使用insert向链表中插入一个节点node1,在执行insert的时,刚让头节点指向node1以后(如上图序号1),捕获到了信号,进入到了该信号的自定义处理方式中。
  • 在自定义处理方式中,同样调用了insert函数向链表中插入一个节点node2,此时完整的执行了insert函数,但是在头节点和最开始那个节点之间同时有了node1和node2(如上图序号2和3)。
  • 当第二次调用insert中让头节点指向node2后(如上图序号3),流程返回到信号的自定义处理函数中,然后再返回到第一次调用insert处,头节点指向node1(如上图序号4)。
  • 最后可以看到,该链表是丢了一个节点的。
  • 重入:像insert函数这样,在main流程中调用还没有返回时就再次被handler流程调用再次进入该函数。

insert函数访问的是一个全局链表,有可能会因为重入和造成错乱,像insert这样的函数就称为不可重入函数

如果一个函数只访问自己的局部变量或参数,则不会造成错乱,此时这样的函数就称为可重入函数

注意: 可/不可重入是函数的特性,是中性的,并不是问题,所以也不需要被解决。

我们目前使用的大部分结构都是不可以重入函数!!!。

符合以下条件之一的就是不可重入函数:

  • 调用了malloc或者free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

👑volatile关键字

int quit = 0;

void handler(int signo)
{
    printf("pid:%d,捕捉到的信号编号是:%d\n",getpid(),signo);
    printf("quid:%d",quit);
    quit = 1;
    printf("->%d\n",quit);
}
int main()
{
    signal(2,handler);
    while(!quit);
    printf("pid:%d,我是正常退出的\n",getpid());
    return 0;
}

定义全局变量quit,当quit是0的时候,一直进行while循环,当quit变成1的时候,结束循环,进程正常退出。
信号2注册自定义处理方式,在函数中将全局变量改成1,让main函数控制的流程正常结束。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
在接收到2号信号后,quit从0变成1,所以main流程也正常结束了,不再循环。

我们的编译器会进行很多的优化,比如debug版本和relase版本中的assert就会被优化。在使用g++编译器的时候,可以指定g++的优化级别。

g++ -o $@ $^ -O3

指定使用级别为3的编译器优化选项。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
仍然是上面代码,运行起来后,发送2号信号,quit是从0变成了1,但是进程并没有结束,还是在运行,再次发送2号信号,quit从1变成1,进程还在继续。

  • 此时可以肯定quit被改成了1,但是while(!quit)还是在循环,没有停下来。

上诉现象的原因是什么?肯定是和优化有关,因为我们加了-O3选项。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • quit在物理内存中一定有一块空间,最开始是0。
  • 当CPU指向while(!quit);指令的时候,会通过虚拟地址和页表的映射将物理内存中的quit数据取到CPU的寄存器中。
  • 当quit被修改后,物理空间中的数据就会从0变成1。

在没有优化前,CPU每次都是从物理内存中拿到quit的数据,再去指向while循环,所以当quit从0变成1后,CPU中寄存器的数据也会及时从0变成1,所以while循环会停下来。

但是采用优化方案后:

  • 在main控制的执行流中,quit没有进行修改,也没有写入,只是被读取,所以在第一次将从物理空间读取到寄存器中便不再读取了,每次执行while时候都是使用的寄存器中的quit值,所以始终都是0。
  • 在handler执行流中,对quit进行了修改,所以物理内存中的quit从0变成了1。

导致上面现象的原因就是CPU执行while时的quit和物理内存中的quit不是一个值。

  • 为了让CPU的寄存器每次都从物理内存中取数据,使用volatile关键字来修饰这个quit变量。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

可以看到,此时在handler的执行流中修改了quit值,并且CPU中该值也得到了及时更新,所以程序可以正常结束。

👑SIGCHLD信号

在学习进程控制的时候,使用wait和waitpid系统调用何以回收僵尸进程,父进程可以阻塞等待,也可以非阻塞等待,采用轮询的方式不停查询子进程是否退出。

  • 采用阻塞式等待,父进程就被阻塞了,什么都干不了,只能等子进程退出。
  • 采用非阻塞式等待,父进程在干自己事的同时还要时不时的轮询一下,程序实现比较复杂。

实际上,子进程的退出并不是悄无声息的,在子进程退出时,会发出SIGCHLD信号给父进程

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

给SIGCHLD信号注册自定义处理方式,打印捕捉到的信号编号。父进程创建处子进程后,子进程在5次循环后退出,父进程始终循环。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

可以看到,子进程在退出时,发出了编号为17的SIGCHLD信号,被父进程捕捉到了。

  • 我们就可以通过在17号信号的自定义处理函数中进行进程等待来回收子进程。
  • 采用这种方式时,main执行流中的父子进程都不会收到影响,当子进程退出时,handler执行流进行进程等待,回收子进程资源。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
waitpid的第一个参数可以填-1,此时只要是该父进程的子进程退出就会被回收。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

在17号信号的自定义处理函数中,循环回收所有子进程,只要是子进程都会被循环回收。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

可以看到,父进程既回收了退出的子进程,而且还不影响父进程干自己的事,因为回收工作是在SIGCHLD信号的自定义处理函数中进行的。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号
SIGCHLD信号的默认处理方式是Ign,也就是忽略的意思。
【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • 当子进程退出后,父进程什么都没有干,子进程就会变成僵尸状态。

【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号

  • 当显式SIGCHLD信号使用忽略方式(SIG_IGN)时,退出的子进程就会被自动回收。
  • 虽然SIGCHLD默认的处理方式就是忽略,但是默认的忽略不会回收子进程,只有显式注册为SIG_IGN(忽略)方式才会自动回收退出的子进程。

👑总结

至此,加上上一篇文章,信号的整个生命周期都介绍完了,重点在于新的产生,信号保存,以及信号捕捉上面,其它衍生的知识了解即可。文章来源地址https://www.toymoban.com/news/detail-413339.html

到了这里,关于【Linux学习】信号——信号保存 | 信号处理 | 不可重入函数,volatile,SIGCHLD信号的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【linux】信号的保存和递达处理

            上节我们了解到了预备(信号是什么,信号的基础知识)再到信号的产生(四种方式)。今天我们了解信号的保存。信号产生,进程不一定立马就去处理,而是等合适的时间去处理,那么在这段时间内,进程就需要保存信号,到了合适时间再去执行! 目录 一、递

    2024年02月08日
    浏览(59)
  • 【Linux】进程信号(完整版) --- 信号产生 信号保存 信号捕捉 可重入函数 volatile SIGCHLD信号等

    🍎 作者: 阿润菜菜 📖 专栏: Linux系统编程 我们想要杀死某个后台进程的时候,无法通过ctrl+c热键终止进程时,我们就会通过kill -9的命令来杀死信号。 查看信号也比较简单,通过 kill -l 命令就可以查看所有信号的种类,虽然最大的信号编号是64,但实际上所有信号只有6

    2024年02月04日
    浏览(38)
  • Linux进程信号 | 信号处理

    前面的文章中我们讲述了信号的产生与信号的保存这两个知识点,在本文中我们将继续讲述与信号处理有关的信息。 之前我们说过在收到一个信号的时候,这个信号不是立即处理的,而是要得到的一定的时间。从信号的保存中我们可以知道如果一个信号之前被block,当解除

    2024年02月09日
    浏览(33)
  • Linux进程信号【信号处理】

    ✨个人主页: 北 海 🎉所属专栏: Linux学习之旅 🎃操作环境: CentOS 7.6 阿里云远程服务器 从信号产生到信号保存,中间经历了很多,当操作系统准备对信号进行处理时,还需要判断时机是否 “合适”,在绝大多数情况下,只有在 “合适” 的时机才能处理信号,即调用信号

    2024年02月11日
    浏览(30)
  • 【Linux】进程信号之信号的处理

    在前面我们讲过信号产生和保存以后,我们知道进程对于产生的信号不是立即去处理的,而是在\\\"合适\\\"的时候去处理信号, 这是因为信号的产生的异步的,当前进程可能正在做更重要的事情!。 那么信号可以被立即处理吗?答案的可以的,但是要满足这个条件: 在 Linux 中如果

    2024年02月12日
    浏览(39)
  • Linux——信号处理

    在Linux系统中, 信号处理 是一个非常重要的概念,它允许 操作系统在特定事件发生时 通知进程。信号可以由 硬件异常、用户输入、软件条件 等多种来源产生。为了有效地处理这些信号,Linux提供了一系列的系统调用和函数,其中 signal 、 sigaction 和 sigprocmask 是三个核心的函

    2024年03月09日
    浏览(26)
  • Linux进程 ----- 信号处理

    目录 前言 一、信号的处理时机 1.1 处理时面临的情况 1.2 “合适”的时机 二、用户态与内核态 2.1 概念理论 2.2 再现 进程地址空间 2.3 信号处理过程 三、信号的捕捉 3.1 内核实现 3.2 sigaction 四、信号部分小结 从信号产生到信号保存,中间经历了很多,当操作系统准备对信号进

    2024年03月21日
    浏览(65)
  • linux信号处理机制

            信号检测是项目开发中必不可少的!提到信号处理机制,很多人都会想到signal函数吧         除了这种方式,还有一种操作信号集的方式更为精确,能够屏蔽,添加,删除,操作等某个信号。这些函数仅支持对 POSIX 信号集进行操作。首先了解下这几个函数: 描

    2024年01月23日
    浏览(34)
  • 【Linux】信号的处理

    信号篇终章 文章目录 前言 一、信号的处理         1.可重入函数         2.volatile         3.SIGCHLD信号 总结 在前两篇linux文章中我们详细的讲解了信号的产生和信号的保存,今天来到最后一个重点信号的处理,对于信号的处理我们会重新引入进程地址空间的知识,并

    2024年02月05日
    浏览(28)
  • 数字信号处理学习1

    基本上算是没怎么学过数字信号处理这门课,因为本科的时候,专业方向用不上,现在没法子了,专业使然,只能自己自学了,但是我又不知道该从何学起,就买了一本现代数字信号处理,结果发现人家把第一章基础知识给删了,这我就斯巴达了。。。所以就又搞了本绿皮的

    2024年02月02日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包