Linux驱动开发之工作队列

一、什么是工作队列

       工作队列(work queue)是Linux kernel中将工作推后执行的一种机制。这种机制和BH或Tasklets不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于它允许重新调度甚至睡眠。

二、如何使用工作队列

       1、创建工作队列结构体(struct workqueue_struct *mfd_wq;)和工作结构体(struct work_struct work;)。

       2、使用create_singlethread_workqueue或者create_workqueue创建一个工作队列。

       3、使用INIT_WORK初始化工作结构体(将工作函数赋值给工作结构体中的func)。

       4、在适当的时候调用queue_work,queue_work会将工作结构体所对应的工作函数激活,然后这个工作函数就可以在内核线程中运行,当工作函数执行完毕,又会进入失效的状态,等待下一次调用queue_work激活。

三、结构体

       这一节主要是展示一下两个要用到的结构体的内容,不用太过关注,可以直接跳过。

1、工作队列结构体

       源码路径:linux-4.9/kernel/queuework.c。

struct workqueue_struct {
	struct list_head	pwqs;		/* WR: all pwqs of this wq */
	struct list_head	list;		/* PR: list of all workqueues */

	struct mutex		mutex;		/* protects this wq */
	int			work_color;	/* WQ: current work color */
	int			flush_color;	/* WQ: current flush color */
	atomic_t		nr_pwqs_to_flush; /* flush in progress */
	struct wq_flusher	*first_flusher;	/* WQ: first flusher */
	struct list_head	flusher_queue;	/* WQ: flush waiters */
	struct list_head	flusher_overflow; /* WQ: flush overflow list */

	struct list_head	maydays;	/* MD: pwqs requesting rescue */
	struct worker		*rescuer;	/* I: rescue worker */

	int			nr_drainers;	/* WQ: drain in progress */
	int			saved_max_active; /* WQ: saved pwq max_active */

	struct workqueue_attrs	*unbound_attrs;	/* PW: only for unbound wqs */
	struct pool_workqueue	*dfl_pwq;	/* PW: only for unbound wqs */

#ifdef CONFIG_SYSFS
	struct wq_device	*wq_dev;	/* I: for sysfs interface */
#endif
#ifdef CONFIG_LOCKDEP
	struct lockdep_map	lockdep_map;
#endif
	char			name[WQ_NAME_LEN]; /* I: workqueue name */
	struct rcu_head		rcu;

	/* hot fields used during command issue, aligned to cacheline */
	unsigned int		flags ____cacheline_aligned; /* WQ: WQ_* flags */
	struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
	struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */
};

2、工作结构体

       源码路径:linux-4.9/include/linux/queuework.h。

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

四、源码分析

struct xx_test
{
	int irq;
	unsigned char gpio;
    struct work_struct work;
	struct workqueue_struct *mfd_wq;
};

/* 创建设备data结构体 */
struct xx_test *test = NULL;

/* 中断处理函数 */
irqreturn_t mfd_irq(int irq, void *handle)
{
	/* 将工作加入工作队列,工作队列会调度work开始工作 */
	queue_work(mfd_wq, &test->work);
}

/* 工作函数 */
void work_handler(struct work_struct *work)
{
	/* container_of函数,通过指向结构体成员函数的指针获取指向该结构体的指针 */
	struct xx_test *xx = container_of(work, struct changan_mfd, work);
	
	printk("this is a test!\n");
}

int xx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/* 为test结构体指针开辟动态内存 */
	test = devm_kzalloc(&client->dev, sizeof(struct xx_test), GFP_KERNEL);
	
	/* 创建一个单线程的工作队列,返回值类型为“struct workqueue_struct” */
	test->mfd_wq = create_singlethread_workqueue("mfd_wq");
	if (!mfd_wq){
		err();
		return -ENOMEM;
	}
	
	/* 初始化work工作结构体,将工作处理函数赋值给work结构体中的func */
	INIT_WORK(&test->work, work_handler);
	
	/* 为gpio口申请软件中断号 */
	test->irq = gpio_to_irq(test->gpio);
	
	irq_flags = irq_get_trigger_type(ts->irq);
	
	/* 中断的动态申请及注册,这个中断只有顶半函数   	                 */
	rc = request_threaded_irq(test->irq, mfd_irq, NULL, irqflags, "mfd", test);
	if (rc) {
		err();
		return rc;
	}
}

static const struct of_device_id changan_of_match[] = {
	{ .compatible = "MFD", },
	{},
};

static const struct i2c_device_id changan_mfd_idtable[] =
{
	{ "MFD", 0 },
	{ }
};

