信號之signal函數


UNIX系統的信號機制最簡單的接口是signal函數。signal函數的功能:為指定的信號安裝一個新的信號處理函數。

#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);

復雜原型分開看:

void (* signal( int signo, void (*func)(int) )  )(int);

函數名      :signal

函數參數   :int signo, void (*func)(int)

返回值類型:void (*)(int);

signo參數是信號名(參見:http://www.cnblogs.com/nufangrensheng/p/3514157.html中UNIX系統信號Signal欄下的信號名)。func的值是常量SIG_IGN、常量SIG_DFL或當接到此信號后要調用的函數的地址。如果指定SIG_IGN,則向內核表示忽略此信號(記住有兩個信號SIGKILL和SIGSTOP不能忽略)。如果指定SIG_DFL,則表示接到此信號后的動作是系統默認動作。當指定函數地址時,則在信號發生時,調用該函數,我們稱這種處理為“捕捉”該信號。稱此函數為信號處理程序(signal handler)或信號捕捉函數(signal-catching function)。

signal的返回值是指向之前的信號處理程序的指針。(之前的信號處理程序,也就是在執行signal(signo,func)之前,對信號signo的信號處理程序)

開頭所示的signal函數原型太復雜了,如果使用下面的typedef,則可使其簡單一些:

typedef void Sigfunc(int);

然后,可將signal函數原型寫成:

Sigfunc *signal(int,Sigfunc *);

如果查看系統的頭文件<signal.h>,則很可能會找到下列形式的聲明:

#define    SIG_ERR        ( void (*) () )-1
#define    SIG_DFL        ( void (*) () )0
#define    SIG_IGN        ( void (*) () )1

這些常量可用於代替“指向函數的指針,該函數需要一個整型參數,而且無返回值”。signal的第二個參數及其返回值就可用它們表示。這些常量所使用的三個值不一定是-1,0和1。但大多數UNIX系統都使用上面所示的值。

程序清單10-1 捕捉SIGUSR1和SIGUSR2的簡單程序

#include "apue.h"

static void sig_usr(int);    /* one handler for both signals */

int
main(void)
{
    if(signal(SIGUSR1, sig_usr) == SIG_ERR)
        err_sys("can't catch SIGUSR1");
    if(signal(SIGUSR2, sig_usr) == SIG_ERR)
        err_sys("can't catch SIGUSR2");
    for(;;)
        pause();
}

static void
sig_usr(int signo)    /* argument is signal number */
{
    if(signo == SIGUSR1)
        printf("received SIGUSR1\n");
    else if (signo == SIGUSR2)
        printf("received SIGUSR2\n");
    else
        err_dump("received signal %d\n", signo);
}

pause函數,它使調用進程在接到一個信號前掛起。

我們在后台運行該程序,並且用kill(1)命令將信號傳送給它。注意,在UNIX中,殺死(kill)這個術語是不恰當的。kill(1)命令和kill(2)函數只是將一個信號送給一個進程或進程組。信號是否終止進程則取決於信號的類型,以及進程是否安排了捕捉該信號。

未命名

因為執行程序清單10-1的進程不捕捉SIGTERM信號,而針對該信號的系統默認動作是終止,所以當該進程發送SIGTERM信號后,該進程就會終止。

1、程序啟動

當執行一個程序時,所有信號的狀態都是系統默認或忽略。通常所有信號都被設置為它們的默認動作,除非調用exec的進程忽略該信號。確切地講,exec函數將原先設置為要捕捉的信號都更改為它們的默認動作,其他信號的狀態則不變(對於一個進程原先要捕捉的信號,當其執行一個新程序后,就自然不能再捕捉它了,因為信號捕捉函數的地址很可能在所執行的新程序文件中無意義)。

一個具體的例子是一個交互式shell如何處理針對后台進程的中斷和退出信號。對於一個非作業控制shell,當在后台執行一個進程時,例如:

cc main.c &

shell自動將后台進程中對中斷和退出信號的處理方式設置為忽略。於是,當按中斷鍵時就不會影響到后台進程。如果沒有執行這樣的處理,那么當按中斷鍵時,它不但會終止前台進程,還會終止所有后台進程。

很多捕捉這兩個信號的交互式程序具有下列形式的代碼:

void sig_int(int), sig_quit(int);

if(signal(SIGINT, SIG_IGN) != SIG_IGN)
    signal(SIGINT, sig_int);
if(signal(SIGQUIT, SIG_IGN) != SIG_IGN)
    signal(SIGQUIT, sig_quit);

這樣處理后,僅當信號當前未被忽略時,進程才會捕捉它們。

從signal的這兩種調用中也可以看到這種函數的限制:不改變信號的處理方式就不能確定信號的當前處理方式

2、進程創建

當一個進程調用fork時,其子進程繼承父進程的信號處理方式。因為子進程在開始時復制了父進程的存儲映像,所以信號捕捉函數的地址在子進程中是有意義的。

本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關於本書可參考:http://www.apuebook.com/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM