中断描述符寄存器idt
存在两个指令lidt和sidt分别是存储到寄存器和加载到内存
// 16byte gate
struct gate_struct {
u16 offset_low;//函数地址低16位
u16 segment;//段地址
//ist表示使用per-cpu中断栈还是ist中断栈,type是何种类型的中断,dpl则是权限位
unsigned ist : 3, zero0 : 5, type : 5, dpl : 2, p : 1;
u16 offset_middle;//函数地址中间16位
u32 offset_high;//函数地址高32位
u32 zero1;
} __attribute__((packed));//告诉编译器不对齐优化,实际多少字节则就是多少字节
相关函数
static inline void set_intr_gate(int nr, void *func)
{
_set_gate(&idt_table[nr], GATE_INTERRUPT, (unsigned long) func, 0, 0);//设置中断门类型
}
static inline void _set_gate(void *adr, unsigned type, unsigned long func, unsigned dpl, unsigned ist)
{
struct gate_struct s;
s.offset_low = PTR_LOW(func);
s.segment = __KERNEL_CS;//内核代码段的段地址
s.ist = ist;
s.p = 1;
s.dpl = dpl;
s.zero0 = 0;
s.zero1 = 0;
s.type = type;
s.offset_middle = PTR_MIDDLE(func);
s.offset_high = PTR_HIGH(func);
/* does not need to be atomic because it is only done once at setup time */
memcpy(adr, &s, 16);
}
看下set_intr_gate函数调用传入的全局变量idt_table,
extern struct gate_struct idt_table[];,也就是引用外部的一个中断门结构体数组
在start_kernel里的调用的是trap_init,下面看看trap_init函数做了什么
void __init trap_init(void)
{
set_intr_gate(0,÷_error);
set_intr_gate_ist(1,&debug,DEBUG_STACK);
set_intr_gate_ist(2,&nmi,NMI_STACK);
set_system_gate(3,&int3);
set_system_gate(4,&overflow); /* int4-5 can be called from all */
set_system_gate(5,&bounds);
set_intr_gate(6,&invalid_op);
set_intr_gate(7,&device_not_available);
set_intr_gate_ist(8,&double_fault, DOUBLEFAULT_STACK);
set_intr_gate(9,&coprocessor_segment_overrun);
set_intr_gate(10,&invalid_TSS);
set_intr_gate(11,&segment_not_present);
set_intr_gate_ist(12,&stack_segment,STACKFAULT_STACK);
set_intr_gate(13,&general_protection);
set_intr_gate(14,&page_fault);
set_intr_gate(15,&spurious_interrupt_bug);
set_intr_gate(16,&coprocessor_error);
set_intr_gate(17,&alignment_check);
#ifdef CONFIG_X86_MCE
set_intr_gate_ist(18,&machine_check, MCE_STACK);
#endif
set_intr_gate(19,&simd_coprocessor_error);
#ifdef CONFIG_IA32_EMULATION
set_system_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
#endif
set_intr_gate(KDB_VECTOR, call_debug);
/*
* Should be a barrier for any external CPU state.
*/
cpu_init();
}
初始化中断门结构体数组。在cpu_init函数里存在几句关键信息
void __init cpu_init (void)
{
.....
cpu_gdt_descr[cpu].size = GDT_SIZE;
cpu_gdt_descr[cpu].address = (unsigned long)cpu_gdt_table[cpu];
asm volatile("lgdt %0" :: "m" (cpu_gdt_descr[cpu]));
asm volatile("lidt %0" :: "m" (idt_descr));
......
}
lidt将idt_descr保存的中断门描述符地址保存在idt寄存器里
struct desc_ptr idt_descr = { 256 * 16, (unsigned long) idt_table };
下面看下desc_ptr结构体
struct desc_ptr {
unsigned short size;//256*16表项
unsigned long address;//保存了idt_table地址
} __attribute__((packed)) ;//10字节
继续看一个除0异常处理
ENTRY(dividec_error)
pushl $0 # no error code,一般用于存在错误码
pushl $do_divide_error //函数错误地址
ALIGN
error_code:
pushl %ds //段寄存器入栈
pushl %eax
xorl %eax, %eax //清零eax
pushl %ebp
pushl %edi
pushl %esi
pushl %edx
decl %eax # eax = -1
pushl %ecx
pushl %ebx
cld
pushl %es
UNWIND_ESPFIX_STACK
popl %ecx
movl ES(%esp), %edi # get the function address ES=0x20保存的是函数地址
movl ORIG_EAX(%esp), %edx # get the error code ORIG_EAX=0x24错误码,edx此时保存了错误码
movl %eax, ORIG_EAX(%esp) //将0写入错误码的位置
movl %ecx, ES(%esp)
movl $(__USER_DS), %ecx
movl %ecx, %ds
movl %ecx, %es
movl %esp,%eax # pt_regs pointer,eax保存了保存的寄存器内容
call *%edi //调用错误处理函数,eax、edx保存了函数参数
jmp ret_from_exception //这里是从异常返回
恢复
#define preempt_stop cli
ret_from_exception:
preempt_stop //也就是cli指令,cli指令是关闭中断
ret_from_intr:
GET_THREAD_INFO(%ebp) //获取thread_info保存在ebp中
movl EFLAGS(%esp), %eax # mix EFLAGS and CS
movb CS(%esp), %al
testl $(VM_MASK | 3), %eax //判断eflags和cs是否处于内核态,如果是则跳转
jz resume_kernel
ENTRY(resume_userspace)
cli # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
movl TI_flags(%ebp), %ecx
//检查eflags里的标志位如果只设置了_TIF_SYSCALL_TRACE、_TIF_SYSCALL_AUDIT、_TIF_SINGLESTEP、_TIF_SECCOMP标志则只是跳转到restore_all继续执行
andl $_TIF_WORK_MASK, %ecx # is there any work to be done on
# int/exception return?
jne work_pending //继续执行之前被pending的任务
jmp restore_all //恢复用户态程序
work_pending:
testb $_TIF_NEED_RESCHED, %cl //设置了需要调度的标志则不会执行下面的jz指令(如果执行结果zf=1则说明没被置位jz执行)
jz work_notifysig
work_resched:
call schedule //调度
cli # 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 # is there any work to be done other
# than syscall tracing? //判断是否还有请求没有完成,如果没有了则jz跳转
jz restore_all //调度回来继续执行之前的进程
testb $_TIF_NEED_RESCHED, %cl //继续判断是否置位了调度标志,置位了则执行jnz指令继续调度,否则执行jnz之后的语句
jnz work_resched
work_notifysig: # deal with pending signals and
# notify-resume requests
testl $VM_MASK, EFLAGS(%esp) //如果设置了VM标志,则进行信号处理
movl %esp, %eax
jne work_notifysig_v86 # returning to kernel-space or
# vm86-space
xorl %edx, %edx //清零edx
call do_notify_resume //调用c函数do_notify_resume处理单步信号
jmp restore_all //继续执行原有的进程
#ifdef CONFIG_PREEMPT
ENTRY(resume_kernel)
cli //关中断
cmpl $0,TI_preempt_count(%ebp) # non-zero preempt_count ? //是否关了抢占,如果关了则执行jnz跳转
jnz restore_nocheck
need_resched:
movl TI_flags(%ebp), %ecx # need_resched set ? //调度标志
testb $_TIF_NEED_RESCHED, %cl //如果没有设置需要调度的标志则执行jz(zf==0)
jz restore_all //恢复执行
testl $IF_MASK,EFLAGS(%esp) # interrupts off (exception path) ?//判断是否是关中断的情况,如果是(zf==1)则说明不能执行调度
jz restore_all//恢复执行
call preempt_schedule_irq //调用preempt_schedule_irq函数执行
jmp need_resched//重新判断
#endif
恢复段的代码
restore_all:
movl EFLAGS(%esp), %eax # mix EFLAGS, SS and CS
# Warning: OLDSS(%esp) contains the wrong/random values if we
# are returning to the kernel.
# See comments in process.c:copy_thread() for details.
movb OLDSS(%esp), %ah
movb CS(%esp), %al
andl $(VM_MASK | (4 << 8) | 3), %eax
cmpl $((4 << 8) | 3), %eax
je ldt_ss # returning to user-space with LDT SS
restore_nocheck:
RESTORE_REGS //恢复寄存器
addl $4, %esp
1: iret