記上一篇的pipe和FIFO,這篇繼續來看看另外一種IPC的方式
就是signal,大部分是參考這裡 http://myblog-maurice.blogspot.com/2011/12/linux-signal.html 和 https://www.thegeekstuff.com/2012/03/catch-signals-sample-c-code/
其實說實在signal只能算是傳遞一個訊息給process,比如說中斷(使用者按Ctrl+c)或是殺死process(執行kill)之類的
並不是用來傳遞資料的
signal的種類有很多,包括錯誤、中斷、計時、使用者自訂等等
這個有興趣就去看看吧~ 網路上很多資料~
對於不同的signal呢,process可以選擇幾種處理方式
第一種是指定處理函式去處理,第二種是不處理,第三種是依照系統的預訂方式處理
那麼來看看第一種怎麼做吧
首先我們來看一個function
void (*signal(int signo, void (*func )(int)))(int);
嗯... 我基本功不太好,這是啥意思啊= ="
有興趣可以看看這邊的解釋https://blog.csdn.net/sever2012/article/details/8281271
拆開來看看就知道了,void (*func)(int)看得出來是一個有int參數的函式的指標
signal(int singo, void (*func)(int))是一個帶有int和(*func)(int)兩個參數的函式
(*signal(int singo, void (*func)(int)))就是一個函式指標
那麼... void (*signal(int singo, void (*func)(int)))(int),就是一個(*signal(int singo, void (*func)(int)))指向的函式,這個函式帶有一個int參數,回傳是void
...應該可以理解吧
不過直接這樣看太複雜了,於是簡化成這樣:
typedef void sigfunc(int)
sigfunc *signal(int, sigfunc*);
稍微看一下就ok了,這種東西我覺得知道怎麼用比較重要...
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void sigroutine(int dunno) { /* 信號處理常式,其中dunno將會得到信號的值 */
switch (dunno) {
case 1:
printf("Get a signal -- SIGHUP ");
break;
case 2:
printf("Get a signal -- SIGINT ");
break;
case 3:
printf("Get a signal -- SIGQUIT ");
break;
}
return;
}
int main() {
printf("process id is %d ",getpid());
signal(SIGHUP, sigroutine); //* 下面設置三個信號的處理方法
signal(SIGINT, sigroutine);
signal(SIGQUIT, sigroutine);
for (;;) ;
}
應該也不難懂吧,就是設定收到不同signal要有不同的處理而已
不過也有一些signal是沒被法被指定處理的,比如kill和stop的signal
如果要忽略或是恢復預設值,在signal的第二個參數可以代這兩個值:
SIG_IGN:忽略參數signum所指的信號。
SIG_DFL:恢復參數signum所指信號的處理方法為預設值。
系統中也有提供一些函式可以讓使用者發出signal
主要有kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()等函式
1. kill()
長這樣:int kill(pid_t pid, int sig),可以向process發出不同的signal
pid表示process的id
如果pid為0,表示要發給和現在的process所屬進程組裡的所有process
如果pid為-1,表示發給除了本身之外系統裡的所有process
如果pid<-1,表示將發送給屬於進程組-pid的所有process
sig表示要發出的signal的編號,signal的編號都是在signal.h中定義的
另外如果發送0是不會發signal的,但是會有回傳值,所以可以用來確認process是否存在
2. raise()
int raise(int sig)
向process本身發送signal,調用成功返回 0;否則返回 -1。
3. sigqueue()
int sigqueue(pid_t pid, int sig, const union sigval val)
調用成功返回 0;否則,返回 -1。
sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是一個聯合資料結構union sigval,指定了信號傳遞的參數
基本上是屬於另一種發signal方式,和kill()不同的地方在於他只能傳給一個process,但是相對可以帶更多的資訊(就是union sigval)
然後可以搭配sigaction()使用,這個sigaction()呢長這樣:
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
其實sigaction也是設定收到signal時的行為用的,第一個參數是sibnal的編號,第二個參數是指向結構sigaction的一個實例的指標,在結構sigaction的實例中,指定了對特定信號的處理,可以為NULL,進程會以預設方式對信號處理;第三個參數oldact指向的物件用來保存原來對相應信號的處理,可指定oldact為NULL。如果把第二、第三個參數都設為NULL,那麼該函數可用於檢查信號的有效性。
struct 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);
}
第一個union是處理函式,sa_mask是用來過濾哪些signal要被block住的,sa_flag則是決定一些... 行為吧,預設情況下當前信號本身被block,除非指定SA_NODEFER或者SA_NOMASK標誌位元
當SA_NODEFER設置時在信號處理函數執行期間不會屏蔽當前信號;當SA_SIGINFO設置時與sa_sigaction 搭配出現,sa_sigaction函數的第一個參數與sa_handler一樣表示當前信號的編號,第二個參數是一個siginfo_t 結搆體,第三個參數一般不用。當使用sa_handler時sa_flags設置為0即可。
...講了一堆亂複雜一把的,如果沒有要特別用到的話,sigqueue()就是發signal的,sigaction()就是決定收到signal要做啥的
兩個可以互相搭配,當sigqueue()發送對應signal時,會把sugqueue()裡的sigval參數的值copy到sigaction裡的sigaction裡的handler裡的siginfo裡面,然後接收端就可以存取到這個數值,達到傳輸的目的
下面這個圖我覺得滿清楚的,參考下吧:
這邊其實滿複雜滿多細節的,詳細可以參考這邊 http://www.cnblogs.com/mickole/p/3191804.html
我就簡易的紀錄了
然後一如往常...,我會去做幾個小實驗,再補充code和結果上來紀錄吧
這邊列幾個我有參考的網頁,之後如果有要用到可以參考參考:
http://www.cnblogs.com/mickole/p/3191804.html
https://blog.csdn.net/jnu_simba/article/details/8947652
http://myblog-maurice.blogspot.com/2011/12/linux_20.html
4. pause()
int pause(void)
就是讓process進入sleep狀態,停在這裡等待一個signal的意思
5. alarm()和setitimer()
就是當作計時器用的
unsigned int alarm(unsigned int seconds)
設定系統在seconds秒後發出一個SIGALRM的signal,就是個有點陽春的功能
比較進階的方式是用底下這兩個:
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
int getitimer(int which, struct itimerval *value);
簡單說就是一個設定timer,一個可以查看timer的狀態
系統提供了process三個計時器,它們各自有其獨有的計時域,當其中任何一個到達,就發送一個相應的信號給進程,並使得計時器重新開始。三個計時器由參數which指定,分別是:
TIMER_REAL:按實際時間計時,計時到達將給進程發送SIGALRM信號。
ITIMER_VIRTUAL:僅當進程執行時才進行計時。計時到達將發送SIGVTALRM信號給進程。
ITIMER_PROF:當進程執行時和系統為該進程執行動作時都計時。與ITIMER_VIRTUAL是一對,該計時器經常用來統計進程在使用者態和內核態花費的時間。計時到達將發送SIGPROF信號給進程。
參數value表示timer的時間,結構如下:
struct itimerval {
struct timeval it_interval; /* 時間的間隔,就是到達設定值後每隔多久再發一次signal */
struct timeval it_value; /* 本次的設定值 */
};
timeval的結構如下:
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微秒,1秒 = 1000000 微秒*/
}
在setitimer 調用中,參數ovalue如果不為空,則其中保留的是上次調用設定的值。計時器將it_value遞減到0時,產生一個信號,並將it_value的值設 定為it_interval的值,然後重新開始計時,如此往復。當it_value設定為0時,計時器停止,或者當它計時到期,而it_interval為0時停止。調用成功時,返回0;錯誤時,返回-1,並設置相應的error code
這篇我覺得寫的滿清楚的,參考下:https://www.cnblogs.com/mickole/p/3191977.html
一樣我會做幾個小實驗,有結果之後再補充
6. abort()
翻成中文是放棄的意思,真的是直接放棄的意思
代表異常的中止這個process,不會去做釋放記憶體之類的清除動作
有興趣可以去看看這篇,裡面有一些assert()、exit()等中止函式的比較:
其實沒接觸過學起來還滿累的...,至少先有點概念吧XD
搞不好之後也用不到啊XDDD