电脑技术学习

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

dn001
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 %dn", 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%dn", vector);
else if (*isrc->is_straycount == MAX_STRAY_LOG)
log(LOG_CRIT,
"too many stray irq %d's: not logging anymoren",
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"));
}
}

标签: