进程信号生命周期详解
创始人
2024-05-28 18:04:29
0

信号和信号量半毛钱关系都没有!

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2

查看信号的机制,如默认处理动作man 7 signal

SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump。

进程信号

kill命令可以给进程发送信号。kill -l查看信号列表,共61个信号,其中信号1-31号:普通信号,34-64号:实时信号。我们只需了解普通信号。信号是给进程发的,kill -信号编号 pid

进程本身是被程序员编写的属性和逻辑的集合—程序员编码完成的,当进程收到信号时,不一定立即处理。

信号及相关概念

信号是进程之间事件异步通知的一种方式,属于软中断

[yyq@VM-8-13-centos 2023_03_01_ProcessSignal]$ kill -l1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

ctrl+c:是一个组合键,OS会识别为kill -2 pid,是2号信号,用于终止前台进程。SIGINT的默认处理动作是终止进程。

  1. 实际执行信号的处理动作称为信号递达(Delivery)。

  2. 信号从产生到递达之间的状态,称为信号未决(Pending)。(信号产生了,但是没有被执行)

  3. 信号可以被进程选择性阻塞。

  4. 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

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

信号的生命周期

预备

首先进程需要认识信号,知道信号的属性和对应要做的动作

信号产生

发送信号的本质就是更改对应进程PCB中的信号位图。PCB的管理者是OS,故信号也是由OS发送,因为只有OS有权利修改PCB里的内容。

无论有多少种发送信号的方式,本质都是通过OS向目标进程发送信号,故OS必须提供发送信号/处理信号的相关系统调用。

当信号来临时,进程不一定马上处理这个信号。异步:各自做着不一样的事情,进程继续执行自己的代码,而不执行信号。同步:进程先执行信号,停下自己的代码。由于信号可以随时产生,所以进程对信号是异步的。

常见的信号产生方式

键盘输入

ctrl+c ==> SIGINT-终止前台进程 ;ctrl+\ ==> SIGQUIT-终止进程

系统调用

kill() 给别人发任意信号、raise() 给自己发任意信号、abort() 给自己发SIGABRT信号

#include 
#include 
原型int kill(pid_t pid, int sig);
参数pid:pid>0,发送给指定进程; pid==0,发送给调用进程的进程组中的每个进程; pid==-1,发送给所有调用进程有权限发送信号的进程,除了进程1 (init); pid<-1, 发送给进程组中ID为-pid的所有进程sig:信号编号
返回值成功返回0;失败返回-1
#include 
原型int raise(int sig);// 相当于kill(getpid(), sig);
参数sig:信号编号
返回值成功返回0;失败返回非0
#include 
原型void abort(void);// 相当于kill(getpid(), SIGABRT);

大多数信号的默认处理动作都是终止进程,信号的意义:信号不同代表不同的事件,对事件发生之后的处理动作可以一样。

硬件异常

1、如除0异常的8号信号SIGFPE

那操作系统如何得知下面这个程序在执行除0操作?

答:通过硬件-cpu内的状态寄存器。状态寄存器有个溢出标记位,当除0溢出标记位就变成1,此次运算结果无意义,CPU发生运算异常,OS作为软硬件资源的管理者,就知道是当前的进程导致的硬件异常,于是OS修改该进程的信号标记位。

#include 
#include 
#include 
#include 
#include 
#include void handler(int signo)
{std::cout << "捕捉到一个信号,编号是" << signo << std::endl;
} int main()
{signal(SIGFPE, handler);// 只是注册了该信号对应的动作int a = 10;a /= 0;while(true){std::cout << "我是一个进程:" << getpid() << std::endl;        sleep(1);}return 0;
}

在执行这个程序后,会一直输出捕捉到一个信号,编号是8,不应该只输出1次,程序就被终止了么?

答:首先我们自定义了收到8号信号的动作,就不会执行默认动作(退出)。其次,1、收到信号不一定会引起进程退出,2、进程没有退出就有可能还会被调度,3、cpu内部对应的寄存器只有一个,但是状态寄存器中的内容属于当前进程的上下文,但是用户没有能力去修改寄存器中的内容,4、当进程被切换的时候,就有无数次状态寄存器被保存和恢复的过程,所以每一次恢复的时候就会让OS识别到状态寄存器的溢出标志位,所以一直给进程发8号信号。

2、如访问野指针 11号信号SIGSEGV

int* p = nullptr;
*p = 100;//段错误

那操作系统如何得知下面这个程序访问野指针了?

答:咱们在程序里写的指针,本质是就是虚拟地址,而虚拟地址空间的0号地址,经过页表和MMU(在cou里))的映射,发现当前进程不允许访问0号地址,MMU这个硬件因为越界/野指针,进而发生异常,OS识别到了MMU异常,就将对应进程的信号位图的11位置为1.

