1. 程式人生 > >Linux程序設計學習筆記——異步信號處理機制

Linux程序設計學習筆記——異步信號處理機制

基本概念 erro 驗證 添加 uid 函數 count ubun generate

轉載請註明出處: http://blog.csdn.net/suool/article/details/38453333

Linux常見信號與處理

基本概念

Linux的信號是一種進程間異步的通信機制,在實現上一種軟中斷。信號能夠導致一個正在執行的進程被異步打斷,轉而去處理一個突發事件。異步事件不可預知,僅僅能通過一些特定方式預防。或者說,當該異步事件發生時依據原來的設定完畢對應的操作。

信號本質

信號是在軟件層次上對中斷機制的一種模擬。在原理上,一個進程收到一個信號與處理器收到一個中斷請求能夠說是一樣的。信號是異步的,一個進程不必通過不論什麽操作來等待信號的到達,其實,進程也不知道信號究竟什麽時候到達。

信號是進程間通信機制中唯一的異步通信機制,能夠看作是異步通知。通知接收信號的進程有哪些事情發生了。

信號機制經過POSIX實時擴展後,功能更加強大。除了基本通知功能外,還能夠傳遞附加信息。

信號來源

信號事件的發生有兩個來源:硬件來源(比方我們按下了鍵盤或者其他硬件故障);軟件來源,最經常使用發送信號的系統函數是kill, raise, alarm和setitimer以及sigqueue函數,軟件來源還包含一些非法運算等操作。


Ubuntu下全部支持的信號例如以下:

技術分享

詳細的信號說明參見這些博文:

http://blog.csdn.net/cloudtech/article/details/3758962

http://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html

這些信號在Linux源代碼的signum.h中的定義例如以下:

/* Signals.  */
#define	SIGHUP		1	/* Hangup (POSIX).  */
#define	SIGINT		2	/* Interrupt (ANSI).  */
#define	SIGQUIT		3	/* Quit (POSIX).  */
#define	SIGILL		4	/* Illegal instruction (ANSI).  */
#define	SIGTRAP		5	/* Trace trap (POSIX).  */
#define	SIGABRT		6	/* Abort (ANSI).  */
#define	SIGIOT		6	/* IOT trap (4.2 BSD).  */
#define	SIGBUS		7	/* BUS error (4.2 BSD).  */
#define	SIGFPE		8	/* Floating-point exception (ANSI).  */
#define	SIGKILL		9	/* Kill, unblockable (POSIX).  */
#define	SIGUSR1		10	/* User-defined signal 1 (POSIX).  */
#define	SIGSEGV		11	/* Segmentation violation (ANSI).  */
#define	SIGUSR2		12	/* User-defined signal 2 (POSIX).  */
#define	SIGPIPE		13	/* Broken pipe (POSIX).  */
#define	SIGALRM		14	/* Alarm clock (POSIX).  */
#define	SIGTERM		15	/* Termination (ANSI).  */
#define	SIGSTKFLT	16	/* Stack fault.  */
#define	SIGCLD		SIGCHLD	/* Same as SIGCHLD (System V).  */
#define	SIGCHLD		17	/* Child status has changed (POSIX).  */
#define	SIGCONT		18	/* Continue (POSIX).  */
#define	SIGSTOP		19	/* Stop, unblockable (POSIX).  */
#define	SIGTSTP		20	/* Keyboard stop (POSIX).  */
#define	SIGTTIN		21	/* Background read from tty (POSIX).  */
#define	SIGTTOU		22	/* Background write to tty (POSIX).  */
#define	SIGURG		23	/* Urgent condition on socket (4.2 BSD).  */
#define	SIGXCPU		24	/* CPU limit exceeded (4.2 BSD).  */
#define	SIGXFSZ		25	/* File size limit exceeded (4.2 BSD).  */
#define	SIGVTALRM	26	/* Virtual alarm clock (4.2 BSD).  */
#define	SIGPROF		27	/* Profiling alarm clock (4.2 BSD).  */
#define	SIGWINCH	28	/* Window size change (4.3 BSD, Sun).  */
#define	SIGPOLL		SIGIO	/* Pollable event occurred (System V).  */
#define	SIGIO		29	/* I/O now possible (4.2 BSD).  */
#define	SIGPWR		30	/* Power failure restart (System V).  */
#define SIGSYS		31	/* Bad system call.  */
#define SIGUNUSED	31

