非常好的一篇對linux訊號(signal)的解析 (轉載)
轉載至:https://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html
【摘要】本文分析了Linux核心對於訊號的實現機制和應用層的相關處理。首先介紹了軟中斷訊號的本質及訊號的兩種不同分類方法尤其是不可靠訊號的原理。接著分析了核心對於訊號的處理流程包括訊號的觸發/註冊/執行及登出等。最後介紹了應用層的相關處理,主要包括訊號處理函式的安裝、訊號的傳送、遮蔽阻塞等,最後給了幾個簡單的應用例項。
【關鍵字】軟中斷訊號,signal,sigaction,kill,sigqueue,settimer,sigmask,sigprocmask,sigset_t
1 訊號本質
軟中斷訊號(signal,又簡稱為訊號)用來通知程序發生了非同步事件。在軟體層次上是對中斷機制的一種模擬,在原理上,一個程序收到一個訊號與處理器收到一箇中斷請求可以說是一樣的。訊號是程序間通訊機制中唯一的非同步通訊機制,一個程序不必通過任何操作來等待訊號的到達,事實上,程序也不知道訊號到底什麼時候到達。程序之間可以互相通過系統呼叫kill傳送軟中斷訊號。核心也可以因為內部事件而給程序傳送訊號,通知程序發生了某個事件。訊號機制除了基本通知功能外,還可以傳遞附加資訊。
收到訊號的程序對各種訊號有不同的處理方法。處理方法可以分為三類:
第一種是類似中斷的處理程式,對於需要處理的訊號,程序可以指定處理函數,由該函式來處理。
第二種方法是,忽略某個訊號,對該訊號不做任何處理,就象未發生過一樣。
第三種方法是,對該訊號的處理保留系統的預設值,這種預設操作,對大部分的訊號的預設操作是使得程序終止。程序通過系統呼叫signal來指定程序對某個訊號的處理行為。
2 訊號的種類
可以從兩個不同的分類角度對訊號進行分類:
可靠性方面:可靠訊號與不可靠訊號;
與時間的關係上:實時訊號與非實時訊號。
2.1可靠訊號與不可靠訊號
Linux訊號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的訊號機制比較簡單和原始,訊號值小於SIGRTMIN的訊號都是不可靠訊號。這就是"不可靠訊號"的來源。它的主要問題是訊號可能丟失。
隨著時間的發展,實踐證明了有必要對訊號的原始機制加以改進和擴充。由於原來定義的訊號已有許多應用,不好再做改動,最終只好又新增加了一些訊號,並在一開始就把它們定義為可靠訊號,這些訊號支援排隊,不會丟失。
訊號值位於SIGRTMIN和SIGRTMAX之間的訊號都是可靠訊號,可靠訊號克服了訊號可能丟失的問題。Linux在支援新版本的訊號安裝函式sigation()以及訊號傳送函式sigqueue()的同時,仍然支援早期的signal()訊號安裝函式,支援訊號傳送函式kill()。
訊號的可靠與不可靠只與訊號值有關,與訊號的傳送及安裝函式無關。目前linux中的signal()是通過sigation()函式實現的,因此,即使通過signal()安裝的訊號,在訊號處理函式的結尾也不必再呼叫一次訊號安裝函式。同時,由signal()安裝的實時訊號支援排隊,同樣不會丟失。
對於目前linux的兩個訊號安裝函式:signal()及sigaction()來說,它們都不能把SIGRTMIN以前的訊號變成可靠訊號(都不支援排隊,仍有可能丟失,仍然是不可靠訊號),而且對SIGRTMIN以後的訊號都支援排隊。這兩個函式的最大區別在於,經過sigaction安裝的訊號都能傳遞資訊給訊號處理函式,而經過signal安裝的訊號不能向訊號處理函式傳遞資訊。對於訊號傳送函式來說也是一樣的。
2.2實時訊號與非實時訊號
早期Unix系統只定義了32種訊號,前32種訊號已經有了預定義值,每個訊號有了確定的用途及含義,並且每種訊號都有各自的預設動作。如按鍵盤的CTRL ^C時,會產生SIGINT訊號,對該訊號的預設反應就是程序終止。後32個訊號表示實時訊號,等同於前面闡述的可靠訊號。這保證了傳送的多個實時訊號都被接收。
非實時訊號都不支援排隊,都是不可靠訊號;實時訊號都支援排隊,都是可靠訊號。
3 訊號處理流程
對於一個完整的訊號生命週期(從訊號傳送到相應的處理函式執行完畢)來說,可以分為三個階段:
訊號誕生
訊號在程序中註冊
訊號的執行和登出
3.1 訊號誕生
訊號事件的發生有兩個來源:硬體來源(比如我們按下了鍵盤或者其它硬體故障);軟體來源,最常用傳送訊號的系統函式是kill, raise, alarm和setitimer以及sigqueue函式,軟體來源還包括一些非法運算等操作。
這裡按發出訊號的原因簡單分類,以瞭解各種訊號:
(1) 與程序終止相關的訊號。當程序退出,或者子程序終止時,發出這類訊號。
(2) 與程序例外事件相關的訊號。如程序越界,或企圖寫一個只讀的記憶體區域(如程式正文區),或執行一個特權指令及其他各種硬體錯誤。
(3) 與在系統呼叫期間遇到不可恢復條件相關的訊號。如執行系統呼叫exec時,原有資源已經釋放,而目前系統資源又已經耗盡。
(4) 與執行系統呼叫時遇到非預測錯誤條件相關的訊號。如執行一個並不存在的系統呼叫。
(5) 在使用者態下的程序發出的訊號。如程序呼叫系統呼叫kill向其他程序傳送訊號。
(6) 與終端互動相關的訊號。如使用者關閉一個終端,或按下break鍵等情況。
(7) 跟蹤程序執行的訊號。
Linux支援的訊號列表如下。很多訊號是與機器的體系結構相關的
訊號值 預設處理動作 發出訊號的原因
SIGHUP 1 A 終端掛起或者控制程序終止
SIGINT 2 A 鍵盤中斷(如break鍵被按下)
SIGQUIT 3 C 鍵盤的退出鍵被按下
SIGILL 4 C 非法指令
SIGABRT 6 C 由abort(3)發出的退出指令
SIGFPE 8 C 浮點異常
SIGKILL 9 AEF Kill訊號
SIGSEGV 11 C 無效的記憶體引用
SIGPIPE 13 A 管道破裂: 寫一個沒有讀埠的管道
SIGALRM 14 A 由alarm(2)發出的訊號
SIGTERM 15 A 終止訊號
SIGUSR1 30,10,16 A 使用者自定義訊號1
SIGUSR2 31,12,17 A 使用者自定義訊號2
SIGCHLD 20,17,18 B 子程序結束訊號
SIGCONT 19,18,25 程序繼續(曾被停止的程序)
SIGSTOP 17,19,23 DEF 終止程序
SIGTSTP 18,20,24 D 控制終端(tty)上按下停止鍵
SIGTTIN 21,21,26 D 後臺程序企圖從控制終端讀
SIGTTOU 22,22,27 D 後臺程序企圖從控制終端寫
處理動作一項中的字母含義如下
A 預設的動作是終止程序
B 預設的動作是忽略此訊號,將該訊號丟棄,不做處理
C 預設的動作是終止程序並進行核心映像轉儲(dump core),核心映像轉儲是指將程序資料在記憶體的映像和程序在核心結構中的部分內容以一定格式轉儲到檔案系統,並且程序退出執行,這樣做的好處是為程式設計師提供了方便,使得他們可以得到程序當時執行時的資料值,允許他們確定轉儲的原因,並且可以除錯他們的程式。
D 預設的動作是停止程序,進入停止狀況以後還能重新進行下去,一般是在除錯的過程中(例如ptrace系統呼叫)
E 訊號不能被捕獲
F 訊號不能被忽略
3.2訊號在目標程序中註冊
在程序表的表項中有一個軟中斷訊號域,該域中每一位對應一個訊號。核心給一個程序傳送軟中斷訊號的方法,是在程序所在的程序表項的訊號域設定對應於該訊號的位。如果訊號傳送給一個正在睡眠的程序,如果程序睡眠在可被中斷的優先順序上,則喚醒程序;否則僅設定程序表中訊號域相應的位,而不喚醒程序。如果傳送給一個處於可執行狀態的程序,則只置相應的域即可。
程序的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;
}
訊號在程序中註冊指的就是訊號值加入到程序的未決訊號集sigset_t signal(每個訊號佔用一位)中,並且訊號所攜帶的資訊被保留到未決訊號資訊鏈的某個sigqueue結構中。只要訊號在程序的未決訊號集中,表明程序已經知道這些訊號的存在,但還沒來得及處理,或者該訊號被程序阻塞。
當一個實時訊號傳送給一個程序時,不管該訊號是否已經在程序中註冊,都會被再註冊一次,因此,訊號不會丟失,因此,實時訊號又叫做"可靠訊號"。這意味著同一個實時訊號可以在同一個程序的未決訊號資訊鏈中佔有多個sigqueue結構(程序每收到一個實時訊號,都會為它分配一個結構來登記該訊號資訊,並把該結構新增在未決訊號鏈尾,即所有誕生的實時訊號都會在目標程序中註冊)。
當一個非實時訊號傳送給一個程序時,如果該訊號已經在程序中註冊(通過sigset_t signal指示),則該訊號將被丟棄,造成訊號丟失。因此,非實時訊號又叫做"不可靠訊號"。這意味著同一個非實時訊號在程序的未決訊號資訊鏈中,至多佔有一個sigqueue結構。
總之訊號註冊與否,與傳送訊號的函式(如kill()或sigqueue()等)以及訊號安裝函式(signal()及sigaction())無關,只與訊號值有關(訊號值小於SIGRTMIN的訊號最多隻註冊一次,訊號值在SIGRTMIN及SIGRTMAX之間的訊號,只要被程序接收到就被註冊)
3.3訊號的執行和登出
核心處理一個程序收到的軟中斷訊號是在該程序的上下文中,因此,程序必須處於執行狀態。當其由於被訊號喚醒或者正常排程重新獲得CPU時,在其從核心空間返回到使用者空間時會檢測是否有訊號等待處理。如果存在未決訊號等待處理且該訊號沒有被程序阻塞,則在執行相應的訊號處理函式前,程序會把訊號在未決訊號鏈中佔有的結構卸掉。
對於非實時訊號來說,由於在未決訊號資訊鏈中最多隻佔用一個sigqueue結構,因此該結構被釋放後,應該把訊號在程序未決訊號集中刪除(訊號登出完畢);而對於實時訊號來說,可能在未決訊號資訊鏈中佔用多個sigqueue結構,因此應該針對佔用sigqueue結構的數目區別對待:如果只佔用一個sigqueue結構(程序只收到該訊號一次),則執行完相應的處理函式後應該把訊號在程序的未決訊號集中刪除(訊號登出完畢)。否則待該訊號的所有sigqueue處理完畢後再在程序的未決訊號集中刪除該訊號。
當所有未被遮蔽的訊號都處理完畢後,即可返回使用者空間。對於被遮蔽的訊號,當取消遮蔽後,在返回到使用者空間時會再次執行上述檢查處理的一套流程。
核心處理一個程序收到的訊號的時機是在一個程序從核心態返回使用者態時。所以,當一個程序在核心態下執行時,軟中斷訊號並不立即起作用,要等到將返回使用者態時才處理。程序只有處理完訊號才會返回使用者態,程序在使用者態下不會有未處理完的訊號。
處理訊號有三種類型:程序接收到訊號後退出;程序忽略該訊號;程序收到訊號後執行使用者設定用系統呼叫signal的函式。當程序接收到一個它忽略的訊號時,程序丟棄該訊號,就象沒有收到該訊號似的繼續執行。如果程序收到一個要捕捉的訊號,那麼程序從核心態返回使用者態時執行使用者定義的函式。而且執行使用者定義的函式的方法很巧妙,核心是在使用者棧上建立一個新的層,該層中將返回地址的值設定成使用者定義的處理函式的地址,這樣程序從核心返回彈出棧頂時就返回到使用者定義的函式處,從函式返回再彈出棧頂時,才返回原先進入核心的地方。這樣做的原因是使用者定義的處理函式不能且不允許在核心態下執行(如果使用者定義的函式在核心態下執行的話,使用者就可以獲得任何許可權)。
4 訊號的安裝
如果程序要處理某一訊號,那麼就要在程序中安裝該訊號。安裝訊號主要用來確定訊號值及程序針對該訊號值的動作之間的對映關係,即程序將要處理哪個訊號;該訊號被傳遞給程序時,將執行何種操作。
linux主要有兩個函式實現訊號的安裝:signal()、sigaction()。其中signal()只有兩個引數,不支援訊號傳遞資訊,主要是用於前32種非實時訊號的安裝;而sigaction()是較新的函式(由兩個系統呼叫實現:sys_signal以及sys_rt_sigaction),有三個引數,支援訊號傳遞資訊,主要用來與 sigqueue() 系統呼叫配合使用,當然,sigaction()同樣支援非實時訊號的安裝。sigaction()優於signal()主要體現在支援訊號帶有引數。
4.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。
傳遞給訊號處理例程的整數引數是訊號值,這樣可以使得一個訊號處理例程處理多個訊號。
#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 (;;) ;
}
其中訊號SIGINT由按下Ctrl-C發出,訊號SIGQUIT由按下Ctrl-發出。該程式執行的結果如下:
localhost:~$ ./sig_test
process id is 463
Get a signal -SIGINT //按下Ctrl-C得到的結果
Get a signal -SIGQUIT //按下Ctrl-得到的結果
//按下Ctrl-z將程序置於後臺
[1]+ Stopped ./sig_test
localhost:~$ bg
[1]+ ./sig_test &
localhost:~$ kill -HUP 463 //向程序傳送SIGHUP訊號
localhost:~$ Get a signal – SIGHUP
kill -9 463 //向程序傳送SIGKILL訊號,終止程序
localhost:~$
4.2sigaction()
#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
sigaction函式用於改變程序接收到特定訊號後的行為。該函式的第一個引數為訊號的值,可以為除SIGKILL及SIGSTOP外的任何一個特定有效的訊號(為這兩個訊號定義自己的處理函式,將導致訊號安裝錯誤)。第二個引數是指向結構sigaction的一個例項的指標,在結構sigaction的例項中,指定了對特定訊號的處理,可以為空,程序會以預設方式對訊號處理;第三個引數oldact指向的物件用來儲存返回的原來對相應訊號的處理,可指定oldact為NULL。如果把第二、第三個引數都設為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;
}
1、聯合資料結構中的兩個元素_sa_handler以及*_sa_sigaction指定訊號關聯函式,即使用者指定的訊號處理函式。除了可以是使用者自定義的處理函式外,還可以為SIG_DFL(採用預設的處理方式),也可以為SIG_IGN(忽略訊號)。
2、由_sa_sigaction是指定的訊號處理函式帶有三個引數,是為實時訊號而設的(當然同樣支援非實時訊號),它指定一個3引數訊號處理函式。第一個引數為訊號值,第三個引數沒有使用,第二個引數是指向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{
...
}...
... ...
}
}
前面在討論系統呼叫sigqueue傳送訊號時,sigqueue的第三個引數就是sigval聯合資料結構,當呼叫sigqueue時,該資料結構中的資料就將拷貝到訊號處理函式的第二個引數中。這樣,在傳送訊號同時,就可以讓訊號傳遞一些附加資訊。訊號可以傳遞資訊對程式開發是非常有意義的。
3、sa_mask指定在訊號處理程式執行過程中,哪些訊號應當被阻塞。預設情況下當前訊號本身被阻塞,防止訊號的巢狀傳送,除非指定SA_NODEFER或者SA_NOMASK標誌位。
注:請注意sa_mask指定的訊號阻塞的前提條件,是在由sigaction()安裝訊號的處理函式執行過程中由sa_mask指定的訊號才被阻塞。
4、sa_flags中包含了許多標誌位,包括剛剛提到的SA_NODEFER及SA_NOMASK標誌位。另一個比較重要的標誌位是SA_SIGINFO,當設定了該標誌位時,表示訊號附帶的引數可以被傳遞到訊號處理函式中,因此,應該為sigaction結構中的sa_sigaction指定處理函式,而不應該為sa_handler指定訊號處理函式,否則,設定該標誌變得毫無意義。即使為sa_sigaction指定了訊號處理函式,如果不設定SA_SIGINFO,訊號處理函式同樣不能得到訊號傳遞過來的資料,在訊號處理函式中對這些資訊的訪問都將導致段錯誤(Segmentation fault)。
5 訊號的傳送
傳送訊號的主要函式有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
5.1kill()
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int signo)
該系統呼叫可以用來向任何程序或程序組傳送任何訊號。引數pid的值為訊號的接收程序
pid>0 程序ID為pid的程序
pid=0 同一個程序組的程序
pid<0 pid!=-1 程序組ID為 -pid的所有程序
pid=-1 除傳送程序自身外,所有程序ID大於1的程序
Sinno是訊號值,當為0時(即空訊號),實際不傳送任何訊號,但照常進行錯誤檢查,因此,可用於檢查目標程序是否存在,以及當前程序是否具有向目標傳送訊號的許可權(root許可權的程序可以向任何程序傳送訊號,非root許可權的程序只能向屬於同一個session或者同一個使用者的程序傳送訊號)。
Kill()最常用於pid>0時的訊號傳送。該呼叫執行成功時,返回值為0;錯誤時,返回-1,並設定相應的錯誤程式碼errno。下面是一些可能返回的錯誤程式碼:
EINVAL:指定的訊號sig無效。
ESRCH:引數pid指定的程序或程序組不存在。注意,在程序表項中存在的程序,可能是一個還沒有被wait收回,但已經終止執行的僵死程序。
EPERM: 程序沒有權力將這個訊號傳送到指定接收訊號的程序。因為,一個程序被允許將訊號傳送到程序pid時,必須擁有root權力,或者是發出呼叫的程序的UID 或EUID與指定接收的程序的UID或儲存使用者ID(savedset-user-ID)相同。如果引數pid小於-1,即該訊號傳送給一個組,則該錯誤表示組中有成員程序不能接收該訊號。
5.2sigqueue()
#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指定的資訊會拷貝到對應sig 註冊的3引數訊號處理函式的siginfo_t結構中,這樣訊號處理函式就可以處理這些資訊了。由於sigqueue系統呼叫支援傳送帶引數訊號,所以比kill()系統呼叫的功能要靈活和強大得多。
5.3alarm()
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
系統呼叫alarm安排核心為呼叫程序在指定的seconds秒後發出一個SIGALRM的訊號。如果指定的引數seconds為0,則不再發送 SIGALRM訊號。後一次設定將取消前一次的設定。該呼叫返回值為上次定時呼叫到傳送之間剩餘的時間,或者因為沒有前一次定時呼叫而返回0。
注意,在使用時,alarm只設定為傳送一次訊號,如果要多次傳送,就要多次使用alarm呼叫。
5.4setitimer()
現在的系統中很多程式不再使用alarm呼叫,而是使用setitimer呼叫來設定定時器,用getitimer來得到定時器的狀態,這兩個呼叫的宣告格式如下:
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
在使用這兩個呼叫的程序中加入以下標頭檔案:
#include <sys/time.h>
該系統呼叫給程序提供了三個定時器,它們各自有其獨有的計時域,當其中任何一個到達,就傳送一個相應的訊號給程序,並使得計時器重新開始。三個計時器由引數which指定,如下所示:
TIMER_REAL:按實際時間計時,計時到達將給程序傳送SIGALRM訊號。
ITIMER_VIRTUAL:僅當程序執行時才進行計時。計時到達將傳送SIGVTALRM訊號給程序。
ITIMER_PROF:當程序執行時和系統為該程序執行動作時都計時。與ITIMER_VIR-TUAL是一對,該定時器經常用來統計程序在使用者態和核心態花費的時間。計時到達將傳送SIGPROF訊號給程序。
定時器中的引數value用來指明定時器的時間,其結構如下:
struct itimerval {
struct timeval it_interval; /* 下一次的取值 */
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,並設定相應的錯誤程式碼errno:
EFAULT:引數value或ovalue是無效的指標。
EINVAL:引數which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一個。
下面是關於setitimer呼叫的一個簡單示範,在該例子中,每隔一秒發出一個SIGALRM,每隔0.5秒發出一個SIGVTALRM訊號:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>
int sec;
void sigroutine(int signo) {
switch (signo) {
case SIGALRM:
printf("Catch a signal -- SIGALRM ");
break;
case SIGVTALRM:
printf("Catch a signal -- SIGVTALRM ");
break;
}
return;
}
int main()
{
struct itimerval value,ovalue,value2;
sec = 5;
printf("process id is %d ",getpid());
signal(SIGALRM, sigroutine);
signal(SIGVTALRM, sigroutine);
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, &ovalue);
value2.it_value.tv_sec = 0;
value2.it_value.tv_usec = 500000;
value2.it_interval.tv_sec = 0;
value2.it_interval.tv_usec = 500000;
setitimer(ITIMER_VIRTUAL, &value2, &ovalue);
for (;;) ;
}
該例子的螢幕拷貝如下:
localhost:~$ ./timer_test
process id is 579
Catch a signal – SIGVTALRM
Catch a signal – SIGALRM
Catch a signal – SIGVTALRM
Catch a signal – SIGVTALRM
Catch a signal – SIGALRM
Catch a signal –GVTALRM
5.5abort()
#include <stdlib.h>
void abort(void);
向程序傳送SIGABORT訊號,預設情況下程序會異常退出,當然可定義自己的訊號處理函式。即使SIGABORT被程序設定為阻塞訊號,呼叫abort()後,SIGABORT仍然能被程序接收。該函式無返回值。
5.6raise()
#include <signal.h>
int raise(int signo)
向程序本身傳送訊號,引數為即將傳送的訊號值。呼叫成功返回 0;否則,返回 -1。
6 訊號集及訊號集操作函式:
訊號集被定義為一種資料型別:
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指向的訊號集中。
7 訊號阻塞與訊號未決:
每個程序都有一個用來描述哪些訊號遞送到程序時將被阻塞的訊號集,該訊號集中的所有訊號在遞送到程序後都將被阻塞。下面是與訊號阻塞相關的幾個函式:
#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來實現對訊號集的操作,操作主要有三種:
SIG_BLOCK 在程序當前阻塞訊號集中新增set指向訊號集中的訊號
SIG_UNBLOCK 如果程序阻塞訊號集中包含set指向訊號集中的訊號,則解除對該訊號的阻塞
SIG_SETMASK 更新程序阻塞訊號集為set指向的訊號集
sigpending(sigset_t *set))獲得當前已遞送到程序,卻被阻塞的所有訊號,在set指向的訊號集中返回結果。
sigsuspend(const sigset_t *mask))用於在接收到某個訊號之前, 臨時用mask替換程序的訊號掩碼, 並暫停程序執行,直到收到訊號為止。sigsuspend 返回後將恢復呼叫之前的訊號掩碼。訊號處理函式完成後,程序將繼續執行。該系統呼叫始終返回-1,並將errno設定為EINTR。
8 訊號應用例項
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驗證訊號的傳送接收及處理。同時,可驗證訊號的排隊問題。
例項二:訊號傳遞附加資訊
主要包括兩個例項:
向程序本身傳送訊號,並傳遞指標引數
#include <signal.h>
#include <sys/types.h>
相關推薦
非常好的一篇對linux訊號(signal)的解析 (轉載)【轉】 Linux訊號(signal) 機制分析
轉自:https://blog.csdn.net/return_cc/article/details/78845346 Linux訊號(signal) 機制分析 轉載至:https://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html
非常好的一篇對linux訊號(signal)的解析 (轉載)
轉載至:https://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html 【摘要】本文分析了Linux核心對於訊號的實現機制和應用層的相關處理。首先介紹了軟中斷訊號的本質及訊號的兩種不同分類方法尤其是不可靠訊號的原理。接著分析了核心對於訊號的處理流程包
做SEO必須要知道如何寫好一篇文章?
效果 搜索結果 用戶 提高 還需 搜索引擎 公司 千萬 那是 文章不僅僅是網站更新而已,網站文章質量好壞,影響著網站的質量度。毫無疑問對網站排名有影響。偽原創如何變成原創,有的人特單純,一篇文章拿過來,這裏插一句,那裏加一句就是偽原創了嗎? 如果是這樣那你網站內容質量度非常
一篇對大資料深度思考的文章,讓你認識並讀懂大資料
在寫這篇文章之前,筆者發現身邊很多IT人對於這些熱門的新技術、新趨勢往往趨之若鶩卻又很難說的透徹,如果你問他大資料和你有什麼關係?估計很少能說出一二三來。究其原因,一是因為大家對新技術有著相同的原始渴求,至少知其然在聊天時不會顯得很“土鱉”;二是在工作和生活環境中真正能參與實踐大資料的案例實在太
在機器學習領域,怎樣寫好一篇論文
當你做好了一個研究工作, 準備發表出來與同仁們分享, 一個首要的任務是把你的工作變成一篇文章。問題來了,怎樣寫作一篇高質量的文章呢?我們以機器學習領域的應用型文章為例,探討一下論文寫作的問題。注意,任何好的文章都要以好的研究工作為基礎,我們這裡不談你的研究工作質量如何,只討論文章的寫作問題。要把一個
推薦一篇講Oracle RAC深度解析的好文章
Oracle RAC 深度解析之(1) Oracle RAC 深度解析之(2) (以下在原文基礎上做了,微改了部分內容) 一、 工作機制 1.1 併發控制 在叢集環境中, 關鍵資料通常是共享存放的,比如放在共享磁碟上。 而各個節點的對資料有相同的訪問許可權, 這時就必須有某種
linux 訊號signal和sigaction理解
今天看到unp時發現之前對signal到理解實在淺顯,今天拿來單獨學習討論下。 signal,此函式相對簡單一些,給定一個訊號,給出訊號處理函式則可,當然,函式簡單,其功能也相對簡單許多,簡單給出個函式例子如下: 1 #incl
如何寫好一篇技術文章?
文章整理自: 掘金YoungZ 、Github ruanyf、知乎 敖天羽 很多開發者都喜歡在各類社群平臺分享一些寫作內容,一方面可以對人生的一些經歷進行記錄,同時將開發中的經驗總結分享給其他人。當然寫作本身益處也是不少的,不僅能鍛鍊邏輯思維能力,還能訓練日益退化的語言表達能力,個人影響力上也能夠達到一定
如何寫好一篇高質量的IEEE/ACM Transaction級別的電腦科學論文?
轉自《知乎》如何寫好一篇高質量的IEEE/ACM Transaction級別的電腦科學論文? 問題: 作為一個博士生,一直為寫論文頭疼,讀過很多高質量論文,覺得寫的真好,但是輪到自己寫總是力不從心。 最討厭的是活生生的把一個A級別的idea寫成C質量
這是一篇對Python深入解析的文章,附帶入門技巧,不看你就虧了!
這是一篇對Python深入解析的文章,附帶入門技巧,不看你就虧了! 隨著機器學習的興起,Python 逐步成為了「最受歡迎」的語言。它簡單易用、邏輯明確並擁有海量的擴充套件包,因此其不僅成為機器學習與資料科學的首選語言,同時在網頁、資料爬取可科學研究等方面成為不二選擇。此外,很多入
Linux訊號signal介紹,signal()函式,sigaction()函式
訊號(signal)是一種程序間通訊機制,它給應用程式提供一種非同步的軟體中斷,使應用程式有機會接受其他程式活終端傳送的命令(即訊號)。應用程式收到訊號後,有三種處理方式:忽略,預設,或捕捉。程序收到一個訊號後,會檢查對該訊號的處理機制。如果是SIG_IGN,就忽略該訊號;如果是SIG_DFT,則會採
Linux 訊號signal處理機制
http://oilbeater.com/2012/05/09/linux-signal/ 鑑於後面把程序的形象給徹底毀掉了,我提前宣告一下,程序是有尊嚴的有節操的,當然大部分人可能也看不到毀形象那一段。為什麼介紹linux要從訊號開始呢,當然是為了保證能講明白,因為翻
如何寫好一篇產品測評報告
那我們為什麼要寫產品測評報告呢?首先,研究競品,競品是滿足同一需求的不同公司所提供出來的同類產品,競品分析能力的好壞,對產品路線、市場佔位具有非常重要的意義。其次,可以培養反推策劃案的能力,進而培養產品感覺和產品經理的思考角度。同時,還能可以發現產品功能和需求的內在邏輯,瞭解使用者的需求,提高對使用者需
程式設計師如何寫好一篇技術文章?
結合自身寫作經歷以及本次訓練營直播分享的內容,談一談寫作技巧以及程式設計師如何寫好一篇技術文章。 ## 寫作訓練營回顧 2020 年 12 月 26 日下午,我參加了一個寫作訓練營的直播,活動內容如下: ![](https://img2020.cnblogs.com/blog/859549/202012
如何寫好一篇部落格
#### 引子 眾所周知,寫部落格是一種入門門檻低,療效好,還能帶來長期影響力的過程。幾乎每一位愛上寫部落格的開發者總是樂於向其他人分享寫部落格的樂趣和益處,但即便如此,身邊的人還是很難體會到寫部落格的好處。 哪怕有時有的公司付費鼓勵員工通過知識庫的形式分享平時自己遇到的問題和解決問題時的思考,也鮮有員工
今天讀到一篇非常好的文章
文章 str 發現 戰勝 七宗罪 財富 成長 成了 過程 人的成長是一個不斷戰勝自己的過程。 這句話一直被誤讀: 他們告訴我們,人要戰勝自己,是通過:【自己的成績一次比一次好,自己的工作一次比一次好,自己的財富一年比一年多。】來體現的-----大謬
Linux系統非阻塞I/O select、poll和epoll非常好的兩篇文章
大佬寫的兩篇博文,讀懂了就算徹底瞭解Linux基本的I/O了 按照先後順序仔細閱讀。。。 https://medium.com/@copyconstruct/nonblocking-i-o-99948ad7c957 https://medium.com/@copycon
一篇非常好的c++學習方法,轉自貼吧
許多朋友都問我同一個問題,到底要不要學習C++。其實這個問題問得很沒有意義。“學C++”和“不學C++”這個二分法是沒意義的,為什麼?因為這個問題很表面,甚至很浮躁。重要的不是你掌握的語言,而是你掌握的能力,借用myan老大的話,“重要的是這個磨練過程,而不是結果,要的是你粗壯的腿,而不是你身上背
一篇非常好的Spring教程
格拉德韋爾在《異數》:並非天資超人一等,而是付出了持續不斷的努力。只要經過1萬小時的錘鍊,任何人都能從平凡變成超凡”。要成為某個領域的專家,需要10000小時:如果每天工作八個小時,一週工作五天,那麼成為一個領域的專家至少需要五年。 linux原始碼線上閱讀 各種線編譯工具
6. "undefined reference to" 問題彙總及解決方法 ------非常非常好的一篇文章
轉載地址: https://segmentfault.com/a/1190000006049907?utm_source=tuicool&utm_medium=referral在實際編譯程式碼的過程中,我們經常會遇到"undefined reference to"的問