引用网址:
http://edu.codepub.com/2010/0209/20378.php linux的中断线程化实现
http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/index.html
Linux 内核中断内幕
在嵌入式领域,业界对 Linux 实时性的呼声越来越高,对中断进行改造势在必行。在 Linux 中,中断具有最高的优先级。不论在任何时刻,只要产生中断事件,内核将立即执行相应的中断处理程序,等到所有挂起的中断和软中断处理完毕后才能执行正常的任务,因此有可能造成实时任务得不到及时的处理。中断线程化之后,中断将作为内核线程运行而且被赋予不同的实时优先级,实时任务可以有比中断线程更高的优先级。这样,具有最高优先级的实时任务就能得到优先处理,即使在严重负载下仍有实时性保证。
目前较新的 Linux 2.6.17 还不支持中断线程化。但由 Ingo Molnar 设计并实现的实时补丁,实现了中断线程化。最新的下载地址为:
http://people.redhat.com/~mingo/realtime-preempt/patch-2.6.17-rt9
下面将对中断线程化进行简要分析。
在初始化阶段,中断线程化的中断初始化与常规中断初始化大体上相同,在 start_kernel() 函数中都调用了 trap_init() 和 init_IRQ() 两个函数来初始化 irq_desc_t 结构体,不同点主要体现在内核初始化创建 init 线程时,中断线程化的中断在 init() 函数中还将调用 init_hardirqs(kernel/irq/manage.c(已经打过上文提到的补丁)),来为每一个 IRQ 创建一个内核线程,最高实时优先级为 50,依次类推直到 25,因此任何 IRQ 线程的最低实时优先级为 25。
void __init init_hardirqs(void) { …… for (i = 0; i < NR_IRQS; i++) { irq_desc_t *desc = irq_desc + i; if (desc->action && !(desc->status & IRQ_NODELAY)) desc->thread = kthread_create(do_irqd, desc, "IRQ %d", irq); …… } } static int do_irqd(void * __desc) { …… /* * Scale irq thread priorities from prio 50 to prio 25 */ param.sched_priority = curr_irq_prio; if (param.sched_priority > 25) curr_irq_prio = param.sched_priority - 1; …… }
|
如果某个中断号状态位中的 IRQ_NODELAY 被置位,那么该中断不能被线程化。
在中断处理阶段,两者之间的异同点主要体现在:两者相同的部分是当发生中断时,CPU 将调用 do_IRQ() 函数来处理相应的中断,do_IRQ() 在做了必要的相关处理之后调用 __do_IRQ()。两者最大的不同点体现在 __do_IRQ() 函数中,在该函数中,将判断该中断是否已经被线程化(如果中断描述符的状态字段不包含 IRQ_NODELAY 标志,则说明该中断被线程化了),对于没有线程化的中断,将直接调用 handle_IRQ_event() 函数来处理。
fastcall notrace unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs) { …… if (redirect_hardirq(desc)) goto out_no_end; …… action_ret = handle_IRQ_event(irq, regs, action); …… } int redirect_hardirq(struct irq_desc *desc) { …… if (!hardirq_preemption || (desc->status & IRQ_NODELAY) || !desc->thread) return 0; …… if (desc->thread && desc->thread->state != TASK_RUNNING) wake_up_process(desc->thread); …… }
|
对于已经线程化的情况,调用 wake_up_process() 函数唤醒中断处理线程,并开始运行,内核线程将调用 do_hardirq() 来处理相应的中断,该函数将判断是否有中断需要被处理,如果有就调用 handle_IRQ_event() 来处理。handle_IRQ_event() 将直接调用相应的中断处理函数来完成中断处理。
不难看出,不管是线程化还是非线程化的中断,最终都会执行 handle_IRQ_event() 函数来调用相应的中断处理函数,只是线程化的中断处理函数是在内核线程中执行的。
并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当被线程化。如果某个中断需要被实时处理,它可以像时钟中断那样,用 SA_NODELAY 标志来声明自己非线程化,例如:
static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT | SA_NODELAY, CPU_MASK_NONE, "timer", NULL, NULL };
|
其中,SA_NODELAY 到 IRQ_NODELAY 之间的转换,是在 setup_irq() 函数中完成的。
linux的中断线程化实现
2.6.25.8内核实现了中断线程化,内核为每一个中断向量建立了一个中断线程,具体就是在结构irq_desc中增加了一个task_struct来代表这个线程:
struct irq_desc {
irq_flow_handler_t handle_irq;
struct irq_chip *chip;
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned int irqs_unhandled;
unsigned long last_unhandled; /* Aging timer for unhandled count */
struct task_struct *thread; //中断线程
wait_queue_head_t wait_for_handler;
cycles_t timestamp;
raw_spinlock_t lock;
const char *name;
}
在 中断产生的时候,还是和往常一样进入do_IRQ,这个函数几乎没有什么变化,在do_IRQ中调用了irq_desc的handle_irq函数,这个 handle_irq是向量相关的,比如有边缘触发等等,这个方式涉及到了硬件规程,故不深入讨论,实际上,每个总线邦定到一个中断向量,而总线的中断方式是总线相关的所以中断向量的方式也就和硬件相关了,这里就以Level type为例来说明,Level type的handle_irq是handle_level_irq:
void handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
unsigned int cpu = smp_processor_id();
struct irqaction *action;
irqreturn_t action_ret;
spin_lock(&desc->lock);
mask_ack_irq(desc, irq); //屏蔽该中断,以防重入
if (unlikely(desc->status & IRQ_INPROGRESS))//如果正在处理则返回
goto out_unlock;
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
kstat_cpu(cpu).irqs[irq]++;
action = desc->action;
if (unlikely(!action || (desc->status & IRQ_DISABLED)))//禁用则返回
goto out_unlock;
desc->status |= IRQ_INPROGRESS; //标记为正在处理
if (redirect_hardirq(desc)) //检测是否为线程化中断,若是则唤醒中断线程
goto out_unlock;
spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action); //非线程化中断,处理之
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
spin_lock(&desc->lock);
desc->status &= ~IRQ_INPROGRESS;
if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
desc->chip->unmask(irq);
out_unlock:
spin_unlock(&desc->lock);
}
我们看看每个中断向量的中断线程是怎么初始化的,初始化的细节可以带给我们一大部分必要的信息,在实际开发中一定注意这一点,一个好的初始化带来的是将来操作的方便与清晰:
void __init init_hardirqs(void)
{
int i;
ok_to_create_irq_threads = 1;
for (i = 0; i < NR_IRQS; i++) { //对于每一个中断向量建立一个中断线程
irq_desc_t *desc = irq_desc + i;
if (desc->action && !(desc->status & IRQ_NODELAY)) //有IRQ_NODELAY标志的中断不允许线程化
start_irq_thread(i, desc);//实际建立线程
}
}
static int start_irq_thread(int irq, struct irq_desc *desc)
{
if (desc->thread || !ok_to_create_irq_threads)
return 0;
desc->thread = kthread_create(do_irqd, desc, "IRQ-%d", irq);//建立一个内核线程,赋值给desc->thread。
if (!desc->thread) {
return -ENOMEM;
}
smp_mb();
wake_up_process(desc->thread); //一切就绪之前即desc->thread被赋值之前可能已经有了中断,故唤醒该中断线程处理之。
return 0;
}
static int do_irqd(void * __desc)
{
struct sched_param param = { 0, };
struct irq_desc *desc = __desc;
current->flags |= PF_NOFREEZE | PF_HARDIRQ;
param.sched_priority = MAX_USER_RT_PRIO/2;
sys_sched_setscheduler(current->pid, SCHED_FIFO, ¶m); //设置实时优先级
while (!kthread_should_stop()) {
local_irq_disable_nort();
do {
set_current_state(TASK_INTERRUPTIBLE);//一定设置这个标志,否则很少有进程可以抢占中断线程,毕竟它是实时线程,如果不设这个标志,即使下面它自己schedule了,那么很大的可能性还是会选中它的
do_hardirq(desc); //处理中断请求
} while (current->state == TASK_RUNNING);
local_irq_enable_nort();
schedule(); //一轮处理完毕后切换到别的进程,实际上除了实时进程可能抢占中断线程,中断线程被强占的可能性极小,因此要主动切出。
}
__set_current_state(TASK_RUNNING);
return 0;
}
static void do_hardirq(struct irq_desc *desc)
{
unsigned long flags;
spin_lock_irqsave(&desc->lock, flags);
if (!(desc->status & IRQ_INPROGRESS))
goto out;
...
else if (desc->handle_irq == handle_level_irq)
thread_level_irq(desc);
...
else if (desc->handle_irq == handle_edge_irq)
thread_edge_irq(desc);
else
thread_do_irq(desc);
out:
spin_unlock_irqrestore(&desc->lock, flags);
if (waitqueue_active(&desc->wait_for_handler))
wake_up(&desc->wait_for_handler);
}
最终在thread_XX_irq中调用handle_IRQ_event来实际处理中断请求。当该中断向量上没有中断要处理的时候,对应的中断线程就主动切出了,而中断来临的时候redirect_hardirq会wakeup对应向量上的中断线程。
现在我们来看看linux中断线程化的意义,传统的linux内核上,中断都是作为最高优先级的执行绪存在的,它实际上并没有什么软件优先级的概念,而是 硬件架构决定了硬件中断到来的时候在该中断没有被屏蔽的条件下必须处理,即便是linux中最高优先级的实时进程也要向中断让路,这就大大削弱了 linux的实时性能,一个实时任务正在运行,将一直被中断打断,特别是在网络负载大的时候,虽然linux将耗时的操作都置于软中断,但是毕竟哪怕很小 延时的硬件中断也要延时,多个小的延时积累起来可能会有很大的延时,这不是实时操作所希望的,于是就有了一种想法,能否让中断也参与到优先级排队,于是中断线程化就是必然结果了。可是即便大操作放到了线程中,但是毕竟像common_interrupt和do_IRQ还是没有在线程中进行,因此还是会有中 断打断实时任务,linux的这种中断线程化实现仅仅将实时任务被硬件打断的延时降低到了很低的程度,实时任务被中断打断本身并没有得到改善,改善这个境地的一个方案就是引入硬件中断优先级并和线程优先级联系,在处理实时任务时非使能屏蔽掉不相关的任何硬件中断,使它们不再发生,就像solaris那样,当然这可以利用硬件的特性,引入“cpu当前优先级”的概念,当cpu处于优先级p时,任何低于p的中断都不能发生,cpu当前优先级和线程优先级以及中 断优先级直接关联,关于这个方案的实现可以参考windows的IRQL或者solaris的IPL。