APUE學習筆記 第十章 信號

第十章 信號

    信號是軟件中斷,它提供了一種處理異步事件的方法。本章對信號機制進行綜述,並說明每種信號的一般用法。

 

1、信號概念

每個信號都有一個名字,這些名字都以3個字符SIG開頭。在頭文件<signal.h>中,信號名都被定義爲正整形常量。

1.1 產生信號的條件:

(1)當用戶按某寫終端按鍵時,引發終端產生的信號。如:Ctrl+C產生SIGINT信號。

(2)硬件異常產生信號。如除零錯,無效的內存應用產生SIGSEGV信號。

(3)進程調用kill函數可將任一信號發送給另一個進程或進程組。

(4)用戶可用kill命令將信號發送給其他進程。

(5)當檢測到某種軟件條件已經發生,並應將其通知有關進程時也產生信號。如SIGPIPE在管道的讀進程已終止後,一個進程寫此管道。

1.2 在某個信號出現時,可以按下列3種方式之一進行處理:

(1) 忽略該信號。大多數信號都可以使用這種方式進行處理,但有兩種信號卻決不能被忽略:SIGKILL和SIGSTOP(只能執行系統默認動作)。

(2) 捕獲信號。通知內核在某信號發生時,調用一個用戶函數對這種時間進行處理。

(3) 執行系統默認動作。對於大多數信號的系統默認動作是終止該進程。

 

2、信號類型

程序錯誤類信號:默認動作使進程流產,產生core文件。

SIGABRT:   調用abort函數生成的信號。
SIGFPE:      浮點計算錯誤。
SIGILL:      非法指令錯誤。
SIGBUS/SIGSEGV: 硬件錯誤-非法地址訪問。
SIGEMT:    硬件錯誤
SIGSYS:       非法系統調用。
SIGTRAP:   硬件錯誤(通常爲斷點指令)。

程序終止類信號:默認動作使進程終止,我們通常要處理這類信號,做一些清理工作,句柄函數應在結束時爲此信號指定默認動作,然後再次生成該信號,使得程序終止。

SIGHUP:終端斷開連接時,生成此信號給控制進程。
SIGINT:Ctrl-C或Delete按下時,由終端驅動生成,併發送給前臺進程組中的所有進程。
SIGKILL:使程序立即終止,不能被捕獲或忽略,也不能被阻塞。
SIGQUIT:Ctrl-\,如SIGINT,並且產生core。
SIGTERM:該信號使程序終止,但是可以阻塞、捕獲、忽略。

鬧鐘類信號:通知定時器到期,默認動作是終止程序,但通常會設置句柄。

SIGALRM:alarm/setitimer函數設置定時到期後,會產生此信號。
SIGPROF:
SIGVTALRM:

I/O類信號:通知進程在描述字上發生了感興趣事件,支持信號驅動IO

SIGIO: fd準備執行輸入輸出時發送此信號。
SIGPOLL:異步I/O信號。
SIGURG:網絡收到帶外數據時可選擇生成此信號。

作業控制類信號:

SIGCHLD:   進程終止或停止時會向其父進程發送該信號,默認動作爲忽略。
SIGCONT:     使停止的進程恢復運行。
SIGSTOP:      停止進程。
SIGTSTP/SIGTTIN/SIGTTOU:

操作錯誤類信號:默認動作終止程序。

SIGPIPE:    管道破裂。
SIGXCPU/SIGXFSZ:

 

3、函數signal

UNIX系統信號機制最簡單的接口是signal函數,不過最好使用sigaction代替該函數。

#include <signal.h>
void (*signal(int signo,void (*func)(int)))(int);
//返回值:若成功,返回以前的信號處理配置;若出錯,返回SIG_ERR。
//可以使用typedef使其變得簡單一點
typedef void Sigfunc(int);
Sigfunc *signal(int,Sigfunc *);

第一個參數指定要捕獲的信號(整形常量),第二個參數指定針對前面信號值的處理handler可以是SIG_IGN(忽略該信號)、SIG_DFL(系統默認動作)或者是接到此信號後要調用的函數地址。我們稱這種處理爲捕捉該信號,稱此函數爲信號處理程序或信號捕捉函數,第二個參數是一個函數指針(處理函數),該函數指針指向的函數返回值是void,參數是int。

下面給出一個簡單得信號處理程序:

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

我們使該程序在後臺運行,而且用kill命令將信號發送給它:

 

4、中斷的系統調用

早期UNIX系統的一個特性是:如果進程在執行一個低速系統調用而阻塞期間捕獲到一個信號,則該系統調用就被中斷不再繼續執行。該系統調用返回出錯,其errno設置爲EINTR。後面的章節會更多的涉及到被中斷的系統調用。

 

5、可重入函數

