linux驱动____中断底半部
来源:互联网 发布:大数据的本质是 编辑:程序博客网 时间:2024/06/16 19:12
前言
在linux老版本的内核(2.6之前),系统中断分为顶半部和底半部。其实这不是linux的专利,很多现代操作系统都有类似的方法来平衡快速响应和执行复杂任务的关系。早期的有bottom half(BH)和task queue来实现底半部机制,但在遇到要求性能较高的子系统(如网络部分),会导致系统性能严重下降,基于此,这2个从Linux内核中去除了,所以就不谈了。于是软中断(softirq)、tasklet和工作队列(work queue)这3个新贵粉墨登场,其实在我当前Android 4.x上的内核(linux 3.x)碰见最多的是work queue,所以可用会重点在这了。
softirq和tasklet很早就引入linux内核(Linux 2.3开始)的底半部机制。软中断是一组静态(即编译时创建的)定义的底半部接口,是Linux系统全局的,最多可挂32个底半部程序,可在多个CPU同时执行,类型相同也可以执行,而tasklet类型相同不可以执行。与软中断相关的函数只能和linux内核一起编译,不能以模块形式(.ko)编译,否则会出现open_softirq、raise_softirq等函数未定义的编译错误。tasklet和内核定时器都是基于软中断的,softirq在驱动程序中使用的较少,目前只有少数的设备如网络设备、块设备在使用。tasklet,也叫小任务,和软中断类似,基于软中断。需要在中断处理程序(顶半部)中注册,softirq和tasklet会在适当的时候唤醒ksoftirqd/n进行处理。
工作队列(work queue)是另一种将工作推后执行的底半部机制,和前2种有所不同。工作队列推后的工作交给内核线程处理,是进程上下文,不是中断上下文,这样就可以享受进程上下文的好处,可以sleep的,而softirq和tasklet不可以休眠,否则会系统崩溃。
相关文件:
kernel/include/linux/workqueue.h
Kernel/kernel/workqueue.c
基本的工作work_struct和workqueue_struct结构如下
struct work_struct {atomic_long_t data;//传给工作队列处理函数的数据struct list_head entry;//工作队列链表头指针work_func_t func;//工作队列的处理函数#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;#endif};
struct delayed_work { struct work_struct work; struct timer_list timer;};
<p>/* * The externally visible workqueue abstraction is an array of * per-CPU workqueues: */struct workqueue_struct { unsigned int flags; /* W: WQ_* flags */ union { struct cpu_workqueue_struct __percpu *pcpu;//SMP使用 struct cpu_workqueue_struct *single;//单CPU使用 unsigned long v; } cpu_wq; /* I: cwq's */ struct list_head list; /* W: list of all workqueues */ //指向所有工作队列列表</p><p> struct mutex flush_mutex; /* protects wq flushing */ int work_color; /* F: current work color */ int flush_color; /* F: current flush color */ atomic_t nr_cwqs_to_flush; /* flush in progress */ struct wq_flusher *first_flusher; /* F: first flusher */ struct list_head flusher_queue; /* F: flush waiters */ struct list_head flusher_overflow; /* F: flush overflow list */</p><p> mayday_mask_t mayday_mask; /* cpus requesting rescue */ struct worker *rescuer; /* I: rescue worker */</p><p> int nr_drainers; /* W: drain in progress */ int saved_max_active; /* W: saved cwq max_active */#ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map;#endif char name[]; /* I: workqueue name */};</p>
工作队列基本原理:
Linux内核会为每个处理器创建一个线程(由worker_thread()创建),用来处理工作。此外内核还会创建一个全局的工作队列(system_wq)。如果工作不紧急就直接挂在system_wq上,当然内核允许我们创建新的工作线程和队列,接下来动态创建会说明。
work-queue可以静态创建(使用系统工作队列)和动态创建(自定义工作队列)。
静态创建:
1. 定义处理函数
typedef void (*work_func_t)(struct work_struct *work);
2. 定义初始化work_struct变量
可以用DECLARE_WORK宏,也可以直接对work_struct结构体直接赋值,不过比较烦,直接用宏吧,输入name和第1步的function即可。
或者先定义work_struct变量再调用INIT_WORK。
#define DECLARE_WORK(n, f)\<span style="white-space:pre"></span>//静态创建,输入work_struct变量名和处理函数struct work_struct n = __WORK_INITIALIZER(n, f)#define DECLARE_DELAYED_WORK(n, f)\struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f)
INIT_WORK(_work, _func)<span style="white-space:pre"></span>//动态创建,先定义一个work_struct变量,再输入这个变量和处理函数INIT_DELAYED_WORK(_work, _func)
3. 工作进行调度
调用就是将work_struct添加到work_queue中,这个工作由schedule_work函数完成。只要传入第2步work_struct变量即可链入系统工作队列system_wq。如果已经被调度过了返回0,否则返回非0。每个CPU都有一个workqueue,schedule_work()是将work_struct添加到当前的CPU的workqueue上。
/** * schedule_work - put work task in global workqueue * @work: job to be done * * Returns zero if @work was already on the kernel-global workqueue and * non-zero otherwise. * * This puts a job in the kernel-global workqueue if it was not already * queued and leaves it in the same position on the kernel-global * workqueue otherwise. */int schedule_work(struct work_struct *work){return queue_work(system_wq, work);}
动态创建:
1. 定义并创建workquque
先定义一个workqueue_struct指针,使用create_workqueue(name)宏创建新的工作队列,并返回队列结构体指针。如下,可见create_workqueue宏调用alloc_workqueue宏,最后调用_alloc_workqueue(),这个函数中先创建一个workqueue_struct,然后为每个CPU创建一个cpu_workqueue_struct,然后cpu_workqueue_struct.wq都指向workqueue_struct,最后创建1个工作线程。
#define alloc_workqueue(fmt, flags, max_active, args...)\__alloc_workqueue_key((fmt), (flags), (max_active),\ NULL, NULL, ##args)struct workqueue_struct *__alloc_workqueue_key(const char *fmt, unsigned int flags, int max_active, struct lock_class_key *key, const char *lock_name, ...) #define create_workqueue(name)\alloc_workqueue((name), WQ_MEM_RECLAIM, 1)
创建工作队列有2个函数create_workqueue()和create_siglethread_workqueue(),他们的区别见下图
2. 定义处理函数
同静态创建第1步
3. 定义工作节点work_struct
同静态创建第2步
4. 初始化工作节点
实际上就是把第2步的处理函数放在第3步的work_struct节点上。
#define INIT_WORK(_work, _func)\do {\__INIT_WORK((_work), (_func), 0);\} while (0)#define INIT_DELAYED_WORK(_work, _func)\do {\INIT_WORK(&(_work)->work, (_func));\init_timer(&(_work)->timer);\} while (0)
5. 将工作节点链入工作队列
将当前工作节点(work_struct)添加到当前CPU的工作队列(workqueue_struct)中,如果想要指定放到其他CPU工作队列,可以使用queue_work_on()。
/** * queue_work - queue work on a workqueue * @wq: workqueue to use * @work: work to queue * * Returns 0 if @work was already on a queue, non-zero otherwise. * * We queue the work to the CPU on which it was submitted, but if the CPU dies * it can be processed by another CPU. */int queue_work(struct workqueue_struct *wq, struct work_struct *work){int ret;ret = queue_work_on(get_cpu(), wq, work);//这个函数参数1也可以指定用哪个CPU,序号从0开始put_cpu();return ret;}
6. 清理
一般Linux驱动卸载时,要销毁destroy_workqueue()新创建的工作队列,但销毁前要先保证所有工作队列中的工作都完成,使用flush_workqueue()完成此工作。
当调度工作时,执行一次就结束了,如果需要再调度,则再次queue_work,取消调度时用
void flush_workqueue(struct workqueue_struct *wq);//确保队列中的工作都完成了void destroy_workqueue(struct workqueue_struct *wq);//销毁工作队列
API列表:
以下函数中,含有schedule关键字的表示与系统工作队列system_wq关联;含有on的关键字的表示与指定CPU/n上的工作队列关联,否则是当前cpu。
API
类型
描 述
DECLARE_WORK(name, fun)
宏
定义和初始化work_struct结构体变量
INIT_WORK(work, fun)
宏
初始化work_struct结构体变量
intschedule_work(struct work_struct *work)
函数
将当前工作添加到系统创建的工作队列中(调度工作)
workqueue_struct*create_workqueue(name)
宏
创建一个新的工作队列
create_singlethread_workqueue(name)
宏
创建一个不与任何CPU绑定的工作线程。也就是说workqueue_struct.flags包含WQ_UNBOUND
intqueue_work(struct
workqueue_struct *wq, struct work_struct *work)
函数
将一个工作添加到当前CPU的wq指定的工作队列中
intqueue_work_on(int cpu, struct
workqueue_struct *wq, struct work_struct *work)
函数
将一个工作添加到指定CPU的wq指定的工作队列中
voidflush_workqueue(struct workqueue_struct *wq)
函数
等待wq指向的工作队列中所有的
工作节点都处理完才返回
voidflush_scheduled_work(void)
函数
等待系统工作队列中所有的
工作节点都处理完才返回
voiddestroy_workqueue(struct workqueue_struct *wq)
函数
销毁wq指向的工作队列
intqueue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay)
函数
延迟delay个时钟周期(tick)后将dwork指向的工作添加到wq指向工作队列中。
intschedule_delayed_work(struct
delayed_work *dwork, unsigned long delay)
函数
延迟delay个时钟周期(tick)后将dwork添加到系统创建的工作队列中
int cancel_delayed_work_sync(struct delayed_work *work)
函数
取消正在等待添加到工作队列的work
示例:
来源于linux 3.4中的cm36283光强/距离传感器。
这个传感器中存在中断方式和poll方式,并且都是使用工作队列,前者使用使用普通的及时工作队列,后者使用delayed工作队列。
中断方式示例:
/*定义work_struct变量及处理函数*/static DECLARE_WORK(sensor_irq_work, sensor_irq_do_work);/*work_struct的处理函数*/static void sensor_irq_do_work(struct work_struct *work){struct cm36283_info *lpi = lp_info;uint16_t intFlag;_cm36283_I2C_Read_Word(lpi->slave_addr, INT_FLAG, &intFlag);control_and_report(lpi, CONTROL_INT_ISR_REPORT, intFlag, 1);enable_irq(lpi->irq);}/*cm36283的中断函数*/static irqreturn_t cm36283_irq_handler(int irq, void *data){struct cm36283_info *lpi = data;disable_irq_nosync(lpi->irq);/*将work放入新创建的工作队列进行调度,相当于之后要调用sensor_irq_do_work()函数*/queue_work(lpi->lp_wq, &sensor_irq_work);return IRQ_HANDLED;}static int cm36283_probe(struct i2c_client *client,const struct i2c_device_id *id){//.../*创建自定义的工作队列,且不与任何CPU绑定*/lpi->lp_wq = create_singlethread_workqueue("cm36283_wq");if (!lpi->lp_wq) {pr_err("[PS][CM36283 error]%s: can't create workqueue\n", __func__);ret = -ENOMEM;goto err_create_singlethread_workqueue;}//...err_cm36283_power_on:/*销毁前面创建的工作队列*/destroy_workqueue(lpi->lp_wq);//...}
poll方式示例:
/* * poll方式使用delayed_work,未创建新的队列,使用默认的系统工作队列,system_wq *//*定义delayed_work*/struct delayed_work ldwork;/*work的工作函数*/static void lsensor_delay_work_handler(struct work_struct *work){//.../*经过ls_poll_delay时间后会再次调度ldwork的本函数*/schedule_delayed_work(&ldwork,msecs_to_jiffies(atomic_read(&lpi->ls_poll_delay)));}/*使能光强传感器,就是要周期性调度work*/static int lightsensor_enable(struct cm36283_info *lpi){//...if (lpi->polling)schedule_delayed_work(&ldwork,msecs_to_jiffies(delay));return ret;}/*禁用光强传感器,就是要停止调度work*/static int lightsensor_disable(struct cm36283_info *lpi){//...if (lpi->polling)cancel_delayed_work_sync(&ldwork);//...return ret;}/*将工作函数放在work节点上,以后只需对work进行调度和cancel即可让handler运行和停止*/static int cm36283_probe(struct i2c_client *client,const struct i2c_device_id *id){//...INIT_DELAYED_WORK(&ldwork, lsensor_delay_work_handler);//...}
- linux驱动____中断底半部
- linux驱动开发--中断:tasklet实现中断底半部
- linux驱动开发--中断:工作者队列实现中断底半部
- linux驱动编程--中断
- Linux驱动之中断
- 驱动-linux 中断处理
- 《Linux驱动》中断
- linux驱动中的中断
- linux驱动-中断机制
- linux驱动-中断
- Linux 驱动中断处理
- Linux驱动中断机制
- Linux中断驱动示例
- linux驱动,中断中关闭中断
- linux驱动开发--中断:按键中断
- linux驱动中中断处理
- 6、linux设备驱动---中断
- linux驱动之按键中断
- 地天泰
- Android(java)同步方法synchronized
- 陈永金:零售变革之道
- linux 压缩解压命令
- 只吃素食对吗,是不是只吃素食好
- linux驱动____中断底半部
- 7-18 ps
- SOAP协议简介
- 分布式搜索引擎Elasticsearch PHP类封装 使用原生api
- 百度员工离职总结:如何做个好员工?(都是大实话)
- 解决Qt5 Creator无法切换输入法(fcitx),不能录入汉字问题
- 七大症状告诉你要补维B
- zookeeper相关资料
- Ext.form.FormPanel