2019年8月18日 星期日

Linux 信號signal處理機制

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

信號機制是進程之間相互傳遞消息的一種方法,信號全稱為軟中斷信號,也有人稱作軟中斷。從它的命名可以看出,它的實質和使用很象中斷。所以,信號可以說是進程控制的一部分。
        一、信號的基本概念
            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
        上述聲明格式比較複雜,如果不清楚如何使用,也可以通過下面這種類型定義的格式來使用(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
        #include
        #include
        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
        #include
         系統調用可以用來向任何進程或進程組發送任何信號。
如果參數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
        該調用使得發出調用的進程進入睡眠,直到接收到一個信號為止。該調用總是返回-1,並設置錯誤代碼為EINTR(接收到一個信號)。下面是一個簡單的範例:
        #include
        #include
        #include
        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
         統調用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
        該系統調用給進程提供了三個計時器,它們各自有其獨有的計時域,當其中任何一個到達,就發送一個相應的信號給進程,並使得計時器重新開始。三個計時器由參數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
        #include
        #include
        #include
        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

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

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

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

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);
                }
        }
}
}

沒有留言: