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()的参数,也是中断
返回时用以恢复现场的寄存器状态。