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