进程信号(1)

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

本节目标是:

    1、掌握信号的基本概念

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

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

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

引言 

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

信号的基本概念:

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

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


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

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

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

man 7 signal:


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

产生信号的方式概览:

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


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

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

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


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

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

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


    ->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;
}

结果为:32915Alarm clock

猜你喜欢

转载自blog.csdn.net/lu_1079776757/article/details/79717244
今日推荐