常见问题常见问题   搜索搜索   会员列表会员列表   团队团队   注册注册    个人资料个人资料   登录查看您的站内信件登录查看您的站内信件   登录登录 

FreeBSD 5 内核源代码分析之中断处理

 
发表新文章   这个论题已经被锁定,您不能发表、回复或者编辑文章。    FreeBSD China -> 中文-桌面-开发-调试
阅读上一个主题 :: 阅读下一个主题  
作者 留言
wheelz
半仙


注册时间: 2002-11-18
文章: 120

文章发表于: Wed 2004-05-12 13:09:59    发表主题: FreeBSD 5 内核源代码分析之中断处理 引用并回复

FreeBSD 5 内核源代码分析之中断处理
by wheelz
---------------------------------

FreeBSD 5 内核中断处理的最大特点是将中断处理程序在线程的上下文中运行。
为此,内核为每个注册的中断源(即vector)准备一个内核线程,即中断线程,
其任务就是等待中断的发生,一旦发生,便运行相应的中断处理程序。

FreeBSD 5这样做,有好处也有坏处。好处是可以简化线程和中断的互斥关系,
并使得中断处理可以阻塞。
坏处是每次响应中断都要进行线程调度,可能有两次线程上下文的切换
(从用户线程切到中断线程再切回来)。未来的想法是进行lazy scheduling,
即尽可能借用当前线程的上下文,只有在中断要阻塞时才进行真正的调度。

与中断有关的源代码主要在

sys/kern/kern_intr.c (与体系结构无关的中断代码)
sys/i386/i386/intr_machdep.c (与i386体系结构相关的中断代码)
sys/i386/isa/atpic.c (与8259A相关的.c代码)
sys/i386/isa/atpic_vector.s (与8259A相关的.s代码)

Contents

1,登记IRQ中断源
1.1 数据结构与函数
1.2 8259A的登记过程
2,IRQ中断的处理过程
3, 软件中断swi
3.1 软件中断的登记
3.2 软件中断的调度

-------------------------------
1,登记IRQ中断源

1.1 数据结构与函数

中断向量表有多个vector,0-31为CPU用,32~32+15对应IRQ0~IRQ15
一个vector对应一个source,数据类型是struct intsrc
代码:

/*
 * An interrupt source.  The upper-layer code uses the PIC methods to
 * control a given source.  The lower-layer PIC drivers can store additional
 * private data in a given interrupt source such as an interrupt pin number
 * or an I/O APIC pointer.
 */
struct intsrc {
   struct pic *is_pic;
   struct ithd *is_ithread;
   u_long *is_count;
   u_long *is_straycount;
   u_int is_index;
};


其实在vector后面的是中断控制器,如8259A,I/O APIC等,
事实上,对中断源的控制实际上就是对中断控制器的操作,
因此,在struct intsrc中有成员struct pic *is_pic,
即中断控制器的操作函数表,通过这个表,可以为不同的中断控制器
定义不同的操作,达到demultiplex的作用。这里pic是
programmable interrupt controller的意思。
代码:

/*
 * Methods that a PIC provides to mask/unmask a given interrupt source,
 * "turn on" the interrupt on the CPU side by setting up an IDT entry, and
 * return the vector associated with this source.
 */
struct pic {
   void (*pic_enable_source)(struct intsrc *);
   void (*pic_disable_source)(struct intsrc *);
   void (*pic_eoi_source)(struct intsrc *);
   void (*pic_enable_intr)(struct intsrc *);
   int (*pic_vector)(struct intsrc *);
   int (*pic_source_pending)(struct intsrc *);
   void (*pic_suspend)(struct intsrc *);
   void (*pic_resume)(struct intsrc *);
};

系统中所有的中断源组成一个数组,由于当采用I/O APIC作为中断控制器时,
可以有191个中断号(IRQ),因此该数组大小定义为191。
代码:

static struct intsrc *interrupt_sources[NUM_IO_INTS];

/* With I/O APIC's we can have up to 191 interrupts. */
#define   NUM_IO_INTS   191

所谓登记中断源,就是将实际的中断控制器的对应struct intsrc数据结构
添加到该数组中去。同时,系统为每个登记的中断源创建一个中断线程,
中断处理程序就在该线程的上下文中运行,该线程的入口函数为ithread_loop(),

struct intsrc结构成员is_ithread指向描述中断线程的数据结构struct ithd,
而struct ithd结构成员it_td指向真正的线程结构struct thread,从而将中断
与系统的调度单元线程联系起来。

代码:

/*
 * Describe an interrupt thread.  There is one of these per interrupt vector.
 * Note that this actually describes an interrupt source.  There may or may
 * not be an actual kernel thread attached to a given source.
 */
struct ithd {
   struct   mtx it_lock;
   struct   thread *it_td;      /* Interrupt process. */
   LIST_ENTRY(ithd) it_list;   /* All interrupt threads. */
   TAILQ_HEAD(, intrhand) it_handlers; /* Interrupt handlers. */
   struct   ithd *it_interrupted;   /* Who we interrupted. */
   void   (*it_disable)(uintptr_t); /* Enable interrupt source. */
   void   (*it_enable)(uintptr_t); /* Disable interrupt source. */
   void   *it_md;         /* Hook for MD interrupt code. */
   int   it_flags;      /* Interrupt-specific flags. */
   int   it_need;      /* Needs service. */
   uintptr_t it_vector;
   char   it_name[MAXCOMLEN + 1];
};

/*
 * Register a new interrupt source with the global interrupt system.
 * The global interrupts need to be disabled when this function is
 * called.
 */
