1, linux-2.6.11.12\arch\i386\kernel\traps.c:
#define SYSCALL_VECTOR 0x80
trap_init->
set_system_gate(
SYSCALL_VECTOR,&
system_call);
(陷阱门是在trap_init中设置)
2,linux-2.6.11.12\arch\i386\kernel\traps.c:
#define GDT_ENTRY_KERNEL_BASE 12
#define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE + 0)
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
#define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE + 0)
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
在保护模式下,中断向量表中的表项由8个字节组成,中断向量表也叫做中断描述符表IDT(Interrupt Descriptor Table,idt_table):
struct
desc_struct {
unsigned long a,b;
};
unsigned long a,b;
};
struct desc_struct
idt_table[256] __attribute__((__section__(".data.idt"))) = { {0, 0}, };
Intel i386共有256个中断,每个中断都有一个0~255之间的数来表示,Intel将前32个中断号(0~31)已经固定设定好或者保留未用。中断号32~255分配给操作系统和应用程序使用。在Linux中,中断号32~47对应于一个硬件芯片的16个中断请求信号,这16个中断包括时钟、键盘、软盘、数学协处理器、硬盘等硬件的中断。系统调用设为中断号128,即0x80。
idt_table门结构:
(1)16-31共16位是中断处理程序所在的段选择符。
(2)0-15位和48-64位组合起来形成32位偏移量,也就是中断处理程序所在段(由16-31位给出)的段内偏移。
(3)40-43位共4位表示描述符的类型。(0111:中断描述符,1010:任务门描述符,1111:陷阱门描述符)
(4)45-46两位标识描述符的访问特权级(DPL,Descriptor Privilege Level)。
(
5)47位P标识段是否在内存中。如果为1则表示段当前不再内存中。
3, linux-2.6.11.12\arch\i386\kernel\traps.c:
_set_gate设置门的具体指:
宏_set_gate()有四个参数:(1)gate_addr:描述符desc_struct结构类型的指针,指定待操作的描述符,通常指向数组idt_table中的某个项。(2)type:描述符类型,对应于门格式中的Type字段。(3)dpl:该描述符的权限级别;(4)addr:中断处理程序入口地址的段内偏移量,由于内核段的起始地址总是为0,因此中断处理程序在内核段中的段内偏移量也就是中断处理程序的入口地址(即核心虚地址)。(5)seg指示内核代码段。
根据上述设置:
1), DPL=3为陷阱门, 用于系统调用,DPL为3,允许用户态直接使用int指令访问,这样才能通过int 0x80访问系统调用;
2), type=15=0b111表示陷阱门;
3), 0x8000用于设置第47位P=1,表示段当前不在内存中;
4), seg<<16,即__KERNEL_CS<<16写入
中断处理程序所在的段选择符(SEGMENT SELECTOR),
由于Linux内核代码均在段选择子__KERNEL_CS
所指向的内核段中,因此门中的Segment Selector字段总是等于
__KERNEL_CS
;
5), (char
*)addr(
system_call
)
写入OFFSET[0-15],OFFSET[16-31];
4,
linux-2.6.11.12\arch\i386\kernel\traps.c:
定义了系统调用system_call,用于调用具体的系统调用。
asm
linkage int system_call(void);
system_call的是在arch\i386\kernel\entry.S中用汇编实现:
# system call handler stub
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_THREAD_INFO(%ebp)
# system call tracing in operation
testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry
cmpl $(nr_syscalls), %eax
jae syscall_badsys
syscall_call:
call *sys_call_table(,%eax,4)
movl %eax,EAX(%esp) # store the return value
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_THREAD_INFO(%ebp)
# system call tracing in operation
testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry
cmpl $(nr_syscalls), %eax
jae syscall_badsys
syscall_call:
call *sys_call_table(,%eax,4)
movl %eax,EAX(%esp) # store the return value
sys_call_table每一项占用4个字节。system_call函数可以读取eax寄存器获得当前系统调用的系统调用号,将其乘以4生成偏移地址,然后以sys_call_table为基址,基址加上偏移地址所指向的内容即是应该执行的系统调用服务例程的地址。