linux kernel中断处理

一、什么是下半部

中断是一个很霸道的东西,处理器一旦接收到中断,就会打断正在执行的代码,调用中断处理函数。如果在中断处理函数中没有禁止中断,该中断处理函数执行过程中仍有可能被其他中断打断。出于这样的原因,大家都希望中断处理函数执行得越快越好。
另外,中断上下文中不能阻塞,这也限制了中断上下文中能干的事。
基于上面的原因,内核将整个的中断处理流程分为了上半部和下半部。上半部就是之前所说的中断处理函数,它能最快的响应中断,并且做一些必须在中断响应之后马上要做的事情。而一些需要在中断处理函数后继续执行的操作,内核建议把它放在下半部执行。
拿网卡来举例,在linux内核中,当网卡一旦接受到数据,网卡会通过中断告诉内核处理数据,内核会在网卡中断处理函数(上半部)执行一些网卡硬件的必要设置,因为这是在中断响应后急切要干的事情。接着,内核调用对应的下半部函数来处理网卡接收到的数据,因为数据处理没必要在中断处理函数里面马上执行,可以将中断让出来做更紧迫的事情。

可以有三种方法来实现下半部:软中断、tasklet和等待队列。

二、软中断

软中断一般很少用于实现下半部,但tasklet是通过软中断实现的,所以先介绍软中断。字面理解,软中断就是软件实现的异步中断,它的优先级比硬中断低,但比普通进程优先级高,同时,它和硬中断一样不能休眠。

软中断是在编译时候静态分配的,要用软中断必须修改内核代码。

在kernel/softirq.c中有这样的一个数组:

 static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

内核通过一个softirq_action数组来维护的软中断,NR_SOFTIRQS是当前软中断的个数,待会再看他在哪里定义。

先看一下softirq_action结构体:

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

结构体里面就一个软中断函数,他的参数就是本身结构体的指针。之所以这样设计,是为了以后的拓展,如果在结构体中添加了新成员,也不需要修改函数接口。在以前的内核,该结构体里面还有一个data的成员,用于传参,不过现在没有了。

接下来看一下如何使用软中断实现下半部
一、要使用软中断,首先就要静态声明软中断:

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
};

上面通过枚举定义了NR_SOFTIRQS(10)个软中断的索引号,优先级最高是0(HI_SOFTIRQ

二、定义了索引号后,还要注册处理程序。
通过函数open_softirq来注册软中断处理函数,使软中断索引号与中断处理函数对应。该函数在kernel/softirq.c中定义:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
    /* softirq_vec是个struct softirq_action类型的数组 */
    softirq_vec[nr].action = action;
}

 系统一般使用open_softirq()函数进行软中断描述符的初始化,主要就是将action函数指针指向该软中断应该执行的函数。在tart_kernel()进行系统初始化中,就调用了softirq_init()函数对HI_SOFTIRQTASKLET_SOFTIRQ两个软中断进行了初始化

void __init softirq_init(void)
{
    int cpu;

    for_each_possible_cpu(cpu) {
        per_cpu(tasklet_vec, cpu).tail =
            &per_cpu(tasklet_vec, cpu).head;
        per_cpu(tasklet_hi_vec, cpu).tail =
            &per_cpu(tasklet_hi_vec, cpu).head;
    }

    /* 开启常规tasklet */
    open_softirq(TASKLET_SOFTIRQ, tasklet_action);
    /* 开启高优先级tasklet */
    open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}


/* 开启软中断 */
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
    softirq_vec[nr].action = action;
}

可以看到,TASKLET_SOFTIRQ的action操作使用了tasklet_action()函数,HI_SOFTIRQ的action操作使用了tasklet_hi_action()函数,这两个函数我们需要结合tasklet进行说明。我们也可以看看其他的软中断使用了什么函数:

    open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action);
    open_softirq(BLOCK_SOFTIRQ, blk_done_softirq);
    open_softirq(BLOCK_IOPOLL_SOFTIRQ, blk_iopoll_softirq);
    open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);
    open_softirq(HRTIMER_SOFTIRQ, run_hrtimer_softirq);
    open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);

除了TASKLET_SOFTIRQHI_SOFTIRQ,其他的软中断更多地是用于特定的设备和环境,对于我们普通的IO驱动和设备而已,使用的软中断几乎都是TASKLET_SOFTIRQHI_SOFTIRQ,而系统为了对这些不同IO设备进行统一的处理,就在TASKLET_SOFTIRQHI_SOFTIRQ的action函数中使用到了tasklet。

  对于每个CPU,都有一个irq_cpustat_t的数据结构,里面有一个__softirq_pending变量,这个变量很重要,用于表示该CPU的哪个软中断处于挂起状态,在软中断处理时可以根据此值跳过不需要处理的软中断,直接处理需要处理的软中断。内核使用local_softirq_pending()获取此CPU的__softirq_pending的值。

  当使用open_softirq设置好某个软中断的action指针后,该软中断就会开始可以使用了,其实更明了地说,从中断初始化完成开始,即使所有的软中断都没有使用open_softirq()进行初始化,软中断都已经开始使用了,只是所有软中断的action都为空,系统每次执行到软中断都没有软中断需要执行罢了。

在每个CPU上一次软中断处理的一个典型流程是:

1, 硬中断执行完毕,开中断。
2, 检查该CPU是否处于嵌套中断的情况,如果处于嵌套中,则不执行软中断,也就是在最外层中断才执行软中断。
3, 执行软中断,设置一个软中断执行最多使用时间和循环次数(10次)。
4, 进入循环,获取CPU的__

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值