欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何给work回调函数传递用户参数

程序员文章站 2022-06-03 23:00:17
...

背景

Linux驱动开发中,经常会用到work queue,该数据结构管理的是一个个的work_struct结构体:

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

最近在做pcie驱动开发,有个需求是1个pcie端口传输5块采集卡的数据,每块卡的收发是独立且随机的,所以需要用work queue,它有一个内置的内核线程,可以轮询每块板卡的状态。

每块板卡都有一个work_struct,用于work queue启动work时 分辨板卡是哪个槽位(slot)的。

struct board{
	int slot;
	work_struct work;
}

INIT_WORK(&work, user_func);  // 驱动初始化时绑定用户函数到work item

schedule_work(&work); // 驱动ISR用work queue的内核线程启动该work

work结构体中有个回调函数字段func,类型work_func_t

typedef void (*work_func_t)(struct work_struct *work);

问题

发现一个问题,该回调函数只有1个入参,即work_struct结构体自身,而没有预留一个用户自定义参数,这样当用户想要传递槽位号时,就没办法了。

错误的思路

根据以往经验,work结构体应该像device结构体有个void *型的driver_data字段一样,有个自己的void * work_data字段,但是看来看去,只有atomic_long_t data字段像,于是打算这样:

board board;
INIT_WORK(&board->work, user_func);
work.data = slot;  // 初始化work后立即将槽位号信息储存到data字段

void user_func(struct work_struct *work) {
	int slot = (int)work->data; // 提取槽位号
	transfer(slot);  // 传输特定槽位的板卡数据
}

但是搜遍内核代码,也没看到有利用work_structdata字段的。仔细查看相关头文件,原来这个data字段最开始确实是用于传递用户数据的,但是后来就变成work queue管理work item内部状态的flag字段。

解决方法

灵机一动想到可以用container_of宏来获取外围的board结构体实例,然后再通过board实例得到槽位号

void user_func(struct work_struct *work) {
	int slot = container_of(work, struct board, work); // 提取槽位号,第一个work是实例指针,第二个work是work_struct在board中的字段名
	transfer(slot);  // 传输特定槽位的板卡数据
}

后来去网上一搜,原来这是标准做法,自己重新发明了一次*????

总结

回调函数传递用户自定义参数的2种模式:

  1. 回调函数自己预留void *指针
  2. 将用户参数跟回调函数的某个参数一起嵌入到用户自定义结构体中

模式2的好处是,如果回调函数不需要处理多种数据,则用户不需要像模式1那样给void *参数传递NULL指针,更*些,扩展性也更好。