1. 程式人生 > 其它 >Linux C/C++程式設計:sigaction

Linux C/C++程式設計:sigaction

技術標籤:# C++

sigaction

理論


/*
* 功能:檢查或者修改與指定訊號相關聯的處理動作
* 引數: signum---------指出要捕獲的訊號
* 		act-------------指定新的訊號處理方式
*       oldact----------不為NULL時輸出先前訊號的處理方式
*/
int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
struct sigaction {
     //sa_handler引數和signal()的引數handler相同,此引數主要用來對訊號舊的安裝函式signal()處理形式的支援
//sa_sigaction新的訊號安裝機制,處理函式被呼叫的時候,不但可以得到訊號編號,而且可以獲悉被呼叫的原因以及產生問題的上下文的相關資訊。 // 除了可以是使用者自定義的處理函式外,還可以為SIG_DFL(採用預設的處理方式),也可以為SIG_IGN(忽略訊號)。 void (*sa_handler)(int); //原型是一個引數為int,返回型別為void的函式指標。引數即為訊號值,所以訊號不能傳遞除訊號值之外的任何資訊。 void (*sa_sigaction)(int, siginfo_t *, void *); //原型是一個帶三個引數,型別分別為int,struct siginfo *,void *,返回型別為void的函式指標。第一個引數為訊號值;第二個引數是一個指向struct siginfo結構的指標,此結構中包含訊號攜帶的資料值;第三個引數沒有使用。
//指定在訊號處理程式執行過程中,哪些訊號應當被阻塞。預設當前訊號本身被阻塞。 // 注意:sa_mask指定的訊號阻塞的前提條件,是在由sigaction()安裝訊號的處理函式執行過程中由sa_mask指定的訊號才被阻塞。 sigset_t sa_mask; // 設定訊號處理的其他相關操作 // ------ SA_RESETHAND:如果置位並且捕獲了訊號,則將訊號的配置重置為SIG_DFL,並且在進入訊號處理程式時不會阻塞該訊號。 // ------ SA_RESTART:如果訊號中斷了程序的某個系統呼叫,則系統自動啟動該系統呼叫 // ------ SA_NODEFER :一般情況下, 當訊號處理函式執行時,核心將阻塞該給定訊號。但是如果設定了 SA_NODEFER標記, 那麼在該訊號處理函式執行時,核心將不會阻塞該訊號
//------- SA_SIGINFO :當設定了該標誌位時,表示訊號附帶的引數可以傳遞到訊號處理函式中。即sa_sigaction指定訊號處理函式,如果不設定SA_SIGINFO,訊號處理函式同樣不能得到訊號傳遞過來的資料,在訊號處理函式中對這些資訊的訪問都將導致段錯誤。 int sa_flags; // 已過時,POSIX不支援它,不應再使用。 void (*sa_restorer)(void); }

sa_handler是用於處理訊號的函式,sa_sigaction也是類似的函式;在有些架構中,這兩者使用了union結構,因此最好不要同時使用;一般使用sa_handler函式即可。sg_mask是掩碼,用於宣告需要遮蔽的訊號集。sa_flags是一個訊號的標誌集合,指明瞭處理訊號的行為

實踐

註冊訊號處理函式

#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <bits/signum.h>

void sig_handler (int sig) {
    if (sig == SIGALRM) {
        puts ("+1s...");
        alarm (1);  // 重新設定1秒定時器
    }
}

int main() {

    struct sigaction sa;
    bzero (&sa, sizeof (sa) );
    sa.sa_handler = sig_handler;
    assert (sigaction (SIGALRM, &sa, NULL) != -1);

    alarm(1);

    while (1) {
        sleep (1);
    }
    return 0;
}

在這裡插入圖片描述

kill與訊號

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

void show_handler(int signo)
{
    printf("recieve signo =%2d\n",signo);
}
int main(int argc,char ** argv)
{
    int i;
    struct sigaction act,oldact;
    act.sa_handler=show_handler;
   // act.sa_flags = SA_NOMASK;
    sigemptyset(&act.sa_mask);
    sigaction(SIGUSR1,&act,&oldact);
    sigaction(SIGINT,&act,&oldact);
    kill(getpid(),SIGUSR1);
    kill(getpid(),SIGUSR1);
    kill(getpid(),SIGINT);
    kill(getpid(),SIGINT);
    sleep(10);
    return 0;
}

在這裡插入圖片描述

sa_flags=SA_RESTART

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
 
static void sig_usr(int signum)
{
    if (signum == SIGUSR1)
    {
        printf("SIGUSR1 received\n");
    }
    else if (signum == SIGUSR2)
    {
        printf("SIGUSR2 received\n");
    }
    else
    {
        printf("signal %d received\n", signum);
    }
}
 
int main(void)
{
    char buf[512];
    int  n;
    struct sigaction sa_usr;
    sa_usr.sa_flags = 0;
    sa_usr.sa_handler = sig_usr;   //訊號處理函式
  //   sa_usr.sa_flags=SA_RESTART;  // 註釋或者不註釋
    
    sigaction(SIGUSR1, &sa_usr, NULL);
    sigaction(SIGUSR2, &sa_usr, NULL);
    
    printf("My PID is %d\n", getpid());
    
    while (1)
    {
        if ((n = read(STDIN_FILENO, buf, 511)) == -1)
        {
            if (errno == EINTR)
            {
                printf("read is interrupted by signal\n");
            }
        }
        else
        {
            buf[n] = '\0';
            printf("%d bytes read: %s\n", n, buf);
        }
    }
    
    return 0;
}

註釋:

