7、信號 - 看這一篇就夠了

七、信號

1.信號的概念

  • 信號是UNIX系統響應某些狀況而產生的事件,進程在接收到信號時會採取相應的行動。
  • 信號是因爲某些錯誤條件而產生的,比如內存段衝突、浮點處理器錯誤或者非法指令等
  • 它們由shell和終端管理器產生以引起中斷。
  • 進程可以生成信號、捕捉並響應信號或屏蔽信號

2、信號名稱

可以使用 kill -l 來查看信號的名字以及序號 ,信號是從1開始編號的,不存在0號信號。kill對於信號0有特殊的應用。

信號名稱 描述
SIGABORT 進程停止運行
SIGALRM 警告鍾
SIGFPE 浮點運算例外
SIGHUP 系統掛斷
SIGILL 非法指令
SIGINT 終端中斷
SIGKILL 停止進程(此信號不能被忽略或捕獲)
SIGPIPE 向沒有讀者的管道
SIGSEGV 無效內存段訪問
信號名稱 描述
SIGQUIT 終端退出ctrl+\
SIGTERM 正常終止
SIGUSR1 用戶定義信號1
SIGUSR2 用戶定義信號2
SIGCHLD 子進程已經停止或退出
SIGCONT 如果被停止則繼續執行
SIGSTOP 停止執行
SIGTSTP 終端停止信號
SIGTOUT 後臺進程請求進行寫操作
SIGTTIN 後臺進程請求進行讀操作

3、signal庫函數(接收信號)

  • 如果想讓程序能夠處理信號,可以使用signal庫函數
  • 函數原型
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • 參數
    • signum :信號的編號
    • handler : 中斷函數的指針

示例

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int ctrl_c_count=0;
/* 定義一個函數指針,用來保存signal調用的返回值 */
void(* old_handler)(int);
void ctrl_c(int);
int main()
{
	int c;
	old_handler = signal(SIGINT,ctrl_c);
	while ((c = getchar())!='\n');
	signal(SIGINT,old_handler);
	for(;;);
	return 0;
}

void ctrl_c(int signum)
{
	/* 信號處理完畢,信號的默認處理方式被還原,所以要重新關聯 */
	//signal(SIGINT,ctrl_c);
	++ctrl_c_count;
	printf("ctrl-c count = %d\n",ctrl_c_count);
}

4、kill函數(發送信號)

  • 進程可以通過調用kill向包括它本身在內的另一個進程發送信號。如果程序沒有發送該信號的權限,對kill的調用就將失敗。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

5、不可靠信號(1-31)

不可靠信號:信號可能會丟失,一旦信號丟失了,進程並不能知道信號丟失

  • linux信號機制基本上是從unix系統中繼承過來的。早期unix系統中的信號機制比較簡單和原始,後來在實踐中暴露出一些問題,它的主要問題是:
    • 進程每次處理信號後,就將對信號的響應設置爲默認動作。在某些情況下,將導致對信號的錯誤處理;因此,用戶如果不希望這樣的操作,那麼就要在信號處理函數結尾再一次調用signal(),重新安裝該信號。
    • 早期unix下的不可靠信號主要指的是進程可能對信號做出錯誤的反應以及信號可能丟失。
    • linux支持不可靠信號,但是對不可靠信號機制做了改進:在調用完信號處理函數後,不必重新調用該信號的安裝函數(信號安裝函數是在可靠機制上的實現)。因此,linux下的不可靠信號問題主要指的是信號可能丟失。
    • 非實時信號都不支持排隊,都是不可靠信號

6、可靠信號

可靠信號:也是阻塞信號

  • 隨着時間的發展,實踐證明了有必要對信號的原始機制加以改進和擴充。所以,後來出現的各種unix版本分別在這方面進行了研究,力圖實現"可靠信 號"。由於原來定義的信號已有許多應用,不好再做改動,最終只好又新增加了一些信號,並在一開始就把它們定義爲可靠信號,這些信號支持排隊,不會丟失。

  • 同時,信號的發送和安裝也出現了新版本:信號發送函數sigqueue()及信號安裝函數sigaction()

  • 實時信號都支持排隊,都是可靠信號。

