最近一直再调试一个基于spi的打印机,一路心酸坎坷啊。
在这个打印的驱动中,需要不断读取打印数据写入spi,在时序方面要达到每1ms往spi写入一次数据,所以用到了工作队列以及定时器来实现。
1、工作队列
工作队列提供一个通用的办法将任务延迟到 bottom halves。处于核心的是工作队列(结构体 workqueue_struct
), 任务被安排到该结构体当中。任务由结构体 work_struct
来说明,用来鉴别哪些任务被延迟以及使用哪个延迟函数。 events/X
内核线程(每 CPU 一个)从工作队列中抽取任务并激活一个 bottom-half 处理程序(由处理程序函数在结构体 work_struct
中指定)。
1
2
3
4
5
6
7
8
|
struct
work_struct {
unsigned
long
pending;
struct
list_head entry;
void
(*func)(
void
*);
void
*data;
void
*wq_data;
struct
timer_list timer;
};
|
pending是用来记录工作是否已经挂在队列上;
entry是循环链表结构;
func作为函数指针,由用户实现;
data用来存储用户的私人数据,此数据即是func的参数;
wq_data一般用来指向工作者线程(工作者线程参考下文);
timer是推后执行的定时器。
work_struct的这些变量里,func和data是用户使用的,其他是内部变量,我们可以不用太过关心。
API:
1
2
3
4
5
|
INIT_WORK(_work, _func, _data);
int
schedule_work(
struct
work_struct *work);
int
schedule_delayed_work(
struct
work_struct *work, unsigned
long
delay);
void
flush_scheduled_work(
void
);
int
cancel_delayed_work(
struct
work_struct *work);
|
1、初始化指定工作,目的是把用户指定的函数_func及_func需要的参数_data赋给work_struct的func及data变量。
2、对工作进行调度,即把给定工作的处理函数提交给缺省的工作队列和工作者线程。工作者线程本质上是一个普通的内核线程,在默认情况下,每个CPU均有一个类型为“events”的工作者线程,当调用schedule_work时,这个工作者线程会被唤醒去执行工作链表上的所有工作。
3、延迟执行工作,与schedule_work类似。
4、刷新缺省工作队列。此函数会一直等待,直到队列中的所有工作都被执行。
5、flush_scheduled_work并不取消任何延迟执行的工作,因此,如果要取消延迟工作,应该调用cancel_delayed_work。
以上均是采用缺省工作者线程来实现工作队列,其优点是简单易用,缺点是如果缺省工作队列负载太重,执行效率会很低,这就需要我们创建自己的工作者线程和工作队列。
API:
1
2
3
4
5
|
struct
workqueue_struct *create_workqueue(
const
char
*name);
int
queue_work(
struct
workqueue_struct *wq,
struct
work_struct *work);
int
queue_delayed_work(
struct
workqueue_struct *wq,
struct
work_struct *work, unsigned
long
delay);
void
flush_workqueue(
struct
workqueue_struct *wq);
void
destroy_workqueue(
struct
workqueue_struct *wq);
|
1、创建新的工作队列和相应的工作者线程,name用于该内核线程的命名。
2、类似于schedule_work,区别在于queue_work把给定工作提交给创建的工作队列wq而不是缺省队列。
3、延迟执行工作。
4、刷新指定工作队列。
5、释放创建的工作队列。
下面一段代码可以看作一个简单的实作:
1.创建工作队列并与工作任务函数绑定:
struct delayed_work ms_queue_work;
struct workqueue_struct *ms_wqueue;
ms_wqueue = create_rt_workqueue("1ms");
INIT_DELAYED_WORK(&ms_queue_work, queue_work_task);
if (!ms_wqueue) {
pr_err("failed to create workqueue\n");
return -1;
}
工作队列的使用有两个办法,一是使用系统默认的工作队列,它对应一个默认的工作者线程,这个线程是什么策略我没有查过,应该是SCHED_NORMAL ,第二种方法就是自己创建一个队列
这里的create_rt_workqueue是为了创建一个调度策略为SCHED_FIFO的工作队列。#define create_workqueue(name) __create_workqueue((name), 0, 0, 0) #define create_rt_workqueue(name) __create_workqueue((name), 0, 0, 1) #define create_freezeable_workqueue(name) __create_workqueue((name), 1, 1, 0) #define create_singlethread_workqueue(name) __create_workqueue((name), 1, 0, 0)</span>
2.延时调度:
queue_delayed_work(ms_wqueue, &ms_queue_work, 1 );
schedule_delayed_work(&ms_queue_work,usecs_to_jiffies(1000));
以上2个函数都可以实现延时调度,第一个是指一个时钟节拍执行一次,一个节拍即1/HZ 的时间,默认值是100,可以在arch/arm/KConfig里面将默认值改成1000,这样时钟节拍就变成了1ms,如无需要还是建议不要去修改这个值。如果要循环调度,则需要每次都调用一次延时调度函数。