最近调试一个音频问题很是奇葩,在比较极端的测试条件下会造成串口丢失数据。问题是这样的当应用层频繁的播放一段短音频文件并且串口不断的在传输数据,此时串口会比较频繁的丢失一些数据。后面查找问题发现是由于频繁的播放短音频文件会导致频繁的开流关流操作,而音频的开流和关流操作会关闭中断,最致命的问题是开流关流时有个很耗时的操作(等待硬件寄存器状态)大概2ms,耗时这么久还把中断关了,这肯定会有问题的。一开始为了简单,直接在耗时长的地方把中断打开,后面进行了简单的测试发现串口确实不会丢失数据了,但是后来老化测试的时候发现会死机。没办法,只能想其他办法了。
对于新办法的思路就是要把耗时的操作延后处理,像中断上下文一样。第一想到的是定时器,后来发现定时器定时时间很不准确,平台HZ为100,即误差精度为10ms,还有使用定时器开流关流顺序很难控制。其次想到的是内核线程再结合等待队列,发现开流关流顺序还是很难控制。最后想到了工作队列,把开流和关流做为单独的工作,应用触发开流时将其放入队列触发关流时放入队列,这样开流和关流就很有序的在队列中执行。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
static struct timer_list timer;
static struct workqueue_struct *queue=NULL;
static struct work_struct work0;
static struct work_struct work1;
static void open_stream_work_handler0(struct work_struct *data)
{
printk("open stream.\n");
}
static void close_stream_work_handler1(struct work_struct *data)
{
printk("close stream.\n");
}
static void timer_fun(unsigned long data)
{
//schedule_work(&work0);
queue_work(queue, &work0);
mod_timer(&timer, jiffies+HZ);
}
static int __init test_init(void)
{
queue = create_singlethread_workqueue("hello world");/*创建一个单线程的工作队列*/
if (!queue) {
goto err;
}
INIT_WORK(&work0, open_stream_work_handler0); //初始化工作0
queue_work(queue, &work0); //将work0放入queue队列中工作
INIT_WORK(&work1, close_stream_work_handler1); //初始化工作1
queue_work(queue, &work1);
//schedule_work(&work1); //将work1放入内核预定义的工作队列中
init_timer(&timer);
timer.function = timer_fun;
//timer.expires = 0; //初始化时expires不赋值的话,add_timer后不会调用回调
add_timer(&timer);
return 0;
err:
return -1;
}
static void __exit test_exit(void)
{
del_timer(&timer); //回收定时器资源
destroy_workqueue(queue); //回收队列
}
MODULE_LICENSE("GPL");
module_init(test_init);
module_exit(test_exit);
以上实例是比较常用的工作队列使用方式,然后还要很多人将工作队列当线程用即在回调函数里加个while(1)如下
static void close_stream_work_handler1(struct work_struct *data)
{
printk("close stream.\n");
while (1) {
.....
}
}
对于这种做法不是说不可以,完全可以,但是会有很多的弊端。第一、如果有多个work需要添加到队列中去执行的话那么你这个while(1)将会阻塞其他work的正常执行,第二、当你调用destroy_workqueue()释放工作队列后你会发现while(1)还在跑,这时就会出现很多奇怪的问题如卸载掉驱动后键盘不能正常输入。所以这种情况下建议使用内核线程。