網頁

2006年4月15日 星期六

簡單地說,va函數的實現就是對參數指針的使用和控制。
typedef char * va_list; // x86平台下va_list的定義
函數的固定參數部分,可以直接從函數定義時的參數名獲得;對於可選參數部分,先將指針指向第一個可選參數,然後依次後移指針,根據與結束標誌的比較來判斷是否已經獲得全部參數。因此,va函數中結束標誌必須事先約定好,否則,指針會指向無效的記憶體地址,導致出錯。
這裡,移動指針使其指向下一個參數,那麼移動指針時的偏移量是多少呢,沒有具體答案,因為這裡涉及到記憶體對齊(alignment)問題,記憶體對齊跟具體使用的硬體平臺有密切關係,比如大家熟知的32位x86平臺規定所有的變數地址必須是4的倍數(sizeof(int) = 4)。va機制中用宏_INTSIZEOF(n)來解決這個問題,沒有這些宏,va的可攜性無從談起。
首先介紹宏_INTSIZEOF(n),它求出變數佔用記憶體空間的大小,是va的實現的基礎。
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
//第一個可選參數地址
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一個參數地址
#define va_end(ap) ( ap = (va_list)0 )
//將指針置為無效
下表是針對函數int TestFunc(int n1, int n2, int n3, …)
參數傳遞時的記憶體堆棧情況。(C編譯器默認的參數傳遞方式是__cdecl。)
對該函數的調用為int result = TestFunc(a, b, c, d. e); 其中e為結束標誌。

高位址
最後一個可選參數 => 用va_start後,arg_ptr指向下一個可選參數的位址
第一個可選參數 => 用va_start後,arg_ptr指向的位址
N3位址
N2位址
N1位址
函式返回位址
低位址

從上圖中可以很清楚地看出va_xxx宏如此編寫的原因。
1. va_start。為了得到第一個可選參數的地址,我們有三種辦法可以做到:
A) = &n3 + _INTSIZEOF(n3)
// 最後一個固定參數的地址 + 該參數佔用記憶體的大小
B) = &n2 + _INTSIZEOF(n3) + _INTSIZEOF(n2)
// 中間某個固定參數的地址 + 該參數之後所有固定參數佔用的記憶體大小之和
C) = &n1 + _INTSIZEOF(n3) + _INTSIZEOF(n2) + _INTSIZEOF(n1)
// 第一個固定參數的地址 + 所有固定參數佔用的記憶體大小之和
從編譯器實現角度來看,方法B),方法C)為了求出地址,編譯器還需知道有多少個固定參數,以及它們的大小,沒有把問題分解到最簡單,所以不是很聰明的途徑,不予採納;相對來說,方法A)中運算的兩個值則完全可以確定。va_start()正是採用A)方法,接受最後一個固定參數。調用va_start()的結果總是使指針指向下一個參數的地址,並把它作為第一個可選參數。在含多個固定參數的函數中,調用va_start()時,如果不是用最後一個固定參數,對於編譯器來說,可選參數的個數已經增加,將給程式帶來一些意想不到的錯誤。(當然如果你認為自己對指針已經知根知底,遊刃有餘,那麼,怎麼用就隨你,你甚至可以用它完成一些很優秀(高效)的代碼,但是,這樣會大大降低代碼的可讀性。)
注意:宏va_start是對參數的地址進行操作的,要求參數地址必須是有效的。一些地址無效的類型不能當作固定參數類型。比如:寄存器類型,它的地址不是有效的記憶體地址值;數組和函數也不允許,他們的長度是個問題。因此,這些類型時不能作為va函數的參數的。
2. va_arg身兼二職:返回當前參數,並使參數指針指向下一個參數。
初看va_arg宏定義很彆扭,如果把它拆成兩個語句,可以很清楚地看出它完成的兩個職責。
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一個參數地址
// 將( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )拆成:
/* 指針ap指向下一個參數的地址 */
1. ap += _INTSIZEOF(t); // 當前,ap已經指向下一個參數了
/* ap減去當前參數的大小得到當前參數的地址,再強制類型轉換後返回它的值 */
2. return *(t *)( ap - _INTSIZEOF(t))
回想到printf/scanf系列函數的%d %s之類的格式化指令,我們不難理解這些它們的用途了- 明示參數強制轉換的類型。
(注:printf/scanf沒有使用va_xxx來實現,但原理是一致的。)
3.va_end很簡單,僅僅是把指針作廢而已。
#define va_end(ap) (ap = (va_list)0) // x86平臺
四、 簡潔、靈活,也有危險
從va的實現可以看出,指針的合理運用,把C語言簡潔、靈活的特性表現得淋漓盡致,叫人不得不佩服C的強大和高效。不可否認的是,給編程人員太多自由空間必然使程式的安全性降低。va中,為了得到所有傳遞給函數的參數,需要用va_arg依次遍歷。其中存在兩個隱患:
1)如何確定參數的類型。
2)va_arg在類型檢查方面與其說非常靈活,不如說是很不負責,因為是強制類型轉換,va_arg都把當前指針所指向的內容強制轉換到指定類型;
3)2)結束標誌。如果沒有結束標誌的判斷,va將按默認類型依次返回記憶體中的內容,直到訪問到非法記憶體而出錯退出。例2中SqSum()求的是自然數的平方和,所以我把負數和0作為它的結束標誌。例如scanf把接收到的回車符作為結束標誌,大家熟知的printf()對字符串的處理用'\0'作為結束標誌,無法想像C中的字符串如果沒有'\0', 代碼將會是怎樣一番情景,估計那時最流行的可能是字符數組,或者是malloc/free。
4)允許對記憶體的隨意訪問,會留給不懷好意者留下攻擊的可能。當處理cracker精心設計好的一串字符串後,程式將跳轉到一些惡意代碼區域執行,以使cracker達到其攻擊目的。(常見的exploit攻擊)所以,必需禁止對記憶體的隨意訪問和嚴格控制記憶體訪問邊界。

2006年4月12日 星期三

Apache-Enabling a cgi directory to each user

