Linux进程信号

一、信号的概念
要了解进程信号,首先要对进程的概念做一个简单的了解,要知道这一部分,请参考上一篇文章,进程概念。https://blog.csdn.net/aaronlanni/article/details/79774496
以及进程控制https://blog.csdn.net/aaronlanni/article/details/79809463
1、信号的基本概念
信号(signal)是linux进程通讯中的一种方式。
信号从软件层次上看是对中断机制的一种模拟。一个进程收到信号时的处理方式与CPU收到中断请求时的处理方式一样。收到信号的进程会跳入信号处理函数,执行完后再跳回原来的位置继续执行。
当用户向操作系统发送指定信号,将会影响当前进程的进行以及状态,最坏的情况,有可能会导致程序不能达到预期的效果,因此对于信号的学习,将更好的辅助我们理解程序的运行。
2、一些场景

  • 用户输入命令,在Shell下启动一个前台进程。
  • 用户按下Ctrl-C,这个键盘输入产生一个硬件中断。
  • 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态 切换到内核态处理硬件中断。
  • 终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一 个SIGINT信号给该进程)。
  • 当某个时刻要从内核返回到该进程的用户空间代码继续执⾏行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程⽽而不再返回它的用户空间代码执行。

对于上面所说的那些点,需要注意以下几点:

  • Ctrl-C产生的信号只能发给前台进程。一个命令 后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。
  • Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像Ctrl-C这种控制键 产生的信号。
  • 前台进程在运⾏行过程中用户随时可能按下Ctrl-C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。
  • 当需要终止后台进程的时候,可以向指定进程发送9号信号,从而终止这个进程。
  • 在一个操作系统中,只允许有一个前台进程,但是可以有多个后台进程
    这里写图片描述
    有关如何查看进程的信号,请参考上一篇文章,进程信号。
    二、进程信号的形成
    1、信号的产生方式
  • 用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号(可使前台进程停止)
  • 硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU(内存管理系统)会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。
  • 一个进程调用kill(2)函数可以发送信号给另一个进程。 可以用kill(1)命令发送信号给某个进程,kill(1)命令也是调⽤用kill(2)函数实现的,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。 当内核检测到某种软件条件发⽣生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。 如果不想按默认动作处理信号,用户程序可以调用sigaction(2)函数告诉内核如何处理某种信号。
  • 软件条件产生
    2、信号的处理方式
  • 忽略此信号。
  • 执行该信号的默认处理动作。
  • 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执⾏行这个处理函数,这种方式称为捕捉(Catch)一个信号。
    注:在自定义捕捉信号之时,9号信号不能被捕捉,原因是,9号信号的作用是杀死一个进程,当这个进程可以被捕捉时,则系统中的信号就没有信号可以杀死,则操作系统就会一直维护着这个进程,导致一直占用着系统资源,从而导致资源的浪费,最后可能会使得系统中的内存被使用完,不能执行其他的操作。
    下面给出一个捕捉信号的例子:
    这里写图片描述

对于上面用于捕捉信号的函数,如下所示:

#include <signal.h> 
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//参数1表示信号,参数2表示为捕捉的方式,而返回值则为void
//返回值:成功返回捕捉的方式,失败返回错误码

三、产生信号详解
1、通过终端按键产生终端
SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump,现在我们来验证一下。
(1)有关Core Dump(核心转储)
首先解释什么是Core Dump。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。
一般在操作系统中,是禁止产生core文件的,因为系统为了维护这个文件,也会为其分配一定的内存,而在一般的大型服务器上,一般的系统会有自启动的功能,而如果这个系统有bug,每次在自启动之时,都会产生core文件,最后可能会导致系统崩溃。
这里写图片描述
调整core文件的大小,如下所示:
这里写图片描述
现在,让我们来写一个死循环,从而使得程序崩溃,产生core文件

  1 #include<stdio.h>
  4 int main()
  5 {
  6     printf("pid:%d\n",getpid()); 
  9     while(1);
 10     return 0;
 11 
 12 }

前台运行这个程序,然后在终端键入Ctrl-C( 貌似不行)但是Ctrl-\(这个可以)产生core文件
这里写图片描述
通过调试这个文件可以得到,如下所示:
注:在产生了core文件之后,不用在进行编译,直接进行进行gdb调试,如下所示:
这里写图片描述

2、调⽤用系统函数向进程发信号
首先在后台执行死循环程序刚才的test1.c程序,然⽤用kill命令给它发SIGSEGV信号,如下所示:
这里写图片描述
之所以要再次回⻋车才显⽰示 Segmentation fault ,是因为在5124进程终止掉之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望Segmentation fault信息和⽤户的输入交错在一起,所以等用户输入命令之后才显示。
kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。
raise函数可以给当前进程发送指定的信号(自己给自己发信号)。
下面来介绍两个函数

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
//给指定进程发送信号
//参数1表示的是指定进程,参数2表示信号
 #include <signal.h>
 int raise(int sig);
//自己给自己发送信号
//参数表示指定的信号
 #include <stdlib.h>
 void abort(void);
//就像exit函数一样,abort函数总是会成功的,所以没有返回值。

如下所示:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 int main()
  5 {
  6 //  pid_t id;
  7 //  kill(getpid(),9);
  8     raise(SIGQUIT);
  9     raise(9);
 10     while(1);
 11     return 0;
 12 }

3、由软件条件产生信号

 #include <unistd.h>
 unsigned int alarm(unsigned int seconds);
 //调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当
 //前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
 //参数senconds=0时,表示取消该闹钟,返回值仍然是以前闹钟设定的剩
 //余的时间

如下所示:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 #include<stdlib.h>
  5 int main()
  6 {
  7     int ret;
  9     ret=alarm(3);
 10     printf("hello world,%d\n",ret);
 11     ret = alarm(0);
 12     printf("hello world,%d\n",ret);
 19     return 0;
 20 }

结果如下所示:
这里写图片描述

上面的知识,大概就是有关Linux信号的一些。希望可以帮助到大家!

只有不停的奔跑,才能不停留在原地!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值