欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

进程信号(1)

程序员文章站 2022-07-12 12:35:11
...

今天我们要来学习一下Linux下的信号。

本节目标是:

    1、掌握信号的基本概念

    2、掌握信号产生的一般方式

    现实生活中存在很多信号,比如说红绿灯,闹钟等等。当我们在人行道上走时,遇到了红灯,我们的第一反应就是停下来等待,而我们又为什么会做这个动作呢?是因为我们知道“红灯停,绿灯行”这个交通规则,而这个规则我们已经记住了,因此才会做出这个反应。

    那么Linux下的信号的是什么呢?

引言 

    信号是一种软件中断,与之相对应的就是硬件中断,区别是,软件中断是由软件代码触发的中断,而硬件中断是由硬件直接产生的电信号触发的中断,说到中断那么一定是异步的,所谓的异步就是中断信号的产生是随机的,对于中断信号的接受者来说,接受到中断信号的时刻自然也就是随机的。

信号的基本概念:

    所谓信号,就是通信内容受限制的一种异步的通信方式。

    首先我们可医用kill -l命令查看系统中定义的信号列表:

进程信号(1)

    乍一看,我们发现好像有64种信号,然而如果仔细观察,你就会发现并非如此。没有32和33号信号。所以一共只有62个信号其中34-64号信号为实时信号。1-31为普通信号,而我们现在只来学习普通信号。

我们发现每个信号都有一个编号和一个宏定义名称,这些名字都是以SIG开头(signal的缩写)。它们的宏定义可以在signal.h中找到,例如其中有定义#define SIGINT 2.

而这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明,在命令行上输入

man 7 signal:

进程信号(1)

了解了信号的基本概念,我们就要知道信号是如何产生的!这里给出了几种信号产生的方法:

产生信号的方式概览:

1. 用户在终端按下某些信号键时,终端驱动程序会发送信号给前台进程。例如Ctrl-C产生SIGIINT信号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号(可以使前台进程停止)

2. 硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法的内存地址,MMU会产生异常,内核称这一异常为SIGSEGV信号发送给进程。

3. ⼀个进程调⽤kill(2)函数可以发送信号给另⼀个进程。 可以⽤kill(1)命令发送信号给某个进程,kill(1)命令也是调⽤kill(2)函数实现的,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终⽌进程。 当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。 如果不想按默认动作处理信号,⽤户程序可以调⽤sigaction(2)函数告诉内核如何处理某种信号。

4. 软件条件产⽣

以上四点可以简单的理解为:

1、由用户产生

2、由软件产生

3、由键盘产生

4、由异常产生

对于信号处理的常见方法如下:

1、忽略此信号 
        除了两种信号(SIGKILL和SIGSTOP)不能被忽略外,其它的信号都可被忽略,这两个信号不能被忽略的原因是:它们提供一种终极的终止或停止进程的可靠方法,这是一种终极裁判权,如果这两个信号都被忽略了的话,某个进程跑飞后就没有办法终止或停止这个进程。 另外忽略某些硬件产生的信号被认为是不可取的,如我们如果忽略非法存储访问或除以0等硬件产生的信号的话,进程状态未定义的(无法确定的状态)。 
2、捕获信号 
       如果某个进程被通知某个信号发生了,但是想要捕获这个信号的话,该进程就必须向内核注册一个捕获函数,当相应的信号发生时,捕获函数就会被调用并执行希望对这个事件的处理。 
提供⼀个信号处理函数(自定义动作),要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。
3、执行系统的默认操作 
        其实我们内核为每个信号在发生时都规定了一个默认的操作,如果我们不捕获也不忽略的话,当这个信号发生时,进程会按照默认方式去处理这些发生的信号,当然对于绝大多数信号而言,其默认的处理方式都是终止接收到该信号的进程或者忽略此信号。

现在我们具体来看一下信号产生的方式:

1、通过终端按键产生信号

    SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump,现在我们来验证一下:

    ⾸先解释什么是Core Dump。当⼀个进程要异常终止时,可以选择把进程的⽤户空间内存数据全部保存到磁盘上,⽂件名通常是core,这叫做Core Dump。进程异常终⽌通常是因为有Bug,⽐如⾮法内存访问导致段错误,事后可以⽤调试器检查core⽂件以查清错误原因,这叫做Post-mortem Debug(事后调试)。⼀个进程允许产⽣多⼤的core⽂件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产生core⽂件的,因为core⽂件中可能包含⽤户密码等敏感信息、不安全。在开发调试阶段可以⽤用ulimit命令改变这个限制,允许产生core⽂件。 ⾸先⽤ulimit命令改变Shell进程的Resource Limit,允许core⽂件最大为1024K: 

$ ulimit -c 1024

进程信号(1)

    然后我们写一个死循环程序:

#include<stdio.h>
int main()
{
        printf("pid is %d\n",getpid());
        while(1);
        return 0;
}

    然后我们在前台运行这个程序,之后再终端按Ctrl-\终止程序。

进程信号(1)

    ulimit命令改变了Shell进程的Resource Limit,test进程的PCB由Shell进程复制⽽来,所以也具有和Shell进程相同的Resource Limit值,这样就可以产⽣Core Dump了。

2、调用系统函数向进程发送信号

首先在后台运行死循环,然后用kill命令给他发送SIGSEGV信号

进程信号(1)

    ->2596是test进程的id。之所以要再次回车才显示Segmentation fault 是因为在2596进程终止之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望Segmentation fault 信息和用户的输入交错在一起,所以等用户输入命令之后才显示。

    ->指定发送某种信号的kill命令可以有多种写法,上面的命令还可以写成kill -SIGSEGV 2596 或kill -11 2596,其中11是信号SIGSEGV的编号。

1、kill命令是调用kill函数实现的,kill函数可以给一个指定的进程发送指定的给信号。

2、raise函数可以给当前进程发送指定的信号(即自己给自己发送信号)。

3、abort函数使当前进程接收到信号而异常终止。

#include<stdio.h>

int kill(pid_t pid,int signo)

int raise(int signo)

void abort(void)

3、由软件条件产生信号

SIGPIPE是一种由软件条件产生的信号,在“管道”中我们已经介绍过了。

以alarm函数 和SIGALRM信号为例

函数原型:

 unsigned int alarm(unsigned int seconds),

作用机制:调用alarm函数可以设定⼀个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终⽌当前进程。这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。

举个例子

    某人要睡觉,设定闹钟为30分钟之后响,20分钟后被别人吵醒了,但还想多睡一会儿。于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

闹钟:在一段时间之后才产生的信号

代码:

#include<stdio.h>
#include<unistd.h>

int main()
{
        int count = 10;
        alarm(1);
        for(;1;count++)
        {
                printf("count = %d\n",count);
        }
        return 0;
}

进程信号(1)

结果为:32915Alarm clock