int
intr_register_source(struct intsrc *isrc)
{
   int error, vector;

   vector = isrc->is_pic->pic_vector(isrc);
   if (interrupt_sources[vector] != NULL)
      return (EEXIST);
   error = ithread_create(&isrc->is_ithread, (uintptr_t)isrc, 0,
       (mask_fn)isrc->is_pic->pic_disable_source,
       (mask_fn)isrc->is_pic->pic_enable_source, "irq%d:", vector);
   if (error)
      return (error);
   mtx_lock_spin(&intr_table_lock);
   if (interrupt_sources[vector] != NULL) {
      mtx_unlock_spin(&intr_table_lock);
      ithread_destroy(isrc->is_ithread);
      return (EEXIST);
   }
   intrcnt_register(isrc);
   interrupt_sources[vector] = isrc;
   mtx_unlock_spin(&intr_table_lock);
   return (0);
}

int
ithread_create(struct ithd **ithread, uintptr_t vector, int flags,
    void (*disable)(uintptr_t), void (*enable)(uintptr_t), const char *fmt, ...)
{
   struct ithd *ithd;
   struct thread *td;
   struct proc *p;
   int error;
   va_list ap;

   /* The only valid flag during creation is IT_SOFT. */
   if ((flags & ~IT_SOFT) != 0)
      return (EINVAL);

   ithd = malloc(sizeof(struct ithd), M_ITHREAD, M_WAITOK | M_ZERO);
   ithd->it_vector = vector;
   ithd->it_disable = disable;
   ithd->it_enable = enable;
   ithd->it_flags = flags;
   TAILQ_INIT(&ithd->it_handlers);
   mtx_init(&ithd->it_lock, "ithread", NULL, MTX_DEF);

   va_start(ap, fmt);
   vsnprintf(ithd->it_name, sizeof(ithd->it_name), fmt, ap);
   va_end(ap);

   error = kthread_create(ithread_loop, ithd, &p, RFSTOPPED | RFHIGHPID,
       0, "%s", ithd->it_name);
   if (error) {
      mtx_destroy(&ithd->it_lock);
      free(ithd, M_ITHREAD);
      return (error);
   }
   td = FIRST_THREAD_IN_PROC(p);   /* XXXKSE */
   mtx_lock_spin(&sched_lock);
   td->td_ksegrp->kg_pri_class = PRI_ITHD;
   td->td_priority = PRI_MAX_ITHD;
   TD_SET_IWAIT(td);
   mtx_unlock_spin(&sched_lock);
   ithd->it_td = td;
   td->td_ithd = ithd;
   if (ithread != NULL)
      *ithread = ithd;
   CTR2(KTR_INTR, "%s: created %s", __func__, ithd->it_name);
   return (0);
}

中断源登记完成后,便可以登记中断处理程序了。struct ithd有一个成员it_handlers,
指向一个链表,这个链表是中断处理程序的链表。为什么多个中断处理程序会连接在一个链表
中呢?这是因为多个设备可以共享同一个IRQ号,即同一个vertor可以登记多个设备的
中断处理函数。当中断来临时,系统分别调用各个设备的中断处理函数,由他们自己判断是否
是自己的中断。

intr_add_handler()函数就是用来登记中断处理程序的,它从系统中分配一个描述中断处理程序
的数据结构struct intrhand,并将传入的参数,即中断处理函数driver_intr_t handler
保存在结构struct intrhand的成员时ih_handler中。中断发生时真正处理中断事务的就是该函数。
代码:

/*
 * Describe a hardware interrupt handler.
 *
 * Multiple interrupt handlers for a specific vector can be chained
 * together.
 */
struct intrhand {
   driver_intr_t   *ih_handler;   /* Handler function. */
   void      *ih_argument;   /* Argument to pass to handler. */
   int       ih_flags;
   const char   *ih_name;   /* Name of handler. */
   struct ithd   *ih_ithread;   /* Ithread we are connected to. */
   int       ih_need;   /* Needs service. */
   TAILQ_ENTRY(intrhand) ih_next;   /* Next handler for this vector. */
   u_char       ih_pri;   /* Priority of this handler. */
};

/* Interrupt handle flags kept in ih_flags */
#define   IH_FAST      0x00000001   /* Fast interrupt. */
#define   IH_EXCLUSIVE   0x00000002   /* Exclusive interrupt. */
#define   IH_ENTROPY   0x00000004   /* Device is a good entropy source. */
#define   IH_DEAD      0x00000008   /* Handler should be removed. */
#define   IH_MPSAFE   0x80000000   /* Handler does not need Giant. */

这里有几个flag值值得一提。
IH_FAST指示该中断是快速中断,系统将尽快执行该处理函数,
并不将它调度到中断线程的上下文中运行,也就是说这种函数的运行是在中断环境下运行,
没有线程的上下文,是为历史遗留的还未迁移到新中断模式下的驱动程序提供的。
IH_EXCLUSIVE指示该中断是独占IRQ的,即不能和其他设备共享IRQ
IH_MPSAFE表明该中断处理函数是SMP安全的。
代码:

int
intr_add_handler(const char *name, int vector, driver_intr_t handler,
    void *arg, enum intr_type flags, void **cookiep)
{
   struct intsrc *isrc;
   int error;

   isrc = intr_lookup_source(vector);
   if (isrc == NULL)
      return (EINVAL);
   error = ithread_add_handler(isrc->is_ithread, name, handler, arg,
       ithread_priority(flags), flags, cookiep);
   if (error == 0) {
      intrcnt_updatename(isrc);
      isrc->is_pic->pic_enable_intr(isrc);
      isrc->is_pic->pic_enable_source(isrc);
   }
   return (error);
}

