Linux 中的信号

为异步事件的处理提供工具。使用信号来接收异步通知。

  驱动开发中的异步通知1也可以参照这一部分

介绍

  信号本质上是 int 类型数字编号。

  信号是事件发生时对进程的通知机制,也可以把它称为软件中断。信号与硬件中断的相似之处在于能够打断程序当前执行的正常流程,其实是在软件层次上对中断机制的一种模拟。

  ‍

  当收到信号时,进程可以有三种处理方式

  • 忽略信号
  • 捕获信号并进行处理
  • 执行系统默认操作

  ‍

  • 中断信号的枚举

    编号 1-31 的是不可靠信号,34-64 对应的是可靠信号

    (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ kill -l
     1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
     6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
    11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
    16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
    21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
    26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
    31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
    38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
    43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
    48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
    53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
    58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
    63) SIGRTMAX-1  64) SIGRTMAX  
    

  ‍

信号分类

可靠信号与不可靠信号

  Linux 信号机制基本上是从 UNIX 系统中继承过来的,早期 UNIX 系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题:

  • 进程每次处理信号后,就将对信号的响应设置为系统默认操作。在某些情况下,将导致对信号的错误处理,因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用 signal(),重新为该信号绑定相应的处理函数。

  • 早期 UNIX 下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能

    丢失(处理信号时又来了新的信号,则导致信号丢失

  ‍

  Linux 支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用

  signal(),在 Linux 系统下,信号值小于 SIGRTMIN(34)的信号都是不可靠信号。

  中断信号的枚举中可以看到 34-64 的是可靠信号,其命名为SIGRTMIN-SIGRTMIN+16,以及SIGRTMAX-SIGRTMAX-14.

  可靠信号支持排队,不会丢失,同时,信号的发送和绑定也出现了新版本,信号发送函数 sigqueue()及信号绑定函数 sigaction()。

实时信号与非实时信息

  实时信号与非实时信号其实是从时间关系上进行的分类,与可靠信号与不可靠信号是相互对应的,非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

  一般我们也把非实时信号(不可靠信号)称为标准信号,如果文档中用到了这个词,那么大家要知道,这里指的就是非实时信号(不可靠信号)

  ‍

  ‍

常见信号

  • SIGINT:按下中断字符,(通常是 CTRL + C)时,内核将发送 SIGINT 信号给前台进程组中的每一个进程
  • SIGQUIT:当用户在终端按下退出字符(通常是 CTRL + \)时,内核将发送 SIGQUIT 信号给前台进程组中的每一个进程。该信号的系统默认操作是终止进程的运行、并生成可用于调试的核心转储文件
  • SIGKILL:此信号为“必杀(sure kill)”信号,用于杀死进程的终极办法,此信号无法被进程阻塞、忽略或者捕获,故而“一击必杀”,总能终止进程。使用 SIGINT 信号和 SIGQUIT 信号虽然能终止进程,但是**前提条件是该进程并没有忽略或捕获这些信号。**​kill -9 pid发送的也是这个信号
  • SIGSTOP:这是一个“必停”信号,用于停止进程(注意停止不是终止,停止只是暂停运行、进程并没有终止),应用程序无法将该信号忽略或者捕获,故而总能停止进程。

  ‍

进程对信号的处理

  收到信号后,进程的三大处理方式:忽略信号、捕获信号或者执行系统默认操作

  通过 signal 或者 sigaction 函数来设置信号的处理方式.

当进程调用 fork 来创建子进程时,子进程会继承父进程信号的处理方式。

  • Signal 函数

    原型

    #include <signal.h>
    typedef void (*sig_t)(int);
    sig_t signal(int signum, sig_t handler);
    

    代码: 演示对 SIGINT 信号的自定义处理

    static void SignalHandler(int sig) {
        printf("Signal handler Receive Signal\n");
        if (sig == SIGINT) {
            printf("Received SIGINT - 正在退出程序\n");
            exit(0);
        }
    }
    int main() {
        printf("Hello World!\n");
        sig_t ret = NULL;
        ret = signal(SIGINT, SignalHandler);
        if (ret == SIG_ERR) {
            perror("signal");
            exit(1);
        }
        while (1) {
            sleep(1);
        }
        exit(0);
    }
    

    演示

    (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ ./main 
    Hello World!
    ^CSignal handler
    Received SIGINT - 正在退出程序
    

    忽略信号或者使用系统默认处理

        ret = signal(SIGINT, SIG_DFL); //系统默认
        ret = signal(SIGINT, SIG_IGN); // 忽略信号
    
  • Sigaction 函数

    除了signal()之外,sigaction()系统调用是设置信号处理方式的另一选择。

    sigaction()更具灵活性以及移植性。sigaction()允许单独获取信号的处理函数而不是设置,并且还可以设置各种属性对调用信号处理函数时的行为施以更加精准的控制

    • 原型

      除了 SIGKILL 信号和 SIGSTOP 信号之外的任何信号。

      act 参数是一个 struct sigaction 类型指针,指向一个 struct sigaction 数据结构

      oldact 参数也是一个 struct sigaction 类型指针,指向一个 struct sigaction 数据结构.会将信号之前的处理方式等信息通过参数 oldact 返回出来

      #include <signal.h>
      int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
      
      
      
      struct sigaction {
       void (*sa_handler)(int); //类似 signal 方式的处理函数
       void (*sa_sigaction)(int, siginfo_t *, void *); //更加可自定义的函数实现
       sigset_t sa_mask;//用于定义对指定信号的临时屏蔽。临时处理 A 信号的时候,希望 B 信号的到来事件被阻塞
       int sa_flags;//sa_flags:参数 sa_flags 指定了一组标志,这些标志用于控制信号的处理过程。例如SA_SIGINFO表示使用sa_sigaction
       void (*sa_restorer)(void);
      };
      
    • 代码示例

      static void SignalHandler(int sig) {
          printf("Signal handler Receive Signal\n");
          if (sig == SIGINT) {
              printf("Received SIGINT - 正在退出程序\n");
              exit(0);
          }
      }
      int main() {
          printf("Hello World!\n");
          struct sigaction sig = {0};
          int ret = -1;
          sig.sa_handler = SignalHandler;
          sig.sa_flags = 0;
          ret = sigaction(SIGINT, &sig, NULL);//丢弃旧的处理函数
          if (ret < 0) {
              perror("sigaction");
              exit(-1);
          }
          while (1) {
              sleep(1);
          }
          exit(0);
      }
      

      测试

      (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ ./main 
      Hello World!
      ^CSignal handler Receive Signal
      Received SIGINT - 正在退出程序
      

  ‍

实际测试

  自定义信号处理函数来实现双击中断信号才退出系统

//定义为 static 将作用域限制在本文件内,其只能在main.c中被调用。可以避免出现命名冲突。
//对于工具函数,使用 static 进行声明是良好的习惯。
static void SignalHandler(int sig) {
    printf("Signal handler Receive Signal\n");
    if (sig == SIGINT) {
        static  int SigCnt = 0;
        SigCnt++;
        printf("Received SIGINT, Cnt %d \n",SigCnt);
        if (SigCnt == 2) {
            exit(0);
        }
    }
    if (sig == SIGALRM) {
        static  int SigCnt = 0;
        SigCnt++;
        printf("Received SIGALRM, Cnt %d \n",SigCnt);
        if (SigCnt == 2) {
            exit(0);
        }
    }
}
int main() {
    printf("Hello World!\n");
    struct sigaction sig = {0};
    int ret = -1;

    sig.sa_handler = SignalHandler;
    sig.sa_flags = 0;
    ret = sigaction(SIGINT, &sig, NULL);//丢弃旧的处理函数
    if (ret < 0) {
        perror("sigaction");
        exit(-1);
    }
    ret = sigaction(SIGALRM, &sig, NULL);
    if (ret < 0) {
        perror("sigaction");
        exit(-1);
    }

    //获取信号的描述信息
    printf("strsignal:SIGINT Description: %s\n", strsignal(SIGINT));

    psignal(SIGINT,"psignal outout");

    while(1) {sleep(1);}
    exit(0);
}

  ‍

(base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ ./main
Hello World!
strsignal:SIGINT Description: Interrupt
psignal outout: Interrupt
^CSignal handler Receive Signal
Received SIGINT, Cnt 1 
^CSignal handler Receive Signal
Received SIGINT, Cnt 2 

  ‍

进程向另一个进程发送信号

kill方法-进程之间发送信号

  通过 kill 来向指定的进程或进程组中的每一个线程发送信号。

  • 函数原型
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

如果 pid 等于 0,则将 sig 发送到当前进程的进程组中的每个进程。
如果 pid 等于-1,则将 sig 发送到当前进程有权发送信号的每个进程,但进程 1(init)除外
如果 pid 小于-1,则将 sig 发送到 ID 为-pid 的进程组中的每个进程
返回值:成功返回 0;失败将返回-1,并设置 errno。

  • 代码示例

    1. 程序 1 接收信号的进程

      //程序 1  接收信号的进程
      static void SignalHandler(int sig) {
          printf("Signal handler Receive Signal\n");
          if (sig == SIGINT) {
              printf("Received SIGINT - 正在退出程序\n");
              exit(0);
          }
      }
      int main() {
          printf("Hello World!\n");
          struct sigaction sig = {0};
          int ret = -1;
          sig.sa_handler = SignalHandler;
          sig.sa_flags = 0;
          ret = sigaction(SIGINT, &sig, NULL);//丢弃旧的处理函数
          if (ret < 0) {
              perror("sigaction");
              exit(-1);
          }
          while (1) {
              sleep(1);
          }
          exit(0);
      }
      
    2. 程序 2 发送信号的进程

      #include <stdio.h>
      #include <stdlib.h>
      #include <sys/types.h>
      #include <signal.h>
      #include <stdlib.h>
      int main(int argc, char *argv[]) {
          printf("Process To Send Signa");
          if (argc < 2) {
              fprintf(stderr, "Error Usage, Received %s",argv[0]);
              exit(1);
          }
          int pid =atoi(argv[1]);
          if (kill(pid,SIGINT)==-1) {
              perror("Error");
              exit(1);
          }
          exit(0);
      }
      
  • 实验验证

    让第一个进程在后台执行。

    (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ gcc ./test.c  -o test
    (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ ./main &
    [1] 565567
    Hello World!
    (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ ps -aux |grep ma
    root          66  0.0  0.0      0     0 ?        I<   11月09   0:00 [kworker/R-md_bitmap]
    root         108  0.0  0.0      0     0 ?        I<   11月09   0:00 [kworker/R-acpi_thermal_pm]
    joe       565567  0.0  0.0   2680  1384 pts/8    S    16:38   0:00 ./main
    joe       565709  0.0  0.0   9304  2416 pts/8    S+   16:38   0:00 grep --color=auto ma
    (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ ./test  565567
    Process To Send SignaSignal handler Receive Signal
    Received SIGINT - 正在退出程序
    [1]+  已完成               ./main
    (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ 
    

  ‍

raise方法-向进程自身发送信号

  raise 向进程本身发送一个 信号

  • 函数原型

  • 示例代码

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    #include <stdlib.h>
    static void SignalHandler(int sig) {
        printf("Signal handler Receive Signal\n");
        if (sig == SIGINT) {
            printf("Received SIGINT - 正在退出程序\n");
            exit(0);
        }
    }
    int main() {
        printf("Hello World!\n");
        struct sigaction sig = {0};
        int ret = -1;
        sig.sa_handler = SignalHandler;
        sig.sa_flags = 0;
        ret = sigaction(SIGINT, &sig, NULL);//丢弃旧的处理函数
        if (ret < 0) {
            perror("sigaction");
            exit(-1);
        }
        int SecCnt = 0;
        while (1) {
            sleep(1);
            SecCnt++;
            printf("SecCnt: %d\n", SecCnt);
            if (SecCnt > 5) {
                raise(SIGINT);//到达时间,触发向自己发送信号
            }
        }
        exit(0);
    }
    
  • 实验验证

    /home/joe/Code/Embeed/ApplicationDevelop/MyImplement/Signal/cmake-build-debug/Signal
    Hello World!
    SecCnt: 1
    SecCnt: 2
    SecCnt: 3
    SecCnt: 4
    SecCnt: 5
    SecCnt: 6
    Signal handler Receive Signal
    Received SIGINT - 正在退出程序
    进程已结束,退出代码为 0
    

  ‍

使用alarm来定时发送信号

  使用 alarm()​函数可以设置一个定时器(闹钟),当定时器定时时间到时,内核会向进程发送 SIGALRM​信号。每个进程只能设置一个 alarm 闹钟。ˇ

  • 函数原型

    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);
    
    • seconds:设置定时时间,以秒为单位;如果参数 seconds 等于 0,则表示取消之前设置的 alarm 闹钟。
    • 返回值:如果在调用 alarm()时,之前已经为该进程设置了 alarm 闹钟还没有超时,则该闹钟的剩余值作为本次 alarm()函数调用的返回值,之前设置的闹钟则被新的替代;否则返回 0。
  • 示例代码

    //定义为 static 将作用域限制在本文件内,其只能在main.c中被调用。可以避免出现命名冲突。
    //对于工具函数,使用 static 进行声明是良好的习惯。
    static void SignalHandler(int sig) {
        printf("Signal handler Receive Signal\n");
        if (sig == SIGINT) {
            printf("Received SIGINT - 正在退出程序\n");
            exit(0);
        }
        if (sig == SIGALRM) {
            printf("Received SIGALRM\n");
            exit(0);
        }
    }
    int main() {
        printf("Hello World!\n");
        struct sigaction sig = {0};
        int ret = -1;
        sig.sa_handler = SignalHandler;
        sig.sa_flags = 0;
        ret = sigaction(SIGINT, &sig, NULL);//丢弃旧的处理函数
        if (ret < 0) {
            perror("sigaction");
            exit(-1);
        }
        ret = sigaction(SIGALRM, &sig, NULL);
        if (ret < 0) {
            perror("sigaction");
            exit(-1);
        }
        alarm(3);
        int timeCnt=0;
        while (1) {
            sleep(1);
            printf("Running \t %d \n",timeCnt++);
        }
        exit(0);
    }
    
  • 验证

    在第三秒,还没触发printf("Running \t %d \n",timeCnt++);就已经触发闹钟了。

    Hello World!
    Running 	 0 
    Running 	 1 
    Signal handler Receive Signal
    Received SIGALRM
    进程已结束,退出代码为 0
    

  ‍

使用 pause 来暂停进程

  pause()系统调用可以使得进程暂停运行、进入休眠状态,直到进程捕获到一个信号为止,只有执行了信号处理函数并从其返回时,pause()才返回。

  • 函数原型

    #include <unistd.h>
    int pause(void);
    
  • 示例代码

    利用 alarm 和 pause 来模拟 sleep 功能。

    //定义为 static 将作用域限制在本文件内,其只能在main.c中被调用。可以避免出现命名冲突。
    //对于工具函数,使用 static 进行声明是良好的习惯。
    static void SignalHandler(int sig) {
        printf("Signal handler Receive Signal\n");
        if (sig == SIGINT) {
            printf("Received SIGINT - 正在退出程序\n");
            exit(0);
        }
        if (sig == SIGALRM) {
            printf("Received SIGALRM\n");
            exit(0);
        }
    }
    int main() {
        printf("Hello World!\n");
        struct sigaction sig = {0};
        int ret = -1;
        sig.sa_handler = SignalHandler;
        sig.sa_flags = 0;
        ret = sigaction(SIGINT, &sig, NULL);//丢弃旧的处理函数
        if (ret < 0) {
            perror("sigaction");
            exit(-1);
        }
        ret = sigaction(SIGALRM, &sig, NULL);
        if (ret < 0) {
            perror("sigaction");
            exit(-1);
        }
        alarm(3);
        pause(); //暂停进程,直到被信号唤醒。
        exit(0);
    }
    
  • 验证

    /home/joe/Code/Embeed/ApplicationDevelop/MyImplement/Signal/cmake-build-debug/Signal
    Hello World!
    Signal handler Receive Signal
    Received SIGALRM
    进程已结束,退出代码为 0
    

  ‍

信号集

需要有一个能表示多个信号(一组信号)的数据类型---信号集(signalset),很多系统调用都

使用到了信号集这种数据类型来作为参数传递

  ‍

初始化信号集

一个是初始化为空的信号集,一个是初始化为满的信号集

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

  ‍

添加/移除信号

int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

  ‍

测试信号是否在信号集中

  返回值:如果信号 signum 在信号集 set 中,则返回 1;如果不在信号集 set 中,则返回 0;失败则返回-1,并设置 errno。

#include <signal.h>
int sigismember(const sigset_t *set, int signum);

  ‍

获取信号的相关信息

sys_siglist

  在 Linux 下,每个信号都有一串与之相对应的字符串描述信息,用于对该信号进行相应的描述。这些字符串位于 sys_siglist 数组中,sys_siglist 数组是一个 char *类型的数组,数组中的每一个元素存放的是一个字符串指针,指向一个信号描述信息。

  • 示例程序

    获取对应信号的描述信息

  • 验证

strsignal

  需要额外包含<string.h>

  • 示例程序

        //获取信号的描述信息
        printf("SIGINT Description: %s\n", strsignal(SIGINT));
    
  • 验证

    SIGINT Description: Interrupt
    

  ‍

  ‍

psignal

  直接在标准错误上输出信号的描述信息。

  原型

#include <signal.h>
void psignal(int sig, const char *s);

  调用 psignal()函数会将参数 sig 指定的信号对应的描述信息输出到标准错误,并且还允许调用者添加一些输出信息,由参数 s 指定。

  示例代码

    //获取信号的描述信息
    printf("strsignal:SIGINT Description: %s\n", strsignal(SIGINT));

    psignal(SIGINT,"psignal outout");

  验证


strsignal:SIGINT Description: Interrupt
psignal outout: Interrupt


  ‍

信号掩码

  • Sigaction 函数这里就提到了存在一个成员变量,其表示信号的掩码,用于设置在处理信号时,希望阻塞的信号(无需包括其自身,系统会自动添加自身到掩码中)。

  • 内核为每一个进程维护了一个信号掩码(其实就是一个信号集),即一组信号。当进程接收到一个属于信号掩码中定义的信号时,该信号将会被阻塞、无法传递给进程进行处理,那么内核会将其阻塞,直到该信号从信号掩码中移除,内核才会把该信号传递给进程从而得到处理。

  • 当设置某一个信号的处理函数之后,当信号触发时,内核会将该信号加入阻塞的信号集中,相同的信号会被阻塞。直到当前信号处理函数运行完毕,内核会将该信号从掩码中移除。

  ‍

  ‍

  原型

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 参数 how 可以设置为以下宏:

    • SIG_BLOCK:将参数 set 所指向的信号集内的所有信号添加到进程的信号掩码中。换言之,将信号掩码设置为当前值与 set 的并集。
    • SIG_UNBLOCK:将参数 set 指向的信号集内的所有信号从进程信号掩码中移除。
    • SIG_SETMASK:进程信号掩码直接设置为参数 set 指向的信号集。

  使用测试

  将 SIGALRM 信号进行屏蔽

  • 源码
    //实现信号掩码的设置
    sigset_t sigset;
    sigemptyset(&sigset); //初始化为空
    sigaddset(&sigset, SIGALRM);//屏蔽闹钟信号
    ret = sigprocmask(SIG_SETMASK, &sigset, NULL); //为信号掩码设置对应的掩码
    if (ret == -1) {
        perror("sigprocmask");
        exit(-1);
    }
    alarm(2);
    while(1) {sleep(1);}
    exit(0);

  • 验证

    alarm 信号没有被触发

    (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ ./main
    Hello World!
    strsignal:SIGINT Description: Interrupt
    psignal outout: Interrupt
    ^CSignal handler Receive Signal
    Received SIGINT, Cnt 1 
    ^CSignal handler Receive Signal
    Received SIGINT, Cnt 2 
    (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ 
    

  ‍


  实际的应用中,可以用于设置一个代码段不断中断信号所中断。执行前将中断信号加入掩码,执行完毕将中断信号放入掩码,然后调用 pause 来等待之前被阻塞的信号。

  存在问题就是当我恢复掩码之后,还没调用 pause,此时信号就来了。那么此时的 pause 就是在等待下一个信号了。

  因此,需要将这个(恢复信号掩码,挂起进程)过程封装成一个原子操作。这就是sigsuspend()。其是一个系统调用

  原型

#include <signal.h>
int sigsuspend(const sigset_t *mask);

  作用

  sigsuspend()函数会将参数 mask 所指向的信号集来替换进程的信号掩码,也就是将进程的信号掩码设置为参数 mask 所指向的信号集,然后挂起进程,直到捕获到信号被唤醒(如果捕获的信号是 mask 信号集中的成员,将不会唤醒、继续挂起。其等价与 SIG_SETMASK。

  ‍

  ‍

  判断进程中有哪些信号正在阻塞

#include <signal.h>
int sigpending(sigset_t *set);

相当于以原子方式实现这样的操作
sigprocmask(SIG_SETMASK, &mask, &old_mask);
pause();
sigprocmask(SIG_SETMASK, &old_mask, NULL);

  ‍

  实战:判断 SIGINT 信号是否处于等待状态。然后恢复其进程

    //实现信号掩码的设置
    sigset_t sigset;
    sigemptyset(&sigset); //初始化为空
    sigaddset(&sigset, SIGALRM);//屏蔽闹钟信号
    ret = sigprocmask(SIG_SETMASK, &sigset, NULL); //为信号掩码设置对应的掩码
    if (ret == -1) {
        perror("sigprocmask");
        exit(-1);
    }
    alarm(2);

    sigset_t checksigset;
    sigemptyset(&checksigset);
    sleep(3);
    //获取正在等待的信号
    sigpending(&checksigset);
    //判断对应信号
    if (sigismember(&checksigset,SIGALRM) == 1) {
        printf("SIGALRM is waiitng\n");
        //解除阻塞
        sigset_t newSigset;
        sigemptyset(&newSigset);
        sigaddset(&newSigset, SIGALRM);
        sigprocmask(SIG_UNBLOCK, &newSigset, NULL);
    }

  进程输出

(base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ ./main
Hello World!
strsignal:SIGINT Description: Interrupt
psignal outout: Interrupt
SIGALRM is waiitng
Signal handler Receive Signal
Received SIGALRM, Cnt 1 
^CSignal handler Receive Signal
Received SIGINT, Cnt 1 
^CSignal handler Receive Signal
Received SIGINT, Cnt 2 

  ‍

信号集阻塞的缺点-引入实时信号

  等待信号集是 一个掩码,仅用于表面是否发生了这个信号,但是无法表示其发生的次数。信号发生;了多次和信号发生了一次,都是最终只发生一次。

  • 标准信号(编号 1-31,SIGRTMIN之前的信号)是不可靠的,核心特性是不排队。无论有多少个相同的阻塞信号被发送,当信号掩码解除阻塞后,进程通常只会收到一个该信号。
  • 系统内核在信号掩码(signal mask)层面不会为标准信号维护一个计数。它只记录某个特定信号是否被发送过并被阻塞。如果该信号被阻塞(pending),第一次发送来时会将其标记为"等待处理"(pending),后续发送的相同信号会被直接丢弃。
  • 只收到一个:当调用 sigprocmask​ 解除信号的阻塞,或者在 sigwait​、sigsuspend 等函数中等待该阻塞信号时,进程只会接收到一次该信号,不管原来有多少个相同的信号被发送过来。

  实时信号相对于标准信号的优势有:

  • 有更多的程序自定义的信号
  • 内核对于实时信号使用的是队列管理机制,某个信号多次发生都会进入队列。所有信号都会被记录下来。
  • 在发送信号时,可以传递参数,一个整型数据或者是一个指针值(但是进程有各自独立的虚拟内存空间)。
  • 不同的实时信号具有相对的优先级,数字更低的具有更高的优先级。
实时信号的发送与接收
  • 使用 sigqueue()系统调用向另一个进程发送实时信号以及伴随数据

  • 接收实时信号的进程要为该信号建立一个信号处理函数,使用sigaction函数为信号建立处理函数,并加入 SA_SIGINFO

  • 函数原型

    • 发送

      #include <signal.h>
      int sigqueue(pid_t pid, int sig, const union sigval value);
      
    • 接收

      使用sigaction来实现对信号的绑定

      struct sigaction sig = {0};
          sig.sa_flags = SA_SIGINFO;// 设置
          sig.sa_sigaction = sig_handler;//使用这个可以接受伴随数据
          if (sigaction(50,&sig,NULL) !=  0) {
              perror("sigaction");
              exit(-1);
          }
      
  • 示例代码

    • 接受者

      #define runTimeSec 50
      /**
       *  Linux中的信号机制,用于实现异步通知
       *
       * typedef void (*sig_t)(int);,所以,sig_t类型指的是无返回值,接受 int 类型的函数
       *
       */
      
      //定义为 static 将作用域限制在本文件内,其只能在main.c中被调用。可以避免出现命名冲突。
      //对于工具函数,使用 static 进行声明是良好的习惯。
      static  void sig_handler(int sig,siginfo_t * info,void* context) {
          sigval_t sigval= info->si_value;
          printf("接收到实时信号:%d \n",sig);
          printf("伴随数据为 %d \n",sigval.sival_int);
          exit(0);
      }
      
      int main() {
          printf("Hello World!\n");
          printf("Current pid \t  %d \n",getpid());
          int ret = -1;
          //进入实时信号的时代
          struct sigaction sig = {0};
          sig.sa_flags = SA_SIGINFO;// 设置
          sig.sa_sigaction = sig_handler;//使用这个可以接受伴随数据
          if (sigaction(50,&sig,NULL) !=  0) {
              perror("sigaction");
              exit(-1);
          }
      
          int timeCnt = 0;
          while(timeCnt < runTimeSec) {
              printf("Current Time: %d \n",timeCnt);
              sleep(1);
              timeCnt++;
          }
          exit(0);
      }
      
    • 发送者

      int main(int argc, char *argv[]) {
          union  sigval  sig_val;
          int pid;
          int sig;
          if (argc< 3) {
              exit(-1);
          }
          pid = atoi(argv[1]);
          sig=atoi(argv[2]);
          printf("Send Signal %d to Process  %d",sig,pid);
      
          /**发送信号*/
          sig_val.sival_int = 10;
          if (sigqueue(pid,sig,sig_val)==-1) {
              perror("sigqueue");
              exit(-1);
          }
          puts("发送成功");
      
          exit(0);
      }
      
  • 验证

    1. 先运行接受者

    2. 运行发送者指定pid和发送的信号

      (base) joe@joe-VM:~/Code/Embeed/ApplicationDevelop/MyImplement/Signal$ ./SignalSender  29712   50
      Send Signal 50 to Process  29712发送成功
      
    3. 接受者成功接收到信号

      /home/joe/Code/Embeed/ApplicationDevelop/MyImplement/Signal/cmake-build-debug/Signal
      Hello World!
      Current pid 	  29712 
      Current Time: 0 
      Current Time: 1 
      Current Time: 2 
      Current Time: 3 
      Current Time: 4 
      Current Time: 5 
      Current Time: 6 
      Current Time: 7 
      Current Time: 8 
      Current Time: 9 
      Current Time: 10 
      Current Time: 11 
      接收到实时信号:50 
      伴随数据为 10 
      

  ‍

  ‍

  ‍

异常退出:abort函数

  了应用程序中结束进程的几种方法,譬如使用 exit()、_exit()或_Exit()这些函数来终止进程。这些方法使用于正常退出应用程序,而对于异常退出程序,则一般使用 abort() 库函数,使用 abort()终止进程运行,会生成核心转储文件,可用于判断程序调用 abort()时的程序状态。

  函数 abort()通常产生 SIGABRT 信号来终止调用该函数的进程,SIGABRT 信号的系统默认操作是终止进程运行、并生成核心转储文件。

  ‍

  • 示例程序

    #define runTimeSec 50
    /**
     *  Linux中的信号机制,用于实现异步通知
     *
     * typedef void (*sig_t)(int);,所以,sig_t类型指的是无返回值,接受 int 类型的函数
     *
     */
    
    //定义为 static 将作用域限制在本文件内,其只能在main.c中被调用。可以避免出现命名冲突。
    //对于工具函数,使用 static 进行声明是良好的习惯。
    static  void sig_handler(int sig,siginfo_t * info,void* context) {
        sigval_t sigval= info->si_value;
        printf("接收到实时信号:%d \n",sig);
        printf("伴随数据为 %d \n",sigval.sival_int);
        exit(0);
    }
    
    static void sig_abort_handler(int sigNum) {
        printf(" Received Signal %d \n",sigNum);
    }
    
    int main() {
        printf("Hello World!\n");
        printf("Current pid \t  %d \n",getpid());
        int ret = -1;
        //进入实时信号的时代
        struct sigaction sig = {0};
        sig.sa_flags = SA_SIGINFO;// 设置
        sig.sa_sigaction = sig_handler;//使用这个可以接受伴随数据
        if (sigaction(50,&sig,NULL) !=  0) {
            perror("sigaction");
            exit(-1);
        }
    
    
        sig.sa_flags = 0;
        sig.sa_handler = sig_abort_handler;
        if (sigaction(SIGABRT,&sig,NULL) <0 ) {
            perror("sigaction");
            exit(-1);
        }
        sleep(1);
        abort();//触发异常退出,这会触发信号
        while (1) {
            sleep((1));
        }
        exit(0);
    }
    
  • 控制台输出

    即使在我们的程序当中捕获了 SIGABRT 信号,但是程序依然会无情的终止,无论阻塞或忽略 SIGABRT 信号,abort()调用均不收到影响,总会成功终止进程

    /home/joe/Code/Embeed/ApplicationDevelop/MyImplement/Signal/cmake-build-debug/Signal
    Hello World!
    Current pid 	  36955 
     Received Signal 6 
    
    进程已结束,退出代码为 134 (interrupted by signal 6:SIGABRT)
    

  ‍


  1. 异步通知

    让驱动自己通知应用程序,其可以被访问了。也就是可以实现,驱动就绪的情况下,应用程序自动执行。

    之前的阻塞、非阻塞都是应用去问驱动是否就绪。

    三种方法的区别
    特性阻塞 I/O非阻塞 I/O异步通知
    CPU 利用率低(进程休眠)中(轮询消耗 CPU)高(事件驱动)
    实现复杂度简单中等较高
    响应延迟高(等待唤醒)低(立即返回)最低(主动通知)
    系统开销中等(上下文切换)高(频繁轮询)低(事件触发)
    并发性能一般优秀

    结合应用场景

    1. 教学/简单应用阻塞 I/O

      • 代码简单,易于理解
      • 适合学习驱动开发基础
    2. 桌面应用/用户界面非阻塞 I/O

      • 保持界面响应性
      • 平衡复杂度和性能
    3. 高性能服务器/实时系统异步通知

      • 追求极致性能
      • 有经验丰富的开发团队
    性能要求推荐方案理由
    低延迟异步通知事件驱动,响应最快
    低 CPU 占用阻塞 I/O休眠时不消耗 CPU
    高并发异步通知可以高效处理多个 I/O 源
    易维护阻塞 I/O代码逻辑简单清晰
    异步通知介绍

    驱动程序通过发送信号给应用程序,告知应用程序可以执行操作。存在不同的信号,也就可以根据信号执行不同的操作。

    应用程序中的操作
    • 编写信号处理函数

      /*
       * SIGIO信号处理函数
       * @param - signum 	: 信号值
       * @return 			: 无
       */
      static void sigio_signal_func(int signum)
      {
      	int err = 0;
      	unsigned int keyvalue = 0;
      
      	err = read(fd, &keyvalue, sizeof(keyvalue));
      	if(err < 0) {
      		/* 读取错误 */
      	} else {
      		printf("sigio signal! key value=%d\r\n", keyvalue);
      	}
      }
      
    • 注册信号处理关系

      fcntl(fd, F_SETFL, flags | FASYNC)会触发驱动的 fasync ​函数,进而启动 fasync_helper ​实现注册

      
      	/* 设置信号SIGIO的处理函数 */
      	signal(SIGIO, sigio_signal_func);
      
      	fcntl(fd, F_SETOWN, getpid());		/* 设置当前进程接收SIGIO信号 	*/
      	flags = fcntl(fd, F_GETFL);			/* 获取当前的进程状态 			*/
      	fcntl(fd, F_SETFL, flags | FASYNC);	/* 设置进程启用异步通知功能 	*/	
      
    驱动程序的操作
    • 新建结构体

      struct fasync_struct *async_queue;		/* 异步相关结构体 */
      static struct file_operations imx6uirq_fops = {
      	.owner = THIS_MODULE,
      	.open = imx6uirq_open,
      	.read = imx6uirq_read,
      	.poll = imx6uirq_poll,
      	.fasync = imx6uirq_fasync,
      	.release = imx6uirq_release,
      };
      
    • 完善 fasync 函数

      应用端的 fcntl(fd, F_SETFL, O_ASYNC | FASYNC) ​会调起这个函数

      static int imx6uirq_fasync(int fd, struct file *filp, int on)
      {
      	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
      	return fasync_helper(fd, filp, on, &dev->async_queue);
      }
      
    • 进行异步通知触发

      发送信号

      if(atomic_read(&dev->releasekey)) {  /* 一次完整的按键过程 */
              if(dev->async_queue)
                  kill_fasync(&dev->async_queue, SIGIO, POLL_IN);  /* 发送SIGIO信号 */
          }
      
    • 资源清理

      释放异步通知资源

      static int imx6uirq_release(struct inode *inode, struct file *filp)
      {
          return imx6uirq_fasync(-1, filp, 0);  // 释放异步通知资源
      }
      

    交互流程
    sequenceDiagram participant App as 应用程序 participant Kernel as 内核 participant Driver as 驱动程序 participant HW as 硬件(按键) Note over App,Driver: 1. 初始化阶段 App->>Kernel: open("/dev/asyncnoti") Kernel->>Driver: 调用open()函数 App->>App: signal(SIGIO, handler) App->>Kernel: fcntl(fd, F_SETOWN, getpid()) App->>Kernel: fcntl(fd, F_SETFL, FASYNC) Kernel->>Driver: 调用fasync()函数 Driver->>Driver: fasync_helper()注册进程 Note over App,Driver: 2. 等待阶段 App->>App: sleep(2) - 做其他工作 App->>App: sleep(2) - 做其他工作 Note over App,Driver: 3. 中断处理阶段 HW->>Driver: 按键按下(中断) Driver->>Driver: key0_handler() Driver->>Driver: 启动定时器消抖 Driver->>Driver: timer_function() Driver->>Driver: kill_fasync()发送SIGIO Driver->>Kernel: 发送SIGIO信号 Kernel->>App: 投递SIGIO信号 Note over App,Driver: 4. 信号处理阶段 App->>App: 信号处理函数执行 App->>Kernel: read(fd, &keyvalue, sizeof(keyvalue)) Kernel->>Driver: 调用read()函数 Driver->>App: 返回按键值 App->>App: printf("sigio signal! key value=%d", keyvalue) Note over App,Driver: 5. 继续等待 App->>App: sleep(2) - 继续做其他工作

    驱动使用
    
    /lib/modules/4.1.15+ # depmod
    /lib/modules/4.1.15+ # modprobe ./asyncnoti.ko
    modprobe: module ./asyncnoti.ko not found in modules.dep
    /lib/modules/4.1.15+ # modprobe asyncnoti.ko
    /lib/modules/4.1.15+ # ./asyncnotiApp /dev/asyncnoti
    sigio signal! key value=1
    sigio signal! key value=1
    sigio signal! key value=1
    sigio signal! key value=1
    sigio signal! key value=1
    
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值