软件条件

1、管道读端关闭,写端一直写==>OS直接终止当前进程,13号信号SIGPIPE

2、定时器alarm()函数 14号信号SIGALRM

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

可以通过alarm()的使用,我们可以明显观察到IO速度很慢

int main()
{alarm(1); // 1s内计算机能够将数据累加到144250int cnt = 0;while(true){std::cout << cnt++ << std::endl;//这里要进行cnt次IO,速度很慢cnt++;}return 0;
}
---------------------------------------------
int cnt = 0;
void handler(int signo)
{std::cout << "cnt: " << cnt << std::endl;//只需要进行1次IO
}
int main()
{alarm(1); // 1s内计算机能够将数据累加到435882026signal(SIGALRM, handler);while(true){cnt++;}return 0;
}

闹钟信号只会通知1次即只有1次输出,不会像硬件异常那样疯狂输出

int cnt = 0;
void handler(int signo)
{std::cout << "cnt: " << cnt << std::endl;//只需要进行1次IOalarm(1);//这样就可以实现每隔1s打印一次,简单的sleep(1)
}
int main()
{alarm(1); // 1s内计算机能够将数据累加到435882026signal(SIGALRM, handler);while(true){cnt++;}return 0;
}

alarm(0)表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。

任何一个进程都可以通过alarm系统调用在内核中设置闹钟,那么OS就要对其进行管理,先描述在组织。

struct alarm { uint64_t when; //未来的时间 即时间戳+闹钟设定的时间int type; //闹钟类型一次性或周期性 task_struct *p; //对应的进程 struct alarm* next; //下一个闹钟
};

比如用最小堆来管理,那么堆顶的闹钟就是将来第一个要超时的闹钟,OS就可以周期性的检查堆顶,当操作系统的当前时间>alarm.when,就给对应的进程发送信号。

信号保存

在信号到来到信号被处理的这段时间,称为时间窗口,必须要保存这个信号。会被保存在进程PCBtask_struct{ unsigned int signal}这个结构体里,unsigned int有32个比特位,用来表示1-31号信号【比特位的位图结构】。比特位的位置,代表信号编号,比特位的内容,代表是否收到该信号,对应的值为0/1,收到对应的信号就置为1。

保存和处理中间的情况:信号阻塞、信号未决

被阻塞的信号将保持在未决(pending)状态,直到进程解除对信号的阻塞(block),才执行递达动作。普通信号在递达之前产生多次只计一次,因为对应二进制位非0即1。

