在中断、异常及系统调用的处理中,涉及一些相关的常量、数据结构及宏,在此先给予介绍(大部分代码在arch/i386/kernel/entry.S中)。
1. 常量定义
下面这些常量定义了进入中断处理程序时,相关寄存器与堆栈指针(ESP)的相对位置,图3.6给出了在相应位置上所保存的寄存器内容:
EBX
= 0x00
ECX=
0x04
EDX=
0x08
ESI=
0x
EDI= 0x10
EB = 0x14
EAX= 0x18
DS= 0x
ES = 0x20
ORIG_EAX = 0x24
EIP = 0x28
CS = 0x
EFLAGS = 0x30
OLDESP= 0x34
OLDSS = 0x38
+0x38 用户栈的SS
+0x34 用户栈的ESP 用户堆栈指针
+0x30 EFLAGS
+0x
+0x28 EIP
返回到用户态的地址
+0x24 ORIG_EAX
+0x20 ES
+0x
+0x18 EAX
+0x14 EBP
+0x10 EDI
+0XC ESI
+8 EDX
内核堆栈指针ESP +4 ECX
0 EBX
图3.6 进入中断理程序时内核堆栈示意图
其中,ORIG_EAX是Original eax之意,其具体含义将在后面介绍。
2.存放在栈中的寄存器结构pt_regs
在内核中,很多函数的参数是pt_regs数据结构,定义在include/i386/ptrace.h中:
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
把这个结构与内核栈的内容相比较,会发现堆栈的内容是这个数据结构的一个映象。
3.保存现场的宏SAVE_ALL
在中断发生前夕,要把所有相关寄存器的内容都保存在堆栈中,这是通过SAVE_ALL宏完成的:
#define SAVE_ALL \
cld; \
pushl %es; \
pushl %ds; \
pushl %eax; \
pushl %ebp; \
pushl %edi; \
pushl %esi; \
pushl %edx; \
pushl %ecx; \
pushl %ebx; \
movl $(__KERNEL_DS),%edx; \
movl %edx,%ds; \
movl %edx,%es;
该宏执行以后,堆栈内容如图3.6所示。把这个宏与图3.5 结合起来就很容易理解图3.6,在此对该宏再给予解释:
· CPU在进入中断处理程序时自动将用户栈指针(如果更换堆栈)、EFLAGS寄存器及返回地址一同压入堆栈。
· 段寄存器DS和ES原来的内容入栈,然后装入内核数据段描述符__KERNEL_DS(定义为0x18),内核段的DPL为0。
4.恢复现场的宏RESTORE_ALL
当从中断返回时,恢复相关寄存器的内容,这是通过RESTORE_ALL宏完成的:
#define RESTORE_ALL \
popl %ebx; \
popl %ecx; \
popl %edx; \
popl %esi;
\
popl %edi;
\
popl %ebp; \
popl %eax; \
1: popl %ds; \
2: popl %es; \
addl
$4,%esp; \
3: iret;
可以看出,RESTORE_ALL与SAVE_ALL遥相呼应。当执行到iret指令时,内核栈又恢复到刚进入中断门时的状态,并使CPU从中断返回。
5.将当前进程的task_struct 结构的地址放在寄存器中
#define GET_CURRENT(reg)
\
movl $-8192, reg; \
andl %esp, reg
从下一章“task_struct 结构在内存存放”一节我们将知道,当前进程的task_struct存放在内核栈的底部,因此,以上两条指令就可以把task_struct结构的地址放在reg寄存器中。