【Linux】信号

  🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12625432.html

9efbcbc3d25747719da38c01b3fa9b4f.gif

目录

信号和信号量

 信号

 信号的处理

信号捕捉

 信号的产生

系统调用 

 signal

raise 

abort 

由软件条件产生信号 

alarm

 硬件异常产生信号

Core、Term

 阻塞信号

信号其他相关常见概念 

在内核中的表示

sigset_t 

 信号集操作函数

 sigprocmask

 sigpending

 捕捉信号

 内核如何实现信号的捕捉

sigaction

可重入函数

 volatile

SIGCHLD信号 


前言

    💬 hello! 各位铁子们大家好哇。

             今日更新了Linux信号的内容
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

信号和信号量

二者之间没有任何关系。

 信号

84b89208dc5b4040a763da034c0e3d23.png

通过 kill -l 可以查看所有信号 

其中,1-31号信号是普通信号。34以上的信号为实时信号。

这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal

基本结论:

 信号:Linux系统提供的一种,向指定进程发送特定事件的方式。

信号的产生和进程是异步的。即进程不知道什么时候会收到信号。

信号可以随时产生。

如果进程做着别的事,可以暂不处理信号,等到合适的时候再处理。

 信号的处理

信号处理有三种情况:

  1. 默认动作
  2. 忽略动作
  3. 自定义处理--信号的捕捉

信号捕捉

signal

ab430f9750ca44f6b99ec76d69b0c484.png

1ab8d85084ff482f912dc609af3c2172.png

运行上面代码,在另一个终端上输入kill -2 指令,数字也可换成对应的宏名称。发现输出了hander函数的内容。

9ac48e8f59284768ac7328aa5d45d988.png214c9314d96d4cada06b6b9d9784813f.png

signal是用来进行信号捕捉的。参数1是信号的编号,参数2是函数指针。如果进程收到参数1对应的信号,就会执行参数2对应的方法。

可以对多个信号进行捕捉,a008f9ebb78b4e7ebe30e96e1458fcf2.png03fa37ed37fd47ff916a314f227b5e9d.png

ae13ae6bc9a4461cb3a12e22fa8899a9.png997103e405bd4759930c1d7587c0ff26.png

98dc1b1c5c454b6aa9da411253daa20f.png1e7d58c6fd3e43be987f3469f6b9c034.pngbfaf5de76a424f7cadbed20fbddf0564.png

由上图,2号信号的默认动作是终止进程。上面没有对2号信号(SIGINT)进行捕捉,就会执行2号信号的默认动作。 

4dcbeff36b0b448f8e4678f8b59dc7dd.png

de8a264bf56646509f21a42b98e2f22d.png

我们对2号信号进行捕捉。然后按ctrl+c。发现执行了hander函数。所以实际上,在命令行上按ctrl+c就是给进程发送2号信号。 

在命令行上按ctrl+\就是发送3号信号。他们都是用来终止进程的。

信号的保存和发送理解:

进程pcb中,是用位图来保存信号的。收到什么信号,就把对应比特位上的数字变为1。

发送信号:修改指定进程pcb中的信号的指定位图的比特位。

3744973a373849d7bce2771863e6967e.png

 信号的产生

信号的产生方式:

  1. 通过kill命令,向指定进程发送指定信号
  2. 键盘可以产生信号。ctrl+c(SIGINT)、ctrl+\(SIGQUIT)
  3. 系统调用
  4. 软件条件
  5. 异常

系统调用 

kill

dc45431a547841d6aa8a0600f557d03e.png

参数1是指定进程,参数2是指定信号。作用是向指定进程发送指定信号。

dc39a8140ddb4cacb8bdef47db787c7f.png7c973cbda3964e6a8867789532365fed.png

如上图,一个循环打印,另一个用kill函数。运行结果如下图,使用kill函数终止了进程。

5843897d5bf04f959147987bcf20e09d.png

7f6174c9894c44a481f2cd836fc9d866.png8517733e7c98464aa6004fa544a96abc.png

raise 

6ef2d179ed5f4ce4ae95734118b2c296.png