task_struct {unsigned int pending = 0; //0000 0000 0000 0000 0000 0000 0000 0000 // 从右向左第1个比特位表示1号信号unsigned int block = 0;   //0000 0000 0000 0000 0000 0000 0000 0000
}if ((1 << (signo - 1)) & pcb->block) 
{//被阻塞,那就不用递达了
}
else
{if((1 << (signo - 1)) & pcb->pending){//未被阻塞,且信号未决,那就递达}
}

①PCB进程控制块中有信号屏蔽状态字(block),信号未决状态字(pending),还有是否忽略标识
②信号屏蔽状态字(block):1代表阻塞,0代表不阻塞;信号未决状态字(pending):1代表未决,0代表信号递达
③向进程发送SIGINT,内核首先判断信号屏蔽状态字是否阻塞,如果信号屏蔽状态字阻塞,信号未决状态字(pengding)相应位置1;
若阻塞解除,信号未决状态字(pending)相应位置0,表示信号可以递达了。
④block状态字,pending状态都是64bit,分别代表Linux系统中的64个信号。例如SIGINT是2号信号,对应block状态字中的第二位
⑤block状态字用户可以读写,pending状态字用户只能读,这是信号的设计机制。

注意:即使一个信号没有产生,它仍然可以先被设置为阻塞状态。进程能识别信号,是因为PCB结构中有相应的数据结构(block

位图、pending位图、handler_t表)

信号处理

typedef void(*handler_t)(int signo);//函数指针-表示操作方法
handler_thandler[32] = 0;//函数指针数组,存储32个信号对应的处理方法 数组的下标+1表示信号编号,数组下标对应的内容表示对应信号的处理方法

收到信号后一般会有3个动作。

默认动作

大多数信号的默认处理动作都是终止进程,信号的意义:信号不同代表不同的事件,对事件发生之后的处理动作可以一样。term,core,ign,stop,cont

进程终止时核心转储问题

当进程出现异常的时候,我们将进程对应时刻在内存中的有效数据转储到磁盘

First the signals described in the original POSIX.1-1990 standard.
//表中第一列是各信号的宏定义名称,第二列是各信号的编号,第三列是默认处理动作
Signal     Value     Action   Comment
──────────────────────────────────────────────────────────────────────
SIGHUP        1       Term    Hangup detected on controlling terminal
or death of controlling process
SIGINT        2       Term    Interrupt from keyboard
SIGQUIT       3       Core    Quit from keyboard
SIGILL        4       Core    Illegal Instruction
SIGABRT       6       Core    Abort signal from abort(3)
SIGFPE        8       Core    Floating point exception
SIGKILL       9       Term    Kill signal
SIGSEGV      11       Core    Invalid memory reference
SIGPIPE      13       Term    Broken pipe: write to pipe with no
readers
SIGALRM      14       Term    Timer signal from alarm(2)
SIGTERM      15       Term    Termination signal
SIGUSR1   30,10,16    Term    User-defined signal 1
SIGUSR2   31,12,17    Term    User-defined signal 2
SIGCHLD   20,17,18    Ign     Child stopped or terminated
SIGCONT   19,18,25    Cont    Continue if stopped
SIGSTOP   17,19,23    Stop    Stop process
SIGTSTP   18,20,24    Stop    Stop typed at terminal
SIGTTIN   21,21,26    Stop    Terminal input for background process
SIGTTOU   22,22,27    Stop    Terminal output for background process
  • Term表示终止当前进程,OS正常结束该进程

  • Core表示终止当前进程并且Core Dump(Core Dump 用于gdb调试)。在命令行输入ulimit -a查看core file size核心转储功能,云服务器默认是0(关闭该功能),命令行输入ulimit -c 1024,打开云服务器的核心转储功能并设置大小为1024字节,此时再执行会发生段错误的程序,就会输出Segmentation fault (core dumped),且当前目录下会生成一个文件core.引起core问题的进程ID,给该文件在编译时带上-g选项,在进行调试的时候,输入core-file core.pid就可以把对应的核心转储文件打开,就可以看到详细的报错信息。

  • Ign表示忽略该信号

  • Stop表示停止当前进程

  • Cont表示继续执行先前停止的进程

捕捉(自定义动作)

用户自定义信号处理函数称为捕捉。9号信号无法捕捉。信号产生不一定被立即处理,而是在合适的时候(由内核态返回用户态)处理。

内核态:由用户/进程发起的,但实际执行系统调用的身份是内核

用户态:平时自己写的代码是运行在用户态的,而我们的代码难免访问2种资源【OS资源(getpid, waitpid),硬件资源(printf, write, read)】,用户为了访问内核和硬件资源,必须通过系统调用来完成对应的访问。

往往系统调用比较费时间,要进行角色转换。CPU内有寄存器,有个current寄存器指向当前正在运行进程的PCB的起始地址,CR3寄存器表征当前进程的运行级别(0表示内核态,3表示用户态)

在这里插入图片描述

合适的时候:从内核态返回用户态的时候,要返回的话,曾经肯定进入了内核态(系统调用、进程切换等),此时要从内核态切换回去,OS会先通过PCB看看每个信号的block表是否被阻塞,没被阻塞的再看对应的pending表是否未决,未决的信号让它递达。【block1,不管;block0&&pending0,不管;block0&&pending==1,查handler表对应的方法并执行】,默认和忽略这两个动作很方便,而自定义动作需要切换回用户态才能执行【不可以在内核态直接执行】,然后再回到内核态获取当前进程的上下文,才能回到用户态跳转的位置继续执行后续代码。

普通信号的自定义捕捉流程:用户态–>内核态【执行系统调用,以及信号检测】–>用户态【执行自定义动作】–>内核态【获取进程上下文】–>用户态。

忽略动作

函数接口

signal()函数 信号捕捉

#include 
功能 信号处理器,即可以收到特定信号时,执行自定义动作原型typedef void (*sighandler_t)(int); // 函数指针,传入的int表示信号的编号sighandler_t signal(int signum, sighandler_t handler);//其实就是拿着signum去操作方法表里修改对应信号的操作方法
// 设置了收到2号信号的自定义动作
void handler(int signo)
{std::cout << "捕捉到2号信号" << signo << std::endl;
}// 这里是对signal函数的调用,而不是对handler的调用
// handler对应的方法一般不会执行,除非收到对应的信号
signal(2, handler);// 只是注册了该信号对应的动作
//要想触发该动作,需要按ctrl+c组合键 SIGINT的默认处理动作是终止进程
//2号的默认动作是退出进程,当我们给这个进程的2号信号设置了自定义动作,就会执行自定义动作

附:1-31号信号产生原因

  1. SIGHUP:当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
  2. SIGINT:当用户按下了组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止里程
  3. SIGQUIT:当用户按下组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信 号。默认动作为终止进程
  4. SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
  5. SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件
  6. SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件
  7. SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件
  8. SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件
  9. SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可 以杀死任何进程的方法
  10. SIGUSE1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程
  11. SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件
  12. SIGUSR2:这是另外一个用户自定义信号 ,程序员可以在程序中定义并使用该信号。默认动作为终止进程
  13. SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。
  1. SIGALRM:定时器超时,超时的时间 由系统调用alarm设置。默认动作为终止进程

  2. SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行 shell命令Kill时,缺省产生这个信号。默认动作为终止进程

  3. SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程

  4. SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号

  5. SIGCONT:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为终止进程

  6. SIGTTIN:后台进程读终端控制台。默认动作为暂停进程

  7. SIGTSTP:停止进程的运行。按下组合键时发出这个信号。默认动作为暂停进程

  8. SIGTTOU:该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程

  9. SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号

  10. SIGXFSZ:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止进程

  11. SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程

  12. SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程

  13. SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程

  14. SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号

  15. SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略

  16. SIGPWR:关机。默认动作为终止进程

  17. SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件

    31号SIGRTMIN~64号SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程。

相关内容

热门资讯

安卓系统怎么关钥匙,轻松掌握钥... 手机里的安卓系统,是不是有时候让你觉得有点儿头疼?比如,当你想关掉手机,却发现钥匙在哪里呢?别急,今...
安卓系统有隐私空间,打造安全私... 你知道吗?在智能手机的世界里,安卓系统可是个超级明星呢!它不仅功能强大,而且现在还悄悄地给你准备了一...
安卓系统设置角标,打造专属通知... 你有没有发现,手机上的安卓系统设置里有个神奇的小功能——角标?这个小东西虽然不起眼,但作用可大了去了...
安卓系统定位信息查询,揭秘移动... 你有没有想过,你的手机里藏着多少秘密?尤其是那个安卓系统,它可是个超级侦探,随时随地都在帮你定位。今...
安卓刷入系统恢复,轻松实现设备... 手机系统崩溃了?别慌!安卓刷入系统恢复大法来啦! 手机,这个我们生活中不可或缺的小伙伴,有时候也会闹...
安卓系统限制无法录音,探索无法... 你有没有遇到过这种情况?手机里明明装了录音软件,却突然发现,哎呀妈呀,竟然无法录音了!这可真是让人头...
怎么降级手机系统安卓,操作指南... 手机系统升级了,新功能层出不穷,但有时候,你可能会觉得,这系统太卡了,想回到那个流畅如丝的年代。别急...
米oa系统是安卓系统吗,深入解... 亲爱的读者,你是否曾好奇过,米OA系统是不是安卓系统的一员?这个问题,就像是一颗好奇的种子,悄悄地在...
手机刷安卓车载系统,手机刷机后... 你有没有发现,现在开车的时候,手机和车载系统之间的互动越来越紧密了呢?想象当你驾驶着爱车,一边享受着...
vivo安卓怎么降系统,viv... 手机用久了,是不是觉得系统越来越卡,运行速度大不如前?别急,今天就来教你怎么给vivo安卓手机降降级...
nova 4刷安卓系统,体验全... 最近手机界可是热闹非凡呢!听说华为nova 4要刷安卓系统了,这可真是让人兴奋不已。你有没有想过,你...
如果当初没有安卓系统,科技世界... 想象如果没有安卓系统,我们的生活会是怎样的呢?是不是觉得有点不可思议?别急,让我们一起穿越时空,探索...
安卓电视装win系统,系统转换... 亲爱的读者们,你是否曾想过,在你的安卓电视上装一个Windows系统,让它瞬间变身成为一台功能强大的...
安卓手机还原系统好处,重拾流畅... 你有没有遇到过安卓手机卡顿、运行缓慢的情况?别急,今天就来给你揭秘一下安卓手机还原系统的那些好处,让...
安卓系统能跑win吗,探索跨平... 你有没有想过,你的安卓手机里能不能装上Windows系统呢?这听起来是不是有点像科幻电影里的情节?别...
安卓车载系统蓝牙设置,畅享智能... 你有没有发现,现在开车的时候,手机和车载系统之间的互动越来越频繁了呢?这不,今天就来给你详细说说安卓...
奥利奥安卓系统,探索新一代智能... 你有没有想过,一块小小的奥利奥饼干竟然能和强大的安卓系统扯上关系?没错,今天就要来聊聊这个跨界组合,...
微信使用安卓系统,功能解析与操... 你有没有发现,现在用微信的人越来越多了呢?尤其是安卓系统的用户,简直就像潮水一样涌来。今天,就让我带...
体验最新原生安卓系统,极致体验... 你有没有想过,手机系统就像是我们生活的调味品,有时候换一种口味,生活都会变得有趣起来呢?最近,我体验...
安卓系统能玩原神,尽享奇幻冒险... 你有没有想过,在安卓系统上也能畅玩《原神》这样的热门游戏呢?没错,就是那个画面精美、角色丰富、玩法多...