Enabling a cgi directory for each user
In order to give each user their own cgi-bin directory, you can use a directive to make a particular subdirectory of a user's home directory cgi-enabled.
<Directory /home/*/public_html/cgi-bin/>Options ExecCGISetHandler cgi-script</Directory>

2006年4月4日 星期二

Linux Signal_4

.3.2 信號集及其操作
信號集的操作函數, sigsetops
============================================================= #include
int sigemptyset (sigset_t *set);
int sigfillset (sigset_t *set);
int sigaddset (sigset_t *set, int signum);
int sigdelset (sigset_t *set, int signum);
int sigismember (const sigset_t *set, int signum);
-------------------------------------------------------------------------------
上述函數的一種實現:
#define sigemptyset (ptr) ( *(ptr) = 0 )
#define sigfillset (ptr) ( *(ptr) = ~(sigset_t)0, 0 )
/* C 語言的逗號運算符: 返回右邊的值作為表達式的返回值 */
#include
#include
#define SIGBAD (signo) ((signo) <=0 (signo) >= NSIG)
int sigaddset (sigset_t *set, int signo)
{
if (SIGBAD (signo)) {errno = EINVAL; return -1;}
*set != 1 << (signo -1);
return 0;
}
int sigdelset (sigset_t *set, int signo)
{
if (SIGBAD (signo)) {errno = EINVAL; return -1;}
*set &= ~(1 << (signo -1));
return 0;
}
int sigismember (sigset_t *set, int signo)
{
if (SIGBAD (signo)) {errno = EINVAL; return -1;}
return ((*set & (1<< (signo - 1))) != 0);
}
=============================================================
7.3.3 可靠信號系統調用
sigaction, sigprocmask, sigpending, sigsuspend
============================================================= #include
int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);
int sigprocmask (int how, const sigset_t *set, sigset_t *oldset);
int sigpending (sigset_t *set);
int sigsuspend (const sigset_t *mask);
-------------------------------------------------------------------------------
struct sigaction {
void (*sa_handler) (int);
void (*sa_sigaction) (int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void); // 廢棄的元素, 不應使用
}
-------------------------------------------------------------------------------
sigaction:
* sigaction 用於改變進程在接收到信號時的動作.
* sigaction 可指定除 SIGKILL SIGSTOP 之外的信號的動作 (act->sa_handler),
可取 SIG_DFL, SIG_IGN 和用戶定義函數.
* sigaction 可指定在處理 signum 信號時, 應該阻塞的信號集 (act->sa_mask).
* sigaction 可指定信號處理過程中的行為 (act->sa_flags), 下述標誌 "" 的結果:
o SA_NOCLDSTOP: 子進程停止時不接收 SIGCHLD.
o SA_ONESHOT SA_RESETHAND: 重置信號動作為默認值.
o SA_RESTART: 如果該信號中斷慢系統調用, 則重新啟動系統調用.
o SA_NOMASK SA_NODEFER: 不要避免在信號處理函數中接收同一信號.
o SA_SIGINFO: 信號處理函數接受三個參數. 這時, 必須設置 act->sa_sigaction
元素, 其中 siginfo_t 是內核傳遞到信號處理函數中的發生信號時進程的狀態
信息. 詳細信息, 可參閱 sigaction(2) 手冊頁.
* oldact 非空時, 可返回先前的設置.
* 利用 SA_SIGINFO siginfo_t 實現有效的存儲管理.
-------------------------------------------------------------------------------
sigprocmask:
* sigprocmask 用於改變進程的當前阻塞信號集.
* how 可取 SIG_BLOCK, SIG_UNBLOCK SIG_MASKSET. 前兩個動作分別在當前阻塞
信號集中添加或刪除由 set 指定的信號集, SIG_MASK 用於完全設置阻塞信號集.
* oldset 非空時, 可返回先前的設置.
-------------------------------------------------------------------------------
sigpending:
* sigpending 用於檢驗掛起的信號.
-------------------------------------------------------------------------------
sigsuspend:
* sigsuspend 用於在接收到某個信號之前, 臨時用 mask 替換進程的信號掩碼,
暫停進程執行.
* sigsuspend 返回後將恢復調用之前的信號掩碼.
* 該系統調用始終返回 -1, 並將 errno 設置為 EINTR.
* 該系統調用實際是阻塞並暫停兩個動作的原子操作.
=============================================================保護關鍵代碼段示例
============================================================= #include
#include
#include
#include
#include
#include
static void sig_int (int);
void err_sys (const char* info)
{
perror (info);
exit (1);
}
void pr_mask (const char* str)
{
sigset_t sigset;
int errno_save;
errno_save = errno; /* this function may be called by signal handler */
if (sigprocmask (0, NULL, &sigset) < 0)
err_sys ("sigprocmask error");
printf ("%s", str);
if (sigismember (&sigset, SIGINT)) printf ("SIGINT ");
if (sigismember (&sigset, SIGQUIT)) printf ("SIGQUIT ");
if (sigismember (&sigset, SIGUSR1)) printf ("SIGUSR1 ");
if (sigismember (&sigset, SIGALRM)) printf ("SIGALRM ");
printf ("\n");
errno = errno_save;
}
int main (void)
{
sigset_t newmask, oldmask, zeromask;
if (signal (SIGINT, sig_int) == SIG_ERR)
err_sys ("signal (SIGINT) error");
sigemptyset (&zeromask);
sigemptyset (&newmask);
sigaddset (&newmask, SIGINT);
/* block SIGINT and save current signal mask */
if (sigprocmask (SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys ("SIG_BLOCK error");

/* critical region of code */
pr_mask ("in critical region: ");
/* allow all signals and pause */
if (sigsuspend (&zeromask) != -1)
err_sys ("sigsuspend error");
pr_mask ("after return from sigsuspend: ");
/* reset signal mask whick unblocks SIGINT */
if (sigprocmask (SIG_SETMASK, &oldmask, NULL) < 0)
err_sys ("SIG_SETMASK error");
/* and continue processing ... */
exit (0);
}
static void sig_int (int signo)
{
pr_mask ("\nin sig_int: ");
return;
}
-------------------------------------------------------------------------------
* Linux , 必須包含頭文件:
#include
以便使用可靠的 signal 函數. 或者使用 sigaction 函數.
=============================================================7.3.4 sigsetjmp siglongjmp
sigsetjmp siglongjmp 專用於從信號處理函數中進行遠跳轉
並能夠明確指定是否保存信號掩碼
============================================================= #include
int sigsetjmp (sigjmp_buf env, int savesigs);
void siglongjmp (sigjmp_buf env, int val);
-------------------------------------------------------------------------------
* 如果 savesigs 非零, siglongjmp 在跳轉之後將恢復保存的信號掩碼.
=============================================================

Linux Signal_3

一、信號生命週期
從信號發送到信號處理函數的執行完畢
對於一個完整的信號生命週期(從信號發送到相應的處理函數執行完畢)來說,可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:信號誕生;信號在進程中註冊完畢;信號在進程中的註銷完畢;信號處理函數執行完畢。相鄰兩個事件的時間間隔構成信號生命週期的一個階段。
下面闡述四個事件的實際意義:
信號"誕生"。信號的誕生指的是觸發信號的事件發生(如檢測到硬件異常、定時器超時以及調用信號發送函數kill()sigqueue()等)。
信號在目標進程中"註冊";進程的task_struct結構中有關於本進程中未決信號的數據成員:
struct sigpending pending
struct sigpending{
struct sigqueue *head, **tail;
sigset_t signal;
};
第三個成員是進程中所有未決信號集,第一、第二個成員分別指向一個sigqueue類型的結構鏈(稱之為"未決信號信息鏈")的首尾,信息鏈中的每個sigqueue結構刻畫一個特定信號所攜帶的信息,並指向下一個sigqueue結構:
struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
信號在進程中註冊指的就是信號值加入到進程的未決信號集中(sigpending結構的第二個成員sigset_t signal),並且信號所攜帶的信息被保留到未決信號信息鏈的某個sigqueue結構中。只要信號在進程的未決信號集中,表明進程已經知道這些信號的存在,但還沒來得及處理,或者該信號被進程阻塞。
註:
當一個實時信號發送給一個進程時,不管該信號是否已經在進程中註冊,都會被再註冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。這意味著同一個實時信號可以在同一個進程的未決信號信息鏈中佔有多個sigqueue結構(進程每收到一個實時信號,都會為它分配一個結構來登記該信號信息,並把該結構添加在未決信號鏈尾,即所有誕生的實時信號都會在目標進程中註冊);
當一個非實時信號發送給一個進程時,如果該信號已經在進程中註冊,則該信號將被丟棄,造成信號丟失。因此,非實時信號又叫做"不可靠信號"。這意味著同一個非實時信號在進程的未決信號信息鏈中,至多佔有一個sigqueue結構(一個非實時信號誕生後,(1)、如果發現相同的信號已經在目標結構中註冊,則不再註冊,對於進程來說,相當於不知道本次信號發生,信號丟失;(2)、如果進程的未決信號中沒有相同信號,則在進程中註冊自己)。
信號在進程中的註銷。在目標進程執行過程中,會檢測是否有信號等待處理(每次從系統空間返回到用戶空間時都做這樣的檢查)。如果存在未決信號等待處理且該信號沒有被進程阻塞,則在運行相應的信號處理函數前,進程會把信號在未決信號鏈中佔有的結構卸掉。是否將信號從進程未決信號集中刪除對於實時與非實時信號是不同的。對於非實時信號來說,由於在未決信號信息鏈中最多只佔用一個sigqueue結構,因此該結構被釋放後,應該把信號在進程未決信號集中刪除(信號註銷完畢);而對於實時信號來說,可能在未決信號信息鏈中佔用多個sigqueue結構,因此應該針對佔用sigqueue結構的數目區別對待:如果只佔用一個sigqueue結構(進程只收到該信號一次),則應該把信號在進程的未決信號集中刪除(信號註銷完畢)。否則,不應該在進程的未決信號集中刪除該信號(信號註銷完畢)。
進程在執行信號相應處理函數之前,首先要把信號在進程中註銷。
信號生命終止。進程註銷信號後,立即執行相應的信號處理函數,執行完畢後,信號的本次發送對進程的影響徹底結束。
註:
1)信號註冊與否,與發送信號的函數(如kill()sigqueue()等)以及信號安裝函數(signal()sigaction())無關,只與信號值有關(信號值小於SIGRTMIN的信號最多只註冊一次,信號值在SIGRTMINSIGRTMAX之間的信號,只要被進程接收到就被註冊)。
2)在信號被註銷到相應的信號處理函數執行完畢這段時間內,如果進程又收到同一信號多次,則對實時信號來說,每一次都會在進程中註冊;而對於非實時信號來說,無論收到多少次信號,都會視為只收到一個信號,只在進程中註冊一次。
二、信號編程注意事項
防止不該丟失的信號丟失。如果對八中所提到的信號生命週期理解深刻的話,很容易知道信號會不會丟失,以及在哪裡丟失。
程序的可移植性
考慮到程序的可移植性,應該盡量採用POSIX信號函數,POSIX信號函數主要分為兩類:
POSIX 1003.1信號函數: Kill()sigaction()sigaddset()sigdelset()sigemptyset()sigfillset()sigismember()sigpending()sigprocmask()sigsuspend()
POSIX 1003.1b信號函數。POSIX 1003.1b在信號的實時性方面對POSIX 1003.1做了擴展,包括以下三個函數: sigqueue()sigtimedwait()sigwaitinfo()。其中,sigqueue主要針對信號發送,而sigtimedwaitsigwaitinfo()主要用於取代sigsuspend()函數,後面有相應實例。
#include <signal.h>
int sigwaitinfo(sigset_t *set, siginfo_t *info).
該函數與sigsuspend()類似,阻塞一個進程直到特定信號發生,但信號到來時不執行信號處理函數,而是返回信號值。因此為了避免執行相應的信號處理函數,必須在調用該函數前,使進程屏蔽掉set指向的信號,因此調用該函數的典型代碼是:
sigset_t newmask;
int rcvd_sig;
siginfo_t info;
sigemptyset(&newmask);
sigaddset(&newmask, SIGRTMIN);
sigprocmask(SIG_BLOCK, &newmask, NULL);
rcvd_sig = sigwaitinfo(&newmask, &info)
if (rcvd_sig == -1) {
..
}
調用成功返回信號值,否則返回-1sigtimedwait()功能相似,只不過增加了一個進程等待的時間。
程序的穩定性。
為了增強程序的穩定性,在信號處理函數中應使用可重入函數。
信號處理程序中應當使用可再入(可重入)函數(註:所謂可重入函數是指一個可以被多個任務調用的過程,任務在調用時不必擔心數據是否會出錯)。因為進程在收到信號後,就將跳轉到信號處理函數去接著執行。如果信號處理函數中使用了不可重入函數,那麼信號處理函數可能會修改原來進程中不應該被修改的數據,這樣進程從信號處理函數中返回接著執行時,可能會出現不可預料的後果。不可再入函數在信號處理函數中被視為不安全函數。
滿足下列條件的函數多數是不可再入的:
1)使用靜態的數據結構,如getlogin()gmtime()getgrgid()getgrnam()getpwuid()以及getpwnam()等等;
2)函數實現時,調用了malloc()或者free()函數;
3)實現時使用了標準I/O函數的。The Open Group視下列函數為可再入的:
_exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、cfsetispeed()、cfsetospeed()、chdir()、chmod()、chown()、close()、creat()、dup()、dup2()、execle()、execve()、fcntl()、fork()、fpathconf()、fstat()、fsync()、getegid()、 geteuid()、getgid()、getgroups()、getpgrp()、getpid()、getppid()、getuid()、kill()、link()、lseek()、mkdir()、mkfifo()、 open()、pathconf()、pause()、pipe()、raise()、read()、rename()、rmdir()、setgid()、setpgid()、setsid()、setuid()、 sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、signal()、sigpending()、sigprocmask()、sigsuspend()、sleep()、stat()、sysconf()、tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、 umask()、uname()、unlink()、utime()、wait()、waitpid()、write()。
即使信號處理函數使用的都是"安全函數",同樣要注意進入處理函數時,首先要保存errno的值,結束時,再恢復原值。因為,信號處理過程中,errno值隨時可能被改變。另外,longjmp()以及siglongjmp()沒有被列為可再入函數,因為不能保證緊接著兩個函數的其它調用是安全的。
三、深入淺出:信號應用實例
linux下的信號應用並沒有想像的那麼恐怖,程序員所要做的最多只有三件事情:
安裝信號(推薦使用sigaction());
實現三參數信號處理函數,handler(int signal,struct siginfo *info, void *)
發送信號,推薦使用sigqueue()
實際上,對有些信號來說,只要安裝信號就足夠了(信號處理方式採用缺省或忽略)。其他可能要做的無非是與信號集相關的幾種操作。
實例一:信號發送及處理
實現一個信號接收程序sigreceive(其中信號安裝由sigaction())。
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=new_op;
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d", signum);
sleep(5);
}
說明,命令行參數為信號值,後台運行sigreceive signo &,可獲得該進程的ID,假設為pid,然後再另一終端上運行kill -s signo pid驗證信號的發送接收及處理。同時,可驗證信號的排隊問題。
註:可以用sigqueue實現一個命令行信號發送程序sigqueuesend,見附錄1
實例二:信號傳遞附加信息
主要包括兩個實例:
向進程本身發送信號,並傳遞指針參數;
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
union sigval mysigval;
int i;
int sig;
pid_t pid;
char data[10];
memset(data,0,sizeof(data));
for(i=0;i < 5;i++)
data[i]='2';
mysigval.sival_ptr=data;
sig=atoi(argv[1]);
pid=getpid();
sigemptyset(&act.sa_mask);
act.sa_sigaction=new_op;//三參數信號處理函數
act.sa_flags=SA_SIGINFO;//信息傳遞開關
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
sigqueue(pid,sig,mysigval);//向本進程發送信號,並傳遞附加信息
}
}
void new_op(int signum,siginfo_t *info,void *myact)//三參數信號處理函數的實現
{
int i;
for(i=0;i<10;i++)
{
printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));
}
printf("handle signal %d over;",signum);
}
這個例子中,信號實現了附加信息的傳遞,信號究竟如何對這些信息進行處理則取決於具體的應用。
2、 不同進程間傳遞整型參數:把1中的信號發送和接收放在兩個程序中,並且在發送過程中傳遞整型參數。
信號接收程序:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
pid_t pid;
pid=getpid();
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_sigaction=new_op;
act.sa_flags=SA_SIGINFO;
if(sigaction(sig,&act,NULL)<0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("the int value is %d \n",info->si_int);
}
信號發送程序:命令行第二個參數為信號值,第三個參數為接收進程ID
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/types.h>
main(int argc,char**argv)
{
pid_t pid;
int signum;
union sigval mysigval;
signum=atoi(argv[1]);
pid=(pid_t)atoi(argv[2]);
mysigval.sival_int=8;//不代表具體含義,只用於說明問題
if(sigqueue(pid,signum,mysigval)==-1)
printf("send error\n");
sleep(2);
}
註:實例2的兩個例子側重點在於用信號來傳遞信息,目前關於在linux下通過信號傳遞信息的實例非常少,倒是Unix下有一些,但傳遞的基本上都是關於傳遞一個整數,傳遞指針的我還沒看到。我一直沒有實現不同進程間的指針傳遞(實際上更有意義),也許在實現方法上存在問題吧,請實現者email我。
實例三:信號阻塞及信號集操作
#include "signal.h"
#include "unistd.h"
static void my_op(int);
main()
{
sigset_t new_mask,old_mask,pending_mask;
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void*)my_op;
if(sigaction(SIGRTMIN+10,&act,NULL))
printf("install signal SIGRTMIN+10 error\n");
sigemptyset(&new_mask);
sigaddset(&new_mask,SIGRTMIN+10);
if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))
printf("block signal SIGRTMIN+10 error\n");
sleep(10);
printf("now begin to get pending mask and unblock SIGRTMIN+10\n");
if(sigpending(&pending_mask)<0)
printf("get pending mask error\n");
if(sigismember(&pending_mask,SIGRTMIN+10))
printf("signal SIGRTMIN+10 is pending\n");
if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)
printf("unblock signal error\n");
printf("signal unblocked\n");
sleep(10);
}
static void my_op(int signum)
{
printf("receive signal %d \n",signum);
}
編譯該程序,並以後台方式運行。在另一終端向該進程發送信號(運行kill -s 42 pidSIGRTMIN+1042),查看結果可以看出幾個關鍵函數的運行機制,信號集相關操作比較簡單。
註:在上面幾個實例中,使用了printf()函數,只是作為診斷工具,pringf()函數是不可重入的,不應在信號處理函數中使用。
sigqueue實現的命令行信號發送程序sigqueuesend,命令行第二個參數是發送的信號值,第三個參數是接收該信號的進程ID,可以配合實例一使用:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char**argv)
{
pid_t pid;
int sig;
sig=atoi(argv[1]);
pid=atoi(argv[2]);
sigqueue(pid,sig,NULL);
sleep(2);
}

Linux Signal_2

linux信號機制遠遠比想像的複雜,本文力爭用最短的篇幅,對該機制做了深入細緻的分析。信號應用實例將在信號(下)中給出。
一、信號及信號來源
信號本質
信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什麼時候到達。
信號是進程間通信機制中唯一的異步通信機制,可以看作是異步通知,通知接收信號的進程有哪些事情發生了。信號機制經過POSIX實時擴展後,功能更加強大,除了基本通知功能外,還可以傳遞附加信息。
信號來源
信號事件的發生有兩個來源:硬件來源(比如我們按下了鍵盤或者其它硬件故障);軟件來源,最常用發送信號的系統函數是kill, raise, alarmsetitimer以及sigqueue函數,軟件來源還包括一些非法運算等操作。
二、信號的種類
可以從兩個不同的分類角度對信號進行分類:(1)可靠性方面:可靠信號與不可靠信號;(2)與時間的關係上:實時信號與非實時信號。在《Linux環境進程間通信(一):管道及有名管道》的附1中列出了系統所支持的所有信號。
1、可靠信號與不可靠信號
"不可靠信號"
Linux信號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信號機制比較簡單和原始,後來在實踐中暴露出一些問題,因此,把那些建立在早期機制上的信號叫做"不可靠信號",信號值小於SIGRTMIN(Red hat 7.2中,SIGRTMIN=32SIGRTMAX=63)的信號都是不可靠信號。這就是"不可靠信號"的來源。它的主要問題是:
進程每次處理信號後,就將對信號的響應設置為默認動作。在某些情況下,將導致對信號的錯誤處理;因此,用戶如果不希望這樣的操作,那麼就要在信號處理函數結尾再一次調用signal(),重新安裝該信號。
信號可能丟失,後面將對此詳細闡述。
因此,早期unix下的不可靠信號主要指的是進程可能對信號做出錯誤的反應以及信號可能丟失。
Linux支持不可靠信號,但是對不可靠信號機制做了改進:在調用完信號處理函數後,不必重新調用該信號的安裝函數(信號安裝函數是在可靠機制上的實現)。因此,Linux下的不可靠信號問題主要指的是信號可能丟失。
"可靠信號"
隨著時間的發展,實踐證明了有必要對信號的原始機制加以改進和擴充。所以,後來出現的各種Unix版本分別在這方面進行了研究,力圖實現"可靠信號"。由於原來定義的信號已有許多應用,不好再做改動,最終只好又新增加了一些信號,並在一開始就把它們定義為可靠信號,這些信號支持排隊,不會丟失。同時,信號的發送和安裝也出現了新版本:信號發送函數sigqueue()及信號安裝函數sigaction()POSIX.4對可靠信號機制做了標準化。但是,POSIX只對可靠信號機制應具有的功能以及信號機制的對外接口做了標準化,對信號機制的實現沒有作具體的規定。
信號值位於SIGRTMINSIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的信號安裝函數sigation()以及信號發送函數sigqueue()的同時,仍然支持早期的signal()信號安裝函數,支持信號發送函數kill()
註:不要有這樣的誤解:由sigqueue()發送、sigaction安裝的信號就是可靠的。事實上,可靠信號是指後來添加的新信號(信號值位於SIGRTMINSIGRTMAX之間);不可靠信號是信號值小於SIGRTMIN的信號。信號的可靠與不可靠只與信號值有關,與信號的發送及安裝函數無關。目前linux中的signal()是通過sigation()函數實現的,因此,即使通過signal()安裝的信號,在信號處理函數的結尾也不必再調用一次信號安裝函數。同時,由signal()安裝的實時信號支持排隊,同樣不會丟失。
對於目前linux的兩個信號安裝函數:signal()sigaction()來說,它們都不能把SIGRTMIN以前的信號變成可靠信號(都不支持排隊,仍有可能丟失,仍然是不可靠信號),而且對SIGRTMIN以後的信號都支持排隊。這兩個函數的最大區別在於,經過sigaction安裝的信號都能傳遞信息給信號處理函數(對所有信號這一點都成立),而經過signal安裝的信號卻不能向信號處理函數傳遞信息。對於信號發送函數來說也是一樣的。
2、實時信號與非實時信號
早期Unix系統只定義了32種信號,Ret hat7.2支持64種信號,編號0-63(SIGRTMIN=31SIGRTMAX=63),將來可能進一步增加,這需要得到內核的支持。前32種信號已經有了預定義值,每個信號有了確定的用途及含義,並且每種信號都有各自的缺省動作。如按鍵盤的CTRL ^C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。後32個信號表示實時信號,等同於前面闡述的可靠信號。這保證了發送的多個實時信號都被接收。實時信號是POSIX標準的一部分,可用於應用進程。
非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。
三、進程對信號的響應
進程可以通過三種方式來響應一個信號:(1)忽略信號,即對信號不做任何處理,其中,有兩個信號不能忽略:SIGKILLSIGSTOP;(2)捕捉信號。定義信號處理函數,當信號發生時,執行相應的處理函數;(3)執行缺省操作,Linux對每種信號都規定了默認操作,詳細情況請參考[2]以及其它資料。注意,進程對實時信號的缺省反應是進程終止。
Linux究竟採用上述三種方式的哪一個來響應信號,取決於傳遞給相應API函數的參數。
四、信號的發送
發送信號的主要函數有:kill()raise()sigqueue()alarm()setitimer()以及abort()
1kill()
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int signo)
參數pid的值 信號的接收進程
pid>0 進程IDpid的進程
pid=0 同一個進程組的進程
pid<0 pid!=-1 進程組ID-pid的所有進程
pid=-1 除發送進程自身外,所有進程ID大於1的進程
Sinno是信號值,當為0時(即空信號),實際不發送任何信號,但照常進行錯誤檢查,因此,可用於檢查目標進程是否存在,以及當前進程是否具有向目標發送信號的權限(root權限的進程可以向任何進程發送信號,非root權限的進程只能向屬於同一個session或者同一個用戶的進程發送信號)。
Kill()最常用於pid>0時的信號發送,調用成功返回 0; 否則,返回 -1。註:對於pid<0時的情況,對於哪些進程將接受信號,各種版本說法不一,其實很簡單,參閱內核源碼kernal/signal.c即可,上表中的規則是參考red hat 7.2
2raise()
#include <signal.h>
int raise(int signo)
向進程本身發送信號,參數為即將發送的信號值。調用成功返回 0;否則,返回 -1
3sigqueue()
#include <sys/types.h>
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval val)
調用成功返回 0;否則,返回 -1
sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(當然也支持前32種),支持信號帶有參數,與函數sigaction()配合使用。
sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是一個聯合數據結構union sigval,指定了信號傳遞的參數,即通常所說的4字節值。
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
sigqueue()kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。如果signo=0,將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用於檢查pid的有效性以及當前進程是否有權限向目標進程發送信號。
在調用sigqueue時,sigval_t指定的信息會拷貝到3參數信號處理函數(3參數信號處理函數指的是信號處理函數由sigaction安裝,並設定了sa_sigaction指針,稍後將闡述)的siginfo_t結構中,這樣信號處理函數就可以處理這些信息了。由於sigqueue系統調用支持發送帶參數信號,所以比kill()系統調用的功能要靈活和強大得多。
註:sigqueue()發送非實時信號時,第三個參數包含的信息仍然能夠傳遞給信號處理函數; sigqueue()發送非實時信號時,仍然不支持排隊,即在信號處理函數執行過程中到來的所有相同信號,都被合併為一個信號。
4alarm()
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
專門為SIGALRM信號而設,在指定的時間seconds秒後,將向進程本身發送SIGALRM信號,又稱為鬧鐘時間。進程調用alarm後,任何以前的alarm()調用都將無效。如果參數seconds為零,那麼進程內將不再包含任何鬧鐘時間。
返回值,如果調用alarm()前,進程中已經設置了鬧鐘時間,則返回上一個鬧鐘時間的剩餘時間,否則返回0
5setitimer()
#include <sys/time.h>
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
setitimer()alarm功能強大,支持3種類型的定時器:
ITIMER_REAL: 設定絕對時間;經過指定的時間後,內核將發送SIGALRM信號給本進程;
ITIMER_VIRTUAL 設定程序執行時間;經過指定的時間後,內核將發送SIGVTALRM信號給本進程;
ITIMER_PROF 設定進程執行以及內核因本進程而消耗的時間和,經過指定的時間後,內核將發送ITIMER_VIRTUAL信號給本進程;
Setitimer()第一個參數which指定定時器類型(上面三種之一);第二個參數是結構itimerval的一個實例,結構itimerval形式見附錄1。第三個參數可不做處理。
Setitimer()調用成功返回0,否則返回-1
6abort()
#include <stdlib.h>
void abort(void);
向進程發送SIGABORT信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數。即使SIGABORT被進程設置為阻塞信號,調用abort()後,SIGABORT仍然能被進程接收。該函數無返回值。
五、信號的安裝(設置信號關聯動作)
如果進程要處理某一信號,那麼就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值的動作之間的映射關係,即進程將要處理哪個信號;該信號被傳遞給進程時,將執行何種操作。
linux主要有兩個函數實現信號的安裝:signal()sigaction()。其中signal()在可靠信號系統調用的基礎上實現, 是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優於signal()主要體現在支持信號帶有參數。
1signal()
#include <signal.h>
void (*signal(int signum, void (*handler) (int)))(int);
如果該函數原型不容易理解的話,可以參考下面的分解方式來理解:
typedef void (*sighandler_t)(int)
sighandler_t signal(int signum, sighandler_t handler));
第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,可以忽略該信號(參數設為SIG_IGN);可以採用系統默認方式處理信號(參數設為SIG_DFL);也可以自己實現處理方式(參數指定一個函數地址)
如果signal()調用成功,返回最後一次為安裝信號signum而調用signal()時的handler值;失敗則返回SIG_ERR
2sigaction()
#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
sigaction函數用於改變進程接收到特定信號後的行為。該函數的第一個參數為信號的值,可以為除SIGKILLSIGSTOP外的任何一個特定有效的信號(為這兩個信號定義自己的處理函數,將導致信號安裝錯誤)。第二個參數是指向結構sigaction的一個實例的指針,在結構sigaction的實例中,指定了對特定信號的處理,可以為空,進程會以缺省方式對信號處理;第三個參數oldact指向的對象用來保存原來對相應信號的處理,可指定oldactNULL。如果把第二、第三個參數都設為NULL,那麼該函數可用於檢查信號的有效性。
第二個參數最為重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程中應屏蔽掉哪些函數等等。
sigaction結構定義如下:
struct sigaction {
union{
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int,struct siginfo *, void *)
}_u
sigset_t sa_mask
unsigned long sa_flags
void (*sa_restorer)(void)
}

