close

 

http://myblog-maurice.blogspot.tw/2011/12/linux-signal.html

 

Linux 信號signal處理機制

 

 

      信號機制是進程之間相互傳遞消息的一種方法,信號全稱為軟中斷信號,也有人稱作軟中斷。從它的命名可以看出,它的實質和使用很象中斷。所以,信號可以說是進程式控制制的一部分。

        一、信號的基本概念

            1、基本概念

        軟中斷信號(signal,又簡稱為信號)用來通知進程發生了非同步事件。進程之間可以互相通過系統調用kill發送軟中斷信號。內核也可以因為內部事件而給進程發送信號,通知進程發生了某個事件。注意,信號只是用來通知某進程發生了什麼事件,並不給該進程傳遞任何資料。

         到信號的進程對各種信號有不同的處理方法。處理方法可以分為三類:第一種是類似中斷的處理常式,對於需要處理的信號,進程可以指定處理函數,由該函數來處 理。第二種方法是,忽略某個信號,對該信號不做任何處理,就象未發生過一樣。第三種方法是,對該信號的處理保留系統的預設值,這種缺省操作,對大部分的信 號的缺省操作是使得進程終止。進程通過系統調用signal來指定進程對某個信號的處理行為。

        在進程表的表項中有一個軟中斷信號域,該域中每一位元對應一個信號,當有信號發送給進程時,對應位置位元。由此可以看出,進程對不同的信號可以同時保留,但對於同一個信號,進程並不知道在處理之前來過多少個。

        2、信號的類型

        發出信號的原因很多,這裡按發出信號的原因簡單分類,以瞭解各種信號:

        1 與進程終止相關的信號。當進程退出,或者子進程終止時,發出這類信號。
        2 與進程例外事件相關的信號。如進程越界,或企圖寫一個唯讀的記憶體區域(如程式正文區),或執行一個特權指令及其他各種硬體錯誤。
        3 與在系統調用期間遇到不可恢復條件相關的信號。如執行系統調用exec時,原有資源已經釋放,而目前系統資源又已經耗盡。
        4 與執行系統調用時遇到非預測錯誤條件相關的信號。如執行一個並不存在的系統調用。
        5 在使用者態下的進程發出的信號。如進程調用系統調用kill向其他進程發送信號。
        6 與終端交互相關的信號。如使用者關閉一個終端,或按下break鍵等情況。
        7 跟蹤進程執行的信號。

      

 

名 字

說明

01

SIGHUP

