linux系統:信号處理器函數的設計原則
1 設計信号處理函數
- 信号處理函數中設定一個全局标志變量并且退出,主程式對此标志進行周期性檢查,一旦标志置位,就執行相應的動作,如果主程式因為I/O狀态無法無法周期性檢查,那麼可以在信号處理函數中向一個專用管道寫入資料,主程式中檢測該管道的讀端的檔案描述符
- 信号處理函數執行完成相應的任務之後,終止程序或者非本地跳轉,将棧解開,傳回主程式
1.1 可重入函數和異步信号安全函數
- 可重入函數:該函數可以被多個任務調用(一般是線程),并且多個任務之間不會互相影響
- 不可重入函數:該函數不能被多個任務調用,因為一旦調用,任務之間會産生互相影響,因為該函數當中的一些變量可能是臨界資源,一個任務1在使用的時候,可能CPU排程到另外一個任務2,任務2又通過這個函數在任務1沒有完成對這個臨界資源的通路的時候,改變了臨界資源,進而造成了任務1的一些錯誤,是以也可以說不可重入函數是不能被中斷的,要讓不可重入函數變成可重入的方法:
- 用互斥或者P、V操作保護臨界資源
- 不随便适用全局變量、靜态變量、malloc、free等操作
- 不可重入函數舉例:
/*************************************************************************\ * Copyright (C) Michael Kerrisk, 2015. * * * * This program is free software. You may use, modify, and redistribute it * * under the terms of the GNU General Public License as published by the * * Free Software Foundation, either version 3 or (at your option) any * * later version. This program is distributed without any warranty. See * * the file COPYING.gpl-v3 for details. * \*************************************************************************/ /* Listing 21-1 */ /* nonreentrant.c Demonstrate the nonreentrant nature of some library functions, in this example, crypt(3). */ #if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600 #define _XOPEN_SOURCE 600 #endif #include <unistd.h> #include <signal.h> #include <string.h> #include "tlpi_hdr.h" static char *str2; /* Set from argv[2] */ static int handled = 0; /* Counts number of calls to handler */ static void handler(int sig) { crypt(str2, "xx"); handled++; } int main(int argc, char *argv[]) { char *cr1; int callNum, mismatch; struct sigaction sa; if (argc != 3) usageErr("%s str1 str2\n", argv[0]); str2 = argv[2]; /* Make argv[2] available to handler */ cr1 = strdup(crypt(argv[1], "xx")); /* Copy statically allocated string to another buffer */ if (cr1 == NULL) errExit("strdup"); sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = handler; if (sigaction(SIGINT, &sa, NULL) == -1) errExit("sigaction"); /* Repeatedly call crypt() using argv[1]. If interrupted by a signal handler, then the static storage returned by crypt() will be overwritten by the results of encrypting argv[2], and strcmp() will detect a mismatch with the value in 'cr1'. */ for (callNum = 1, mismatch = 0; ; callNum++) { if (strcmp(crypt(argv[1], "xx"), cr1) != 0) { mismatch++; printf("Mismatch on call %d (mismatch=%d handled=%d)\n", callNum, mismatch, handled); } } }
#include <string.h> /******************* 函數功能:指派s指向的内容 傳回值:指向複制緩沖區的指針,緩沖區是通過malloc動态開辟的,是以使用完畢之後,要釋放 ********************/ char *strdup(const char *s); /*函數原型*/ char * __strdup(const char *s) { size_t len = strlen(s) +1; void *new = malloc(len); if (new == NULL) return NULL; return (char *)memecpy(new,s,len); } /* 由strcpy和strdup函數實作可知 1>strdup函數傳回指向被複制的字元串的指針,所需空間由malloc()函數配置設定且可以由free()函數釋放。stdrup可以直接把要複制的内容複制給沒有初始化的指針,因為它會自動配置設定空間給目的指針。 2>strcpy的目的指針一定是已經配置設定好的記憶體指針。 3)strdup的缺點: 使用strdup函數的時候,往往會忘記記憶體的釋放,因為申請記憶體空間的動作是在strdup函數内實作,如果對該函數的實作不是很了解,則會忘記使用free函數來釋放空間 */
#define _XOPEN_SOURCE /* See feature_test_macros(7) */ #include <unistd.h> /*********************** 函數功能:crypt()算法會接受一個最長可達8字元的密鑰(即key),并施以資料加密算法(DES)的一種變體。salt參數指向一個兩個字元的字元串,用來擾動(改變)DES算法。該函數傳回一個指針,指向長度13個字元的字元串 ***********************/ char *crypt(const char *key, const char *salt);
- 标準的異步信号安全函數:某些函數從信号處理器函數調用時,可以保證其實作是安全的,如果某一個函數是可以重入的,又或者信号處理器無法将其中斷,就稱該函數是異步信号安全函數
- 保證信号處理器程式安全:
- 隻調用異步信号安全函數
- 主程式執行不安全函數時候,信号處理器中也有可能執行的時候,阻塞信号的傳遞
- 導緻不安全的另外一個因素:信号處理器中更新errno值的時候,可能會覆寫主程式調用相同函數時候設定的errno值,是以在信号處理器函數開始執行的時候,儲存舊的errno值,信号處理器函數結束的時候把errno值設定為舊的errno值就可以了
void handler(int sig) { int saveErrno; saveErrno = errno; /*Now we can execute a function that might modify errno*/ errno = saveErrno; }
1.2 全局變量和sig_atomic_t資料類型
- 實際上信号處理器函數任然可能需要和主程式共享全局變量,有時候對全局變量的讀寫可能不止一條機器指令,為了防止處理器函數會在此期間打斷,是以應該用sig_atomic_t定義一個全局變量,保證讀寫操作的原子性,并且用volatile修飾,防止編譯器将其優化到寄存器中
2 終止信号處理器函數的其他方法
- 使用_exit()終止程序
- 使用kill殺死程序
- 信号處理器中執行非本地跳轉
- 使用abort()函數終止程序,并且産生核心存儲
2.1 信号處理器中執行非本地跳轉
- 簡單來說就是信号處理程序在退出的時候可以退出到指定的位置,具體見linux_unix系統程式設計手冊
3 SA_SAGINFO标志
- sigaction()建立處理器函數時設定了SA_SIGINFO标志,在收到信号的時候處理器函數可以擷取該信号的一些附加資訊,但是此時處理器函數必須按如下聲明:
void handler(int sig, siginfo *siginfo, void *ucontext) /*參數: siginfo:指向一個siginfo結構體的指針,該結構體包含了一些附加資訊 sig:表示信号編号 ucontent:指向一個ucontent_t類型結構,主要包含使用者的上下文資訊,描述調用處理器程序函數前的程序狀态 */ struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }; //是以初始化的時候一定要初始化sa_sigaction字段 //siginfo_t結構體 siginfo_t { int si_signo; /* Signal number */ int si_errno; /* An errno value */ int si_code; /* Signal code */ int si_trapno; /* Trap number that caused hardware-generated signal (unused on most architectures) */ pid_t si_pid; /* Sending process ID */ uid_t si_uid; /* Real user ID of sending process */ int si_status; /* Exit value or signal */ clock_t si_utime; /* User time consumed */ clock_t si_stime; /* System time consumed */ sigval_t si_value; /* Signal value */ int si_int; /* POSIX.1b signal */ void *si_ptr; /* POSIX.1b signal */ int si_overrun; /* Timer overrun count; POSIX.1b timers */ int si_timerid; /* Timer ID; POSIX.1b timers */ void *si_addr; /* Memory location which caused fault */ long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */ int si_fd; /* File descriptor */ short si_addr_lsb; /* Least significant bit of address (since kernel 2.6.32) */ }
4 系統調用的中斷和重新開機
- 為某一個程序建立一個信号處理函數
- 發起一個阻塞的系統調用,在系統阻塞期間,信号傳遞過來,打斷了阻塞的系統調用,在信号處理例程結束之後,預設情況下系統調用失敗,并且将errno設定為EINTR
- 一般情況下都希望系統調用可以重新開機,解決方案:
/*通過errno值判斷是否被中斷,然後決定是否繼續執行被打斷的系統調用*/
while ((cnt = read(fd, buf, BUF_SIZE) == -1 && errno == EINTR))
continue;
//如果該代碼頻繁使用:
#define NO_EINTR(SMT) while ((smt) == -1 && errno == EINTR);
- 指定SA_RESTART标志的sigaction()來建立信号處理器函數,進而令核心代表程序自動重新開機系統調用,但是并非所有系統調用都可以
- 可以重新開機的庫函數
linux系統:信号處理器函數的設計原則 - 不可以重新開機的:
linux系統:信号處理器函數的設計原則