#define	_NSIG		65	/* Biggest signal number + 1
				   (including real-time signals).  */
與信號中斷處理相關的術語:

發送信號:產生信號。有多種發送信號的方式。一個進程向還有一個進程發送特定的信號,內核向用戶進程發送一個信號,一個進程想自己發送信號。

安裝中斷:設置信號到來時的不再運行默認操作。而是運行自己定義的代碼,即期望某個信號到來時讓進程運行對應的中斷服務。

遞送信號:一個信號被OS發送到目標進程。

捕獲信號:被遞送的信號在目標進程引起某段處理程序的運行

屏蔽信號:進程告訴OS臨時不接受信號。若在屏蔽期間向進程發送了某信號。該信號不會被捕獲,可是在屏蔽結束後。該信號將被捕獲。

忽略信號:進程被遞送到目標進程。可是目標進程不會處理,直接丟棄。

未決信號:信號已經產生,可是由於目標進程屏蔽信號導致臨時不能被目標進程捕獲的信號。

可靠信號與不可靠信號:

Linux信號機制基本上是從Unix系統中繼承過來的。把那些建立在早期機制上的信號叫做"不可靠信號",信號值小於SIGRTMIN的信號都是不可靠信號。這就是"不可靠信號"的來源。它的主要問題是:

  • 進程每次處理信號後,就將對信號的響應設置為默認動作。

    在某些情況下,將導致對信號的錯誤處理;因此,用戶假設不希望這種操作,那麽就要在信號處理函數結尾再一次調用signal(),又一次安裝該信號。

  • 信號可能丟失,後面將對此具體闡述。
    因此,早期unix下的不可靠信號主要指的是進程可能對信號做出錯誤的反應以及信號可能丟失。

Linux支持不可靠信號,可是對不可靠信號機制做了改進:在調用完信號處理函數後,不必又一次調用該信號的安裝函數(信號安裝函數是在可靠機制上的實現)。

因此。Linux下的不可靠信號問題主要指的是信號可能丟失。

可靠信號

隨著時間的發展,實踐證明了有必要對信號的原始機制加以改進和擴充。因為原來定義的信號已有很多應用。不好再做修改。終於僅僅好又新添加了一些信號。並在一開始就把它們定義為可靠信號。這些信號支持排隊,不會丟失。同一時候,信號的發送和安裝也出現了新版本號:信號發送函數sigqueue()及信號安裝函數sigaction()。可是,POSIX僅僅對可靠信號機制應具有的功能以及信號機制的對外接口做了標準化,對信號機制的實現沒有作詳細的規定。

信號值位於SIGRTMIN和SIGRTMAX之間的信號都是可靠信號。可靠信號克服了信號可能丟失的問題。Linux在支持新版本號的信號安裝函數sigation()以及信號發送函數sigqueue()的同一時候。仍然支持早期的signal()信號安裝函數,支持信號發送函數kill()。

註:不要有這種誤解:由sigqueue()發送、sigaction安裝的信號就是可靠的。其實,可靠信號是指後來加入的新信號(信號值位於SIGRTMIN及SIGRTMAX之間)。不可靠信號是信號值小於SIGRTMIN的信號

信號的可靠與不可靠僅僅與信號值有關。與信號的發送及安裝函數無關。眼下linux中的signal()是通過sigation()函數實現的,因此。即使通過signal()安裝的信號,在信號處理函數的結尾也不必再調用一次信號安裝函數。同一時候。由signal()安裝的實時信號支持排隊,相同不會丟失。

