信号
    信号是Linux进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件,发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:

    对于前台进程(占用shell),用户可以通过输入特殊的终端字符来给它发送信号。比如Ctrl+C通常会给进程发送一个中断信号。

    硬件发生异常,即硬件检查到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被除0,或者引用了无法访问的内存区域。

    系统状态变化,比如alarm定时器到期将引起SIGALRM信号,进程执行的cpu时间超限,或者该进程的某个子进程退出。

    运行kill命令或调用kill命令

    一般信号用于两种用途:1让进程知道已经发生了一个特定的事情2强迫进程执行它自己代码中的信号处理程序
    信号的特定:简单,不能携带大量信息,满足某个特定条件才发送,优先级比较高。
    查看系统定义的信号列表:kill -l 前31个信号为常规信号 其余为实时信号

    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

    图片1.png
    SIGCHILD不是只在子进程结束才发出,在子进程状态改变 比如子进程挂起。。都会发出这个信号
    注意SIGCONT,SIGSTOP中说的进程停止并不是指进程结束,指的是进程暂停。
    图片2.png

    常见的5种信号默认处理动作
    查看信号的详细信息 man 7 signal
    1Term 终止进程 2Ign当前进程忽略掉这个信号3Core终止进程 并生成一个Core文件4Stop暂停当前进程5Cont继续执行当前被暂停的进程

    信号的几种状态:产生、未决(信号还未被进程接收到)、递达(进程接收并处理信号)
    SIGKILL和SIGSTOP信号不能被捕捉、阻塞或者忽略 只能执行默认动作

    CORE文件会将程序终止的异常信息保存。ulimit -a中有一个参数core file size (block -c) 默认为0表示不会生成core文件,ulimit -c 1024将core文件的最大大小改为1024个字节。这样的话如果产生核心以转储的错误,错误信息会存在同路径下的core文件中,core文件需要gdb调试才能用,需要在编译时gcc -g(调试,断点信息)
    gdb a.out (gdb)core-file core(core文件文件名) 会输出错误信息

    kill、raise、abort函数
    int kill(pid_t pid,int sig);
    int raise(int sig);
    vois abort(void);

    1. /*
    2. #include <sys/types.h>
    3. #include <signal.h>
    4. int kill(pid_t pid, int sig);
    5. 给任何进程或进程组发送信号
    6. pid>0 则发送给这个特定PID的进程信号,pid=0给当前调用kill的进程所在的进程组中的所有进程发送信号
    7. pid=-1向允许接收当前进程信号的所有进程发送信号(除了1号init进程)
    8. pid<-1 向进程组号为-pid的所有进程发送信号
    9. sig=0则不发送信号
    10. 成功返回0 失败返回-1
    11. 不要被名字迷惑了 kill不仅仅用于杀死进程 kill(getpid(),9) kill(getppid(),9)
    12. kill用于向某个进程(pid)发送信号(sig 信号编号或宏值(信号名称))
    13. #include <signal.h>
    14. int raise(int sig);
    15. 给当前调用raise的进程发送信号
    16. 成功返回0 失败返回非0
    17. vois abort(void);
    18. 发送 SIGABRT信号给当前的进程 SIGABRT 终止进程并产生core文件
    19. */
    20. #include <stdio.h>
    21. #include <sys/types.h>
    22. #include <signal.h>
    23. int main()
    24. {
    25. //演示kill
    26. pid_t pid = fork();
    27. if (pid == 0) //子进程
    28. {
    29. ;
    30. }
    31. else if (pid > 0) //父进程
    32. {
    33. //父进程中 返回的pid是子进程的pid
    34. kill(pid, 9);
    35. }
    36. return 0;
    37. }

    alarm函数
    usigned int alarm(unsigned int seconds);

    1. /*
    2. #include <unistd.h>
    3. unsigned int alarm(unsigned int seconds);
    4. 设置定时器 函数调用开始倒计时seconds秒
    5. 参数为0定时器无效 不进行倒计时也不发送信号 我们取消定时器就是通过将alarm设置为0实现的
    6. 当时间到后 函数会给调用alarm的当前进程发送SIGALAEM信号
    7. 返回值
    8. 第一次调用 alarm 返回0
    9. 之前已调用过alarm 返回之前的定时器的倒计时剩余时间
    10. 进程收到SIGALRM信号 默认终止当前进程
    11. 每个进程都有且只有唯一的一个定时器!!!
    12. alarm(10);
    13. 过了1秒....
    14. unsigned int rest = alarm(5);//这个5秒倒计时会将上面的10秒定时器给取消掉并且重新从0开始记5秒
    15. 返回的rest为之前10秒定时器余下的没走完的时间 即9秒
    16. 定时器与进程的状态无关,无论进程处于什么状态 alarm都会计时!!!!
    17. alarm不阻塞
    18. */
    19. #include <unistd.h>
    20. #include <stdio.h>
    21. #include <sys/types.h>
    22. #include <signal.h>
    23. int main()
    24. {
    25. //实际运行时间 = 内核时间(系统调用)+用户程序时间+IO消耗的时间
    26. //alarm为系统调用
    27. return 0;
    28. }

    setitimer定时器函数
    int setitimer(int which,const struct itimerval new_val,struct itimerval old_value);

    1. /*
    2. #include <sys/time.h>
    3. int getitimer(int which, struct itimerval *curr_value);
    4. int setitimer(int which, const struct itimerval *new_value,
    5. struct itimerval *old_value);
    6. 非阻塞
    7. 设置定时器 替代alarm函数, alarm精度只能是秒,setitimer进度可以达到us
    8. 可以实现周期性定时
    9. which 使用什么定时时间类型
    10. ITIMER_REAL 真实时间(物理世界的时间)=内核时间(系统调用)+用户程序时间+IO消耗的时间+....
    11. 每次倒计时 时间到发送SIGALRM信号 并重新开始计时
    12. ITIMER_VIRTUAL 虚拟时间 这个线程使用的用户程序时间 计算在应用层递减的时间 不计算在内核层的递减的时间
    13. (包含这个在进程中所有线程消耗的时间)
    14. 每次倒计时 时间到发送SIGVTALRM信号 并重新开始计时
    15. ITIMER_PROF 这个线程使用的内核时间(系统调用)+用户程序时间CPU时间 会减去在系统中阻塞的时间(无内核与用户间切换消耗的时间)
    16. (包含在这个进程中所有线程消耗的时间)
    17. 每次倒计时 时间到发送SIGPROF信号 并重新开始计时
    18. new_value 设置定时器的属性
    19. struct itimerval//定时器结构体
    20. {
    21. struct timeval it_interval; //计时器重新启动动的间歇值 it_interval减到0 发送信号 并重新填充
    22. struct timeval it_value; //计时器安装后首先启动的初始值 it_value的时间值减到0 也会发送一个信号
    23. };
    24. 也可以这么理解 it_interval 下次定时取值 it_value 本次定时设置值
    25. 定时器工作时,先将it_value的时间值减到0,发送一个信号,再将it_value赋值为it_interval的值,重新开始定时,如此反复。
    26. 如果it_value值被设置为0,则定时器停止定时;如果it_value值不为0但it_interval值为0,则定时器在一次定时后终止
    27. struct timeval//时间结构体
    28. {
    29. time_t tv_sec; //秒
    30. suseconds_t tv_usec;//微秒
    31. };
    32. 在setitimer后经过it_value后启动定时器 it_interval就为定时器周期定时时长
    33. old_value :记录了上次调用setitimer所设置的定时器的属性 这是个传出参数
    34. 返回值 成功0 失败-1
    35. */
    36. #include <unistd.h>
    37. #include <stdio.h>
    38. #include <sys/types.h>
    39. #include <signal.h>
    40. #include <sys/time.h>
    41. int main()
    42. {
    43. struct itimerval new_value;
    44. new_value.it_interval.tv_sec = 2; //定时器周期时间
    45. new_value.it_interval.tv_usec = 0; //虽然用不到微秒 但还是设置一下 否则是个随机值 负数的话 是tv_sec-|tv_usec| 正数的话是tv_sec+tv_usec
    46. new_value.it_value.tv_sec = 3; //过多长时间第一次启动定时器
    47. new_value.it_value.tv_usec = 0; //
    48. //过3秒后启动定时器 定时器周期为2秒
    49. setitimer(ITIMER_REAL, &new_value, NULL); //不需要上次的定时器属性 所以old传的是null 非阻塞立即向下执行
    50. }

    signal信号捕捉函数
    sighandler_t signal(int signum,sighandler_t handler);
    int sigaction(int signum,const struct sigaction act,struct sigaction oldact);

    1. /*
    2. #include <signal.h>
    3. typedef void (*sighandler_t)(int);//sighandler_t为函数指针类型 指向只有一个int参数 返回值为0的函数
    4. signal在不同的linux版本是不同的 所以最好用sigaction函数来代替!!!!
    5. sighandler_t signal(int signum, sighandler_t handler);
    6. 捕获信号后 执行相关处理
    7. SIGKILL SIGSTOP 无法被捕获和忽略
    8. signum 要捕获的信号的编号 用宏值
    9. handler 信号处理函数
    10. handler 可以用宏值SIG_IGN忽略这个信号 SIG_DFL采取这个信号默认的动作(看笔记里的表 有讲每个signal的默认动作)
    11. 如果有给这个信号对应的回调函数指针 则(内核自动调用)执行这个函数
    12. 返回值
    13. 成功 前一次的handler值(第一次调用 返回null) 失败 返回宏SIG_ERR
    14. */
    15. #include <unistd.h>
    16. #include <stdio.h>
    17. #include <stdlib.h>
    18. #include <sys/types.h>
    19. #include <signal.h>
    20. #include <sys/time.h>
    21. void sig_handler(int num);
    22. int main()
    23. {
    24. //注册SIGALRM的信号的回调函数
    25. __sighandler_t ret = signal(SIGALRM, sig_handler); //函数名相当于 这个函数的地址
    26. if (ret == SIG_ERR)
    27. {
    28. perror("signal:");
    29. return -1;
    30. }
    31. struct itimerval new_value;
    32. new_value.it_interval.tv_sec = 2; //定时器周期时间
    33. new_value.it_interval.tv_usec = 0; //虽然用不到微秒 但还是设置一下 否则是个随机值 负数的话 是tv_sec-|tv_usec| 正数的话是tv_sec+tv_usec
    34. new_value.it_value.tv_sec = 3; //过多长时间第一次启动定时器
    35. new_value.it_value.tv_usec = 0; //
    36. //过3秒后启动定时器 定时器周期为2秒
    37. setitimer(ITIMER_REAL, &new_value, NULL); //不需要上次的定时器属性 所以old传的是null 非阻塞立即向下执行
    38. while (1)
    39. ; //不断地 等待定时器触发回调函数
    40. }
    41. void sig_handler(int num)
    42. {
    43. printf("捕获到的信号编号为:%d\n", num); //num=14 SIGALRM
    44. }

    信号集以及相关函数
    信号集许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可以使用一个叫信号集的数据结构来表示,其系统数据类型为sigset_t(64位数据 每一位代表一种信号)。在PCB中有两个非常重要的信号集,一个称之为“阻塞信号集”另一个称之为“未决信号集”。这两个信号集都是内核使用位图(二进制)机制来实现的,但操作系统不允许我们直接对这两个信号集进行位操作,而需要自定义另一个集合,借助信号集操作函数(系统调用)来对PCB的信号集进行修改。阻塞信号集可以修改未决信号集无法修改(只能读)

    信号集的“未决”是一种状态指的是从信号的产生到信号被处理前的这一段时间(信号被处理了 就是递达状态),信号的“阻塞”是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
    信号的阻塞就是让系统暂时保留信号待以后发送,由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作所以暂时阻塞一些信号。
    图片5.png
    用户通过键盘 ctrl+c 产生2号信号SIGINT(信号被创建),此时SIGINT还未被处理属于未决状态,未决信号集存储已经被创建但是没有被处理的信号,此时2-SIGINT位置被置为1(未决信号集中0表示非未决状态 1表示未决状态)。在未决信号被处理前需要和阻塞信号集进行比较,若此时阻塞信号集的2号位置为0表示2号信号不阻塞(2号信号可被发送),进程接收到并处理结束后未决信号集的2号位置立即变为0。若此时阻塞信号集的2号位置为1表示2号信号阻塞,则2号信号被阻塞内核暂时不会发送2号信号给线程(直到阻塞信号集中 2号位置为0 内核才会发送这个信号)。 阻塞信号集默认全不阻塞(全0)
    信号集相关函数
    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,int signum);

    1. /*
    2. 一下信号集函数都是对自定义的信号集 进行操作!!!!!!!
    3. int sigemptyset(sigset_t *set);
    4. 清空信号集中的数据(64位数全置为0) sigset_t相当于一个64位数 传入后将这个六十四位数清0,当这个函数结束后再查看传入的set已经清0了
    5. 成功返回0 失败返回-1
    6. int sigfillset(sigset_t *set);
    7. 将信号集中所有的标志位置为1(64位数全置为1)
    8. 成功返回0 失败返回-1
    9. int sigaddset(sigset_t *set,int signum);
    10. 设置信号集set中的signum号信号对应的位置 设为1(用于阻塞某个信号)
    11. 设置过后的set会通过set参数传出
    12. 成功返回0 失败返回-1
    13. int sigdelset(sigset_t *set,int signum);
    14. 设置信号集set中的signum号信号对应的位置 设为0 (不阻塞某个信号)
    15. 设置过后的set会通过set参数传出
    16. 成功返回0 失败返回-1
    17. int sigismember(const sigset_t *set,int signum);
    18. 判断set中signum号信号是否为1(是否阻塞)
    19. signum号信号为1 返回1(被阻塞) 为0 返回0(不被阻塞) 失败返回-1
    20. */
    21. #include <unistd.h>
    22. #include <stdio.h>
    23. #include <stdlib.h>
    24. #include <sys/types.h>
    25. #include <signal.h> //信号集函数
    26. #include <sys/time.h>
    27. int main()
    28. {
    29. //创建一个信号集
    30. sigset_t set;
    31. //创建set 但没有初始化 其内容是随机的 所以需要先清空
    32. sigemptyset(&set);
    33. //判断SIGINT 是否被置为1了
    34. int ret = sigismember(&set, SIGINT);
    35. if (ret == 0)
    36. {
    37. printf("SIGINT不阻塞\n");
    38. }
    39. else if (ret == 1)
    40. {
    41. printf("SIGINT阻塞\n");
    42. }
    43. else
    44. {
    45. printf("出错\n");
    46. return -1;
    47. }
    48. //添加信号到信号集中
    49. sigaddset(&set, SIGINT);
    50. sigaddset(&set, SIGQUIT);
    51. //从信号集中删除信号
    52. sigdelset(&set, SIGQUIT);
    53. int ret = sigismember(&set, SIGQUIT);
    54. if (ret == 0)
    55. {
    56. printf("SIGQUIT不阻塞\n");
    57. }
    58. else if (ret == 1)
    59. {
    60. printf("SIGQUIT阻塞\n");
    61. }
    62. else
    63. {
    64. printf("出错\n");
    65. return -1;
    66. }
    67. return 0;
    68. }