Linux Kernel Driver 中断 之 Tasklet & 工作队列

前言


在 Linux 内核中,中断不属于任何进程,不参与进程的调度。如果中断的处理函数,长时间占用CPU,那么必将导致整个系统的并发和响应能力受到影响。

所以:内核要求中断处理函数的执行速度要足够快,更不能进行休眠(注意是休眠,不是阻塞,操作系统底层的阻塞有可能是休眠实现,也有可能是CPU空转实现(类似 for (i = 0; i < 1024*1024*1024:i++);再比如spin_lock自旋锁,也会阻塞,但是可以用于中断上下文,因为自旋锁是CPU轮转实现的阻塞,而不是休眠阻塞)。但是,实际编程时,中断服务程序中总有一些耗时操作,那么内核针对这些是如何优化的呢?

Top half - Bottom half 机制

针对中断需要快速响应并处理的需求,内核提供了 Top half - Bottom half 机制。

Top half 特点

1. 本质仍旧是中断处理函数

2. 执行原中断处理函数中紧急、耗时较短的操作

3. 该部分,不能被中断

4. 注册Bottom half 中需要的操作,以便CPU将来执行

Bottom half 特点

1. 本质是延迟执行的一种手段,并非要同 Top half 一起使用,Bottom half 底半部可以单独使用。

2. 如果有中断,那么执行该中断处理函数中的不紧急、耗时较长的操作。

3. 可被别的任务中断

4. CPU根据自身的资源分配调度情况,去执行 Bottom half 中的任务。

5. 至于何时注册,没有强制要求,一旦被注册,CPU资源可被获取时,该注册的Bottom half 中的任务,便可被执行。

6. Bottom half 中的实现方式,实际有三种:

tasklet,工作队列,软中断

tasklet

tasklet 本质仅仅是延迟执行。

特点

1. tasklet本身是基于软中断实现的。

2. 延后执行的操作都被放在tasklet的一个函数中,此函数又称为延后处理函数。

3. tasklet 的延后处理函数工作在中断上下文中,所以不能进行休眠操作。

介绍

实际是一个单链表。

其中 func 域就是延后处理函数。

过程

1. 定义并初始化一个tasklet对象

或者:

2. 延后处理函数的实现

3. 适当的地方注册/登记延后处理函数,其实就是找个合适的时机,调用 tasklet_schedule() 函数

注意:schedule 是一个 ont shot 的注册方式。

 接下来,我们用hello world的driver 试一试。

代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>

MODULE_DESCRIPTION("Frocheng: Driver for DEMO!");
MODULE_AUTHOR("Frodo Cheng");
MODULE_LICENSE("GPL");
MODULE_VERSION("V0.0.1");

#define HELLO_IRQ   (36U)

static char* dev = "hello";

static struct tasklet_struct tl;

void tasklet_handle_cb(unsigned long data)
{
    printk("===[frocheng]===[%s]===[%s]===[%d]===[data: %lu]===\n",__FILE__, __func__, __LINE__, data);
}


static irqreturn_t irq_handle_cb(int irq, void * arg)
{
    tasklet_init(&tl, tasklet_handle_cb, (unsigned long)irq);
    tasklet_schedule(&tl);
    return IRQ_NONE;
}

static int __init hello_init(void)
{
    printk("===[frocheng]===[%s]===[%s]===[%d]===[Hello !]===\n",__FILE__, __func__, __LINE__);
    irqreturn_t ret;
    ret = request_irq(HELLO_IRQ, irq_handle_cb, IRQF_SHARED, "hello", (void*)dev);
    return 0;
}

static void __exit hello_exit(void)
{
    free_irq(HELLO_IRQ, (void*)dev);
    printk("===[frocheng]===[%s]===[%s]===[%d]===[Bye bye...]===\n",__FILE__, __func__, __LINE__);
}

module_init(hello_init);
module_exit(hello_exit);

结果

tasklet的延后处理函数,仍旧是在中断上下文中执行的,所以,prink的在其中,仍旧没有达到优化的效果。

工作队列

1. 工作队列,所谓的工作,也就是需要延后执行的操作

2. 工作队列也是延后执行的一个手段

3. 工作队列中的每一个节点代表需要延后执行的任务,每放入一个函数,此函数即工作队列的延后处理函数

4. 工作队列中的每一个节点对应的延后处理函数,都工作在进程上下文中,可以进行休眠操作

5. 工作队列就是为了解决tasklet的延后处理函数不能进行休眠的问题

6. 工作队列的延后处理函数效率,不如tasklet

7. 工作队列可采用内核默认的队列,自行创建节点即可

节点结构

可以知道工作队列的节点是 work_struct 结构,工作队列自身实际是一个基于链表实现的。

1. 初始化

2. 延后处理函数的实现

3. 登记/注册

该函数,把该工作节点放到了内核默认的工作队列中去了。当然还有其他的工作队列,可以参考linux源码,此处略。

试验代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/smp.h>

MODULE_DESCRIPTION("Frocheng: Driver for DEMO!");
MODULE_AUTHOR("Frodo Cheng");
MODULE_LICENSE("GPL");
MODULE_VERSION("V0.0.1");

#define HELLO_IRQ   (36U)

static char* dev = "hello";

static struct work_struct w;

void wq_handle_cb(struct work_struct* w)
{
    printk("===[frocheng]===[%s]===[%s]===[%d]===\n",__FILE__, __func__, __LINE__);
}

static irqreturn_t irq_handle_cb(int irq, void * arg)
{
    INIT_WORK(&w,wq_handle_cb);
	//schedule_work_on(smp_processor_id(), &w);
	schedule_work(&w);
    return IRQ_NONE;
}

static int __init hello_init(void)
{
    printk("===[frocheng]===[%s]===[%s]===[%d]===[Hello !]===\n",__FILE__, __func__, __LINE__);
    irqreturn_t ret;
    ret = request_irq(HELLO_IRQ, irq_handle_cb, IRQF_SHARED, "hello", (void*)dev);
    return 0;
}

static void __exit hello_exit(void)
{
    free_irq(HELLO_IRQ, (void*)dev);
    printk("===[frocheng]===[%s]===[%s]===[%d]===[Bye bye...]===\n",__FILE__, __func__, __LINE__);
}

module_init(hello_init);
module_exit(hello_exit);

结果

 软中断

由于软中断不能采用 动态 加载以及卸载,所以暂且无法进行试验。此处仅仅说明一下其特点:

1. 软中断本质也是延迟处理函数

2. 可运行在多个CPU上(同时,并行执行(不是并发))

3. 由于可并发执行在多个CPU上,所以要求软中断的延迟处理函数必须具备可重入性。

函数可重入性说明:

a. 尽量避免访问全局变量、全局资源

b. 如果需要访问全局,需要进行互斥访问,此时会大大降低代码执行效率

4. tasklet 的延迟处理函数基于软中断实现,但是其延迟处理函数仅能运行在某一个CPU之上,这样其避免了竞态

5. 基于第二以及第四点,软中断相比tasklet,总体执行效率要高

6. 软中断不能采用 insmod rmmod的方式进行加载卸载,只能静态编译进内核 Image,无形之中加大了代码的实现复杂度

后续详细讨论软中断的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值