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

背景

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 retrieve_work; // 板卡的work_struct
}
// 定义board数组
struct board boards[5];  //有5张板卡,分属5个槽位

// 驱动初始化时绑定用户函数retrieve_func到work item
for (int i = 0; i < 5; i++) {
	INIT_WORK(&boards[i]->retrieve_work, retrieve_func);
}

// 驱动ISR用work queue启动特定板卡的retrieve_work
int slot = get_field(STATUS_REG);
schedule_work(&boards[slot].retrieve_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字段像是干这个的,于是打算板卡初始化时这样

for (int i = 0; i < 5; i++) {
	INIT_WORK(&boards[i]->retrieve_work, retrieve_func);
	boards[i].retrieve_work.data = slot;  // 将槽位号信息储存到data字段
}

在回调函数retrieve_func里这样做:

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

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

解决方法

灵机一动想到可以用container_of宏,通过work_struct结构体的指针获取外围的board结构体指针,然后再通过常规的pBoard->slot之类的表达式得到槽位号

void retrieve_func(struct work_struct *work) {
	// 获取外围结构体
	struct board *b = container_of(work, struct board, retrieve_work);
	transfer(b->slot);  // 传输特定槽位的板卡数据
}

对于container_of宏三个参数的解释:

  • 第一个参数workwork_struct实例的地址
  • 第二个参数struct board外围结构体类型名
  • 第三个参数retrieve_workwork_struct在外围结构体中的字段名

不过后来去网上一搜,原来这是标准做法,自己重新发明了一次轮子

总结

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

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

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

你可能感兴趣的:(驱动开发,内核,linux,驱动,workqueue,work_struct)