中断初始化

下面的分析是基于3.6.10内核版本。

中断描述符

硬中断产生之后通过门描述符来寻找中断处理程序的入口。中断描述符表的每个表项由8字节组成,叫做一个门描述符。主要包括三种门描述符:陷阱门、中断门和系统门。

这里看下中断门描述符的结构:

中断门描述主要包括中断如理程序所在的段选择符和段内偏移及一些标志位。低4字节的bit16~bit31为段选择符,bit0~15为段内偏移量的低16位,高4字节的bit31~16为段内偏移量的高16位。

 

中断描述符初始化

◆在arch/x86/kernel/head_64.S

.section .bss, "aw", @nobits

.align L1_CACHE_BYTES

ENTRY(idt_table)

.skip IDT_ENTRIES * 16

上面的汇编代码的意思是变量idt_table在.bss段内,预留空间大小为256*16字节,并且每个字节的值都为0

 

◆在arch/x86/kernel/cpu/common.c

struct desc_ptr idt_descr NR_VECTORS 16 1, (unsigned longidt_table };

IDT的大小为256*16字节,地址在idt_table

 

◆在arch/x86/kernel/cpu/common.ccpu_init函数中

load_idt((const struct desc_ptr *)&idt_descr);  

IDT表加载到IDTR寄存器中。中断描述符表IDT可以驻留在线性地址空间的任何地方,使用IDTR寄存器定位IDT表的位置。

 

下面看下初始化的整个流程:

1、 在start_kernel函数中会调用trap_init函数来初始化陷阱门和系统调用门,init_IRQ函数来初始化中断门描述符,调用early_irq_init初始化与硬件无关的中断门相关事项,在后面会分析此函数。

2、 init_IRQ函数通过x86_init操作集的intr_init调用native_init_IRQ函数

apic_intr_init();    

FIRST_EXTERNAL_VECTOR 

for_each_clear_bit_from(iused_vectorsNR_VECTORS) {  

/* IA32_SYSCALL_VECTOR could be used in trap_init already. */  

set_intr_gate(iinterrupt[FIRST_EXTERNAL_VECTOR]);  

 

apic_intr_init用来初始化与APIC中断控制器相关的中断门描述符的。FIRST_EXTERNAL_VECTOR值为0x20IDT表中外部中断向量从32开始。在前面trap_init函数中对所有的陷阱门描述符和系统调用门描述符都设置了used_vectors数组中的相应位,剩下没有设置的就是用作外部中断的中断门描述符了。Interrupt数组保存了这些外部中断描述符门的处理程序的地址。

3alloc_intr_gate函数首先调用alloc_system_vector设置中断向量对应在used_vectors数组中的位,然后调用set_intr_gate函数设置一个中断描述符表项。

static inline void set_intr_gate(unsigned int nvoid *addr

 

 BUG_ON((unsigned)n 0xFF);  

 _set_gate(nGATE_INTERRUPTaddr00__KERNEL_CS);  

}  

首先判断中断向量号是否大于255,然后调用_set_gate函数。

static inline void _set_gate(int gateunsigned typevoid *addr 

        unsigned dplunsigned istunsigned seg 

  

 pack_gate(&stype, (unsigned long)addrdplistseg);    

 write_idt_entry(idt_tablegate&s);  

}  

pack_gate函数主要是填充中断门描述符表项的8字节的相应位。中断处理程序地址填充为段内偏移,段选择符为__KERNEL_CS,特权级DPL0,用户态程序不能访问中断门。所有中断程序的访问都必须经过中断门,这样中断的处理就限制在内核态了。

 

中断入口

在前面中断描述符初始化中看到一般外部硬中断的处理程序地址存放在Interrupt数组中。下面看下Interrupt:(arch/x86/kernel/entry_64.S)

.section .init.rodata,"a"

ENTRY(interrupt)

.section .entry.text

.p2align 5

.p2align CONFIG_X86_L1_CACHE_SHIFT

ENTRY(irq_entries_start)

INTR_FRAME

vector=FIRST_EXTERNAL_VECTOR

.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7

.balign 32

  .rept 7

    .if vector < NR_VECTORS

      .if vector <> FIRST_EXTERNAL_VECTOR

CFI_ADJUST_CFA_OFFSET -8

      .endif

1: pushq_cfi $(~vector+0x80)    //将中断向量号取反加上0x80后压栈

      .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6

jmp 2f

      .endif

      .previous    //使汇编器返回到前面的数据段进行汇编

.quad 1b       //存储着标签1处的地址

      .section .entry.text

vector=vector+1

    .endif

  .endr

2: jmp common_interrupt

.endr

CFI_ENDPROC

END(irq_entries_start)

 

.previous

END(interrupt)

.previous

1、 变量Interrupt数组位于.init.rodata数据段中,变量irq_entries_start位于.entry.text代码段中。

2、 下面来看下irq_entries_start干了些什么。宏INTR_FRAME用来初始化中断时的栈帧状态。Vector初始值为FIRST_EXTERNAL_VECTOR(即为32),在外循环中会重复(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7 = (256 – 32 + 6) / 7 = 32次。

3、 内循环重复7次,宏pushq_cfi使用pushq指令将(~vector+0x80)的值压栈,然后做一些检查之后就跳转到common_interrupt函数。

 

上面的代码相当于是把NR_VECTORS-FIRST_EXTERNAL_VECTOR个中断处理程序的地址存在变量Interrupt数组开始的数据段中,处理程序的代码存储在变量irq_entries_start开始的代码段中。common_interrupt函数是这些中断处理程序的公共的入口,只是每个中断压栈的参数中断向量号不同而已。

common_interrupt函数分析:

common_interrupt:

XCPT_FRAME

addq $-0x80,(%rsp) /* Adjust vector to [-256,-1] range */

interrupt do_IRQ

/* 0(%rsp): old_rsp-ARGOFFSET */

ret_from_intr:

common_interrupt函数使用宏interrupt来调用do_IRQ函数,当然在进入do_IRQ函数之前,做了压栈保存现场的操作,以及设置gs寄存器值等。

进入do_IRQ函数之后的处理流程这里就不分析了。

 

Linux中断初始化是在内核初始化过程中完成的。具体来说,中断初始化包括三个方面:中断子系统初始化中断或异常处理和中断API的初始化。首先,在中断子系统初始化过程中,内核会对中断处理机制进行初始化,包括中断描述符表的初始化中断请求队列的初始化等。其次,中断或异常处理是实际处理中断或异常的过程。最后,中断API提供一组函数给设备驱动程序使用,包括注册与释放、激活与禁止等操作。在中断描述符表的初始化中,有两个阶段。第一阶段发生在内核引导过程中,主要完成为中断描述符表分配空间和初始化默认值,同时将IDT的起始位置存储到IDTR寄存器。第二阶段发生在内核初始化过程中,使用trap_init和init_IRQ函数进行初始化。trap_init函数完成对系统保留中断向量的初始化,而init_IRQ函数完成其余中断向量的初始化。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Linux中断机制:硬件处理,初始化中断处理](https://blog.csdn.net/wads23456/article/details/106105492)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [linux中断系列之中断子系统初始化(三)](https://blog.csdn.net/zhao2272062978/article/details/70272066)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值