Linux:进程信号(二.信号的保存与处理、递达、volatile关键字、SIGCHLD信号)

目录

1.信号保存

1.1递达、未决、阻塞等概念

1.2再次理解信号产生与保存

1.3信号集操作函数

sigset_t类型

sigemptyset() 函数

sigismember()函数

sigaddset ()函数

sigdelset() 函数

sigprocmask()系统调用

sigpending()系统调用

2.信号的处理/递达

2.1信号处理时机与过程

2.2用户态和内核态

2.3再看进程地址空间

谁来运行OS

2.4信号的捕捉—sigaction()函数

3.补充知识

3.1可重入函数

3.2volatile关键字

3.3 SIGCHLD信号


1.信号保存

1.1递达、未决、阻塞等概念

  • 信号未决(Pending):当信号产生时,会首先进入未决状态,即信号还没有被进程处理。此时,信号被标记为未决状态,等待进程处理。
  • 信号递达(Delivery):当进程解除对信号的阻塞时,信号才会被递达,即信号被传递给进程的信号处理函数进行处理

三种信号处理方式:

  • 默认处理(Default Handling):每个信号都有一个默认的处理方式,当信号递达时,操作系统会执行默认的信号处理动作,传入SIG_DFL
  • 自定义处理(Custom Handling):进程可以通过设置信号处理函数(一般是handler)来自定义对信号的处理方式。当信号递达时,操作系统会调用进程设置的信号处理函数来处理信号
  • 忽略处理(Ignore Handling):进程还可以选择忽略某个信号,即在信号递达时不做任何处理。通过将信号处理函数设置为 SIG_IGN,进程可以忽略某个信号
  • 阻塞信号:进程可以选择阻塞某个或多个信号,使其在未决状态下等待。被阻塞的信号不会递达,保持在未决状态,直到进程解除对此信号的阻塞

1.2再次理解信号产生与保存

在操作系统中,进程信号相关的"Pending位图"和"Block位图"是两种数据结构,用于跟踪进程当前挂起/未决(pending)的信号和已经阻塞(blocked)的信号

1. Pending位图

  • 作用Pending位图用于记录当前对进程发送但尚未被处理的信号。当操作系统向进程发送信号时,如果进程当前不能立即处理该信号(比如正在处理其他信号或忙于执行其他任务),该信号会被添加到进程的Pending位图中。
  • 操作:操作系统会定期检查进程的Pending位图,并根据信号处理方式(默认处理、自定义处理、忽略处理)来决定如何处理挂起的信号。

2. Block位图

  • 作用Block位图用于记录当前被阻塞的信号。进程可以选择阻塞某些信号,使得这些信号被阻塞不会被递送给进程。
  • 操作:当信号被阻塞时,该信号会被添加到进程的Block位图中。被阻塞的信号不会被递送给进程,直到解除阻塞。
  • 特点Block位图记录了进程当前被阻塞的信号,帮助进程控制哪些信号可以递送到进程。

其中信号的阻塞与否,跟是否收到信号毫无关系

对应信号在进程的信号未决位图中的比特位会在信号递达前被设置为1,表示信号需要处理,而在信号被处理完后会被清零,即改为0

是先清0,再进行递达

而进程能识别信号,也是因为早在未收到信号之前,我们就已经知道是否堵塞,怎么处理了(利用上述三个表)

  • 信号处理表(hander):在进程创建时,内核会为其分配一个信号处理表,用于记录每个信号对应的信号处理函数(Signal Handler)。当进程收到一个信号时,内核会根据信号处理表中对应信号的处理函数来执行相应的操作。
  • 信号未决位图:在进程接收到一个信号时,内核会更新进程的信号未决位图,用于记录当前被没屏蔽的信号。这个位图帮助进程确定是否有信号需要处理。
  • 信号挂起位图(pending):当一个信号被进程接收但尚未处理时,内核会将这个信号标记为挂起,即更新进程的信号挂起位图。这个位图帮助进程确定哪些信号需要等待处理。

