天天看點

使用sigaction處理核心信号

文章目錄

  • ​​函數描述​​
  • ​​函數使用​​
  • ​​抓取發送信号的程序資訊​​

mark一次擷取核心信号,并作相應處理的手段

linux核心中斷機制的一個重要實作就是信号。信号使得核心和使用者态的互動更加便捷,這個便捷對開發者來說可以更好的利用系統原生核心來處理資訊。

《深入了解unix核心》中對信号作用的描述如下:

  • 讓程序知道已經發生了一個特定事件
  • 強迫程序執行它自己代碼中的信号處理程式

這裡主要描述一下借用sigaction系統調用,對信号進行注冊并做出相應的處理

函數描述

包含頭檔案:​

​#include <signal.h>​

​​ 函數原型:​

​int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);​

​ 參數含義如下:

  • ​signum​

    ​​ 表示特例化信号代表的數字,其中​

    ​SIGKILL​

    ​​和​

    ​SIGSTOP​

    ​信号不能被注冊
  • ​struct sigaction​

    ​ 該資料結構如下
struct sigaction {
   void     (*sa_handler)(int); /*表示要執行操作的類型,它的值可以是指向信号處理程式的一個指針
                           ,SIG_DFL(值為0,指定預設操作, 
                           或者SIG_IBN(值為1,指定忽略信号)*/
   void     (*sa_sigaction)(int, siginfo_t *, void *);
   sigset_t   sa_mask; /*這是一個标志集,制定必須怎樣處理信号。*/
   int        sa_flags; /*這個是類型為sigset_t的變量,指定當運作信号處理程式時要屏蔽的信号,一般為SA_SIGINFO,來擷取處理程式的附件資訊*/
   void     (*sa_restorer)(void);
};      
  • 這裡一般是我們在程式中封裝該sigaction資料結構,将我們想要的參數封裝好傳入進去,再通過系統調用執行處理。

    其中SA_SIGINFO資料結構包含如下參數,直接通過siginfo_t 的結構體變量來擷取

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 */
  long     si_band;     /* Band event (was int in
                           glibc 2.3.2 and earlier) */
  int      si_fd;       /* File descriptor */
  short    si_addr_lsb; /* Least significant bit of address
                           (since Linux 2.6.32) */
}      
  • ​const struct sigaction *act​

    ​ 該參數非空的,存儲最新的一個action的資訊的資料結構
  • ​struct sigaction *oldact​

    ​ 該參數也為空,同時存儲上一個action的資訊的資料結構

函數使用

檢視如下代碼

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/time.h> 
#include <time.h>  
void get_unlegal_memory()  
{  
    char *a = NULL;
    /*未配置設定空間,模拟非法通路記憶體*/
    printf("get the unmalloc memmory %c!\n",a[0]);
}
void print_stacktrace()                  
{
      int size = 16,i=0;
      void * array[16];
      int stack_num = backtrace(array, size);                   
      char ** stacktrace = backtrace_symbols(array, stack_num);                                                                                                                                                                                                         
      for (; i < stack_num; ++i)                       
      {
           printf("%s\n", stacktrace[i]);                           
      }
      free(stacktrace);                                         
}
void sig_op(int signo, siginfo_t* info, void* context)
{
  print_stacktrace();
  printf("get the kernel signal\n");
  printf("sig signo is %d\n",info->si_signo);
  exit(0);
}
int main(int argc,char** argv)
{
    struct sigaction act;
    struct sigaction oact;
    pid_t pid;

    pid=getpid();
    sigemptyset(&act.sa_mask);
    /*注冊信号處理函數如下,sig_op,用來列印函數調用棧以及信号集中發出該信号的程序詳細資訊*/
    act.sa_handler=sig_op; 
    act.sa_flags=SA_SIGINFO;
    /*注冊了信号SIGSEGV用來擷取非法記憶體通路*/
    if(sigaction(SIGSEGV,&act,&oact)== -1)
        printf("%d","install error~!\n",SIGSEGV);
    printf("the pid is %d\n",pid);
    get_unlegal_memory();
    return 0;
}      

編譯方式如下

​​

​gcc test.c -rdynamic -g -o test​

​​ 這裡增加了列印函數調用棧,需要将所有符号連接配接到二進制檔案,是以需要​

​-rdynamic​

​參數,這裡想要進一步了解列印函數調用棧,可以參考關于ceph源碼 backtrace 列印函數調用棧

執行結果如下可以看到函數調用棧如下,sig_op函數是由系統c庫發出

./test
the pid is 7754
./test_memory(print_stacktrace+0x32) [0x400b2b]
./test_memory(sig_op+0x1d) [0x400ba5]
/lib64/libc.so.6(+0x35a00) [0x7f470de32a00]
./test_memory(get_unlegal_memory+0x20) [0x400ae0]
./test_memory(main+0x9c) [0x400c6c]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f470de1eaf5]
./test_memory() [0x4009f9]
get the kernel signal
sig signo is 11      

抓取發送信号的程序資訊

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/time.h> 
#include <time.h>  

/*列印目前時間*/
void printDatetime()  
{  
    time_t now;
    struct tm *tm_now;
    time(&now);
    tm_now = localtime(&now);
    printf("now datetime: %d-%d-%d %d:%d:%d\n", tm_now->tm_year, tm_now->tm_mon, tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec);
    fflush(stdout);
}