對於眼下linux的兩個信號安裝函數:signal()及sigaction()來說,它們都不能把SIGRTMIN曾經的信號變成可靠信號(都不支持排隊,仍有可能丟失,仍然是不可靠信號),並且對SIGRTMIN以後的信號都支持排隊。這兩個函數的最大差別在於,經過sigaction安裝的信號都能傳遞信息給信號處理函數(對全部信號這一點都成立)。而經過signal安裝的信號卻不能向信號處理函數傳遞信息。

對於信號發送函數來說也是一樣的。

實時信號與非實時信號

早期Unix系統僅僅定義了32種信號,Ret hat7.2支持64種信號,編號0-63(SIGRTMIN=31,SIGRTMAX=63)。將來可能進一步添加,這須要得到內核的支持。前32種信號已經有了提前定義值。每一個信號有了確定的用途及含義,而且每種信號都有各自的缺省動作。如按鍵盤的CTRL ^C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。後32個信號表示實時信號。等同於前面闡述的可靠信號。這保證了發送的多個實時信號都被接收。實時信號是POSIX標準的一部分,可用於應用進程。

非實時信號都不支持排隊。都是不可靠信號;實時信號都支持排隊,都是可靠信號。

信號生命周期

1、在目標進程安裝信號

2、信號被某個進程產生,同一時候設置此信號的目標進程

3、信號在目標進程被註冊

4、信號在進程中註銷

5、信號生命終止

信號的發送

信號的發送不是直接由發送的而是有OS轉發的。

發送信號的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

1、kill()
#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。註:對於pid<0時的情況,對於哪些進程將接受信號。各種版本號說法不一,事實上非常easy,參閱內核源代碼kernal/signal.c就可以,上表中的規則是參考red hat 7.2。

2、raise()
#include <signal.h>
int raise(int signo)
向進程本身發送信號,參數為即將發送的信號值。調用成功返回 0;否則,返回 -1。

3.unalarm定時

4、alarm()
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
專門為SIGALRM信號而設,在指定的時間seconds秒後。將向進程本身發送SIGALRM信號,又稱為鬧鐘時間。進程調用alarm後。不論什麽曾經的alarm()調用都將無效。假設參數seconds為零,那麽進程內將不再包括不論什麽鬧鐘時間。


返回值,假設調用alarm()前,進程中已經設置了鬧鐘時間。則返回上一個鬧鐘時間的剩余時間。否則返回0。

5、setitimer()
#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。

函數setitimer()和getitimer依據逝去時間、在用戶進程運行時間、總的運行時間設置/獨處超時定時器的信息,定時器將在超時後產生對應的信號。

gettitimer()函數用來獲取和設置上述的三種定時器。

以下是一個演示樣例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>

int main(void)
{	
	struct itimerval setvalue;
	setvalue.it_interval.tv_sec=3;
	setvalue.it_interval.tv_usec=0;
	setvalue.it_value.tv_sec=3;
	setvalue.it_value.tv_usec=0;
	setitimer(ITIMER_REAL,&setvalue,NULL);
	
	setvalue.it_interval.tv_sec=3;
	setvalue.it_interval.tv_usec=0;
	setvalue.it_value.tv_sec=3;
	setvalue.it_value.tv_usec=0;
	setitimer(ITIMER_VIRTUAL,&setvalue,NULL);

	setvalue.it_interval.tv_sec=3;
	setvalue.it_interval.tv_usec=0;
	setvalue.it_value.tv_sec=1;
	setvalue.it_value.tv_usec=0;
	setitimer(ITIMER_PROF,&setvalue,NULL);

	while(1)
	{
		struct itimerval value;
		getitimer(ITIMER_REAL,&value);
		printf("ITIMER_REAL: internal:%ds%dms,remain:%ds%dms\n",value.it_interval.tv_sec,value.it_interval.tv_usec,value.it_value.tv_sec,value.it_value.tv_usec);

		getitimer(ITIMER_VIRTUAL,&value);
		printf("ITIMER_VIRTUAL:internal:%ds%dms,remain:%ds%dms\n",value.it_interval.tv_sec,value.it_interval.tv_usec,value.it_value.tv_sec,value.it_value.tv_usec);

		getitimer(ITIMER_PROF,&value);
		printf("ITIMER_PROF: internal:%ds%dms,remain:%ds%dms\n\n",value.it_interval.tv_sec,value.it_interval.tv_usec,value.it_value.tv_sec,value.it_value.tv_usec);
		sleep(1);
	}
}

