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次就引发,不知道是为什么。