掛起(hangup

02

SIGINT

中斷,當使用者從鍵盤按ctrl+c

03

SIGQUIT

退出,當使用者從鍵盤按quit鍵時

04

SIGILL

非法指令

05

SIGTRAP

跟蹤陷阱(trace trap),啟動進程,跟蹤代碼的執行

06

SIGIOT

IOT指令

07

SIGEMT

EMT指令

08

SIGFPE

浮點運算溢出

09

SIGKILL

殺死、終止進程

10

SIGBUS

匯流排錯誤

11

SIGSEGV

段違例(segmentation? violation),進程試圖去訪問其虛位址空間以外的位置

12

SIGSYS

系統調用中參數錯,如系統調用號非法

13

SIGPIPE

向某個非讀管道中寫入資料

14

SIGALRM

鬧鐘。當某進程希望在某時間後接收信號時發此信號

15

SIGTERM

軟體終止(software? termination

16

SIGUSR1

使用者自訂信號1

17

SIGUSR2

使用者自訂信號2

18

SIGCLD

某個子進程死

19

SIGPWR

電源故障

 

        注意 信號SIGKILLSIGSTOP既不能被捕捉,也不能被忽略。信號SIGIOTSIGABRT是一個信號。可以看出,同一個信號在不同的系統中值可能不一樣,所以建議最好使用為信號定義的名字,而不要直接使用信號的值。

二、有關信號的系統調用

     系統調用signal是進程用來設定某個信號的處理方法,系統調用kill是用來發送信號給指定進程的。這 兩個調用可以形成信號的基本操作。後兩個調用pausealarm是通過信號實現的進程暫停和計時器,調用alarm是通過信號通知進程計時器到時。所 以在這裡,我們還要介紹這兩個調用。

        1signal 系統調用

        系統調用signal用來設定某個信號的處理方法。該調用聲明的格式如下:
        void (*signal(int signum, void (*handler)(int)))(int);
        在使用該調用的進程中加入以下頭檔:
        #include <signal.h>

        上述聲明格式比較複雜,如果不清楚如何使用,也可以通過下面這種類型定義的格式來使用(POSIX的定義):
        typedef void (*sighandler_t)(int);
        sighandler_t signal(int signum, sighandler_t handler);
        但這種格式在不同的系統中有不同的類型定義,所以要使用這種格式,最好還是參考一下連線手冊。

        在調用中,參數signum指出要設置處理方法的信號。第二個參數handler是一個處理函數,或者是
        SIG_IGN:忽略參數signum所指的信號。
        SIG_DFL:恢復參數signum所指信號的處理方法為預設值。

        傳遞給信號處理常式的整數參數是信號值,這樣可以使得一個信號處理常式處理多個信號。系統調用signal返回值是指定信號signum前一次的處理常式或者錯誤時返回錯誤代碼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:~$

        2kill 系統調用

        系統調用kill用來向進程發送一個信號。該調用聲明的格式如下:
        int kill(pid_t pid, int sig);
        在使用該調用的進程中加入以下頭檔:
        #include <sys/types.h>
        #include <signal.h>

         系統調用可以用來向任何進程或進程組發送任何信號。
如果參數pid是正數,那麼該調用將信號sig發送到進程號為pid的進程。
如果pid等於0,那麼信 sig將發送給當前進程所屬進程組裡的所有進程。
如果參數pid等於-1,信號sig將發送給除了進程1和自身以外的所有進程。
如果參數pid小於- 1,信號sig將發送給屬於進程組-pid的所有進程。如果參數sig0,將不發送信號。該調用執行成功時,返回值為0;錯誤時,返回-1,並設置相應 的錯誤代碼errno。下面是一些可能返回的錯誤代碼:
        EINVAL:指定的信號sig無效。
        ESRCH:參數pid指定的進程或進程組不存在。注意,在進程表項中存在的進程,可能是一個還沒有被wait收回,但已經終止執行的僵死進程。
        EPERM 進程沒有權力將這個信號發送到指定接收信號的進程。因為,一個進程被允許將信號發送到進程pid時,必須擁有root權力,或者是發出調用的進程的UID EUID與指定接收的進程的UID或保存使用者IDsavedset-user-ID)相同。如果參數pid小於-1,即該信號發送給一個組,則該錯誤 表示組中有成員進程不能接收該信號。

        3pause系統調用

        系統調用pause的作用是等待一個信號。該調用的聲明格式如下:
        int pause(void);
        在使用該調用的進程中加入以下頭檔:
        #include <unistd.h>

        該調用使得發出調用的進程進入睡眠,直到接收到一個信號為止。該調用總是返回-1,並設置錯誤代碼為EINTR(接收到一個信號)。下麵是一個簡單的範例:

        #include <unistd.h>
        #include <stdio.h>
        #include <signal.h>
        void sigroutine(int unused) {
        printf("Catch a signal SIGINT ");
        }

        int main() {
        signal(SIGINT, sigroutine);
        pause();
        printf("receive a signal ");
        }

        在這個例子中,程式開始執行,就象進入了閉環一樣,這是因為進程正在等待信號,當我們按下Ctrl-C時,信號被捕捉,並且使得pause退出等候狀態。

4alarm setitimer系統調用

        系統調用alarm的功能是設置一個計時器,當計時器計時到達時,將發出一個信號給進程。該調用的聲明格式如下:
        unsigned int alarm(unsigned int seconds);
        在使用該調用的進程中加入以下頭檔:
        #include <unistd.h>

         統調用alarm安排內核為調用進程在指定的seconds秒後發出一個SIGALRM的信號。如果指定的參數seconds0,則不再發送 SIGALRM信號。後一次設定將取消前一次的設定。該調用返回值為上次定時調用到發送之間剩餘的時間,或者因為沒有前一次定時調用而返回0

        注意,在使用時,alarm只設定為發送一次信號,如果要多次發送,就要多次使用alarm調用。

        對於alarm,這裡不再舉例。現在的系統中很多程式不再使用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:參數valueovalue是無效的指針。
        EINVAL:參數which不是ITIMER_REALITIMER_VIRTITIMER_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

三、參考代碼

參考 1.

#include<stdio.h>

 

char buf[]={"check lock!\n"};

main()

{

        int i,p1,p2,fd;

        fd=creat("lock.dat",0644);

        write(fd,buf,20);

        while((p1=fork())==-1);

        if(p1==0)

        {

                lockf(fd,1,0);

                for (i=1;i<=3;i++)

                        printf("child1!\n");

                lockf(fd,0,0);

        }

        else{while((p2=fork())==-1);

        if (p2==0)

        {

                lockf(fd,1,0);

                for (i=1;i<=4;i++)

                        printf("child2!\n");

                lockf(fd,0,0);

        }

        else printf("parrent!\n");

        }

        close(fd);

}

 

參考 2

#include <stdio.h>

#include <unistd.h>

#include <signal.h>

 

int waite;

 

static void start(){

   waite=0;

}

 

//自訂中斷調用函數

static void waiting(){

   while(waite==1);

}

 

main(){

   int pid1,pid2;

   while((pid1=fork())==-1);           

   if(pid1>0){

      printf("chilld process 1 is %d\n",pid1);

      while((pid2=fork())==-1);

      if(pid2>0){

         printf("child process 2 is %d\n",pid2);

         printf("please press 'delete'\n");

         waite=1;

         if(signal(SIGUSR1,start)==SIG_ERR);

         else{

            alarm(5);

            signal(SIGALRM,start);//alarm函數使用的信號

         }

         waiting();

         kill(pid1,16);//child 1 子進程發送16號中斷

         kill(pid2,17);//child 2 子進程發送17號中斷

         wait(0);//等待兩個子進程結束

         wait(0);

         printf("parent process is killed\n");

         exit(0);

      }

      else{

         waite=1;

         signal(17,start);//接受父進程發送的17號中斷,調用信號中斷函數start()

         waiting();

         printf("child 2 is killed\n");

         exit(0);

      }

   }

   else{

     waite=1;

     signal(16,start);//接受父進程發送的16號中斷,調用信號中斷函數start()

     waiting();

     printf("child 1 is killed\n");

     exit(0);

   }

}

 

參考三

#include <stdio.h>

#include <signal.h>

 

int main()

{

int i,j,stop();

signal(SIGINT,stop);

if(i=fork())

{

        if(j=fork())

        {

                //signal(SIGINT,SIG_IGN);

                sleep(10);

                kill(i,15);

                kill(j,16);

                wait(0);

                wait(0);

                printf("Parent process is killed!\n");

        }

        else {

                signal(16,SIG_IGN);

                sleep(10);

                //signal(16,stop);

                printf("Child process 3 is killed!\n");

                exit(0);

        }     

}

else {

        signal(15,SIG_IGN);

        sleep(10);

        //signal(15,stop);

        printf("Child process 1 is killed!\n");

        exit(0);

}

}

 

stop()

{

//printf("del key is got!\n");

}

 

 

參考四

#include <stdio.h>

#include <unistd.h>

#include <stdio.h>

#include <signal.h>

 

int pid1,pid2;

 

main()

{

int fd[2];

char OutPipe[100],InPipe[100];

pipe(fd);

for(;;)

{

        while((pid1=fork())==-1);

        if(pid1 == 0)

        {

                lockf(fd[1],1,0);

                sprintf(OutPipe,"Child process 1 is sending message!\n");

                write(fd[1],OutPipe,50);

                sleep(3);

                lockf(fd[1],0,0);

                exit(0);

        }

        else {

                while((pid2=fork())==-1);

                if(pid2 == 0)

                {

                        lockf(fd[1],1,0);

                        sprintf(OutPipe,"Child process 2 is sending message!\n");

                        write(fd[1],OutPipe,50);

                        sleep(3);

                        lockf(fd[1],0,0);

                }

                else {

                        wait(0);

                        read(fd[0],InPipe,50);

                        printf("%s\n",InPipe);

                }

        }

}

}

 

 

 

 

 

 

 

 

 

 

 

sigaction(查詢或設置信號處理方式)

 

相關函數

signalsigprocmasksigpendingsigsuspend

表標頭檔

#include<signal.h>

定義函數

int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);

