天天看點

Linux 2.6 核心閱讀筆記 信号

2014年8月3日 信号處理程式調用過程

當一個程序接收到一個信号時,需要暫停程序執行轉去執行專門的信号處理函數(如果定義了這個信号的專門處理函數的話),然後再繼續執行程序代碼。

所有的信号處理都是通過核心函數do_signal進行的,do_signal如果發現需要處理的信号,并且這個信号有專門的處理函數,就需要調用這個使用者态的函數,這是通過handle_signal來處理的。執行信号處理函數是非常複雜的任務,因為在使用者态和核心态來回切換需要特别謹慎地處理棧裡的内容,在目前程序恢複“正常”執行前,首先需要執行使用者态的信号處理函數。此外當核心打算恢複程序的正常執行時,核心态堆棧不再包含被中斷程式的硬體上下文,因為每當從核心态到使用者态轉換時,都會清空核心棧的内容。另外一個複雜性是因為信号處理程式可以調用系統調用,在這種情況下,在執行了系統調用後,必須回到信号處理程式而不是到被中斷程式的正常代碼。

核心采用的解決方法是把儲存在核心态中的硬體上下文拷貝到目前程序的使用者态堆棧中。當信号處理程式結束時,自動調用sigreturn()系統調用把這個硬體上下文恢複到核心态堆棧中。

先來看個圖:

Linux 2.6 核心閱讀筆記 信号

一個非阻塞的信号發送給一個程序,當中斷或者異常發生時,切換到核心态,正要傳回到使用者态之前,核心執行do_signal函數來處理信号(如果有的話),通過調用handle_signal來處理使用者态的信号處理函數的執行環境的建立(調用setup_frame),然後回到使用者态,直接到了信号處理函數(由于對使用者态堆棧進行了修改),信号處理函數處理完之後,又調用了sys_sigreturn系統調用(由于對使用者态堆棧進行了修改),然後對使用者堆棧進行恢複操作,結束後回到了使用者态的正常執行路徑。

接下來讓我們看下核心态是怎麼對使用者态核心的堆棧進行修改的,主要是setup_frame這個函數:

//sig為捕獲的信号
//ka包含使用者态的信号處理函數
//set為組設信号的位掩碼數組位址
//regs為目前核心棧的寄存器值,修改這個對象可以使得核心跳到使用者态的信号處理函數裡
static int setup_frame(int sig, struct k_sigaction *ka,
		       sigset_t *set, struct pt_regs * regs)
{
	void __user *restorer;
	struct sigframe __user *frame;
	int err = 0;
	int usig;
	
	//得到一個使用者态的信号處理棧結構,裡面包含了信号值,信号處理函數執行完畢後的傳回位址,信号上下文等等資料
	frame = get_sigframe(ka, regs, sizeof(*frame));

	if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
		goto give_sigsegv;

	usig = current_thread_info()->exec_domain
		&& current_thread_info()->exec_domain->signal_invmap
		&& sig < 32
		? current_thread_info()->exec_domain->signal_invmap[sig]
		: sig;
	//将信号值存放到信号處理棧裡
	err = __put_user(usig, &frame->sig);//
	if (err)
		goto give_sigsegv;
	//建立信号處理上下文,存放到信号處理棧裡
	err = setup_sigcontext(&frame->sc, &frame->fpstate, regs, set->sig[0]);
	if (err)
		goto give_sigsegv;

	if (_NSIG_WORDS > 1) {
		err = __copy_to_user(&frame->extramask, &set->sig[1],
				      sizeof(frame->extramask));
		if (err)
			goto give_sigsegv;
	}
	//這裡就是信号處理函數執行完畢後的傳回位址
	restorer = &__kernel_sigreturn;
	if (ka->sa.sa_flags & SA_RESTORER)
		restorer = ka->sa.sa_restorer;

	/* Set up to return from userspace.  */
	err |= __put_user(restorer, &frame->pretcode);
	 
	/*
	 * This is popl %eax ; movl $,%eax ; int $0x80
	 *
	 * WE DO NOT USE IT ANY MORE! It's only left here for historical
	 * reasons and because gdb uses it as a signature to notice
	 * signal handler stack frames.
	 */
	err |= __put_user(0xb858, (short __user *)(frame->retcode+0));
	err |= __put_user(__NR_sigreturn, (int __user *)(frame->retcode+2));
	err |= __put_user(0x80cd, (short __user *)(frame->retcode+6));

	if (err)
		goto give_sigsegv;

	/* Set up registers for signal handler */
	//将棧頂指針置為信号處理棧
	regs->esp = (unsigned long) frame;
	//将epi指向信号處理函數的第一條指令
	regs->eip = (unsigned long) ka->sa.sa_handler;
	//eax為信号的值,也就是信号處理函數中的參數
	regs->eax = (unsigned long) sig;
	regs->edx = (unsigned long) 0;
	regs->ecx = (unsigned long) 0;

	set_fs(USER_DS);
	regs->xds = __USER_DS;
	regs->xes = __USER_DS;
	regs->xss = __USER_DS;
	regs->xcs = __USER_CS;

	/*
	 * Clear TF when entering the signal handler, but
	 * notify any tracer that was single-stepping it.
	 * The tracer may want to single-step inside the
	 * handler too.
	 */
	regs->eflags &= ~TF_MASK;
	if (test_thread_flag(TIF_SINGLESTEP))
		ptrace_notify(SIGTRAP);

#if DEBUG_SIG
	printk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p\n",
		current->comm, current->pid, frame, regs->eip, frame->pretcode);
#endif

	return 0;

give_sigsegv:
	force_sigsegv(sig, current);
	return -EFAULT;
}
           

由于對核心棧寄存器進行了修改(esp,eip),即當核心回到使用者态的時候,就到了信号處理函數進行執行,當信号處理函數執行完畢後,就回到了__kernel_sigreturn的彙編函數裡:

_ _kernel_sigreturn:
popl %eax  //将信号編号從棧中移除
movl $_ _NR_sigreturn, %eax //将sys_sigreturn系統調用編号放入eax
int $0x80 //觸發系統調用中斷,進入sys_sigreturn系統調用
           

然後sys_sigreturn系統調用對核心棧和使用者棧進行了恢複操作:

asmlinkage int sys_sigreturn(unsigned long __unused)
{
	struct pt_regs *regs = (struct pt_regs *) &__unused;
	//注意這個frame在setup_fram函數裡已經存到esp裡了
	struct sigframe __user *frame = (struct sigframe __user *)(regs->esp - 8);
	sigset_t set;
	int eax;
	//檢查使用者态的位址是否合法
	if (!access_ok(VERIFY_READ, frame, sizeof(*frame)))
		goto badframe;
	if (__get_user(set.sig[0], &frame->sc.oldmask)//将執行信号處理函數之前所阻塞的信号的位數組從frame->sc裡拷貝出來
	    || (_NSIG_WORDS > 1			
		&& __copy_from_user(&set.sig[1], &frame->extramask,
				    sizeof(frame->extramask))))
		goto badframe;

	sigdelsetmask(&set, ~_BLOCKABLE);
	spin_lock_irq(¤t->sighand->siglock);
	current->blocked = set;//将目前程序的信号阻塞掩碼恢複成執行信号處理函數之前所阻塞的信号的位數組
	recalc_sigpending();
	spin_unlock_irq(¤t->sighand->siglock);
	
	if (restore_sigcontext(regs, &frame->sc, &eax))//将存放在frame裡面的硬體上下文恢複到目前核心棧裡,并從使用者态删除frame
		goto badframe;
	return eax;

badframe:
	force_sig(SIGSEGV, current);
	return 0;
}