• Linux中断底半部机制


    参考:

    Linux下半部处理之软中断 

    linux中断底半部机制   

    《深入理解Linux内核》软中断/tasklet/工作队列

    软中断和tasklet介绍

    详解操作系统中断

    Linux内核:中断、软中断、tasklet

    为了提高系统的响应能力和并发能力,Linux将中断处理分了上半部和下半部。当一个中断产生,调用该中断对应的处理程序(上半部),然后告诉系统,对应的后半部可以执行了,中断处理程序立即返回,下半部会在合适的时机由操作系统调用。这样一来就大大的减少了中断处理所需要的时间。

    中断上半部处理函数就是Linux中断体系结构中介绍的request_irq中注册的irq_handler_t类型的函数。

    int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
    一般地,有如下特征的任务放在上半部
    1、对时间非常敏感
    2、与硬件相关的
    3、不能被其他中断打断的工作
    以上三点之外的,考虑放在下半部。
    中断底半部实现的机制有:
    1.tasklet
    2.工作队列work queue
    3.软中断softirq
    其中tasklet由软中断实现。
     
    软中断实现机制

    软中断可以使内核延期执行某个任务,他们的运作方式和具体的硬件类似,甚至可以说这里就是模拟的硬件中断,所以称之为软件中断也不为过。既然提到软中断,那么自然就设计到几个点:

    • 软中断的注册
    • 软中断的触发
    • 软中断的处理

    内核版本中定义了10个软中断,并且系统不建议用户自己添加软中断,所以对于软中断基本用于已定义好的功用,而如果用户需要,可以使用其中的一个类型即TASKLET_SOFTIRQ

    具体的软中断类型如下:

    复制代码
     1 enum
     2 {
     3     HI_SOFTIRQ=0,
     4     TIMER_SOFTIRQ,
     5     NET_TX_SOFTIRQ,
     6     NET_RX_SOFTIRQ,
     7     BLOCK_SOFTIRQ,
     8     BLOCK_IOPOLL_SOFTIRQ,
     9     TASKLET_SOFTIRQ,
    10     SCHED_SOFTIRQ,
    11     HRTIMER_SOFTIRQ,
    12     RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
    13 
    14     NR_SOFTIRQS
    15 };
    复制代码

     每个CPU维护一个软中断位图__softirq_pending,其实是一个32位的字段,每一位对应一个软中断。处理软中断时会获取当前CPU的软中断位图,根据各个位的设置,进行处理。

    typedef struct {
        unsigned int __softirq_pending;
        unsigned int local_timer_irqs;
    } ____cacheline_aligned irq_cpustat_t;
    irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
    
    #define __IRQ_STAT(cpu, member)    (irq_stat[cpu].member)
    /* arch independent irq_stat fields */
    #define local_softirq_pending() 
        __IRQ_STAT(smp_processor_id(), __softirq_pending)

    1、软中断的注册

    软中断的核心机制是一张表,类似于IDT,包含32个softirq_vec结构,该结构很简单:就是一个函数地址,每个软中断对应其中的一个,所以现在也仅仅使用前10项。

    struct softirq_action
    {
        void    (*action)(struct softirq_action *);
        void    *data;
    };
    static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;

    系统通过open_softirq函数注册一个软中断,具体就是在softirq_vec数组中根据中断号设置其对应的处理例程。

    softirq_init中初始化了TASKLET相关的处理函数。

    void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
    {
        softirq_vec[nr].data = data;
        softirq_vec[nr].action = action;
    }
    void __init softirq_init(void)
    {
        open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
        open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
    }

    nr是上面的一个枚举值,action便是对应软中断的处理函数。软中段执行时taskelet_action是执行的入口函数。

     1 static void tasklet_action(struct softirq_action *a)
     2 {
     3     struct tasklet_struct *list;
     4 
     5     local_irq_disable();
     6     list = __get_cpu_var(tasklet_vec).list;
     7     __get_cpu_var(tasklet_vec).list = NULL;
     8     local_irq_enable();
     9 
    10     while (list) {
    11         struct tasklet_struct *t = list;
    12 
    13         list = list->next;
    14 
    15         if (tasklet_trylock(t)) {
    16             if (!atomic_read(&t->count)) {
    17                 if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
    18                     BUG();
    19                 t->func(t->data);
    20                 tasklet_unlock(t);
    21                 continue;
    22             }
    23             tasklet_unlock(t);
    24         }
    25 
    26         local_irq_disable();
    27         t->next = __get_cpu_var(tasklet_vec).list;
    28         __get_cpu_var(tasklet_vec).list = t;
    29         __raise_softirq_irqoff(TASKLET_SOFTIRQ);
    30         local_irq_enable();
    31     }
    32 }
    static void tasklet_action(struct softirq_action *a)

    Linux系统通过raise_softirq函数引发一个软中断,每个CPU有个软中断位图,有32位,最多可对应32个软中断,当置位图对应位为1时,表明触发了对应的软中断。在下次系统检查是否有软中断时就会被检测得到,从而进行处理。

    复制代码
    1 void raise_softirq(unsigned int nr)
    2 {
    3     unsigned long flags;
    4 
    5     local_irq_save(flags);
    6     raise_softirq_irqoff(nr);
    7     local_irq_restore(flags);
    8 }
    复制代码

    对于tasklet,在tasklet_schedule()函数中会调用到raise_softirq。

    static inline void tasklet_schedule(struct tasklet_struct *t)
    {
    if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
    __tasklet_schedule(t);
    }
    void fastcall __tasklet_schedule(struct tasklet_struct *t)
        -->struct tasklet_head per_cpu_tasklet_vec.list = t; //加入链表
        -->raise_softirq_irqoff(TASKLET_SOFTIRQ);
            -->__raise_softirq_irqoff(nr);
                -->or_softirq_pending(1UL << (nr));
                    -->local_softirq_pending() |= (1UL << (nr))//即struct irq_cpustat_t irq_stat[0].__softirq_pending |= (1UL << (nr)

    处理时机:

    软中断大概在三个地方会被检测是否存在,如果存在会进行处理:

    • 从一个硬件中断返回时
    • 在ksoftirqd内核线程中
    • 在那些显式检查和执行待处理的软中断的代码中

    中断上下文:CPU处于处理中断上半部或者下半部,内核用in_interrupt来判断是否处于中断上下文。这是一个宏:

    #define in_interrupt() (irq_count())

    #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))

    可以看到这里中断上下文包括硬件中断、软件中断、NMI中断。说到这里,出现了一个preempt_count(),LInux为每个进程的thread_info结构中维护了一个preempt_count字段,该字段是int型,因此有32位,用于支持内核抢占。当该字段为0的时候,表示当前允许内核抢占,否则不可以。具体请参考另一篇博文:Linux中的进程调度

    处理过程:以从一个硬件中断返回时的情景为例分析,这也是tasklet的使用情景。

    软中断的处理核心都在do_softirq函数。

    asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
        -->irq_exit();//硬件中断返回时检测软中断
            -->invoke_softirq();
                -->asmlinkage void __do_softirq(void)
                    -->pending = local_softirq_pending();//获取挂起位
                        h = softirq_vec;//依次执行挂起位对应的处理函数
                        do {
                            if (pending & 1) {
                                h->action(h);
                                rcu_bh_qsctr_inc(cpu);
                            }
                            h++;
                            pending >>= 1;
                        } while (pending);

    对于tasklet的软中断处理函数执行过程解析如下:

    struct tasklet_struct
    {
        struct tasklet_struct *next;
        unsigned long state;
        atomic_t count;
        void (*func)(unsigned long);
        unsigned long data;
    };                    
    static void tasklet_action(struct softirq_action *a)
        struct tasklet_struct *list = __get_cpu_var(tasklet_vec).list //查找__tasklet_schedule(struct tasklet_struct *t)时加入的链表
            -->遍历链表,执行每一个tasklet_struct结构中的func函数

    为什么要使用工作队列work queue?(work queue和软中断的区别)
    上面我们介绍的可延迟函数运行在中断上下文中(软中断的一个检查点就是do_IRQ退出的时候),于是导致了一些问题:软中断不能睡眠、不能阻塞。由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。
    因此在2.6版的内核中出现了在内核态运行的工作队列(替代了2.4内核中的任务队列)。它也具有一些可延迟函数的特点(需要被激活和延后执行),但是能够能够在不同的进程间切换,以完成不同的工作。

     关于工作队列的实现机制,参考Linux工作队列实现机制  Linux内核:工作队列

  • 相关阅读:
    Linux 环境下搭建单机版 Redis
    为什么要同时重写equals和hashcode方法
    使用ThreadPoolExecutor 创建线程池,完成并行操作
    一个简单的通过newFixedThreadPool实现多线程案例
    Java 获取对象的所有属性及其对应的值
    分布式id生成方案总结
    使用idea和gradle编译spring5源码
    Pandas中常用的函数使用
    单链表的反转 python实现实例
    kaggle注册中的坑---2018,12.3试过有效
  • 原文地址:https://www.cnblogs.com/yangjiguang/p/7636049.html
Copyright © 2020-2023  润新知