目錄
1. sigaction函數詳解 1.1 結構體siginfo_t詳解 1.2 sa_sigaction函數指標中的第3個參數void * 講解
1. sigaction函數詳解
sigaction函數相對於signal函數更加健壯,而且功能也更強大些,其函數原型為:
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);傳回值:成功: 0 失敗:-1 錯誤原因置於errno中
對於參數:
- signum: 指要捕獲的訊號類型,支援linux系統上的64種訊號(除了SIGKILL和SIGSTOP)
- act:該參數指定新的訊號處理方式。
- oact:該參數則輸出訊號先前的處理方式(若不為NULL的話)
從該函數的參數可以看出:act和oact都是sigaction結構體類型的指標,sigaction結構體描述了訊號處理的細節,其結構定義如下:
//該結構體描述當訊號到達時採取的行動struct sigaction {#ifdef __USE_POSIX199309 union { __sighandler_t sa_handler; void (*sa_sigaction) (int, siginfo_t *, void *); } __sigaction_handler;# define sa_handler __sigaction_handler.sa_handler# define sa_sigaction __sigaction_handler.sa_sigaction#else __sighandler_t sa_handler;#endif __sigset_t sa_mask; int sa_flags; void (*sa_restorer) (void); };
對於結構體struct sigaction中各成員的詳細描述:
- sa_hander: 指定訊號處理函數,此參數和signal()函數中的參數handler相同,此參數主要用來對訊號舊的安裝函數signal()處理形式的支援。
- sa_sigaction: 為新的訊號安裝機制。處理函數被調用的時候,不但可以得到訊號的編號,而且可以獲悉被調用的原因以及產生問題的內容相關的相關資訊。
%%%% 注意:sa_hander和sa_sigaction是互斥的,若執行sa_hander,就無法去執行sa_sigaction;反之亦然 %%%%
- sa_mask: 用來設定進程的訊號掩碼,也就是說在進程原有的訊號掩碼基礎上增加訊號掩碼,以指定哪些訊號不能夠發送給本進程。sa_mask是訊號集sigset_t類型,該類型指定一組訊號。
- sa_restorer: 該成員已經過時,不要使用。
- sa_flags: 用來設定程式收到訊號時的行為。其可選值如下表:
| 選項 |
含義 |
| SA_NOCLDSTOP |
如果sigaction的sig參數是SIGCHLD,則設定該標誌表示子進程暫停時不產生 |
| SA_NOCLDWAIT |
如果sigaction的sig參數是SIGCHLD,則設定該標誌表示子進程結束時不產生殭屍進程 |
| SA_SIGINFO |
使用sa_sigaction作為訊號處理函數(而不是sa_handler),它給進程提供更多相關資訊 |
| SA_ONSTACK |
調用有sigaltstack函數色或者的可選訊號棧上的訊號處理函數 |
| SA_RESTART |
重新調用被該訊號終止的系統調用 |
| SA_NODEFER |
當接收到訊號並進入其訊號處理函數時,不屏蔽該訊號。預設情況下,我們期望進程在處理一個訊號時不再接收到同種訊號,否則引起一些競態條件 |
| SA_RESETHAND |
訊號處理函數執行完成以後,回複訊號的預設處理方式 |
| SA_INTERRUPT |
終端系統調用 |
重點:SA_SIGINFO
代碼1:
/************************************************************************* * File Name: sigaction.c * Author: The answer * Function: Other * Mail: 2412799512@qq.com * Created Time: 2018年04月29日 星期日 05時06分43秒 ************************************************************************/#include <stdio.h>#include <signal.h>#include <sys/types.h>#include <unistd.h>#include <stdlib.h>#include <string.h>void sig_handler(int signum,siginfo_t *info,void *data){ printf("receive signal [%d]\n",signum); sleep(5);}int main(int argc,char **argv){ if(argc <2 ) { fprintf(stderr,"Usage:%s signum.\n",argv[0]); return -1; } struct sigaction act; int signum ; signum = atoi(argv[1]); sigemptyset(&act.sa_mask); act.sa_sigaction = sig_handler; act.sa_flags | SA_SIGINFO; if(sigaction(signum,&act,NULL) < 0) { fprintf(stderr,"sigaction err.\n"); return -2; } while(1) { sleep(2); puts("wait for the sigal."); } return 0;}
該程式會每隔2秒鐘列印一次:wait for the sigal. 此時若linux終端按下按鍵組合:ctrl+c,則會調用使用者自訂的函數列印:^Creceive signal [2] 然後繼續每隔2秒鐘列印一次wait for the sigal. 若在終端按下按鍵組合:ctrl + \,則程式退出並終止。
2018-05-06 補充:
上面在講解結構體sigaction時提到過該結構體成員sa_flags用來說明並指定該sigaction函數中安裝資訊的函數是舊的signal還是最新的。若為sa_sigaction新的訊號函數,則:sa_flags | SA_SIGINFO;(“|”或—>位元運算);若想要使用舊的sa_handle,則指定sa_flags = 0;便可。
代碼2:
/************************************************************************* * File Name: sigaction.c * Author: The answer * Function: Other * Mail: 2412799512@qq.com * Created Time: 2018年05月06日 星期日 14時48分03秒 ************************************************************************/#include <stdio.h>#include <signal.h>#include <sys/types.h>#include <unistd.h>#include <stdlib.h>#include <string.h>void sig_handler(int signum,siginfo_t *info,void *data){ printf("receive signal [%d]\n",signum); sleep(5);}void oldSig_handler(int signo){ printf("oldSig_handler receive signal [%d]\n",signo); sleep(2);}int main(int argc,char **argv){ if(argc <2 ) { fprintf(stderr,"Usage:%s signum.\n",argv[0]); return -1; } struct sigaction act; int signum ; signum = atoi(argv[1]); sigemptyset(&act.sa_mask); act.sa_handler = oldSig_handler; //將oldSig_handler函數地址賦給sa_hanlder函數指標 act.sa_flags = 0; //指定sa_flags = 0; if(sigaction(signum,&act,NULL) < 0) { fprintf(stderr,"sigaction err.\n"); return -2; } while(1) { sleep(2); puts("wait for the sigal."); } return 0;}
該程式實現的功能和”代碼1”實現的功能相同。見“代碼1”中詳細描述。
1.1 結構體siginfo_t詳解
前面說過sigaction是sigal函數的加強版。因此sigaction函數肯定會有sigal函數沒有的功能,雖然“代碼1”中用了sa_sigaction新函數,但是我只使用了第一個參數,訊號的編號(若只是用這一個參數,和直接使用sigal沒有任何區別)。
man sigaction可以看到sgaction機構體中的成員sa_sigaction是一個函數指標,該函數指標指向的函數中公有3個參數,如下:
void (sa_sigaction)(int, siginfo_t , void *);
其中siginfo_t是一個結構體,其定義如下:
typedef struct { int si_signo; /* 訊號 */ int si_errno; /* 錯誤值,見<error.h>中*/ int si_code; /* 訊號代碼 *//*************** union聯合體類型 ******************/ union { int _pad[__SI_PAD_SIZE]; /* kill(). */ struct { __pid_t si_pid; /* 發送進程的ID. */ __uid_t si_uid; /* 發送過程的真實ID. */ } _kill; /* POSIX.1b timers. */ struct { int si_tid; /* 定時器ID. */ int si_overrun; /* 超額計數. */ sigval_t si_sigval; /* 訊號值. */ } _timer; /* POSIX.1b signals. */ struct { __pid_t si_pid; /* 發送進程的ID. */ __uid_t si_uid; /* 發送過程的真實ID. */ sigval_t si_sigval; /* 訊號值 */ } _rt; /* SIGCHLD. */ struct { __pid_t si_pid; /* 發送進程的ID. */ __uid_t si_uid; /* 發送過程的真實ID. */ int si_status; /* 退出值或訊號. */ __sigchld_clock_t si_utime; /* 使用者時間消耗. */ __sigchld_clock_t si_stime; /* 系統時間消耗. */ } _sigchld; /* SIGILL, SIGFPE, SIGSEGV, SIGBUS. */ struct { void *si_addr; /* 斷層斷陷/記憶體引用. */ short int si_addr_lsb; /* 報告地址的有效LSB. */ } _sigfault; /* SIGPOLL. */ struct { long int si_band; /* SIGPOLL訊號中的band事件. */ int si_fd; /* 檔案描述符. */ } _sigpoll; /* SIGSYS. */ struct { void *_call_addr; /* 使用者調用 */ int _syscall; /* 觸發系統本機號碼. */ unsigned int _arch; /* AUDIT_ARCH_的系統調用. */ } _sigsys; } _sifields; // %%%% union範圍 %%%% /******************************************/ } siginfo_t __SI_ALIGNMENT;
該siginfo_t結構體中內嵌了一個聯合體(又名“共用體”),而該聯合體中嵌套了多個結構體strcut類型。該聯合體union的範圍為:
union { union成員類別 }_sifields,除了這個union聯合體外,該siginfo_t結構體中主要有3個結構體成員:
int si_signo; /* 訊號 */int si_errno; /* 錯誤值,見<error.h>中*/int si_code; /* 訊號代碼 ;si_code是一個值(不是位元遮罩),說明為什麼發送這個訊號 */
si_signo、si_errno和si_code為所有訊號定義。(si_errno通常在Linux上不使用。)結構的其餘部分可能是一個聯合,因此,應該唯讀取對給定訊號有意義的欄位。 1.2 sa_sigaction函數指標中的第3個參數void * 講解
sa_sigaction函數是函數指標,所指向的函數為3個參數,前面已經講解了該形參中的前面2個參數的含義。下面具體講解第3個參數。sa_sigaction指向函數的具體形式:
void
handler(int sig, siginfo_t *info, void *ucontext)
{
…
}
這是一個指向ucontext_t結構體的指標,轉換為void *類型。該欄位指向的結構包含了訊號上下文。在使用者空間堆棧上儲存的資訊。具體可以去參考:sigreturn(2). 更進一步的資訊在 getcontext(3). 可以看到ucontext_t結構體定義。 但是通常都沒有用到第3個參數 ;
代碼3中用到了sa_sigaction中的第2個參數:並列印了其發送訊號的進程id與真實id。
/************************************************************************* * File Name: sigaction.c * Author: The answer * Function: Other * Mail: 2412799512@qq.com * Created Time: 2018年04月29日 星期日 05時06分43秒 ************************************************************************/#include <stdio.h>#include <signal.h>#include <sys/types.h>#include <unistd.h>#include <sys/stat.h>#include <stdlib.h>#include <string.h>void sig_handler(int signum,siginfo_t *info,void *data){ printf("main pid: [%d]\n",getpid()); printf("receive signal [%d]\n",signum); printf("Signal number [%d]\n",info->si_signo); printf("Signal code [%d]\n",info->si_code); printf("Sigal errno [%d]\n",info->si_errno); printf("Sending process ID [%d]\n",info->si_pid); printf("Real user ID of sending process [%d]\n",info->si_uid); printf("SI_USER [%d]\n",SI_USER); sleep(5);}void oldSig_handler(int signo){ printf("oldSig_handler receive signal [%d]\n",signo); sleep(2);}int main(int argc,char **argv){ if(argc <2 ) { fprintf(stderr,"Usage:%s signum.\n",argv[0]); return -1; } struct sigaction act; int signum ; signum = atoi(argv[1]); sigemptyset(&act.sa_mask); act.sa_sigaction = sig_handler; act.sa_flags | SA_SIGINFO; if(sigaction(signum,&act,NULL) < 0) { fprintf(stderr,"sigaction err.\n"); return -2; } while(1) { sleep(2); puts("wait for the sigal."); } return 0;}