这三个表是操作系统内核为了管理进程信号处理而设计的数据结构,它们在进程创建时被初始化并与进程关联,帮助进程识别和处理信号

1.3信号集操作函数

sigset_t类型

每个信号只有一个bit的未决标志,非01,不记录该信号产生了多少次(只表示收到否信号,对于信号的数量没办法也没必要),阻塞标志也是这样表示的。

因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态

  • 在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞
  • 在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态

阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略

对于我们使用者来说,应该将sigset_t类型看作一个抽象的信号集合,而不需要关心其内部的具体实现细节。sigset_t类型的具体表示方式可能会因系统而异,可能是一个位图、一个数组或其他数据结构,但这些细节对于使用者来说并不重要。

我们使用者应该通过系统提供的函数来操作sigset_t变量,比如sigemptyset、sigfillset、sigaddset、sigdelset等函数来对信号集进行操作。这些函数会根据系统的具体实现来正确处理信号集的操作,确保其正确性和可移植性。

因此,直接打印sigset_t变量是没有意义的,因为sigset_t类型的内部表示对于使用者来说是不透明的
 

#include <signal.h>
int sigemptyset(sigset_t* set);//初始化信号集,将所有信号的对应bit清零
int sigfillset(sigset_t* set);
int sigaddset (sigset_t* set, int signo);//屏蔽指定的信号,在该block表中,将该信号的比特位设置为1
int sigdelset(sigset_t* set, int signo);//在信号集中,将某个指定信号的比特位设置为0
int sigismember(const sigset_t* set, int signo);//此函数用于检查一个指定的信号的比特位是否为1
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
  • 在使用sigset_ t类型的变量之前,一定要调 用sigemptysetsigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddsetsigdelset在该信号集中添加或删除某种有效信号
sigemptyset() 函数

sigemptyset()函数是标准C库(libc)中用于操作自定义信号集(signal set)的一个函数。信号集是一个包含多个信号的集合,通常用于信号处理和控制。sigemptyset()函数的作用是初始化一个自定义信号集,将其所有信号都清空,也就是将信号集中的所有的标志位置为0,使得这个集合不包含任何信号,也就是不阻塞任何信号。此函数通常在设置信号处理程序(signal handler)之前调用,以确保信号集的正确初始化。

函数原型如下:

#include <signal.h> // 使用此函数需导入这个头文件

int sigemptyset(sigset_t *set);

参数:

  • set:指向sigset_t类型的指针,sigset_t是一个信号集类型,用于表示一个信号集。我们会把用这个指针指向的信号集清空,也就是不阻塞任何信号。

返回值:

  • 成功时,sigemptyset()函数返回0
  • 失败时,返回-1,并设置errno表示错误原因。
sigismember()函数

sigismember函数是标准C库中用于操作自定义信号集的一个函数。此函数用于检查一个指定的信号是否在给定的信号集中,也就是检查该信号是否被阻塞当您需要确定信号集中是否包含某个特定信号,以便进行相应的信号处理、信号屏蔽等操作时,可以使用此函数。

 函数原型如下:

#include <signal.h> // 使用此函数需导入这个头文件

int sigismember(const sigset_t *set, int signum);

参数:

  • set:指向sigset_t类型的指针,sigset_t是一个信号集类型,表示一个信号集。
  • signum:要检查的信号编号。

返回值:

  • 如果指定的信号在信号集中,sigismember()函数返回1;如果指定的信号不在信号集中,返回0
  • 失败时,返回-1,并设置errno表示错误原因。
sigaddset ()函数

sigaddset()函数是标准C库中用于操作自定义信号集的一个函数。该函数允许您将一个指定的信号添加到一个自定义信号集中,也就是将该信号的标准位设为1,表示阻塞这个信号。当您需要创建或修改信号集,以便在信号处理、信号屏蔽等操作中使用时,可以使用此函数。(屏蔽指定的信号,在该block表中,将该信号的比特位设置为1)

函数原型如下:

#include <signal.h> // 使用此函数需导入这个头文件