int
ithread_add_handler(struct ithd* ithread, const char *name,
    driver_intr_t handler, void *arg, u_char pri, enum intr_type flags,
    void **cookiep)
{
   struct intrhand *ih, *temp_ih;

   if (ithread == NULL || name == NULL || handler == NULL)
      return (EINVAL);

   ih = malloc(sizeof(struct intrhand), M_ITHREAD, M_WAITOK | M_ZERO);
   ih->ih_handler = handler;
   ih->ih_argument = arg;
   ih->ih_name = name;
   ih->ih_ithread = ithread;
   ih->ih_pri = pri;
   if (flags & INTR_FAST)
      ih->ih_flags = IH_FAST;
   else if (flags & INTR_EXCL)
      ih->ih_flags = IH_EXCLUSIVE;
   if (flags & INTR_MPSAFE)
      ih->ih_flags |= IH_MPSAFE;
   if (flags & INTR_ENTROPY)
      ih->ih_flags |= IH_ENTROPY;

   mtx_lock(&ithread->it_lock);
   if ((flags & INTR_EXCL) != 0 && !TAILQ_EMPTY(&ithread->it_handlers))
      goto fail;
   if (!TAILQ_EMPTY(&ithread->it_handlers)) {
      temp_ih = TAILQ_FIRST(&ithread->it_handlers);
      if (temp_ih->ih_flags & IH_EXCLUSIVE)
         goto fail;
      if ((ih->ih_flags & IH_FAST) && !(temp_ih->ih_flags & IH_FAST))
         goto fail;
      if (!(ih->ih_flags & IH_FAST) && (temp_ih->ih_flags & IH_FAST))
         goto fail;
   }

   TAILQ_FOREACH(temp_ih, &ithread->it_handlers, ih_next)
       if (temp_ih->ih_pri > ih->ih_pri)
          break;
   if (temp_ih == NULL)
      TAILQ_INSERT_TAIL(&ithread->it_handlers, ih, ih_next);
   else
      TAILQ_INSERT_BEFORE(temp_ih, ih, ih_next);
   ithread_update(ithread);
   mtx_unlock(&ithread->it_lock);

   if (cookiep != NULL)
      *cookiep = ih;
   CTR3(KTR_INTR, "%s: added %s to %s", __func__, ih->ih_name,
       ithread->it_name);
   return (0);

fail:
   mtx_unlock(&ithread->it_lock);
   free(ih, M_ITHREAD);
   return (EINVAL);
}

1.2 8259A的登记过程

下面我们以8259A为例,看看系统是如何为其注册中断源的,即注册INTSRC(0)~INTSRC(15)。
描述8259A中断控制器的数据结构是struct atpic_intsrc,其第一个成员是一个中断源结构,
这种类型定义方法是BSD中常用的方法,起到了面向对象编程中继承的作用。

