分类:
2012-08-20 18:28:25
原文地址:2.6.29.6-rt24中断线程化的实现机制 作者:jinxinxin163
最近在给我的开发板上的内核作升级的时候,意外发现我现在正在移植的linux-2.6.29.6-rt24的内核和我以前使用的非rt内核的处理中断的方式不一样!在非rt kernel上,在arch/arm/kernel/entry-armv.S文件里有一句:bne asm_do_IRQ,进入了asm_do_IRQ函数。然后是asm_do_IRQ(arm平台)->handle_level_irq/handle_edge_irq->具体的中断服务例程;但在linux-linux-2.6.29.6-rt24则不是这样的,且听我慢慢道来。
linux-2.6.29.6-rt24的init/main.c文件的kernel_init()函数的开头处有一句代码:
init_hardirqs();
这个函数的实现在kernel/irq/manage.c文件中,其定义如下:
#ifdef CONFIG_PREEMPT_HARDIRQS
void __init init_hardirqs(void)
{
struct irq_desc *desc;
int irq;
ok_to_create_irq_threads = 1;
for_each_irq_desc(irq, desc) {
if (desc->action && !(desc->status & IRQ_NODELAY))
start_irq_thread(irq, desc);
//对于每个中断线,假如存在相应的中断服务例程描述符,
//并且这些中断不需要立刻执行,则对此中断线进行线程化
}
}
#else
...
#endif
start_irq_thread的实现如下:
static int start_irq_thread(int irq, struct irq_desc *desc)
{
if (desc->thread || !ok_to_create_irq_threads)
return 0;
//假如此中断线不允许线程话,那么返回
init_waitqueue_head(&desc->wait_for_handler);
desc->thread = kthread_create(do_irqd, desc, "IRQ-%d", irq);
//为中断号为irq的中断线建立一个内核级别的线程
if (!desc->thread) {
printk(KERN_ERR "irqd: could not create IRQ thread %d!\n", irq);
return -ENOMEM;
}
/*
* An interrupt may have come in before the thread pointer was
* stored in desc->thread; make sure the thread gets woken up in
* such a case:
*/
smp_mb();//smp_mb可以确保之前的read/write memory operation和之后的 //read/write memory operation相对有序的发生
wake_up_process(desc->thread);
//在线程指针保存在desc->thread以前,一个中断可能已经发生。在这种情况下,先让现线程 休眠
return 0;
}
do_irqd的代码如下:
static int do_irqd(void * __desc)
{
struct sched_param param = { 0, };
struct irq_desc *desc = __desc;
#ifdef CONFIG_SMP
set_cpus_allowed_ptr(current, desc->affinity);
#endif
//我们不是smp,此处略过
current->flags |= PF_NOFREEZE | PF_HARDIRQ;
//PF_NOFREEZE这个标记表示:the thread should not be frozen
//PF_HARDIRQ:hardirq context
/*
* Set irq thread priority to SCHED_FIFO/50:
*/
param.sched_priority = MAX_USER_RT_PRIO/2;
//将中断线程的优先级设为100/2=50
sys_sched_setscheduler(current->pid, SCHED_FIFO, ¶m);
//此函数进行一些列检查,然后会调用sched_setscheduler,这可是系统级的调度器
while (!kthread_should_stop()) {
//只要当前线程没有停止就一直循环
local_irq_disable_nort();
//如果定义了CONFIG_PREEMPT_RT,那么此函数为空
set_current_state(TASK_INTERRUPTIBLE);
//设置当前进程为可TASK_INTERRUPTIBLE
#ifndef CONFIG_PREEMPT_RT
irq_enter(); //进入中断上下文
#endif
do_hardirq(desc);
#ifndef CONFIG_PREEMPT_RT
irq_exit(); //退出中断上下文
#endif
local_irq_enable_nort();
//如果定义了CONFIG_PREEMPT_RT,那么此函数为空
cond_resched();
#ifdef CONFIG_SMP
/*
* Did IRQ affinities change?
*/
if (!cpumask_equal(¤t->cpus_allowed, desc->affinity))
set_cpus_allowed_ptr(current, desc->affinity);
#endif
schedule();
}
__set_current_state(TASK_RUNNING);
return 0;
}
kthread_should_stop()函数实现如下:
int kthread_should_stop(void)
{
return (kthread_stop_info.k == current);
}
你可以通过设置kthread_stop_info变量来结束某个线程。
若kthread_stop_info.k == current,则表示当前进程停止了
上面提到的do_hardirq函数实现如下:
static void do_hardirq(struct irq_desc *desc)
{
unsigned long flags;
spin_lock_irqsave(&desc->lock, flags);
if (!(desc->status & IRQ_INPROGRESS))
goto out;
//假如此中断线没有激活,出错返回
if (desc->handle_irq == handle_simple_irq)
thread_simple_irq(desc);
else if (desc->handle_irq == handle_level_irq)
thread_level_irq(desc);
else if (desc->handle_irq == handle_fasteoi_irq)
thread_fasteoi_irq(desc);
else if (desc->handle_irq == handle_edge_irq)
thread_edge_irq(desc);
else
thread_do_irq(desc);
//根据handle_irq的类型分别进入到不同的线程化函数
out:
spin_unlock_irqrestore(&desc->lock, flags);
if (waitqueue_active(&desc->wait_for_handler))
wake_up(&desc->wait_for_handler);
//如果desc->wait_for_handler队列上有等待进程,则唤醒该进程
}
在此处我仅仅关注thread_level_irq函数:
thread_level_irq函数的实现如下:
static void thread_level_irq(irq_desc_t *desc)
{
unsigned int irq = desc->irq;
thread_simple_irq(desc);
if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
desc->chip->unmask(irq);
//假如此中断线可用,且存在相应的unmask函数,则开启此中断线
}
thread_simple_irq的实现如下
static void thread_simple_irq(irq_desc_t *desc)
{
struct irqaction *action = desc->action;
//拿到第一个中断服务例程描述符
unsigned int irq = desc->irq;
irqreturn_t action_ret;//irqreturn_t其实是个枚举变量,仅有两个值,IRQ_NONE和IRQ_HANDLED
do {
if (!action || desc->depth)
break;
//假如此中断服务例程描述符为空或者desc->depth > 0,那么说明此中断线上没有注册中断服务例程或者中断被禁用,退出。关于depth变量,以下有说明:
如果启用这条IRQ中断线,depth则为0,如果禁用这条IRQ中断线不止一次,则为一个正数。每当调用一次disable_irq(),该函数就对这个域的值加1;如果depth等于0,该函数就禁用这条IRQ中断线。相反,每当调用enable_irq()函数时,该函数就对这个域的值减1;如果depth变为0,该函数就启用这条IRQ中断线。
desc->status &= ~IRQ_PENDING;
//去除“未决”标记
spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action);
cond_resched_hardirq_context();
//必要的时候抢占一个硬中断
spin_lock_irq(&desc->lock);
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
} while (desc->status & IRQ_PENDING);//只要此中断线是未决的,则一直循环
desc->status &= ~IRQ_INPROGRESS;
//执行到此处的时候,此次中断已经处理完毕,去除IRQ_INPROGRESS标记,以相应下一轮中断
}
对于handle_IRQ_event函数,大家都比较熟悉吧,此君遍历中断号为irq的中断线上的所有的中断服务例程。到此,一轮中断就完成了。
注:2.6.29 非rt的执行路线如下:
do_asm_IRQ->generic_handle_irq->generic_handle_irq_desc->__do_IRQ->handle_IRQ_event
综上所述:linux-2.6.29.6-rt24的rt补丁对硬中断进行了线程化。如下为我的内核配置:
CONFIG_PREEMPT_RT=y
CONFIG_PREEMPT=y
CONFIG_PREEMPT_SOFTIRQS=y
CONFIG_PREEMPT_HARDIRQS=y
CONFIG_PREEMPT_RCU=y
CONFIG_RCU_TRACE=y
CONFIG_PREEMPT_RCU_TRACE=y
至于不同版本的kernel的rt patch对中断线程化的支持情况,请看下图: