11) SIGSEGV

  产生该信号的原因是对地址的非法访问,意思就是访问了你不该访问的地方,也是在实际工作中遇到的段错误最可能的原因,比如:访问不知道从哪里弄过来的指针、访问空指针,数组越界等等,测试代码如下:

 1 /**
 2  * filename: signal_11.c
 3  * author: Suzkfly
 4  * date: 2021-02-16
 5  * platform: Ubuntu
 6  *     操作步骤:
 7  *     分别修改MODE的值为1,2,3,运行可执行程序,观察打印信息。
 8  */
 9 #include <stdio.h>
10 #include <signal.h>
11 #include <unistd.h>
12 #include <sys/types.h>
13 #include <stdlib.h>
14 
15 #define MODE 3
16 
17 /* 自定义信号处理函数 */
18 void func(int sig)
19 {
20     switch (sig) {
21         case SIGSEGV :
22             printf("SIGSEGV signal captured. value = %d\n", sig);
23             exit(0);    /* 必须退出,否则会一直触发 */
24         break;
25     }
26 }
27 
28 int main(int argc,char *argv[])
29 {
30     void(*p_func)(int) = NULL;
31     int *p_test = NULL;
32     char array[2] = { 0 };
33     char cc = 0;
34     
35     p_func = signal(SIGSEGV, func);
36     if (p_func == SIG_ERR) {
37         printf("signal failed\n");
38     }
39     printf("MODE = %d\n", MODE);
40     
41 #if (MODE == 1)
42     p_test = (int *)0x12345678;     /* 访问非法地址 */
43     *p_test = 0;
44 #elif (MODE == 2)
45     p_test = NULL;                  /* 访问空指针 */
46     *p_test = 0;
47 #elif (MODE == 3)
48     array[2] = 'a';
49     printf("array[2] = %c\n", array[2]);
50     printf("cc = %c\n", cc);
51     array[100000] = 'a';            /* 数组越界 */
52     printf("array[100000] = %c\n", array[100000]);
53 #endif
54     return 0;
55 }

执行结果:

  

 结果分析:

  该程序通过改变MODE的值,实现3种不同的操作,第1种是访问一个随便写的地址,第2种是访问空指针,第3种是数组越界。从结果能看出来,这3种操都引发了SIGSEGV信号,但实际上,访问随便写的地址(随机地址)和数组越界不一定会引发SIGSEGV,它主要看你有没有权限去访问它,如果这个地址恰好是有权限访问的,那就不会引发SIGSEGV信号。

  没引发SIGSEGV信号并不是说就没有问题,比如看MODE为3时的代码,定义了char型数组array为2个元素,之后定义了char型变量cc,并赋给cc的初值为0,之后改变了array[2],实际上这个时候已经越界了,但系统继续运行了,并且变量cc躺枪了,它的值被改变了,这也是C语言被人诟病的一个原因。在测试过程中,我访问了array[100],array[1000]程序都能正常执行,但是这个操作改变了什么东西,我也不知道。

  按照这个思路,如果在MODE为1的时候给p_test指针赋的值恰好是用户可以访问的,那它也不会引发SIGSEGV,但访问空指针肯定会引发SIGSEGV信号,这就是为什么代码规范要求指针必须要初始化,因为如果指针没有初始化,它就是一个随机值,万一在后面的程序中直接拿来用了,程序不一定会崩,但是它不崩带来的后果比崩掉更加严重,因为你不知道它改变了什么东西。所以程序员一定要养成一个良好的习惯,在定义一个指针变量的时候给它赋值为NULL,这样即使在后面直接使用它了,也能确保立刻就能发现问题。

 12) SIGUSR2

  通SIGUSR1,参见:https://www.cnblogs.com/Suzkfly/p/14407953.html 10) SIGUSR1

13) SIGPIPE

  该信号由管道破裂时发出,当管道所有可读的文件描述符都关闭了,但有程序仍然往管道写数据时就会造成管道破裂。目前我测试出3种情况能发出该信号:

13.1 无名管道破裂

 1 /**
 2  * filename: signal_13_pipe.c
 3  * author: Suzkfly
 4  * date: 2021-02-17
 5  * platform: Ubuntu
 6  *     操作步骤:
 7  *     运行可执行程序,观察打印信息。该程序使无名管道破裂,引发SIGPIPE信号。
 8  */
 9 #include <stdio.h>
10 #include <signal.h>
11 #include <unistd.h>
12 #include <sys/types.h>
13 #include <string.h>
14 
15 /* 自定义信号处理函数 */
16 void func(int sig)
17 {
18     switch (sig) {
19         case SIGPIPE :
20             printf("SIGPIPE signal captured. value = %d\n", sig);
21         break;
22     }
23 }
24 
25 int main(int argc,char *argv[])
26 {
27     int ret = 0;
28     void(*p)(int) = NULL;
29     int pipefd[2] = { 0 };
30     
31     /* 注册信号 */
32     p = signal(SIGPIPE, func);
33     if (p == SIG_ERR) {
34         printf("signal failed\n");
35     }
36 
37     /* 创建管道 */
38     ret = pipe(pipefd);
39     if (ret < 0) {
40         printf("pipe failed\n");
41     }
42     
43     close(pipefd[0]);                                 /* 将读端关闭 */
44     ret = write(pipefd[1], "Hello", strlen("Hello")); /* 往管道写数据 */
45     if (ret > 0) {
46         printf("write len = %d\n", ret);
47     }
48     close(pipefd[1]);
49 
50     return 0;
51 }