其中,sa_restorer,已過時,POSIX不支持它,不應再被使用。
1、聯合數據結構中的兩個元素_sa_handler以及*_sa_sigaction指定信號關聯函數,即用戶指定的信號處理函數。除了可以是用戶自定義的處理函數外,還可以為SIG_DFL(採用缺省的處理方式),也可以為SIG_IGN(忽略信號)。
2、由_sa_handler指定的處理函數只有一個參數,即信號值,所以信號不能傳遞除信號值之外的任何信息;由_sa_sigaction是指定的信號處理函數帶有三個參數,是為實時信號而設的(當然同樣支持非實時信號),它指定一個3參數信號處理函數。第一個參數為信號值,第三個參數沒有使用(posix沒有規範使用該參數的標準),第二個參數是指向siginfo_t結構的指針,結構中包含信號攜帶的數據值,參數所指向的結構如下:
siginfo_t {
int si_signo; /* 信號值,對所有信號有意義*/
int si_errno; /* errno值,對所有信號有意義*/
int si_code; /* 信號產生的原因,對所有信號有意義*/
union{ /* 聯合數據結構,不同成員適應不同信號 */
//確保分配足夠大的存儲空間
int _pad[SI_PAD_SIZE];
//SIGKILL有意義的結構
struct{
...
}...

... ...
... ...
//SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結構
struct{
...
}...
... ...
}
}

