x86-idt初始化

这篇博客详细介绍了x86架构下中断描述符表(IDT)的初始化过程,包括如何设置中断门和系统门,以及异常处理流程。通过`set_intr_gate`等函数配置中断处理程序,并在`trap_init`和`cpu_init`中初始化IDT。在异常处理过程中,如除0错误,会保存现场并调用相应的错误处理函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

### 关于ArcGIS License Server无法启动的解决方案 当遇到ArcGIS License Server无法启动的情况,可以从以下几个方面排查并解决问题: #### 1. **检查网络配置** 确保License Server所在的计算机能够被其他客户端正常访问。如果是在局域网环境中部署了ArcGIS Server Local,则需要确认该环境下的网络设置是否允许远程连接AO组件[^1]。 #### 2. **验证服务状态** 检查ArcGIS Server Object Manager (SOM) 的运行情况。通常情况下,在Host SOM机器上需将此服务更改为由本地系统账户登录,并重启相关服务来恢复其正常工作流程[^2]。 #### 3. **审查日志文件** 查看ArcGIS License Manager的日志记录,寻找任何可能指示错误原因的信息。这些日志可以帮助识别具体是什么阻止了许可服务器的成功初始化。 #### 4. **权限问题** 确认用于启动ArcGIS License Server的服务账号具有足够的权限执行所需操作。这包括但不限于读取/写入特定目录的权利以及与其他必要进程通信的能力。 #### 5. **软件版本兼容性** 保证所使用的ArcGIS产品及其依赖项之间存在良好的版本匹配度。不一致可能会导致意外行为完全失败激活license server的功能。 #### 示例代码片段:修改服务登录身份 以下是更改Windows服务登录凭据的一个简单PowerShell脚本例子: ```powershell $serviceName = "ArcGISServerObjectManager" $newUsername = ".\LocalSystemUser" # 替换为实际用户名 $newPassword = ConvertTo-SecureString "" -AsPlainText -Force Set-Service -Name $serviceName -StartupType Automatic New-ServiceCredential -ServiceName $serviceName -Account $newUsername -Password $newPassword Restart-Service -Name $serviceName ``` 上述脚本仅作为示范用途,请依据实际情况调整参数值后再实施。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值