- 论坛徽章:
- 0
|
通过代码解释整个过程会比较清楚:
内核代码版本 2.6.19-rc1-git7
你所说的这些同步异常的处理在CPU的中断表中有固定的位置, 如除0, 对应第0项.
内核初始化时调用trap_init()初始化同步异常中断表.
set_trap_gate(0,÷_error);
当用户空间除0, CPU自动跳转到内核态,并将当前寄存器信息压入内核堆栈, 然后调用entry.s中的divide_error()
我一直认为, entry.s是内核中最重要的一个文件, 完全理解这个文件, 对理解整个内核的运转非常关键.
ENTRY(divide_error)
RING0_INT_FRAME
pushl $0 # no error code
CFI_ADJUST_CFA_OFFSET 4
pushl $do_divide_error //将执行函数压栈
CFI_ADJUST_CFA_OFFSET 4
jmp error_code
CFI_ENDPROC
error_code:
......
call *%edi
jmp ret_from_exception
CFI_ENDPROC
所以, 除0错误在do_divide_error()中处理, 执行完后, 执行ret_from_exception().
很明显,对用户空间任务的处理不应该在do_divide_error()中实现, 因为这个函数执行在异常上下文中,优先级非常高.
do_divide_error()
#define DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr) \
fastcall void do_##name(struct pt_regs * regs, long error_code) \
{ \
/* 设置SIGINFO信息, 如果用户空间使用sigaction()挂了自己的信号处理函数,
且flag & SIGINFO, 可以接收到这些信息 */
siginfo_t info; \
info.si_signo = signr; \
info.si_errno = 0; \
info.si_code = sicode; \
info.si_addr = (void __user *)siaddr; \
/* 执行内核设置的监控函数, 如kprobe, oprofile等 */
if (notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr) \
== NOTIFY_STOP) \
return; \
/* 处理这个异常 */
do_trap(trapnr, signr, str, 0, regs, error_code, &info); \
}
static void __kprobes do_trap(int trapnr, int signr, char *str, int vm86,
struct pt_regs * regs, long error_code,
siginfo_t *info)
{
struct task_struct *tsk = current;
tsk->thread.error_code = error_code;
tsk->thread.trap_no = trapnr;
if (regs->eflags & VM_MASK) {
if (vm86)
goto vm86_trap;
goto trap_signal;
}
/* 很明显,如果内核出现除0的错误,说明内核代码有bug,必须触发oops */
if (!user_mode(regs))
goto kernel_trap;
/* 如果仅仅是用户空间任务除0, 发送信号给那个程序,
而不需要触发内核bug. 发送信号的过程仅仅是将当前任务的thread_info flag
添加上sigpending, 并将info信息添加到task->pending列表中 */
trap_signal: {
if (info)
force_sig_info(signr, info, tsk);
else
force_sig(signr, tsk);
return; //用户出错, 直接返回
}
kernel_trap: {
if (!fixup_exception(regs))
die(str, regs, error_code);
return;
}
vm86_trap: {
int ret = handle_vm86_trap((struct kernel_vm86_regs *) regs, error_code, trapnr);
if (ret) goto trap_signal;
return;
}
}
所以直接返回到ret_from_exception() (entry.s)
当用户空间任务从内核返回, 都会执行这样的路径:
ret_from_xxx() --> check_userspace() --> resume_userspace()
在resume_userspace()中:
ENTRY(resume_userspace)
DISABLE_INTERRUPTS # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # 信号被设置, 会触发这个条件
jne work_pending
jmp restore_all
work_pending:
testb $_TIF_NEED_RESCHED, %cl #如果当前任务同时被设置了需要重新调度,
#为保证实时性,会首先执行重新调度,但在除0异常中, 不可能触发这个,
#因为中断始终没有放开
jz work_notifysig
work_resched:
call schedule
DISABLE_INTERRUPTS # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # is there any work to be done other
# than syscall tracing?
jz restore_all
testb $_TIF_NEED_RESCHED, %cl
jnz work_resched
work_notifysig: # deal with pending signals and
# notify-resume requests
testl $VM_MASK, EFLAGS(%esp)
movl %esp, %eax
jne work_notifysig_v86 # returning to kernel-space or
# vm86-space
xorl %edx, %edx
call do_notify_resume #deliver信号到用户空间!!
jmp resume_userspace_sig #循环检查是否有信号要deliver
由上可见, do_notify_resume() 才会将信号deliver到用户空间.
如果用户没有挂除0处理函数,执行路径:
i386/kernel/signal.c do_notify_resume() --> do_signal() --> get_signal_to_deliver()
在get_signal_to_deliver()中, 用户空间任务直接被do_group_exit() 杀掉.
如果用户挂了除0的处理函数,执行路径:
i386/kernel/signal.c do_notify_resume() --> do_signal() --> handle_signal()
handle_signal()比较复杂, 他要构建用户栈, 修改eip等寄存器, 这样从内核返回后(entry.s中的restore_all),
会直接调用用户空间的信号处理函数. 信号处理函数执行完后, 通过vdso重新恢复上一条指令, vsyscall_sigreturn.S. |
|