註:為了更便於閱讀,在說明問題時常把該結構表示為附錄2所表示的形式。
siginfo_t結構中的聯合數據成員確保該結構適應所有的信號,比如對於實時信號來說,則實際採用下面的結構形式:
typedef struct {
int si_signo;
int si_errno;
int si_code;
union sigval si_value;
} siginfo_t;

結構的第四個域同樣為一個聯合數據結構:
union sigval {
int sival_int;
void *sival_ptr;
}
採用聯合數據結構,說明siginfo_t結構中的si_value要麼持有一個4字節的整數值,要麼持有一個指針,這就構成了與信號相關的數據。在信號的處理函數中,包含這樣的信號相關數據指針,但沒有規定具體如何對這些數據進行操作,操作方法應該由程序開發人員根據具體任務事先約定。
前面在討論系統調用sigqueue發送信號時,sigqueue的第三個參數就是sigval聯合數據結構,當調用sigqueue時,該數據結構中的數據就將拷貝到信號處理函數的第二個參數中。這樣,在發送信號同時,就可以讓信號傳遞一些附加信息。信號可以傳遞信息對程序開發是非常有意義的。
信號參數的傳遞過程可圖示如下:
3sa_mask指定在信號處理程序執行過程中,哪些信號應當被阻塞。缺省情況下當前信號本身被阻塞,防止信號的嵌套發送,除非指定SA_NODEFER或者SA_NOMASK標誌位。
註:請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數執行過程中由sa_mask指定的信號才被阻塞。
4sa_flags中包含了許多標誌位,包括剛剛提到的SA_NODEFERSA_NOMASK標誌位。另一個比較重要的標誌位是SA_SIGINFO,當設定了該標誌位時,表示信號附帶的參數可以被傳遞到信號處理函數中,因此,應該為sigaction結構中的sa_sigaction指定處理函數,而不應該為sa_handler指定信號處理函數,否則,設置該標誌變得毫無意義。即使為sa_sigaction指定了信號處理函數,如果不設置SA_SIGINFO,信號處理函數同樣不能得到信號傳遞過來的數據,在信號處理函數中對這些信息的訪問都將導致段錯誤(Segmentation fault)。
註:很多文獻在闡述該標誌位時都認為,如果設置了該標誌位,就必須定義三參數信號處理函數。實際不是這樣的,驗證方法很簡單:自己實現一個單一參數信號處理函數,並在程序中設置該標誌位,可以察看程序的運行結果。實際上,可以把該標誌位看成信號是否傳遞參數的開關,如果設置該位,則傳遞參數;否則,不傳遞參數。
六、信號集及信號集操作函數:
信號集被定義為一種數據類型:
typedef struct {
unsigned long sig[_NSIG_WORDS]
} sigset_t
信號集用來描述信號的集合,linux所支持的所有信號可以全部或部分的出現在信號集中,主要與信號阻塞相關函數配合使用。下面是為信號集操作定義的相關函數:
#include <signal.h>
int sigemptyset(sigset_t *set)
int sigfillset(sigset_t *set)
int sigaddset(sigset_t *set, int signum)
int sigdelset(sigset_t *set, int signum)
int sigismember(const sigset_t *set, int signum)
sigemptyset(sigset_t *set)初始化由set指定的信號集,信號集裡面的所有信號被清空;
sigfillset(sigset_t *set)調用該函數後,set指向的信號集中將包含linux支持的64種信號;
sigaddset(sigset_t *set, int signum)set指向的信號集中加入signum信號;
sigdelset(sigset_t *set, int signum)set指向的信號集中刪除signum信號;
sigismember(const sigset_t *set, int signum)判定信號signum是否在set指向的信號集中。
七、信號阻塞與信號未決:
每個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的所有信號在遞送到進程後都將被阻塞。下面是與信號阻塞相關的幾個函數:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset))
int sigpending(sigset_t *set));
int sigsuspend(const sigset_t *mask))
sigprocmask()函數能夠根據參數how來實現對信號集的操作,操作主要有三種:
參數how 進程當前信號集
SIG_BLOCK 在進程當前阻塞信號集中添加set指向信號集中的信號
SIG_UNBLOCK 如果進程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞
SIG_SETMASK 更新進程阻塞信號集為set指向的信號集
sigpending(sigset_t *set))獲得當前已遞送到進程,卻被阻塞的所有信號,在set指向的信號集中返回結果。
sigsuspend(const sigset_t *mask))用於在接收到某個信號之前, 臨時用mask替換進程的信號掩碼, 並暫停進程執行,直到收到信號為止。sigsuspend 返回後將恢復調用之前的信號掩碼。信號處理函數完成後,進程將繼續執行。該系統調用始終返回-1,並將errno設置為EINTR
附錄1:結構itimerval
struct itimerval {
struct timeval it_interval; /* next value */
struct timeval it_value; /* current value */
};
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
附錄2:三參數信號處理函數中第二個參數的說明性描述:
siginfo_t {
int si_signo; /* 信號值,對所有信號有意義*/
int si_errno; /* errno值,對所有信號有意義*/
int si_code; /* 信號產生的原因,對所有信號有意義*/
pid_t si_pid; /* 發送信號的進程ID,kill(2),實時信號以及SIGCHLD有意義 */
uid_t si_uid; /* 發送信號進程的真實用戶ID,對kill(2),實時信號以及SIGCHLD有意義 */
int si_status; /* 退出狀態,對SIGCHLD有意義*/
clock_t si_utime; /* 用戶消耗的時間,對SIGCHLD有意義 */
clock_t si_stime; /* 內核消耗的時間,對SIGCHLD有意義 */
sigval_t si_value; /* 信號值,對所有實時有意義,是一個聯合數據結構,可以為一個整數(由si_int標示,也可以為一個指針,由si_ptr標示)*/

void * si_addr; /* 觸發fault的內存地址,對SIGILL,SIGFPE,SIGSEGV,SIGBUS 信號有意義*/
int si_band; /* SIGPOLL信號有意義 */
int si_fd; /* SIGPOLL信號有意義 */
}
實際上,除了前三個元素外,其他元素組織在一個聯合結構中,在聯合數據結構中,又根據不同的信號組織成不同的結構。註釋中提到的對某種信號有意義指的是,在該信號的處理函數中可以訪問這些域來獲得與信號相關的有意義的信息,只不過特定信號只對特定信息感興趣而已。

