2012-07-14 61 views
1

我有一個簡單的程序,它使用select和類似的東西來實現多路複用IO。
要中斷「服務器」進程,我集成了一個sig_handler,它對SIGINT作出反應。在SIGINT上免費分配內存的最佳實踐

每次分配內存時,包含方法都會釋放自己或調用方法。

使用valgrind顯示出來,表明有一些分配沒有被釋放。
也許沒有必要,但我想知道什麼是處理信號的最佳方式。
當按下STRG + C時似乎不會調用free調用。
因此,以休息狀態退出循環將毫無意義,這是我的第一個方法。

在關閉整個程序之前,是否有可能清理所有東西?

感謝您的任何提示和建議。

+0

如果您計劃退出,則無需執行此操作。只有當您計劃繼續時,纔有必要確保沒有泄漏。 – pizza 2012-07-15 02:20:18

回答

8

Valgrind是隻是尋找內存泄漏的工具,而不是甲骨文公司的建議必須得到重視。制定一項「Valgrind-clean」計劃是一個有價值的目標,但不要讓它失控。問問你自己一些有關該計劃的問題。

  1. 方案是否需要做任何事情,當它接收到一個SIGINTSIGQUIT或什麼?它需要做某種乾淨的關機嗎?例如,服務器可能決定完成處理所有打開的請求,或者至少向已連接的客戶端發送關閉消息。

  2. 突然終止是否總是留下某些塊?那麼你可以撤銷Valgrind的報告,而不必花費額外的時間來釋放已經釋放的內存。

簡單來說,只有兩個原因調用free在程序即將退出。

  1. 如果是推翻Valgrind的消息最簡單的方法(即,不讀Valgrind的手冊)

  2. 如果它使你的代碼更簡單。

否則,就是不程序退出時調用free,因爲所有它的作用就是燒CPU週期。

處理SIGINT:我能想到的四種常見的方式來處理SIGINT:

  1. 使用默認的處理程序。強烈建議,這需要最少量的代碼,並且不太可能導致任何異常的程序行爲。你的程序將退出。使用longjmp立即退出。這是爲喜歡騎摩托車而不戴頭盔的民衆。這就像玩圖書館電話玩俄羅斯輪盤賭。不建議。

  2. 設置一個標誌,並中斷主循環的pselect/ppoll。這是一個很難做到的事情,因爲你必須用信號掩碼來旋轉。您只想中斷pselect/ppoll,而不是像mallocfree這樣的非重入函數,因此您必須非常小心地處理信號掩碼等內容。不建議。您必須使用pselect/ppoll而不是select/poll,因爲「p」版本可以自動設置信號掩碼。如果您使用selectpoll,則在檢查標誌後但在致電select/poll之前,信號可能會到達,這很糟糕。

  3. 創建管道以在主線程和信號處理程序之間進行通信。請致電select/poll。信號處理程序只需將一個字節寫入管道,並且如果主循環成功從另一端讀取一個字節,則它會乾淨地退出。強烈推薦。您也可以讓信號處理程序自行卸載,因此不耐煩的用戶可以兩次點擊CTRL+C以立即退出。

兩種最簡單,最傻瓜的方法是#1和#4。

退出的程序沒有任何泄漏。只有正在運行的程序可能有泄漏。一旦程序退出,所有內存都被釋放(所以沒有泄漏)。

+5

+1表示「退出的程序沒有任何泄漏」。愛它。 – 2012-07-15 01:23:16

+0

有了這個說法,我認爲如果它解決了異步信號安全和試圖從異步信號處理程序中釋放內存的危險性,那麼您的答案可能會更好。 – 2012-07-15 01:23:56

1

這是我的簡單和稍微髒的解決方案。

#include <signal.h> 

volatile bool gContinue; 

void handleCtrlC(int) { 
    gContinue = false; 
} 

int main() { 
    gContinue = true; 

    signal(SIGINT, handleCtrlC); 

    ... allocate memory ... 

    sigset_t sigmask; 
    sigemptyset (&sigmask); 

    while (gContinue) { 

     /*...*/ 
     ready = pselect(nfds, &readfds, &writefds, &exceptfds, 
      timeout, &sigmask); 
     /*...*/ 
    } 

    ... free memory ... 

    return 0; 
} 

編輯:添加到PSELECT循環。

+0

我認爲'gContinue'必須是'volatile'。我不知道是誰告訴你全局變量是「壞」,但你應該停止聽他們。這正是全局變量適合的問題。 – 2012-07-15 00:21:58

+0

謝謝。我會添加volatile並刪除評論。 – 2012-07-15 00:27:28

+0

順便說一句,我沒有評論其他答案的能力,但我認爲在使用pselect時不需要sigprocmask。這對我來說是新的信息,但我認爲sigprocmask和select一樣使用。 sigprocmask(SIG_SETMASK,&sigmask,&origmask); ready = select(nfds,&readfds,&writefds,&exceptfds,timeout); sigprocmask(SIG_SETMASK,&origmask,NULL); – 2012-07-15 00:30:54

0

Adam Hunt的建議將起作用,前提是您在致電select之前爲國旗添加支票。它必須在之前,因爲您的正常EINTR處理可能會跳過檢查,否則退出將延遲,直到您的下一個select返回一個真實的事件。

while (gContinue) { 
    /*set up some stuff for next select call*/ 
    do { 
     if (gContinue == 0) break; 
     nfds = select(...); 
    } while (nfds == -1 && errno == EINTR); 
    /*handle select return*/ 
} 

編輯:迪特里希埃普指出存在的標誌檢查和select通話只能通過調用關閉,以pselect之間的競爭條件。 pselectselect非常相似,主要區別在於最後一個參數被用作掩碼來確定要阻止哪些信號。所以下面的代碼示例關閉標誌檢查和pselect電話之間的競爭:

sigset_t emptyset, blockset, origset; 
sigemptyset(&emptyset); 
sigemptyset(&blockset); 
sigaddset(&blockset, SIGINT); 

while (gContinue) { 
    /*...*/ 
    sigprocmask(SIG_BLOCK, &blockset, &origset); 
    do { 
     if (gContinue == 0) break; 
     nfds = pselect(..., &emptyset); 
    } while (nfds == -1 && errno == EINTR); 
    sigprocmask(SIG_SETMASK, &origset, NULL); 
    /*...*/ 
}; 

另一種方法是你分配的所有元素註冊到一個全局數據結構,使他們可以通過一個atexit處理中解放出來的是你安裝。如果您最終獲得解除分配的代碼,請首先從全局數據結構中註銷它;

m = malloc(sz); 
register_allocation(m); 
/*...*/ 
unregister_allocation(m); 
free(m); 

而且使用atexit

void cleanup_allocations() { 
    /*...*/ 
} 

atexit(cleanup_allocations); 
+0

請注意,這種情況正是我們選擇'pselect'的原因。你不能避免檢查標誌和調用'select'之間的競爭,所以你必須**使用'pselect',除非你想失去一些SIGINT。 – 2012-07-14 23:55:35

+0

雖然'atexit'的東西是愚蠢的,因爲你所做的一切都是在消除Valgrind的信息而不知道它們是否是真正的泄漏。你可能只是禁用Valgrind的泄漏檢查,因爲這就是你正在做的事情。 – 2012-07-14 23:57:58

+0

@DietrichEpp:我將編輯「pselect」的答案,謝謝你的信息。 'atexit'的使用僅僅取決於OP的目標。它可以作爲獨立的內存管理器使用,它自己的泄漏檢測始終處於開啓狀態並且可以查詢,即使在已部署的代碼中也是如此。 – jxh 2012-07-15 00:04:53