int sigaddset(sigset_t *set, int signum);

参数:

  • set:指向sigset_t类型的指针,sigset_t是一个信号集类型,表示一个信号集。
  • signum:需要添加到信号集中的信号编号。

返回值:

  • 成功时,sigaddset()函数返回0
  • 失败时,返回-1,并设置errno表示错误原因。
sigdelset() 函数

sigdelset函数是标准C库中用于操作自定义信号集的一个函数。该函数允许您从一个自定义信号集中删除一个指定的信号,也就是将该信号的标准位设为0,不阻塞这个信号当您需要调整信号集,以便在信号处理、信号屏蔽等操作中使用时,可以使用此函数。

(在信号集中,将某个指定信号的比特位设置为0)

函数原型如下:

#include <signal.h> // 使用此函数需导入这个头文件

int sigdelset(sigset_t *set, int signum);

参数:

  • set:指向sigset_t类型的指针,sigset_t是一个信号集类型,表示一个信号集。
  • signum:需要从信号集中删除的信号编号。

返回值:

  • 成功时,sigdelset()函数返回0
  • 失败时,返回-1,并设置errno表示错误原因。
sigprocmask()系统调用

sigprocmask是一个系统调用,用于检查修改当前进程的信号屏蔽集(signal mask)。信号屏蔽集是一个用来指定哪些信号在进程处理信号时应该被阻塞的集合。通过操作信号屏蔽集,进程可以控制哪些信号可以被接收和处理,哪些信号应该被暂时屏蔽。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数说明:

1.how:表示对信号屏蔽集的操作方式,有三种取值:

  • SIG_BLOCK:将set中指定的信号添加到当前信号屏蔽集中。
  • SIG_UNBLOCK:从当前信号屏蔽集中移除set中指定的信号。
  • SIG_SETMASK:将当前信号屏蔽集设置为set中指定的信号集。

2.set:指向一个sigset_t类型的指针,用于指定要操作的信号集。

3.oldset:指向一个sigset_t类型的指针,用于存储之前的信号屏蔽集。

返回值:

  • 如果函数调用成功,返回0;如果出现错误,返回-1,并设置errno变量来指示错误类型。

功能:

  • sigprocmask函数允许进程检查或修改当前进程的信号屏蔽集。
  • 通过how参数指定的操作,可以添加、移除或替换信号屏蔽集中的信号。
  • 如果oldset参数不为NULL,则会将之前的信号屏蔽集存储到oldset中。
sigpending()系统调用

sigpending是一个系统调用,用于获取当前进程挂起/未决(pending)的信号集挂起的信号是指已经发送给进程但尚未被处理的信号。通过sigpending函数,进程可以查询当前有哪些信号处于挂起状态,以便进一步处理这些信号。

#include <signal.h>
int sigpending(sigset_t *set);

参数说明:

  • set:指向一个sigset_t类型的指针,用于存储当前进程挂起的信号集。

返回值:

  • 如果函数调用成功,返回0;如果出现错误,返回-1,并设置errno变量来指示错误类型。

功能:

  • sigpending函数允许进程获取当前进程挂起的信号集。
  • 通过set参数返回当前进程挂起的信号集,可以进一步对这些信号进行处理。
#include<iostream>
#include<signal.h>
#include<unistd.h>
#include <sys/types.h>

using namespace std;

//打印
void PrintPending(sigset_t &pending)
{
	for(int i=31;i>=1;i--)
	{
		if(sigismember(&pending,i))//由于判断该信号是否存在,存在返回1,否则返回0
		{
			cout<<"1";
		}
		else{
			cout<<"0";
		}
	}
	cout<<endl<<endl;//刷新缓冲区
}

