先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
一 why
前面的博文中,我们已经讨论过了中断,以及中断的实现方式。那么何为中断下半部呢?既然有下半部,那肯定有上半部,那么上半部又是什么意思?
我们知道,当发生中断时,CPU会被抢断,当前正在执行的线程会被打断,CPU会跳转到中断服务程序中执行,如下图所示
那么为什么需要中断下半部呢?
我们知道,中断的一个特点是,会抢断CPU,造成其他进程无法执行,如果一个中断处理程序执行时间很长,会带来问题。比如一个线程申请了一把锁,此时来了一个中断,线程被打断,线程不会及时释放这把锁,但是由于中断时间很长,该线程长时间占着这把锁,没有及时释放,而其他线程又需要申请这把锁,导致其他线程长时间申请不到锁的资源,该线程的执行就会出现问题。
那么linux就引入了下半部,去解决这个问题,将中断中处理耗时比较长的部分延后,放到了下半部去处理。
二 what
中断安下半部的实现机制有三种,分别是
1. softirq: 处理比较快,它是内核级别的机制,如果需要这个机制需要修改整个内核源码
不推荐也不常用
2. tasklet: 内部实现实际上调用了softirq
3. workqueue: 工作队列
今天,我们先看看tasklet的实现方式,如何在驱动中通过tasklet实现下半部处理
三 how
对驱动程序来说,我们要做的主要就是构建一个tasklet的结构体,并且把它注册到内核中,那么它何时才能被调度,完全是由内核自己的调度节奏决定,我们的驱动并不能决定tasklet中的方法何时被执行
a. 初始化
根据结构体可知,我们最主要要实现的就是tasklet的实现方法函数定义
// 主要的一个结构体
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long); //下半部的实现方法
unsigned long data; // 传递给方法 func
};
//初始化
tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)
b. 启动
//启动下半部tasklet,注意这个启动函数必须是在中断上半部中被调用
tasklet_schedule(struct tasklet_struct * t)
c. 销毁
//一般地有初始化,就会有销毁,一般是在模块卸载exit中调用
tasklet_kill(struct tasklet_struct * t)
一般步骤
1. 驱动中先注册一个中断
2. 注册中断的时候,同时初始化中断下半部
3. 实现中断处理函数,在中断处理程序中,启动下半部
4. 实现下半部处理函数
一般框架如下
static int __init key_drv_init(void)
{
int ret = 0;
//1. 设定一个全局的设备对象
key_dev = (key_desc_t *)kzalloc(sizeof(key_desc_t), GFP_KERNEL);
if (key_dev == NULL) {
printk("kzalloc failure\n");
return -EINVAL;
}
......
//4. 注册中断
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key3_exit10", NULL);
// 初始化中断下半部tasklet
tasklet_init(&key_dev->mytasklet, key_irq_bottom_half_tasklet, 1);
return 0;
}
中断处理函数
irqreturn_t key_irq_handler(int irq_no, void *devid)
{
printk("-----------------\n");
//启动中断下半部开始调度
tasklet_schedule(key_dev->mytasklet);
......
return IRQ_HANDLED;
}
中断下半部处理函数
void key_irq_bottom_half_tasklet(unsigned long data)
{
......
}
注销中断下半部
static void __exit key_drv_exit(void)
{
printk(KERN_INFO "new exit chardev0\n");
tasklet_kill(&key_dev->mytasklet);
......
}