1. 程式人生 > >【Linux多程序通訊】訊號

【Linux多程序通訊】訊號

注意區分訊號與訊號量之間的區別。

一、什麼是訊號

用過Windows的我們都知道,當我們無法正常結束一個程式時,可以用工作管理員強制結束這個程序,但這其實是怎麼實現的呢?同樣的功能在Linux上是通過生成訊號和捕獲訊號來實現的,執行中的程序捕獲到這個訊號然後作出一定的操作並最終被終止。 訊號是UNIX/Linux系統響應某些條件而產生的一個事件,接收到該訊號的程序會相應地採取一些行動。通常訊號是由一個錯誤產生的。但它們還可以作為程序間通訊或修改行為的一種方式,明確地由一個程序傳送給另一個程序。一個訊號的產生叫生成,接收到一個訊號叫捕獲。
二、訊號的種類 訊號的名稱是在標頭檔案signal.h中定義的,訊號都以SIG開頭,常用的訊號並不多,常用的訊號如下:


更多的訊號型別可檢視附錄表。

三、訊號的處理——signal函式 程式可用使用signal函式來處理指定的訊號,主要通過忽略和恢復其預設行為來工作。signal函式的原型如下:
#include <signal.h>  
void (*signal(int sig, void (*func)(int)))(int);  

這是一個相當複雜的宣告,耐心點看可以知道signal是一個帶有sig和func兩個引數的函式,func是一個型別為void (*)(int)的函式指標。signal函式的返回值是一個與func相同型別的函式指標,指向先前指定訊號處理函式的函式指標。準備捕獲的訊號的引數由sig給出,接收到的指定訊號後要呼叫的函式由引數func給出。

在 Linux Programmer's Manual 上,有更清楚的解釋:

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

其實這個函式的使用是相當簡單的,通過下面的例子就可以知道。注意訊號處理函式的原型必須為void func(int)(裡面的 int 是接收到的訊號值,說明一個訊號處理函式可以處理多個訊號,只需依據傳進來的訊號值加以判斷再寄予不同的處理即可)或者是下面的特殊值:     SIG_IGN:忽略訊號
    SIG_DFL:恢復訊號的預設行為



下面用一個例子來說明:

#include <signal.h>  
#include <stdio.h>  
#include <unistd.h>  
  
void ouch(int sig)  
{  
    printf("\nOUCH! - I got signal %d\n", sig);  
    //恢復終端中斷訊號SIGINT的預設行為  
    (void) signal(SIGINT, SIG_DFL);  
}  
  
int main()  
{  
    //改變終端中斷訊號SIGINT的預設行為,使之執行ouch函式  
    (void) signal(SIGINT, ouch);  
    while(1)  
    {  
        printf("Hello World!\n");  
        sleep(1);  
    }  
    return 0;  
}  

輸出如下:
Hello World!
Hello World!
Hello World!
Hello World!
^COUCH, I got signal 2
Hello World!
Hello World!
Hello World!
^C

第一次的 ctrl+c 被捕捉到後執行了 ouch 函式,第二次的 ctrl+c 結束了程序。如我們所願。 四、訊號處理——sigaction函式 前面我們看到了signal函式對訊號的處理,但是一般情況下我們可以使用一個更加健壯的訊號介面——sigaction函式。
The behavior of signal() varies across UNIX versions, and has also
varied historically across different versions of Linux.  Avoid its
use: use sigaction(2) instead.

它的原型為:
#include <signal.h>  
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);  

該函式與signal函式一樣,用於設定與訊號sig關聯的動作。oldact如果不是空指標的話,就用它來儲存原先對該訊號的動作的位置,act不為空時則用於設定指定訊號的動作。 sigaction結構體定義在signal.h中:
The sigaction structure is defined as something like:

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

       On some architectures a union is involved: do not assign to both
       sa_handler and sa_sigaction.

       The sa_restorer field is not intended for application use.  (POSIX
       does not specify a sa_restorer field.)  Some further details of
       purpose of this field can be found in sigreturn(2).

       sa_handler specifies the action to be associated with signum and may
       be SIG_DFL for the default action, SIG_IGN to ignore this signal, or
       a pointer to a signal handling function.  This function receives the
       signal number as its only argument.

       If SA_SIGINFO is specified in sa_flags, then sa_sigaction (instead of
       sa_handler) specifies the signal-handling function for signum.  This
       function receives the signal number as its first argument, a pointer
       to a siginfo_t as its second argument and a pointer to a ucontext_t
       (cast to void *) as its third argument.  (Commonly, the handler
       function doesn't make any use of the third argument.  See
       getcontext(3) for further information about ucontext_t.)

       sa_mask specifies a mask of signals which should be blocked (i.e.,
       added to the signal mask of the thread in which the signal handler is
       invoked) during execution of the signal handler.  In addition, the
       signal which triggered the handler will be blocked, unless the
       SA_NODEFER flag is used.

       sa_flags specifies a set of flags which modify the behavior of the
       signal. It is formed by the bitwise OR of zero or more other flags.