作用:谁调用这个函数,它就给调用者发送指定信号。

kill是给任意进程发送任意信号。如果想给自己发送信号,相当于kill(getpid(),sig)==raise(sig)

 11062809930340c098f82b8dee60171b.pnga74a7941975d4d0b9f2fcc110230af8f.png

上面是用raise来给自己发送信号的例子。

raise不重要,kill重要。 

abort 

c2bcb50c73334baa9a8f6a02c2c739a5.png

8a50aad970c44f12bc34be5808677f74.png

21667888199c45639086c96f0ec22420.png

运行上面代码,两秒后进程就终止了。

572461c3bd5b4317a033c6881eace602.png

abort相当于6号信号。作用是给自己发送6号信号(SIGABRT),终止进程。

d387145f773d429e8282d26768dfecf2.png

19a6d3bbe78b4229b50259b6c6e8924b.png

 运行代码,发现打印了一次后就终止了,不会因为自定义捕捉而一直打印下去。所以它是一个例外。

如果我们把所有信号都捕捉了,是不是就无法关闭进程了?

其实不会,上面的例子就是证明,实际上,还有别的信号可以关闭进程,如9号信号。9号信号不允许自定义捕捉。

 上面讲的都是信号产生的方式,都是通过用户完成的。但是真正发送信号的只有一个人:系统。

因为发送信号的本质是修改进程pcb中的信号位图,只有OS才有资格修改。

由软件条件产生信号 

SIGPIPE是一种由软件条件产生的信号。下面介绍alarm函数和SIGALRM信号。

alarm

86f383812d9a42d5b97a8db1d1977199.png 2be97c82cd824ec1b6f6ba882b00931e.png

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动 作是终止当前进程。 

d5a9dde03ec7423285af96e699e6245a.png

运行结果如下图:

4d223eca8f924f069ee083cd8871d413.png

 我们修改代码成如下:23dd00433dc7413280a33e2b3d97c8d6.png876d93db928c4c4d80f5cc364688a995.png

我们让循环单纯++,不再打印,直到闹钟响了再打印。结果发现cnt的值比前面大了很多。这是因为IO很慢。 

64380d8c8c5b4699a6b0f1fe848f45ba.png 44156547be874dacb1b62cd9f59b97a1.png

alarm(0)表示0秒后超时,也就是时间是当前时间。所以alarm(0)表示取消闹钟。alarm返回值是上一个闹钟的剩余时间。

e010d8cc3c9641babed9db8ab30aba6f.png70c1b16460dd4f6cb4121c2d0965149a.png

如上图,表示4秒后取消前一个10秒的闹钟,设置一个2秒的新闹钟。

6688cf2f0e034d26a6bc1a669558a249.png 358f1abd6d1b4969a973217d645407a1.png

由上图结果可得,闹钟设置一次,默认就触发一次。 

c64b625904a54f3ba3def14143e6b125.pngdb7f9657000244fe8c683b2c7169c93d.png

我们在hander里面也设置闹钟,相当于每次闹钟响了,就再设置一个闹钟,这样就有了一个常设性的闹钟了。

 硬件异常产生信号

70fc8b80791f431d9423dd5a6875c24c.png98619f953bcc4271bca07464013bdc40.pngb6d2281f402d4cc7917d449ab02e83b9.pngde11e42574be427697613206dbae1cbe.png

运行上面代码都会崩溃,一个是除以0,一个是野指针。 一个报浮点数错误,另一个报段错误。崩溃了会退出,默认是终止进程。

之所以崩溃,是因为非法访问、操作,导致OS向进程发送信号了。

除以0,会发送8号信号(SIGFPE)。野指针会发送11号(SIGSEGV)。

67705bd063794f72a664cf1ff9189294.png

如上图,我们将8号信号捕捉,运行代码后发现,会死循环一直打印 。2ac578b7a34a4c0292acf059aecc4039.png 

再将注释互换,如下图,结果也是死循环打印。

0d7e6dbf15ad4b21b37c62457b3276ea.png81904689aba6488a98e496c60df5373d.png

