一. 如果发生异常或中断,cpu将做以下4件事:
1.确定所发生的中断或异常向量i(在0~255之间)
2.通过 IDTR(中断描述表)寄存器,读取IDT表的第i项(或者叫第i个门)。
3.有效性检查:
a) “段“级检查,IDT 中第i 项段选择符中的DPLDPL与CPL做比较
DPL(3) > CPL(0),产生一个“通用保护“异常(中断向量13),
因为中断处理程序的特权级要大于引起中断的程序的特权级。
但是这种可能性不大,因为中断处理程序一般运行在内核态,其特权级为0。
b) “门”级检查,把CPL 与IDT 中第i 个门的DPL 相比较,如果CPL 大于DPL,
也就是当前特权级(3)小于这个门的特权级(0),CPU 就不能“穿过”这个门, 于是产生一个“通用保护”异常,这是为了避免用户应用程序访问特殊的陷阱门或 中断门。但是请注意,这种“门”级检查是针对一般的用户程序,而不包括外部 I/O 产生的中断或因CPU内部异常而产生的异常,也就是说,如果产生了中断或 异常,就免去了“门”级检查。
4.检测检测特权级是否变化
a) 当中断发生在用户态(特权级为3),而中断处理程序运行在内核态(特权级为0),
特权级发生了变化,所以会引起堆栈的更换
b) 从用户堆栈切换到内核堆栈。而当中断发生在内核态时,
即CPU 在内核中运行时,则不会更换堆栈.
二. 由异常引起的中断,进入中断服务程序时,cpu要将当前EFLAGS寄存器的内容以及返回地址压入堆栈,返回地址是由段寄存器cs的内容和取指针EIP的内容共同组成的,还要把异常原因出错代码也压入堆栈。
用户栈的SS |
用户栈的ESP |
EFLAGS |
用户空间的CS |
EIP |
错误码或0 |
函数地址 |
GS |
FS |
ES |
DS |
EAX |
EBP |
EDI |
ESI |
EDX |
ECX |
EBX |
①ENTRY(name)
RING0_EC_FRAME
pushl $do_handler_name //将异常处理函数压入栈中
CFI_ADJUST_CFA_OFFSET 4
jmp error_code //为异常处理程序调用和返回进行相关操作
CFI_ENDPROC
END(name)
#==================================================================
②error_code:
/* the function address is in %gs’s slot on the stack */
pushl %fs
CFI_ADJUST_CFA_OFFSET 4 //4字节对齐
/*CFI_REL_OFFSET fs, 0*/
pushl %es
CFI_ADJUST_CFA_OFFSET 4
/*CFI_REL_OFFSET es, 0*/
pushl %ds
CFI_ADJUST_CFA_OFFSET 4
/*CFI_REL_OFFSET ds, 0*/
pushl %eax
CFI_ADJUST_CFA_OFFSET 4
CFI_REL_OFFSET eax, 0
pushl %ebp
CFI_ADJUST_CFA_OFFSET 4
CFI_REL_OFFSET ebp, 0
pushl %edi //把C 函数可能用到的寄存器都保存在栈中
CFI_ADJUST_CFA_OFFSET 4
CFI_REL_OFFSET edi, 0
pushl %esi
CFI_ADJUST_CFA_OFFSET 4
CFI_REL_OFFSET esi, 0
pushl %edx
CFI_ADJUST_CFA_OFFSET 4
CFI_REL_OFFSET edx, 0
pushl %ecx
CFI_ADJUST_CFA_OFFSET 4
CFI_REL_OFFSET ecx, 0
pushl %ebx
CFI_ADJUST_CFA_OFFSET 4
CFI_REL_OFFSET ebx, 0
Cld // 清eflags 的方向标志,以确保edi 和esi 寄存器的值自动增加
movl $(__KERNEL_PERCPU), %ecx
movl %ecx, %fs
UNWIND_ESPFIX_STACK
GS_TO_REG %ecx
movl PT_GS(%esp), %edi # get the function address
movl PT_ORIG_EAX(%esp), %edx # get the error code
movl $-1, PT_ORIG_EAX(%esp) # no syscall to restart
REG_TO_PTGS %ecx
SET_KERNEL_GS %ecx
movl $(__USER_DS), %ecx
movl %ecx, %ds //把内核数据段选择符装入ds 寄存器
movl %ecx, %es
TRACE_IRQS_OFF
movl %esp,%eax //中存放当前进程task_struct 结构的地址
call *%edi //调用这个异常处理程序
jmp ret_from_exception
CFI_ENDPROC
#==================================================================
③ret_from_exception:
preempt_stop(CLBR_ANY)
ret_from_intr:
GET_THREAD_INFO(%ebp) //获取当前线程数据结构
check_userspace: //检测是否是特权级别
movl PT_EFLAGS(%esp), %eax # mix EFLAGS and CS
movb PT_CS(%esp), %al
andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax
cmpl $USER_RPL, %eax
jb resume_kernel # not returning to v8086 or userspace
ENTRY(resume_userspace)
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we dont 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 on
# int/exception return?
jne work_pending //处理异常
jmp restore_all
END(ret_from_exception)
#==================================================================
# perform work that needs to be done immediately before resumption
ALIGN
RING0_PTREGS_FRAME # cant unwind into user space anyway
④work_pending:
testb $_TIF_NEED_RESCHED, %cl
jz work_notifysig
work_resched:
call schedule
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we dont 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
xorl %edx, %edx
call do_notify_resume // 用于通知当前线程恢复用户空间执行。
jmp resume_userspace_sig
END(work_pending)
#==================================================================
# 在调度信号处理函数之前,把错误现场存到用户空间栈,然后在执行信号处理函数
#==================================================================
do_signal –>do_signal –>handle_signal–>setup_rt_frame
setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
sigset_t *set, struct pt_regs *regs)
{
int usig = signr_convert(sig);
int ret;
/* Set up the stack frame */
if (is_ia32) {
if (ka->sa.sa_flags & SA_SIGINFO) //sa_flags包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。
ret = ia32_setup_rt_frame(usig, ka, info, set, regs);
else
ret = ia32_setup_frame(usig, ka, set, regs);
} else
ret = __setup_rt_frame(sig, ka, info, set, regs);
if (ret) {
force_sigsegv(sig, current);
return -EFAULT;
}
return ret;
}
ia32_setup_rt_frame() –>get_sigframe()
未经许可,不得转载!