• 信号发送与处理


    问题

     

    按下 CTRL + C之后,命令行中的前台进程会被终止。为什么???

    什么是信号?

    信号是一种“软件中断”,用来处理异步事件

    1. 内核发送信号到某个进程,通知进程事件的发生
    2. 事件可能来自硬件,可能来自用户输入,可能来自除零错误

    信号是一种类型的进程间通信方式(一个进程向另外一个进程发送信号)

    1. A进程发送事件T,向B进程发送信号,B进程执行动作响应事件
    2. 进程可以对接收的不同信号进行不同动作响应(信号 -> 处理)

    信号的分类

    硬件异常

            内核检测到硬件错误,发送相应信号给相关进程

    终端信号(用户交互信号,最典型:ctrl+c)

            在终端输入 “特殊字符” 等价于向前台进程组发送信号

    软件信号

            在软件层面(进程代码中)触发的信号(发送给自身或其他进程)

    硬件异常信号

    信号说明
    SIGBUS7总线错误,进程发生了内存访问错误
    SIGFPE8算术错误,FPE表示浮点异常
    SIGILL9指令错误,进程尝试执行非法指令
    SIGSEGV11段错误,进程访问了非法内存区域

    终端相关信号

    1. SIGINT(Ctrl + c) :程序终止信号,用于通知前台进程组终止进程
    2. SIGQUIT(Ctrl + \) :与SIGINT类似,进程收到该信号退出时可产生 coredump 文件
    3. SIGTSTP(Ctrl + Z) :停止进程的运行,进程收到该信号后可以选择处理或忽略。Ctrl + Z :进程收到该信号后停止运行(状态发生转换),后续可以恢复运行状态。

    软件相关信号

    1. 子进程退出:父进程收到 SIGCHLD 信号
    2. 父进程退出:子进程可能收到信号(什么信号?)
    3. 定时器到期:alarm(),ualarm(),timer_create(),...
    4. 主动发信号:kill(),raise(),...

    内核与信号

    1.内核检测到相关的错误,将信号发送给对应的进程。

    2.进程可以通过系统调用,告诉内核,自己需要信号。比如:时钟信号。比如进程A告诉内核,我需要时钟信号,那么内核就定期给进程A发送时钟信号。

    3.进程间通信:进程A通过内核 将信号 发送给进程B

    信号的发送与处理,无非就是以上三种情况。

    信号的默认处理

    默认处理方式说明示例
    ignore进程丢弃信号不会产生任何影响SIGCHLD SIGURG
    terminate终止进程SIGKILL SIGHUP
    coredump终止进程并产生转储文件SIGQUIT SIGILL
    stop/continue停止进程执行/恢复进程执行SIGSTOP,SIGCONT

    知识加油站:System V vs BSD

    System V :也被称为 AT&T SystemV,是Unix操作系统众多版本中的一支

    BSD :加州大学巴克利分校开创,Unix衍生系统,代表由此派生出的各种套件集合

    Linux之所以被称为类Unix操作系统(Unix Like),部分原因就是Linux的操作风格是介于上述二者之间,且不同的厂商为了照顾不同的用户,其发行版本的操作风格有存在差异。

    自定义信号发送

    #include

    #include

    int kill(pid_t pid,int sig);

    //pid > 0 ==> 发送信号给进程 ,pid == 0 ==> 发送信号给进程组 ,pid < -1  比如-1000,将信号发送给PGID为-1000的进程组

    int raise(int sig); //信号处理完毕才返回

    notes:

    标准信号是unix系统中的信号,编号范围从1到31。

    实时信号是Linux独有的信号,编号范围从32到64。

    下面来看几个demo示例。

    signal

     可以看到在终端按下 CTRL + C 之后,信号处理函数 signal_handler就不断地调用。然后按下 CTRL + \ 后退出。

    sysv_signal

    1. #define _GNU_SOURCE
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. void signal_handler(int sig)
    8. {
    9. printf("handler : sig = %d\n",sig);
    10. }
    11. int main(void)
    12. {
    13. //signal(SIGINT,signal_handler);
    14. sysv_signal(SIGINT,signal_handler);
    15. while(1)
    16. {
    17. sleep(1);
    18. }
    19. return 0;
    20. }
    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. ^Chandler : sig = 2
    4. ^C
    5. zhaixue@ubuntu:~/DTLinux/0-11$

    sysv_signal 按下一次 CTRL + C 就已经退出了。

    bsd_signal

    1. //#define _GNU_SOURCE
    2. #define _XOPEN_SOURCE 600
    3. #define _POSIX_C_SOURCE 200800L
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. void signal_handler(int sig)
    10. {
    11. printf("handler : sig = %d\n",sig);
    12. }
    13. int main(void)
    14. {
    15. //signal(SIGINT,signal_handler);
    16. //sysv_signal(SIGINT,signal_handler);
    17. bsd_signal(SIGINT,signal_handler);
    18. while(1)
    19. {
    20. sleep(1);
    21. }
    22. return 0;
    23. }
    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. ^Chandler : sig = 2
    4. ^Chandler : sig = 2
    5. ^Chandler : sig = 2
    6. ^Chandler : sig = 2
    7. ^Chandler : sig = 2
    8. ^Chandler : sig = 2
    9. ^Chandler : sig = 2
    10. ^\Quit (core dumped)
    11. zhaixue@ubuntu:~/DTLinux/0-11$

    发送自定义信号

    由于信号在表现形式上是一个整数值,所以将值为40的信号映射到 信号处理函数 signal_handler上来。

    main.c

    1. //#define _GNU_SOURCE
    2. #define _XOPEN_SOURCE 600
    3. #define _POSIX_C_SOURCE 200800L
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. void signal_handler(int sig)
    10. {
    11. printf("handler : sig = %d\n",sig);
    12. }
    13. int main(void)
    14. {
    15. signal(SIGINT,signal_handler);
    16. signal(40,signal_handler);
    17. //sysv_signal(SIGINT,signal_handler);
    18. //bsd_signal(SIGINT,signal_handler);
    19. while(1)
    20. {
    21. sleep(1);
    22. }
    23. return 0;
    24. }

    想发送值为40的信号给信号处理函数。signal(40,signal_handler);

    test.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main(int argc,char* argv[])
    7. {
    8. int pid = atoi(argv[1]);
    9. int sig = atoi(argv[2]);
    10. printf("send sig(%d) to process(%d)...\n",sig,pid);
    11. kill(pid,sig); //send signal
    12. raise(SIGINT); //end execute
    13. while(1)
    14. {
    15. printf("while...\n");
    16. sleep(1);
    17. }
    18. return 0;
    19. }

    编译输出:

    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out &
    3. [1] 9437
    4. zhaixue@ubuntu:~/DTLinux/0-11$ gcc test.c -o test.out
    5. zhaixue@ubuntu:~/DTLinux/0-11$ ./test.out 9437 40
    6. send sig(40) to process(9437)...
    7. handler : sig = 40
    8. zhaixue@ubuntu:~/DTLinux/0-11$

    信号从 test.out 进程发送到 main.out 进程了。这也是一种进程间通信的方式了。

    思考

    以上signal、sysv_signal、bsd_signal 三种自定义信号处理函数有什么区别?

    信号的OneShot特性

    System V风格的signal函数,注册的信号处理是一次性的。

    1. 进程收到信号后,调用由signal注册的处理函数。
    2. 处理函数一旦执行,进程通过默认的方式处理后续相同信号。
    3. 如果想要重复触发,那么必须再次调用signal注册函数。
    4. BSD 风格的 signal 函数不存在OneShot,能够自动反复触发处理函数的调用。

    信号的自身屏蔽特性

    在信号处理函数执行期间,很有可能再次收到当前信号

            即:处理A信号的时候,再次收到A信号

    对于 System V风格的 signal 函数,会引起信号处理函数的重入

            即:调用处理函数的过程中,再次触发处理函数的调用

    在注册信号处理函数时:

            System V风格的 signal 不屏蔽任何信号

            BSD风格的 signal 会屏蔽当前注册的信号

            问题:BSD风格的 signal 函数,处理A信号期间,如果收到B信号会发生什么?

    系统调用重启特性

            系统调用期间,可能受到信号,此时进程必须从系统调用中返回

            对于执行时间较长的系统调用(write / read),被信号中断的可能性很大

                    如果希望信号处理之后,被中断的系统调用能够重启,则:

                    可以通过条件 errno == EINTR 判断重启系统调用

    -System V风格的 signal 函数(只能手动重启)

            系统调用被信号中断后,直接返回 -1,并且 errno == EINTR

    -BSD风格的 signal 函数(自动重启)

            系统调用被中断,内核在信号处理函数结束后,自动重启系统调用

    实验OneShot特性

    OneShot特性,只有System V风格的 signal 函数才有。

    main.c

    1. #define _GNU_SOURCE
    2. //#define _XOPEN_SOURCE 600
    3. //#define _POSIX_C_SOURCE 200800L
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. void signal_handler(int sig)
    10. {
    11. printf("handler : sig = %d\n",sig);
    12. }
    13. int main(void)
    14. {
    15. //signal(SIGINT,signal_handler);
    16. //signal(40,signal_handler);
    17. sysv_signal(SIGINT,signal_handler);
    18. //bsd_signal(SIGINT,signal_handler);
    19. while(1)
    20. {
    21. sleep(1);
    22. }
    23. return 0;
    24. }

    编译运行:

    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. ^Chandler : sig = 2
    4. ^C
    5. zhaixue@ubuntu:~/DTLinux/0-11$

    可以看到,只按下了一次 ctrl + c ,进程就退出了。因为 sysv_signal(SIGINT,signal_handler); 是一次性的,一旦 signal_handler 信号处理函数被调用了,那么这个信号处理函数:signal_handler 就已失效了。

    接下来,我们接着实验 BSD风格的信号处理函数。

    1. //#define _GNU_SOURCE
    2. #define _XOPEN_SOURCE 600
    3. #define _POSIX_C_SOURCE 200800L
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. void signal_handler(int sig)
    10. {
    11. printf("handler : sig = %d\n",sig);
    12. }
    13. int main(void)
    14. {
    15. //signal(SIGINT,signal_handler);
    16. //signal(40,signal_handler);
    17. //sysv_signal(SIGINT,signal_handler);
    18. bsd_signal(SIGINT,signal_handler);
    19. while(1)
    20. {
    21. sleep(1);
    22. }
    23. return 0;
    24. }
    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. ^Chandler : sig = 2
    4. ^Chandler : sig = 2
    5. ^Chandler : sig = 2
    6. ^Chandler : sig = 2
    7. ^Chandler : sig = 2
    8. ^Chandler : sig = 2
    9. ^Chandler : sig = 2
    10. ^Chandler : sig = 2
    11. ^Chandler : sig = 2
    12. ^\Quit (core dumped)
    13. zhaixue@ubuntu:~/DTLinux/0-11$

    可以看到每次按下 ctrl + c,就会调用一次 信号处理函数。Linux中默认的信号处理函数 signal 和 BSD风格的信号处理函数行为是一致的。

    信号重入实验

    main.c

    1. #define _GNU_SOURCE
    2. //#define _XOPEN_SOURCE 600
    3. //#define _POSIX_C_SOURCE 200800L
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. void delay_handler(int sig)
    10. {
    11. int i = 0;
    12. printf("begin delay handler...\n");
    13. for(i=0;i<5;i++)
    14. {
    15. printf("sleep %d ...\n",i);
    16. sleep(1);
    17. }
    18. printf("end delay handler...\n");
    19. }
    20. void signal_handler(int sig)
    21. {
    22. printf("handler : sig = %d\n",sig);
    23. }
    24. int main(void)
    25. {
    26. //signal(SIGINT,signal_handler);
    27. //signal(40,signal_handler);
    28. sysv_signal(SIGINT,delay_handler);
    29. //bsd_signal(SIGINT,signal_handler);
    30. while(1)
    31. {
    32. sleep(1);
    33. }
    34. return 0;
    35. }
    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. ^Cbegin delay handler...
    4. sleep 0 ...
    5. sleep 1 ...
    6. sleep 2 ...
    7. sleep 3 ...
    8. sleep 4 ...
    9. end delay handler...
    10. ^C
    11. zhaixue@ubuntu:~/DTLinux/0-11$

    可以看到这一次 oneShot特性。

    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. ^Cbegin delay handler...
    4. sleep 0 ...
    5. sleep 1 ...
    6. ^C
    7. zhaixue@ubuntu:~/DTLinux/0-11$

    来修改一下 void delay_handler(int sig) 函数,在void delay_handler(int sig)函数中再一次调用 sysv_signal(SIGINT,delay_handler); 那么就相当于去掉OneShot特性。

    1. #define _GNU_SOURCE
    2. //#define _XOPEN_SOURCE 600
    3. //#define _POSIX_C_SOURCE 200800L
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. void delay_handler(int sig)
    10. {
    11. int i = 0;
    12. sysv_signal(SIGINT,delay_handler);
    13. printf("begin delay handler...\n");
    14. for(i=0;i<5;i++)
    15. {
    16. printf("sleep %d ...\n",i);
    17. sleep(1);
    18. }
    19. printf("end delay handler...\n");
    20. }
    21. void signal_handler(int sig)
    22. {
    23. printf("handler : sig = %d\n",sig);
    24. }
    25. int main(void)
    26. {
    27. //signal(SIGINT,signal_handler);
    28. //signal(40,signal_handler);
    29. sysv_signal(SIGINT,delay_handler);
    30. //bsd_signal(SIGINT,signal_handler);
    31. while(1)
    32. {
    33. sleep(1);
    34. }
    35. return 0;
    36. }

    编译运行:

    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. ^Cbegin delay handler...
    4. sleep 0 ...
    5. sleep 1 ...
    6. sleep 2 ...
    7. ^Cbegin delay handler...
    8. sleep 0 ...
    9. sleep 1 ...
    10. sleep 2 ...
    11. ^Cbegin delay handler...
    12. sleep 0 ...
    13. sleep 1 ...
    14. sleep 2 ...
    15. sleep 3 ...
    16. sleep 4 ...
    17. end delay handler...
    18. sleep 3 ...
    19. sleep 4 ...
    20. end delay handler...
    21. sleep 3 ...
    22. sleep 4 ...
    23. end delay handler...
    24. ^Cbegin delay handler...
    25. sleep 0 ...
    26. sleep 1 ...
    27. sleep 2 ...
    28. sleep 3 ...
    29. sleep 4 ...
    30. end delay handler...
    31. ^\Quit (core dumped)
    32. zhaixue@ubuntu:~/DTLinux/0-11$

    可以看到OneShot特性相当于已经没有了。

    接下来看 BSD风格的信号处理函数,会有什么样的结果。

    main.c

    1. //#define _GNU_SOURCE
    2. #define _XOPEN_SOURCE 600
    3. #define _POSIX_C_SOURCE 200800L
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. void delay_handler(int sig)
    10. {
    11. int i = 0;
    12. //sysv_signal(SIGINT,delay_handler);
    13. printf("begin delay handler...\n");
    14. for(i=0;i<5;i++)
    15. {
    16. printf("sleep %d ...\n",i);
    17. sleep(1);
    18. }
    19. printf("end delay handler...\n");
    20. }
    21. void signal_handler(int sig)
    22. {
    23. printf("handler : sig = %d\n",sig);
    24. }
    25. int main(void)
    26. {
    27. //signal(SIGINT,signal_handler);
    28. //signal(40,signal_handler);
    29. //sysv_signal(SIGINT,delay_handler);
    30. bsd_signal(SIGINT,delay_handler);
    31. while(1)
    32. {
    33. sleep(1);
    34. }
    35. return 0;
    36. }
    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. ^Cbegin delay handler...
    4. sleep 0 ...
    5. sleep 1 ...
    6. sleep 2 ...
    7. ^Csleep 3 ...
    8. sleep 4 ...
    9. end delay handler...
    10. begin delay handler...
    11. sleep 0 ...
    12. sleep 1 ...
    13. sleep 2 ...
    14. sleep 3 ...
    15. sleep 4 ...
    16. end delay handler...
    17. begin delay handler...
    18. sleep 0 ...
    19. ^Csleep 1 ...
    20. sleep 2 ...
    21. ^Csleep 3 ...
    22. sleep 4 ...
    23. end delay handler...
    24. begin delay handler...
    25. sleep 0 ...
    26. sleep 1 ...
    27. sleep 2 ...
    28. sleep 3 ...
    29. sleep 4 ...
    30. end delay handler...
    31. ^\Quit (core dumped)
    32. zhaixue@ubuntu:~/DTLinux/0-11$

    可以看到不会发生重入,必须等待当前的信号处理函数处理完,才能够处理下一次的信号。当下一次信号来的时候,必须排队等着。

    Linux 默认的 signal 信号处理函数 和 BSD风格的 bsd_signal(SIGINT,delay_handler) 信号处理函数是一致的。

    系统调用重启实验

    main.c

    1. #define _GNU_SOURCE
    2. //#define _XOPEN_SOURCE 600
    3. //#define _POSIX_C_SOURCE 200800L
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. void delay_handler(int sig)
    11. {
    12. int i = 0;
    13. //sysv_signal(SIGINT,delay_handler);
    14. printf("begin delay handler...\n");
    15. for(i=0;i<5;i++)
    16. {
    17. printf("sleep %d ...\n",i);
    18. sleep(1);
    19. }
    20. printf("end delay handler...\n");
    21. }
    22. void signal_handler(int sig)
    23. {
    24. printf("handler : sig = %d\n",sig);
    25. }
    26. int r_read(char* data,int len)
    27. {
    28. int ret = -1;
    29. while(data && ((ret = read(STDIN_FILENO,data,len-1)) == -1) && (errno == EINTR))
    30. {
    31. printf("restart syscall mannually...\n");
    32. }
    33. if(ret != -1)
    34. {
    35. data[ret] = 0;
    36. }
    37. return ret;
    38. }
    39. int main(void)
    40. {
    41. char buf[32]={0};
    42. //signal(SIGINT,delay_handler);
    43. //signal(40,signal_handler);
    44. sysv_signal(SIGINT,signal_handler);
    45. //bsd_signal(SIGINT,delay_handler);
    46. r_read(buf,32);
    47. printf("input: %s\n",buf);
    48. return 0;
    49. }
    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. 1234^Chandler : sig = 2
    4. restart syscall mannually...
    5. test
    6. input: test
    7. zhaixue@ubuntu:~/DTLinux/0-11$

    可以看到 read 系统调用,被信号给中断了。

    实验 BSD 风格的 signal 函数。

    1. //#define _GNU_SOURCE
    2. #define _XOPEN_SOURCE 600
    3. #define _POSIX_C_SOURCE 200800L
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. void delay_handler(int sig)
    11. {
    12. int i = 0;
    13. //sysv_signal(SIGINT,delay_handler);
    14. printf("begin delay handler...\n");
    15. for(i=0;i<5;i++)
    16. {
    17. printf("sleep %d ...\n",i);
    18. sleep(1);
    19. }
    20. printf("end delay handler...\n");
    21. }
    22. void signal_handler(int sig)
    23. {
    24. printf("handler : sig = %d\n",sig);
    25. }
    26. int r_read(char* data,int len)
    27. {
    28. int ret = -1;
    29. while(data && ((ret = read(STDIN_FILENO,data,len-1)) == -1) && (errno == EINTR))
    30. {
    31. printf("restart syscall mannually...\n");
    32. }
    33. if(ret != -1)
    34. {
    35. data[ret] = 0;
    36. }
    37. return ret;
    38. }
    39. int main(void)
    40. {
    41. char buf[32]={0};
    42. //signal(SIGINT,signal_handler);
    43. //signal(40,signal_handler);
    44. //sysv_signal(SIGINT,signal_handler);
    45. bsd_signal(SIGINT,signal_handler);
    46. r_read(buf,32);
    47. printf("input: %s\n",buf);
    48. return 0;
    49. }
    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. 1234^Chandler : sig = 2
    4. ^Chandler : sig = 2
    5. ^Chandler : sig = 2
    6. ^\Quit (core dumped)
    7. zhaixue@ubuntu:~/DTLinux/0-11$

    可以看到,BSD风格的 signal 函数会自动重启系统调用。和 sysv 风格的信号函数不一样,没有打印 restart syscall mannually... 出来。

    默认风格的 signal 函数 和 BSD风格的 signal 函数一样,会自动重启系统调用。

    问题:BSD风格的 signal 函数,处理A信号期间,如果收到B信号会发生什么?会不会重入?

    main.c

    1. //#define _GNU_SOURCE
    2. #define _XOPEN_SOURCE 600
    3. #define _POSIX_C_SOURCE 200800L
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. void delay_handler(int sig)
    11. {
    12. int i = 0;
    13. //sysv_signal(SIGINT,delay_handler);
    14. printf("begin delay handler...\n");
    15. for(i=0;i<5;i++)
    16. {
    17. printf("sleep %d ...\n",i);
    18. sleep(1);
    19. }
    20. printf("end delay handler...\n");
    21. }
    22. void signal_handler(int sig)
    23. {
    24. printf("handler : sig = %d\n",sig);
    25. }
    26. int r_read(char* data,int len)
    27. {
    28. int ret = -1;
    29. while(data && ((ret = read(STDIN_FILENO,data,len-1)) == -1) && (errno == EINTR))
    30. {
    31. printf("restart syscall mannually...\n");
    32. }
    33. if(ret != -1)
    34. {
    35. data[ret] = 0;
    36. }
    37. return ret;
    38. }
    39. int main(void)
    40. {
    41. char buf[32]={0};
    42. //signal(SIGINT,signal_handler);
    43. bsd_signal(40,signal_handler);
    44. //sysv_signal(SIGINT,signal_handler);
    45. bsd_signal(SIGINT,delay_handler);
    46. r_read(buf,32);
    47. printf("input: %s\n",buf);
    48. return 0;
    49. }

    test.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main(int argc,char* argv[])
    7. {
    8. int pid = atoi(argv[1]);
    9. int sig = atoi(argv[2]);
    10. printf("send sig(%d) to process(%d)...\n",sig,pid);
    11. kill(pid,sig); //send signal
    12. raise(SIGINT); //end execute
    13. while(1)
    14. {
    15. printf("while...\n");
    16. sleep(1);
    17. }
    18. return 0;
    19. }

    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
    3. ^Cbegin delay handler...
    4. sleep 0 ...
    5. sleep 1 ...
    6. sleep 2 ...
    7. handler : sig = 40
    8. sleep 3 ...
    9. sleep 4 ...
    10. end delay handler...
    11. ^Cbegin delay handler...
    12. sleep 0 ...
    13. sleep 1 ...
    14. sleep 2 ...
    15. sleep 3 ...
    16. sleep 4 ...
    17. end delay handler...
    18. ^\Quit (core dumped)
    19. zhaixue@ubuntu:~/DTLinux/0-11$

    1. zhaixue@ubuntu:~/DTLinux/0-11$ gcc test.c -o test.out
    2. zhaixue@ubuntu:~/DTLinux/0-11$ ps -ajx | grep main.out
    3. 8966 11517 11517 8966 pts/0 11517 S+ 1000 0:00 ./main.out
    4. 11392 11524 11523 11392 pts/1 11523 S+ 1000 0:00 grep --color=auto main.out
    5. zhaixue@ubuntu:~/DTLinux/0-11$ ./test.out 11517 40
    6. send sig(40) to process(11517)...
    7. zhaixue@ubuntu:~/DTLinux/0-11$

    问题:BSD风格的 signal 函数,处理A信号期间,如果收到B信号会发生什么?会不会重入?

    会立即马上的处理B信号,处理完B信号之后,马上返回处理A信号。

    注意事项

    -并非所有的系统调用对信号中断都表现同样的行为

            一些系统调用支持信号中断后自动重启

            read()、write()、wait()、waitpid()、ioctl() ...

            一些系统调用完全不支持中断后自动重启

            poll()、select()、usleep()、...

    结论

    函数OneShot屏蔽自身重启系统调用
    signal(...)falsetruetrue
    sysv_signal(...)yesfalsefalse
    bsd_signal(...)falsetruetrue

    在信号处理上,Linux系统更接近 BSD风格的操作;默认的signal函数在不同的 Linux发行版上语义可能不同,从代码移植行角度,避免直接使用 signal(...)函数。

    初探现代信号处理

    现代信号处理的注册函数

    1. /* Get and/or set the action for signal SIG. */
    2. extern int sigaction (
    3.                 int __sig,
    4.                 const struct sigaction *__restrict __act,
    5.                 struct sigaction *__restrict __oact) __THROW;

    int __sig :自定义处理那个信号

    const struct sigaction *__restrict __act :信号的处理方式

    struct sigaction *__restrict __oact   :原有的信号处理方式,通常情况下为 NULL

    1. /* Structure describing the action to be taken when a signal arrives. */
    2. struct sigaction
    3. {
    4. /* Signal handler. */
    5. #if defined __USE_POSIX199309 || defined __USE_XOPEN_EXTENDED
    6. union
    7. {
    8. /* Used if SA_SIGINFO is not set. */
    9. __sighandler_t sa_handler;
    10. /* Used if SA_SIGINFO is set. */
    11. void (*sa_sigaction) (int, siginfo_t *, void *);
    12. }
    13. __sigaction_handler;
    14. # define sa_handler __sigaction_handler.sa_handler
    15. # define sa_sigaction __sigaction_handler.sa_sigaction
    16. #else
    17. __sighandler_t sa_handler;
    18. #endif
    19. /* Additional set of signals to be blocked. */
    20. __sigset_t sa_mask;
    21. /* Special flags. */
    22. int sa_flags;
    23. /* Restore handler. */
    24. void (*sa_restorer) (void);
    25. };

    img_v2_c79bfb34-dd70-4d20-8301-27eeac6d87bl 

    1.  为什么有2个信号处理函数呢?第二个信号处理函数所带的信号处理信息多一些。

    信号屏蔽与标记

    sigset_t sa_mask; 

            信号屏蔽:sa_mask = SIGHUP | SIGINT | SIGUSR1;

            注意: 并不是所有的信号都 可以被屏蔽,如:SIGKILL、SIGSTOP

    信号可靠性剖析

    先来看一个问题:基于信号发送的进程间通信方式可靠吗???

    信号查看(kill -l)

     可以看到 Linux一共支持 64种类型的信号,但是有个奇怪的现象就是:

    1. 信号从31直接跳到了34。没有32 和 33,这个是为什么呢?
    2. 并且31之前的信号都是有一个名字的,但是31之后的信号是没有名字的。

    信号分类

    不可靠信号(传统信号)

    1. 信号值在[1,31]之间的所有信号

    可靠信号(实时(RT)信号)

    1.  信号值在[SIGRTMIN,SIGRTMAX],即:[34,64]
    2. SIGRTMIN -> 34
    3. SIGTMAX  -> 64

    信号小知识

    1. 信号32 与 信号33 (SIGCANCEL & SIGSETXID) 被NPTL线程库征用
    2. NPTL -> Native Posix Threading Library  -> 即:POSIX线程库,Linux可以使用这个库进行多线程编程
    3. 对于Linux内核,信号32是最小的可靠信号
    4. SIGRTMIN 在signal.h中定义,不同平台的Linux可能不同 (arm Linux)

    不可靠信号 VS 可靠信号

    不可靠信号

    1. 内核不保证信号可以递送到目标进程(内核对信号状态进行标记)
    2. 如果信号处于未决状态,并且相同信号被发送,内核丢弃后续相同的信号

    可靠信号

    1. 内核维护信号队列,未决信号位于队列中,因此信号不会被丢弃
    2. 严格意义上,信号队列有上限,因此不能无限制保存可靠信号

    一些注意事项

    1. 不可靠信号的默认处理行为可能不同(忽略,结束)
    2. 可靠信号的默认处理行为都是结束进程
    3. 信号的可靠性由信号数值决定,与发送方式无关
    4. 信号队列的上限可以通过命令设置

                  4.1 查询信号队列上限 :ulimit -i

                  4.2 设置信号队列上限 :ulimit -i 10000

    信号可靠性实验设计

    目标:验证信号可靠性(不可靠信号 or 可靠信号)

    方案:对目标进程“疯狂”发送N次信号,验证信号处理函数调用次数

    预备函数:

            int sigaddset(sigset_t* set,int signum);

            int sigdelset(sigset_t* set,int signum);

            int sigfillset(sigset_t* set);

            int sigemptyset(sigset_t* set);

            int sigprocmask(int how,const sigset_t* set,sigset_t* oldset);

    信号优先级与安全性

    信号优先级的概念

    1. 信号的本质是一种软中断(中断有优先级,信号也有优先级)
    2. 对于同一个未决实时信号,按照发送先后顺序递送给进程
    3. 对于不同的未决实时信号,信号值越小优先级越高
    4. 不可靠信号 与 可靠信号 同时未决

                    4.1 严格意义上,没有明确规定优先级

                    4.2 实际上,Linux优先递送不可靠信号

          5. 多个 不可靠信号 同时未决,优先递送谁?

                    优先递送硬件相关信号

                    SIGSEGV、SIGBUS、SIGILL、SIGTRAP、SIGFPE、SIGSYS

           6. 优先递送信号值小的不可靠信号

           7. 不可靠信号 优先于 可靠信号 递送

            

    信号安全性

    什么是信号安全性

            程序能正确且无意外的按照预期方式执行。

    信号处理的不确定性

            什么时候信号递达是不确定的  -->  主程序被中断的位置是不确定的

    当信号递达,转而执行信号处理函数时,不可重入的函数不能调用

            不可重入函数:函数不能由超过一个任务共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)

    信号处理设计模式

  • 相关阅读:
    一个开源的汽修rbac后台管理系统项目,基于若依框架,实现了activiti工作流,附源码
    C++ 坑人小程序(全集)
    吃瓜神奇!企查查、天眼查、天眼销,到底哪家强?
    408 | 【2015年】计算机统考真题 自用回顾知识点整理
    速卖通年底布局,自养号测评补单助力销量提升
    Python + Django4 搭建个人博客(七): Admin后台管理系统
    绿盟安全事件响应观察及远程代码执行漏洞
    MySQL数据库进阶篇
    prometheus描点原理
    Java集合
  • 原文地址:https://blog.csdn.net/weixin_42136255/article/details/130913047