• Unix环境_信号处理机制


    一、基本的概念
      1、中断
      中止、暂停当前正在执行的进程,转而去执行其它的任务。
      硬中断:来自硬件设备的中断(手机的按键)
      软中断:来自其它程序的中断(信号,Qt中的信号和槽)

      2、信号
      信号是一种软中断,可以把他看作是进程与进程、内核与进程通信的一种方式,它为进程的异步执行,提供了技术支持。

      3、常见的信号
      SIGINT(2)       终端中断信号Ctrl+c
      SIGQUIT(3)    终端退出信号Ctrl+ 
      SIGABRT(6)     调用abort函数产生的异常中止信号
      SIGFPE(8)     算术异常信号(例如除以0、浮点溢出等)
      SIGKILL(9)     死亡信号,不能被捕获或忽略。常用于杀死进程
      SIGSEGV(11)   段错误信号,试图访问未分配的内存,或向没有写权限的内存写入数据
      SIGALRM(14)   闹钟信号,以alarm函数设置的计时器到期,或以setitimer函数设置的间隔时间到期,均产生此信号
      SIGCHLD(17)   子进程结束信号,在一个进程终止或停止时,将此信号发送给其父进程
      SIGCONT(18)   进程继续信号,向处于停止状态的进程发送此信号,令其继续运行
      SIGSTOP(19)   进程暂停信号,不能被捕获或忽略。停止一个进程
      SIGTSTP(20)    终端停止信号,用户按停止键(Ctrl+Z),产生此信号,并送至前台进程组的所有进程

      4、不可靠信号(非实时)
       编号小于SIGRTMIN(34)的信号都是不可靠的,这些信号是建立在早期的信号机制上的,一个事件发生可能会产生多次信号。
       不可靠信号不支持排队,在接收信号的时候可能会丢失,如果信号发给一个进程多次,它可能只接收到一次,其它的可能就丢失了。
       进程在处理这种信号的时候,哪怕设置的信号处理函数,当信号处理函数执行完毕后,会再次恢复成默认的信号处理方式。

      5、可靠信号(实时)
        位于[SIGRTMIN(34),SIGRTMAX(64)]区间的都是可靠信号。
        可靠信号支持排队,不会丢失。
        无论是可靠信号还是不可靠信号都是通过:kill、signal、sigqueue、sigaction函数进行处理。

      6、信号的来源
      硬件来源:
      键盘:Ctrl+c、Ctrl+、Ctrl+z
      驱动:硬件设备被激活、使用、失效。
      内存:非法访问内存。
      软件来源:
      命令:kill、killall
      函数:kill/raise/alarm/setitimer/sigqueue。

      7、信号的处理方式
        忽略
        终止
        终止+core
        捕获并处理

    二、信号的捕获和处理
      #include <signal.h>

      typedef void (*sighandler_t)(int);

      sighandler_t signal(int signum, sighandler_t handler);
      功能:注册一个信号处理函数
      signum:信号的编号,可以直接写数字,也可以使用系统提供的宏。
      handler:函数指针,可设置为SIG_IGN (忽略信号)、SIG_DFL (恢复信号默认的处理方式)

      例如设置  signal(SIGINT,SIG_IGN),终端中断信号就被忽略了,Ctrl+c将失去其作用

      又如下所示,按下Crtl+c,则会调用函数sigint函数,打印“您按了Ctrl+c”

            

    三、信号的发送
      1、键盘
        Ctrl+c 终端中断信号
        Ctrl+z 终端暂停信号,fg命令再次开启
        Ctrl+/ 终端退出信号
      2、错误产生的信号
        除0
        非法内存访问
        硬件总线
      3、命令产生的信号
        kill -信号 进程号
        killall -信号 程序名(杀死所有同名的进程)。
      4、函数产生的信号
       int kill(pid_t pid, int sig);
       功能:向指定的进程发送信号

       返回值:成功返回0,出错返回-1

       pid:目标进程的进程ID(可通过函数getpid获取)

       sig:信号的编号,可以直接写数字,也可以使用系统提供的宏。

      例如:kill(5684,SIGINT)表示向进程ID为5684的进程发送了一个终端中断信号(Ctrl+c)。
      sig的值可以为0,其不会向进程发送信号,但是会测试是否能向pid发送信号,这样可以检测一个进程是否存在,返回0表示进程存在,返回-1表示进程不存在,errno为ESRCH。
      int raise(int sig);

      功能:向自己发送信号

    四、功能函数

      1、pause函数
      #include <unistd.h>
      int pause(void);
      功能:休眠

      进程调用了pause函数后会进程睡眠状态,直到有信号把它叫醒(不被忽略的信号)。
      当信号来临后,先执行信号处理函数,信号处理函数结束后pause再返回。
      pause函数要么不返回(一直睡眠),要么返回-1,并且修改errno的值。
         从功能上来讲它相当于没有时间限制的sleep函数。

      2、sleep函数
      #include <unistd.h>
      unsigned int sleep(unsigned int seconds);
      功能:使用调用的进程睡眠seconds秒

      调用sleep的进程如果没有睡眠足够的秒数,除非收到信号后才会返回。
      sleep的返回值是0,或剩余的睡眠秒数。
      相当于有时间限制的pause
      

      3、usleep函数

      int usleep(useconds_t usec);v
      功能:睡眠usec微秒,它是一种更精确的睡眠函数。

      4、alarm函数
      #include <unistd.h>
      unsigned int alarm(unsigned int seconds);
      功能:定时一个闹钟信号,让内核向调用它的进程,在seconds秒后发送一个SIGALRM信号,SIGALRM信号的默认处理方式是直接退出。

    五、信号集和信号屏蔽
      1、信号集:
      多个信号的集合,sigset_t
      由128个二进制位组成,每个二进制位表示一个信号

      int sigemptyset(sigset_t *set);   功能:清空信号集
     
      int sigfillset(sigset_t *set);          功能:填满信号信

      int sigaddset(sigset_t *set, int signum);  功能:向信号集中添加信号

      int sigdelset(sigset_t *set, int signum);   功能:从信号集中删除信号

      int sigismember(const sigset_t *set, intsignum);  功能:测试一个信号集中是否有某个信号
      返回值:有返回1,没有返回0,失败返回-1

    #include <stdio.h>
    #include <signal.h>
    
    int main()
    {
        // 定义一个信号集
        sigset_t set;
        sigemptyset(&set);
        //sigfillset(&set);
        printf("向信号集中添加信号%d %s
    ",SIGINT,sigaddset(&set,SIGINT)?"失败":"成功");
        printf("向信号集中添加信号%d %s
    ",SIGSEGV,sigaddset(&set,SIGSEGV)?"失败":"成功");
    
        for(int signum = 1; signum<65; signum++)
        {
            printf("信号%d,%s信号集中
    ",signum,
                sigismember(&set,signum)?"存在":"不存在");
        }
    }


      2、屏蔽信号集中的信号 
      每个进程都有一个信号掩码(signal mask),它就是一个信号集,里面包含了进程所屏蔽的信号。
      int sigprocmask(int how, const sigset_t*set, sigset_t *oldset);
      功能:设置进程的信号掩码(信号屏蔽码)
      how:修改信号掩码的方式
      SIG_BLOCK:向信号掩码中添加信号
      SIG_UNBLOCK:从信号掩码中删除信号
      SIG_SETMASK:用新的信号集替换旧的信号掩码
      newset:新添加、删除、替换的信号集,也可以为空
      oldset:获取旧的信号掩码
      当newset为空时,就是在备份信号掩码

      当进程执行一些敏感操作时不希望被打扰(原子操作),此需要向屏蔽信号。屏蔽信号的目的不是为了不接收信号,而是延时接收,当处理完要做的事情后,应该把屏蔽的信号还原。当信号屏蔽时发生的信号会记录一次,这个信号设置为末决状态,当信号屏蔽结束后,会再发送一次.不可靠信号在信号屏蔽期间无论信号发生多少次,信号解除屏蔽后,只发送一次。可靠信号在信号屏蔽期间发生的信号会排队记录,在信号解除屏蔽后逐个处理。在执行处理函数时,会默认把当前处理的信号屏蔽掉,执行完成后再恢复
     
      int sigpending(sigset_t *set);
      功能:获取末决状态的信号

    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    
    // 更新数据
    void updata(void)
    {
        for(int i=0; i<10; i++)
        {
            printf("更新第%d条数据...
    ",i);
            sleep(1);
        }
    }
    
    void sigint(int signum)
    {
        printf("收到信号%d,正在处理...
    ",signum);
    }
    
    int main()
    {
        signal(34,sigint);
        printf("我是进程%d
    ",getpid());
        // 定义信号集
        sigset_t set,oldset;
        // 初始化信号集
        sigemptyset(&set);
        // 向信号集中添加信号
        printf("向信号集中添加%d %s
    ",34,sigaddset(&set,34)?"失败":"成功");
        // 设置信号掩码
        printf("设置信号掩码%s
    ",sigprocmask(SIG_SETMASK,&set,&oldset)?"失败":"成功");
    
        updata();
        sigpending(&set);
        for(int signum=1; signum<65; signum++)
        {
            if(sigismember(&oldset,signum))
            {
                printf("默认屏蔽的的信号%d
    ",signum);
            }
        }
        printf("还原信号掩码%s
    ",sigprocmask(SIG_SETMASK,&oldset,NULL)?"失败":"成功");
        pause();
    }

    六、信号处理sigaction
      int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
      功能:设置或获取信号处理方式
      int signum:要处理的信号编号

      struct sigaction {
      // 信号处理函数指针
      void (*sa_handler)(int);
      // 信号处理函数指针 需要使用sigqueue发送信号
      void (*sa_sigaction)(int, siginfo_t *, void *);
      // 信号屏蔽码
      sigset_t sa_mask;

      int sa_flags;
      SA_NOCLDSTOP:忽略SIGCHLD信号
      SA_NODEFER/SA_NOMASK:在处理信号时不屏蔽信号
      SA_RESETHAND:处理完信号后,恢复系统默认处理方式
      SA_RESTART:当信号处理函数中断的系统调用,则重启系统调用。
      SA_SIGINFO:用sa_sigaction处理信号
      // 保留
      void (*sa_restorer)(void);
    };


      int sigqueue(pid_t pid, int sig, const union sigval value);

      功能:向指定的进程发送信号,并附加一些数据

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    
    int main()
    {
        int pid = 0;
        scanf("%d",&pid);
        union sigval value = {};
        value.sival_int = 1314;
        sigqueue(pid,SIGINT,value);
    }
    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    
    void sigint(int signum,siginfo_t* info,void* ptr)
    {
        printf("收到信号%d,附加数据有:%d %d
    ",getpid(),info->si_pid,info->si_int);
    }
    
    int main()
    {
        printf("%d
    ",getpid());
        struct sigaction act = {};
        act.sa_sigaction = sigint;
        act.sa_flags = SA_SIGINFO;
    
        sigaction(SIGINT,&act,NULL);
        pause();
    }
  • 相关阅读:
    HBase导入数据同时与Phoenix实现同步映射
    Hive导入数据到HBase,再与Phoenix映射同步
    CDH5.16.1离线集成Phoenix
    设计原则学习笔记
    Maven安装配置
    SpringBoot之Mybatis操作中使用Redis做缓存
    Linux服务器防火墙白名单设置
    Linux查看端口占用情况,并强制释放占用的端口
    shell脚本切割tomcat日志文件
    mysql读写分离
  • 原文地址:https://www.cnblogs.com/xiehuan-blog/p/9392474.html
Copyright © 2020-2023  润新知