kernel源码(十三)signal.c
signal.c用于信号处理。Linux的信号机制是使用信号来进行进程之前传递消息的机制。
进程接收到信号后有三种处理:1)忽略该信号 2)捕获该信号,执行自定义操作函数 3)执行系统默认的操作,一般就是结束进程。
信号位图:就是一个数组,数组的下标代表不同的信号,数组的元素则是对应信号的状态。linux定义了一个32位的信号位图,目前只有22个有效信号,每个信号有自己的一个处理函数,进程在系统调用结束返回之前会检测有没有收到信号,并执行与信号相关的处理函数,最后才会返回到进行系统调用的地方.
进程的PCB中有一块内存放着信号位图,信号产生时,内核在该信号位图中设置该信号的未决标志,进程在系统调用结束后通过检测信号位图来判断是否有信号到达。
信号屏蔽位图:就是阻塞相关信号的位图,阻塞信号是为了在我们执行相关信号处理函数的时候不会接收到其他信号而造成意外,比如进程提前终止等等。信号一旦被阻塞哪怕接收到信号,也不会被处理。
信号位图和信号屏蔽位图都用一个32位的信号集表示
1 总览
该c文件中为我们提供的和信号处理相关的系统调用中断处理c函数有2个:sys_signal和sys_sigaction。还为我们提供了一个信号处理函数do_signal,这个函数是在系统调用中断处理函数(比如sys_fork)执行完毕返回后在ret_from_sys_call标号中执行的。
sys_signal在有些情况下是不可靠的,sys_sigaction是可靠的信号处理系统调用
信号处理一般过程:
- 当系统调用c处理函数结束后,会执行ret_from_sys_call标号处的代码(参考system_call.s那篇博客)。此标号处代码会检查进程信号位图是否有要处理的信号,如果有则选出一个信号位图对应的信号进行处理,具体是调用do_signal
- do_signal中,会将信号处理句柄插入到用户栈中,这样当系统调用结束返回后就会立即执行该句柄,进而达到信号处理的目的。见下图
signal()系统调用的例子:
我们在系统中创建一个sig.c。这里我们首先为当前进程设置SIGINT信号并设置其信号处理函数为自定义的handler函数。在handle函数中,我们又调用了了一次signal函数并设置其信号处理函数为SIG_DFL。
#include <signal.h> #include <stdio.h> #include <unistd.h> void handler(int sig) { printf("The signal is %d\n", sig); (void) signal(SIGINT, SIG_DFL); //这里是glibc库为我们提供的函数,在这里边会发起int 0x80系统调用,陷入内核 } int main() { (void) signal(SIGINT, handler); while(1) { printf("Signal test. \n"); sleep(1); } }
编译
gcc sig.c -o sig
运行
2 源码
/* * linux/kernel/signal.c * * (C) 1991 Linus Torvalds */ #include <linux/sched.h> #include <linux/kernel.h> #include <asm/segment.h> #include <signal.h> volatile void do_exit(int error_code); int sys_sgetmask() { return current->blocked; } int sys_ssetmask(int newmask) { int old=current->blocked; current->blocked = newmask & ~(1<<(SIGKILL-1)); return old; } static inline void save_old(char * from,char * to) { int i; verify_area(to, sizeof(struct sigaction)); for (i=0 ; i< sizeof(struct sigaction) ; i++) { put_fs_byte(*from,to); from++; to++; } } static inline void get_new(char * from,char * to) { int i; for (i=0 ; i< sizeof(struct sigaction) ; i++) *(to++) = get_fs_byte(from++); } int sys_signal(int signum, long handler, long restorer) { struct sigaction tmp; if (signum<1 || signum>32 || signum==SIGKILL) return -1; tmp.sa_handler = (void (*)(int)) handler; tmp.sa_mask = 0; tmp.sa_flags = SA_ONESHOT | SA_NOMASK; tmp.sa_restorer = (void (*)(void)) restorer; handler = (long) current->sigaction[signum-1].sa_handler; current->sigaction[signum-1] = tmp; return handler; } int sys_sigaction(int signum, const struct sigaction * action, struct sigaction * oldaction) { struct sigaction tmp; if (signum<1 || signum>32 || signum==SIGKILL) return -1; tmp = current->sigaction[signum-1]; get_new((char *) action, (char *) (signum-1+current->sigaction)); if (oldaction) save_old((char *) &tmp,(char *) oldaction); if (current->sigaction[signum-1].sa_flags & SA_NOMASK) current->sigaction[signum-1].sa_mask = 0; else current->sigaction[signum-1].sa_mask |= (1<<(signum-1)); return 0; } void do_signal(long signr,long eax, long ebx, long ecx, long edx, long fs, long es, long ds, long eip, long cs, long eflags, unsigned long * esp, long ss) { unsigned long sa_handler; long old_eip=eip; struct sigaction * sa = current->sigaction + signr - 1; int longs; unsigned long * tmp_esp; sa_handler = (unsigned long) sa->sa_handler; if (sa_handler==1) return; if (!sa_handler) { if (signr==SIGCHLD) return; else do_exit(1<<(signr-1)); } if (sa->sa_flags & SA_ONESHOT) sa->sa_handler = NULL; *(&eip) = sa_handler; longs = (sa->sa_flags & SA_NOMASK)?7:8; *(&esp) -= longs; verify_area(esp,longs*4); tmp_esp=esp; put_fs_long((long) sa->sa_restorer,tmp_esp++); put_fs_long(signr,tmp_esp++); if (!(sa->sa_flags & SA_NOMASK)) put_fs_long(current->blocked,tmp_esp++); put_fs_long(eax,tmp_esp++); put_fs_long(ecx,tmp_esp++); put_fs_long(edx,tmp_esp++); put_fs_long(eflags,tmp_esp++); put_fs_long(old_eip,tmp_esp++); current->blocked |= sa->sa_mask; }
所有信号定义在signal.h中
#define _NSIG 32 #define NSIG _NSIG #define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGUNUSED 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGTERM 15 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22
设置和获取当前进程的信号屏蔽位图
int sys_sgetmask() { return current->blocked; } int sys_ssetmask(int newmask) { int old=current->blocked; current->blocked = newmask & ~(1<<(SIGKILL-1)); //SIGKILL是不能被屏蔽的 return old; }
save_old函数作用是把from指向的内容复制到to指向的地址处。在本文件中的意义是:把内核段的数据拷贝到用户段
static inline void save_old(char * from,char * to) { int i; verify_area(to, sizeof(struct sigaction)); //首先验证内存空间是否足够大,确保to地址处有结构体大小的内存可读写 for (i=0 ; i< sizeof(struct sigaction) ; i++) {//把sigaction中的信息复制到fs当中 put_fs_byte(*from,to); from++; to++; } }
put_fs_byte在include/asm/segment.h中定义
extern inline void put_fs_byte(char val,char *addr) { __asm__ ("movb %0,%%fs:%1"::"r" (val),"m" (*addr));//把val变量放入fs段偏移地址为*addr处 }
把sigaction数据从fs段的from位置复制到to位置,也就是从用户数据段复制到内核的空间当中
static inline void get_new(char * from,char * to) { int i; for (i=0 ; i< sizeof(struct sigaction) ; i++) *(to++) = get_fs_byte(from++); }
get_fs_byte在include/asm/segment.h中定义
extern inline unsigned char get_fs_byte(const char * addr) { unsigned register char _v; __asm__ ("movb %%fs:%1,%0":"=r" (_v):"m" (*addr));把fs段偏移地址addr处的值放到_v变量中,然后返回_v return _v; }
为当前进程的signum信号设置新的信号处理函数。
int sys_signal(int signum, long handler, long restorer) //signum为信号编号,handler为指定的句柄,restorer恢复函数的指针 { struct sigaction tmp; if (signum<1 || signum>32 || signum==SIGKILL) //判断是否超过范围 return -1; tmp.sa_handler = (void (*)(int)) handler; //指定的信号处理句柄 tmp.sa_mask = 0; //执行时的信号屏蔽码 tmp.sa_flags = SA_ONESHOT | SA_NOMASK; //sa_flags为执行时的一些标志的组合。该句柄只使用1次后就恢复到默认值,并允许信号能够在自己的处理句柄中收到 tmp.sa_restorer = (void (*)(void)) restorer; //保存恢复处理函数的指针 handler = (long) current->sigaction[signum-1].sa_handler; //取信号原来的处理句柄的指针 current->sigaction[signum-1] = tmp; //为信号安装新的处理函数 return handler; //返回原来的句柄(函数指针) }
下面函数功能和上面的sys_signal功能类似。
int sys_sigaction(int signum, const struct sigaction * action, struct sigaction * oldaction) { struct sigaction tmp; if (signum<1 || signum>32 || signum==SIGKILL) return -1; tmp = current->sigaction[signum-1]; //获取当前进程原signum对应的sigaction结构体。 get_new((char *) action, (char *) (signum-1+current->sigaction)); //在信号的sigaction结构中设置新的操作 if (oldaction) save_old((char *) &tmp,(char *) oldaction); //如果oldaction指针不为空的话,则将原操作指针保存到oldaction所指的位置。 if (current->sigaction[signum-1].sa_flags & SA_NOMASK) //如果允许信号在自己的信号句柄中收到,则令屏蔽码为0,否则设置屏蔽本信号 current->sigaction[signum-1].sa_mask = 0; else current->sigaction[signum-1].sa_mask |= (1<<(signum-1)); return 0; }
该函数在system_call.s中调用。该函数的作用是:将信号处理函数放入用户堆栈,函数调用返回时可保证立即执行信号处理函数,然后继续执行用户的程序。
那么如何将信号处理函数放入用户态堆栈中的呢?在system_call.s中我们已经知道,fs寄存器指向的是用户态堆栈,因此,我们只要把信号处理函数压入fs即可,当执行完do_signal返回用户态时,就会执行这个信号处理函数。
do_signal返回用户态时,所执行的信号处理函数的参数也是在用户态堆栈中,也是在do_signal函数中压入用户态堆栈的,见do_signal函数中的8个put_fs_long函数调用。
还有一个问题,我们知道,do_signal函数是在system_call.s被调用的,我们也知道,do_signal函数的13个参数是system_call.s中压入堆栈的参数,为什么压入堆栈后就可以实现参数传递呢?因为signal.c编译后会和system_call.s合并在一起,都是汇编,而汇编传值用的就是堆栈。
void do_signal(long signr,long eax, long ebx, long ecx, long edx, long fs, long es, long ds, long eip, long cs, long eflags, unsigned long * esp, long ss) { unsigned long sa_handler; long old_eip=eip; struct sigaction * sa = current->sigaction + signr - 1; int longs; unsigned long * tmp_esp; sa_handler = (unsigned long) sa->sa_handler;//取得句柄 if (sa_handler==1) //如果为SIG_IGN,则忽略返回 return; if (!sa_handler) { //如果为0(SIG_DFL) if (signr==SIGCHLD) //如果signr为SIGCHLD,则返回 return; else //否则执行do_exit do_exit(1<<(signr-1)); } if (sa->sa_flags & SA_ONESHOT) //如果信号句柄只使用一次,则把信号句柄置为空 sa->sa_handler = NULL; *(&eip) = sa_handler; //eip指向信号处理句柄 longs = (sa->sa_flags & SA_NOMASK)?7:8; //要么是7,要么是8,对应下面调用put_fs_long函数的次数。 *(&esp) -= longs; //esp扩展7或者8个单位 verify_area(esp,longs*4); //检验内存使用情况,如果内存超界了,则分配一个新的页 tmp_esp=esp; put_fs_long((long) sa->sa_restorer,tmp_esp++); //在用户堆栈中从上到下依次放入sa_restorer、signr、屏蔽码、eax、ecx、edx、eflags、用户代码段的原代码段指针 put_fs_long(signr,tmp_esp++); if (!(sa->sa_flags & SA_NOMASK)) put_fs_long(current->blocked,tmp_esp++); put_fs_long(eax,tmp_esp++); put_fs_long(ecx,tmp_esp++); put_fs_long(edx,tmp_esp++); put_fs_long(eflags,tmp_esp++); put_fs_long(old_eip,tmp_esp++); current->blocked |= sa->sa_mask; //当前进程的屏蔽码添加sa->sa_mask }
其中put_fs_long在segment.h中定义。前面system_call.s中我们已经讲过ds、es指向内核段,fs指向用户栈。put_fs_long作用就是向用户栈压入数据,所压的值为参数val。
extern inline void put_fs_long(unsigned long val,unsigned long * addr) { __asm__ ("movl %0,%%fs:%1"::"r" (val),"m" (*addr)); //把val的值放入fs段偏移地址addr处 }
下面展示了从系统调用开始到系统调用结束内核栈和用户栈的变化。需要注意的是用户自定义信号处理句柄sa_handler是在用户程序中调用sys_signal进行设定的。sa_restorer是库函数提供的,用于恢复必要的上下文环境