Linux内核软中断

Linux内核软中断

软中断(softirq)常常表示可延迟函数的所有种类,目前linux上使用的软中断个数是有限的,linux最多注册32个,目前使用了10个,在interrupt.h中定义,中断上下文:表示内核当前正在执行一个中断处理程序或者一个可延迟函数。软中断(即使同一类型的软中断)可以并发运行在多个CPU上,因此软中断是可重入函数必须使用自旋锁保护其数据结构。一个软中断不会去抢占另外一个软中断。执行软中断有专门的内核线程,每个处理器对应一个线程,名称ksoftirqd ( ps | grep ksoftirqd )

1.软中断和tasklet的区别

由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性:

  • 一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
  • 多个不同类型的tasklet可以并行在多个CPU上。
  • 软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)

2.数据结构

Linux内核用softirq_action结构管理软件中断的注册和激活

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

Linux内核支持的软件中断类型,可以在这里为内核添加自己的软中断

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

    NR_SOFTIRQS
};

多个软中断可以同时在多个cpu运行,就算是同一种软中断,也有可能同时在多个cpu上运行。内核为每个cpu都管理着一个待决软中断变量

typedef struct {
    unsigned int __softirq_pending;
} ____cacheline_aligned irq_cpustat_t;

在cpu的热插拔阶段,内核为每个cpu创建了一个用于执行软件中断的守护进程ksoftirqd,同时定义了一个per_cpu变量用于保存每个守护进程的task_struct结构指针

DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

内核的开发者们不建议我们擅自增加软件中断的数量,如果需要新的软件中断,尽可能把它们实现为基于软件中断的tasklet形式。与上面的枚举值相对应,内核定义了一个softirq_action的结构数组,每种软中断对应数组中的一项

//kernel/softirq.c
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

3.注册软中断函数

软中断处理程序执行的时候,允许响应中断,但自己不能休眠,如果软中断在执行的时候再次触发,则别的处理器可以同时执行,所以加锁很关键

extern void open_softirq(int nr, void (*action)(struct softirq_action *));

4.触发软中断的函数

extern void raise_softirq(unsigned int nr);

5.软中断的运行

软中断的执行既可以守护进程中执行,也可以在中断的退出阶段执行,更多的是在中断的退出阶段执行 ,以便达到更快的响应,加入守护进程机制,只是担心一旦有大量的软中断等待执行,会使得内核过长地留在中断上下文中

扫描二维码关注公众号,回复: 2524761 查看本文章
void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
    local_irq_disable();
#else
    WARN_ON_ONCE(!irqs_disabled());
#endif

    account_irq_exit_time(current);
    trace_hardirq_exit();
    sub_preempt_count(HARDIRQ_OFFSET);
    if (!in_interrupt() && local_softirq_pending())
        invoke_softirq();

    tick_irq_exit();
    rcu_irq_exit();
}

如果有待处理的软中断,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);
}

__do_softirq()中的Pending表示的待执行的软中断的位图。当执行软中断的时间大于MAX_SOFTIRQ_RESTART时候会调用某个专门线程来执行,防止watchdog timeout

#define MAX_SOFTIRQ_TIME  msecs_to_jiffies(2)
#define MAX_SOFTIRQ_RESTART 10

asmlinkage void __do_softirq(void)
{
    struct softirq_action *h;
    __u32 pending;
    unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
    int cpu;
    unsigned long old_flags = current->flags;
    int max_restart = MAX_SOFTIRQ_RESTART;

    /*
     * Mask out PF_MEMALLOC s current task context is borrowed for the
     * softirq. A softirq handled such as network RX might set PF_MEMALLOC
     * again if the socket is related to swap
     */
    current->flags &= ~PF_MEMALLOC;
    //获取pending的状态
    pending = local_softirq_pending();
    account_irq_enter_time(current);

    __local_bh_disable((unsigned long)__builtin_return_address(0),
                SOFTIRQ_OFFSET);
    //禁止软中断,主要是为了防止和软中断守护进程发生竞争
    lockdep_softirq_enter();

    cpu = smp_processor_id();
restart:
    //清除所有的软中断待决标志
    set_softirq_pending(0);
    //打开本地cpu中断
    local_irq_enable();

    h = softirq_vec;
    //循环执行待决软中断的回调函数
    do {
        if (pending & 1) {
            unsigned int vec_nr = h - softirq_vec;
            int prev_count = preempt_count();

            kstat_incr_softirqs_this_cpu(vec_nr);

            trace_softirq_entry(vec_nr);
            h->action(h);
            trace_softirq_exit(vec_nr);
            if (unlikely(prev_count != preempt_count())) {
                printk(KERN_ERR "huh, entered softirq %u %s %p"
                       "with preempt_count %08x,"
                       " exited with %08x?\n", vec_nr,
                       softirq_to_name[vec_nr], h->action,
                       prev_count, preempt_count());
                preempt_count() = prev_count;
            }

            rcu_bh_qs(cpu);
        }
        h++;
        pending >>= 1;
    } while (pending);

    local_irq_disable();

    pending = local_softirq_pending();
    //如果循环完毕,发现新的软中断被触发,则重新启动循环
    if (pending) {
        if (time_before(jiffies, end) && !need_resched() &&
            --max_restart)
            goto restart;

        wakeup_softirqd();
    }
    //恢复软中断
    lockdep_softirq_exit();

    account_irq_exit_time(current);
    __local_bh_enable(SOFTIRQ_OFFSET);
    tsk_restore_flags(current, old_flags, PF_MEMALLOC);
}

6.驱动实例

#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/sched.h>

#define err(msg)                printk(KERN_ERR "%s: " msg "\n", DRIVER_NAME)
#define __debug(fmt, arg...)     printk(KERN_INFO fmt, ##arg)

struct timer_list timer;

static void softirq_action_function(struct softirq_action *data)
{
    printk(KERN_INFO "=======softirq atcion ok!=======\n");
    return;
}

void softirq_test_timer(unsigned long arg)
{  
    __debug("==========softirq_test_timer==========\n");
    raise_softirq(TIMER_SOFTIRQ);
}

static int __init softirq_test_init(void)
{
    init_timer(&timer);
    timer.function = softirq_test_timer;

    add_timer(&timer);
    mod_timer(&timer, jiffies + HZ * 3);

    open_softirq(TIMER_SOFTIRQ, softirq_action_function);

    __debug("\n===================softirq_test_init====================\n");
    return 0;
}

static void __exit softirq_test_exit(void)
{
    del_timer(&timer);

    __debug("\n===================softirq_test_exit====================\n");
    return;
}

module_init(softirq_test_init);
module_exit(softirq_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyy");

7.软中断编译问题

在编译软中断驱动的时候出现以下错误

WARNING: "open_softirq" [/home/wyy/nfsroot/test_test_test/test.ko] undefined!
WARNING: "raise_softirq" [/home/wyy/nfsroot/test_test_test/test.ko] undefined!

解决方法

//kernel\softirq.c
修改内核,使用EXPORT_SYMBOL宏导出open_softirq和raise_softirq函数

8.添加软中断

//如果需要为内核添加一个软中断,在kernel\softirq.c枚举变量中添加一个
enum
{
    HI_SOFTIRQ=0,
    ...
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

    MY_SOFTIRQ,     //添加一个自己的SOFTIRQ

    NR_SOFTIRQS
};

猜你喜欢

转载自blog.csdn.net/wyy626562203/article/details/81297683