2013-08-27 67 views
0

這裏的目標是捕獲SIGINT以關閉小型套接字服務器上的服務器套接字。我試圖使用嵌套函數來保持代碼清潔。但...OSX帶有嵌套函數的「非法指令4」

當我做Ctrl-C(SIGINT,對嗎?)時,我得到Illegal instruction: 4。在閱讀this post後,我試着在編譯標誌中加入-mmacosx-version-min=10.8,因爲我在10.8上。執行Ctrl-C時出現同樣的錯誤。這裏

兩個問題:爲什麼我收到`非法指令4" 我怎樣才能關閉服務器套接字不使用全局變量

我的軟件:??

Mac OSX 10.8.4 
GCC 4.2.1 

下面是我「M編譯:

gcc -fnested-functions main.c 

下面的代碼:

#include <sys/socket.h> 
#include <unistd.h> 
#include <signal.h> 
#include <stdio.h> 
#include <stdlib.h> 

void register_sigint_handler(int *serverSocket) 
{ 
    void sigint_handler(int signal) { 
     printf("Shutting down...\n"); 
     printf("Server socket was %d\n", *serverSocket); 
     close(*serverSocket); 
     exit(0); 
    } 
    signal(SIGINT, &sigint_handler); 
} 

int main(void) { 
    int serverSocket = 0, guestSocket = 0; 

    register_sigint_handler(&serverSocket); 

    serverSocket = socket(PF_INET, SOCK_STREAM, 0); 

    while (1) {} 

    close(serverSocket); 
    return 0; 
} 
+0

您是否嘗試使用調試器(gdb/lldb)? – Kevin

+1

這不會導致你的問題,但'printf'不安全的調用信號處理程序。信號處理程序在其中可以做的事情非常有限 - 最安全的做法是設置一個標誌(類型爲'volatile sigatomic_t'),然後在主循環期間定期檢查該標誌,並注意'EINTR '系統調用的錯誤代碼,如'accept(2)'和'select(2)'。 –

+0

@AdamRosenfield:由於代碼使用套接字,因此它可能使用[POSIX](http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04)規定的不太嚴格的規則安全地從信號處理程序調用。安全列表仍然不包含'printf()',但它確實允許使用很多其他函數,包括'write()'。但是,您評論的主要內容仍然是準確的:(1)不要調用'printf()'等,(2)謹慎處理信號處理程序中的操作。 –

回答

6

雖然我不能告訴你具體是什麼情況發生,gcc docs有一個概括:

如果您嘗試 包含函數退出後通過其地址來調用嵌套函數,所有的地獄破散。

將函數指針傳遞給signal()會做到這一點,在包含函數退出後調用你的本地函數。所以你不應該通過一個嵌套函數指針信號()

你應該可能只是使用一個正常的處理函數,它設置一個標誌。

static volatile int do_exit; 
void sigint_handler(int sig) 
{ 
    do_exit = 1; 
} 

在服務器中,人們通常有某種主循環,例如,選擇或輪詢,主循環,空while循環現在可以成爲

while (!do_exit) { 
    pause(); 
} 

(請注意,插座自動操作系統,當進程退出關閉;)

+0

感謝您的徹底解答。只是爲了讓其他人想知道爲什麼在這裏需要'volatile',這個Q/A幫助我理解了這一點:http://stackoverflow.com/questions/246127/why-is-volatile-needed-in-c – conradkdotcom

5

Don't do that, then.

GCC的嵌套功能在C擴展而不是提供真正的關閉。當你把地址sigint_handler,一個「蹦牀」(一小塊自修改代碼)寫入堆棧;一旦register_sigint_handler退出,蹦牀被摧毀;隨後嘗試調用蹦牀(由內核調用該信號)會導致未定義的行爲。

根據定義,信號處理程序是過程全局的。因此,原則上不正確嘗試避免在信號處理程序中使用全局變量。想象一下你會怎麼做,以便使代碼能夠應對兩個服務器套接字:你只能有一個SIGINT處理程序註冊,所以不知怎的,它不得不關閉兩個套接字

當您的進程終止時,所有打開的文件描述符都會自動關閉。因此,不需要先手動關閉它們。此外,這是違反約定退出^C成功。如果此程序是由監督進程驅動的,則該進程想知道(通過waitpid的狀態代碼)它由於SIGINT而退出。把這兩個東西放在一起,你不應該有這個信號處理器在所有。例如,如果您想在退出時爲每個活動客戶端連接寫入內容,則需要一個信號處理程序。但是,如果您想在退出時爲每個活動客戶端連接寫入內容,在這一點上你想有信號處理程序提醒主事件循環,並做好了那裏。)

(考慮使用libevent對於這樣的事情,而不是做所有的低級別咕自己。)