Linux Singal_1

信號是進程之間互傳消息的一種方法俗稱軟件中斷。很多比較重要的應用程序都需處理信號。信號提供了一種

處理異步事件的方法:終端用戶鍵入中斷鍵,則會通過信號機構停止一個程序。所以,信號可以說是進程控制的一部分。
Redhat 7.3kill -l 得到
CODE
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
…….
信號出現在 UNIX 的早期版本中 ,但早期的信號模型是不可靠的, 信號可能被丟失,
也很難處理關鍵段。UNIX 的兩個重要分支 BSD System V 分別對早期的信號進行了擴展 ,
但這兩個系統的擴展並不兼容POSIX 統一了這兩種實現, 最終提供了可靠的信號模型。
信號的產生條件
. 當用戶按某些終端鍵時,產生信號。
. 硬件異常產生信號:除數為0、無效的存儲訪問等等。
. 進程用kill函數可將信號發送給另一個進程或進程組
. 用戶可用kill命令將信號發送給其他進程
. 當檢測到某種軟件條件已經發生,並將其通知有關進程時也產生信號。
接到信號的處理辦法
1,忽略,但是SIGKILLSIGSTOP不能忽略。
2,捕捉。
3,執行系統的默認處理。
信號的分類
非可靠信號:早期unix下的不可靠信號主要指的是進程可能對信號做出錯誤的反應
以及信號可能丟失。
可靠信號: 信號值位於SIGRTMINSIGRTMAX之間的信號都是可靠信號,可靠信號
克服了信號可能丟失的問題。
信號的可靠與不可靠只與信號值有關,與信號的發送及安裝函數無關。
當然也可以稱為實時信號或者非實時信號,非實時信號都不支持排隊,都是不可靠信號;

實時信號都支持排隊,都是可靠信號。
這裡我說一下信號的生命週期,對於理解信號的分類有很大的幫助。
從信號發送到信號處理函數的執行完畢。對於一個完整的信號生命週期(從信號發送到相應的處
理函數執行完畢)來說,可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:
信號產生;信號在進程中註冊完畢;信號在進程中的註銷完畢;信號處理函數執行完畢。
相鄰兩個事件的時間間隔構成信號生命週期的一個階段。當一個實時信號發送給一個進程時,不管該信號是否已經在進程中註冊,都會被再註冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。這意味著同一個實時信號可以在同一個進程的未決信號信息鏈中佔有多個sigqueue結構(進程每收到一個實時信號,都會為它分配一個結構來登記該信號信息,並把該結構添加在未決信號鏈尾,即所有誕生的實時信號都會在目標進程中註冊);
當一個非實時信號發送給一個進程時,如果該信號已經在進程中註冊,則該信號將被丟棄,造成信號丟失。
因此,非實時信號又叫做"不可靠信號"。這意味著同一個非實時信號在進程的未決信號信息鏈中,至多佔有一個
sigqueue結構(一個非實時信號產生後,1如果發現相同的信號已經在目標結構中註冊,則不再註冊,對於
進程來說,相當於不知道本次信號發生,信號丟失;2如果進程的未決信號中沒有相同信號,則在進程中註冊自己)。
需要注意的要點是:
1)信號註冊與否,與發送信號的函數(如kill()sigqueue()等)以及信號安裝函數(signal()sigaction())無關,
只與信號值有關(信號值小於SIGRTMIN的信號最多只註冊一次,信號值在SIGRTMINSIGRTMAX之間的信號,只要被進程接收到就被註冊)。
2)在信號被註銷到相應的信號處理函數執行完畢這段時間內,如果進程又收到同一信號多次,則對實時信號來說,
每一次都會在進程中註冊;而對於非實時信號來說,無論收到多少次信號,都會視為只收到一個信號,只在進程中註冊一次。
當然還有有些需要知道的概念比如 低速系統調用,中斷系統調用,可重入函數等等概念,請查閱環境高級編程。
一些概念性的東西大概就這些東西吧,下面就分類介紹一下具體的函數。
我下面介紹的,如果沒有特殊說明,都是對於linux上的實現,當然都是符合POSIX標準嘍。


,函數詳細介紹
A:信號的發送
發送信號的主要函數有:kill(),raise(),sigqueue(),alarm(),setitimer()以及abort()
1, int kill(pid_t pid,int signo)
用到的頭文件:
#include <sys/types.h>
#include <signal.h>
參數pid的值 信號的接收進程
pid>0 進程IDpid的進程
pid=0 同一個進程組的進程
pid<0 pid!=-1 進程組ID-pid的所有進程
pid=-1 除發送進程自身外,所有進程ID大於1的進程
Sinno是信號值,當為0時(即空信號),實際不發送任何信號,但照常進行錯誤檢查,因此,可用於檢查目標進程是否存在,
以及當前進程是否具有向目標發送信號的權限(root權限的進程可以向任何進程發送信號,非root權限的進程只能向屬於同
一個session或者同一個用戶的進程發送信號)。
kill最常用於pid>0時的信號發送,調用成功返回 0; 否則,返回 -1。註:對於pid<0時的情況,對於哪些進程將接受信號,
各種版本說法不一,其實很簡單,參閱內核源碼kernal/signal.c
2, int raise(int signo)
用到的頭文件:
#include <signal.h>
向進程本身發送信號,參數為即將發送的信號值。調用成功返回 0;否則,返回 -1
3, int sigqueue(pid_t pid, int sig, const union sigval val)
用到的頭文件:
#include <sys/types.h>
#include <signal.h>
調用成功返回 0;否則,返回 -1
sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(當然也支持前32種),支持信號帶有參數,
與函數sigaction()配合使用。sco 5.05沒有此函數。
sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是一個聯合數據結構union sigval
指定了信號傳遞的參數,即通常所說的4字節值。
typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;
sigqueue()kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。如果sig0
將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用於檢查pid的有效性以及當前進程是否有權限向目標進程發送信號。
在調用sigqueue時,sigval_t指定的信息會拷貝到3參數信號處理函數(3參數信號處理函數指的是信號處理函數由sigaction安裝,
並設定了sa_sigaction指針,稍後將闡述)的siginfo_t結構中,這樣信號處理函數就可以處理這些信息了。由於sigqueue系統調用
支持發送帶參數信號,所以比kill()系統調用的功能要靈活和強大得多。
註:sigqueue()發送非實時信號時,第三個參數包含的信息仍然能夠傳遞給信號處理函數; sigqueue()發送非實時信號時,仍然不支持排隊,
即在信號處理函數執行過程中到來的所有相同信號,都被合併為一個信號。
4, unsigned int alarm(unsigned int seconds)
用到的頭文件:
#include <unistd.h>
專門為SIGALRM信號而設,在指定的時間seconds秒後,將向進程本身發送SIGALRM信號,又稱為鬧鐘時間。進程調用alarm後,
任何以前的alarm()調用都將無效。如果參數seconds為零,那麼進程內將不再包含任何鬧鐘時間。
函數返回是這樣的,如果調用alarm()前,進程中已經設置了鬧鐘時間,則返回上一個鬧鐘時間的剩餘時間,否則返回0
5int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
用到的頭文件:
#include <sys/time.h>
setitimer()alarm功能強大,支持3種類型的定時器:
ITIMER_REAL: 設定絕對時間;經過指定的時間後,內核將發送SIGALRM信號給本進程;
ITIMER_VIRTUAL 設定程序執行時間;經過指定的時間後,內核將發送SIGVTALRM信號給本進程;
ITIMER_PROF 設定進程執行以及內核因本進程而消耗的時間和,經過指定的時間後,內核將發送ITIMER_VIRTUAL信號給本進程;
Setitimer()第一個參數which指定定時器類型(上面三種之一);第二個參數是結構itimerval的一個實例,結構itimerval
結構itimerval