  1. 第一個終端執行:
[[email protected] test]# g++ -o test test.cpp
[[email protected] test]# ./test
My PID is 1939
  1. 第二個終端執行:
[[email protected] ~]# kill -USR1 1939
  1. 第一個終端結果
SIGUSR1 received
read is interrupted by signal

不註釋:

  1. 第一個終端執行:
[[email protected] test]# g++ -o test test.cpp
[[email protected] test]# ./test
My PID is 2087
  1. 第二個終端執行:
[[email protected] ~]# kill -USR1 2087
  1. 第一個終端結果
SIGUSR1 received

比較這兩個例子的結果,體會sa_usr.sa_flags=SA_RESTART;的含義。

SA_RESTART:被訊號中斷的系統呼叫會自行重啟,因此沒有read is interrupted by signal列印。


#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <zconf.h>


void WrkProcess(int nsig)
{
    printf("WrkProcess .I get signal.%d threadid:%d/n",nsig,pthread_self());


    int i=0;
    while(i<5){
        printf("%d/n",i);
        sleep(1);
        i++;
    }
}
int main()
{
    struct sigaction act,oldact;
    act.sa_handler  = WrkProcess;
//      sigaddset(&act.sa_mask,SIGQUIT);
//      sigaddset(&act.sa_mask,SIGTERM)
    act.sa_flags = SA_NODEFER | SA_RESETHAND;
//        act.sa_flags = 0;
    sigaction(SIGINT,&act,&oldact);
    printf("main threadid:%d/n",pthread_self());
    while(1)sleep(5);
    return 0;
}

1)執行改程式時,ctrl+c,第一次不會導致程式的結束。而是繼續執行,當用戶再次執行ctrl+c的時候,程式採用結束。

  • 第一次產生ctrl+c訊號的時候,該訊號被自己設定的訊號處理函式進行了處理。在處理過程中,由於我們設定了SA_RESETHAND標誌位,又將該訊號的處理函式設定為預設的訊號處理函式(系統預設的處理方式為IGN),所以在第二次傳送ctrl+d訊號的時候,是由預設的訊號處理函式處理的,導致程式結束;

2)如果對程式稍微進行一下改動,則會出現另外一種情況。

改動為:act.sa_flags = SA_NODEFER;

經過這種改變之後,無論對ctrl+d操作多少次,程式都不會結束。

  • 我們去掉了SA_RESETHAND了標誌位,導致程式中所有的ctrl+d訊號均是由我們自己的訊號處理函式來進行了處理,所以我們傳送多少次ctrl+c訊號程式都不會退出;

用sigaction實現的signal函式

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

void  handler();
void (*Signal(int signo,void(*func)(int)))(int);

int main()
{
    int i;

    Signal(SIGALRM,handler);
    alarm(5);

    for(i=1;i<8;i++){
        printf("sleep is -----%d\n",i);
        sleep(1);
    }
    return 0;
}

