Linux——信号小结1

为了理解信号,首先来看一个场景:

1.用户输入命令,在shell下启动一个前台进程

2.用户按下Ctrl+C键,此时会产生一个硬件中断

3.如果CPU当前正在执行这个进程的代码,则改进程的用户与空间代码暂停执行,CPU从用户态切换带内核态处理硬件中断

4.终端驱动程序将Ctrl+C键解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)

5.当某个时刻要从内核返回到用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接而终止进程而不返回它的用户空间代码执行

用kill -l 命令查看一下系统信号列表:


每个信号都有一个编号宏定义名称,上述信号的产生条件和默认处理动作在signal(7)中有详细说明:

man 7 signal

产生信号的方式:

    1.用户在终端按下一些按键(如Ctrl+C等)

    2.硬件异常产生信号

    3.一个进程调用kill(2)函数可以发送信号给另一个进程

    4.软件条件产生

信号处理方式:

    1.忽略该信号

    2.执行该信号的默认处理动作

    3.执行用户自定义动作(信号的捕捉

下面详细介绍一下信号的产生

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

    SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程Core Dump(当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。默认是不允许产生core文件的,因为core文件中可能包含用户密码等信息,在开发调试阶段可以使用ulimit命令改变这个限制,允许产生core文件,允许core文件最大为1024K)


写一个死循环验证一下:


运行上图程序,在终端输入Ctrl+\(注意这里Ctrl+C并不能展示出Core Dump)


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

首先在后台执行死循环程序,然后用kill命令给它发SIGSEGV信号:

    · 之所以要多按一次回车才显示Segmentation fault,是因为在7715进程中止掉之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望Segmentation fault信息和用户的输入交错在一起,所以等待用户输入之后才显示。

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

kill命令是调用kill函数实现的,kill函数可以给指定的一个进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发信号)

#include <signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
这两个函数都是成功返回0,错误返回-1

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

#include <stdlib.h>
void abort(void)
就像exit函数一样,abort函数总是会成功,所以没有返回值

3.由软件条件产生信号

    SIGPIPE是一种由软件条件产生的信号,在管道(https://blog.csdn.net/tangduobutian/article/details/79638121)中已经介绍过了,以下主要介绍alarm函数和SIGALRM信号:

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

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

例:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        int count=14;
        alarm(3);
        for(;1;count++){
            printf("count == %d\n",count);
        }
        return 0;
    }

此程序的作用是3秒钟之内不停地数数,3秒到了就被SIGALRM信号终止.

阻塞信号

1.信号的一些其他相关常见概念

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

    · 信号从产生到递达之间的状态,称为信号未决(Pending)

    · 进程可以选择阻塞(Block)某个信号

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

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

2.在内核中的表示


                                                    信号在内核中的表示示意图

     每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作,信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才解除该标志。
     在上图例子中:    
     · SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作
     · SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞
    · SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞它的处理动作是用户自定义函数sighandler
如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?
    · POSIX 1允许递达该信号一次或多次
    · Linux是这样实现的:常规信号在递达之前只计一次(即普通信号允许丢失),而实时信号在递达之前产生多次可以依次放在一个队列里 
3.sigset_t

    从上图可知,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以利用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。

4.信号集操作函数

#include <signal.h>
int sigemptyset(sigset_t *set);//初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号不包含任何有效信号
int sigfillset(sigset_t *set);//初始化set所指向的信号集使其中所有信号的对应bit置位,表示该信号集的有效信号包含系统支持的所有信号
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int sigismember(const sigset_t *set,int signo);
这四个函数的返回值都是成功返回0,失败返回-1;
sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号

注意:在使用sigset_t类型的变量之间,一定要调用sigemptyset或sigfillset做初始化,使信号处于确定的状态。

sigprocmask(调用该函数可以读取或更改进程的信号屏蔽字(阻塞信号集))

#include <signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
返回值:成功返回0,失败返回-1

sigpending

#include <signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,成功返回0,出错返回-1

例:

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    
    int printsigset(sigset_t *set)
    {
        int i = 0;
        for(;i<32;i++){
            if(sigismember(set,i)){       //判断指定信号是否在目标集合中
               putchar('1');
            }else{
                putchar('0');
            }
        }
        puts("");
    }
    
    int main()
    {
        sigset_t s,p;
        sigemptyset(&s);             //定义信号集对象,并清空初始化
        sigaddset(&s,SIGINT);
        sigprocmask(SIG_BLOCK,&s,NULL);                //设置阻塞信号集,阻塞SIGINT信号
        while(1){
            sigpending(&p);                            //读取未决信号集
            printsigset(&p);
            sleep(3);
        }
        return 0;
    }

测试结果:


由于阻塞了SIGINT信号,按Ctrl+C将会使SIGINT信号处于未决状态,按Ctrl+\仍然可以终止程序,因为SIGQUIT信号没有阻塞。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
进程信号的处理过程:(1)在目的进程中安装该信号,即设置如果目标进程捕获该信号执行的操作代码。Linux采用sigal和sgation系统调用来完成。因信号是异步事件的典型应用,产生信号进程而言是随机出现的,因此,进程不能预先知道信号会不会发送到当前进程,也不能预先知道信号什么时候发送到当前进程,因此只能在信号到来前告诉内核“在此信号发生时,请执行下列操作”,即所谓的安装信号。 (2)信号被某个进程产生,同时设置此信号的目标进程(一般为目标进程的pid),然后由操作系统管理。Linux采用kill()、arise()、alarm()等系统调用来实现。 (3)信号在目的进程被注册。操作系统将信号添加到目的进程的PCB相关的数据结构中。在每个进程的PCB (task_struct结构)中有一个未决信号的数据成员。 (3)信号进程中的注销。进程执行信号相应处理函数之前,首先要把信号进程中注销。如果存在未决信号等待处理且该信号没有被进程屏蔽,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。 (5)信号生命终止。进程注销信号后,目的进程根据当前进程对此信号设置的处理方式,暂时终止当前代码的执行,保护上下文,转而执行信号处理函数,即捕获该信号执行完成后再恢复到被中断的位置继续执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值