int main()
{
	cout<<"pid:"<<getpid()<<endl;
	sigset_t set,olbset;//set:是我们要设置进进程的信号集,olbset:保存原来的信号集
    sigemptyset(&set);//初始化信号集
    sigemptyset(&olbset);//初始化信号集
	
	for(int i=1;i<=10;i++)
	{
        sigaddset(&set,i);//屏蔽1~10号信号,也就是将1~10号比特位设置为1
	}
	
	sigprocmask(SIG_SETMASK,&set,&olbset);//将set信号集覆盖block表,olbset用来保存原来的block表
	 
	sigset_t pending;

    while(true)
	{
		sigpending(&pending);//获取当前进程pending表,将其放入这个变量中
        
        //打印
		PrintPending(pending);
		sleep(1);
	}

	return 0;
}

由于我们屏蔽了1~10信号,所以我们给进程1~10信号时,进程不会有反应,但是在pending表中会显示出来(9和19这两个信号除外,是不能被屏蔽的)

2.信号的处理/递达

在信号处理中,一般情况下有三种处理方式,分别是:

  • 忽略信号(Ignore):进程可以选择忽略某些信号,这样当该信号到达时,系统不会采取任何操作,也不会调用任何信号处理函数。一些信号(比如SIGKILLSIGSTOP,9号和11号)是不能被忽略的,它们具有特殊的含义和作用。
  • 执行默认操作(Default Action):每个信号都有一个默认的处理方式,当进程接收到信号时,系统会执行该信号的默认操作。比如,当进程接收到SIGINT信号(通常由Ctrl+C触发),系统会默认终止进程的执行。
  • 捕捉信号并执行处理函数(Signal Handling):进程可以捕捉信号并注册相应的信号处理函数,当接收到信号时,系统会调用该处理函数来处理信号。进程可以自定义信号处理函数,根据需要对信号进行处理,比如记录日志、关闭文件、释放资源等。

2.1信号处理时机与过程

我们之前只是泛泛的讲:进程会在合适时候进行对信号的处理,那什么是合适的时候?——进程从内核态切换会用户态的时候,信号会被检测并处理

每次进程从内核态切换到用户态时,操作系统会依次检查进程是否有未处理的信号。如果有未处理的信号,操作系统会根据信号的处理方式(比如忽略、捕获、默认处理等)来进行相应的处理。如果信号没有被阻塞,操作系统会执行信号处理程序来处理该信号,然后继续执行用户态程序。

在第三步我们讨论的是自定义处理,如果是默认和忽略呢?

  • 默认:更改PCB的状态即可观在是内核身份,直接杀掉进程
  • 忽略:处理这个信号什么都不做,直接把pending表对应比特位置为0

为什么在第四步里,特地回到用户态执行自定义处理函数:操作系统不相信任何人,不会轻易执行用户的代码,因为用户代码可能包含恶意代码或错误代码,可能会导致系统崩溃、数据泄露等安全问题

2.2用户态和内核态

用户态和内核态是操作系统中的两种运行模式,用于区分程序的权限和访问级别。下面是它们的主要特点和区别:

1. 用户态(User Mode):

  • 用户态是指程序在执行时所处的一种权限较低的状态,程序在用户态下只能访问受限的资源和执行受限的操作。

  • 在用户态下,程序运行在用户空间,只能访问自己的内存空间和受限的系统资源,不能直接访问操作系统内核或其他进程的内存空间(内核空间)。

  • 用户态下的程序通常是普通应用程序,如文本编辑器、浏览器等,它们无法直接执行特权指令或访问系统底层资源。

2. 内核态(Kernel Mode):

  • 内核态是指程序在执行时所处的一种权限较高的状态,程序在内核态下具有更多的权限和访问系统资源的能力。

  • 在内核态下,程序运行在内核空间,可以直接访问系统内核和底层资源,执行特权指令和进行敏感操作。

  • 内核态下的程序通常是操作系统内核的一部分,如设备驱动程序、系统调用处理程序等,它们负责管理系统资源、处理中断、执行特权操作等。

我们不同的状态主要是不同的权限:通过改变CPU内的执行权限,设置了寄存器内的特定标志位,来改变状态

2.3再看进程地址空间

进程无论如何切换,总能找到OS:我们访问OS,本质就是通过进程的地址空间的[3,4]GB的内核空间来访问的