测试结果:

  略

13.2 有名管道破裂

 1 /**
 2  * filename: signal_13_namedpipe.c
 3  * author: Suzkfly
 4  * date: 2021-02-17
 5  * platform: Ubuntu
 6  *     操作步骤:
 7  *     运行可执行程序,观察打印信息。该程序使无名管道破裂,引发SIGPIPE信号。
 8  */
 9 #include <stdio.h>
10 #include <signal.h>
11 #include <unistd.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <fcntl.h>
15 #include <string.h>
16 
17 /* 自定义信号处理函数 */
18 void func(int sig)
19 {
20     switch (sig) {
21         case SIGPIPE :
22             printf("SIGPIPE signal captured. value = %d\n", sig);
23         break;
24     }
25 }
26 
27 int main(int argc,char *argv[])
28 {
29     int ret = 0;
30     void(*p)(int) = NULL;
31     int pid = 0;
32     int fd = 0;
33     
34     /* 注册信号 */
35     p = signal(SIGPIPE, func);
36     if (p == SIG_ERR) {
37         printf("signal failed\n");
38     }
39 
40 #if 1   /* 首次运行需创建管道文件 */
41     ret = mkfifo("myfifo", 0666);
42     if (ret == -1) {
43         printf("mkfifo failed\n");
44         return 0;
45     }
46 #endif
47 
48     pid = fork();
49     if (pid > 0) {          /* 父进程 */
50         fd = open("myfifo", O_WRONLY);  /* 以只写方式打开fifo文件 */
51         if (fd < 0) {
52             printf("parent open failed\n");
53             return 0;
54         }
55         sleep(1);   /* 等待子进程打开并且关闭fifo文件 */
56         ret = write(fd, "Hello", strlen("Hello"));  /* 写数据将造成管道破裂 */
57         if (ret > 0) {
58             printf("write len = %d\n", ret);
59         }
60         
61     } else if (pid == 0) {  /* 子进程 */
62         /* 子进程要以带读的方式打开并且关闭fifo文件,否则父进程写数据将阻塞,
63            不会造成管道破裂 */
64         fd = open("myfifo", O_RDONLY);  /* 以只读方式打开fifo文件 */
65         if (fd < 0) {
66             printf("child open failed\n");
67             return 0;
68         }
69         close(fd);
70     }
71     
72     return 0;
73 }

测试结果:

  略

13.3 TCP读端关闭

  该程序分为2部分,一部分建立TCP服务器,用来发送数据,同时用来注册信号,另一部分为TCP客户端,建立连接后将客户端关闭,此时在服务器端写数据将发出SIGPIPE信号,代码如下:

signal_13_tcp_send.c

  1 /**
  2  * filename: signal_13_tcp_send.c
  3  * author: Suzkfly
  4  * date: 2021-02-17
  5  * platform: Ubuntu
  6  *     操作步骤:
  7  *     在一个终端先运行tcp_send,另开一个终端运行tcp_recv,建立连接之后在tcp_send
  8  *     端输入字符,按回车后tcp_recv能够成功接收并打印,但如果关掉tcp_recv(可以用
  9  *     Ctrl+C关闭)之后,在tcp_send端再发送字符,则会引发SIGPIPE。
 10  */
 11 #include <stdio.h>
 12 #include <signal.h>
 13 #include <unistd.h>
 14 #include <string.h>
 15 #include <sys/socket.h>
 16 #include <netinet/in.h>
 17 #include <arpa/inet.h>
 18 
 19 #define PORT 10000  /* 定义端口号 */
 20 
 21 /* 自定义信号处理函数 */
 22 void func(int sig)
 23 {
 24     switch (sig) {
 25         case SIGPIPE :
 26             printf("SIGPIPE signal captured. value = %d\n", sig);
 27         break;
 28     }
 29 }
 30 
 31 int main(int argc,char *argv[])
 32 {
 33     void(*p)(int) = NULL;
 34     int sock_fd = 0, confd = 0;
 35     struct sockaddr_in serv_addr;       /* 服务器IP(本机IP) */
 36     struct sockaddr_in client_addr;     /* 客户端IP(连接者IP) */
 37     socklen_t addr_len = sizeof(struct sockaddr_in);
 38     int ret = 0;                        /* 用于接收函数返回值 */
 39     char buf[128] = { 0 };              /* 用于存放数据的缓冲区 */
 40     int len = 0;                        /* 发送和接收数据的长度 */
 41     
 42     /* 注册信号 */
 43     p = signal(SIGPIPE, func);
 44     if (p == SIG_ERR) {
 45         printf("signal failed\n");
 46     }
 47     
 48     /* 创建TCP套接字 */
 49     sock_fd = socket(AF_INET, SOCK_STREAM, 0);
 50     
 51     /* 将套接字与IP和端口绑定 */
 52     memset(&serv_addr, 0, sizeof(struct sockaddr_in));
 53     serv_addr.sin_family = AF_INET;
 54     //serv_addr.sin_addr.s_addr = inet_addr(IP_ADDR); /* 绑定IP */
 55     serv_addr.sin_addr.s_addr = 0;                  /* 绑定0就是绑定自己 */
 56     serv_addr.sin_port = htons(PORT);               /* 端口号 */
 57     ret = bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr_in));
 58     if (ret == 0) {
 59         printf("bind ok\n");
 60     } else {
 61         printf("bind failed\n");
 62         close(sock_fd);
 63         return 0;
 64     }
 65     
 66     /* 让套接字进入被动监听状态 */
 67     ret = listen(sock_fd, 10);
 68     if (ret == 0) {
 69         printf("listen ok\n");
 70     } else {
 71         printf("listen failed\n");
 72         close(sock_fd);
 73         return 0;
 74     }
 75   
 76     /* 接收客户端请求(阻塞) */
 77     memset(&client_addr, 0, sizeof(client_addr));
 78     printf("accept...\n");
 79     confd = accept(sock_fd, (struct sockaddr *)&client_addr, &addr_len);
 80     if (confd > 0) {
 81         printf("accept ok\n");
 82     } else {
 83         printf("accept failed\n");
 84         close(sock_fd);
 85         return 0;
 86     }
 87     
 88     /* 打印客户端信息 */
 89     printf("addr_len = %d\n", addr_len);
 90     printf("Client IP: %s\n", inet_ntoa(client_addr.sin_addr)); /* IP地址 */
 91     printf("Client Port:%d\n", ntohs(client_addr.sin_port));      /* 端口号 */
 92       
 93     while (1) {
 94          memset(buf, 0, sizeof(buf));
 95          scanf("%s", buf);
 96          len = send(confd, buf, strlen(buf), 0);
 97     }
 98     
 99     return 0;