技術分享


6、abort()
#include <stdlib.h>
void abort(void);

向進程發送SIGABORT信號,默認情況下進程會異常退出。當然可定義自己的信號處理函數。

即使SIGABORT被進程設置為堵塞信號,調用abort()後,SIGABORT仍然能被進程接收。

該函數無返回值。


進程對信號的響應

進程能夠通過三種方式來響應一個信號:(1)忽略信號,即對信號不做不論什麽處理,當中,有兩個信號不能忽略:SIGKILL及SIGSTOP;(2)捕捉信號。定義信號處理函數。當信號發生時,運行對應的處理函數;(3)運行缺省操作,Linux對每種信號都規定了默認操作。

信號的安裝

假設進程要處理某一信號。那麽就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值的動作之間的映射關系,即進程將要處理哪個信號。該信號被傳遞給進程時,將運行何種操作。

linux主要有兩個函數實現信號的安裝:signal()、sigaction()。當中signal()在可靠信號系統調用的基礎上實現, 是庫函數。它僅僅有兩個參數,不支持信號傳遞信息。主要是用於前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,當然。sigaction()相同支持非實時信號的安裝。sigaction()優於signal()主要體如今支持信號帶有參數。

signal安裝信號

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

以下是一個使用signal安裝信號的演示樣例,該程序會在shell終端向其發送特定的信號:

/*************************************************************************
> File Name: kill_signal.c
> Author:SuooL 
> Mail:[email protected] || [email protected]
> Website:http://blog.csdn.net/suool | http://suool.net
> Created Time: 2014年08月09日 星期六 10時14分14秒
> Description: 使用signal安裝信號演示樣例
************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sig_usr(int sig);

int main(int argc,char *argv[])
{ 
    int i = 0;
    if(signal(SIGUSR1,sig_usr) == SIG_ERR)  // 安裝處理信號,並指示該信號到來時運行的操作
    printf("Cannot catch SIGUSR1\n");       // 同一時候推斷信號是否為SIGUSE1
    if (signal(SIGUSR2,sig_usr) == SIG_ERR) // 安裝信號處理。推斷信號是否為SIGUSE2 
    printf("Cannot catch SIGUSR2\n");
    while(1) 
    {
        printf("%2d\n", i);                 // 等待信號的到來
        pause(); 
        /* pause until signal handler
        has processed signal */
        i++;
    }
    return 0;
}

void sig_usr(int sig)                       // 信號處理函數
{
    if (sig == SIGUSR1)
    printf("Received SIGUSR1\n");
    else if (sig == SIGUSR2)
    printf("Received SIGUSR2\n");
    else
    printf("Undeclared signal %d\n", sig);
}

技術分享

sigaction安裝信號

#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;       // 影響信號行為的特殊標誌
                  void (*sa_restorer)(void);      // 過時不用
                  }

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{
              ...
              }...
            ... ...
            }
      }
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;	
		}

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)。

註:非常多文獻在闡述該標誌位時都覺得,假設設置了該標誌位,就必須定義三參數信號處理函數。實際不是這種,驗證方法非常easy:自己實現一個單一參數信號處理函數,並在程序中設置該標誌位。能夠察看程序的執行結果。實際上,能夠把該標誌位看成信號是否傳遞參數的開關。假設設置該位,則傳遞參數。否則,不傳遞參數。

以下是一個演示樣例程序。七其完畢的基本功能能夠使用signal替代:

