阅读上一个主题 :: 阅读下一个主题 |
作者 |
留言 |
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 发表主题: |
|
|
好文章啊,只可惜俺看不懂
支持~~ |
|
返回页首 |
|
 |
wheelz 半仙
注册时间: 2002-11-18 文章: 120
|
|
返回页首 |
|
 |
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指令会开中断(应该是这样 )
引用: |
总感觉这种中断处理方式挺别扭的。灵活性是有了,但究竟有多少中断处理例程利用了这一灵活性呢?如果我想利用中断处理可阻塞的灵活性,应当从哪里入手呢?是不是允许我在我的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.
到这就不动了。 |
|
返回页首 |
|
 |
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这种方法是不是模糊了中断上下文和线程上下文的关系? |
|
返回页首 |
|
 |
|
|
您不能发布新主题 您不能在这个论坛回复主题 您不能在这个论坛编辑自己的文章 您不能在这个论坛删除自己的文章 您不能在这个论坛发表投票
|
|