Chinaunix首页 | 论坛 | 博客
  • 博客访问: 815554
  • 博文数量: 172
  • 博客积分: 3836
  • 博客等级: 中校
  • 技术积分: 1988
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-10 14:59
文章分类

全部博文(172)

文章存档

2014年(2)

2013年(1)

2012年(28)

2011年(141)

分类: LINUX

2011-08-16 10:50:09

------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处: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中中断的处理流程,在后续的专题里,再对中断响应过程,系统调用过程做更详尽的分析
阅读(873) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~