函數說明

sigaction()會依參數signum指定的信號編號來設置該信號的處理函數。參數signum可以指定SIGKILLSIGSTOP以外的所有信號。
如參數結構sigaction定義如下


struct sigaction
{
void (*sa_handler) (int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer) (void);
}


sa_handler此參數和signal()的參數handler相同,代表新的信號處理函數,其他意義請參考signal()
sa_mask 用來設置在處理該信號時暫時將sa_mask 指定的信號擱置。
sa_restorer 此參數沒有使用。
sa_flags 用來設置信號處理的其他相關操作,下列的數值可用。
OR 運算(|)組合
A_NOCLDSTOP : 如果參數signumSIGCHLD,則當子進程暫停時並不會通知父進程
SA_ONESHOT/SA_RESETHAND:當調用新的信號處理函數前,將此信號處理方式改為系統預設的方式。
SA_RESTART:被信號中斷的系統調用會自行重啟
SA_NOMASK/SA_NODEFER:在處理此信號未結束前不理會此信號的再次到來。
如果參數oldact不是NULL指標,則原來的信號處理方式會由此結構sigaction 返回。

返回值

執行成功則返回0,如果有錯誤則返回-1

錯誤代碼

EINVAL 參數signum 不合法, 或是企圖攔截SIGKILL/SIGSTOPSIGKILL信號
EFAULT 參數actoldact指針地址無法存取。
EINTR 此調用被中斷

 

1.  #include <stdio.h>  

2.  #include <signal.h>  

3.  #include <unistd.h>  

4.    

5.  void show_handler(int sig)  

6.  {  

7.      printf("I got signal %d\n", sig);  

8.      int i;  

9.      for(i = 0; i < 5; i++) {  

10.         printf("i = %d\n", i);  

11.         sleep(1);  

12.     }  

13. }  

14.    

15. int main(void)  

16. {  

17.     int i = 0;  

18.     struct sigaction act, oldact;  

19.     act.sa_handler = show_handler;  

20.     sigaddset(&act.sa_mask, SIGQUIT); //見注(1)  

21.     act.sa_flags = SA_RESETHAND | SA_NODEFER; //見注(2)  

22.     //act.sa_flags = 0; //見注(3)  

23.   

24.     sigaction(SIGINT, &act, &oldact);  

25.     while(1) {  

26.         sleep(1);  

27.         printf("sleeping %d\n", i);  

28.         i++;  

29.     }  

30. }  

 

 

sigaddset(增加一個信號至信號集)

 

相關函數

sigemptysetsigfillsetsigdelsetsigismember

表標頭檔

#include<signal.h>

定義函數

int sigaddset(sigset_t *set,int signum);

函數說明

sigaddset()用來將參數signum 代表的信號加入至參數set 信號集裡。

返回值

執行成功則返回0,如果有錯誤則返回-1

錯誤代碼

EFAULT 參數set指針地址無法存取
EINVAL 參數signum非合法的信號編號

 

 

sigprocmask(查詢或設置信號遮罩)

 

相關函數

signalsigactionsigpendingsigsuspend

表標頭檔

#include<signal.h>

定義函數

int sigprocmask(int how,const sigset_t *set,sigset_t * oldset);

函數說明

sigprocmask()可以用來改變目前的信號遮罩,其操作依參數how來決定
SIG_BLOCK 新的信號遮罩由目前的信號遮罩和參數set 指定的信號遮罩作聯集
SIG_UNBLOCK 將目前的信號遮罩刪除掉參數set指定的信號遮罩
SIG_SETMASK 將目前的信號遮罩設成參數set指定的信號遮罩。
如果參數oldset不是NULL指標,那麼目前的信號遮罩會由此指標返回。

返回值

執行成功則返回0,如果有錯誤則返回-1

錯誤代碼

EFAULT 參數setoldset指針地址無法存取。
EINTR 此調用被中斷

實例:

 

C代碼  收藏代码

1.   #include <signal.h>  

2.   #include <stdio.h>  

3.   #include <string.h>  

4.   #include <errno.h>  

5.   #include <unistd.h>  

6.   #define PROMPT "你想終止程式嗎?"  

7.   char *prompt=PROMPT;  

8.   void ctrl_c_op(int signo)  

9.   {  

10.  write(STDERR_FILENO,prompt,strlen(prompt));  

11.  }  

12.  int main()  

13.  {  

14.  struct sigaction act;  

15.  act.sa_handler=ctrl_c_op;  

16.  sigemptyset(&act.sa_mask);  

17.  act.sa_flags=0;  

18.  if(sigaction(SIGINT,&act,NULL)<0)  

19.  {  

20.  fprintf(stderr,"Install Signal Action Error%s\n\a",strerror(errno));  

21.  exit(1);  

22.  }  

23.  while(1);  

24.  }  

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 立你斯 的頭像
    立你斯

    立你斯學習記錄

    立你斯 發表在 痞客邦 留言(0) 人氣()