调用系统调用也是在地址空间内进行的

在操作系统内核中,通常会有一个系统调用表(System Call Table)用于存储系统调用号与对应系统调用处理程序的映射关系。当用户进程发起系统调用时,会将系统调用号放入特定寄存器中,CPU根据系统调用号找到对应的系统调用处理程序在系统调用表中的位置,然后跳转到该函数的地址进行调用。

在这个过程中,操作系统内核会确保系统调用表的起始虚拟地址是已知的,并且系统调用号与处理程序的映射关系是正确的。通过这种方式,CPU能够根据系统调用号正确地找到对应的系统调用处理程序,并执行相应的操作。

谁来运行OS

2.4信号的捕捉—sigaction()函数

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字。如果我们处理完对应的信号,该信号默认也会从信号屏蔽字中进行移除——不想让信号,嵌套式进行捕捉处理(正在处理时你又来了,那就又去调用处理函数)

sigaction()函数是用于设置和修改信号处理程序的系统调用函数。通过sigaction()函数,进程可以指定在接收到特定信号时应该执行的处理程序。这个处理程序可以是系统默认的处理方式,也可以是用户自定义的处理函数。

sigaction()函数的原型如下:

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

其中,signum参数指定了要设置的信号的编号,act参数指定了新的信号处理方式,oldact参数用于保存之前的信号处理方式。

二者都是struct sigaction类型的,对于struct sigaction

  • void (*sa_handler)(int):这是一个函数指针,用于指定信号处理函数的地址。当接收到信号时,系统会调用这个函数来处理信号。函数接受一个整型参数,表示接收到的信号编号。如果将sa_handler设置为SIG_IGN,表示忽略该信号;将其设置为SIG_DFL,表示使用系统默认的信号处理方式。

  • void (*sa_sigaction)(int, siginfo_t *, void *):这也是一个函数指针,用于指定扩展的信号处理函数的地址。与sa_handler不同的是,sa_sigaction函数接受三个参数:第一个参数是信号编号,第二个参数是一个指向siginfo_t结构体的指针,其中包含了关于信号的更多信息,第三个参数是一个指向void类型的指针。(一般用于实时信号,我们不管这个)

  • sigset_t sa_mask:这是一个信号集合,用于指定在信号处理函数执行期间需要屏蔽的信号。如果有信号在sa_mask指定的信号集合中,则这些信号会被阻塞,直到信号处理函数执行完毕。

  • int sa_flags:用于指定信号处理的行为。可以是以下几个标志的组合:

                            SA_RESTART:表示系统调用在接收到信号后会自动重启。
                            SA_NOCLDSTOP:子进程暂停和继续时不会产生SIGCHLD信号。
                            SA_NODEFER:不会在执行信号处理函数期间阻止同一信号的传递。
                            SA_SIGINFO:表示使用sa_sigaction字段指定的信号处理函数。(我们一般设置为0就行了)

  • void (*sa_restorer)(void)这是一个保留字段,已经废弃,不再使用。

返回值为0表示函数调用成功,返回-1表示函数调用失败。在函数调用失败的情况下,可以通过errno全局变量获取具体的错误信息。

