linux信号机制
|
|
|
信号提供了一种通知进程系统事件发生的机制,它也是作为用户进程之间通信和同步的 一种原始机制。在进程迁移的情况下,如何处理信号呢?这部分介绍了MOSIX系统对信号 机制的处理。 LINUX信号机制
信号是异步的进程间通讯机制,是在软件层次上对中断机制的一种模拟。LINUX内核的信 号机制符合POSIX.4的规定,这是POSIX.1标准的一个超集。
每个进程的task_struct结构中都有个指针sig,指向一个singal_struct结构,结构中的 数组action[]相当于一个信号向量表,每个元素确定了进程接收到一个具体的信号时应 该采取的行动。
struct signal_struct { atomic_t count; struct k_sigaction action[_NSIG]; spinlock_t siglock; };
那么系统如何判断一个进程是否有信号在等待处理呢?这是通过task_struct结构中的si gpending成员。task_struct结构中的blocked成员则为屏蔽信号的集合,pending成员则 为信号队列,每产生一个信号则把它挂入这个队列,信号位图signal也保存在其中。
用户常常要自己定义对信号的处理程序,并且用户的处理函数是位于用户空间的。LINUX 提供了系统调用signal(sys_signal)和sigaction(sys_sigaction 或sys_rt_sigacti on)为信号设置处理向量。用户设置信号处理的时机我们是不能确定的,可以在进程迁 移前,也可以在进程迁移之后,进程可以在不同的节点间多次迁移,因此,如何保证信 号不被丢失并且都能被正确处理就很重要。并且我们注意到,进程在迁移时,并不将信 号向量表迁移到目标进程,而只是将进程的异步信号和强制信号信息传送到目标进程【 参见mig_send_misc()和mig_do_receive_misc()】。
struct asig_h { unsigned int sigs;/*信号*/ int nforced;/*内核发送的强制信号的个数*/ }; struct mosix_task {。。。。。。。 uint32_t asig; /*到达REMOTE的信号 */ siginfo_t *forced_sigs; /* REMOTE强制信号信息*/ int nforced_sigs; /* REMOTE强制信号的个数 */ short sigmig; /* 迁移时接收的信号 */ }
int mig_send_misc(int credit) { struct mig_misc_h m; register struct task_struct *p = current; ……………….. m.asig.sigs = p->mosix.asig; m.asig.nforced = p->mosix.nforced_sigs; forced_sigs = p->mosix.forced_sigs; sti(); if(comm_send(MIG_MISC, &m, sizeof(m), forced_sigs, m.asig.nforced * sizeof(siginfo_t), 0)) ……………… }
因此,我们可以说进程信号处理的状态是保留在DEPUTY方的。这样做也是很自然的。首 先在MOSIX中,对于REMOTE进程,几乎所有的系统调用都是请求DEPUTY来处理,和信号相 关的一些系统调用也不了例外。例如,sigprocmask()改变本进程得信号屏蔽位图,sigp ending()检查有哪些信号已到达而未被处理,signal()和sigaction()安装信号处 理程序。其次,在不少内核操作中,进程进入睡眠以后刚被唤醒时,都会检测信号的存 在从而提前返回到用户空间。而DEPUTTY和REMOTE可以分别看作对系统上下文和用户上下 文的抽象,所以DEPUTY保留着信号处理的状态。
信号响应
对信号的检测和响应总是发生在系统空间,通常发生在两种情况下:第一,当前进程由 于系统调用、中断或异常而进入系统空间以后,从系统空间返回到用户空间的前夕。第 二,当前进程在内核中进入睡眠以后刚被唤醒时,由于信号的存在而提前返回到用户空 间。
当进程由于中断进入系统空间以后,中断处理程序服务完后,将会转到入口ret_from_in tr 。当进程由于异常而进入系统空间后,将会跳到error_code从而最终转到ret_from_e xception处理【参见entry.S】。如果中断或异常发生于用户空间,则转移到ret_check_ reschedule,否则发生于内核空间,则到达restore_all。当进程由于系统调用进入系统 空间,将最终走到ret_from_sys_call。
ENTRY(ret_from_sys_call) 。。。。。 ret_check_reschedule: cli # need_resched and signals atomic test cmpl $0,need_resched(%ebx)/*判断是否需要调度*/ jne reschedule cmpl $0,sigpending(%ebx) /*判断是否有悬挂的信号/ jne signal_return /*如果有信号待处理,则跳到signal_return */ straight_to_mosix: call SYMBOL_NAME(mosix_pre_usermode_actions) testl %eax,%eax jne ret_from_sys_call restore_all: RESTORE_ALL ALIGN signal_return: sti # 开中断 handler testl $(VM_MASK),EFLAGS(%esp)/*是否处于VM86模式*/ movl %esp,%eax jne v86_signal_return /*是VM86模式的话,则转到v86_signal_return */ call SYMBOL_NAME(do_signal) /* do_signal 对信号进行处理*/ jmp straight_to_mosix
从代码中我们可以看到,如果有信号待处理,则在退出系统空间前,会跳到signal_retu rn,调用do_signal处理信号。
我们看看do_signal ,它对信号作出具体的反应。如果当前进程是REMOTE,它只是简单 的返回0。否则,该函数根据当前进程的signal域,确定进程收到了那些信号。对进程收 到的每一个信号,从进程的信号等待队列中找到该信号对应的附加信息,从进程的sig域 的action数组中找到信号的处理程序及其相关的信息。于是,如果用户设置了信号处理 程序(在用户空间中),则最终会通过函数handle_signal()准备好对处理程序的执行 。
用户提供的信号处理程序是在用户空间执行的,而且执行完毕以后还还要回到系统空间 。LINUX实现的机制如下:
用户空间堆栈中为信号处理程序的执行预先创建一个框架,框架中包括一个作为局部量 的数据结构,并把系统空间的"原始框架"保存在这个数据结构中 在信号处理程序中插入对系统调用sigreturn()的调用 将系统空间堆栈中"原始框架"修改成为执行信号处理程序所需的框架 "返回"到用户空间,但是却执行信号处理程序 信号处理程序执行完毕后,通过系统调用sigreturn()重返系统空间 在系统调用sigreturn()中从用户空间恢复"原始框架" 最后再返回到用户空间,继续执行原先的用户程序
对于本地进程,这是在handle_signal()中由setup_rt_frame()或setup_frame()作出安 排的。但是,对于DEPUTY进程,则是通过mosix_deputy_setup_frame()实现的。因为, 我们已经知道,DEPUTY是永远运行在核心态中的;进程迁移后,代码段和数据段等都完 全迁移到远程REMOTE进程。因此,信号处理程序必然是在REMOTE进程上运行的。那么, 这又是如何实现的呢。
mosix_deputy_setup_frame()通过deputy_request()函数向REMOTE发送DEP_SETUPFRAM E请求,REMOTE将在remote_wait()函数中接收到该请求,调用remote_setup_frame() 来响应该请求。REMOTE进程在remote_setup_frame()中,根据DEPUTY传来的参数,通 过setup_rt_frame()或setup_frame()安排好一个框架。
这样,当REMOTE进程从系统空间返回到用户空间时,将执行信号处理程序。然后,将通 过sigreturn()系统调用重返系统空间。sys_sigreturn()的作用就是从用户空间执行信 号处理程序的框架中恢复当初系统空间中的原始框架。它通过restore_sigcontext() 恢复框架的。但是对于DEPUTY进程,则是通过mosix_deputy_restore_sigcontext()函 数来恢复系统空间的原始框架的【参见sys_sigreturn()】。这里,因为是REMOTE进程调 用sigreturn()系统调用,因此根据我们前面对系统调用的分析,REMOTE进程向DEPUTY进 程发送REM_SYSCALL请求,DEPUTY将在通过deputy_syscall()函数中调用sys_sigretur n()来响应该请求。
mosix_deputy_restore_sigcontext()则向REMOTE进程发送DEP_RESTORESIGCONTEXT请 求。REMOTE在向DEPUTY发送REM_SYSCALL请求后,将处于remote_wait()循环中等待REM_S YSCALL请求的应答【参见remote_standard_system_call()】。REMOTE在remote_wait() 中接收到DEP_RESTORESIGCONTEXT请求后,则通过remote_restore_sigcontext()函数调 用restore_sigcontext()真正恢复核心空间的原始框架。此后,当REMOTE进程从系统 空间返回后,将回到信号处理前原先的用户空间处继续往下执行。
信号发送
发送一个信号给进程可以在用户空间通过系统调用发送,如通过sys_kill和 syr_rt_sigqueueinfo调用发送。内核也可以通过force_sig()和force_sig_info()向进 程强制发送信号,将屏蔽位强制清除,不允许目标进程忽略该信号。
在用户空间向一个进程发送信号由系统调用sys_kill()实现。该函数调用函数kill_some thing_info(),它根据情况,或者向单个进程发送信号(kill_proc_info()),或者向一 个进程组中的所有进程发送信号(kill_pg_info()), 最终都会调用函数send_sig_info( )来完成真正的信号发送。kill_pg_info()中,通过for_each_local_task(p)来查找属于 同组的进程。这是因为MOSIX中, 信号只会发给本地进程,而不会发送给REMOTE进程的 。对于REMOTE进程,当通过kill()发送信号时,根据我们前面对系统调用的分析,我 们知道最终将是由DEPUTY来调用sys_kill()。因此,信号是被挂入DEPUTY的task_struct 结构中的pending队列里。
异步和强制信号的处理
内核也会向进程发送信号,例如当页面异常而又无法恢复时,do_page_fault()页面异 常处理程序会通过force_sig()zx向当前进程发送一个SIGBUS信号。内核发送的信号一般 都是需要立即作出反应的。MOSIX对系统发送信号的处理方式也和用户发送信号有所不同 ,内核发送的"强制"信号都保存在mosix_task的forced_sigs指针中。
我们首先看看函数force_sig_info()。如果信号的目的地为REMOTE进程,则:
如果处于中断服务中,则系统panic 得到进程已有的强制信号数(n= t->mosix.nforced_sigs)并试图分配内存(x = kmalloc((n + 1) * sizeof(siginfo_t), GFP_KERNEL))用于保存这n+1个信号信息 如果内存分配失败,则尽量再次发送该信号send_sig(sig, t, 0);返回0 因为分配内存返回时,可能已经处理了一部分信号,因此要进行检测。如果是,则释放 刚刚申请的内存,跳到第二步 将保存的siginfo_t和新的info拷贝到分配的内存中,保存在mosix结构的forced_sigs中 ,并增加强制信号计数t->mosix.forced_sigs = x; t->mosix.nforced_sigs++;
我们前面已经分析过,REMOTE进程从系统空间返回到用户态之前,将会调用remote_pre_ usermode_actions()函数。remote_pre_usermode_actions()函数将会检测当前进程是否 有异步或"强制"信号待处理。如果有,它会通过函数transfer_signals_to_deputy() 发送REM_ASIG请求将信号传递给DEPUTY处理【参见remote_pre_usermode_actions()】。 DEPUTY则会通过函数deputy_analyse_remote_signals()来处理REM_ASIG请求。它首先 从连接中获得"强制"信号信息,通过force_sig_info()向当前进程(即DEPUTY本身) 发送强制信息。然后得到每个信号,依次处理,一般都是通过send_sig发给当前进程。
| |
转自:http://blog.chinaunix.net/u/14053/showart_82160.html