zoukankan      html  css  js  c++  java
  • Linux中断分层--软中断和tasklet

    1. Linux中断分层

    (1)上半部:当中断发生时,它进行相应的硬件读写,并“登记”该中断。通常由中断处理程序充当上半部。(一般情况下,上半部不可被打断)

    (2)下半部:在系统空闲的时候,对上半部“登记”的中断进行后续处理(“延迟处理”)

    2. 对于中断下半部的实现方式一共有三种

    (1)软中断

    (2)tasklet微线程

    (3)工作队列

    3. Linux内核软中断分析

    (1)当中断发生时,Linux内核会跳转到中断总入口函数asm_do_IRQ(),根据传入的中断号,执行相应handle_irq()函数。做完这些工作之后,会调用函数irq_exit()(位于文件:kernal/softirq.c),该函数负责调用和处理待决的软中断。

    void irq_exit(void)
    {
        account_system_vtime(current);
        trace_hardirq_exit();
        sub_preempt_count(IRQ_EXIT_OFFSET);
        if (!in_interrupt() && local_softirq_pending())
            invoke_softirq();
    
    #ifdef CONFIG_NO_HZ
        /* Make sure that timer wheel updates are propagated */
        rcu_irq_exit();
        if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
            tick_nohz_stop_sched_tick(0);
    #endif
        preempt_enable_no_resched();
    }

    (2)invoke_softirq()是一个宏,等价于do_softirq()。调用do_softirq函数,就说明程序已经进入软中断环境了。与asm_do_IRQ所处的中断的上半部不同,处于软中断环境中,是可以被其他中断程序打断的,甚至是处于同一中断线的中断。也因为此,所以软中断可以执行一些稍微时间长一点的任务,也不会迟滞系统对中断的反应时间。
    (3)软中断由一个softirq_action结构体表示,在该结构体中只定义了一个函数指针

    struct softirq_action
    {
        void    (*action)(struct softirq_action *);
    };

    (4)Linux内核中用一个数据项为softirq_action类型的数组softirq_vec来存储所支持的所有软中断

    static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

    (5)在驱动程序中,注册软中断使用函数open_softirq

    void open_softirq(int nr, void (*action)(struct softirq_action *))
    {
        softirq_vec[nr].action = action;
    }

    (6)softirq_vec数组的下表代表不同类型的软中断,值越小,优先级越高,他们定义为一个枚举常量

    enum
    {
        HI_SOFTIRQ=0,
        TIMER_SOFTIRQ,
        NET_TX_SOFTIRQ,
        NET_RX_SOFTIRQ,
        BLOCK_SOFTIRQ,
        TASKLET_SOFTIRQ,
        SCHED_SOFTIRQ,
        HRTIMER_SOFTIRQ,
        RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
    
        NR_SOFTIRQS
    };

    (7)驱动程序所要做的工作是把软中断注册进去,而执行是do_softirq调用一个名为Ksoftirq的内核线程来完成。我们如果想要执行我们注册的软中断,还需要调用raise_softirq函数将我们想要执行的软中断处理程序挂起

    void raise_softirq(unsigned int nr)
    {
        unsigned long flags;
    
        local_irq_save(flags);
        raise_softirq_irqoff(nr);
        local_irq_restore(flags);
    }

    (8)相应类型的软中断被挂起之后,将会通过do_softirq得到执行

    asmlinkage void do_softirq(void)
    {
        __u32 pending;
        unsigned long flags;
    
        if (in_interrupt())
            return;
    
        local_irq_save(flags);
    
        pending = local_softirq_pending();
    
        if (pending)
            __do_softirq();
    
        local_irq_restore(flags);
    }

    (9)do_softirq调用函数__do_softirq来真正执行软中断

    asmlinkage void __do_softirq(void)
    {
        struct softirq_action *h;
        __u32 pending;
        int max_restart = MAX_SOFTIRQ_RESTART;
        int cpu;
    
        pending = local_softirq_pending();
        account_system_vtime(current);
    
        __local_bh_disable((unsigned long)__builtin_return_address(0));
        trace_softirq_enter();
    
        cpu = smp_processor_id();
    restart:
        /* Reset the pending bitmask before enabling irqs */
        set_softirq_pending(0);
    
        local_irq_enable();
    
        h = softirq_vec;
    
        do {
            if (pending & 1) {
                int prev_count = preempt_count();
    
                h->action(h);
    
                if (unlikely(prev_count != preempt_count())) {
                    printk(KERN_ERR "huh, entered softirq %td %p"
                           "with preempt_count %08x,"
                           " exited with %08x?
    ", h - softirq_vec,
                           h->action, prev_count, preempt_count());
                    preempt_count() = prev_count;
                }
    
                rcu_bh_qsctr_inc(cpu);
            }
            h++;
            pending >>= 1;
        } while (pending);
    
        local_irq_disable();
    
        pending = local_softirq_pending();
        if (pending && --max_restart)
            goto restart;
    
        if (pending)
            wakeup_softirqd();
    
        trace_softirq_exit();
    
        account_system_vtime(current);
        _local_bh_enable();
    }

    4. Linux内核tasklet分析

    (1)软中断是将操作推迟到将来某一个时刻执行的最有效的方法。由于该延迟机制处理复杂,多个处理器可以同时并且独立得处理(即do_softirq函数可以被多个CPU同时执行),并且一个软中断的处理程序可以在多个CPU上同时执行,因此处理程序必须要被设计为完全可重入和线程安全的。此外临界区必须用自旋锁保护。软中断因为这些原因显得太过于麻烦,因此引入tasklet机制。

    (2)tasklet是基于软中断实现的,确切的说应该是软中断的一个类型。所以根据软中断的性质,一个软中断类型对应一个软中断处理程序action。同理,也可以推出tasklet也会对应于一个唯一的action。

    (3)每一个CPU都会有自己独立的tasklet队列,虽然一个tasklet类型的软中断只对应一个action处理程序,但是我们可以在该处理程序中轮询执行一个tasklet队列,队列里面的每一个tasklet_struct都会对应一个tasklet处理函数,这样当我们的驱动程序中需要使用到tasklet的时候,只要往这个tasklet队列加入我们自定义的tasklet_struct对象就可以了。同时,由于每一个CPU都会有一个tasklet队列,并且每一个CPU只会执行自己tasklet队列里面的tasklet_struct对象,因此tasklet并不需要自旋锁的保护(当然这只能是对同一个tasklet而言,如果多个不同的tasklet需要使用同一资源的话,仍需要自旋锁的保护)。

    (4)Linux内核通过一个tasklet_struct结构体来描述一个tasklet对象,该结构体定义在includelinuxinterrupt.h文件中

    struct tasklet_struct
    {
        struct tasklet_struct *next;
        unsigned long state;
        atomic_t count;
        void (*func)(unsigned long);
        unsigned long data;
    };

    (5)tasklet的队列示意图

    (5)Linux内核时通过名为tasklet_vec的tasklet_head结构体来组织tasklet对象的

    struct tasklet_head
    {
        struct tasklet_struct *head;
        struct tasklet_struct **tail;
    };

      head:指向第一个tasklet_struct结构体的指针

      tail:指向tasklet队列最后一个tasklet_struct的next指针的地址

    (6)如果tasklet队列没有元素,两个指针的指向是这样的

    (7)向tasklet队列添加tasklet_struct对象,就是将最后一个tasklet_sturct的next指针指向新加的tasklet_struct对象,同时将列表头的tail指针的指针指向新加的tasklet_struct结构体的next指针的地址。代码如下

    tasklet_struct * t;
    * _get_cpu_var(tasklet_vec).tail = t;
    _get_cpu_var(tasklet_vec).tail = &(t->next);

    (8)tasklet类型的软中断唯一对应的action叫做tasklet_action(一般其他类型软中断的action都是由用户自己编写,但是tasklet不一样,Linux设计师已经帮我们实现了。所以也是因为这样,tasklet被广泛应用于驱动程序中)

    static void tasklet_action(struct softirq_action *a)
    {
        struct tasklet_struct *list;
    
        local_irq_disable();
        list = __get_cpu_var(tasklet_vec).head;
        __get_cpu_var(tasklet_vec).head = NULL;
        __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
        local_irq_enable();
    
        while (list) {
            struct tasklet_struct *t = list;
    
            list = list->next;
    
            if (tasklet_trylock(t)) {
                if (!atomic_read(&t->count)) {
                    if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
                        BUG();
                    t->func(t->data);
                    tasklet_unlock(t);
                    continue;
                }
                tasklet_unlock(t);
            }
    
            local_irq_disable();
            t->next = NULL;
            *__get_cpu_var(tasklet_vec).tail = t;
            __get_cpu_var(tasklet_vec).tail = &(t->next);
            __raise_softirq_irqoff(TASKLET_SOFTIRQ);
            local_irq_enable();
        }
    }

    (9)另一个tasklet非常重要的函数,就是tasklet_schedule,这个函数通常用于中断处理程序中,用于将tasklet_struct加入所在CPU的tasklet队列,同时将tasklet软中断挂起。
    因为我们知道,在中断的上半部中的irq_exit函数中,会激活do_softirq函数,所以在中断处理程序中使用tasklet_schedule函数就显得特别必要。tasklet_schedule源代码如下,

    static inline void tasklet_schedule(struct tasklet_struct *t)
    {
        if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
            __tasklet_schedule(t);
    }

    test_and_set_bit(TASKLET_STATE_SCHED, &t->state):这个函数的目的是设置t->state的第TASKLET_STATE_SCHED(0)位,并返回t->state的第TASKLET_STATE_SCHED位原来的值。

    (10)_tasklet_schedule函数:

    void __tasklet_schedule(struct tasklet_struct *t)
    {
        unsigned long flags;
    
        local_irq_save(flags);
        t->next = NULL;
        *__get_cpu_var(tasklet_vec).tail = t;
        __get_cpu_var(tasklet_vec).tail = &(t->next);
        raise_softirq_irqoff(TASKLET_SOFTIRQ);
        local_irq_restore(flags);
    }

    (11)我们驱动程序中若要使用tasklet,首先我们还必须要创建一个tasklet_struct对象,通常创建tasklet_struct对象一共有两种方式:
    ① 静态方式:

    #define DECLARE_TASKLET(name, func, data) 
    struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
    
    #define DECLARE_TASKLET_DISABLED(name, func, data) 
    struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

    ② 动态方式:

    static struct tasklet_struct my_tasklet;
    tasklet_init(&my_tasklet, tasklet_handler, 0); //count = 0,处于激活状态。

    tasklet_init源码

    void tasklet_init(struct tasklet_struct *t,
              void (*func)(unsigned long), unsigned long data)
    {
        t->next = NULL;
        t->state = 0;
        atomic_set(&t->count, 0);
        t->func = func;
        t->data = data;
    }

    然后,我们再调用tasklet_schedule函数将tasklet对象加入到tasklet队列中即可。

  • 相关阅读:
    Linux命令:head
    Linux命令:less
    分布式锁的实现(java)
    mysql大数据量使用limit分页,随着页码的增大,查询效率越低下。(转载)
    SpringBoot实现热加载方式
    报表设计细节
    Pentaho Report Designer 数据大于某值显示红色
    Centos7更改yum源与更新系统
    Centos7安装配置NFS服务和挂载
    centos7上搭建ftp服务器(亲测可用)
  • 原文地址:https://www.cnblogs.com/wulei0630/p/9506762.html
Copyright © 2011-2022 走看看