首页 > 使用sigaction处理内核信号

使用sigaction处理内核信号

文章目录

        • 函数描述
        • 函数使用
        • 抓取发送信号的进程信息


mark一次获取内核信号,并作相应处理的手段

linux内核中断机制的一个重要实现就是信号。信号使得内核和用户态的交互更加便捷,这个便捷对开发者来说可以更好的利用系统原生内核来处理信息。

《深入理解unix内核》中对信号作用的描述如下:

  • 让进程知道已经发生了一个特定事件
  • 强迫进程执行它自己代码中的信号处理程序

这里主要描述一下借用sigaction系统调用,对信号进行注册并做出相应的处理

函数描述

包含头文件:#include

函数原型:int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

参数含义如下:

  • signum 表示特例化信号代表的数字,其中SIGKILLSIGSTOP信号不能被注册
  • 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 causedhardware-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 inglibc 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 
#include 
#include 
#include 
#include  
#include   
void get_unlegal_memory()  
{   char *a = NULL;/*未分配空间,模拟非法访问内存*/printf("get the unmalloc memmory %c!
",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
", stacktrace[i]);                           }free(stacktrace);                                         
}
void sig_op(int signo, siginfo_t* info, void* context)
{ print_stacktrace();printf("get the kernel signal
");printf("sig signo is %d
",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~!
",SIGSEGV);printf("the pid is %d
",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

抓取发送信号的进程信息

使用以上获取内核命令的方式,我们可以在今后抓取终止当前进程的某个进程(/proc/p_id/status 文件)信息,举例如下

改代码注册了键盘中断信号(SIGINT)以及终止信号(SIGTERM),同时将发送该信号的进程信息打印出来(包括进程名称,进程p_id,占用内存,所处内存地址等信息)

#include 
#include 
#include 
#include 
#include  
#include   /*打印当前时间*/
void printDatetime()  
{   time_t now;struct tm *tm_now;time(&now);tm_now = localtime(&now);printf("now datetime: %d-%d-%d %d:%d:%d
", 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
",buf);}         fclose(fp);  }else{ printf("NULL
");}
}  /*打印函数调用栈*/
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
", stacktrace[i]);                          }free(stacktrace);                                         
}/*内核信号处理函数,打印发送该信号的进程信息*/
void sig_op(int signo, siginfo_t* info, void* context)
{ print_stacktrace();printf("the signo is %d
",signo);printf("sig pid is %d
", (int)(info->si_pid));printf("sig uid is %d
", (int)(info->si_uid));printf("sig signo is %d
",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
",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~!
");if(sigaction(SIGTERM,&act,&oact)==-1)printf("%s","install error~!
");if(sigaction(SIGSEGV,&act,&oact)== -1)printf("%d","install error~!
",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

更多相关:

  •         Apache POI是一个开源的利用Java读写Excel,WORD等微软OLE2组件文档的项目。        我的需求是对Excel的数据进行导入或将数据以Excel的形式导出。先上简单的测试代码:package com.xing.studyTest.poi;import java.io.FileInputSt...

  • 要取得[a,b)的随机整数,使用(rand() % (b-a))+ a; 要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a; 要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1; 通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。 要取得a到b之间的...

  • 利用本征图像分解(Intrinsic Image Decomposition)算法,将图像分解为shading(illumination) image 和 reflectance(albedo) image,计算图像的reflectance image。 Reflectance Image 是指在变化的光照条件下能够维持不变的图像部分...

  • 题目:面试题39. 数组中出现次数超过一半的数字 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入: [1, 2, 3, 2, 2, 2, 5, 4, 2] 输出: 2 限制: 1 <= 数组长度 <= 50000 解题: cl...

  • 题目:二叉搜索树的后序遍历序列 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。 参考以下这颗二叉搜索树:      5     /    2   6   /  1   3示例 1: 输入: [1,6,3,2,5] 输出...

  • 实验4-2:fork父子进程 实验目的: 理解fork创建子进程的本质   实验要求: 1、按如下要求编写程序: (1)、打开一个有内容的文件; (2)、调用fork创建子进程; (3)、读文件的第一个字符输出打印出来; (4)、看看父进程和子进程分别读到的字符是什么 2、按如下要求编写程序:...

  • Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数据和结构,Linux 内核所有涉及到进程和程序的所有算法都是围绕该数据结构建立的,是内核中最重要的数据结构之一。该数据结构在内核文件 include/linux/sched.h 中定义,在Linux 3.8 的内核中,该数据结构足足有 380 行之多,在这...

  • 转自:http://www.cnblogs.com/chaofan/archive/2009/12/02/1615691.html   今天在使用apache的时候80端口被占用了,解决办法如下 在命令行里输入netstat -aon|findstr "80"                         查看使用了80端口的tc...