由于两个级连的8259A中断控制器可以控制16个中断,因此系统注册16个struct atpic_intsrc。
这些中断响应程序的的入口地址是IDTVEC(atpic_intr ## irq )。IDTVEC在.c文件中将扩展成
Xatpic_intr0 至Xatpic_intr15,即为函数名的引用。而在.s文件中将扩展成
代码:

   ALIGN_TEXT;
   .globl Xatpic_intr0;
   .type Xatpic_intr0,@function;
Xatpic_intr0:

等等,即定义一个全局的函数,也就是说在.c文件中只是引用该函数,真正定义该函数的是
在sys/i386/isa/atpic_vector.s中,该函数实际上就是一个对atpic_handle_intr()
函数的包装,我们后面还将看到该函数。
代码:

struct atpic_intsrc {
   struct intsrc at_intsrc;
   int   at_irq;      /* Relative to PIC base. */
   inthand_t *at_intr;
   u_long   at_count;
   u_long   at_straycount;
};

static struct atpic_intsrc atintrs[] = {
   INTSRC(0),
   INTSRC(1),
   INTSRC(2),
   INTSRC(3),
   INTSRC(4),
   INTSRC(5),
   INTSRC(6),
   INTSRC(7),
   INTSRC(8),
   INTSRC(9),
   INTSRC(10),
   INTSRC(11),
   INTSRC(12),
   INTSRC(13),
   INTSRC(14),
   INTSRC(15),
};

#define   INTSRC(irq)                     \
   { { &atpics[(irq) / 8].at_pic }, (irq) % 8,         \
       IDTVEC(atpic_intr ## irq ) }

系统启动时,调用8259A的初始化函数atpic_init(),为非SLAVE IRQ号注册中断源。
并在i386初始化时调用atpic_startup()函数,注册中断向量
IDTVEC(atpic_intr ## irq ),注意,这只是注册总的包装函数,
具体IRQ号的中断处理函数将由设备驱动通过intr_add_handler()函数来注册。
代码:

SYSINIT(atpic_init, SI_SUB_INTR, SI_ORDER_SECOND + 1, atpic_init, NULL)

static void
atpic_init(void *dummy __unused)
{
   int i;

   /* Loop through all interrupt sources and add them. */
   for (i = 0; i < sizeof(atintrs) / sizeof(struct atpic_intsrc); i++) {
      if (i == ICU_SLAVEID)
         continue;
      intr_register_source(&atintrs[i].at_intsrc);
   }
}

void
init386(first)
   int first;
{
   ......

#ifdef DEV_ISA
   atpic_startup();
#endif

   ......
}

void
atpic_startup(void)
{
   struct atpic_intsrc *ai;
   int i;

   /* Start off with all interrupts disabled. */
   imen = 0xffff;
   i8259_init(&atpics[MASTER], 0);
   i8259_init(&atpics[SLAVE], 1);
   atpic_enable_source((struct intsrc *)&atintrs[ICU_SLAVEID]);

   /* Install low-level interrupt handlers for all of our IRQs. */
   for (i = 0; i < sizeof(atintrs) / sizeof(struct atpic_intsrc); i++) {
      if (i == ICU_SLAVEID)
         continue;
      ai = &atintrs[i];
      ai->at_intsrc.is_count = &ai->at_count;
      ai->at_intsrc.is_straycount = &ai->at_straycount;
      setidt(((struct atpic *)ai->at_intsrc.is_pic)->at_intbase +
          ai->at_irq, ai->at_intr, SDT_SYS386IGT, SEL_KPL,
          GSEL(GCODE_SEL, SEL_KPL));
   }
}


2,IRQ中断的处理过程
代码:

/*
 * Macros for interrupt interrupt entry, call to handler, and exit.
 */
#define   INTR(irq_num, vec_name) \
   .text ;                        \
   SUPERALIGN_TEXT ;                  \
IDTVEC(vec_name) ;                     \
   pushl   $0 ;      /* dummy error code */         \
   pushl   $0 ;      /* dummy trap type */         \
   pushal ;      /* 8 ints */            \
   pushl   %ds ;      /* save data and extra segments ... */   \
   pushl   %es ;                     \
   pushl   %fs ;                     \
   mov   $KDSEL,%ax ;   /* load kernel ds, es and fs */      \
   mov   %ax,%ds ;                  \
   mov   %ax,%es ;                  \
   mov   $KPSEL,%ax ;                  \
   mov   %ax,%fs ;                  \
;                           \
   FAKE_MCOUNT(13*4(%esp)) ;   /* XXX late to avoid double count */ \
   pushl   $irq_num;    /* pass the IRQ */         \
   call   atpic_handle_intr ;               \
   addl   $4, %esp ;   /* discard the parameter */      \
;                           \
   MEXITCOUNT ;                     \
   jmp   doreti

IRQ产生时,系统根据产生中断的IRQ号找到相应的中断向量入口,即此处的IDT_VEC(vec_name),
再这里,构造好函数atpic_handle_intr()的调用栈后,将转到atpic_handle_intr()进行处理。
同系统调用一样,这里的调用栈struct intrframe既是atpic_handle_intr()的参数,也是中断
返回时用以恢复现场的寄存器状态。
代码:

/* Interrupt stack frame */
struct intrframe {
   int   if_vec;
   int   if_fs;
   int   if_es;
   int   if_ds;
   int   if_edi;
   int   if_esi;
   int   if_ebp;
   int   :32;
   int   if_ebx;
   int   if_edx;
   int   if_ecx;
   int   if_eax;
   int   :32;      /* for compat with trap frame - trapno */
   int   :32;      /* for compat with trap frame - err */
   /* below portion defined in 386 hardware */
   int   if_eip;
   int   if_cs;
   int   if_eflags;
   /* below only when crossing rings (e.g. user to kernel) */
   int   if_esp;
   int   if_ss;
};

void
atpic_handle_intr(struct intrframe iframe)
{
   struct intsrc *isrc;

   KASSERT((uint)iframe.if_vec < ICU_LEN,
       ("unknown int %d\n", iframe.if_vec));
   isrc = &atintrs[iframe.if_vec].at_intsrc;

   /*
    * If we don't have an ithread, see if this is a spurious
    * interrupt.
    */
   if (isrc->is_ithread == NULL &&
       (iframe.if_vec == 7 || iframe.if_vec == 15)) {
      int port, isr;

      /*
       * Read the ISR register to see if IRQ 7/15 is really
       * pending.  Reset read register back to IRR when done.
       */
      port = ((struct atpic *)isrc->is_pic)->at_ioaddr;
      mtx_lock_spin(&icu_lock);
      outb(port, OCW3_SEL | OCW3_RR | OCW3_RIS);
      isr = inb(port);
      outb(port, OCW3_SEL | OCW3_RR);
      mtx_unlock_spin(&icu_lock);
      if ((isr & IRQ7) == 0)
         return;
   }
   intr_execute_handlers(isrc, &iframe);
}

经过简单的有关8259A特有的检查,atpic_handle_intr()就转到intr_execute_handlers()
继续处理。

intr_execute_handlers()是一个重要的函数,它先得到IRQ号,然后判断是否是快速中断,
如果是,则直接在当前线程的上下文中运行,如果不是,则调度对应的中断线程来运行。
这个处理是被critical_enter()/critical_exit()保护起来的,以保证不会嵌套调度中断线程。
代码:

void
intr_execute_handlers(struct intsrc *isrc, struct intrframe *iframe)
{
   struct thread *td;
   struct ithd *it;
   struct intrhand *ih;
   int error, vector;

   td = curthread;
   td->td_intr_nesting_level++;

   /*
    * We count software interrupts when we process them.  The
    * code here follows previous practice, but there's an
    * argument for counting hardware interrupts when they're
    * processed too.
    */
   atomic_add_long(isrc->is_count, 1);
   atomic_add_int(&cnt.v_intr, 1);

   it = isrc->is_ithread;
   if (it == NULL)
      ih = NULL;
   else
      ih = TAILQ_FIRST(&it->it_handlers);

   /*
    * XXX: We assume that IRQ 0 is only used for the ISA timer
    * device (clk).
    */
   vector = isrc->is_pic->pic_vector(isrc);
   if (vector == 0)
      clkintr_pending = 1;

   critical_enter();
   if (ih != NULL && ih->ih_flags & IH_FAST) {
      /*
       * Execute fast interrupt handlers directly.
       * To support clock handlers, if a handler registers
       * with a NULL argument, then we pass it a pointer to
       * a trapframe as its argument.
       */
      TAILQ_FOREACH(ih, &it->it_handlers, ih_next) {
         MPASS(ih->ih_flags & IH_FAST);
         CTR3(KTR_INTR, "%s: executing handler %p(%p)",
             __func__, ih->ih_handler,
             ih->ih_argument == NULL ? iframe :
             ih->ih_argument);
         if (ih->ih_argument == NULL)
            ih->ih_handler(iframe);
         else
            ih->ih_handler(ih->ih_argument);
      }
      isrc->is_pic->pic_eoi_source(isrc);
      error = 0;

凡是总是有例外,fast中断不在中断线程的上下文中运行,而是直接在用户进程的上下文中运行
代码:

   } else {
      /*
       * For stray and threaded interrupts, we mask and EOI the
       * source.
       */
      isrc->is_pic->pic_disable_source(isrc);
      isrc->is_pic->pic_eoi_source(isrc);
      if (ih == NULL)
         error = EINVAL;
      else
         error = ithread_schedule(it, !cold);
   }

其他的非快速中断则需要调度。这里先应答中断控制器,然后调度。
代码:

   critical_exit();
   if (error == EINVAL) {
      atomic_add_long(isrc->is_straycount, 1);
      if (*isrc->is_straycount < MAX_STRAY_LOG)
         log(LOG_ERR, "stray irq%d\n", vector);
      else if (*isrc->is_straycount == MAX_STRAY_LOG)
         log(LOG_CRIT,
             "too many stray irq %d's: not logging anymore\n",
             vector);
   }
   td->td_intr_nesting_level--;
}

中断线程调度函数ithread_schedule()处理有关中断线程调度的工作。
代码:

int
ithread_schedule(struct ithd *ithread, int do_switch)
{
   struct int_entropy entropy;
   struct thread *td;
   struct thread *ctd;
   struct proc *p;

   /*
    * If no ithread or no handlers, then we have a stray interrupt.
    */
   if ((ithread == NULL) || TAILQ_EMPTY(&ithread->it_handlers))
      return (EINVAL);

   ctd = curthread;
   /*
    * If any of the handlers for this ithread claim to be good
    * sources of entropy, then gather some.
    */
   if (harvest.interrupt && ithread->it_flags & IT_ENTROPY) {
      entropy.vector = ithread->it_vector;
      entropy.proc = ctd->td_proc;
      random_harvest(&entropy, sizeof(entropy), 2, 0,
          RANDOM_INTERRUPT);
   }

如果该中断线程有IT_ENTROPY标志,说明可以当作随机数的来源。
代码:

   td = ithread->it_td;
   p = td->td_proc;
   KASSERT(p != NULL, ("ithread %s has no process", ithread->it_name));
   CTR4(KTR_INTR, "%s: pid %d: (%s) need = %d",
       __func__, p->p_pid, p->p_comm, ithread->it_need);

   /*
    * Set it_need to tell the thread to keep running if it is already
    * running.  Then, grab sched_lock and see if we actually need to
    * put this thread on the runqueue.  If so and the do_switch flag is
    * true and it is safe to switch, then switch to the ithread
    * immediately.  Otherwise, set the needresched flag to guarantee
    * that this ithread will run before any userland processes.
    */
   ithread->it_need = 1;

设置it_need,可以保证中断线程不会在还有中断的情况下,错过中断而去睡眠,见ithread_loop()。
代码:

   mtx_lock_spin(&sched_lock);
   if (TD_AWAITING_INTR(td)) {
      CTR2(KTR_INTR, "%s: setrunqueue %d", __func__, p->p_pid);
      TD_CLR_IWAIT(td);
      setrunqueue(td);
      if (do_switch &&
          (ctd->td_critnest == 1) ) {
         KASSERT((TD_IS_RUNNING(ctd)),
             ("ithread_schedule: Bad state for curthread."));
         ctd->td_proc->p_stats->p_ru.ru_nivcsw++;
         if (ctd->td_flags & TDF_IDLETD)
            ctd->td_state = TDS_CAN_RUN; /* XXXKSE */
         mi_switch();
      } else {
         curthread->td_flags |= TDF_NEEDRESCHED;
      }

如果中断线程正在睡眠,也就是说中断线程正在等待中断的到来,则将它放入runqueue,马上运行。
如果参数指示可以调度,并且当前线程的嵌套调度深度为1,即第一次试图调度中断线程,则进行
上下文切换,否则,将不立即调度运行中断线程,而要等到正常调度时再运行。

这里需要指出的是,如果决定mi_switch(),由于中断线程优先级很高,中断线程将会立即执行,
中断处理函数完成后也许将回到这里,也可能有变数,不会马上回到这里(FIXME),因此前面
intr_execute_handlers()中先应答中断控制器,将中断处理必须做的先做完。

调度回来后,继续运行,完成整个中断的处理。
代码:

   } else {
      CTR4(KTR_INTR, "%s: pid %d: it_need %d, state %d",
          __func__, p->p_pid, ithread->it_need, td->td_state);
   }

否则,由于已经设置了it_need=1,已经在运行的中断线程将负责处理之。
代码:

   mtx_unlock_spin(&sched_lock);

   return (0);
}

我们再来看看中断线程本身,该函数较为简单,两个嵌套的循环保证不会遗漏中断,
如果中断服务完成,则睡眠,调用mi_switch()
代码:

/*
 * This is the main code for interrupt threads.
 */
static void
ithread_loop(void *arg)
{
   struct ithd *ithd;      /* our thread context */
   struct intrhand *ih;      /* and our interrupt handler chain */
   struct thread *td;
   struct proc *p;
   
   td = curthread;
   p = td->td_proc;
   ithd = (struct ithd *)arg;   /* point to myself */
   KASSERT(ithd->it_td == td && td->td_ithd == ithd,
       ("%s: ithread and proc linkage out of sync", __func__));

   /*
    * As long as we have interrupts outstanding, go through the
    * list of handlers, giving each one a go at it.
    */
   for (;;) {
      /*
       * If we are an orphaned thread, then just die.
       */
      if (ithd->it_flags & IT_DEAD) {
         CTR3(KTR_INTR, "%s: pid %d: (%s) exiting", __func__,
             p->p_pid, p->p_comm);
         td->td_ithd = NULL;
         mtx_destroy(&ithd->it_lock);
         mtx_lock(&Giant);
         free(ithd, M_ITHREAD);
         kthread_exit(0);
      }

如果已经删除当前IRQ的中断处理程序,则需要退出中断线程。
代码:

      CTR4(KTR_INTR, "%s: pid %d: (%s) need=%d", __func__,
           p->p_pid, p->p_comm, ithd->it_need);
      while (ithd->it_need) {
         /*
          * Service interrupts.  If another interrupt
          * arrives while we are running, they will set
          * it_need to denote that we should make
          * another pass.
          */
         atomic_store_rel_int(&ithd->it_need, 0);

清除it_need标志,当清除后又有中断发生时,it_need将变成1,从而循环继续。
代码:

restart:
         TAILQ_FOREACH(ih, &ithd->it_handlers, ih_next) {
            if (ithd->it_flags & IT_SOFT && !ih->ih_need)
               continue;
            atomic_store_rel_int(&ih->ih_need, 0);
            CTR6(KTR_INTR,
                "%s: pid %d ih=%p: %p(%p) flg=%x", __func__,
                p->p_pid, (void *)ih,
                (void *)ih->ih_handler, ih->ih_argument,
                ih->ih_flags);

            if ((ih->ih_flags & IH_DEAD) != 0) {
               mtx_lock(&ithd->it_lock);
               TAILQ_REMOVE(&ithd->it_handlers, ih,
                   ih_next);
               wakeup(ih);
               mtx_unlock(&ithd->it_lock);
               goto restart;
            }
            if ((ih->ih_flags & IH_MPSAFE) == 0)
               mtx_lock(&Giant);
            ih->ih_handler(ih->ih_argument);

调用设备驱动的中断服务函数。所有注册到该IRQ的函数都将被调用,各个设备的函数将检查
自己设备的状态以确定是否是自己的设备产生的中断。
代码:

            if ((ih->ih_flags & IH_MPSAFE) == 0)
               mtx_unlock(&Giant);
         }
      }

      /*
       * Processed all our interrupts.  Now get the sched
       * lock.  This may take a while and it_need may get
       * set again, so we have to check it again.
       */
      WITNESS_WARN(WARN_PANIC, NULL, "suspending ithread");
      mtx_assert(&Giant, MA_NOTOWNED);
      mtx_lock_spin(&sched_lock);
      if (!ithd->it_need) {
         /*
          * Should we call this earlier in the loop above?
          */
         if (ithd->it_enable != NULL)
            ithd->it_enable(ithd->it_vector);
         TD_SET_IWAIT(td); /* we're idle */
         p->p_stats->p_ru.ru_nvcsw++;
         CTR2(KTR_INTR, "%s: pid %d: done", __func__, p->p_pid);
         mi_switch();
         CTR2(KTR_INTR, "%s: pid %d: resumed", __func__, p->p_pid);
      }

如果此时it_need==1,则说明新来了中断,继续for循环为该中断服务,
否则挂起调度。
代码:

      mtx_unlock_spin(&sched_lock);
   }
}

3,软件中断swi

我们将举例说明软件中断swi。

3.1登记

系统启动时,调用start_softintr()登记两个重要的软件中断,
软时钟中断和VM软中断。
当情况需要时,内核将调用swi_sched()来调度软件中断的运行。
代码:

/*
 * Start standard software interrupt threads
 */
static void
start_softintr(void *dummy)
{
   struct proc *p;

   if (swi_add(&clk_ithd, "clock", softclock, NULL, SWI_CLOCK,
      INTR_MPSAFE, &softclock_ih) ||
       swi_add(NULL, "vm", swi_vm, NULL, SWI_VM, INTR_MPSAFE, &vm_ih))
      panic("died while creating standard software ithreads");

   p = clk_ithd->it_td->td_proc;
   PROC_LOCK(p);
   p->p_flag |= P_NOLOAD;
   PROC_UNLOCK(p);
}

int
swi_add(struct ithd **ithdp, const char *name, driver_intr_t handler,
       void *arg, int pri, enum intr_type flags, void **cookiep)
{
   struct ithd *ithd;
   int error;

   if (flags & (INTR_FAST | INTR_ENTROPY))
      return (EINVAL);

   ithd = (ithdp != NULL) ? *ithdp : NULL;

   if (ithd != NULL) {
      if ((ithd->it_flags & IT_SOFT) == 0)
         return(EINVAL);
   } else {
      error = ithread_create(&ithd, pri, IT_SOFT, NULL, NULL,
          "swi%d:", pri);
      if (error)
         return (error);

      if (ithdp != NULL)
         *ithdp = ithd;
   }
   return (ithread_add_handler(ithd, name, handler, arg,
          (pri * RQ_PPQ) + PI_SOFT, flags, cookiep));
}

3.2调度

硬件时钟中断,需要处理非紧急时钟事务时,调度softclock,以便在响应完硬件时钟中断后
运行softclock。
代码:

/*
 * The real-time timer, interrupting hz times per second.
 */
void
hardclock(frame)
   register struct clockframe *frame;
{
   ......

   if (need_softclock)
      swi_sched(softclock_ih, 0);

   ......
}

/*
 * Schedule a heavyweight software interrupt process.
 */
void
swi_sched(void *cookie, int flags)
{
   struct intrhand *ih = (struct intrhand *)cookie;
   struct ithd *it = ih->ih_ithread;
   int error;

   atomic_add_int(&cnt.v_intr, 1); /* one more global interrupt */
      
   CTR3(KTR_INTR, "swi_sched pid %d(%s) need=%d",
      it->it_td->td_proc->p_pid, it->it_td->td_proc->p_comm, it->it_need);

   /*
    * Set ih_need for this handler so that if the ithread is already
    * running it will execute this handler on the next pass.  Otherwise,
    * it will execute it the next time it runs.
    */
   atomic_store_rel_int(&ih->ih_need, 1);
   if (!(flags & SWI_DELAY)) {
      error = ithread_schedule(it, !cold && !dumping);
      KASSERT(error == 0, ("stray software interrupt"));
   }
}

_________________
>|
>| http://people.FreeBSDChina.org/wheelz/
>|
返回页首
阅览会员资料 发送站内信件 浏览发表者的主页
TOLLY
道士


注册时间: 2002-08-20
文章: 824
来自: 广州

文章发表于: Wed 2004-05-12 13:16:48    发表主题: 引用并回复

好文章啊,只可惜俺看不懂 Crying or Very sad
支持~~
返回页首
阅览会员资料 发送站内信件 发送电子邮件
wheelz
半仙


注册时间: 2002-11-18
文章: 120

文章发表于: Wed 2004-05-12 13:24:32    发表主题: 引用并回复

>
呵呵,也许是我写的不好。 Embarassed
TOLLY 写到:
好文章啊,只可惜俺看不懂 Crying or Very sad
支持~~

_________________
>|
>| http://people.FreeBSDChina.org/wheelz/
>|
返回页首
阅览会员资料 发送站内信件 浏览发表者的主页
yanshiping
半仙


注册时间: 2003-10-23
文章: 15

文章发表于: Wed 2004-05-12 17:41:58    发表主题: 引用并回复

高手
返回页首
阅览会员资料 发送站内信件
steven
半仙


注册时间: 2004-01-06
文章: 148

文章发表于: Thu 2004-05-13 11:35:51    发表主题: 引用并回复

写得很好,希望能够长期坚持下去,为本站增加一些可讨论问题。
返回页首
阅览会员资料 发送站内信件
pubb
半仙


注册时间: 2004-05-15
文章: 50

文章发表于: Sun 2004-05-16 00:47:36    发表主题: 引用并回复

文中FIXME处所指不是很明白。
如果是关于中断线程的调度原则,我的理解是,FreeBSD虽然用线程来处理中断,但并不按照通常调度线程的时机来调度中断线程――FreeBSD是核心不可抢占的,如果中断线程都等到控制返回用户态时再启动,恐怕不能满足操作系统的很多需求(具体什么需求一时想不清楚)。所以,一旦发生中断,FreeBSD都会立即调度中断线程(更迅速的就直接在当前线程的上下文中处理,连上下文切换都省了,正如你文中所言),除非中断已经在处理中(td_cristnest==1,由intr_execute_handlers()里调用critical_enter()设置)。
这一点我还没想明白,critical_enter()会关闭中断(cpu_critical_enter()、intr_disable()、disable_intr()、”cli”),此时应该不会再次出现中断要求响应吧?

总感觉这种中断处理方式挺别扭的。灵活性是有了,但究竟有多少中断处理例程利用了这一灵活性呢?如果我想利用中断处理可阻塞的灵活性,应当从哪里入手呢?是不是允许我在我的handler里sleep?那我势必就要critical_exit()是不是?否则中断还阻塞着就mi_switch(),不是太危险了吗?那何时critical_enter()从而回到intr_execute_handlers()所设置的状态呢?太复杂了,想不清楚。

希望我说清楚我的糊涂想法了。
返回页首
阅览会员资料 发送站内信件
pubb
半仙


注册时间: 2004-05-15
文章: 50

文章发表于: Sun 2004-05-16 00:56:05    发表主题: 引用并回复

不让编辑也不让删除,有一处写得不对......
引用:
除非中断已经在处理中(td_cristnest==1,由intr_execute_handlers()里调用critical_enter()设置)。

应该是当前线程的td_cristnest大于1的情况。

版主们能不能开放“编辑”和“删除”能力啊,不明白这样限制有何目的。
返回页首
阅览会员资料 发送站内信件
pubb
半仙


注册时间: 2004-05-15
文章: 50

文章发表于: Mon 2004-05-24 22:40:50    发表主题: 引用并回复

帖子被埋了吗?
返回页首
阅览会员资料 发送站内信件
wheelz
半仙


注册时间: 2002-11-18
文章: 120

文章发表于: Wed 2004-05-26 11:03:28    发表主题: 引用并回复

pubb 写到:
文中FIXME处所指不是很明白。
如果是关于中断线程的调度原则,我的理解是,FreeBSD虽然用线程来处理中断,但并不按照通常调度线程的时机来调度中断线程――FreeBSD是核心不可抢占的,如果中断线程都等到控制返回用户态时再启动,恐怕不能满足操作系统的很多需求(具体什么需求一时想不清楚)。所以,一旦发生中断,FreeBSD都会立即调度中断线程(更迅速的就直接在当前线程的上下文中处理,连上下文切换都省了,正如你文中所言),除非中断已经在处理中(td_cristnest==1,由intr_execute_handlers()里调用critical_enter()设置)。
这一点我还没想明白,critical_enter()会关闭中断(cpu_critical_enter()、intr_disable()、disable_intr()、”cli”),此时应该不会再次出现中断要求响应吧?

critical_enter()是会关闭中断,但这是在当前进程的上下文中。
当系统通过mi_switch()->cpu_switch()恢复中断线程的上下文时,
popfl指令会开中断(应该是这样Smile)

引用:

总感觉这种中断处理方式挺别扭的。灵活性是有了,但究竟有多少中断处理例程利用了这一灵活性呢?如果我想利用中断处理可阻塞的灵活性,应当从哪里入手呢?是不是允许我在我的handler里sleep?那我势必就要critical_exit()是不是?否则中断还阻塞着就mi_switch(),不是太危险了吗?那何时critical_enter()从而回到intr_execute_handlers()所设置的状态呢?太复杂了,想不清楚。


应该很多中断处理例程都利用了这一灵活性。
可以允许在interrupt handler里sleep.并不需要critical_exit(),
因为critical section issue是per thread的。
不危险,因为中断就是个线程。
intr_execute_handlers()所设置的critical section状态
是另外某个线程(大多数情况下是普通线程)的状态,
当重新调度回该状态时,就恢复了。

_________________
>|
>| http://people.FreeBSDChina.org/wheelz/
>|
返回页首
阅览会员资料 发送站内信件 浏览发表者的主页
pubb
半仙


注册时间: 2004-05-15
文章: 50

文章发表于: Fri 2004-05-28 09:36:24    发表主题: 引用并回复

谢谢,明白了。佩服wheelz大侠的综合能力。

cristnest应该是指的“中断嵌套层数”吧,大于1的情况是不是说某个线程上次被中断时服务还没完成(可能中断线程在sleep,所以没有调用critical_exit()降低cristnest),本次中断又来了,从而两次(或多次)调用critical_enter()?
看起来这不像是中断嵌套,而像是中断重叠,因为本次中断的处理线程仍然有可能被切换到,从而造成多个中断的处理过程重叠。我这样的理解有什么错误吗?
如果是同一种中断的两次出现,好像ithread_loop()会根据it_need进行处理,不过还没看明白。
对cristnest的含义还是不太明白。能进一步解释一下吗?
返回页首
阅览会员资料 发送站内信件
wheelz
半仙


注册时间: 2002-11-18
文章: 120

文章发表于: Fri 2004-05-28 10:03:03    发表主题: 引用并回复

pubb 写到:
cristnest应该是指的“中断嵌套层数”吧,大于1的情况是不是说某个线程上次被中断时服务还没完成(可能中断线程在sleep,所以没有调用critical_exit()降低cristnest),本次中断又来了,从而两次(或多次)调用critical_enter()?
看起来这不像是中断嵌套,而像是中断重叠,因为本次中断的处理线程仍然有可能被切换到,从而造成多个中断的处理过程重叠。我这样的理解有什么错误吗?
如果是同一种中断的两次出现,好像ithread_loop()会根据it_need进行处理,不过还没看明白。
对cristnest的含义还是不太明白。能进一步解释一下吗?


cristnest的主要作用不是指的“中断嵌套层数”,而是临界区的嵌套深度,
在FreeBSD中是有临界区的,比如对spinlock进行修改时,因为不可能以
原子机器指令来完成,因此必须加以保护,不能让中断进来,一旦中断进来,
也对此spinlock操作的话,spinlock的状态就不对了。在中断处理中判断
cristnest是否等于1就是这个目的,intr_execute_handlers()自己会有一个
critical_enter(),因此等于1就说明当前进程不在临界区,不会破坏spinlock
等原子操作,
因此可以切换到中断线程,所以调用mi_switch(),否则,就不能切换,
只能设置一个标志,等当前进程退出临界区时再切换。

it_need和TD_AWAITING_INTR()一起,保证了中断处理不能重入。

另外,不是由中断线程调用critical_exit(),而是被中断的线程自己调用
critical_exit(),说得更准确一些,是重新调度到被中断的线程,
在其上下文中执行intr_execute_handler()的后半部分的时候调用的,
这和中断线程没有关系。

_________________
>|
>| http://people.FreeBSDChina.org/wheelz/
>|
返回页首
阅览会员资料 发送站内信件 浏览发表者的主页
pubb
半仙


注册时间: 2004-05-15
文章: 50

文章发表于: Sat 2004-05-29 21:04:00    发表主题: 引用并回复

除了感谢以外,暂时没什么要说的。
返回页首
阅览会员资料 发送站内信件
willy_young
半仙


注册时间: 2004-07-12
文章: 1

文章发表于: Sun 2004-07-18 10:40:59    发表主题: Re: FreeBSD 5 内核源代码分析之中断处理 引用并回复

wheelz 写到:
FreeBSD 5 内核源代码分析之中断处理
by wheelz
---------------------------------

代码:

/*
 * Macros for interrupt interrupt entry, call to handler, and exit.
 */
#define   INTR(irq_num, vec_name) \
   .text ;                        \
   SUPERALIGN_TEXT ;                  \
IDTVEC(vec_name) ;                     \
   pushl   $0 ;      /* dummy error code */         \
   pushl   $0 ;      /* dummy trap type */         \
   pushal ;      /* 8 ints */            \
   pushl   %ds ;      /* save data and extra segments ... */   \
   pushl   %es ;                     \
   pushl   %fs ;                     \
   mov   $KDSEL,%ax ;   /* load kernel ds, es and fs */      \
   mov   %ax,%ds ;                  \
   mov   %ax,%es ;                  \
   mov   $KPSEL,%ax ;                  \
   mov   %ax,%fs ;                  \
;                           \
   FAKE_MCOUNT(13*4(%esp)) ;   /* XXX late to avoid double count */ \
   pushl   $irq_num;    /* pass the IRQ */         \
   call   atpic_handle_intr ;               \
   addl   $4, %esp ;   /* discard the parameter */      \
;                           \
   MEXITCOUNT ;                     \
   jmp   doreti

请问大侠这一段定义的宏是在哪调用的?是通过IDTVEC(atpic_intr ## irq )找到的吗?
返回页首
阅览会员资料 发送站内信件
夜客
道士


注册时间: 2003-09-04
文章: 731

文章发表于: Mon 2005-01-10 17:51:14    发表主题: 引用并回复

Waiting 5 seconds for SCSI devices to settle
(noperiph:sym0:0:-1:-1): SCSI BUS reset delivered.

到这就不动了。
返回页首
阅览会员资料 发送站内信件 MSN Messenger
SoBeIt
半仙


注册时间: 2005-01-13
文章: 5

文章发表于: Thu 2005-01-13 16:09:20    发表主题: 引用并回复

简化线程和中断的互斥关系。

想请问一下wheelz,这应该怎么理解?
返回页首
阅览会员资料 发送站内信件
SoBeIt
半仙


注册时间: 2005-01-13
文章: 5

文章发表于: Fri 2005-01-14 19:36:28    发表主题: 引用并回复

我是想问FreeBSD 5.x把中断用中断处理线程来实现,一个是这些中断线程的内核栈都是不能换出内存,占用了很多空间。而且这里所说的中断被阻塞,应该是被优先级更高的中断线程强占,那么这和中断嵌套有啥区别呢?还有另一个优点简化线程和中断的互斥关系应该怎么理解?现在大多数操作系统处理线程和中断的关系不是都挺好吗?FreeBSD这种方法是不是模糊了中断上下文和线程上下文的关系?
返回页首
阅览会员资料 发送站内信件
从以前的文章开始显示:   
发表新文章   这个论题已经被锁定,您不能发表、回复或者编辑文章。    FreeBSD China -> 中文-桌面-开发-调试 论坛时间为 北京时间
1页/共1

 
转跳到:  
不能发布新主题
不能在这个论坛回复主题
不能在这个论坛编辑自己的文章
不能在这个论坛删除自己的文章
不能在这个论坛发表投票


Powered by phpBB 2023cc © 2003 Opensource Steps; © 2003-2009 The FreeBSD Simplified Chinese Project
Powered by phpBB © 2001, 2005 phpBB Group
Protected by Project Honey Pot and phpBB.cc
silvery-trainer
The FreeBSD China Project 网站: 中文计划网站 社区网站
The FreeBSD China Project 版权所有 (C) 1999 - 2003 网页设计版权 著作权和商标