struct itimerval
{
struct timeval it_interval; /* next value */
struct timeval it_value; /* current value */
};
struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
第三個參數可不做處理。
Setitimer()調用成功返回0,否則返回-1
這是我man setitimer時看到的
getitimer and setitimer are not part of any currently supported standard;
they were developed at the University of California at Berkeley and are
used by permission.
6void abort(void);
用到的頭文件:
#include <stdlib.h>
向進程發送SIGABORT信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數。
即使SIGABORT被進程設置為阻塞信號,調用abort()後,SIGABORT仍然能被進程接收。該函數無返回值。
B:信號的捕獲與安裝(設置信號關聯動作)
如果進程要處理某一信號,那麼就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值
的動作之間的映射關係,
即進程將要處理哪個信號;該信號被傳遞給進程時,將執行何種操作。
linux主要有兩個函數實現信號的安裝:signal()sigaction()。其中signal()在可靠信號系統調用的基礎上實現,
是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的
函數(由兩個系統調用實現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與
sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優於signal()主要體現
在支持信號帶有參數。

1void (*signal(int signum, void (*handler))(int)))(int);
#include <signal.h>
如果該函數原型不容易理解的話,可以參考下面的分解方式來理解:
typedef void (*sighandler_t)(int)
sighandler_t signal(int signum, sighandler_t handler));
第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,
a,可以忽略該信號(參數設為SIG_IGN);
b,可以採用系統默認方式處理信號(參數設為SIG_DFL)
b,也可以自己實現處理方式(參數指定一個函數地址)
如果signal()調用成功,返回最後一次為安裝信號signum而調用signal()時的handler值;失敗則返回SIG_ERR
2int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

#include <signal.h>
sigaction函數用於改變進程接收到特定信號後的行為。該函數的第一個參數為信號的值,可以為除SIGKILLSIGSTOP外的任何
一個特定有效的信號(為這兩個信號定義自己的處理函數,將導致信號安裝錯誤)。第二個參數是指向結構sigaction的一個實例
的指針,在結構sigaction的實例中,指定了對特定信號的處理,可以為空,進程會以缺省方式對信號處理;第三個參數oldact指向
的對象用來保存原來對相應信號的處理,可指定oldactNULL。如果把第二、第三個參數都設為NULL,那麼該函數可用於檢查信號的有效性。
第二個參數最為重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程中應屏蔽掉哪些函數等等。
sigaction結構定義如下:
struct sigaction
{
union
{
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int,struct siginfo *, void *)
}_u
sigset_t sa_mask
unsigned long sa_flags
void (*sa_restorer)(void)
}
其中,sa_restorer,已過時,POSIX不支持它,不應再被使用。

a,聯合數據結構中的兩個元素_sa_handler以及*_sa_sigaction指定信號關聯函數,即用戶指定的信號處理函數。除了可以是用戶自
定義的處理函數外,還可以為SIG_DFL(採用缺省的處理方式),也可以為SIG_IGN(忽略信號)。
b,_sa_handler指定的處理函數只有一個參數,即信號值,所以信號不能傳遞除信號值之外的任何信息;由_sa_sigaction是指定的
信號處理函數帶有三個參數,是為實時信號而設的(當然同樣支持非實時信號),它指定一個3參數信號處理函數。
第一個參數為信號值,第三個參數沒有使用(POSIX沒有規範使用該參數的標準),第二個參數是指向siginfo_t結構的指針,結構中包含
信號攜帶的數據值,參數所指向的結構如下:
siginfo_t {
int si_signo; /* 信號值,對所有信號有意義*/
int si_errno; /* errno值,對所有信號有意義*/
int si_code; /* 信號產生的原因,對所有信號有意義*/
union{ /* 聯合數據結構,不同成員適應不同信號 */
//確保分配足夠大的存儲空間
int _pad[SI_PAD_SIZE];
//SIGKILL有意義的結構
struct{
...
}...

... ...
... ...
//SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結構
struct{
...
}...
... ...
}
}
註:為了更便於閱讀,在說明問題時常把該結構表示為如下所表示的形式。
siginfo_t {
int si_signo; /* 信號值,對所有信號有意義*/
int si_errno; /* errno值,對所有信號有意義*/
int si_code; /* 信號產生的原因,對所有信號有意義*/
pid_t si_pid; /* 發送信號的進程ID,kill(2),實時信號以及SIGCHLD有意義 */
uid_t si_uid; /* 發送信號進程的真實用戶ID,對kill(2),實時信號以及SIGCHLD有意義 */
int si_status; /* 退出狀態,對SIGCHLD有意義*/
clock_t si_utime; /* 用戶消耗的時間,對SIGCHLD有意義 */
clock_t si_stime; /* 內核消耗的時間,對SIGCHLD有意義 */
sigval_t si_value; /* 信號值,對所有實時有意義,是一個聯合數據結構,可以為一個整數(由si_int標示,也可以為一個指針,由si_ptr標示)*/
void * si_addr; /* 觸發fault的內存地址,對SIGILL,SIGFPE,SIGSEGV,SIGBUS 信號有意義*/
int si_band; /* SIGPOLL信號有意義 */
int si_fd; /* SIGPOLL信號有意義 */
}
實際上,除了前三個元素外,其他元素組織在一個聯合結構中,在聯合數據結構中,又根據不同的信號組織成不同的結構。
註釋中提到的對某種信號有意義指的是,在該信號的處理函數中可以訪問這些域來獲得與信號相關的有意義的信息,只不過特定信號只
對特定信息感興趣而已。
siginfo_t結構中的聯合數據成員確保該結構適應所有的信號,比如對於實時信號來說,則實際採用下面的結構形式:
typedef struct {
int si_signo;
int si_errno;
int si_code;
union sigval si_value;
} siginfo_t;
結構的第四個域同樣為一個聯合數據結構:
union sigval {
int sival_int;
void *sival_ptr;
}
採用聯合數據結構,說明siginfo_t結構中的si_value要麼持有一個4字節的整數值,要麼持有一個指針,這就構成了與信號相關的數據。
在信號的處理函數中,包含這樣的信號相關數據指針,但沒有規定具體如何對這些數據進行操作,操作方法應該由程序開發人員根據具體
任務事先約定。
前面在討論系統調用sigqueue發送信號時,sigqueue的第三個參數就是sigval聯合數據結構,當調用sigqueue時,該數據結構中的數據就將
拷貝到信號處理函數的第二個參數中。這樣,在發送信號同時,就可以讓信號傳遞一些附加信息。信號可以傳遞信息對程序開發是非常有意義
的。
c,sa_mask指定在信號處理程序執行過程中,哪些信號應當被阻塞。缺省情況下當前信號本身被阻塞,防止信號的嵌套發送,除非指定SA_NODEFER或者SA_NOMASK標誌位。
註:請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數執行過程中由sa_mask指定的信號才被阻塞。
d,sa_flags中包含了許多標誌位,包括剛剛提到的SA_NODEFERSA_NOMASK標誌位。另一個比較重要的標誌位是SA_SIGINFO,當設定了該標誌
位時,表示信號附帶的參數可以被傳遞到信號處理函數中,因此,應該為sigaction結構中的sa_sigaction指定處理函數,而不應該為sa_handler
指定信號處理函數,否則,設置該標誌變得毫無意義。即使為sa_sigaction指定了信號處理函數,如果不設置SA_SIGINFO,信號處理函數同樣
不能得到信號傳遞過來的數據,在信號處理函數中對這些信息的訪問都將導致段錯誤(Segmentation fault)。
註:很多文獻在闡述該標誌位時都認為,如果設置了該標誌位,就必須定義三參數信號處理函數。實際不是這樣的,驗證方法很簡單:自己實現
一個單一參數信號處理函數,並在程序中設置該標誌位,可以察看程序的運行結果。實際上,可以把該標誌位看成信號是否傳遞參數的開關,如果設置該位,
則傳遞參數;否則,不傳遞參數。
C:信號集及信號集操作函數:
信號集被定義為一種數據類型:
typedef struct {
unsigned long sig[_NSIG_WORDS]
} sigset_t
信號集用來描述信號的集合,linux所支持的所有信號可以全部或部分的出現在信號集中,主要與信號阻塞相關函數配合使用。下面是為
信號集操作定義的相關函數:
int sigemptyset(sigset_t *set)
int sigfillset(sigset_t *set)
int sigaddset(sigset_t *set, int signum)
int sigdelset(sigset_t *set, int signum)
int sigismember(const sigset_t *set, int signum)