通过sigaction()函数,进程可以设置信号的处理方式为以下几种之一:

  • 忽略信号(SIG_IGN
  • 执行默认处理方式(SIG_DFL
  • 指定自定义的信号处理函数
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;

void handler(int signum)
{
    cout << "收到了信号:" << signum << endl;
}

int main()
{
    struct sigaction act, oldact;
    act.sa_handler = handler;//将自定义的处理方式给他
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);//初始化
    sigaction(2, &act, &oldact); // 进行信号捕捉
    while (true)
        sleep(2);
    return 0;
}

3.补充知识

3.1可重入函数

被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,函数有可能因为重入而造成错乱,像这样的函数称为不可重入函数

反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数

可重入函数(Reentrant Function)也称为可重入代码(Reentrant Code)或重入函数(Reentrant Routine),是指在并发执行环境中,能够被多个线程同时调用的函数。这种函数能够在任何时候被中断,并在之后从中断点恢复执行,而不会导致数据错误或系统崩溃。
为了实现可重入性,可重入函数必须满足以下条件:

  • 不使用静态(全局)非常量数据:静态或全局非常量数据可能在多个线程之间共享,如果一个线程修改了这些数据,其他线程可能无法正确地读取或写入这些数据,导致数据错误。

  • 不调用不可重入函数:如果一个函数调用了另一个不可重入的函数,那么它本身也将是不可重入的。

  • 不返回指向静态(全局)非常量数据的指针:与第一条类似,返回这样的指针可能导致其他线程错误地修改或读取数据。

  • 使用局部变量:局部变量存储在函数的栈帧中,每个函数调用都有自己的栈帧,因此局部变量是线程私有的,不会被其他线程干扰。

  • 对共享资源的访问进行保护:如果函数需要访问共享资源(如文件、数据库、共享内存等),则需要使用适当的同步机制(如互斥锁、信号量等)来保护这些资源,防止数据竞争和冲突。

3.2volatile关键字

volatile 关键字在 C 和 C++ 语言中是一个类型限定符它告诉编译器不要对访问该关键字声明的变量的代码进行优化即每次都需要从内存中读取变量的值,而不是使用存储在寄存器中的副本。这是为了确保多线程环境或者硬件中断等场景下,对该变量的访问总是最新的、未被其他线程或硬件修改过的值。
有时因为编译器优化的原因,会导致我们代码出错

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include<iostream>

using namespace std;

int g_flag = 0;
//volatile int g_flag = 0;//加了volatile后,无论是否优化,都只会从内存中拿数据,所以g_flag都会改变

void changeflag(int signo)
{
	g_flag = 1;
}

//没有优化:输入2号信号时,g_flag=1,既会退出while,最终退出程序
//优化后:输入2号信号时,程序不会退出
int main()
{
    cout<<"pid:"<<getpid()<<endl;
	signal(2, changeflag);

	while (!g_flag)
		; 

	cout<<"g_flag="<<g_flag<<endl;
	return 0;
}

优化后:

没有优化:

这里,如果编译器进行优化,会把内存里的g_flag拷贝一份到寄存器里,那下一次判断直接从寄存器里拿。不用再去内存里拿,收到信号2后我们更改的是内存里的g_flag,但是我们while判断的是寄存器里的g_flag——寄存器屏蔽了内存

3.3 SIGCHLD信号

SIGCHLD信号是在Linux系统中用于进程间通信的一种机制。具体来说,当子进程终止或停止时,子进程会向其父进程发送SIGCHLD信号。这个信号是子进程状态改变时发送给父进程的信号,用于通知父进程其子进程的状态已经发生了变化。

父进程可以捕获这个信号,并通过调用如wait()waitpid()等函数来获取子进程的退出状态、终止原因等信息。SIGCHLD信号常用于以下几种情况:

  • 子进程终止,父进程需要回收子进程的资源。

  • 父进程需要等待子进程的状态改变,比如子进程终止或停止。

  • 父进程需要在子进程终止后进行一些操作。

处理SIGCHLD信号时,通常会在信号处理函数中循环调用waitpid()函数来非阻塞等待子进程状态改变,以避免僵尸进程的产生。

有可能:有100个子进程,有50个退出了,50个还没有。那么在循环到51次时,waitpid会一直堵塞住,父进程就一直卡在那里,所以不能堵塞等待

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

void CleanupChild(int signum)
{
    while (true)
    {
        pid_t rid = waitpid(-1, nullptr, WNOHANG); // -1 : 回收任意一个子进程;这里非堵塞
        if (rid > 0)//等待成功
        {
            std::cout << "wait child success: " << rid << std::endl;
        }
        else if (rid <= 0)
            break;
    }
}

int main()
{
    signal(SIGCHLD, CleanupChild);
    for (int i = 0; i < 100; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            // child
            int cnt = 5;
            while (cnt--)
            {
                std::cout << "I am child process: " << getpid() << std::endl;
                sleep(1);
            }
            std::cout << "child process died" << std::endl;
            exit(0);
        }
    }

    // father
    while (true)
        sleep(1);
    return 0;
}

事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用

  signal(SIGCHLD, SIG_IGN);//直接这样就行

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

kotlin 知识点 七 泛型的高级特性

对泛型进行实化 泛型实化这个功能对于绝大多数Java 程序员来讲是非常陌生的&#xff0c;因为Java 中完全没有这个概 念。而如果我们想要深刻地理解泛型实化&#xff0c;就要先解释一下Java 的泛型擦除机制才行。 在JDK 1.5之前&#xff0c;Java 是没有泛型功能的&#xff0c;…

go基本语法

跟Java比较学习。 hello word 示例代码 test1.go文件&#xff1a; // 包路径 package main// 导入模块&#xff0c;下面两种都行 import ("fmt" ) import "log"// main方法 func main() {log.Print("hello word !!!")fmt.Print("hello …

Linux内核,slub分配流程

我们根据上面的流程图&#xff0c;依次看下slub是如何分配的 首先从kmem_cache_cpu中分配&#xff0c;如果没有则从kmem_cache_cpu的partial链表分配&#xff0c;如果还没有则从kmem_cache_node中分配&#xff0c;如果kmem_cache_node中也没有&#xff0c;则需要向伙伴系统申请…

冯诺依曼体系结构 ──── linux第8课

目录 冯诺依曼体系结构 关于冯诺依曼&#xff0c;必须强调几点&#xff1a; 冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系 输入单元&#xff1a;包括键盘, 鼠标&#xff0c;网卡,扫…

国标28181协议在智联视频超融合平台中的接入方法

一. 国标28181介绍 国标 28181 协议全称是《安全防范视频监控联网系统信息传输、交换、控制技术要求》&#xff0c;是国内视频行业最重要的国家标准&#xff0c;目前有三个版本&#xff1a; 2011 年&#xff1a;推出 GB/T 28181-2011 版本&#xff0c;为安防行业的前端设备、平…

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(四)

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷&#xff08;四&#xff09; 第一部分&#xff1a;网络平台搭建与设备安全防护任务书第二部分&#xff1a;网络安全事件响应、数字取证调查、应用程序安全任务书任务 1&#xff1a;应急响应&…

音乐游戏Dance Dance Revolution(DDR)模拟器

文章目录 &#xff08;一&#xff09;Dance Dance Revolution&#xff08;1.1&#xff09;基本情况&#xff08;1.2&#xff09;机体 &#xff08;二&#xff09;模拟器&#xff08;2.1&#xff09;主程序&#xff08;2.2&#xff09;模拟器主题 &#xff08;三&#xff09;曲谱…

货车一键启动无钥匙进入手机远程启动的正确使用方法

一、移动管家货车无钥匙进入系统的使用方法 基本原理&#xff1a;无钥匙进入系统通常采用RFID无线射频技术和车辆身份识别码识别系统。车钥匙需要随身携带&#xff0c;当车钥匙靠近货车时&#xff0c;它会自动与货车的解码器匹配。开门操作&#xff1a;当靠近货车后&#xff0…

vscode如何使用鼠标滚轮调整字体大小

1.打开设置 2.搜索Font Ligatures 3.编辑配置文件 4.修改代码并保存 修改前 修改后 在最后一行添加&#xff1a;“editor.mouseWheelZoom”: true 记得在上一行最后&#xff0c;加上英文版的“,”逗号 5.配置成功&#xff0c;再次按Ctrl鼠标滚轮便可以缩放了。

视频裂变加群推广分享引流源码

源码介绍 视频裂变加群推广分享引流源码 最近网上很火&#xff0c;很多人都在用&#xff0c;适合引流裂变推广 测试环境&#xff1a;PHP7.4(PHP版本不限制) 第一次访问送五次观看次数&#xff0c;用户达到观看次数后需要分享给好友或者群,好友必须点击推广链接后才会增加观看次…

