首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 操作系统 > UNIXLINUX >

linux内核中断、错误、系统调用的分析以及实践

2012-07-27 
linux内核中断、异常、系统调用的分析以及实践linux内核中断、异常、系统调用的分析以及实践 2010年12月03日  

linux内核中断、异常、系统调用的分析以及实践

linux内核中断、异常、系统调用的分析以及实践
2010年12月03日
  报告内容
  中断是由间隔定时器和和I/O设备产生的。
  异常则是由程序的错误产生,或者由内核必须处理的异常条件产生。第一种情况下,内核通过发送一个信号来处理异常;第二种情况下,内核执行恢复异常需要的所有步骤,或对内核服务的一个请求。
  中断和异常改变处理器执行的指令顺序,通常与CPU芯片内部或外部硬件电路产生的电信号相对应。它们提供了一种特殊的方式,使处理器转而去执行正常控制流之外的代码。
  中断是异步的,由硬件随机产生,在程序执行的任何时候可能出现。异常是同步的,在(特殊的或出错的)指令执行时由CPU控制单元产生。
  每个中断和异常由0~255之间的一个数(8位)来标识,Intel称其为中断向量(vector)。非屏蔽中断的向量和异常的向量是固定的,可屏蔽中断的向量可以通过对中断控制器的编程来改变。
  Linux对中断描述符进行了如下分类:
  1.中断门
  用户态的进程不能访问的一个中断门(特权级为0),所有的中断都通过中断门激活,并全部在内核态。由set_intr_gate()函数负责在IDT表项中插入一个中断门。
  2.系统门
  用户态的进程可以访问的一个陷阱门(特权级为3),通过系统门来激活4个linux异常处理程序,它们的向量是3,4,5和128。因此,在用户态下可以发布int3,into,bound和int $0x80四条汇编指令。由set_system_gate ()函数负责在IDT表项中插入一个系统门。
  3.陷阱门
  用户态的进程不能访问的一个陷阱门(特权级为0),大部分linux异常处理程序通过陷阱门激活。由set_trap_gate ()函数负责在IDT表项中插入一个陷阱门。
  三个门均调用了_set_gate宏,代码如下:
  #define _set_gate(gate_addr,type,dpl,addr) \
  do { \
  int __d0, __d1; \
  __asm__ __volatile__ ("movw %%dx,%%ax\n\t" \
  "movw %4,%%dx\n\t" \          //将转化后的dpl值存进dx寄存器
  "movl %%eax,%0\n\t" \          //将eax寄存器的值存进gate_addr,即idt_table+n处
  "movl %%edx,%1" \
  :"=m" (*((long *) (gate_addr))), \
  "=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \
  :"i" ((short) (0x8000+(dpl中的每一项,这个动作是在初始化系统时,由arch/i386/kernel/head.S中的Startup_32()函数完成。
  setup_idt:
  lea ignore_int,%edx
  movl $(__KERNEL_CS 中的start_kernel()函数中调用trap_init()和init_IRQ(),来分别的用有意义的陷阱和中断处理程序替换这个空的处理程序。
  void __init init_IRQ(void)
  {
  ……
  /*
  * NR_IRQS=224
  * FIRST_EXTERNAL_VECTOR定义为0x20,十进制32
  * SYSCALL_VECTOR定义为0x80,中断号为128的中断向量
  */
  for (i = 0; i lock);       //自旋锁,在单处理系统中没有作用
  /*
  *应答PIC的中断,并禁用这条IRQ线。(为串行处理同类型中断)
  */
  desc->handler->ack(irq);      //应答
  status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);  //设置IRQ线状态
  status |= IRQ_PENDING; /* we _want_ to handle it */
  action = NULL;           //在真正开始工作之前,检查相关标志位
  if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) {
  action = desc->action;
  status &= ~IRQ_PENDING; /* we commit to handling */
  status |= IRQ_INPROGRESS; /* we are handling it */
  }
  desc->status = status;
  if (!action)          //action为null则跳出
  goto out;
  for (;;) {
  spin_unlock(&desc->lock);             //释放中断自旋锁
  handle_IRQ_event(irq, &regs, action);    //在循环中执行中断服务例程
  spin_lock(&desc->lock);               //执行完一次则上锁
  if (!(desc->status & IRQ_PENDING))
  break;
  //若PENDING标志位清0,则循环结束,中断不进一步传递给另一个CPU
  desc->status &= ~IRQ_PENDING;
  }
  desc->status &= ~IRQ_INPROGRESS;      //清除IRQ_INPROGERSS标志位
  out:
  desc->handler->end(irq); 
  /*
  *调用主IRQ描述符的end方法,单处理系统上相应的
  *end_8259A_irq()函数重新激活IRQ线,允许处理同类型中断
  */
  spin_unlock(&desc->lock);               //为do_IRQ释放自旋锁
  if (softirq_pending(cpu))              //检查下半部分是否执行
  do_softirq();
  return 1;
  } 
  handle_IRQ_evnet()函数依次调用这些设备例程:
  int handle_IRQ_event(unsigned int irq, struct pt_regs * regs, struct irqaction * action)
  {
  ……
  irq_enter(cpu, irq);
  /*调用irq_enter方法增加正执行CPU的irq_stat元素的__local_irq_count字段*/
  ……
  if (!(action->flags & SA_INTERRUPT))
  __sti();     //如果SA_INTERRUPT标志被清0,用sti指令打开本地中断
  do {
  status |= action->flags;
  action->handler(irq, action->dev_id, regs);
  action = action->next;
  } while (action);    //循环执行每个中断的中断服务例程
  if (status & SA_SAMPLE_RANDOM)
  add_interrupt_randomness(irq);
  __cli();          //用cli指令打开本地中断
  irq_exit(cpu, irq);
  /*调用irq_exit方法减少正执行CPU的irq_stat元素的__local_irq_count字段*/
  return status;
  } 
  异常:
  异常可以是由程序错误产生,或者由内核必须处理的异常条件产生的。linux下发生异常,内核会自动产生一个异常中断。在这异常中断处理程序中会判断异常来自用户程序或者内核,如果是发生在用户程序,内核通过发送一个信号来处理异常,,再根据异常信号的回调函数通知用户程序发生异常。如果发生在内核里面,那么内核执行恢复异常需要的所有步骤(例如缺页),或对内核服务的一个请求,即会搜索内核模块的异常结构表,找到相应的处理调用地址,修改异常中断的返回地址为异常处理的地址,中断返回的时候程序就跳到异常处理程序处理执行了。
  异常处理有一个标准的结构,由三部分组成:
  1.在内核态堆栈中保存大多数寄存器的内容
  2.调用C语言的函数
  3.通过ret_from_exception()从异常处理程序退出
  为了利用异常,必须对IDT进行初始化,使得每个被确认的异常都有一个异常处理程序。Trap_init()函数是将一些最终值插入到IDT表中的非屏蔽中断及异常表项中。这是由set_trap_gate和set_system_gate宏实现该IDT表项的初始化。
  void __init trap_init(void)
  {
  ……
  set_trap_gate(0,&divide_error);      // set_trap_gate()函数设置陷阱门
  set_trap_gate(1,&debug);
  set_intr_gate(2,&nmi);
  set_system_gate(3,&int3);           // set_system_gate()函数设置系统门
  set_system_gate(4,&overflow);
  set_system_gate(5,&bounds);
  set_trap_gate(6,&invalid_op);
  set_trap_gate(7,&device_not_available);
  set_trap_gate(8,&double_fault);
  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);
  set_trap_gate(18,&machine_check);
  set_trap_gate(19,&simd_coprocessor_error);
  set_system_gate(SYSCALL_VECTOR,&system_call);
  // SYSCALL_VECTOR=0x80,即十进制128
  ……
  } 
  在/arch/i386/kernel/entry.S中我们可以看到每个为异常处理程序定义入口,如page_fault的入口如下:
  ENTRY(page_fault)
  pushl $ SYMBOL_NAME(do_page_fault)    //执行的实体

热点排行