#define BUF_SIZE 1024
/*列印發送信号程序的詳細資訊,即從/proc/p_id/status 檔案目錄下擷取*/
void printTaskStatusByPid(int pid) {  
    char proc_pid_path[BUF_SIZE]={0};  
    char buf[BUF_SIZE]={0};  
    sprintf(proc_pid_path, "/proc/%d/status", pid);  
    FILE* fp = fopen(proc_pid_path, "r");  
    if(NULL != fp){
        while(fgets(buf, BUF_SIZE-1, fp)!= NULL){
            printf("%s\n",buf);
        }         
        fclose(fp);  
    }
    else
    {
        printf("NULL\n");
    }
}  

/*列印函數調用棧*/
void print_stacktrace()                  
{
      int size = 16,i=0;
      void * array[16];
      int stack_num = backtrace(array, size);                   
      char ** stacktrace = backtrace_symbols(array, stack_num);                                                                                                                                                                                                         
      for (; i < stack_num; ++i)                       
      {
           printf("%s\n", stacktrace[i]);                          
      }
      free(stacktrace);                                         
}

/*核心信号處理函數,列印發送該信号的程序資訊*/
void sig_op(int signo, siginfo_t* info, void* context)
{
  print_stacktrace();
  printf("the signo is %d\n",signo);
  printf("sig pid is %d\n", (int)(info->si_pid));
  printf("sig uid is %d\n", (int)(info->si_uid));
  printf("sig signo is %d\n",info->si_signo);
  printTaskStatusByPid((int)(info->si_pid));
  fflush(stdout);
}

int main(int argc,char** argv)
{
    struct sigaction act;
    struct sigaction oact;
    pid_t pid;
    pid=getpid();
    
    printDatetime(); 
    printf("the pid is %d\n",pid);
    fflush(stdout);

    /*初始化sigset_t變量中的位*/
    sigemptyset(&act.sa_mask);
    act.sa_handler=sig_op; 
    act.sa_flags=SA_SIGINFO;
    
    /*注冊信号*/
    if(sigaction(SIGINT,&act,&oact)==-1)
        printf("%s","install error~!\n");
    if(sigaction(SIGTERM,&act,&oact)==-1)
        printf("%s","install error~!\n");
    if(sigaction(SIGSEGV,&act,&oact)== -1)
        printf("%d","install error~!\n",SIGSEGV);
  
  /*死循環來列印時間,當遇到注冊的核心資訊,此時列印出程序詳細資訊*/
    while(1)
    {
        sleep(1);
        printDatetime();
        fflush(stdout);
    }

    return 0;
}      
now datetime: 119-6-13 19:44:29
now datetime: 119-6-13 19:44:30
#執行了killall操作,擷取到了SIGTERM信号,列印出來的資訊
./test(print_stacktrace+0x32) [0x400e86]
./test(sig_op+0x1d) [0x400f00]
/lib64/libc.so.6(+0x35a00) [0x7f18223b9a00]
/lib64/libc.so.6(nanosleep+0x10) [0x7f1822441890]
/lib64/libc.so.6(sleep+0xd4) [0x7f1822441744]
./test(main+0x109) [0x401083]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f18223a5af5]
./test() [0x400c49]
the signo is 15
sig pid is 20863
sig uid is 0
sig signo is 15
#可以看到發送SIGTERM信号的程序名稱如下
Name: killall
State:  R (running)
Tgid: 20863
Ngid: 0
Pid:  20863
PPid: 15099
TracerPid:  0
Uid:  0 0 0 0
Gid:  0 0 0 0
FDSize: 256
Groups: 0 
VmPeak:   116760 kB
VmSize:   116760 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:       924 kB
VmRSS:       924 kB
VmData:      212 kB
VmStk:       136 kB
VmExe:        20 kB
VmLib:      2508 kB
VmPTE:        64 kB
VmSwap:        0 kB
Threads:  1
SigQ: 1/125771
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000180000000
CapInh: 0000000000000000
CapPrm: 0000001fffffffff
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff
Seccomp:  0
Cpus_allowed: ff
Cpus_allowed_list:  0-7
Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:  0
voluntary_ctxt_switches:  1
nonvoluntary_ctxt_switches: 1
now datetime: 119-6-13 19:44:30
now datetime: 119-6-13 19:46:45
now datetime: 119-6-13 19:46:46
#執行鍵盤中斷擷取到信号資訊,但是該執行并不是一個程序,是以并不會列印調用棧
^C./test(print_stacktrace+0x32) [0x400e86]
./test(sig_op+0x1d) [0x400f00]
/lib64/libc.so.6(+0x35a00) [0x7f18223b9a00]
/lib64/libc.so.6(nanosleep+0x10) [0x7f1822441890]
/lib64/libc.so.6(sleep+0xd4) [0x7f1822441744]
./test(main+0x109) [0x401083]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f18223a5af5]
./test() [0x400c49]
the signo is 2
sig pid is 0
sig uid is 0
sig signo is 2
NULL
now datetime: 119-6-13 19:46:47
now datetime: 119-6-13 19:48:10
now datetime: 119-6-13 19:48:11
#當執行了kill -9 指令時,即發送SIGKILL信号,會徹底終止目前程序。(SIGKILL信号不允許被注冊,抓取)
Killed