Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1204459
  • 博文数量: 185
  • 博客积分: 495
  • 博客等级: 下士
  • 技术积分: 1418
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-02 15:12
个人简介

治肾虚不含糖,专注内核性能优化二十年。 https://github.com/KnightKu

文章分类

全部博文(185)

文章存档

2019年(1)

2018年(12)

2017年(5)

2016年(23)

2015年(1)

2014年(22)

2013年(82)

2012年(39)

分类:

2012-11-05 15:01:09

最近在给我的开发板上的内核作升级的时候,意外发现我现在正在移植的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对中断线程化的支持情况,请看下图:


 


这张图来自:
本文作者向我们介绍了标准内核为支持实时性而做的努力和不足之处,以及rt补丁为实时性所做的努力及尚不足之处。让我们稍稍过过:
 
主流Linux虽然部分满足POSIX 1003.1b实时扩展标准,但还不完全是一个实时操作系统,主要表现为:

* 任务调度与内核抢占
2.6版本内核添加了许多抢占点,使进程执行在内核代码时也可被抢占。为支持内核代码可抢占,在2.6版内核中通过采用禁用中断的自旋锁来保护临界区。但此时如果有低优先级进程在临界区中执行,高优先级进程即使不访问低优先级所保护的临界区,也必须等待低优先级进程退出临界区。
* 中断延迟
在主流Linux内核设计中,中断可以抢占最高优先级的任务,使高优先级任务被阻塞的最长时间不确定。而且,由于内核为保护临界区需要关闭中断,更加增长了高优先级任务阻塞时间。
* 时钟精度
Linux通过硬件时钟编程来产生毫秒级周期性时钟中断进行内核时间管理,无法满足实时系统较高精度的调度要求。内核定时器精度同样也受限于时钟中断,无法满足实时系统的高精度定时需求。
* 其他延迟 
此外,Linux内核其他子系统也存在多种延迟。比如为了增强内核性能和减少内存消耗,Linux仅在需要时装载程序地址空间相应的内存页。当被存取内容(如代码)不在RAM中则内存管理单元(MMU)将产生页表错误(Page-Fault)触发页面装载,造成实时进程响应时间不确定。

实时抢占内核补丁针对Linux各种延迟进行了实时化改进[5],主要包括了几个方面的技术。
  
* 实时抢占内核
为了实现内核完全可抢占,实时内核临界区用高性能优先级继承mutex替换原来自旋锁(spin-lock)来进行保护,使得在临界区内的执行也可被抢占。只有当线程想访问一个其他线程正在访问的临界区时,才被调度至睡眠,直到所保护的临界区被释放时被唤醒。
在实时抢占内核中通过优先级继承机制(PI)在线程被一个低优先级线程所持有的资源阻塞时,低优先级线程通过继承被阻塞线程优先级,尽快执行并释放所持资源而不被其他线程所抢占。
* 新型锁机制带来内核性能提升
实时抢占补丁替换了大内核锁(BKL),将BKL从spin lock改成是mutex,持有BKL的线程也可以被抢占,减少了内核调度延迟。此外,实时抢占补丁通过mutex替代semaphore,避免了不必要的时间负载。实时抢占补丁实现了可抢占的RCU(Read Copy Update)锁和串行化读写锁,保证了执行可预测性,提高了性能。
* 中断线程化
实时抢占补丁通过内核线程来实现一些硬件中断和软件中断的服务程序。体系结构相关处理代码设置IRQ状态、检查线程化的中断是否使能,并唤醒相关线程。在中断线程被调度执行后,进行中断服务处理。在实时抢占内核中,用户线程优先级可以高于设备中断服务线程。实时任务无需等待设备驱动处理程序执行,减小了实时抢占延迟。
* 时钟系统改进
实时抢占内核的时钟系统重新进行了设计,实现了高精度定时器[6]。时钟精度不再依赖jiffies,使POSIX定时器和nanosleep精度由具体硬件所能提供的精度决定,使得gettimeofday能够提供实时系统所需的精确时间值。
* 其他改进
Linux在用户层支持性能良好的futex,实现原理类似于内核优先级继承mutex,仅在产生竞态时进入内核,提高了应用程序性能。此外,实时抢占补丁内核还提供mutex死锁检测、延迟跟踪与测量、中断关闭跟踪与延迟测量、抢占延迟测量等内核调试与诊断、内核性能测量与调优等工具、实时Trace支持( Ftrace)等支持。

阅读(733) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~