致前行的人:
要努力,但不着急,繁花锦簇,硕果累累都需要过程!
目录
1.信号概念
1.1生活角度的信号
2. 技术应用角度的信号
3.Linux操作系统中查看信号
4.常用信号发送
4.1通过键盘发送信号
4.2调用系统函数发送信号
4.3硬件异常产生信号
4.4软件条件产生信号
5.核心转储
6.不同信号的意义
7.阻塞信号
7.1信号常见概念:
7.2信号在内核中的表示
7.3sigset_t
7.4信号操作函数
7.5sigprocmask
7.6sigpending
7.7实例代码
8.捕捉信号
8.1如何实现信号的捕捉
8.2sigaction
9.可重入函数
10.volatile
11.SIGCHLD信号
1.信号概念
1.1生活角度的信号
你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”
当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取”。
在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”
当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:1. 执行默认动作(幸福的打开快递,使用商品)2. 执行自定义动作(快递是零食,你要送给你你的女朋友)3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)
快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话
2. 技术应用角度的信号
如何将上面的概念,迁移到进程中呢?
1.首先我们都知道,信号是给进程发的,那进程是如何识别信号的呢?因为进程本身就是程序员编写的属性的逻辑的集合,所以进程识别信号是通过程序员编码完成的;2.当进程收到信号的时候,进程可能正在执行更重要的代码,所以信号不一定会被立即处理;3.进程必须对信号具有保存能力;4.进程在处理信号的时候,会有三种动作(默认动作,自定义动作,忽略动作),当进程收到信号时就称之为信号捕捉
上面我们知道进程必须对信号具有保存能力,而进程要保存,应该保存在哪里呢?通过之前的学习我们知道,当一个进程被加载到内存的时候,操作系统会创建PCB对象,保存进程的相关属性,其中就一个字段是用来保存进程是否收到信号,当进程收到信号的时候就将该字段的对应信号的比特位置为1,所以发送信号并保存信号的本质是修改PCB中的信号位图。
PCB的管理者是OS,既然是要修改PCB中的内容,只能由操作系统自己进行修改,所以在上层我们进行发送信号一定要调用操作系统提供的对应接口,所以在使用kill命令进行发送信号的时候一定在底层调用了对应的系统调用接口
3.Linux操作系统中查看信号
kill-l 查看所有的信号:如图所示
【1,31】称为称为普通信号【34,64】称为实时信号
4.常用信号发送
4.1通过键盘发送信号
1. 用户输入命令,在Shell下启动一个前台进程。
用户按下 Ctrl-C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程,前台进程因为收到信号,进而引起进程退出
#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
while(true)
{
cout << "我是一个进程" << getpid() << endl;
}
return 0;
}
按Ctrl + c时本质上就是给进程发送2号信号,操作系统从键盘上捕捉到信号 ,然后终止前台正在运行的进程。
验证ctrl + c是2号信号
下面介绍一个函数是专门进行捕捉信号:signal
函数原型:
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
参数介绍:
signum:要进行捕捉的信号
handler:捕捉信号之后采用回调的方式执行要采取的动作
#include<iostream> #include<signal.h> #include<unistd.h> using namespace std; void handler(int signao) { cout<< "捕捉了一个信号,信号编号是:"<< signao << endl; } int main() { signal(2,handler); while(true) { cout << "我是一个进程" << getpid() << endl; sleep(1); } return 0; }
2. 用户输入命令,在Shell下启动一个前台进程。
用户按下 Ctrl-\ ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程,前台进程因为收到信号,进而引起进程退出
#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
while(true)
{
cout << "hello world" << getpid() <<endl;
sleep(1);
}
return 0;
}
Ctrl + \对应的就是3号信号
4.2调用系统函数发送信号
1.调用函数kill
参数介绍:
第一个参数:要终止进程的pid
第二个参数:要发送的信号
返回值:On success (at least one signal was sent), zero is returned. On error, -1 is returned, and errno is set appropriately.
使用介绍:调用kill函数终止掉任意进程
//mysignal.cc
static void Usage(const string& proc)
{
cout << "\nUsage: " << proc << " pid signo\n" << endl;
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
//使用手册说明
Usage(argv[0]);
exit(1);
}
pid_t pid = atoi(argv[1]);
int signo = atoi(argv[2]);
int n = kill(pid,signo);
if(n != 0)
{
perror("kill fail:");
}
return 0;
}
//mytest.cc
int main()
{
while(true)
{
cout << "我是一个正在运行的进程,pid:" << getpid() << endl;
sleep(1);
}
return 0;
}
2.调用函数raise
参数介绍:
sig:要发送的信号
返回值:returns 0 on success, and nonzero for failure.
使用说明:给自己发送任意信号
//给自己发送任意信号:
int main()
{
int count = 0;
while(count <= 10)
{
printf("%d\n",count++);
if(count >= 5)
raise(9);
}
return 0;
}
3.调用函数abort
使用说明:给自己发送指定信号
int main()
{
int count = 0;
while(count <= 10)
{
printf("%d\n",count++);
if(count >= 5)
abort();
}
return 0;
}
调用abort函数终止了该进程,但是给进程发送的是几号信号呢?下面我们来验证一下:
通过验证发现调用abort函数是给进程发送了6号信号
4.3硬件异常产生信号
信号产生不一定是用户显示的发信号,也有可能是硬件异常,操作系统主动发送信号
1.如下面这段代码:
int main()
{
printf("运行中.....\n");
sleep(1);
int a = 10;
a /= 0;
return 0;
}
当在代码出现除0的操作时,报了一个浮点数错误,并且终止掉了该进程
那为什么会终止进程呢?
这里是因为收到了OS系统发的信号,那操作系统发的是哪个信号呢?通过验证发现操作系统发的是8号信号:SIGFPE
那8号信号是什么呢?
通过调用signal函数验证8号信号:
void catchSig(int signo)
{
cout << "获取到一个信号,信号编号是:" << signo << endl;
sleep(1);
}
int main()
{
signal(SIGFPE,catchSig);
while(true)
{
printf("运行中.....\n");
sleep(1);
int a = 10;
a /= 0;
}
return 0;
}
通过验证也发现,当除0的时候,操作系统确实给该进程发送8号信号
此时就有了新的疑问,当进行一次/=0的时候,为什么会一直收到信号呢?,除此之外,操作系统是如何得知我/0了呢?
关于上面的两个问题,其实是跟硬件有关系的,下面我们具体来看看:
此时我们就回答了上面的一个问题,操作系统如何得知我/0了,对于另外一个问题/0一次,会一直捕捉
这是因为收到信号不一定会引起进程退出,没有退出,就有可能被调到,CPU内部的寄存器只有一份,但是寄存器中的内容,是属于进程的上下文,当进程被切换的时候,就有无数次寄存器被保存和恢复的过程,所以每一次恢复的时候,让OS系统识别到了CPU内部状态寄存器的溢出标志位为1,所以就出现了上面的现象,在/一次0之后,就会一直捕捉到8号信号
2.如下面的这段代码:
int main()
{
while(true)
{
printf("运行中.....\n");
sleep(1);
int* p = nullptr;
*p = 100; //野指针错误
}
return 0;
}
当出现野指针问题的时候,操作系统发的是几号信号呢?
void catchSig(int signo)
{
cout << "获取到一个信号,信号编号是:" << signo << endl;
exit(1);
}
int main()
{
signal(SIGFPE,catchSig);
while(true)
{
printf("运行中.....\n");
sleep(1);
int* p = nullptr;
*p = 100; //野指针错误
}
return 0;
}
通过验证发现,当出现野指针错误的时候操作系统发送的是11号信号
还是和上面相同的问题,操作系统是如何得知我写的代码有空指针的问题呢?
当空指针解引用从虚拟地址映射到物理空间的时候,CPU中有一个MMU的内存管理单元,当遇到空指针解引用的时候MMU就会出现异常,然后被操作系统得知,操作系统向进程发送信号
4.4软件条件产生信号
1.在管道通信的时候,当读端关闭,写端一直写的时候,OS系统就会向写端发送13号信号SIGPIPE终止掉写端,把这种产生信号的方式就被称为由软件条件产生的信号
2.调用函数alarm
使用说明:调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
int main()
{
alarm(1);
int cnt = 0;
while(true)
{
printf("%d\n",cnt++);
}
return 0;
}
验证alarm给进程发送SIGALRM信号:
int cnt = 0;
void catchSig(int signo)
{
cout << "获取到一个信号" << cnt << endl;
exit(1);
}
int main()
{
signal(SIGALRM,catchSig);
alarm(1);
while(true)
{
cnt++;
}
return 0;
}
注:alarm(0)可以取消闹钟
如何理解“闹钟”是软件条件产生的信号:
任意一个进程,都可以通过alarm系统调用在内核中设置闹钟,OS系统内可能存在着很多闹钟,那操作系统该如何管理这些闹钟:
所以说闹钟是软件条件产生的信号。
信号产生总结:
上面所说的所有信号产生,最终都要有OS来进行执行,为什么?
OS是进程的管理者
信号的处理是否是立即处理的?在合适的时候信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
需要被暂时记录下来,记录在进程的PCB中
一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?
知道,就如同在没有看到红灯之前就已经知道,看到红灯之后应该如何处理
如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?
当操作系统向进程发送信号时,本质上是在修改进程PCB的位图。
5.核心转储
概念:当进程异常退出的时候,进程在对应的时刻,将内存中的有效数据转储到磁盘中。
现象:
进程在退出的时候,一般会有两种情况,一种是Term表示正常退出,一种是core表示异常退出:
如图所示:
在云服务器上,默认进程如果是以core退出的,暂时看不到明显的现象,如果想要看到需要打开core file选项:
int main()
{
int a[10] = { 0 };
a[10000] = 100;
return 0;
}
数组越界访问时进程会异常退出:
当打开core file选项的时候就会产生一个临时文件:文件名=core + 进程的pid
该文件是可被调试的,那如何进行调试呢?
如图所示:
通过这种方式就能快速定位进程异常的位置了
6.不同信号的意义
通过上面的学习,我们已经了解了信号产生的几种方式,虽然产生信号的方式有所不同,但是大部分信号,默认处理动作都是终止进程,既然默认处理动作都是终止进程,那为什么还需要分那么多的信号呢?这是因为信号不同,代表不同的时间,当进程被终止的时候,能够更快的定位到进程是因为什么而终止的
注:kill -9信号是管理员信号,即使所有的信号被捕捉时,无法终止进程,9号信号也能终止掉该进程:
例:
void catchSig(int signo)
{
cout << "获取到一个信号" << signo << endl;
}
int main()
{
for(int signo = 1; signo <= 31; signo++)
{
signal(signo,catchSig);
}
while(true)
{
printf("我正在运行>>%d\n",getpid());
sleep(1);
}
return 0;
}
7.阻塞信号
7.1信号常见概念:
实际执行信号的动作称为信号递达
信号从产生到递达之间的状态称为信号未决
进程可以阻塞某个信号
被阻塞的信号在产生时将保持未决状态,直到信号解除对此信号的阻塞,才执行递达的动作
注:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是信号是在递达之后的一种可选的处理动作
7.2信号在内核中的表示
信号在内核的表示示意图:
执行逻辑:
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作,信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志,当信号产生时,信号被阻塞,所以暂时不递达,当信号没有被阻塞,并且该信号处于未决状态,该信号被递达
7.3sigset_t
从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。
7.4信号操作函数
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置为1,表示该信号集的有效信号包括系统支持的所有信号。
注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号
这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。
7.5sigprocmask
调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
SIG_BLOCK:set包含了我们希望添加到当前信号屏蔽字的信号,相当于 maks = mask | set
SIG_UNBLOCK:set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask = mask & ~ set
SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask = set
如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
7.6sigpending
#include <signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
7.7实例代码
#include<iostream>
#include<vector>
#include<signal.h>
#include<unistd.h>
#define MAX_SIGNUM 31
using namespace std;
static vector<int> sigarr = {2};
static void show_pending(const sigset_t &pending)
{
for(int signo = MAX_SIGNUM; signo >= 1; signo--)
{
if(sigismember(&pending, signo))
{
cout << "1";
}
else cout << "0";
}
cout << "\n";
}
static void myhandler(int signo)
{
cout << signo <<"号信号被递达" << endl;
}
int main()
{
//执行自定义动作
for(const auto& sig : sigarr)
{
signal(sig,myhandler);
}
//屏蔽指定信号:
sigset_t block,oblock,pending;
//初始化:
sigemptyset(&block);
sigemptyset(&oblock);
sigemptyset(&pending);
//添加要屏蔽的信号:
for(const auto& sig : sigarr)
{
sigaddset(&block,sig);
}
//开始屏蔽,设置进入内核:
sigprocmask(SIG_SETMASK,&block,&oblock);
//遍历打印pending信号集:
int cnt = 10;
while(true)
{
//初始化:
sigemptyset(&pending);
//获取:
sigpending(&pending);
//打印:
show_pending(pending);
sleep(1);
if(cnt-- == 0)
{
//对特定信号解除屏蔽,让操作系统递达该信号
sigprocmask(SIG_SETMASK,&oblock,&block);
cout << "恢复对信号的屏蔽,不屏蔽任何信号\n";
}
}
return 0;
}
运行截图:
8.捕捉信号
8.1如何实现信号的捕捉
信号在产生的时候,不会立即被处理,而是在合适的时候进行处理,所谓合适的时候就是从内核态返回到用户态的时候进行处理。
对于内核态和用户态又该如何理解呢?
如何标识当前进程的身份是用户态还是内核态:
进程是如何进入操作系统执行对应的方法呢?
每一个进程都有自己的地址空间,内核空间被映射到了[3,4]G的空间,当进程想要访问操作系统的接口时,只需要在自己的地址空间进行跳转就可以了
注:进入内核态的方式,第一种是在调用系统调用接口,第二种是进程切换的时候
捕捉信号的具体过程:
逻辑抽象:
8.2sigaction
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:
将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用
#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
void handler(int signo)
{
cout<< "get a signo" << signo << endl;
}
int main()
{
struct sigaction act,oact;
act.sa_handler = handler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
//捕捉2号信号
sigaction(SIGINT,&act,&oact);
while(true)
{
sleep(1);
}
return 0;
}
运行截图:
当某个信号处理的函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数,本章不详细解释这两个字段。
#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
void Count(int cnt)
{
while(cnt)
{
printf("cnt:%2d\r",cnt);
fflush(stdout);
cnt--;
sleep(1);
}
printf("\n");
}
void handler(int signo)
{
cout<< "get a signo" << signo<< "正在处理中……"<< endl;
Count(20);
}
int main()
{
struct sigaction act,oact;
act.sa_handler = handler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
//当正在处理某一种信号,也想屏蔽其它信号的时候,可以将屏蔽的信号加入sa_mask中
sigaddset(&act.sa_mask,3);
//捕捉2号信号
sigaction(SIGINT,&act,&oact);
while(true)
{
sleep(1);
}
return 0;
}
运行截图:
9.可重入函数
main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。
如果在main函数和在handler函数中insert函数被重复进入,出现问题,该函数就被称为是不可重入函数
如果在main函数和在handler函数中insert函数被重复进入,没有出现问题,该函数就被称为是可重入函数
如果一个函数符合以下条件之一则是不可重入的:
1.调用了malloc或free,因为malloc也是用全局链表来管理堆的。
2.调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
10.volatile
#include<stdio.h>
int quit = 0;
void handler(int signo)
{
printf("%d号信号正在被捕捉\n",signo);
printf("quit:%d\n",quit);
quit = 1;
printf("->%d\n",quit);
}
int main()
{
signal(2,handler);
while(!quit);
printf("我是正常退出的!!\n");
return 0;
}
标准情况下,键盘输入CTRL+c,2号信号被捕捉,执行自定义动作,修改quit = 1,while(条件不满足),退出循环,进程退出。
运行截图:
将该进程进行优化为-O3
mysignal:mysignal.c
gcc -o $@ $^ -O3 #-std=c++11
.PHONY:clean
clean:
rm -rf mysignal
运行截图:
优化情况下,键入 CTRL-C ,2号信号被捕捉,执行自定义动作,修改 quit=1 ,但是 while 条件依旧满足,进程继续运行!但是很明显quit肯定已经被修改了,但是为何循环依旧执行?很明显, while 循环检查的quit,并不是内存中最新的quit,这就存在了数据二异性的问题。 while 检测的quit其实已经因为优化,被放在了CPU寄存器当中。
CPU处理数据的过程分为:取指令,分析指令,执行指令,将结果写回到内存中
未优化前:
优化后:因为编译器识别到在main函数内部中并没有对quit进行修改,而是每次做一个判断,所以直接将quit变量中的内容写入到寄存器中,下一次再判断的时候直接使用寄存器变量中的值,所以while()条件一直为假,进程也就没有退出
如何解决呢?很明显需要 volatile
运行截图:
volatile的作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作
11.SIGCHLD信号
进程一章讲过用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一下,程序实现复杂。
其实,子进程在终止时会给父进程发SIGCHLD(17)信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait,清理子进程即可。
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void Count(int cnt)
{
while(cnt)
{
printf("cnt:%2d\r",cnt);
fflush(stdout);
cnt--;
sleep(1);
}
printf("\n");
}
void handler(int signo)
{
//1.有非常多的子进程,在同一时刻退出了
//处理方案:waitpid(-1) while(1) 采用循环的方式等待任意一个进程
//2.有非常多的子进程,在同一时刻退出了一部分:
//处理方案:采用非阻塞式的等待方式等待任意一个进程
// while(1)
// {
// pid_t ret = waitpid(-1,NULL,WNOHANG);
// if(ret == 0) break;
// }
printf("pid:%d,%d号信号正在被捕捉\n",getpid(),signo);
}
int main()
{
signal(17,handler);
printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
pid_t id = fork();
if(id == 0)
{
printf("我是子进程,pid:%d,ppid:%d,我要退出了\n",getpid(),getppid());
Count(5);
exit(1);
}
while(1) sleep(1);
return 0;
}
运行截图:
事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。
运行截图:5秒过后子进程自动被回收了 文章来源:https://www.toymoban.com/news/detail-789445.html
文章来源地址https://www.toymoban.com/news/detail-789445.html
到了这里,关于深入剖析Linux——进程信号的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!