進程捕捉的信號並對其進行處理時,進程正在執行的正常指令序列就被信號處理程序臨時終端,它首先執行該信號處理程序中的指令,如果從信號處理程序返回,則繼續執行在捕捉的信號時進程正在執行的正常指令序列。但是在返回控制時不會出現任何錯誤,就叫可重入函數。而不可重入的函數由於使用了一些系統資源,比如全局變量區,中斷向量表等,所以它如果被中斷的話,可能會出現問題,這類函數是不能運行在多任務環境下的。

在信號處理函數中調用某些函數可能對導致安全問題(其結果是不可預知的),下面列出了這些異步信號安全的函數,沒有列入圖中的大多數函數是不可重入的。因爲(a)已知他們使用靜態數據類型;(b)他們調用malloc或free;(c)他們是標準I/O。標準I/O庫的很多實現都以不可重入方式使用全局數據結構。

 

6、函數kill和raise

kill函數將信號發送給進程或進程組,raise函數則允許進程向自身發送信號。

#include <signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
//兩個函數返回值:若成功,返回0;若出錯,返回-1.

    調用raise(signo);相當於調用kill(getpid(),signo);

    Kill的pid參數有以下4種不同的情況:

(1) pid>0 將該信號發送給進程ID爲pid的進程

(2) pid=0 將該信號發送給於發送進程屬於同一個進程組的所有進程

(3) pid<0 將該信號發送給其進程組ID等於pid絕對值,而且發送進程具有權限向其發送信號的所有進程

(4) pid=-1 將該進程發送給發送進程有權限向它們發送信號的所有進程。

其上所說所有進程不包括系統進程集中的進程。

 

7、函數alarm和pause

函數alarm設置一個定時器,當定時器超時時,產生SIGALRM信號。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//返回值:0或以前設置的鬧鐘時間的餘留秒數。

pause函數使調用進程掛起直至捕捉到一個信號

#include <signal.h>
int pause(void);
//返回值:-1,errno設置爲EINTR。

只有執行了一個信號處理程序並從其返回時,pause才返回。此時,pause返回-1,errno設置爲EINTR。

 

8、信號集

信號集是能表示多個信號的數據結構(sigset_t),下面列出5個處理信號集的函數

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
//四個函數返回值:若成功,返回0;若出錯,返回-1.
int siggismember(const sigset_t *set,int signo);
//返回值:若真,返回1;若假,返回0.

在使用信號集之前,要對該信號集進行初始化(調用sigemptyset或者sigfillset)。

 

9、函數sigprocmask

進程的信號屏蔽字規定了當前阻塞而不能遞送給該進程的信號集。調用函數sigprocmask可以檢測或更改進程的信號屏蔽字。

#include <signal.h>
int sigprocmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);//返回值:若成功,返回0;若出錯,返回-1.

首先,若oset是非空指針,那麼進程的當前信號屏蔽字通過oset返回。其次,若set是一個非空指針,則參數how指示如何修改當前信號屏蔽字。SIG_BLOCK是或操作,SIG_SETMASK則是賦值操作。不能阻塞SIGKILL和SIGSTOP信號。如果set是空指針,則不改變該進程的信號屏蔽字。


10、函數sigpending

sigpending函數返回一信號集,對於調用進程而言,其中的各信號是阻塞不能遞送的,因而也一定是當前未決的。

#include <signal.h>
ing sigpending(sigset_t *set);
//返回值:若成功,返回0;若出錯,返回-1.

下面展示信號設置和sigprocmask實例


進程開始阻塞SIGQUIT信號,保存了當前信號屏蔽字(以便以後恢復),然後休眠5秒。在此期間所產生的退出信號SIGQUIT都被阻塞,不遞送至該進程。

5秒休眠後,檢查該信號是否是未決的,然後將SIGQUIT設置爲不再阻塞。

運行程序,在5s之內鍵入退出字符Ctril+\(產生SIGQUIT信號),然後在第二個5s之內再次鍵入退出字符。

 

11、 函數sigaction

sigaction函數的功能是檢查或修改與制定信號相關聯的處理動作。此函數取代了UNIX早期版本使用的signal函數。

#include <signal.h>
int sigaction(int signo,const struct sigction *restrict act,struct sigaction *restrict oact);//返回值:若成功,返回0;若出錯,返回-1.

參數signo是要檢測或修改其具體動作的信號編號。若act指針非空,則根據參數act修改其動作。若oact指針非空,則由oact指針返回該信號的上一個動作。

此函數使用下列結構:

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

sa_handler字段包含一個信號捕捉函數的地址。

sa_mask字段說明了一個信號集,在調用該信號捕捉函數之前,這一信號集要加到進程的信號屏蔽字中。僅當從信號捕捉函數返回時將進程的信號屏蔽字恢復爲原先值。

sa_sigaction字段是一個替代的信號處理程序,當sa_flags設置爲SA_SIGINFO時,使用該信號處理程序。

sa_flags字段指定對信號進行處理的各個選項。

通常按下列方式調用信號處理程序:

void handler(int signo);

在設置了SA_SIGINFO標誌,那麼按下列凡是調用信號處理程序:

void handler(int signo,siginfo_t *info,void *context);