sa_flags,除了 SA_SIGINFO,通常可以取以下的值:

下面給出使用 sigaction() 的例子:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>

void ouch(int sig)
{
	printf("OUCH, I got signal %d\n", sig);
}

int main()
{
	//改變終端中斷訊號SIGINT的預設行為,使之執行ouch函式
	struct sigaction act;
	act.sa_handler = ouch;
	//建立空的訊號遮蔽字,即不遮蔽任何訊號
	sigemptyset(&act.sa_mask);
	//恢復終端中斷訊號SIGINT的預設行為
	act.sa_flags = SA_RESETHAND;
	sigaction(SIGINT, &act, 0);
	while(1)
	{
		printf("Hello World!\n");
		sleep(1);
	}
}
輸出結果與之前的一樣。 注意sigaction函式在預設情況下是不被重置的,如果要想它重置,則sa_flags就要為SA_RESETHAND。 下面再舉個例子說明 sa_mask 的作用:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>

void ouch(int sig)
{
	printf("OUCH, I got signal %d\n", sig);
	sleep(5);
}

int main()
{
	
  //改變終端中斷訊號SIGINT的預設行為,使之執行ouch函式
  struct sigaction act;
  act.sa_handler = ouch;//建立空的訊號遮蔽字,即不遮蔽任何訊號
  sigemptyset(&act.sa_mask);
  sigaddset(&act.sa_mask, SIGQUIT); //在訊號處理函式執行階段將會遮蔽掉SIGQUIT訊號(ctrl+\),完畢後處理
  act.sa_flags = 0;
  sigaction(SIGINT, &act, 0);
  while(1){printf("Hello World!\n");sleep(1);}
}



輸出如下:
Hello World!
Hello World!
Hello World!
^COUCH, I got signal 2
^\Quit (core dumped)
先按下ctrl+c ,然後馬上ctrl+\,程式是不會馬上終止的,即等到handler處理完畢才會處理 SIGQUIT 訊號。
關於sigaction函式,還有更多細節,這裡不做過多的分析。 五、傳送訊號 上面說到的函式都是一些程序接收到一個訊號之後怎麼對這個訊號作出反應,即訊號的處理的問題,有沒有什麼函式可以向一個程序主動地發出一個訊號呢?我們可以通過兩個函式kill和alarm來發送一個訊號。
1、kill函式 先來看看kill函式,程序可以通過kill函式向包括它本身在內的其他程序傳送一個訊號,如果程式沒有傳送這個訊號的許可權,對kill函式的呼叫就將失敗,而失敗的常見原因是目標程序由另一個使用者所擁有。想一想也是容易明白的,你總不能控制別人的程式吧,當然超級使用者root,這種上帝般的存在就除外了。 kill函式的原型為:
#include <sys/types.h>  
#include <signal.h>  
int kill(pid_t pid, int sig);  

它的作用把訊號sig傳送給程序號為pid的程序,成功時返回0。 kill呼叫失敗返回-1,呼叫失敗通常有三大原因: 1、給定的訊號無效(errno = EINVAL) 2、傳送許可權不夠( errno = EPERM ) 3、目標程序不存在( errno = ESRCH ) 2、alarm函式 這個函式跟它的名字一樣,給我們提供了一個鬧鐘的功能,程序可以呼叫alarm函式在經過預定時間後向自己傳送一個SIGALRM訊號。 alarm函式的型如下:
#include <unistd.h>  
unsigned int alarm(unsigned int seconds);  

alarm函式用來在seconds秒之後安排傳送一個SIGALRM訊號,如果seconds為0,將取消所有已設定的鬧鐘請求。 下面實踐一下:
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

static bool flag = true;

void handler(int sig)
{
	if(SIGALRM == sig)
	{
		printf("alarm, I got signal %d\n", sig);
	}
	else
	{
		printf("quit, I got signal %d\n", sig);
		flag = false;
	}
}

int main()
{
	pid_t pid;
	pid = fork();
	if(pid < 0)
	{
		printf("fork() error\n");
	}
	else if(pid > 0)
	{
		sleep(5);
		kill(pid, SIGQUIT);
		exit(0);
	}

	struct sigaction act;
	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGALRM, &act, 0);
	sigaction(SIGQUIT, &act, 0);

	while(flag)
	{
		alarm(1);
		pause();
	}

	printf("Over\n");
	
	return 0;
}
以上程式先 fork(),子程序每隔1秒收到一次 SIGALRM 訊號,5秒後父程序向子程序傳送 SIGQUIT 訊號,flag 被修改,子程序退出了 while 迴圈,然後結束。 PS:這裡用了 handler() 處理了兩個訊號。 關於 pause() :pause() causes the calling process (or thread) to sleep until a signal is delivered that either terminates the process or causes the invocation of a signal-catching function.
六、附錄——訊號表

如果程序接收到上面這些訊號中的一個,而事先又沒有安排捕獲它,程序就會終止。
此外,還有其他的一些訊號,如下: