Linux操作系统进程信号
Posted Ricky_0528
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux操作系统进程信号相关的知识,希望对你有一定的参考价值。
文章目录
1. 信号介绍
信号产生之后,是发给进程的,进程要在合适的时候执行对应的动作
进程在没有收到信号的时候,就已经知道了哪种信号对应哪一个动作,进程具有识别信号并处理信号的能力,远远早于信号的产生
进程收到某种信号的时候,并不一定是立即处理的,而是在合适的时候,因为信号随时都有能产生(异步的),但进程当前可能在执行更重要的事
因此信号不能被立即处理的时候,收到的信号需要被保存起来,保存在struct task_struct
中,信号的本质也是数据,所以信号的发送就变成了往进程的task_struct
中写入信号数据,这个操作由操作系统来完成,因而无论我们的信号是如何发送的,本质都是底层在通过操作系统发送的
信号产生前:信号产生的各种方式 -> 信号产生中:信号的保存方式 -> 信号产生后:信号处理的方式
各种信号(64个)
ctrl + c
发出的就是2号信号
typedef void (*sighandler_t)(int)
:为一个函数指针
signum
:信号对应的整型值
handler
:修改进程对信号的默认处理动作
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signum)
printf("\\nrecieve a signal, signal num: %d, process pid: %d\\n", signum, getpid());
int main()
// 通过signal注册对2号信号的处理动作,改成我们自己自定义的动作
signal(2, handler);
while (1)
printf("hello, my pid: %d\\n", getpid());
sleep(1);
return 0;
注意:注册函数的时候,不会调用这个函数,只有当信号到来的时候,这个函数才会被调用
使用ctrl + c
向该进程发出2号信号
使用kill命令同样可以向该进程发出2号信号
2号信号无法退出,可以使用
ctrl + \\
退出进程
2. 信号的产生
2.1 键盘产生
信号产生的方式其中一种就是通过键盘产生,注意键盘产生的信号只能用来终止前台进程,加上&
运行的后台进程不行
一般而言,进程收到信号的处理方式有三种
- 默认动作——如终止自己、暂停等
- 忽略动作——是一种信号处理的方式,只不过动作就是什么也不干
- 自定义动作(信号的捕捉)——如signal方法,就是在修改信号的处理动作由默认动作 -> 自定义动作
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signum)
switch(signum)
case 2:
printf("hello, this is No.2, pid: %d\\n", getpid());
break;
case 3:
printf("hello, this is No.3, pid: %d\\n", getpid());
break;
case 9:
printf("hello, this is No.9, pid: %d\\n", getpid());
break;
default:
printf("hello, this is No.%d, pid: %d\\n", signum, getpid());
break;
int main()
int sig = 1;
// 注册1~31号信号
for (; sig <= 31; sig++)
signal(sig, handler);
while (1)
printf("hello, my pid: %d\\n", getpid());
sleep(1);
return 0;
发现9号信号不可以被捕捉,即无法自定义动作
2.2 程序中存在异常
程序中存在异常问题,会导致进程收到信号退出
#include <stdio.h>
int main()
while (1)
int *p = NULL;
*p = 100;
printf("hello, my pid: %d\\n", getpid());
sleep(1);
return 0;
程序会发生段错误:Segmentation fault,即进程的崩溃,这是因为进程收到了11号信号
在Windows或Linux下,进程崩溃的本质就是收到了对应的信号,然后进程执行信号的默认处理动作
当进程崩溃时,我们需要知道崩溃的原因,waitpid()
的status
会携带进程异常退出的原因
进程如果异常的时候,core-dump该位置会被设置为1
我们还需要知道程序是在哪一行崩溃
- 在Linux中,当一个进程推出的时候,它的退出码和推出信号都会被设置(正常情况)
- 当一个进程异常的时候,进程的终止信号会被设置,表明当前进程退出的原因
- 如果必要,操作系统会设置退出信息中的
core dump
标志位,并将进程在内存中的数据转储到磁盘当中,方便后期调试
云服务器上core file
默认是关闭的,这种情况下程序崩溃会发生:
core file
打开之后
8398为异常进程的pid
如何查看异常情况(事后调式)
- 编译时加上选项
-g
- 运行让程序产生异常,生成core文件
- 再用gdb调试该程序
- 在gdb中输入
core-file [core文件名]
,即可查看异常的详细信息
验证打开core file
之后异常退出后core dump
被设置为1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main()
if (fork() == 0)
while (1)
printf("I am child process...\\n");
int a = 10;
a /= 0;
int status = 0;
waitpid(-1, &status, 0);
printf("exit code: %d, exit sig: %d, core dump: %d\\n", (status >> 8) & 0xff, status & 0x7f, (status >> 7) & 1);
return 0;
2.3 系统调用
① kill
模拟实现一个kill命令
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
static void Usage(const char *proc)
printf("Usage: \\n\\t %s signal who\\n", proc);
int main(int argc, char *argv[])
if (argc != 3)
Usage(argv[0]);
return 1;
int signo = atoi(argv[1]);
int who = atoi(argv[2]);
kill(who, signo);
printf("signal: %d, who: %d\\n", signo, who);
return 1;
② raise
可以给当前进程发送指定的信号,即自己给自己发信号
#include <stdio.h>
#include <signal.h>
int main()
raise(11);
return 0;
成功返回0,错误返回-1
③ abort
使当前进程接收到信号而异常终止
#include <stdio.h>
#include <stdlib.h>
int main()
abort();
return 0;
就像exit函数一样,abort函数总是会成功的,所以没有返回值
2.4 软件条件
通过某种软件(OS)来触发信号的发送,系统层面设置了定时器,或者某种操作而导致条件不就绪等这样的场景下,触发信号的发送
举例:进程间通信时,当读端不仅不读,而且还关闭了读fd,写端一直在写,最终进程会收到SIGPIPE(13),就是一种典型的软件条件触发的信号发送
① alarm
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM
信号,该信号的默认处理动作是终止当前进程
这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数
打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数
#include <stdio.h>
#include <unistd.h>
int main()
int ret = alarm(20);
while (1)
printf("I am a process, ret = %d\\n", ret);
sleep(5);
int res = alarm(0); //取消闹钟
printf("res = %d\\n", res);
return 0;
利用alarm来比较有IO与无IO的情况下,CPU执行效率
-
有IO
#include <stdio.h> #include <unistd.h> int count = 0; int main() alarm(1); // 没有设置alarm的捕捉动作,就执行默认动作,终止进程 while(1) printf("hello, %d\\n",count++); return 0;
-
无IO
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> int count = 0; void handlerAlarm(int signum) printf("hello, %d\\n", count); exit(1); int main() signal(SIGALRM, handlerAlarm); alarm(1); // 没有设置alarm的捕捉动作,就执行默认动作,终止进程 while(1) count++; return 0;
无IO的情况下会快很多
2.5 OS给进程发送信号
信号产生的方式种类虽然非常多,但是无论产生信号的方式千差万别,但是最终一定都是通过OS向目标进程发送信号
产生信号的方式,其实都是OS发送信号数据给task_struct
struct task_struct中有进程的各种属性,那么其中也一定有对应的数据变量,来保存是否收到了对应的信号,而信号的编号也是有规律的1~31
进程中采用uint32_t sigs;
——位图结构来标识该进程是否收到信号
第31个<--------------------------------------------第1个
0000 0000 0000 0000 0000 0000 0000 0000
比特位的位置(第几个)代表的就是哪一个信号,比特位的内容(0或1),代表的就是是否收到了信号
故本质是OS向指定进程的task_struct中的信号位图写入比特1,即完成信号的发送,也可以说是信号的写入
以上是关于Linux操作系统进程信号的主要内容,如果未能解决你的问题,请参考以下文章