zoukankan      html  css  js  c++  java
  • 信号

    原文链接:http://www.orlion.ga/1122/

    一、信号的概念

        一个熟悉的场景:

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

      2. 用户按下Ctrl-C,键盘输入产生一个硬件中断

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

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

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

        

        Ctrl-C产生的信号只能发给前台进程,该进程的用户空间代码执行到任何地方都可能收到SIGINT信号而终止,信号相对于进程的控制流程来说是异步的。   

        产生信号的条件主要有:

      • 用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,如Ctrl-C产生SIGINT信号

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

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

      • 可以用kill(1)命令发送信号给某个进程,kill(1)命令也是调用kill(2)函数实现的,如果不确定指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。

      • 当内核检测到某软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。

     

        如果不想按默认动作处理信号,用户程序可以调用sigaction(2)函数告诉内核如果处理某种信号,可选的处理动作有一下三种:

      1. 忽略该信号

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

      3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式成为捕捉(Catch)一个信号。

     

    二、产生信号

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

        SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump,Core Dump:当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误。事后可以用调试器检查core文件以查清错误原因这叫做Post-mortem Debug。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。

        首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K:

    $ ulimit -c 1024

        然后写一个无限循环的程序:

    #include <unistd.h>
    
    int main(void)
    {
        while(1);
        return 0;
    }

        前台运行这个程序,然后在终端键入Ctrl-C或Ctrl-:

    $ ./a.out
        (按Ctrl-C)
    $ ./a.out
        (按Ctrl-)Quit(core dump)
    $ ls -l core
    -rw------- 1 akaedu akaedu 147456 2008-11-05 23:40 core

        ulimit命令改变了Shell进程的Resource Limit,a.out进程的PCB由Shell进程复制而来,所以也具有Shell进程相同的Resource Limit值,这样就可以产生Core Dump了。

     

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

        在后台执行a.out,然后用kill命令给它发SIGSEGV信号。

    $ ./a.out &
    [1] 7940
    $ kill -SIGSEGV 7940
    $ (再次回车)
    [1]+  Segmentation fault      (core dumped) ./a.out

        7940是a.out进程的id。之所以要再次回车才显示Segmentation fault,是因为在7940进程终止掉之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望Segmentation fault信息和用户的输入交错在一起,所以等用户输入命令后才显示。指定某种信号的kill命令可以有多种写法,上面的命令还可以写成kill -SEGV 7940或kill -11 7940,11是信号SIGSEGV的编号,以往遇到的段错误都是由非法内存访问产生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误。

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

    #include <signal.h>
    
    int kill(pid_t pid, int signo);
    int raise(int signo);

        这两个函数都是成功返回0,错误返回-1.

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

    #include <stdlib.h>
    
    void abort(void);

        像exit一样,abort函数总是会成功,没有返回值。

     

        3、由软件条件产生信号

        SIGPIPE是一种由软件条件产生的信号

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

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

  • 相关阅读:
    Vim直接打开Tampermonkey网址的方法。
    利用 Tampermonkey 和 Surfingkeys 效率操作网页
    tampermonkey利用@require调用本地脚本的方法
    用AutoHotkey一键完成Xmind插入图片等功能
    用AutoHotkey调整Windows音量
    用AutoHotkey重置Excel的Ctrl+Alt+方向键选择的范围
    远程终端协议TELNET
    文件传送协议
    域名系统DNS
    计算机网络运输层习题5-38
  • 原文地址:https://www.cnblogs.com/orlion/p/5350813.html
Copyright © 2011-2022 走看看