7、信號在內核中的表示

  • 執行信號的處理動作稱爲信號遞達(Delivery),信號從產生到遞達之間的狀態,稱爲信號未決(Pending)。
  • 進程可以選擇阻塞(Block)某個信號。被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作。
  • 注意,阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作。

8、信號集操作函數

#include <signal.h>

int sigemptyset(sigset_t *set);//初始化 set 中傳入的信號集,清空其中所有信號
int sigfillset(sigset_t *set);//把信號集填1,讓 set 包含所有的信號
int sigaddset(sigset_t *set, int signo);//把信號集對應位置爲1
int sigdelset(sigset_t *set, int signo);//吧信號集對應位置爲0
int sigismember(const sigset_t *set, int signo);//判斷signal是否在信號集

9、sigprocmask

  • 功能:讀取或更改進程的信號屏蔽字
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
  • 參數:

    • how:
    參數 描述
    SIG_BLOCK set包含了我們希望添加到當前信號屏蔽字的信號,相當於mask=mask|set
    SIG_UNBLOCK set包含了我們希望添加到當前信號屏蔽字中解除阻塞的信號,相當於mask=mask&~set
    SIG_SETMASK 設置當前信號屏蔽字爲set所指向的值,相當於mask=set
    • set:如果set是非空指針,則更改進程的信號屏蔽字,參數how指示如何更改。

    • oset:如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。

    • 如果oset和set都是非空指針,則先將原來的信號屏蔽字備份到oset裏,然後根據set和how參數更改信號屏蔽字。

  • 返回值

    • 若成功則爲0,若出錯則爲-1

10、sigpending

  • 功能:sigpending讀取當前進程的未決信號集,通過set參數傳出。

  • 返回值:若成功則爲0,若出錯則爲-1

11、信號操作函數示例

#include <unistd.h>
#include <signal.h>
#include <stdio.h>

void printsigset(sigset_t *set)
{
    int i;
    for(i = 1;i < 64;i++)
    {
		if(sigismember(set,i))
            putchar('1');
        else
            putchar('0');
    }
    puts("");
}

void handler(int sig)
{
    if(sig == SIGQUIT)
    {
        sigset_t s;
        sigemptyset(&s);
        sigaddset(&s,SIGINT);
        sigprocmask(SIG_UNBLOCK,&s,NULL);
    }
    if(sig == SIGINT)
    {
        printf("recv a signal %d\n",sig);
    }
}
int main()
{
    sigset_t s;
    sigset_t p;
    signal(SIGINT,handler);
    sigemptyset(&s);
    sigaddset(&s,SIGINT);
    sigprocmask(SIG_BLOCK,&s,NULL);
    for(;;)
    {
        sigpending(&p);
        printsigset(&p);
        sleep(1);
    }
    return 0;
}

12、sigaction庫函數(接收)

  • 功能:sigaction函數用於改變進程接收到特定信號後的行爲。
  • 原型
int  sigaction(int signum,const struct sigaction *act,const struct sigaction *oldact);
  • 參數
    • signum:信號的值,可以爲除sigkill及sigstop外的任何一 個特定有效的信號(爲這兩個信號定義自己的處理函數,將導致信號安裝錯誤)
    • act:指向結構sigaction的一個實例的指針,在結構 sigaction的實例中,指定了對特定信號的處理,可以爲空,進程會以缺省方式對信號處理
    • oldact:指向的對象用來保存原來對相應信號 的處理,可指定oldact爲null。
struct sigaction 
{
    void (*sa_handler)(int);//信號處理程序,不接受額外數據,SIG_IGN 爲忽略,SIG_DFL 爲默認動作
    void (*sa_sigaction)(int, siginfo_t *, void *);//信號處理程序,能夠接受額外數據和sigqueue配合使用
    sigset_t sa_mask;//阻塞關鍵字的信號集,可以再調用捕捉函數之前,把信號添加到信號阻塞字,信號捕捉函數返回之前恢復爲原先的值。
    int sa_flags;//影響信號的行爲SA_SIGINFO表示能夠接受數據
    void (*sa_restorer)(void);
};

