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_struct
的data
字段的。仔细查看相关头文件,原来这个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宏三个参数的解释:
work
是work_struct
实例的地址struct board
是外围结构体
的类型名
retrieve_work
是work_struct
在外围结构体中的字段名
不过后来去网上一搜,原来这是标准做法,自己重新发明了一次轮子
回调函数传递用户自定义参数的2种方法:
方法2的好处是,如果回调函数不需要处理多种数据,则用户不需要像模式1那样给void *参数传递NULL指针,更自由些,扩展性也更好。