中断——中断描述符表的定义和初始化(一) (基于3.16-rc4)
1.中断描述符表的定义(arch/x86/kernel/traps.c)
gate_desc debug_idt_table[NR_VECTORS] __page_aligned_bss;
定义的描述符表为一个结构体数组,数组元素类型为gate_desc,大小为8B。NR_VECTORS宏为256,即描述符表大小为256*8B。
2.idt_descr变量的定义(arch/x86/kernel/head_32.S)
idt_descr:
.word IDT_ENTRIES*- # idt contains entries
.long idt_table # boot GDT descriptor (later on used by CPU#):
.word # bit align gdt_desc.address
这是内核定义的一个全局变量,存放有中断描述符表的大小和首地址。该变量将存放在idtr寄存器中。
3.中断描述符初步的初始化(arch/x86/kernel/head_32.S)
__INIT
setup_once:
/*
* Set up a idt with 256 entries pointing to ignore_int,
* interrupt gates. It doesn't actually load idt - that needs
* to be done on each CPU. Interrupts are enabled elsewhere,
* when we can be relatively sure everything is ok.
*/ movl $idt_table,%edi
movl $early_idt_handlers,%eax movl $NUM_EXCEPTION_VECTORS,%ecx
:
movl %eax,(%edi)
movl %eax,(%edi)
/* interrupt gate, dpl=0, present */
movl $(0x8E000000 + __KERNEL_CS),(%edi)
addl $,%eax
addl $,%edi
loop 1b movl $ - NUM_EXCEPTION_VECTORS,%ecx
movl $ignore_int,%edx
movl $(__KERNEL_CS << ),%eax
movw %dx,%ax /* selector = 0x0010 = cs */
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
:
movl %eax,(%edi)
movl %edx,(%edi)
addl $,%edi
loop 2b
...
...
这段代码是对中断描述符表的初步初始化,14-20行是对前32个中断描述符进行初始化,让所有描述符指向early_idt_handlers处理函数。22-31行是对后256-32=224个中断描述符进行初始化,使之指向ignore_int处理函数。省略号以后是对GDT描述符表的初始化,这里不予讨论。
4.中断描述符表最终的初始化(arch/x86/kernel/traps.c)
void __init trap_init(void)
{
int i; #ifdef CONFIG_EISA
void __iomem *p = early_ioremap(0x0FFFD9, ); if (readl(p) == 'E' + ('I'<<) + ('S'<<) + ('A'<<))
EISA_bus = ;
early_iounmap(p, );
#endif set_intr_gate(X86_TRAP_DE, divide_error);
set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
/* int4 can be called from all */
set_system_intr_gate(X86_TRAP_OF, &overflow);
set_intr_gate(X86_TRAP_BR, bounds);
set_intr_gate(X86_TRAP_UD, invalid_op);
set_intr_gate(X86_TRAP_NM, device_not_available);
#ifdef CONFIG_X86_32
set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
#else
set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
#endif
set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun);
set_intr_gate(X86_TRAP_TS, invalid_TSS);
set_intr_gate(X86_TRAP_NP, segment_not_present);
set_intr_gate_ist(X86_TRAP_SS, &stack_segment, STACKFAULT_STACK);
set_intr_gate(X86_TRAP_GP, general_protection);
set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug);
set_intr_gate(X86_TRAP_MF, coprocessor_error);
set_intr_gate(X86_TRAP_AC, alignment_check);
#ifdef CONFIG_X86_MCE
set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);
#endif
set_intr_gate(X86_TRAP_XF, simd_coprocessor_error); /* Reserve all the builtin and the syscall vector: */
for (i = ; i < FIRST_EXTERNAL_VECTOR; i++)
set_bit(i, used_vectors); #ifdef CONFIG_IA32_EMULATION
set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
set_bit(IA32_SYSCALL_VECTOR, used_vectors);
#endif #ifdef CONFIG_X86_32
set_system_trap_gate(SYSCALL_VECTOR, &system_call);FIRST_EXTERNAL_VECTOR
set_bit(SYSCALL_VECTOR, used_vectors);
#endif /*
* Set the IDT descriptor to a fixed read-only location, so that the
* "sidt" instruction will not leak the location of the kernel, and
* to defend the IDT against arbitrary memory write vulnerabilities.
* It will be reloaded in cpu_init() */
__set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);
idt_descr.address = fix_to_virt(FIX_RO_IDT); /*
* Should be a barrier for any external CPU state:
*/
cpu_init(); x86_init.irqs.trap_init(); #ifdef CONFIG_X86_64
memcpy(&debug_idt_table, &idt_table, IDT_ENTRIES * );
set_nmi_gate(X86_TRAP_DB, &debug);
set_nmi_gate(X86_TRAP_BP, &int3);
#endif
}
该函数对中断描述表的进行了部分初始化,13-36行对系统已分配的异常和非屏蔽中断进行初始化,中断向量号为0-19。接着,39-40行在中断位图表中对已初始化的中断所对应的位进行标记。接着,43和48行又出始化了两个中断,一个是系统中断门,中断向量号为0x80,一个是系统陷阱门,中断向量号为2。
在该函数中,大家可以看出,对中断进行初始化的函数有如下几个:
set_intr_gate()
set_system_intr_gate()
set_system_trap_gate()
set_task_gate()
这几个函数也在arch/x86/kernel/traps.c中定义。分别是对中断门,系统中断门,系统陷阱门,任务门描述符的初始化。进一步深入可发现,这几个函数都调用了如下的函数:
static inline void _set_gate(int gate, unsigned type, void *addr,
unsigned dpl, unsigned ist, unsigned seg)
{
gate_desc s; pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
/*
* does not need to be atomic because it is only done once at
* setup time
*/
write_idt_entry(idt_table, gate, &s);
write_trace_idt_entry(gate, &s);
}
该函数定义在arch/x86/include/asm/desc.h文件中。在该函数中定义了一个gate_desc类型变量s,并将s的指针传递给pack_gate函数,把要初始化的描述符各个字段的值临时存放在s中。下边分析下pack_gate函数,在分析该函数之前,我们先看下gate_desc结构体。
struct desc_struct {
union {
struct {
unsigned int a;
unsigned int b;
};
struct {
u16 limit0;
u16 base0;
unsigned base1: , type: , s: , dpl: , p: ;
unsigned limit: , avl: , l: , d: , g: , base2: ;
};
};
} __attribute__((packed)); typedef struct desc_struct gate_desc
该结构体定义位于arch/x86/include/asm/desc_defs.h中。该结构体中包含了一个共用体,共用体中又包含了两个结构体。我们知道,共用体在分配内存单元时,并不为每个成员都分配,而是为最大的成员来分配。可以看出该共用体的两个结构体成员大小相等,都是8B,因此整个gate_desc结构体大小就为8B。我们可以使用共用体中的任意一个结构体成员来为这个gate_desc赋值,也就是说我们既可以将gate_desc看成是struct { unsigned int a; unsigned int b; };也可以看成是struct { u16 limit0; u16 base0; .... };下面在分析pack_gate函数过程中将看到赋值过程,我们将gate_desc看作是struct { unsigned int a; unsigned int b; };。
static inline void pack_gate(gate_desc *gate, unsigned char type,
unsigned long base, unsigned dpl, unsigned flags,
unsigned short seg)
{
gate->a = (seg << ) | (base & 0xffff);
gate->b = (base & 0xffff0000) | (((0x80 | type | (dpl << )) & 0xff) << );
}
该函数也定义在arch/x86/include/asm/desc.h文件中。在该函数中为gate所指向的gate_desc描述符进行初始化。gate->a是描述符的0-31位,gate->b是描述符的32-63位。描述符的如下所示:
接着,我们分析_set_gate()中的11行,write_idt_entry()调用。
static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
{
memcpy(&idt[entry], gate, sizeof(*gate));
} #define write_idt_entry() native_write_idt_entry() //粗略的写了下,大家能明白就行
该函数定义在arch/x86/include/asm/desc.h中。在该函数中,使用memcpy()函数将gate中的字段复制到&idt[entry]所指向的各个字段中。很显然,idt[]数组就是内核中定义的中断描述符表,我们在文章开头给大家看过该定义。gate就是我们在_set_gate()中定义的临时变量s,在这里我们将s中的字段值赋给idt[]数组的对应元素,至此一个描述符的初始化工作就全部完成了,s变量的用途也就结束了,另外,entry变量中存放的是要初始化的中断向量号,用该号来定位idt数组的元素。
最后,再补充说明一点东西,回头看下第4点中的trap_init()函数,在该函数中对中断描述符表进行初始化,使用了很多初始化函数比如set_intr_gate()或set_system_intr_gate()等等,我们拿第一个初始化函数set_intr_gate(X86_TRAP_DE, divide_error)来做说明。X86_TRAP_DE是枚举类型参数,代表的是中断向量号,定义在arch/x86/include/asm/traps.h文件中。这种枚举类型其实有很多。
/* Interrupts/Exceptions */
enum {
X86_TRAP_DE = , /* 0, Divide-by-zero */
X86_TRAP_DB, /* 1, Debug */
X86_TRAP_NMI, /* 2, Non-maskable Interrupt */
X86_TRAP_BP, /* 3, Breakpoint */
X86_TRAP_OF, /* 4, Overflow */
X86_TRAP_BR, /* 5, Bound Range Exceeded */
X86_TRAP_UD, /* 6, Invalid Opcode */
X86_TRAP_NM, /* 7, Device Not Available */
X86_TRAP_DF, /* 8, Double Fault */
X86_TRAP_OLD_MF, /* 9, Coprocessor Segment Overrun */
X86_TRAP_TS, /* 10, Invalid TSS */
X86_TRAP_NP, /* 11, Segment Not Present */
X86_TRAP_SS, /* 12, Stack Segment Fault */
X86_TRAP_GP, /* 13, General Protection Fault */
X86_TRAP_PF, /* 14, Page Fault */
X86_TRAP_SPURIOUS, /* 15, Spurious Interrupt */
X86_TRAP_MF, /* 16, x87 Floating-Point Exception */
X86_TRAP_AC, /* 17, Alignment Check */
X86_TRAP_MC, /* 18, Machine Check */
X86_TRAP_XF, /* 19, SIMD Floating-Point Exception */
X86_TRAP_IRET = , /* 32, IRET Exception */
};
第二个参数,是汇编函数的函数名(在这里作为函数指针来使用),该函数为内核原先就定义好的中断或异常处理程序。这种类型的函数有很多,都定义在arch/x86/kernel/entry_32.S文件中,下边我们列举几个给大家看看,有兴趣自己去查。
ENTRY(segment_not_present)
RING0_EC_FRAME
ASM_CLAC
pushl_cfi $do_segment_not_present
jmp error_code
CFI_ENDPROC
END(segment_not_present) ENTRY(stack_segment)
RING0_EC_FRAME
ASM_CLAC
pushl_cfi $do_stack_segment
jmp error_code
CFI_ENDPROC
END(stack_segment) ENTRY(alignment_check)
RING0_EC_FRAME
ASM_CLAC
pushl_cfi $do_alignment_check
jmp error_code
CFI_ENDPROC
END(alignment_check) ENTRY(divide_error)
RING0_INT_FRAME
ASM_CLAC
pushl_cfi $ # no error code
pushl_cfi $do_divide_error
jmp error_code
CFI_ENDPROC
END(divide_error)
这些汇编代码只是异常处理程序的开头一部分,可以看到每一个汇编段中,都有一条pushl_cfi $do_***的指令,该$do_***才是真正的异常处理程序(函数名,也是函数指针),现将该函数名压入栈中,然后通过jmp error_code指令跳转到$do_***函数中。error_code其实也是一段汇编代码,如下所示:
1 error_code:
2 /* the function address is in %gs's slot on the stack */
3 pushl_cfi %fs
4 /*CFI_REL_OFFSET fs, 0*/
5 pushl_cfi %es
6 /*CFI_REL_OFFSET es, 0*/
7 pushl_cfi %ds
8 /*CFI_REL_OFFSET ds, 0*/
9 pushl_cfi %eax
10 CFI_REL_OFFSET eax, 0
11 pushl_cfi %ebp
12 CFI_REL_OFFSET ebp, 0
13 pushl_cfi %edi
14 CFI_REL_OFFSET edi, 0
15 pushl_cfi %esi
16 CFI_REL_OFFSET esi, 0
17 pushl_cfi %edx
18 CFI_REL_OFFSET edx, 0
19 pushl_cfi %ecx
20 CFI_REL_OFFSET ecx, 0
21 pushl_cfi %ebx
22 CFI_REL_OFFSET ebx, 0
23 cld
24 movl $(__KERNEL_PERCPU), %ecx
25 movl %ecx, %fs
26 UNWIND_ESPFIX_STACK
27 GS_TO_REG %ecx
28 movl PT_GS(%esp), %edi # get the function address
29 movl PT_ORIG_EAX(%esp), %edx # get the error code
30 movl $-1, PT_ORIG_EAX(%esp) # no syscall to restart
31 REG_TO_PTGS %ecx
32 SET_KERNEL_GS %ecx
33 movl $(__USER_DS), %ecx
34 movl %ecx, %ds
35 movl %ecx, %es
36 TRACE_IRQS_OFF
37 movl %esp,%eax # pt_regs pointer
38 call *%edi
39 jmp ret_from_exception
40 CFI_ENDPROC
41 END(page_fault)
该片段来自arch/x86/kernel/entry_32.S文件中。代码的开始部分3-22行,对寄存器进行压栈操作,因为这些寄存器将要在随后的异常处理程序中用到,所以事先要保存。最后可以看到在38行,执行了call %edi命令,调用了最终的异常处理程序,在28行可以看到将异常处理程序地址存入了edi寄存器中。第39行通过跳入ret_from_exception中,返回被中断的进程。
至此,中断描述符的初始化工作就告一段落。文中有问题的地方希望大家指正。qq:1193533825
中断——中断描述符表的定义和初始化(一) (基于3.16-rc4)的更多相关文章
- 中断——中断描述符表的定义和初始化(二) (基于3.16-rc4)
上篇博文对中断描述符表(IDT)中异常和非屏蔽中断部分的初始化做了说明,这篇文章将分析外部中断部分的初始化. 在上篇博文中,可以看到,内核在setup_once汇编片段中,对中断和异常部分做了初步的初 ...
- linux中断子系统:中断号的映射与维护初始化mmap过程
本文均属自己阅读源代码的点滴总结.转账请注明出处谢谢. 欢迎和大家交流.qq:1037701636 email:gzzaigcn2009@163.com 写在前沿: 好久好久没有静下心来整理一些东西了 ...
- Linux内核学习之中断 中断本质【转】
转自:http://www.linuxidc.com/Linux/2011-11/47657.htm [中断概述] 中断本质上是一种特殊的电信号,由硬件设备发向处理器.异常和中断的不同是异常在产生时必 ...
- 软中断与硬中断 & 中断抢占 中断嵌套
参考了这篇文章:http://blog.csdn.net/zhangskd/article/details/21992933 从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通 ...
- c语言中结构体的定义、初始化及内存分配
#include <stdio.h> struct person { char *name; int age; }; int main() { //结构体可以定义在函数内,也可以定义到函数 ...
- 你好,C++(7)第三部分 C++世界众生相 3.2.1 变量的定义与初始化
第3部分 C++世界众生相 在听过了HelloWorld.exe的自我介绍,完成了与C++世界的第一次亲密接触后,大家是不是都急不可待地想要一试身手,开始编写C++程序了呢?程序的两大任务是描述数据和 ...
- C 语言字符数组的定义与初始化
1.字符数组的定义与初始化字符数组的初始化,最容易理解的方式就是逐个字符赋给数组中各元素.char str[10]={ 'I',' ','a','m',' ',‘h’,'a','p','p','y'} ...
- C++中数组定义及初始化
一.一维数组 静态 int array[100]; 定义了数组array,并未对数组进行初始化 静态 int array[100] = {1,2}: 定义并初始化了数组array 动态 int* ar ...
- GoLang学习之变量定义和初始化
变量命名原则 go语言的变量名有字母数字和下划线组成,首字母不能为数字,但是字母不仅仅只限于英文字母,所有的UTF-8字符都是可以的. 变量声明和初始化方式 使用var关键字 var a int = ...
随机推荐
- uva 10534
一开始WA了 参考了一下 求正反两个方向的最长上升子序列 并分别记录在两个数组中 最后求最大值 #include <iostream> #include <cstdio&g ...
- Firefly安装说明 与 常见问题
原地址:http://bbs.gameres.com/thread_223688.html 第三方库依赖: twisted, python-memcached ftp://ftp.tummy.c ...
- 深入了解nagios的各配置文件
转自http://wolfword.blog.51cto.com/4892126/1220209 对每个配置文件进行讲解,深入理解nagios,好好学习,天天向上~ (1)templates.cf ...
- C++智能指针(auto_ptr)详解
智能指针(auto_ptr) 这个名字听起来很酷是不是?其实auto_ptr 只是C++标准库提供的一个类模板,它与传统的new/delete控制内存相比有一定优势,但也有其局限.本文总结的8个问题足 ...
- C++ 嵌套类使用(二)
C++嵌套类 1. 嵌套类的名字只在外围类可见. 2. 类的私有成员只有类的成员和友元可以访问,因此外围类不可以访问嵌套类的私有成员.嵌套类可以访问外围类的成员(通过对象.指针或者引用). 3 ...
- 利用VS2005进行dump文件调试
前言:利用drwtsn32或NTSD进行程序崩溃处理,都可以生成可用于调试的dmp格式文件.使用VS2005打开生成的DMP文件,能很方便的找出BUG所在位置.本文将讨论以下内容: 1. 程序编译选 ...
- maven新建Spring MVC + MyBatis + Oracle的Web项目中pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...
- BZOJ 1030 文本生成器
很老的题目了,很早以前学AC自动机的时候就A过一次 今天算是复习啦 我们可以把问题转化成一个给定字符串都没出现的字符串有多少个 我们建立AC自动机,设dp[i][j]表示走了i步当前在j节点上 在DP ...
- jdbc操作mysql
本文讲述2点: 一. jdbc 操作 MySQL .(封装一个JdbcUtils.java类,实现数据库表的增删改查) 1. 建立数据库连接 Class.forName(DRIVER); connec ...
- 【原创】通俗易懂的讲解KMP算法(字符串匹配算法)及代码实现
一.本文简介 本文的目的是简单明了的讲解KMP算法的思想及实现过程. 网上的文章的确有些杂乱,有的过浅,有的太深,希望本文对初学者是非常友好的. 其实KMP算法有一些改良版,这些是在理解KMP核心思想 ...