void  handler()
{
    printf("hello\n");
}

void (*Signal(int signo,void(*func)(int)))(int){
    struct sigaction act, oact;

    sigemptyset(&act.sa_mask);
    act.sa_handler = func;
    //對除SIGALRM以外的所有訊號,我們都有意嘗試設定SA_RESTART標誌,於是這些訊號中斷的系統呼叫都能重啟動。*/
    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;
}

sigprocmask & sigpending

一個程序的訊號遮蔽字規定了當前阻塞而不能送遞給該程序的訊號集。
呼叫函式sigprocmask可以檢測或更改其訊號遮蔽字,或者在一個步驟中同時執行檢測和更改。

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
static void int_hander(int s)
{
    printf("Catch a signal sigint\n");
}
int
main(void)
{
    int i;
    struct sigaction act, oact;
    act. sa_handler = int_hander;
    sigemptyset(&act. sa_mask);    //設定為空的sa_mask。這意味著在處理程式執行時沒有訊號被阻塞---------清空sa_mask遮蔽字,只在func工作時有效
    act. sa_flags = 0;  // 預設值,使用預設屬性
    sigaction(SIGINT, &act, &oact);
//signal(SIGINT, SIG_IGN);
    while(1){
        for(i=0; i<5; i++){
            write(1, ".", 1);
            sleep(1);
        }
        write(1, "\n", 1);
    }
    sigaction(SIGINT, &oact, NULL); //恢復成原始狀態
    return 0;
}

在這裡插入圖片描述

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

void termination_handler(int signum)
{
    printf("Hello from handler\n");
    sleep(1);
}

int main (void)
{
    //描述與SIGINT訊號相關聯的舊動作和新動作的結構(鍵盤上的Ctrl+c)。
    struct sigaction new_action,old_action;

    //在new_action結構中設定處理程式
    new_action.sa_handler = termination_handler;
    //設定為空的sa_mask。這意味著在處理程式執行時沒有訊號被阻塞。
    sigemptyset(&new_action.sa_mask);
    //阻塞SEGTERM訊號。
    // 這意味著當處理程式執行時,SIGTERM訊號被忽略
    sigaddset(&new_action.sa_mask,SIGTERM);
    //從sa_flag中移除任何標記。
    new_action.sa_flags = 0;

    //讀取與SIGINT相關聯的舊訊號(鍵盤,參見signal(7))
    sigaction(SIGINT,&old_action, NULL);

    //如果舊的處理程式不是SIG_IGN(它只是一個“忽略”訊號的處理程式)
    if (old_action.sa_handler != SIG_IGN)
    {
        //用new_action描述的訊號處理程式替換SIGINT的訊號處理程式
        sigaction(SIGINT,&new_action, NULL);
    }

    while(1)
    {
        printf("In the loop\n");
        sleep(100);
    }
    return 0;
}

在這裡插入圖片描述

Linux 程序------sigaction 函式解析
Linux下的訊號處理

常見訊號:
訊號名稱 訊號說明 預設處理
SIGALRM timer到期,有alarm或setitimer 程序終止
SIGCHLD 子程序停止,繼續或終止 結束終止併產生core檔案(除錯用)
SIGHUP 終端關閉時產生這個訊號 程序終止
SIGINT 終端輸入了中斷字元ctrl+c 程序終止
SIGKILL 不能被使用者捕捉的訊號 程序終止
SIGPIPE 管道讀者已不在,或往已斷開的socket寫資料 程序終止
SIGSEGV 記憶體非法訪問,段錯誤 程序終止併產生core檔案
SIGSTOP 不能用使用者捕捉的訊號 程序暫停執行
SIGTERM 由kill函式呼叫產生 程序終止
SIGUSR1 使用者自定義訊號 程序終止
SIGUSR2 使用者自定義訊號 程序終止

當我們按下ctrl+c的時候,操作為:向系統傳送SIGINT訊號,SIGINT訊號的預設處理,退出程式。

當我們按下ctrl+/的時候,操作為:向系統傳送SIGQUIT訊號,該訊號的預設處理為退出程式。

sigaction 用法例項