下面使用sigaction實現signal函數,它力圖阻止被中斷的系統調用重啓動

typedef void Sigfunc(int);
Sigfunc* mysignal(int signo,Sigfunc *func)
{

     struct sigaction    act,oact;
     act.sa_handler = func;  
     sigemptyset(&act.sa_mask);  
     act.sa_flags = 0;
     if(signo == SIGALRM)   
     {
 #ifdef SA_INTERRUPT
         act.sa_flags |= SA_INTERRUPT;
 #endif
     }
     else   
     {
 #ifdef  SA_RESTART
         act.sa_flags |= SA_RESTART;
 #endif
     }
     if(sigaction(signo,&act,&oact)<0)
         return (SIG_ERR);
     return (oact.sa_handler);
 }

 

12、 函數sigsetjmp和siglongjmp

之前說明了setjmp和longjmp函數可以用於非局部轉移,sigsetjmp跟siglongjmp指定了對信號屏蔽字的作用。

在信號處理程序中進行非局部轉移時應當使用這兩個函數。

#include <setjmp.h>
int sigsetjmp(sigjmp_buf env,int savemask);
//返回值:若直接調用,返回0;若從siglongjmp調用返回,則返回非0.
void siglongjmp(sigjmp_buf env,int val);

與setjmp和longjmp函數唯一的區別是sigsetjmp增加了一個參數savemask。如果savemask非0,則sigsetjmp在env中保存在env中保存進程的當前信號屏蔽字。調用siglongjmp時,從已經保存的env中恢復保存的信號屏蔽字。


 

13、函數sigsuspend

sigsuspend用於在接收到某個信號之前,臨時用sigmask替換進程的信號屏蔽字,並暫停進程執行,直到捕捉到一個信號而且從該信號處理程序返回,並且進程的信號屏蔽字設置爲調用sigsuspend之前的值。

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
//返回值:-1,並將errno設置爲EINTR。

進程的屏蔽字設置爲由sigmask指向的值。

下面顯示了保護代碼臨界區,使其不被特定信號中斷的正確方法

#include "apue.h"

static void    sig_int(int);

int
main(void)
{
    sigset_t    newmask, oldmask, waitmask;

    pr_mask("program start: ");

    if (signal(SIGINT, sig_int) == SIG_ERR)
        err_sys("signal(SIGINT) error");
    sigemptyset(&waitmask);
    sigaddset(&waitmask, SIGUSR1);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);

    /*
     * Block SIGINT and save current signal mask.
     */
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        err_sys("SIG_BLOCK error");

    /*
     * 代碼臨界區
     */
    pr_mask("in critical region: ");

    /*
     * Pause, allowing all signals except SIGUSR1.
     */
    if (sigsuspend(&waitmask) != -1)
        err_sys("sigsuspend error");

    pr_mask("after return from sigsuspend: ");

    /*
     * Reset signal mask which unblocks SIGINT.
     */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");

    /*
     * And continue processing ...
     */
    pr_mask("program exit: ");

    exit(0);
}

static void
sig_int(int signo)
{
    pr_mask("\nin sig_int: ");
}

下面是程序運行結果:

 

14、函數abort

abort函數的功能是使程序異常終止

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

此函數將SIGABRT信號發送給調用進程。讓進程捕捉SIGABRT信號目的是在進程終止之前由其執行所需的清理操作。默認情況是終止調用進程。

 

15、函數system

POSIX.1要求system函數忽略SIGINT和SITQUIT信號,阻塞SIGCHLD。

 

16、函數sleep

此函數使調用進程被掛起,直到滿足下列條件之一:

(1)已經經過seconds所指定的牆上時鐘時間。

(2)調用進程捕捉到一個信號並從信號處理程序返回。

#include<unistd.h>
unsigned int sleep(unsigned int seconds);


17、函數sigqueue

之前學過kill,raise,alarm,abort等功能稍簡單的信號發送函數,現在我們學習一種新的功能比較強大的信號發送函數sigqueue.

#include <sys/types.h>
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval val)
//調用成功返回 0;否則,返回 -1。

sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(當然也支持前32種),支持信號帶有參數,與函數sigaction()配合使用。

sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是一個聯合數據結構union sigval,指定了信號傳遞的參數,即通常所說的4字節值。

typedef union sigval {
    int  sival_int;
  void *sival_ptr;
}sigval_t;

sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。如果signo=0,將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用於檢查pid的有效性以及當前進程是否有權限向目標進程發送信號。

在調用sigqueue時,sigval_t指定的信息會拷貝到對應sig 註冊的3參數信號處理函數的siginfo_t結構中,這樣信號處理函數就可以處理這些信息了。由於sigqueue系統調用支持發送帶參數信號,所以比kill()系統調用的功能要靈活和強大得多。

 

參考:http://www.cnblogs.com/runnyu/p/4641346.html

https://blog.csdn.net/men_wen/article/details/61204064

https://www.cnblogs.com/mickole/p/3191804.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章