redis小记

redis小记 下载redis sudo apt-get install redis-server redis基本命令 ubuntu16下的redis没有protected-mode属性&#xff0c;就算sudo启动&#xff0c;也不能往/var/spool/cron/crontabs写计划任务&#xff0c;感觉很安全 #连接到redis redis-cli -h 127.0.0.1 -p 6379 …

IDEA关闭SpringBoot程序后仍然占用端口的排查与解决

IDEA关闭SpringBoot程序后仍然占用端口的排查与解决 问题描述 在使用 IntelliJ IDEA 开发 Spring Boot 应用时&#xff0c;有时即使关闭了应用&#xff0c;程序仍然占用端口&#xff08;例如&#xff1a;4001 端口&#xff09;。这会导致重新启动应用时出现端口被占用的错误&a…

Vue04

自定义指令 directives是Vue的一个配置项 这里写自定义指令 自定义指令被调用的时机 指令与元素成功绑定时 指令所在的模板被重新解析时 函数式 <span v-big"n"></span> directives:{ big(element,binding){ element.innerText bingin…

岳阳市美术馆预约平台(小程序论文源码调试讲解)

第4章 系统设计 一个成功设计的系统在内容上必定是丰富的&#xff0c;在系统外观或系统功能上必定是对用户友好的。所以为了提升系统的价值&#xff0c;吸引更多的访问者访问系统&#xff0c;以及让来访用户可以花费更多时间停留在系统上&#xff0c;则表明该系统设计得比较专…

Linux 基本开发工具的使用(yum、vim、gcc、g++、gdb、make/makefile)

文章目录 Linux 软件包管理器 - yum理解什么是软件包和yum如何查看/查找软件包如何安装软件如何实现本地机器和云服务器之间的文件互传如何卸载软件 Linux 编辑器 - vim 的使用vim 的基本概念vim 的基本操作vim 命令模式各命令汇总vim 底行模式各命令汇总vim 的简单配置 Linux …

4部署kibana:5601

kibana 是一个基于浏览器页面的Elasticsearch前端展示工具&#xff0c;, 是一个开源和免费的工具 Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面, 可以帮你汇总、分析和搜索重要数据日志 1.安装-所有的es节点 # tar xf kibana-6.4.1-linux-x86_64.t…

1.介绍一下TCP/IP模型和OSI模型的区别【中高频】

OSI模型 将 这个协议 划分为7个不同的层级&#xff0c;分别为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层&#xff0c;而TCP/IP模型只有4个层级&#xff0c;分别为网络接口层、网络层、传输层和应用层&#xff0c;其中应用层在用户态&#xff0c;传输层及以下…

【反爬】拦截comBusiness.js disable-devtool.js

一、现象 无法使用ctrls保存网页&#xff0c;但是可以在设置菜单中可以保存&#xff1b;无法使用F12和ctrlshifti打开开发者窗口&#xff0c;但是可以在设置菜单中打开&#xff1b;打开开发者窗口后网站快速关闭&#xff0c;说明被检测到了&#xff1b; 二、涉及js 打开设置菜…

【11】子网

区块链子网概述 什么是子网&#xff1f; 子网是在较大网络上下文中运行的较小网络&#xff0c;因此由对应的“主网”&#xff0c;主网即包含多个子网的较大网络或具有隶属关系的上一级网络。子网允许在主网中进行一些独立的事务或控制网络参数。 对于互联网而言&#xff0c;…

爬虫基础入门之爬取豆瓣电影Top250-Re正则的使用

网址:豆瓣电影 Top 250 本案例所需要的模块 requests (用于发送HTTP请求)re (用于字符串匹配和操作) 确定需要爬取的数据 &#xff1a; 电影的名称电影的年份电影的评分电影评论人数 一. 发送请求 模拟浏览器向服务器发送请求 准备工作 -分析页面: F12 or 右击点击检查 查看…