100 }

signal_13_tcp_recv.c

 1 /**
 2  * filename: signal_13_tcp_recv.c
 3  * author: Suzkfly
 4  * date: 2021-02-17
 5  * platform: Ubuntu
 6  *     操作步骤:
 7  *     在一个终端先运行tcp_send,另开一个终端运行tcp_recv,建立连接之后在tcp_send
 8  *     端输入字符,按回车后tcp_recv能够成功接收并打印,但如果关掉tcp_recv(可以用
 9  *     Ctrl+C关闭)之后,在tcp_send端再发送字符,则会引发SIGPIPE。
10  */
11 #include <stdio.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <string.h>
15 #include <netinet/in.h>
16 #include <arpa/inet.h>
17 
18 #define IP_ADDR "192.168.0.128"  /* 服务器IP地址 */
19 #define PORT    10000            /* 服务器端口号 */
20 
21 
22 int main(int argc, const char *argv[])
23 {
24     int sock_fd = 0;
25     int ret = 0;
26     struct sockaddr_in serv_addr; /* 服务器地址 */
27     char buf[128] = { 0 };
28     int len = 0;
29 
30     /* 创建TCP套接字 */
31     sock_fd = socket(AF_INET, SOCK_STREAM, 0);
32     if (sock_fd < 0) {
33         printf("socket failed\n");
34         return 0;
35     }
36 
37     /* 与服务器建立连接 */
38     memset(&serv_addr, 0, sizeof(struct sockaddr_in));
39     serv_addr.sin_family = AF_INET;
40     serv_addr.sin_addr.s_addr = inet_addr(IP_ADDR);     /* 服务器IP */
41     serv_addr.sin_port = htons(PORT);                   /* 服务器端口号 */
42     ret = connect(sock_fd,
43                   (struct sockaddr *)&serv_addr,
44                   sizeof(struct sockaddr_in));
45     if (ret == 0) {
46         printf("connect ok\n");
47     } else {
48         printf("connect failed\n");
49         close(sock_fd);
50         return 0;
51     }
52 
53     while (1) {
54         memset(buf, 0, sizeof(buf));
55         len = recv(sock_fd, buf, sizeof(buf), 0);
56 
57         if (len == 0) {     /* 如果recv返回0,则表示远端断开连接 */
58             break;
59         }
60 
61         printf("len = %d\n", len);
62         printf("data: %s\n", buf);
63     }
64 }

操作步骤:

  1. 执行命令:gcc signal_13_tcp_send.c -o tcp_send

  2. 执行命令:gcc signal_13_tcp_recv.c -o tcp_recv

  3. 运行tcp_send,此时程序将阻塞在accept

  4. 另开一个终端,运行tcp_recv,此时tcp_send继续运行

  5. 在tcp_send端输入数据,按回车键确认,在tcp_recv端可以看到发送的数据

  6. 杀死tcp_recv端(可以用Ctrl+C)

  7. 在tcp_send端继续键入数据发送,第2次发送的时候将引发SIGPIPE信号

疑问:

  在tcp_recv进程终止之后,tcp_send要发送第2次数据的时候才会引发SIGPIPE信号,而不是第1次就引发,不知道是为什么。