所以程序崩溃了可以不退出,通过捕获异常即可。但是推荐终止进程。

寄存器只有一套,但是寄存器里的数据是属于每一个进程的---硬件的上下文保存和恢复。

如果我们捕获异常,不让进程退出,进程就要调度,进程要调度就得切换。进程要切换,就会把cpu里的寄存器值作保存和恢复。这意味着每次保存进程,就会把异常保存起来,因为进程不退,又会把异常恢复。所以OS就会一直触发错误,就会一直循环打印了。

因此推荐终止进程,本质是为了释放进程的上下文数据。

Core、Term

c8ec41c35b5a41f7aabb6fd6fab1ceef.png

Core、Term都是终止进程的意思,那他们有啥区别呢?

Term:异常终止

Core:异常终止,但是它会帮我们形成一个类似debug文件。

7ab712812eb749289fb3fb1ccad91f16.png dc3197591f0d42fbafeee6681e6ce6ce.png

运行上面代码,结果报错了。也并没有形成类似的debug文件。因为该功能默认是关闭的。

如何查看呢?指令 ulimit -a 可以查看系统中对于普通用户能使用资源对应的限制。下面可以看到core file size 大小是0,所以云服务器默认不允许我们形成core文件。c9582319c33a47a78e28ff0ff64ca3cb.png

1bf92466d88741649211db694af39585.png

通过ulimit -c 数字 指令,这样core file选项就打开了。此时再运行程序,就有core文件了。8c5ef7efd1774d26accd74fe28bec344.png

Core文件就是进程退出时候的镜像数据,这个功能叫核心转储。

核心转储其实是进程异常时,核心数据转而存储到磁盘上。

16145ee31a5c46579b99d4745528219c.png

所以上面图中,core dump标志位为0时表示没有核心转储,为1表示有核心转储。 

如果进程是Term就没有核心转储。如果是Core并且打开了核心转储功能,就有核心转储。

 2fd1eb3bdb004ff4bbea726e6f37583a.png

 我们把Makefile文件里g++带上-g选项,允许被调试。

969b5964b5d145a9bee106392394096c.png

当程序里面有除0错误时,并且有了core文件。我们gdb进行调试。 输入 core-file core 给gdb加载core文件,我们就可以直接定位到程序出错的地方。

45c1ac2fc49f4c40b74cc809ecf7087f.png

所以core是协助我们进行debug的文件,这种操作也叫事后调试。

c23b355ae87b42adb7bb04f7f11dd1a7.pngc2ff7476993441a2af3a6ae75cec901e.png

 运行代码,结果core dump是0。8号信号的终止动作是core,为什么这里结果是0?因为core功能默认是关闭的,我们没打开。打开功能后结果就是1了。

 阻塞信号

信号其他相关常见概念 

  • 实际执行信号的处理动作称为信号递达 (Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block )某个信号。阻塞和有没有未决,二者没有关系
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

在内核中的表示

信号在内核中的表示示意图

12bb14ff09a14f369f1b77c2d3a9f15f.png

每个进程pcb中会维护三张表。

4c35d1a21ec249e4b5dc7de7dad559f9.png

pending表就是未决表,它是一张位图。有32个比特位,使用其中31位。 假设最左边一位不用,从右往左数,第几个比特位就代表第几个信号。为1就是处于未决状态,为0则不处于。

8c5ad0065f7e4294a97ded51fcb9a0e3.png

handler表就是函数指针数组。handler表里面写的就是该信号如何被处理,信号的编号就是数组的下标。

因此我们之前的signal函数调用,如signal(2,handler)就表示用2号编号在handler数组里索引,把自己写的handler函数地址传进handler表里,这样系统就知道你要怎么处理对应信号了。

6f4d8c1a5edf4f0ebe3e0464be3355a5.png

block表也是一张位图,和pending表类型一样,也只使用其中31位。

fe2149407abe4e50aa7108cce1f42c17.png

这三张表要横着,对应着编号看。

因此,两张位图+一张函数指针数组就可以让进程识别信号。 

sigset_t 

每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

sigset_t就是Linux给用户提供的一个用户级的数据类型,禁止用户直接修改位图。

 信号集操作函数

 sigset_t类型内部如何存储这些bit依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量

  • int sigemptyset(sigset_t *set);  把位图全部清空
  • int sigfillset(sigset_t *set);    把整个位图全部置1
  • int sigaddset (sigset_t *set, int signo);  把特定信号设置进该集合里。比如信号是5,就是把第五个bit位置1。
  • int sigdelset(sigset_t *set, int signo);  把特定位置置0,如果是1就置0,如果是0就不动
  • int sigismember(const sigset_t *set, int signo);  判断一个信号是否在集合当中

这四个函数都是成功返回0,出错返回-1。

sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

注意,在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。

 sigprocmask

59fdd174e762484fbfd6496c6c60a3f9.png

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。 

返回值:若成功则为0,若出错则为-1

set是输入型参数,oldset是输出型参数。

如果oldset是非空指针,则读取进程的当前信号屏蔽字通过oldset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oldset和set都是非空指针,则先将原来的信号屏蔽字备份到oldset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。f471866c28f34f3c8d88312752cd2f6c.png

 sigpending

d6a25c1eceee4a75b04a69c7b3dbb144.png

sigpending的作用是获取当前进程的pending位图,它的参数是输出型参数。

调用成功则返回0,出错则返回-1。

91d7df5addca41eda0dfe1da011e4800.png107199e254834b1ea4fa4b3e5d55ae99.pngd12b32cc046e4601b103e179e834eb2e.png

 运行上面代码,发送2号信号后,因为此时2号信号已经被屏蔽了,所以pending位图里2号信号对应位置就由0置1了。

解除信号屏蔽,一般会立即处理当前被解除的信号(如果被pending)

pending位图对应的信号也要清0,在信号递达之前。

完整代码

#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>void PrintPending(sigset_t &pending)
{std::cout << "curr process[" << getpid() << "]pending: ";for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo)){std::cout << 1;}else{std::cout << 0;}}std::cout << "\n";
}void handler(int signo)
{std::cout << signo << " 号信号被递达!!!" << std::endl;std::cout << "-------------------------------" << std::endl;sigset_t pending;sigpending(&pending);PrintPending(pending);std::cout << "-------------------------------" << std::endl;
}int main()
{// 0. 捕捉2号信号signal(2, handler); // 自定义捕捉signal(2, SIG_IGN); // 忽略一个信号signal(2, SIG_DFL); // 信号的默认处理动作// 1. 屏蔽2号信号sigset_t block_set, old_set;sigemptyset(&block_set);sigemptyset(&old_set);sigaddset(&block_set, SIGINT); // 1.1 设置进入进程的Block表中sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!int cnt = 15;while (true){// 2. 获取当前进程的pending信号集sigset_t pending;sigpending(&pending);// 3. 打印pending信号集PrintPending(pending);cnt--;// 4. 解除对2号信号的屏蔽if (cnt == 0){std::cout << "解除对2号信号的屏蔽!!!" << std::endl;sigprocmask(SIG_SETMASK, &old_set, &block_set);}sleep(1);}
}

 捕捉信号

d92118c110b84bb890a1ced90e10476c.png

如果一个信号不做任何处理,它默认就是SIG_DFL选项。

SIG_IGN选项就是忽略一个信号。

 内核如何实现信号的捕捉

6bb9791f14024fcf90ef93969ebccd72.png

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行 main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号 SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。 

信号捕捉的过程:要经历4次状态的切换。

在内核态切换回用户态的时候,进行信号的检测和处理。

 再谈地址空间

cdcaf260452043098e78b1c1d57e6222.png

开机时,操作系统是最先加载的软件,所以OS也要在内存中。内核级页表是将内核地址空间和OS之间进行映射的。因此OS本身就在我的进程地址空间中。

如果有多个进程,不会再创建一个新的内核级页表,而是共用一张。

sigaction

c39469be3bf7420f88f60898ec2dc5e1.png88e82c504a29410a821b806504efaf86.png

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signum 是指定信号的编号。act是输入型参数,是结构体类型,结构如上图,这里只了解结构体里的第一行,即函数指针。所以act传的是函数指针。oldact是输出型参数,用来保存旧的结构体。

sigaction本质就是修改信号的handler表。  

sigaction跟前面的signal本质作用是一样的,都是对特定信号进行捕捉。

ca6a5190273849319100ef47cd6a643b.png875b18d2ce3d4a4db4fae1fd8a434435.png

运行后,ctrl+c发送2号信号,发现2号信号被捕捉了,执行了handler函数。

 97af8f7a994a4465b7a792f0c2213fb9.png

我们把上面的handler函数修改一下,添加sleep。155c1fc01ecd4477ac56ff5a2e41ddc2.png 运行后一直按ctrl+c,发现只执行了一次handler函数。因为当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来 的信号屏蔽字。这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。

#include<iostream>
#include<signal.h>
#include<unistd.h>void Print(sigset_t &pending)
{for(int sig = 31; sig > 0; sig--){if(sigismember(&pending, sig)){std::cout << 1;}else{std::cout << 0;}}std::cout << std::endl;
}void handler(int signum)
{std::cout << "get a sig: " << signum << std::endl;while(true){sigset_t pending;sigpending(&pending);Print(pending);sleep(1);// sleep(30);// break;}// exit(1);
}int main()
{struct sigaction act, oact;act.sa_handler = handler;sigemptyset(&act.sa_mask);  // 如果你想在处理2号时(OS对2号自动屏蔽),同时对其他信号也进行屏蔽sigaddset(&act.sa_mask,3);act.sa_flags = 0;sigaction(2, &act, &oact);while(true){std::cout << "I am a process, pid: " << getpid() << std::endl;sleep(1);}return 0;
}

38c822748a904d629108ed0d7c086b78.png

 除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需 要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。代码如上,通过sa_mask字段屏蔽了3号信号。

可重入函数

bf20cf4c73fb4b81989e09d83a30bc75.png

  • main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从 sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后向链表中插入两个节点,而最后只有一个节点真正插入链表中了。 node2丢失,内存泄露了。
  • 像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之, 如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。

如果一个函数符合以下条件之一则是不可重入的:

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

 volatile

47a9a0dd48454c6ab15522e09f924971.png5e04b08c4cac46a5bf670d85eaa918f8.png

运行上面代码,按下ctrl+c后,信号被捕捉,gflag就被修改了,while循环条件为假,程序就结束了。 

47f6618b95e94a37a1aaa36c9ae1a778.png

 Linux系统中g++是有各种优化级别的。

 默认优化级别是-O0,即没有优化。

ad2803ace13e48a19ed34198a0840e57.png

 优化后,发现按ctrl+c 程序不会结束。因为main执行流判定代码里没有对gflag进行修改,觉得不用每次都从内存拿数据,直接在第一次拿的时候,把gflag的值优化到寄存器里,从此之后,每次while检测只检测寄存器里的值。当收到信号后修改gflag的值,修改的是内存里的gflag,就导致寄存器隐藏了内存中的真实值。这是编译器过度优化导致的问题。f66addda3b42481183f3c8376b1eba28.png

为了保持内存的可见性,就有了volatile关键字。

71c987f6de7c4fb189fa8250a9fbc3bf.png5a3e9513455f48dd8276e09e7565f3a1.png

有了volatile修饰,就没有被优化的问题了。

volatile的作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作 

SIGCHLD信号 

子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。 

2cb66197b08348a893f6755f0493605e.png

 如果有多个子进程同时退出,此时会同一时间向父进程发送多个SIGCHLD信号。普通信号是用pending位图接收信号的,收到了多个SIGCHLD信号,但pending位图只会记录一次,导致最后只会回收一个子进程。所以waitpid等待时,外面需要套一层while循环,不断回收。

如果有的子进程退出,有的永远不退出,此时就要用非阻塞等待。否则就会阻塞在信号捕捉里,父进程永远做不了别的事情。


 wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进 程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一下,程序实现复杂。 

要想不产生僵尸进程还有另外一种办法:父进程调用signal将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。

cc131fe31b364c30b09a24994c024faa.png

系统默认的忽略动作和用户用signal函数自定义的忽略通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证 在其它UNIX系统上都可用。

如果不关心子进程的退出信息,不想产生僵尸进程,就可以用这样做。

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

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

相关文章

【国内中间件厂商排名及四大中间件对比分析】

国内中间件厂商排名 随着新兴技术的涌入&#xff0c;一批国产中间件厂商破土而出&#xff0c;并在短时间内迅速发展&#xff0c;我国中间件市场迎来洗牌&#xff0c;根据市占率&#xff0c;当前我国中间件厂商排名依次为&#xff1a;东方通、宝兰德、中创股份、金蝶天燕、普元…

【题解】CF2033G

题目 CF2033G 分析 一道很显然是树形dp的题&#xff0c;但非常恶心QwQ。   先不管复杂度&#xff0c;找找递推关系&#xff0c;一种很直接的想法如下&#xff08;我觉得是错误的&#xff09;&#xff1a; d p [ i ] [ k ] m a x ( d p [ f a i ] [ k − 1 ] , d p [ s o …

SpringBoot之定时任务

1. 前言 本篇博客是个人的经验之谈&#xff0c;不是普适的解决方案。阅读本篇博客的朋友&#xff0c;可以参考这里的写法&#xff0c;如有不同的见解和想法&#xff0c;欢迎评论区交流。如果此篇博客对你有帮助&#xff0c;感谢点个赞~ 2. 场景 我们讨论在单体项目&#xff0c…

【日志】力扣58.最后一个单词的长度//14.最长公共前缀//28. 找出字符串中第一个匹配项的下标

2024.11.6 【力扣刷题】 58. 最后一个单词的长度 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/length-of-last-word/?envTypestudy-plan-v2&envIdtop-interview-150 int lengthOfLastWord(char* s) {int count 0;for (int i strlen(s) - 1; i…

智能家居的未来:AI让生活更智能还是更复杂?

内容概要 智能家居的概念源于将各种家居设备连接到互联网&#xff0c;并通过智能技术进行控制和管理。随着人工智能的迅速发展&#xff0c;这一领域也迎来了前所未有的机遇。从早期简单的遥控器到如今可以通过手机应用、语音助手甚至是环境感应进行操作的设备&#xff0c;智能…

1. 初步认识 Java 虚拟机

一、前言 其实一直都想系统性的学习一下 JVM&#xff0c;尝试过很多次&#xff0c;最终没能坚持下来&#xff0c;现在已经工作多年&#xff0c;发现对于 JVM这块知识还是很薄弱&#xff0c;不利于职业长远发展&#xff0c;并且之前掌握的都是一些零散的知识&#xff0c;没能形…

数据结构之二叉树的链式结构——递归的暴力美学

1. 实现链式的二叉树结构 我们之前用顺序表里面数组的底层结构实现了二叉树中堆的结构&#xff0c;但是不是所有的二叉树都具有着堆的性质&#xff0c;所以我们现在需要一个链式结构来描述普遍的二叉树。其底层结构类似一个链表&#xff0c;但是每一个结点由单个区域&#xff…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-31

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-31 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-31目录1. Large Language Models for Manufacturing摘要创新点算法模型实验效果&#xff08;包含重要数据与结论&#xff09;推荐…

利用SpringBoot构建城镇住房保障平台

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理城镇保障性住房管理系统的相关信息成为必然…

【笔记】扩散模型(九):Imagen 理论与实现

论文链接&#xff1a;Photorealistic Text-to-Image Diffusion Models with Deep Language Understanding 非官方实现&#xff1a;lucidrains/imagen-pytorch Imagen 是 Google Research 的文生图工作&#xff0c;这个工作并没有沿用 Stable Diffusion 的架构&#xff0c;而是级…

Windows下载安装Ollama本地运行大模型,新手详细

目录 1. 下载安装Ollama2. 环境配置- 关闭开机自启动&#xff08;可选&#xff09;&#xff1a;- 配置环境变量&#xff08;必须&#xff09;&#xff1a;- 配置端口&#xff08;可选&#xff09;&#xff1a;- 允许浏览器跨域请求&#xff08;可选&#xff09;&#xff1a; 3.…

代码随想录算法训练营Day55 | 图论理论基础、深度优先搜索理论基础、卡玛网 98.所有可达路径、797. 所有可能的路径、广度优先搜索理论基础

目录 图论理论基础 深度优先搜索理论基础 卡玛网 98.所有可达路径 广度优先搜索理论基础 图论理论基础 图论理论基础 | 代码随想录 图的基本概念 图的种类 大体分为有向图和无向图。 图中的边有方向的是有向图&#xff1a; 图中的边没有方向的是无向图&#xff1a; 图…

牛客练习赛131(dp,dfs,bfs,线段树维护等差数列)

文章目录 牛客练习赛131&#xff08;dp&#xff0c;dfs&#xff0c;bfs&#xff0c;线段树维护等差数列&#xff09;A. 小H学语文B. 小H学数学&#xff08;dp、偏移值&#xff09;C. 小H学生物&#xff08;DFS、树上两点间路径的距离&#xff09;D. 小H学历史(BFS)E. 小H学物理…

干货分享篇:Air780EP的硬件设计原理全解析(上)

一、绪论 Air780EP是一款基于移芯EC718P平台设计的LTE Cat 1无线通信模组。支持FDD-LTE/TDD-LTE的4G远距离无线传输技术。另外&#xff0c;模组提供了USB/UART/I2C等通用接口满足IoT行业的各种应用诉求。 二、综述 2.1 型号信息 表格 1&#xff1a;模块型号列表 2.2 主要性能…

Python将Word文档转为PDF

将word转pdf&#xff0c;只能使用办公工具&#xff0c;但是这些工具大都是收费。因此想用python 将word转pdf,发现很好用特此记录下。方法一&#xff1a;使用docx2pdf模块将docx文件转为pdf 要实现这样的功能&#xff0c;需要用到的就是 docx2pdf 这个python第三方库。对于doc…

无惧任天堂的法律威胁:Switch模拟器Ryujinx v1.2.72版发布

此前任天堂向多个提供 Nintendo Switch 模拟器项目发送律师函甚至直接起诉&#xff0c;要求这些项目立即停止更新、删除以及向任天堂提供经济赔偿。其中 Ryujinx 项目已经在 2024 年 10 月 1 日因任天堂的法律威胁而放弃项目&#xff0c;不过很快就有分叉版本出现&#xff0c;这…

JavaWeb——Web入门(6/9)-HTTP协议:协议解析(客户端的 HTTP 协议解析、服务端的 HTTP 协议解析、Web服务器的作用)

目录 概述 客户端的 HTTP 协议解析 服务端的 HTTP 协议解析 Web服务器的作用 概述 了解完 HTTP 协议的请求数据格式以及响应数据格式之后&#xff0c;接下来我们来讲了解 HTTP 协议的解析。 HTTP 协议的解析分为客户端和服务端两个部分&#xff0c;客户端浏览器中内置了解…

操作系统-实验报告单(2)

目录 1 实验目标 2 实验工具 3 实验内容、实验步骤及实验结果 一、自定义操作系统并启动 1. 最简单操作系统的编写并生成镜像文件 2.虚拟机启动操作系统 【思考题&#xff1a;1、仔细阅读helloos.nas&#xff0c;结合操作系统启动过程尝试分析它的作用&#xff1b;2、若…

城镇住房保障:SpringBoot系统优化技巧

3系统分析 3.1可行性分析 通过对本城镇保障性住房管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本城镇保障性住房管理系统采用SSM框架&#xff0c;JA…

FlyMcu串口下载STLink Utility

1、FlyMcu FlyMcu串口下载&#xff0c;同STC-ISP&#xff08;51单片机下载&#xff09;。 使用步骤&#xff1a; 1、STM32的USART1通过串口转usb连接到电脑 2、通过keil生成Hex、bin文件 生成bin、hex文件可参考 keil生成bin文件&#xff08;简单&#xff09;-CSDN博客 创建…