linux 中斷底半部機制對比(任務隊列,工作隊列,軟中斷)–由linux RS485引出的血案【轉】

  • 2019 年 10 月 10 日
  • 筆記

轉自:http://blog.chinaunix.net/uid-20768928-id-5077401.html

在LINUX RS485的使用過程中,由於各種原因,最後不得不使用中斷底半部機制的方法來進行實現此功能。先講兩個小故事來描述一下,遇到的問題。也是因為自己對底半部機制理解得不透徹。這些故事的前提都是在串口中斷中,一定條件後去完成某件事情,但時間上不能超過5ms。 故事一,最開始想到的是用workqueue。印象中workqueue 就是用來做這種事的,並且還記得可以延時一段時間再來做。

點擊(此處)摺疊或打開

  1. INIT_WORK(&my_wq,(void (*) (void*))my_wq_func);
  2. schedule_work(&my_wq);
  3. //schedule_delayed_work(&my_wq,delay);

最終實現的結果是,my_wq_func 的執行是在中斷響應後,但響應時間不確定。短的時候是1毫秒以內,長得的時候出現過幾十個毫秒。這樣就達不到我們的要求。為什麼出現這種時間不確定的問題呢?等故事講完再一起分析。schedule_delayed_work 延時執行的時間為最小一個jiffies,顯然不能用在我們這種情況,我們要求小於5ms。

故事二,工作隊列不行後,感覺底半部機制就實現不了,滿足不了我們的要求。上網翻了一些資料,覺得任務隊列時效性應該比工作隊列更好。就像買葯的做廣告一樣,抱着試一試的態度嘗試了一下。

點擊(此處)摺疊或打開

  1. tasklet_init(&my_task0,my_wq_func,(unsigned long) my_wq_arg);
  2. tasklet_schedule(&my_task0);
  3. tasklet_hi_schedule(&my_task0);

最終這種方法實現了。但過程也是相當曲折。tasklet_schedule時效性可以達到,hi_schedule 更是完美,感覺會犧牲系統性能。那麼過程曲折在哪呢?剛開始以為搞好了,回家睡大覺,等我九點半到家,同事打電話說不行,出問題了。單個串口沒有問題,多個串口同時用的時候,前面打開的串口對應的rs485 都不能正常使用。GPIO拉高後,就不低。而my_wq_func就是實現GPIO拉低的動作。最後的原因是my_wq_func被多次調用,而其只響應最後一次。這個地方還得感謝這位老兄,http://blog.csdn.net/goodluckwhh/article/details/9003353 。tasklet 是一個特殊的函數, 它在軟中斷上下文被調度。它可能被調度運行多次,但是tasklet調度不累積,也就是即使在tasklet被執行之前請求了多次來執行該tasklet,它也只運行一次。不會有同一個tasklet的多個實例同時運行。但是tasklet可以與SMP系統上的其他tasklet並行運行。因此, 如果多個tasklet會使用相同的資源, 它們必須採取某類加鎖來避免彼此衝突。除非tasklet重新激活自己,否則每次tasklet激活只會運行一次。最後的解決方法就是將my_wq_func一個函數可以實現的內容,複製成了四個函數,問題就解決了。 故事講完了,這時候該來分析分析理論上的底半部機制。前面的曲折,主要是因為自己對底半部機制的一知半解。這裡來着重分析一下任務隊列,工作隊列的區別,同時也COPY一些別人對軟中斷的理解,以備後續查看 工作隊列,任務隊列,軟中斷

工作隊列:Linux kernel中將工作推後執行的一種機制。這種機制和BH或Tasklets不同之處在於工作隊列是把推後的工作交由一個內核線程去執行,因此工作隊列的優勢就在於它允許重新調度甚至睡眠。工作隊列是2.6內核開始引入的機制,在2.6.20之後,工作隊列的數據結構發生了一些變化。可以參考http://blog.csdn.net/angle_birds/article/details/8448070

點擊(此處)摺疊或打開

  1. DECLARE_WORK(struct work_struct , work_func_t func);
  2. INIT_WORK(struct work_struct *work, work_func_t func);
  3. INIT_DELAYED_WORK(struct delayed_work *work, work_func_t func);
  4. int schedule_work(struct work_struct *work);
  5. int schedule_delayed_work(struct delayed_work *work, unsigned long delay);
  6. int schedule_delayed_work_on(struct delayed_work *work, unsigned long delay);
  7. struct workqueue_struct *create_workqueue(const char *name);
  8. int queue_work(struct workqueue_struct *wq, struct work_struct *work);
  9. int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work,
  10. unsigned long delay);
  11. void flush_scheduled_work(void);
  12. void flush_workqueue(struct workqueue_struct *wq);
  13. int cancel_delayed_work(struct delayed_work *work);
  14. void destroy_workqueue(struct workqueue_struct *wq);

任務隊列:是一個由系統決定的安全時刻在軟件中斷上下文被調度運行的特殊函數。注意tasklet只會運行一次,即使在激活tasklet的運行之前重複請求該tasklet的運行也是這樣。但是他可以與其他tasklet並行的運行在對稱多處理器(SMP)系統上。

點擊(此處)摺疊或打開

  1. DECLARE_TASKLET(name, func, data);
  2. void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);
  3. void tasklet_schedule(struct tasklet_struct *t);
  4. void tasklet_hi_schedule(struct tasklet_struct *t);
  5. void tasklet_disable(struct tasklet_struct *t);
  6. void tasklet_disable_nosync(struct tasklet_struct *t);
  7. void tasklet_enable(struct tasklet_struct *t);
  8. void tasklet_kill(struct tasklet_struct *t);

軟中斷:利用硬件中斷的概念,用軟件方式進行模擬,實現宏觀上的異步執行效果。很多情況下,軟中斷和"信號"有些類似,同時,軟中斷又是和硬中斷相對應的,"硬中斷是外部設備對CPU的中斷","軟中斷通常是硬中斷服務程序對內核的中斷","信號則是由內核(或其他進程)對某個進程的中斷"

點擊(此處)摺疊或打開

  1. void open_softirq(int nr, void (*action)(struct softirq_action *));
  2. void raise_softirq(unsigned int nr);
  3. asmlinkage void do_softirq(void) ;

他們之間的差異做了一個對比

通過上面的表格也就能明白,這三種分別適應的場合。以下原則

1,需要睡眠,阻塞的,只能用工作隊列。

2,短時間內中斷數量很多的,任務隊列,軟中斷會更好。例如網絡。

3,對性能要求很高的話,軟中斷最好。

4,使用任務隊列時,應該注意同一個任務被多次調用,同一個函數被多個任務隊列注意。

5,軟中斷要注意SMP,函數的重入。