在arm-linux上用gdb除錯程式,出現“Program received signal SIGPIPE, Broken pipe”
出現這種情況大多是因為程式採用CS架構(伺服器/客戶端)在讀寫操作時出現,我第一次也是在這樣的情況下遇到的。首先我們都知道套接字的通訊方式是雙工的,同端即可寫也可讀。而出現Broken pipe這種情況的原因是寫段正在寫入時,另一端已關閉套接字,這樣程序就會向系統傳送SIGPIPE訊號,然後系統再回頭叫停執行緒,這樣就會出現管道破裂的訊號並且退出程式。這雖然是程序的一種保護機制,但是在執行過程中一般我們是不希望出現退出程式的保護。於是便上網查了一番。發現在main函式開始加一行“signal(SIGPIPE, SIG_IGN);”程式碼即可意思是遮蔽SIGPIPE訊號,但是加上後並沒有起作用,依舊還是出現管道破裂錯誤。後來在網上看到有大神說是因為signal函式設定的訊號處理只起一次作用,處理一次後就會再重置為預設處理,要用sigaction來設定自定義的訊號處理方式“struct sigaction sa;sa.sa_handler = SIG_IGN;sigaction( SIGPIPE, &sa, 0 );”,於是我自行寫了程式碼測試這種情況是不是如這位大神所說,發現並非如此,只好我用的這個開發板不是如此。於是又陷入在網上一番查詢。
最後決定把這種情況復現出來,這樣更容易測試(這部分程式碼來自網路並自己修改的,如有侵權,請私信告訴我)
cli_test.c
#include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <errno.h>
#define HELLO_WORLD_SERVER_PORT 6666 #define BUFFER_SIZE 1024
void a(void) { printf("123456789\n"); }
int main(int argc, char **argv) { if (argc != 2) { printf("Usage: ./%s ServerIPAddress\n",argv[0]); exit(1); } //signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, a); //struct sigaction sa; //sa.sa_handler = SIG_IGN; //sigaction( SIGPIPE, &sa, 0 );
struct sockaddr_in client_addr; bzero(&client_addr,sizeof(client_addr)); client_addr.sin_family = AF_INET; client_addr.sin_addr.s_addr = htons(INADDR_ANY); client_addr.sin_port = htons(0);
int client_socket = socket(AF_INET,SOCK_STREAM,0);
if( client_socket < 0) { printf("Create Socket Failed!\n"); exit(1); }
if( bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr))) { printf("Client Bind Port Failed!\n"); exit(1); }
struct sockaddr_in server_addr; bzero(&server_addr,sizeof(server_addr)); server_addr.sin_family = AF_INET; if(inet_aton(argv[1],&server_addr.sin_addr) == 0) { printf("Server IP Address Error!\n"); exit(1); } server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT); socklen_t server_addr_length = sizeof(server_addr); if(connect(client_socket,(struct sockaddr*)&server_addr, server_addr_length) < 0) { printf("Can Not Connect To %s!\n",argv[1]); exit(1); }
char buffer[BUFFER_SIZE]; bzero(buffer,BUFFER_SIZE); int length = recv(client_socket,buffer,BUFFER_SIZE,0); if(length < 0) { printf("Recieve Data From Server %s Failed!\n", argv[1]); exit(1); } printf("From Server %s :\t%s",argv[1],buffer);
bzero(buffer,BUFFER_SIZE); strcpy(buffer,"Hello, World! From Client\n");
while(1){ sleep(1); int ret = send(client_socket,buffer,BUFFER_SIZE,/*MSG_NOSIGNAL*/0); if (ret == -1 && errno == EPIPE){ printf("receive sigpipe\n"); printf("receive %s\n", strerror(errno)); } }
close(client_socket); return 0; }
ser_test.c
#include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h>
#define HELLO_WORLD_SERVER_PORT 6666 #define LENGTH_OF_LISTEN_QUEUE 20 #define BUFFER_SIZE 1024
int main(int argc, char **argv) { struct sockaddr_in server_addr; bzero(&server_addr,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htons(INADDR_ANY); server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
int server_socket = socket(AF_INET,SOCK_STREAM,0); if( server_socket < 0) { printf("Create Socket Failed!"); exit(1); }
if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr))) { printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT); exit(1); }
if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) ) { printf("Server Listen Failed!"); exit(1); }
while (1) { struct sockaddr_in client_addr; socklen_t length = sizeof(client_addr);
int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length); if ( new_server_socket < 0) { printf("Server Accept Failed!\n"); break; }
char buffer[BUFFER_SIZE]; bzero(buffer, BUFFER_SIZE); strcpy(buffer,"Hello,World from server!"); strcat(buffer,"\n"); send(new_server_socket,buffer,BUFFER_SIZE,0);
bzero(buffer,BUFFER_SIZE); while(1){ length = recv(new_server_socket,buffer,BUFFER_SIZE,0); if (length < 0) { printf("Server Recieve Data Failed!\n"); exit(1); } printf("\n%s",buffer); } close(new_server_socket); } close(server_socket); return 0; }
Makefile
CC = arm-hisiv500-linux-gcc //我用的交叉編譯工具,根據自己情況進行修改編譯工具 #CC = gcc #CFLAGS = -g -Wall -O3 SRCS = cli_test.c ser_test.c
SER = ser CLI = cli
OBJS = $(SRCS:.c=.o)
%.o:%.c $(CC) $(CFLAGS) -o [email protected] -c $<
all:$(SER) $(CLI) $(SER):ser_test.o $(CC) -o [email protected] $^
$(CLI):cli_test.o $(CC) -o [email protected] $^ rm -rf *.bak
clean: rm -rf $(SER) $(CLI) $(OBJS) *.bak
make編譯,分別將ser端和cli端執行起來,正常接收資訊後,Ctrl+c結束ser程式,cli端即出現管道破裂錯誤。
最後經過測試發現上面提到過的兩種遮蔽方式在gcc編譯工具生成的檔案都是可行的,都能遮蔽成功,唯獨在arm-linux下不行。後來又找到一種臨時可用的拼比方法,那就是將send的flags位(最後一位引數)傳入MSG_NOSIGNAL,這是遮蔽send所產生的所有訊號,當然包括段錯誤訊號,這樣就會很危險,當然也是一種臨時可行的方法。
後來發現用arm-linux和gcc兩種編譯及操作方式唯一不同的就是我在arm-linux上用了gdb除錯,於是猜想會不會是gdb在搞鬼呢。於是不用gdb直接執行程式碼,經過測試發現還真的是gdb。
後來再經過查詢發現,原因是這樣的,當程序出現Broken pipe錯誤時,會將該訊號傳送給系統,系統收到訊號後會反過來再發給程序來叫停程序,當用gdb除錯時,收到系統發的訊號的並不是程序,而是讓gdb給半路攔截下來了,當gdb收到訊號後預設處理方式是暫停程式,將錯誤打印出來。這樣一來我們的程序實際在並沒有收到訊號的情況下就被叫停了,所以在程式裡面不管怎樣處理訊號都是做無用功。
所以要想繼續用gdb除錯執行,則需要修改gdb對訊號的處理方式
handle SIGPIPE nostop