admin管理员组

文章数量:1794759

x86

x86

中断描述符寄存器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,&divide_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
 

本文标签: x86