static struct i2c_driver changan_mfd_driver =
{
	.driver = {
		.name   = "MFD",
		.of_match_table = of_match_ptr(changan_of_match),
		.pm	= &pm_ops,
	},
	.id_table   = xx_idtable,
	.probe      = xx_probe,
	.remove     = xx_remove,
};

module_i2c_driver(mfd_driver);

       可以看到,这份伪代码属于i2c设备驱动的一部分。

1、module_i2c_driver

       首先,我们看到,79行的代码“module_i2c_driver(mfd_driver)”。看到这里,可能有一些朋友不太了解,这是什么?按理来说,这里不应该是“module_init”之类的函数吗?其实,“module_i2c_driver”是i2c.h所定义的宏,宏定义如下。

#define module_i2c_driver(__i2c_driver) module_driver(__i2c_driver, i2c_add_driver, i2c_del_driver)

       而“module_driver”也是宏,它在device.h中定义,宏定义如下。

#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
	return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
	__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

       因此,将“module_i2c_driver(mfd_driver)”展开如下:

static int __init mfd_driver_init(void)
{
    return i2c_add_driver(&mfd_driver);
}

module_init(mfd_driver_init);

static void __exit mfd_driver_exit(void)
{
    i2c_del_driver(&mfd_driver);
}

module_exit(mfd_driver_exit);

       其实,和普通的i2c设备驱动入口没什么不同,只是这里换成了宏而已。

2、create_singlethread_workqueue

       我们继续看代码。

       为了不浪费时间,我们直接进入probe函数,看到示例代码34行的“create_singlethread_workqueue(mfd_wq)”。

       create_singlethread_workqueue定义在workqueue.h中,也是一个宏,定义如下。

#define create_singlethread_workqueue(name) alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)

       该宏函数返回一个类型为struct workqueue_struct的指针变量,这个指针变量所指向的内存地址在函数内部调用kzalloc动态生成,所以driver在不再使用该work queue的情况下应调用“void destroy_workqueue(struct workqueue_struct *wq)”来释放此处的内存地址。

3、create_singlethread_workqueue与create_workqueue的区别

       使用create_singlethread_workqueue创建工作队列,即使对于多CPU系统,内核也只负责在一个cpu上创建一个worker_thread内核线程;而使用create_workqueue创建工作队列对于多CPU系统,内核将会在每个CPU上创建一个worker_thread内核线程,使得线程处理的事务能够并行化。

       虽然使用create_workqueue可以使得线程处理的事务能够并行化,但这同时也会大量占用我们的cpu。如果不是有特别需求,一般而言,我们都只使用create_singlethread_workqueue在一个cpu上创建一个worker_thread内核线程。

4、INIT_WORK

       继续回到示例代码,看到41行。INIT_WORK在workqueue.h中定义,是一个宏函数,宏定义如下。

#define INIT_WORK(_work, _func)	__INIT_WORK((_work), (_func), 0)
#ifdef CONFIG_LOCKDEP
#define __INIT_WORK(_work, _func, _onstack)				\
	do {								\
		static struct lock_class_key __key;			\
									\
		__init_work((_work), _onstack);				\
		(_work)->data = (atomic_long_t) WORK_DATA_INIT();	\
		lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, 0); \
		INIT_LIST_HEAD(&(_work)->entry);			\
		(_work)->func = (_func);				\
	} while (0)
#else
#define __INIT_WORK(_work, _func, _onstack)				\
	do {								\
		__init_work((_work), _onstack);				\
		(_work)->data = (atomic_long_t) WORK_DATA_INIT();	\
		INIT_LIST_HEAD(&(_work)->entry);			\
		(_work)->func = (_func);				\
	} while (0)
#endif

       宏函数中其实需要我们关注也不多。

       “(_work)->data = (atomic_long_t) WORK_DATA_INIT();”初始化私有数据。

       “INIT_LIST_HEAD(&(_work)->entry);”初始化链表。

       “(_work)->func = (_func);”将工作函数赋值给工作结构体的func。

5、queue_work

       queue_work出现在示例代码16行,可以看到它是在中断函数中被调用的。

       queue_work的源码(workqueue.h和workqueue.c)比较绕,我们就不分析了。

       对queue_work的功能可以这么理解:queue_work会将工作结构体所对应的工作函数激活,然后这个工作函数会在create_singlethread_workqueue创建的内核线程中运行,当工作函数执行完毕,又会进入失效的状态,等待下一次激活。

五、总结

       关于工作队列就分析到这里了,希望对大家有所帮助,有不懂的欢迎在评论区留言。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值