本文距最近一次更新已过去 620 天,部分内容可能已经过时,请结合当前资料核验。
信号(Signal)是一种软中断。信号机制是进程间通信的一种方式,采用异步通信方式。
流程
应用程序注册信号,信号事件发生后,内核将信号置为pending状态
在中断返回或者系统调用返回时,查看pending的信号,内核在应用程序的栈上构建一个信号处理栈帧
然后通过中断返回或者系统调用返回到用户态,执行信号处理函数
执行信号处理函数之后,再次通过sigreturn系统调用返回到内核
在内核中再次返回到应用程序被中断打断的地方或者系统调用返回的地方接着运行
信号处理 在进程task_struct结构体中有一个未决信号的成员变量 struct sigpending pending。每个信号在进程中注册都会把信号值加入到进程的未决信号集。
内核处理进程收到的signal是在当前进程的上下文,故进程必须是Running状态。当进程唤醒或者调度后获取CPU,则会从内核态转到用户态时检测是否有signal等待处理,处理完,进程会把相应的未决信号从链表中去掉。
信号处理方式
默认:接收到信号后按默认的行为处理该信号。其中,部分信号无法修改其默认的处理行为,例如SIGKILL等。
自定义:自定义信号处理函数(Handler)执行特定操作。
忽略:相当于信号没有发出。部分信号的默认操作就是忽略,例如SIGCHLD等。
自定义信号处理方式 信号处理程序会在接收到信号的线程中执行,而不是在子线程中。也就是说,信号处理程序是在同一线程的上下文中运行 的。
signal and sigaction这两个都是修改信号处理方式的系统调用。
void( *signal(int sig,void(*handler)(int))(int)
只能设置信号及其处理Handler,每个Kernel的实现方式可能不同,因此不建议使用。
handler赋值为常数SIG_IGN表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号。
int sigaction(int sig,const struct sigaction *act,struct sigaction *oldact)
act: 指向 sigaction 结构体的指针,该结构体定义了信号的处理方式。如果不需要改变信号处理方式,可以传递 NULL
oldact: 指向 sigaction 结构体的指针,用于存储信号的旧处理方式。如果不需要获取旧的处理方式,可以传递 NULL
struct sigaction1 2 3 4 5 6 7 struct sigaction { void (*sa_handler)(int ); void (*sa_sigaction)(int , siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void ); };
不要同时赋值给 sa_handler 和 sa_sigaction
sa_handler 指定与 signum 相关联的动作
赋值为常数SIG_IGN表示忽略信号
赋值为常数SIG_DFL表示执行系统默认动作
赋值为一个函数指针表示用自定义函数捕捉信号
sa_sigaction只有在sa_flags指定了SA_SIGINFO时才能使用,需要的函数原型如下
1 void handler (int sig, siginfo_t *info, void *ucontext) { ... }
sig 引发处理程序的信号编号
info 指向 siginfo_t 的指针,它是一个结构体,包含有关信号的更多信息
ucontext 这是一个指向ucontext_t结构体的指针,转换为void *。这个字段指向的结构包含由内核在用户空间栈上保存的信号上下文信息
sa_mask字段表示在信号处理程序执行期间应该被屏蔽的任何信号。
sa_flags字段确定了几个不同的值,但其中重要的是是否可以获取扩展信息( SA_SIGINFO ),以及是否自动重启被信号中断的系统调用( SA_RESTART )。否则,中断的系统调用将失败,因此自动重启显然是更好的方法。
struct siginfo1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 typedef struct siginfo { union { __SIGINFO; int _si_pad[SI_MAX_SIZE/sizeof (int )]; }; } __ARCH_SI_ATTRIBUTES siginfo_t ; #ifndef __ARCH_HAS_SWAPPED_SIGINFO #define __SIGINFO \ struct { \ int si_signo; \ int si_errno; \ int si_code; \ union __sifields _sifields; \ } #else #define __SIGINFO \ struct { \ int si_signo; \ int si_code; \ int si_errno; \ union __sifields _sifields; \ } #endif union __sifields { struct { __kernel_pid_t _pid; __kernel_uid32_t _uid; } _kill; struct { __kernel_timer_t _tid; int _overrun; sigval_t _sigval; int _sys_private; } _timer; struct { __kernel_pid_t _pid; __kernel_uid32_t _uid; sigval_t _sigval; } _rt; struct { __kernel_pid_t _pid; __kernel_uid32_t _uid; int _status; __ARCH_SI_CLOCK_T _utime; __ARCH_SI_CLOCK_T _stime; } _sigchld; struct { void *_addr; #ifdef __ARCH_SI_TRAPNO int _trapno; #endif #ifdef __ia64__ int _imm; unsigned int _flags; unsigned long _isr; #endif #define __ADDR_BND_PKEY_PAD (__alignof__(void *) < sizeof(short) ? \ sizeof(short) : __alignof__(void *)) union { short _addr_lsb; struct { char _dummy_bnd[__ADDR_BND_PKEY_PAD]; void *_lower; void *_upper; } _addr_bnd; struct { char _dummy_pkey[__ADDR_BND_PKEY_PAD]; __u32 _pkey; } _addr_pkey; }; } _sigfault; struct { __ARCH_SI_BAND_T _band; int _fd; } _sigpoll; struct { void *_call_addr; int _syscall; unsigned int _arch; } _sigsys; };
存储signal发出信号时的信息。
[!NOTE]
这里可以考虑加入timestamp字段以支持记录信号发出时的时间戳。具体记录时间戳的函数位置暂时未知。
以解决ASYNC时顺序倒挂的问题。同时兼用ASYMM性能好的优势。
写一个高级的Signal Handler 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <signal.h> #include <string.h> #include <stdio.h> #include <stdlib.h> static void handle_prof_signal (int sig_no, siginfo_t * info, void *context) { printf ("Done\n" ); exit (1 ); } void main () { struct sigaction sig_action ; memset (&sig_action, 0 , sizeof (sig_action)); sig_action.sa_sigaction = handle_prof_signal; sig_action.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&sig_action.sa_mask); sigaction(SIGPROF, &sig_action, 0 ); struct itimerval timeout = {0 }; timeout.it_value.tv_sec=1 ; setitimer(ITIMER_PROF, &timeout, 0 ); do { } while (1 ); }
Async-safe函数 异步安全函数是指即使在不同的执行上下文中被调用,也能安全地操作共享资源的函数。许多系统调用在信号处理程序中是不安全的。
使函数在异步环境下安全运行的一种方法是,在函数执行期间阻止信号的到达,并在函数离开关键代码段后才重新启用信号。这可以通过使用sigprocmask()函数来实现。
防止循环调用Signal Handler 主要看ucontext上下文。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <signal.h> #include <string.h> #include <unistd.h> #include <sys/ucontext.h> static void handle_prof_signal (int sig_no, siginfo_t * info, void *vcontext) { char output[100 ]; ucontext_t *context = (ucontext_t *)vcontext; unsigned long pc = context->uc_mcontext.gregs[REG_PC]; snprintf (output,100 ,"Illegal instruction at %lx value %lx\n" ,pc,*(int *)pc); write(1 ,output,strlen (output)+1 ); context->uc_mcontext.gregs[REG_PC] = context->uc_mcontext.gregs[REG_nPC]; context->uc_mcontext.gregs[REG_nPC] = context->uc_mcontext.gregs[REG_nPC]+4 ; } void main () { struct sigaction sig_action ; memset (&sig_action, 0 , sizeof (sig_action)); sig_action.sa_sigaction = handle_prof_signal; sig_action.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&sig_action.sa_mask); sigaction(SIGILL, &sig_action, 0 ); timeout.it_value.tv_sec=1 ; setitimer(ITIMER_PROF, &timeout, 0 ); asm (".word 0x00000000" ); asm (".word 0x00000000" ); asm (".word 0x00000000" ); asm (".word 0x00000000" ); }
ucontext_t 结构体用于获取当前执行上下文,包括程序计数器(PC)和其它寄存器。
pc 是当前程序计数器的值,指向出错的指令。
不可重入问题 如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant)函数。
当捕捉到信号时,不论进程的主控制流程当前执行到哪儿,都会先跳到信号处理函数中执行,从信号处理函数返回后再继续执行主控制流程。信号处理函数是一个单独的控制流程,因为它和主控制流程是异步的,二者不存在调用和被调用的关系,并且使用不同的堆栈空间。引入了信号处理函数使得一个进程具有多个控制流程,如果这些控制流程访问相同的全局资源(全局变量、硬件资源等),就有可能出现冲突。
如果一个函数符合以下条件之一则是不可重入的:
调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
ucontext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 struct ucontext { unsigned long uc_flags; struct ucontext *uc_link ; stack_t uc_stack; sigset_t uc_sigmask; __u8 __unused[1024 / 8 - sizeof (sigset_t )]; struct sigcontext uc_mcontext ; }; struct sigcontext { __u64 fault_address; __u64 regs[31 ]; __u64 sp; __u64 pc; __u64 pstate; __u8 __reserved[4096 ] __attribute__((__aligned__(16 ))); };
这里给出访问ARM64的PC和opcode及其基址的代码:
1 2 3 4 5 6 7 8 9 10 struct ucontext *uc = (struct ucontext *)context;uint64_t pc = uc->uc_mcontext.pc; uint32_t opcode;opcode = *(uint32_t *)pc; printf ("Received signal %d\n" , sig);printf ("PC: 0x%llx, Opcode: 0x%x\n" , pc, opcode);
由于ARM64的指令定长,可以使用以下方式进行指令的解码:
未完待续
ref 4. 捕捉信号
ucontext的简单介绍 - ink19 - 博客园
linux ucontext族函数的原理及使用_ucontext排查短促-CSDN博客
Linux信号(signal)机制 - Gityuan博客 | 袁辉辉的技术博客
signal(7) - Linux manual page
Linux kernel signal原理_linux kernel 如何 判断 收到的是什么 signal-CSDN博客
The Linux kernel: Signals
sigaction(2) - Linux manual page
Signals - HackMD
How to Write Advanced Signal Handlers