/*************************************************************************
	> File Name: sigaction.c
	> Author:SuooL 
	> Mail:[email protected] || [email protected]
	> Website:http://blog.csdn.net/suool | http://suool.net
	> Created Time: 2014年08月09日 星期六 10時30分14秒
	> Description: 使用sigaction函數的演示樣例程序
 ************************************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void myHandler(int sig);
int main(int argc,char *argv[])
{ 
  	struct sigaction act, oact;

  	act.sa_handler = myHandler;
  	sigemptyset(&act.sa_mask); /*initial. to empty mask*/
  	act.sa_flags = 0;
  	sigaction(SIGUSR1, &act, &oact); // 設置信號處理方式
    while (1) 
	{ 
		printf("Hello world.\n"); 
		pause();           // 等待信號發生
	}
  }

  void myHandler(int sig)       // 信號處理程序
  {
    printf("I got signal: %d.\n", sig);
  }
  // to end program, <Ctrl + \> to generate SIGQUIT


技術分享

signal因為有系統漏洞,不建議使用。可是我在本機測試的時候沒有發現該漏洞。預計是被修補了。

信號集及其操作

信號集被定義為一種數據類型:

typedef struct {       // 此結構體占領32*32=1024bit,每一個bit相應一個信號,val[0]的0-31位相應經常使用的1-31號
			unsigned long sig[_NSIG_WORDS];
			} sigset_t
信號集用來描寫敘述信號的集合,linux所支持的所有信號能夠所有或部分的出如今信號集中,主要與信號堵塞相關函數配合使用。

以下是為信號集操作定義的相關函數:

信號集用來描寫敘述信號的集合。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指向的信號集中。

sigprocmask()用來設置進程的屏蔽信號集,函數的使用方式請參閱相關文檔.

信號集操作演示樣例

1.信號集存儲結構

/*************************************************************************
	> File Name: sig_setmember.c
	> Author:SuooL 
	> Mail:[email protected] || [email protected]
	> Website:http://blog.csdn.net/suool | http://suool.net
	> Created Time: 2014年08月09日 星期六 10時53分14秒
	> Description: 將某個信號加入到信號集後,該命令值的變化測試
 ************************************************************************/
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
int output(sigset_t set);

int main()
{
	sigset_t set;
	printf("after empty the set:\n");
	sigemptyset(&set);
	output(set);

	printf("after add signo=2:\n");
	sigaddset(&set,2);
	output(set);
	printf("after add signo=10:\n");
	sigaddset(&set,10);
	output(set);
	
	sigfillset(&set);
	printf("after  fill all:\n");
	output(set);
	return 0;
}

int output(sigset_t set)
{
	int i=0;
	for(i=0;i<1;i++)	//can test i<32
	{
		printf("0x%8x\n",set.__val[i]);
		if((i+1)%8==0)
			printf("\n");
	}
}

技術分享

2.進程屏蔽信號應用演示樣例

/*************************************************************************
> File Name: sig_setmember.c
> Author:SuooL 
> Mail:[email protected] || [email protected]
> Website:http://blog.csdn.net/suool | http://suool.net
> Created Time: 2014年08月09日 星期六 10時59分14秒
> Description: 進程屏蔽信號應用
************************************************************************/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

static void sig_quit(int);

int main(int argc,char *argv[])
{
    sigset_t    newmask, oldmask, pendmask;
    if (signal(SIGQUIT, sig_quit) == SIG_ERR)     // 安裝信號處理函數
    { 
        perror("signal");
        exit(EXIT_FAILURE);
    }
    printf("install sig_quit\n");

    // Block SIGQUIT and save current signal mask.
    sigemptyset(&newmask);             // 清理全部信號集
    sigaddset(&newmask, SIGQUIT);       // 加入sigquit到信號集

    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
    {                                    // 設置進程屏蔽newmask,原來值讀到oldmask
     perror("signal");
     exit(EXIT_FAILURE);
    }
    printf("Block SIGQUIT,wait 15 second\n");
    sleep(15);   /* SIGQUIT here will remain pending */   // 等待15秒
    if (sigpending(&pendmask) < 0)            // 保存屏蔽信號
    { 
        perror("signal");
        exit(EXIT_FAILURE);
    }

    if (sigismember(&pendmask, SIGQUIT)) // 檢測SIGQUIT是否在信號集
    printf("\nSIGQUIT pending\n");

    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)  // 替換進程掩碼.即清除SIGQUIT
    { 
        perror("signal");
        exit(EXIT_FAILURE);
    }
    printf("SIGQUIT unblocked\n");

    sleep(15);   /* SIGQUIT here will terminate with core file */
    return 0;
}

