最近調試一個音頻問題很是奇葩,在比較極端的測試條件下會造成串口丟失數據。問題是這樣的當應用層頻繁的播放一段短音頻文件並且串口不斷的在傳輸數據,此時串口會比較頻繁的丟失一些數據。後面查找問題發現是由於頻繁的播放短音頻文件會導致頻繁的開流關流操作,而音頻的開流和關流操作會關閉中斷,最致命的問題是開流關流時有個很耗時的操作(等待硬件寄存器狀態)大概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)還在跑,這時就會出現很多奇怪的問題如卸載掉驅動後鍵盤不能正常輸入。所以這種情況下建議使用內核線程。