siginfo_t 
{
    int      si_signo;    /* Signal number */
    int      si_errno;    /* An errno value */
    int      si_code;     /* Signal code */
    int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
    pid_t    si_pid;      /* Sending process ID */
    uid_t    si_uid;      /* Real user ID of sending process */
    int      si_status;   /* Exit value or signal */
    clock_t  si_utime;    /* User time consumed */
    clock_t  si_stime;    /* System time consumed */
    sigval_t si_value;    /* Signal value */
    int      si_int;      /* POSIX.1b signal */
    void    *si_ptr;      /* POSIX.1b signal */
    int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
    int      si_timerid;  /* Timer ID; POSIX.1b timers */
    void    *si_addr;     /* Memory location which caused fault */
    int      si_band;     /* Band event */
    int      si_fd;       /* File descriptor */}
}

13、sigqueue庫函數(發送)

  • 功能:新的發送信號系統調用,主要是針對實時信號提出的支持信號帶有參數,與函數sigaction()配合使用。

  • 原型

int sigqueue(pid_t pid, int sig, const union sigval value);
  • 參數
    • pid:進程id
    • sig:確定即將發送的信號
    • value:聯合數據結構union sigval,指定了信號傳遞的參數,即通常所說的4字節值。
typedef union sigval
{ 
	int sival_int; 
	void *sival_ptr; 
}sigval_t; 

  • 採用聯合數據結構,說明siginfo_t結構中si_value要麼持有一個4字節的整數值,要麼持有一個指針,這就構成了與信號相關的數據。
  • sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。

13-1、sigaction與sigqueue示例

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void handler(int signo, siginfo_t *info, void *data)
{
    printf("receive sig = %d, si_signo = %d, si_pid = %d, si_value.sival_int = %d\n", signo, info->si_signo, info->si_pid, info->si_value.sival_int);
    
}

int main(void)
{

    pid_t pid;
    pid = fork();
    if(pid == 0)
    {
        printf("This is child process.\n");
        struct sigaction act;
        memset(&act, 0, sizeof(act));
        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;//SA_SIGINFO表示能夠接受數據
        sigaction(SIGINT, &act, NULL);
        while(1)
        {
        //讓程序不關閉
        }
    }
    else if(pid > 0)
    {
        printf("This is parent process.\n");
        sleep(1);
        union sigval value = {666};
        while(1)
        {
            sigqueue(pid, SIGINT, value);
            sleep(1);
        }
    }
    else
    {
        perror("fork()");
    }
    return 0;
}

14、abort函數

  • 功能:向進程發送sigabort信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數。
  • 原型:
#include <stdlib.h>
void abort(void);
  • 說明:即使sigabort被進程設置爲阻塞信號,調用abort()後,sigabort仍然能被進程接收。該函數無返回值。

15、alarm函數

  • 功能:專門爲sigalrm信號而設,在指定的時間seconds秒後,將向進程本身發送sigalrm信號,又稱爲鬧鐘時間。
  • 原型:
unsigned int alarm(unsigned int seconds);
  • 參數:

    • seconds:爲零,那麼進程內將不再包含任何鬧鐘時間。
  • 返回值:

    • 如果調用alarm()前,進程中已經設置了鬧鐘時間,則返回上一個鬧鐘時間的剩餘時間,否則返回0
  • 說明:

    • 進程調用alarm後,任何以前的alarm()調用都將無效。

16、setitimer函數

  • 功能setitimer()比alarm功能強大,支持3種類型的定時器

  • 原型:

int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
  • 參數:
    • which:指定定時器類型
    • value:是結構itimerval的一個實例,結構itimerval形式
    • ovalue:可不做處理。
  • 返回值:
    • 成功返回0失敗返回-1
參數 描述
itimer_real 設定絕對時間;經過指定的時間後,內核將發送SIGALRM信號給本進程
itimer_virtual 設定程序執行時間,經過指定的時間後,內核將發送SIGVTALRM信號給本進程
itimer_prof 設定進程執行以及內核因本進程而消耗的時間和,經過指定的時間後,內核將發送SIGPROF信號給本進程

專欄 《linux網絡編程》 將持續更新中…
從linux 零基礎 到 高併發服務器架構

如果我的文章能夠幫到您,可以點個贊!
您的每次 點贊、關注、收藏 都是對我最大的鼓勵!

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