static void sig_quit(int signo)             // 信號處理函數
{
    printf("caught SIGQUIT,the process will quit\n");
    if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)    // 再次安裝
    { 
        perror("signal");
        exit(EXIT_FAILURE);
    }
}

技術分享

3.進程在捕獲信號的過程中屏蔽信號

使用sigaction安裝信號時,結構體struct sigaction成員sa_mask表示在處理信號過程中加入到當前屏蔽信號集中的信號.一下測試了程序在各個階段輸出了對應的信號集的值,從而查看屏蔽信號集的變化:

/*************************************************************************
> File Name: sigaction_setr.c
> Author:SuooL 
> Mail:[email protected] || [email protected]
> Website:http://blog.csdn.net/suool | http://suool.net
> Created Time: 2014年08月09日 星期六 11時13分14秒
> Description: 進程屏蔽信號應用
************************************************************************/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

int output(sigset_t set)
{
	printf("set.val[0]=%x\n",set.__val[0]);  // 輸出信號集
}
void handler(int sig)        // sigalrm信號處理函數
{
	int i;
	sigset_t sysset;
	printf("\nin handler sig=%d\n",sig);
	sigprocmask(SIG_SETMASK,NULL,&sysset);
	output(sysset);			//in handler to see the process mask set
	printf("return\n");
}
int main(int argc,char *argv[])
{
	struct sigaction act;
	sigset_t set,sysset,newset;
	
	sigemptyset(&set);
	sigemptyset(&newset);
	sigaddset(&set,SIGUSR1);     // 將sigusr1加入到set中
	sigaddset(&newset,SIGUSR2);  // sigusr2加入到newset
	
	printf("\nadd SIGUSR1,the value of set:");
	output(set);
	
	printf("\nadd SIGUSR2,the value of newset:");
	output(newset);
	
	printf("\nafter set proc block set ,and then read to sysset\n");
	sigprocmask(SIG_SETMASK,&set,NULL);       // 設置當前信號屏蔽集
	sigprocmask(SIG_SETMASK,NULL,&sysset);    // 讀取驗證設置是否成功
	printf("system mask is:\n");
	output(sysset);

	printf("install SIGALRM,and the act.sa_mask is newset(SIGUSR2)\n");
	act.sa_handler=handler;
	act.sa_flags=0;
	act.sa_mask=newset;             // 處理過程中將newset加入到集合
	sigaction(SIGALRM,&act,NULL);   // 安裝SIGALRM信號
	pause();                        // 等待信號
	
	printf("after exec ISR\n");
	sigemptyset(&sysset);
	sigprocmask(SIG_SETMASK,NULL,&sysset);   // 信號完畢處理,又一次讀取值
	output(sysset);
}

技術分享

等待信號

使用pause()函數和sigsuspend函數.

每一個進程都有一個用來描寫敘述哪些信號遞送到進程時將被堵塞的信號集。該信號集中的全部信號在遞送到進程後都將被堵塞。

以下是與信號堵塞相關的幾個函數:

#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。

結構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 */
            };

三參數信號處理函數中第二個參數的說明性描寫敘述:

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信號有意義 */
}

實際上。除了前三個元素外。其它元素組織在一個聯合結構中。在聯合數據結構中。又依據不同的信號組織成不同的結構。凝視中提到的對某種信號有意義指的是,在該信號的處理函數中能夠訪問這些域來獲得與信號相關的有意義的信息,僅僅只是特定信號僅僅對特定信息感興趣而已。



信號應用演示樣例

基本功能

本演示樣例程序創建了兩個進程:

父進程運行文件復制操作(請使用M級別以上的文件),假設收到SIGUSR1信號,將打印當前的復制進度,因此父進程須要安裝SIGUSR1信號.

子進程每隔一段時間(時間由ualrm函數產生的SIGALRM信號決定)向父進程發送SIGUSR1信號,因此子進程須要安裝SIGALRM信號
.

源代碼及分析

/*************************************************************************
> File Name: sig_setmember.c
> Author:SuooL 
> Mail:[email protected] || [email protected]
> Website:http://blog.csdn.net/suool | http://suool.net
> Created Time: 2014年08月09日 星期六 11時13分14秒
> Description: 信號應用
************************************************************************/

#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<stdlib.h>

//the copy fie size must>M          // 請使用M級別的文件
int count;				//current copy number, 當前復制大小
int file_size;			//the file size 文件大小,因在中斷無法傳遞普通參數,故用全局變量
void sig_alarm(int arg);        // 處理alarm信號
void sig_usr(int sig);          // 處理普通信號SIGUSR1

int main(int argc,char *argv[])
{
	pid_t pid;
	int i;
	int fd_src,fd_des;
	char buf[128];       //in order to infirm the problem, buf can set small 復制暫時操作空間
	
	if(argc!=3)     // 參數檢查
	{
		printf("check the format:comm src_file des_file\n");
		return -1;
	}
	if( ( fd_src=open(argv[1],O_RDONLY) )==-1 )
	{
		perror("open file src");     // 僅僅讀打開源文件
		exit(EXIT_FAILURE);
	}
	
	file_size=lseek(fd_src,0,SEEK_END);   // 獲取文件大小
	lseek(fd_src,0,SEEK_SET);             // 又一次設置讀寫文件頭
	
	if( (fd_des=open(argv[2],O_RDWR|O_CREAT,0644) )==-1 ) 
	{                             // 以讀寫方式打開目標文件,如不存在則創建
		perror("open fd_fdes");
		exit(EXIT_FAILURE);
	}
	
	if( (pid=fork())==-1)     // 創建子進程
	{
		perror("fork");
		exit(EXIT_FAILURE);
	}
	else if(pid>0)           // 在父進程中,拷貝文件處於子進程信號請求
	{                        // 並在信號處理時打印復制進程
		signal(SIGUSR1,sig_usr);    // 向父進程安裝SIGUSR1信號
		do
		{
			memset(buf,‘\0‘,128);
			if((i=read(fd_src,buf,1))==-1)	//the copy number may modify
			{                       // 復制數據,為驗證結果能夠改變每次復制的大小
				perror("read");
				exit(EXIT_FAILURE);
			}
			else if(i==0)  // 復制完成,向子進程發送SIGINT信號
			{              // 終止子進程
				kill(pid,SIGINT);
				break;
			}
			else
			{
				if(write(fd_des,buf,i)==-1)  // 否則運行復制操作
				{
					perror("write");
					exit(EXIT_FAILURE);
				}
				count+=i;      // 更新已經復制的大小
				//usleep(1);
			}
		}while(i!=0);
		
		wait(pid,NULL,0);      // 等待子進程退出
		exit(EXIT_SUCCESS);
	}

	else if(pid==0)      // 子進程中每隔一段時發送信號給父進程,請求父進程復制進度
	{
		usleep(1);	//wait for parent to install signal
		
		signal(SIGALRM,sig_alarm);  // 安裝信號
		ualarm(1,1);	//if alarm ,in sig_alarm function to install again
		while(1)              // 一直運行
		{
			;
		}
		exit(EXIT_SUCCESS);
	}

}

void sig_alarm(int arg)
{
	//alarm(1);			//if alarm may add this line
	kill(getppid(),SIGUSR1);       //在子進程的SIGQLRM信號處理中向父進程發送SIGUSR1信號
}

void sig_usr(int sig)      // 父進程的SIGUSR信號處理函數
{
	float i;
	i=(float)count/(float)file_size;	//得出復制進程
	printf("curent over :%0.0f%%\n",i*100);
}


技術分享

NEXT

System V 進程通信

多線程編程


轉載請註明出處: http://blog.csdn.net/suool/article/details/38453333

Linux程序設計學習筆記——異步信號處理機制