• 进程函数linux系统编程之信号(一):信号基本概述


    在写这篇文章之前,xxx已经写过了几篇关于改进程函数主题的文章,想要了解的朋友可以去翻一下之前的文章

        一、为了懂得信号,先从我们最熟习的场景说起:
    1. 用户输入命令,在Shell下启动一个前台进程。
    2. 用户按下Ctrl-C,这个键盘输入发生一个硬件中断。
    3. 如果CPU以后正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断。
    4. 终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。

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

        

        用kill -l命令可以观察系统定义的信号列表:
    每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定义#define SIGINT 2。编号34以上的是实时信号,这些信号各自由什么条件下发生,默认的处理动作是什么(Term表现终止以后进程,Core表现终止以后进程并且Core Dump,Ign表现忽略该信号,Stop表现停止以后进程,Cont表现继承执行先前停止的进程)
    ,在signal(7)中都有详细说明。

        0~31 不可靠信号,多个信号不会排队只保存一个,即信号可能丢失。

        34~64 可靠(实时信号),支撑排队信号不会丢失,可应用sigqueue发送信号,不像0~31有缺省的定义。

        

        二、发生信号的条件主要有:

        1、用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C发生SIGINT信号,Ctrl-\发生SIGQUIT信号,Ctrl-Z发生SIGTSTP信号

        2、硬件异常发生信号,这些条件由硬件检测到并通知内核,然后内核向以后进程发送适当的信号。例如以后进程执行了除以0的指令,CPU的运算单元会发生异常,内核将这个异常解释为SIGFPE信号发送给进程。

        3、再比如以后进程访问了合法内存地址,MMU会发生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

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

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

        6、raise:给自己发送信号。raise(sig)等价于kill(getpid(), sig);
    7、killpg:给进程组发送信号。killpg(pgrp, sig)等价于kill(-pgrp, sig);
    8、sigqueue:给进程发送信号,支撑排队,可以附带信息。

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

        

        调用pause函数:将进程置为可中断睡眠状态。然后它调用schedule(),使linux进程调度器找到另一个进程来运行。pause使调用者进程挂起,直到一个信号被捕获处理后函数才返回。

        

        三、用户程序可以调用signal(2) / sigaction(2)函数告诉内核如何处理某种信号(若未注册则按缺省处理),可选的处理动作有三种:
    1. 忽略此信号。有两个信号不能被忽略:SIGKILL和SIGSTOP。
    2. 执行该信号的默认处理动作。
    3. 供给一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这类方式称为捕捉(Catch)一个信号。

        

        四、信号与中断的区别

        信号与中断的相似点:
    (1)采用了相同的异步通信方式;
    (2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行响应的处理程序;
    (3)都在处理完毕后返回到本来的断点;
    (4)对信号或中断都可停止屏蔽。
    信号与中断的区别:
    (1)中断有优先级,而信号没有优先级,全部的信号都是平等的;
    (2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
    (3)中断响应是及时的,而信号响应通常都有较大的时间延迟。

        

        五、signal(2) 信号注册函数

        typedef void (*__sighandler_t) (int);
    #define SIG_ERR ((__sighandler_t) -1)
    #define SIG_DFL ((__sighandler_t) 0)
    #define SIG_IGN ((__sighandler_t) 1)

        函数原型:
    __sighandler_t signal(int signum, __sighandler_t handler);
    参数
    signal是一个带signum和handler两个参数的函数,准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数由handler给出,handler这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void
    handler也可以是两个特殊值:SIG_IGN屏蔽该信号;SIG_DFL    恢复默认行为

        RETURN VALUE
           signal() returns the previous value of the signal handler, or SIG_ERR on error.

        

        示例小程序:

        

        每日一道理
    曾经辉煌过,曾经凋零过,这可是你至死不渝的生活吗?我亲爱的母亲—大自然。多少次,我伏在地上,去聆听你沉重的脉搏声;多少次,我伫立在山前,去感受那松涛千年的浩瀚。你的豪壮,足以让中华民族腾飞;你的无私,谱写了一曲曲感人至深的千古壮曲。

        

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
     
    /*************************************************************************
        > File Name: process_.c
        > Author: Simba
        > Mail: dameng34@163.com
        > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
     ************************************************************************/

    #include<sys/types.h>
    #include<sys/stat.h>
    #include<unistd.h>
    #include<fcntl.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<signal.h>

    #define ERR_EXIT(m) \
         do { \
            perror(m); \
            exit(EXIT_FAILURE); \
        }  while( 0)

    void handler( int sig);

    int main( int argc,  char *argv[])
    {
        __sighandler_t oldhandler;
        oldhandler = signal(SIGINT, handler);  // 返回值是先前的信号处理程序函数指针
         if (oldhandler == SIG_ERR)
            ERR_EXIT( "signal error");

         while (getchar() !=  '\n') ;

         /* signal(SIGINT, SIGDFL) */
         if (signal(SIGINT, oldhandler) == SIG_ERR)
            ERR_EXIT( "signal error");
         for (; ;) ;

         return  0;
    }

    void handler( int sig)
    {
        printf( "recv a sig=%d\n", sig);
    }

        测试输出如下:

        simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./signal 
    ^Crecv a sig=2
    ^Crecv a sig=2
    ^Crecv a sig=2


    ^C
    simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ 

        程序执行开始注册了SIGINT信号的处理函数,故我们按下ctrl+c 并不会像平常一样终止程序,只是打印了recv a  sig = 2。接着按下回车,从新注册了SIGINT的默认处理,此时再ctrl+c 程序就被终止了。

        将程序中的 32 ~37 行 换成如下的表述:

        

        

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
     
    for (; ;)
    {
        pause();  //使进程挂起直到一个信号被捕获(信号处理函数完成后返回)
         //且调用schedule()使系统调度其他程序运行,
         //这样比完全的死循环的利益是让出cpu
        printf( "pause return\n");
    }

        调用pause 的利益是在等待信号的时候让出cpu,让系统调度其他进程运行,而不是完全的死循环,当然这样ctrl+c 就是始终终止不了程序,我们可以应用 ctrl+\ 发生SIGQUIT信号终止程序。

        事实上根据man手册,signal 函数可移植性并非很好,最好只是用在SIG_DFL, SIG_IGN 上,注册信号处理函数用sigaction 比较好。

        

    文章结束给大家分享下程序员的一些笑话语录: 开发时间
      项目经理: 如果我再给你一个人,那可以什么时候可以完工?程序员: 3个月吧!项目经理: 那给两个呢?程序员: 1个月吧!
    项目经理: 那100呢?程序员: 1年吧!
    项目经理: 那10000呢?程序员: 那我将永远无法完成任务.

  • 相关阅读:
    备战-Java 并发
    备战-Java 容器
    备战-Java 基础
    算法-链表
    2021-常见问题收集整理-1
    算法-双指针
    HTTP 下载文件的一些记录
    语义化版本 2.0.0
    勒索病毒典型传播途径与预防建议
    看杨院士如何解读——北斗与综合PNT体系
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/3085968.html
Copyright © 2020-2023  润新知