頭文件
#include <signal.h>

sigemptyset(sigset_t *set)初始化由set指定的信號集,信號集裡面的所有信號被清空;
sigfillset(sigset_t *set)調用該函數後,set指向的信號集中將包含linux支持的64種信號;
sigaddset(sigset_t *set, int signum)set指向的信號集中加入signum信號;
sigdelset(sigset_t *set, int signum)set指向的信號集中刪除signum信號;
sigismember(const sigset_t *set, int signum)判定信號signum是否在set指向的信號集中。
D:信號阻塞與信號未決:
每個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的所有信號在遞送到進程後都將被阻塞。下面是與信號阻塞
相關的幾個函數:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset))
int sigpending(sigset_t *set));
int sigsuspend(const sigset_t *mask))
頭文件:
#include <signal.h>
sigprocmask()函數能夠根據參數how來實現對信號集的操作,操作主要有三種:
參數how 進程當前信號集
SIG_BLOCK 在進程當前阻塞信號集中添加set指向信號集中的信號
SIG_UNBLOCK 如果進程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞
SIG_SETMASK 更新進程阻塞信號集為set指向的信號集
sigpending(sigset_t *set))獲得當前已遞送到進程,卻被阻塞的所有信號,在set指向的信號集中返回結果。
sigsuspend(const sigset_t *mask))用於在接收到某個信號之前, 臨時用mask替換進程的信號掩碼, 並暫停進程執行,直到收到信號為止。
sigsuspend 返回後將恢復調用之前的信號掩碼。信號處理函數完成後,進程將繼續執行。該系統調用始終返回-1,並將errno設置為EINTR
,示例程序
實例一:信號發送及處理,看看函數sigaction是怎麼使用的。
CODE
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void user_func(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void * )user_func;
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void user_func(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d\n\n\n", signum);
sleep(5);
}
在一終端執行cc -o act act.c
$./act 8&
[1] 992
$ wait for the signal
在另一終端執行
#kill -s 8 992
看看。。
$receive signal 8
實例二:信號阻塞及信號集操作
CODE
#include <signal.h>
#include <unistd.h>
void user_func(int);
main()
{
sigset_t new_mask,old_mask,pending_mask;
struct sigaction act;

sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void*)user_func;
if(sigaction(SIGRTMIN+10,&act,NULL))
printf("install signal SIGRTMIN+10 error\n");
sigemptyset(&new_mask);
sigaddset(&new_mask,SIGRTMIN+10);
if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))
printf("block signal SIGRTMIN+10 error\n");
sleep(20);
printf("\n\nNow begin to get pending mask and unblock SIGRTMIN+10\n\n");
if(sigpending(&pending_mask)<0)
printf("get pending mask error\n");
if(sigismember(&pending_mask,SIGRTMIN+10))
printf("signal SIGRTMIN+10 is pending\n");
if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)
printf("unblock signal error\n");
printf("signal unblocked ,ok ... ...\n\n\n");
}
void user_func(int signum)
{
printf("receive signal %d \n",signum);
}
$cc -o sus sus.c
$./sus&
[1] 997
another console
#kill -s 42 pid
看看...
$
Now begin to get pending mask and unblock SIGRTMIN+10

signal SIGRTMIN+10 is pending
receive signal 42
signal unblocked ,ok ... ...
[1]+ Exit 31 ./d
$
實例三:signal例子

CODE
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#define damo
void user_func(int no)
{
switch (no)
{
case 1:
printf("Get SIGHUP.\n");
break;
case 2:
printf("Get SIGINT\n");
break;
case 3:
printf("Get SIGQUIT \n");
break;
default:
printf("What wan yi a \n\n");
break;
}

}
int main()
{
printf("Process id is %d\n ",getpid());
#ifdef damo
signal(SIGHUP, user_func);
signal(SIGINT, user_func);
signal(SIGQUIT, user_func);
signal(SIGBUS, user_func);
#else
signal(SIGHUP, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGBUS, SIG_DFL);
#endif
while(1)
;
}
這個就是signal的用法集中展示,也是我經常用的一個方法。說實話,sco太古老了。。俺只能用這個函數了。
測試時你可以把#define damo這個註釋和不註釋看看。深刻體會signal的用法。
BTWsignal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
等等忽略了這些信號後,可以讓一個進程終端無關,即使你退出這個tty.當然kill信號還是不能屏蔽的。
這種方式多在守護進程中採用。
$ cc -o si si.c
$./si
Process id is 1501
在另一個tty
#ps -ef
bank 1501 1465 51 04:07 pts/0 00:00:13 ./si
bank 1502 800 0 04:08 pts/1 00:00:00 ps -ef
#
注意觀察這時候是1456
你把./si這個tty關掉。
這時候你再
#ps -ef看看
bank 1501 1 50 04:07 ? 00:00:59 ./si
bank 1503 800 0 04:09 pts/1 00:00:00 ps -ef
注意這個1?,知道這個進程成啥了吧?成精拉。哈哈??
實例四:pause函數
CODE
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void user_func()
{
printf("\n\nCatch a signal SIGINT \n");
}
int main()
{
printf ("pid = %d \n\n ",getpid());
signal(SIGINT, user_func);
pause();
printf("receive a signal \n\n");
}
在這個例子中,程序開始執行,就像進入了死循環一樣,這是因為進程正在等待信號,
當我們按下Ctrl-C時,信號被捕捉,並且使得pause退出等待狀態。
實例五:下面是關於setitimer調用的一個簡單例子。
CODE
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>
void user_func(int sig)
{
if ( sig == SIGALRM)
printf("Catch a signal SIGALRM \n");
else if ( sig == SIGVTALRM)
printf("Catch a signal SIGVTALRM\n");
}

int main()
{
struct itimerval value,ovalue,value2;
printf("Process id is = %d \n",getpid());
signal(SIGALRM, user_func);
signal(SIGVTALRM, user_func);
value.it_value.tv_sec = 1;
value.it_value.tv_usec = 0;
value.it_interval.tv_sec = 1;
value.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &value, &amp;amp;amp;ovalue);
value2.it_value.tv_sec = 1;
value2.it_value.tv_usec = 500000;
value2.it_interval.tv_sec = 1;
value2.it_interval.tv_usec = 500000;
setitimer(ITIMER_VIRTUAL, &value2, &amp;amp;amp;ovalue);
while(1);
}
在該例子中,每隔1秒發出一個SIGALRM,每隔1.5秒發出一個SIGVTALRM信號:
結果如下
$ ./ti
Process id is = 1734
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
ctrl+c中斷。
....
開始喜歡setitimer函數了。。。
,補充
不得不介紹一下setjmplongjmp的作用。
在用信號的時候,我們看到多個地方要求進程在檢查收到信號後,從原來的系統調用中直接返回,而不是等到該調用完成。
這種進程突然改變其上下文的情況,就是使用setjmplongjmp的結果。setjmp將保存的上下文存入用戶區,並繼續在舊的
上下文中執行。這就是說,進程執行一個系統調用,當因為資源或其他原因要去睡眠時,內核為進程作了一次setjmp,如果
在睡眠中被信號喚醒,進程不能再進入睡眠時,內核為進程調用longjmp,該操作是內核為進程將原先setjmp調用保存在進程
用戶區的上下文恢復成現在的上下文,這樣就使得進程可以恢復等待資源前的狀態,而且內核為setjmp返回1,使得進程知道
該次系統調用失敗。這就是它們的作用。
有時間再man 一下waitpid吧。。都是很有用的。