------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://ericxiao.cublog.cn/
------------------------------------------
一:引言
在Intel的文档中,把中断分为两种.一种是异常,也叫同步同断.一种称之为中断,也叫异常中断.
同步中断指的是由CPU控制单元产生,之所以称之为同步,是因为只有一条指令执行完毕后才会发出中断.例如除法运算
中,除数为零的时候,就会产生一个异常
异步中断是由外部设备按照CPU的时钟随机产生的.例如,网卡检测到一个数据到来就会产生一个中断.
二:x86的中断处理过程
由于中断是开着的,所以当执行完一条指令后,cs和eip这对寄存器中已经包含了下一条将要执行的指令的逻辑地址。
在处理那条指令之前,控制单元会检查在运行前一条指令时是否发生了一个中断或异常。如果发生了一个中断和异常,那么控制单元执行下列操作:
1. 确定与中断或异常关联的向量i(0≤ i ≤255)
2. 读由idtr寄存器指向的IDT表中的第i项。
3.
从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的选择符标识的段描述符。这个描述符指定中断或异常处理程序所在的段的基地
址。
4.
确信中断是由授权的(中断)发生源发出的。首先将当前特权级CPL(存放在cs寄存器的低两位)与段描述符(存放在GDT中)的描述符特权级DPL比较。
如果CPL小于DPL,就产生一个“通常保护”异常,因为中断处理程序的特权级不能低于引起中断的程序的特权。对于编程异常,则做进一步的安全检查:比较
CPL与处于IDT中的门描述符的DPL,如果DPL小于CPL,就产生一个“通常保护”异常,这最后
一个检查可以避免用户应用程序访问特殊的陷阱门和中断门。
5.
检查是否发生了特权级的变化,也就是说,CPL是否不同于所选择的段描述符的DPL。如果是,控制单元必须开始使用与新的特权级相关的栈,通过执行以下步
骤来保证这一点:
A. 读tr寄存器,以访问运行进程的TSS段。
B. 用与新特权级相关的栈段和栈指针的正确值装载ss和esp寄存器。这些值可以在TSS中找到。
C. 在新的栈中保存ss和esp以前的值,这些值定义了与旧特权级相关的栈的逻辑地址。
6. 如果故障已发生,用引起异常的指令地址装载cs和eip寄存器,从而使得这条指令能再次被执行。
7. 在栈中保存eflag、cs和eip的内容。
8. 如果异常产生了一个硬件出错码,则将它保存在栈中。
9.
装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。这些值给出了中断或者异常处理程序的第一条指令的逻辑地址。
控制单元所执行的最后一步就是跳转到中断或异常处理程序。换句话说,处理完中断信号后,控制单元所执行的指令就是被
选中处理程序的第一条指令。
上面的处理过程的描述摘自<<深入理解linux内核>>,其中有几点值得注意的地方:
1:通过门后,只能提高运行级别.就像上面所述的
“当前特权级CPL(存放在cs寄存器的低两位)与段描述符(存放在GDT中)的描述符特权级DPL比较。如果CPL小于DPL,就产生一个“通常保护”
异常”.在中断处理中,通常把IDT中的相应段选择符设为__KERNEL_CS.即最高的运行级别
2:上面C所述:“在新的栈中保存ss和esp以前的值,这些值定义了与旧特权级相关的栈的逻辑地址”,那
ss,esp以前的值是如何找到的呢?应该是从TSS中.在中断发生的时候,如果检测到运行级别发生了改了,将寄存器SS,ESP中的值保存进TSS的相
应级别位置.再加载新的SS,ESP的值,然后从TSS中取出旧的SS,ESP值,再压栈.
3:堆栈的改变,如下图所示:
从上图中我们可以看到,硬件自动保存的
硬件环境是非常少,要在中断后恢复到以前的环境,还需要保存更多的寄存器值,这
是由操作系统完成的.这我们在以后的代码分析中可以看到
中断和异常被处理完毕后,相应的处理程序必须产生一条iret指令,把控制权转交给被中断的进程,这将迫使控制单
元:
1.
用保存在栈中的值装载cs、eip和eflag寄存器。如果一个硬件出错码曾被压入栈中,并且在eip内容的上面,那么,执行iret指令前必须先弹出这
个硬件出错码。
2. 检查处理程序的CPL是否等于cs中的低两位的值。如果是,iret终止返回;否则,转入下一步。
3. 从栈中转载ss和esp寄存器,因此,返回到与旧特权级相关的栈。
4.
检查ds、es、fs及gs段寄存器的内容,如果其中一个寄存器包含的选择符是一个段描述符,并且其DPL值小于CPL,那么,清相关的段寄存器。控制单
元这么做是为了禁止用户态的程序利用内核以前所用的段寄存器。如果不清除这些寄存器的话,恶意的用户程序就会利用他们来访问内核地址空间。
注意到4:举例说明一下.如果通过系统调用进入内核态.然后将DS,ES的值赋为__KERNEL_DS(在2.4
的内核里),处理完后(调用iret后),恢复CS,EIP的值,此时CS的CPL是3.因为DS,ES被设为了__KERNEL_DS,所以其DPL是
0,所以要将DS,ES中的值清除.在2.6内核中,发生中断或异常后,将DS,ES的值设为了__USER_DS,避免了上述的清除过程,提高了效率.
三:重要的数据结构
在深入源代码之前,先把所用到的数据结构分析如下:
Irq_desc[]定义如下:
extern irq_desc_t irq_desc [NR_IRQS]
typedef struct irq_desc {
unsigned int status; /* IRQ的状态;IRQ
是否被禁止了,有关IRQ 的设备当前是否正被自动检测*/
hw_irq_controller *handler;/*指向一个中断控制器的指针*/
c *action; /* 挂在IRQ上的中断处理程序 */
unsigned int depth; /*
为0:该IRQ被启用,如果为一个正数,表示被禁用 */
unsigned int irq_count; /*
该IRQ发生的中断的次数 */
unsigned int irqs_unhandled;
/*该IRQ线上没有被处理的IRQ总数*/
spinlock_t lock;
} ____cacheline_aligned irq_desc_t;
Hw_irq_controller定义如下:
struct hw_interrupt_type {
const char *
typename; /*中断控制器的名字*/
unsigned int (*startup)(unsigned int
irq); /*允许从IRQ线产生中断*/
void (*shutdown)(unsigned int irq);
/*禁止从IRQ线产生中断*/
void (*enable)(unsigned int irq);
/*enable与disable函数在8259A中与上述的startup shutdown函数相同*/
void (*disable)(unsigned int irq);
void (*ack)(unsigned int irq);
/*在IRQ线上产生一个应答*/
void (*end)(unsigned int irq);
/*在IRQ处理程序终止时被调用*/
void (*set_affinity)(unsigned int irq, cpumask_t
dest); /*在SMP系统中,设置IRQ处理的亲和力*/
}
typedef struct hw_interrupt_type hw_irq_controller;
struct irqaction定义如下:
struct irqaction {
//中断处理例程
irqreturn_t (*handler)(int, void *, struct
pt_regs *);
//flags:
//SA_INTERRUPT:中断嵌套
//SA_SAMPLE_RANDOM:这个中断源于物理随机性
//SA_SHIRQ:中断线共享
unsigned long flags;
//在x86平台无用
cpumask_t mask;
//产生中断的硬件名字
const char *name;
//设备ID,一般由厂商指定
void *dev_id;
//下一个irqaction.共享的时候,通常一根中断线对应很多硬件设备的中断处理例程
struct irqaction *next;
}
可以用下图来表示上述数据结构的关系:
四:idt在保护模式下的初始化
有关实模式下的初始化,以后再做专题分析.详情请关注本站更新.
在init/main.c中:
asmlinkage void __init start_kernel(void)
{
……
//设定系统规定的异常/中断
trap_init();
//设置外部IRQ中断
init_IRQ();
……
}
在start_kernel中,调用trap_init()来设置系统规定的异常与中断,调用init_IRQ()
来设置外部中断.
void __init trap_init(void)
{
……
set_trap_gate(0,÷_error);
set_intr_gate(1,&debug);
set_intr_gate(2,&nmi);
set_system_intr_gate(3, &int3); /* int3-5
can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_intr_gate(14,&page_fault);
set_trap_gate(15,&spurious_interrupt_bug);
set_trap_gate(16,&coprocessor_error);
set_trap_gate(17,&alignment_check);
#ifdef CONFIG_X86_MCE
set_trap_gate(18,&machine_check);
#endif
set_trap_gate(19,&simd_coprocessor_error);
set_system_gate(SYSCALL_VECTOR,&system_call); //系统调用
……
}
如上所示,设置了0~19的中断/异常处理程序,这些都是intel所规定的,除些之后设置了系统调用入口(用户空
间的 int SYSCALL_VECTOR )
那,
set_trap_gate()/set_intr_gate()/set_system_gata()都有一些什么样的区别呢?继续看代码:
void set_intr_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,14,0,addr,__KERNEL_CS);
}
static inline void set_system_intr_gate(unsigned int
n, void *addr)
{
_set_gate(idt_table+n, 14, 3, addr,
__KERNEL_CS);
}
void __init set_trap_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,15,0,addr,__KERNEL_CS);
}
void __init set_system_gate(unsigned int n, void
*addr)
{
_set_gate(idt_table+n,15,3,addr,__KERNEL_CS);
}
都是通过统一的接口_set_gate().在i386中,这段代码是用嵌入式汇编完成的,如下所示:
#define _set_gate(gate_addr,type,dpl,addr,seg) \
do { \
int __d0, __d1; \
__asm__ __volatile__ ("movw %%dx,%%ax\n\t" \
"movw %4,%%dx\n\t" \
"movl %%eax,%0\n\t" \
"movl %%edx,%1" \
:"=m" (*((long *) (gate_addr))), \
"=m" (*(1+(long *) (gate_addr))), "=&a"
(__d0), "=&d" (__d1) \
:"i" ((short)
(0x8000+(dpl<<13)+(type<<8))), \
"3" ((char *) (addr)),"2" ((seg) << 16));
\
} while (0)
我们看可以看: _set_gate(gate_addr,type,dpl,addr,seg)中:
Gate_addr:相应IDT项的地址.type:设置IDT项的TYPE字段,
15表示系统门,14表示中断门.dpl:IDT项对应的DPL值,addr:中断处理程序的地址,seg:IDT中对应项的段选择符
由此可以看出,陷阱门与中断门被锁定在内核态(DPL为0),系统门可以从用户态进入.那既然陷阱门与中断门又有什
么区别呢?唯一的区别是,通过陷阱门不会改变FLAGES中的中断标志,但是中断门就会改变,即会屏弊中断
接下来看IRQ中断的设置:
void __init init_IRQ(void)
{
int i;
//8259初始化
pre_intr_init_hook();
for (i = 0; i < (NR_VECTORS -
FIRST_EXTERNAL_VECTOR); i++) {
//FIRST_EXTERNAL_VECTOR:第一个可用号,前面部份均为系统保留
int vector = FIRST_EXTERNAL_VECTOR + i;
if (i >= NR_IRQS)
break;
//跳过系统调用号
if (vector != SYSCALL_VECTOR)
set_intr_gate(vector, interrupt[i]);
}
……
……
}
在深入这段代码之前,我们先看下x86的中断的硬件处理机制.x86中断处理系统一般采用两个8259A芯片级连的
方式.每个8259A有8根中断信号线,从片有一根信号线连接至了主片,所以,总共可以处理15个IRQ信号.如下图所示:
来看下具体的代码:
pre_intr_init_hook() -à init_ISA_irqs()
void __init init_ISA_irqs (void)
{
int i;
#ifdef CONFIG_X86_LOCAL_APIC
init_bsp_APIC();
#endif
//初始化8259A芯片
init_8259A(0);
//初始化irq_desc[]数组
for (i = 0; i < NR_IRQS; i++) {
//状态:禁用
irq_desc[i].status = IRQ_DISABLED;
//初始化为NULL.表示无中断处理函数,系统初始化完成之后,可以调用request_irq()注册中断处理函数
irq_desc[i].action = NULL;
//depth值为1,表示当前IRQ线被禁用
irq_desc[i].depth = 1;
// 只使用了15根(两块8259A级联)
if (i < 16) {
//将irq_desc[i].handler:设置为8259A的中断控制器处理
irq_desc[i].handler =
&i8259A_irq_type;
} else {
//其它的在x86平台被设为no_irq_type.表示无中断控制器
irq_desc[i].handler =
&no_irq_type;
}
}
}
在这个函数里,初始化了irq_desc[]数组.随后,调用了set_intr_gate(vector,
interrupt[i])为第n条中断线设置的中断处理函数为interrupt[n- FIRST_EXTERNAL_VECTOR].
Interrupt[]数组在哪里定义的呢?接下来往下看:
ENTRY(interrupt)
.previous
vector=0
ENTRY(irq_entries_start)
.rept NR_IRQS
ALIGN
1: pushl $vector-256
jmp common_interrupt
.data
.long 1b
.previous
vector=vector+1
.endr
相当于,interrupt[i]执行下列操作:
Pushl $i-256 //中断号取负再压栈
Jmp common_interrupt //跳转至一段公共的处理函数
至此,保护模式下的中断子系统初始化完成.接下来分析linux如何响应中断
五:中断入口分析
1:IRQ入口分析
如上所述.将中断号取负压栈之后,会跳转一段公共的处理函数,跟进这段函数:
common_interrupt:
SAVE_ALL
call do_IRQ #调用相应的中断处理函数
jmp ret_from_intr #从中断返回
SAVE_ALL定义如下:
#define SAVE_ALL \
__SAVE_ALL; \
__SWITCH_KERNELSPACE;
#在没有定义CONFIG_X86_HIGH_ENTRY的情况下,此宏是一个空宏
__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 $(__USER_DS), %edx; \
movl %edx, %ds; \
movl %edx, %es;
相当于把中断发生时,硬件没有保存的寄存器压栈保存下来.把DS.ES设为了__USER_DS是有一定原因的,参
考上节所述.
经过SAVE_ALL后.堆栈内容如下所示:
这个图是从<<中断处理源码情景分析>>一文中摘出来的.事实上图中的用户堆栈指针在
IRQ处理中是不可能存在的,因为中断处理对应IDT项的PL值为0,所以,不可能是从用户空间发生的
2:异常处理入口分析
异常处理程序也有很多相同的操作,以16号向量对应的中断处理程序为例:
set_trap_gate(16,&coprocessor_error);
ENTRY(coprocessor_error)
pushl $0
#把0入栈.如果异常没有产生一个硬件出错码,就把0入栈
pushl $do_coprocessor_error #相应的异常处理程序
jmp error_code #跳转到error_code
error_code定义如下:
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
movl %es, %ecx
movl ORIG_EAX(%esp), %esi # get the error code
movl ES(%esp), %edi # get the function
address
movl %eax, ORIG_EAX(%esp)
movl %ecx, ES(%esp)
pushl %esi # push the error code
movl $(__USER_DS), %edx
movl %edx, %ds
movl %edx, %es
/* clobbers edx, ebx and ebp */
__SWITCH_KERNELSPACE
leal 4(%esp), %edx # prepare pt_regs
pushl %edx # push pt_regs
call *%edi
addl $8, %esp
jmp ret_from_exception把
在上述代码在cld指令之前的堆栈内容与IRQ处理的堆栈相比,如下示:
其中大部份的数据都是一样的,经过后续处理,堆栈内容会变成如下所示:
经过调整之后变得跟IRQ处理的堆栈一样的了,栈顶指针指向存放EBX的位置,栈顶向上一个存储单位是
ERROR_CODE.这样做是因为异常处理程序一般都有两个参数,一个是struct
pt_regs.表示所有保存的寄存器的值,一个是unsigned long,表示出错码
这样,就为中断处理跟异常处理构造了一个统一的堆栈,当然这样做只是为了使用统一的参数类型,即struct
pt_regs
3:系统调用入口分析
系统调用的处理函数为: system_call,定义如下:
set_system_gate(SYSCALL_VECTOR,&system_call);
ENTRY(system_call)
pushl %eax # save
orig_eax(系统调用号).在发生系统调用的时候,系统调用号都是存放在EAX中的
SAVE_ALL #SAVE_ALL在IRQ处理入中已经分析过了
GET_THREAD_INFO(%ebp) #取得当前进程的task描述符
# system call tracing in
operation
testb
$(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry
#如果进程被TRACE,跳转至syscall_trace_entry,假设进程没有被trace
cmpl $(nr_syscalls), %eax
#在系统调用表中取得相应的处理函数
jae syscall_badsys #系统调用号无效
syscall_call:
call *sys_call_table(,%eax,4) #call相应的处理函数
movl %eax,EAX(%esp) # store the return
value
syscall_exit:
cli # make sure we don't miss
an interrupt
# setting need_resched or
sigpending
# between sampling and the
iret
movl TI_flags(%ebp), %ecx
testw $_TIF_ALLWORK_MASK, %cx #
current->work
jne syscall_exit_work
restore_all:
RESTORE_ALL
六:中断返回分析
1:IRQ中断返回分析
如上节所述,IRQ中断处理如下:
common_interrupt:
SAVE_ALL
call do_IRQ
jmp ret_from_intr
分析一下执行完相应的中断处理函数之后,如何返回,即恢复硬件环境原状
Ret_from_intr定义如下:
ret_from_intr:
GET_THREAD_INFO(%ebp) # 取得当前过程的task描述符
movl EFLAGS(%esp), %eax #
中断前的EFLAGS中的值存进EAX
movb CS(%esp), %al #
将中断前的CS低16移至AL
#至此EFLAGS
的H16和CS的L16构成了EAX的内容
testl $(VM_MASK | 3), %eax #
EFLAGS中有一位表示是否运行在vm86模式中
#CS的最低二位表示当前进程的运行级别
jz resume_kernel #
如果中断前不是在用户空间,且不是在VM86模式下,跳转到resume_kernel
ENTRY(resume_userspace) # 用户模式下
cli #开中断,以防中断丢失
movl TI_flags(%ebp), %ecx
#将task->flags成员的值存进ecx
andl $_TIF_WORK_MASK, %ecx # is there any work
to be done on 还有事情没做完?
# int/exception return?
jne work_pending #还有事情没有处理完
jmp restore_all #所有事情都处理完了
resume_kernel定义如下:
在没有定义CONFIG_PREEMPT的情况下:
#define resume_kernel restore_all
Restore_all被定义成:
restore_all:
RESTORE_ALL
RESTORE_ALL定义如下:
#define RESTORE_ALL \
__SWITCH_USERSPACE; \
#选择配置项.忽略
__RESTORE_ALL;
转至__RESTORE_ALL:
#define __RESTORE_ALL \
__RESTORE_REGS \
#pop在SAVE_ALL中入栈的寄存器
addl $4, %esp; \
#记否?在SAVE_ALL之前压入了一个中断向量的负值或者是系统调用号
333: iret; #iret中断返回,交给硬件完成中断的返回工作
__RESTORE_REGS被定义成:
#define __RESTORE_REGS \
__RESTORE_INT_REGS; \
#define __RESTORE_INT_REGS \
popl %ebx; \
popl %ecx; \
popl %edx; \
popl %esi; \
popl %edi; \
popl %ebp; \
popl %eax
如果编译内核被配置成允许被抢占的情况:
#ifdef CONFIG_PREEMPT
ENTRY(resume_kernel)
cmpl $0,TI_preempt_count(%ebp) # non-zero
preempt_count ?
jnz restore_all
#preempt_count为非0,表示此时不能发生抢占,则跳转到restore_all
need_resched:
movl TI_flags(%ebp), %ecx # need_resched set ?
testb $_TIF_NEED_RESCHED, %cl
#此时preempt_count=0,如果need_resched标志置位,则发生调度。反之则跳转到restore_all
jz restore_all
#如果是发生了异常,则不会进行抢占调度,此时preempt_count=0,且need_resched置位
testl $IF_MASK,EFLAGS(%esp) # interrupts off
(exception path) ?
jz restore_all
#将最大值赋值给preempt_count,表示不允许再次被抢占
movl $PREEMPT_ACTIVE,TI_preempt_count(%ebp)
sti
#调度
call schedule
#preempt_count还原为0
movl $0,TI_preempt_count(%ebp)
cli
#跳转到need_resched,判断是否又需要发生被调度
jmp need_resched
#endif
分析完内核态且不是VM86下的情况,我们接着分析用户态的情况
……
jz resume_kernel #
如果中断前不是在用户空间,且不是在VM86模式下,跳转到resume_kernel
ENTRY(resume_userspace) # 用户模式下
cli #开中断,以防中断丢失
movl TI_flags(%ebp), %ecx
#将task->flags成员的值存进ecx
andl $_TIF_WORK_MASK, %ecx # is there any work
to be done on 还有事情没做完?
# int/exception return?
jnev #还有事情没有处理完
jmp restore_all #所有事情都处理完了
restore_all的代码我们在上面已经分析完了.我们继续分析如果还有后续事情没有处理的情况:
work_pending的处理:
work_pending:
#在返回用户空间时,只需要判断need_resched是否置位,不需要判断preempt_count,如果
#置位则发生调度,反之则跳转到work_notifysig
testb $_TIF_NEED_RESCHED, %cl
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
#继续测试是否还有其他额外的事情要处理,如果没有,则跳转到restore_all
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # is there any work
to be done other
# than syscall tracing?
jz restore_all
#如果need_resched再次被置位,则继续调度,反之,则执行work_notifysig
testb $_TIF_NEED_RESCHED, %cl
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
#进行信号处理
call do_notify_resume
# CONFIG_X86_HIGH_ENTRY选择配置项,忽略
#if CONFIG_X86_HIGH_ENTRY
/*
* Reload db7 if necessary:
*/
movl TI_flags(%ebp), %ecx
testb $_TIF_DB7, %cl
jnz work_db7
jmp restore_all
work_db7:
movl TI_task(%ebp), %edx;
movl task_thread_db7(%edx), %edx;
movl %edx, %db7;
#endif
jmp restore_all
由于work_pending涉及到进程的调度与信号处理,详细的处理过程将在后续专题陆续给出.详情请关注本站更
新^_^
2:异常返回分析
转回去看下异常处理 error_code的代码:
Error_code:
……
……
call *%edi #调用相应的异常处理函数
addl $8, %esp
#esp上移两个存储单元,我们知道,在调整堆栈的时候,把错误码和第一个参数的指针压栈了
jmp ret_from_exception
ret_from_exception如下示:
ret_from_exception:
preempt_stop
ret_from_intr:
GET_THREAD_INFO(%ebp) # 取得当前过程的task描述符
movl EFLAGS(%esp), %eax #
中断前的EFLAGS中的值存进EAX
……
从此我们看到,异常返回的后半部份与IRQ中断返回相比只是多了一个preempt_stop的处理.
Preempt_stop的代码如下:
#ifdef CONFIG_PREEMPT
#define preempt_stop cli
#else
#define preempt_stop
3:系统调用返回分析:
回忆系统调用的处理代码,如下示:
ENTRY(system_call)
……
……
syscall_call:
call *sys_call_table(,%eax,4)
movl %eax,EAX(%esp) # 将返回值压入调用前的EAX中
syscall_exit:
cli
movl TI_flags(%ebp), %ecx
testw $_TIF_ALLWORK_MASK, %cx #
current->work
jne syscall_exit_work
restore_all:
RESTORE_ALL
Syscall_exit_work代码如下:
syscall_exit_work:
testb
$(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cl
jz work_pending
sti # could let
do_syscall_trace() call
# schedule() instead
movl %esp, %eax
movl $1, %edx
call do_syscall_trace
jmp resume_userspac
上述很多标号都在前面分析过,这里就不再赘述了.
上面三种情况的返回情况,总结成下图:
七:小结
在这个专题里,主要分析了x86硬件中断处理子系统,异常/中断/系统调用下的入口与返回情况分析,了解了
linux中中断的处理流程,在后续的专题里,再对中断响应过程,系统调用过程做更详尽的分析