tasklet


                                                               tasklet

Tasklet的使用比較簡單,只需要定義tasklet及其處理函數並將兩者關聯

例子:

Void my_tasklet_func(unsigned long)

DECLARE_TASKLET(my_tasklet.my_tasklet_func,data)

代碼DECLARE_TASKLET實現了定義名稱爲my_tasklet的tasklet並將其與my_tasklet_func這個函數綁定,而傳入這個函數的參數爲data。

需要調度tasklet的時候引用一個tasklet_schedule()函數就能使系統在適當的時候進行調度,如下所示

Tasklet_schedule(&my_tasklet)

下面給出驅動模板

 

  1. void xxx_do_tasklet(unsigned long);  
  2.   
  3. DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);  
  4.   
  5. void xxx_do_tasklet(unsigned long)  
  6.   
  7. {  
  8.   
  9. ……  
  10.   
  11. }  
  12.   
  13. irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)  
  14.   
  15. {  
  16.   
  17.       ……  
  18.   
  19.       tasklet_schedule(&xxx_tasklet);  
  20.   
  21.       ……  
  22.   
  23. }  
  24.   
  25. int _init xxx_init(void)  
  26.   
  27. {  
  28.   
  29.       ……  
  30.   
  31.       result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL)  
  32.   
  33.       ……  
  34.   
  35. }  
  36.   
  37. void _exit xxx_exit(void)  
  38.   
  39. {  
  40.   
  41.       ……  
  42.   
  43.       free_irq(xxx_irq,xxx_irq_interrupt);  
  44.   
  45.       ……  
  46.   
  47. }  
  1. void xxx_do_tasklet(unsigned long);  
  2.   
  3. DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);  
  4.   
  5. void xxx_do_tasklet(unsigned long)  
  6.   
  7. {  
  8.   
  9. ……  
  10.   
  11. }  
  12.   
  13. irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)  
  14.   
  15. {  
  16.   
  17.       ……  
  18.   
  19.       tasklet_schedule(&xxx_tasklet);  
  20.   
  21.       ……  
  22.   
  23. }  
  24.   
  25. int _init xxx_init(void)  
  26.   
  27. {  
  28.   
  29.       ……  
  30.   
  31.       result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL)  
  32.   
  33.       ……  
  34.   
  35. }  
  36.   
  37. void _exit xxx_exit(void)  
  38.   
  39. {  
  40.   
  41.       ……  
  42.   
  43.       free_irq(xxx_irq,xxx_irq_interrupt);  
  44.   
  45.       ……  
  46.   
  47. }  


二、tasklet函數詳解

它對於中斷處理特別有用:硬件中斷必須儘快處理, 但大部分的數據管理可以延後到以後安全的時間執行。

tasklet 以一個數據結構形式存在,使用前必須被初始化。初始化能夠通過調用一個特定函數或者通過使用某些宏定義聲明結構:



 

  1. #include <linux/interrupt.h>   
  2. struct tasklet_struct  
  3. {  
  4.     struct tasklet_struct *next;  
  5.     unsigned long state;  
  6.     atomic_t count;  
  7.     void (*func)(unsigned long);  
  8.     unsigned long data;  
  9. };  
  10. void tasklet_init(struct tasklet_struct *t,  
  11.  void (*func)(unsigned long), unsigned long data);  
  12.   
  13. #define DECLARE_TASKLET(name, func, data) \  
  14. struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }  
  15. #define DECLARE_TASKLET_DISABLED(name, func, data) \  
  16. struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }  
  17.   
  18. void tasklet_disable(struct tasklet_struct *t);   
  19. /*函數暫時禁止給定的 tasklet被 tasklet_schedule 調度,直到這個 tasklet 被再次被enable;若這個 tasklet 當前在運行, 這個函數忙等待直到這個tasklet退出*/  
  20. void tasklet_disable_nosync(struct tasklet_struct *t);   
  21. /*和tasklet_disable類似,但是tasklet可能仍然運行在另一個 CPU */  
  22. void tasklet_enable(struct tasklet_struct *t);   
  23. /*使能一個之前被disable的 tasklet;若這個 tasklet 已經被調度, 它會很快運行。 tasklet_enable 和tasklet_disable必須匹配調用, 因爲內核跟蹤每個 tasklet 的"禁止次數"*/   
  24. void tasklet_schedule(struct tasklet_struct *t);   
  25. /*調度 tasklet 執行,如果tasklet在運行中被調度, 它在完成後會再次運行; 這保證了在其他事件被處理當中發生的事件受到應有的注意. 這個做法也允許一個 tasklet 重新調度它自己*/  
  26. void tasklet_hi_schedule(struct tasklet_struct *t);   
  27. /*和tasklet_schedule類似,只是在更高優先級執行。當軟中斷處理運行時, 它處理高優先級 tasklet 在其他軟中斷之前,只有具有低響應週期要求的驅動才應使用這個函數, 可避免其他軟件中斷處理引入的附加週期*/  
  28. void tasklet_kill(struct tasklet_struct *t);   
  29. /* 確保了 tasklet 不會被再次調度來運行,通常當一個設備正被關閉或者模塊卸載時被調用。如果 tasklet 正在運行, 這個函數等待直到它執行完畢。若 tasklet 重新調度它自己,則必須阻止在調用 tasklet_kill 前它重新調度它自己,如同使用 del_timer_sync*/  
  1. #include  
  2.  <linux/interrupt.h>   
  3. struct tasklet_struct  
  4. {  
  5.     struct tasklet_struct *next;  
  6.     unsigned long state;  
  7.     atomic_t count;  
  8.     void (*func)(unsigned long);  
  9.     unsigned long data;  
  10. };  
  11. void tasklet_init(struct tasklet_struct *t,  
  12.  void (*func)(unsigned long), unsigned long data);  
  13.   
  14. #define DECLARE_TASKLET(name, func, data) \  
  15. struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }  
  16. #define DECLARE_TASKLET_DISABLED(name, func, data) \  
  17. struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }  
  18.   
  19. void tasklet_disable(struct tasklet_struct *t);   
  20. /*函數暫時禁止給定的 tasklet被 tasklet_schedule 調度,直到這個 tasklet 被再次被enable;若這個  
  21. tasklet 當前在運行, 這個函數忙等待直到這個tasklet退出*/  
  22. void tasklet_disable_nosync(struct tasklet_struct *t);   
  23. /*和tasklet_disable類似,但是tasklet可能仍然運行在另一個 CPU */  
  24. void tasklet_enable(struct tasklet_struct *t);   
  25. /*使能一個之前被disable的 tasklet;若這個 tasklet 已經被調度, 它會很快運行。 tasklet_enable  
  26. 和tasklet_disable必須匹配調用, 因爲內核跟蹤每個 tasklet 的"禁止次數"*/   
  27. void tasklet_schedule(struct tasklet_struct *t);   
  28. /*調度 tasklet 執行,如果tasklet在運行中被調度, 它在完成後會再次運行;  
  29. 這保證了在其他事件被處理當中發生的事件受到應有的注意. 這個做法也允許一個 tasklet 重新調度它自己*/  
  30. void tasklet_hi_schedule(struct tasklet_struct *t);   
  31. /*和tasklet_schedule類似,只是在更高優先級執行。當軟中斷處理運行時, 它處理高優先級 tasklet  
  32. 在其他軟中斷之前,只有具有低響應週期要求的驅動才應使用這個函數, 可避免其他軟件中斷處理引入的附加週期*/  
  33. void tasklet_kill(struct tasklet_struct *t);   
  34. /*確保了 tasklet 不會被再次調度來運行,通常當一個設備正被關閉或者模塊卸載時被調用。如果 tasklet 正在運行,  
  35. 這個函數等待直到它執行完畢。若 tasklet 重新調度它自己,則必須阻止在調用 tasklet_kill 前它重新調度它自己,如同使用  
  36. del_timer_sync*/  

工作隊列

一、       工作隊列的使用

定義一個工作隊列

Struct work_struct my_wq;

Void my_wq_func(unsigned long);

通過INIT_WORK可以初始化這個工作隊列並將工作隊列與處理函數綁定

INIT_WORK(&my_wq,(void(*)(void*))my_wq_func,NULL)

調度工作隊列

Schedule_work(&my_wq)

使用模板

  1. struct work_struct xxx_wq;  
  2.   
  3. void xxx_do_work(unsigned long);  
  4.   
  5. void xxx_do_work(unsigned long)  
  6.   
  7. {  
  8.   
  9.       ……  
  10.   
  11. }  
  12.   
  13. irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)  
  14.   
  15. {  
  16.   
  17.       ……  
  18.   
  19.       schedule_work(&xxx_wq);  
  20.   
  21.       ……  
  22.   
  23. }  
  24.   
  25. int init(void)  
  26.   
  27. {  
  28.   
  29.       ……  
  30.   
  31.       result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,“xxx”,NULL)  
  32.   
  33.       ……  
  34.   
  35.       INIT_WORK(&xxx_wq,(void(*)(void*))xxx_do_work,NULL);  
  36.   
  37.       …….  
  38.   
  39. }  
  40.   
  41. void xxx_exit(void)  
  42.   
  43. {  
  44.   
  45.       ……  
  46.   
  47.       free_irq(xxx_irq,xxx_interrupt);  
  48.   
  49.       ……  
  50.   
  51. }  
  1. struct work_struct xxx_wq;  
  2.   
  3. void xxx_do_work(unsigned long);  
  4.   
  5. void xxx_do_work(unsigned long)  
  6.   
  7. {  
  8.   
  9.       ……  
  10.   
  11. }  
  12.   
  13. irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)  
  14.   
  15. {  
  16.   
  17.       ……  
  18.   
  19.       schedule_work(&xxx_wq);  
  20.   
  21.       ……  
  22.   
  23. }  
  24.   
  25. int init(void)  
  26.   
  27. {  
  28.   
  29.       ……  
  30.   
  31.       result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,“xxx”,NULL)  
  32.   
  33.       ……  
  34.   
  35.       INIT_WORK(&xxx_wq,(void(*)(void*))xxx_do_work,NULL);  
  36.   
  37.       …….  
  38.   
  39. }  
  40.   
  41. void xxx_exit(void)  
  42.   
  43. {  
  44.   
  45.       ……  
  46.   
  47.       free_irq(xxx_irq,xxx_interrupt);  
  48.   
  49.       ……  
  50.   
  51. }  


 

二、       工作隊列函數詳解

工作隊列有 struct workqueue_struct 類型,在 <linux/workqueue.h> 中定義。一個工作隊列必須明確的在使用前創建,宏爲:

struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);

每個工作隊列有一個或多個專用的進程("內核線程"), 這些進程運行提交給這個隊列的函數。 若使用 create_workqueue, 就得到一個工作隊列它在系統的每個處理器上有一個專用的線程。在很多情況下,過多線程對系統性能有影響,如果單個線程就足夠則使用 create_singlethread_workqueue 來創建工作隊列。

提交一個任務給一個工作隊列,在這裏《LDD3》介紹的內核2.6.10和我用的新內核2.6.22.2已經有不同了,老接口已經不能用了,編譯會出錯。這裏我只講2.6.22.2的新接口,至於老的接口我想今後內核不會再有了。從這一點我們可以看出內核發展。

  1. /*需要填充work_struct或delayed_work結構,可以在編譯時完成, 宏如下: */  
  2.   
  3. struct work_struct {  
  4.     atomic_long_t data;  
  5. #define WORK_STRUCT_PENDING 0        /* T if work item pending execution */  
  6. #define WORK_STRUCT_FLAG_MASK (3UL)  
  7. #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)  
  8.     struct list_head entry;  
  9.     work_func_t func;  
  10. };  
  11.   
  12. struct delayed_work {  
  13.     struct work_struct work;  
  14.     struct timer_list timer;  
  15. };  
  16.   
  17. DECLARE_WORK(n, f)      
  18. /*n 是聲明的work_struct結構名稱, f是要從工作隊列被調用的函數*/  
  19. DECLARE_DELAYED_WORK(n, f)  
  20. /*n是聲明的delayed_work結構名稱, f是要從工作隊列被調用的函數*/  
  21.   
  22. /*若在運行時需要建立 work_struct 或 delayed_work結構, 使用下面 2 個宏定義:*/  
  23. INIT_WORK(struct work_struct *work, void (*function)(void *));   
  24. PREPARE_WORK(struct work_struct *work, void (*function)(void *));   
  25. INIT_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));   
  26. PREPARE_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));   
  27. /* INIT_* 做更加全面的初始化結構的工作,在第一次建立結構時使用. PREPARE_* 做幾乎同樣的工作, 但是它不初始化用來連接 work_struct或 delayed_work 結構到工作隊列的指針。如果這個結構已經被提交給一個工作隊列, 且只需要修改該結構,則使用 PREPARE_* 而不是 INIT_* */  
  28.   
  29. /*有 2 個函數來提交工作給一個工作隊列:*/  
  30. int queue_work(struct workqueue_struct *queue, struct work_struct *work);  
  31. int queue_delayed_work(struct workqueue_struct *queue, struct delayed_work *work, unsigned long delay);  
  32. /* 每個都添加work到給定的workqueue。如果使用 queue_delay_work, 則實際的工作至少要經過指定的 jiffies 纔會被執行。 這些函數若返回 1 則工作被成功加入到隊列; 若爲0,則意味着這個 work 已經在隊列中等待,不能再次加入*/  
  1. /* 
  2. 需要填充work_struct或delayed_work結構,可以在編譯時完成, 宏如下: */  
  3.   
  4. struct work_struct {  
  5.     atomic_long_t data;  
  6. #define WORK_STRUCT_PENDING 0        /* T if work item pending execution  
  7.  */  
  8. #define WORK_STRUCT_FLAG_MASK (3UL)  
  9. #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)  
  10.     struct list_head entry;  
  11.     work_func_t func;  
  12. };  
  13.   
  14. struct delayed_work {  
  15.     struct work_struct work;  
  16.     struct timer_list timer;  
  17. };  
  18.   
  19. DECLARE_WORK(n, f)      
  20. /*n 是聲明的work_struct結構名稱, f是要從工作隊列被調用的函數*/  
  21. DECLARE_DELAYED_WORK(n, f)  
  22. /*n是聲明的delayed_work結構名稱, f是要從工作隊列被調用的函數*/  
  23.   
  24. /*若在運行時需要建立 work_struct 或 delayed_work結構, 使用下面 2 個宏定義:*/  
  25. INIT_WORK(struct work_struct *work, void (*function)(void *));   
  26. PREPARE_WORK(struct work_struct *work, void (*function)(void *));   
  27. INIT_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));   
  28. PREPARE_DELAYED_WORK(struct delayed_work *work, void (*function)(void   
  29. *));   
  30. /* INIT_* 做更加全面的初始化結構的工作,在第一次建立結構時使用. PREPARE_* 做幾乎同樣的工作, 但是它不初始化用來連接  
  31. work_struct或delayed_work 結構到工作隊列的指針。如果這個結構已經被提交給一個工作隊列, 且只需要修改該結構,則使用  
  32. PREPARE_* 而不是 INIT_* */  
  33.   
  34. /*有 2 個函數來提交工作給一個工作隊列:*/  
  35. int queue_work(struct workqueue_struct *queue, struct work_struct   
  36. *work);  
  37. int queue_delayed_work(struct workqueue_struct *queue, struct   
  38. delayed_work *work, unsigned long delay);  
  39. /*每個都添加work到給定的workqueue。如果使用 queue_delay_work, 則實際的工作至少要經過指定的 jiffies  
  40. 纔會被執行。 這些函數若返回 1 則工作被成功加入到隊列; 若爲0,則意味着這個 work 已經在隊列中等待,不能再次加入*/  


 

在將來的某個時間, 這個工作函數將被傳入給定的 data 值來調用。這個函數將在工作線程的上下文運行, 因此它可以睡眠 (你應當知道這個睡眠可能影響提交給同一個工作隊列的其他任務) 工作函數不能訪問用戶空間,因爲它在一個內核線程中運行, 完全沒有對應的用戶空間來訪問。
取消一個掛起的工作隊列入口項可以調用:

int cancel_delayed_work(struct delayed_work *work);
void cancel_work_sync(struct work_struct *work)

如果這個入口在它開始執行前被取消,則返回非零。內核保證給定入口的執行不會在調用 cancel_delay_work 後被初始化. 如果 cancel_delay_work 返回 0, 但是, 這個入口可能已經運行在一個不同的處理器, 並且可能仍然在調用 cancel_delayed_work 後在運行. 要絕對確保工作函數沒有在 cancel_delayed_work 返回 0 後在任何地方運行, 你必須跟隨這個調用來調用:

void flush_workqueue(struct workqueue_struct *queue);

在 flush_workqueue 返回後, 沒有在這個調用前提交的函數在系統中任何地方運行。
而cancel_work_sync會取消相應的work,但是如果這個work已經在運行那麼cancel_work_sync會阻塞,直到work完成並取消相應的work。

當用完一個工作隊列,可以去掉它,使用:

void destroy_workqueue(struct workqueue_struct *queue);

三、與tasklet的聯繫

工作隊列類似 tasklets,允許內核代碼請求在將來某個時間調用一個函數,不同在於:
(1)tasklet 在軟件中斷上下文中運行,所以 tasklet 代碼必須是原子的。而工作隊列函數在一個特殊內核進程上下文運行,有更多的靈活性,且能夠休眠。
(2)tasklet 只能在最初被提交的處理器上運行,這只是工作隊列默認工作方式。
(3)內核代碼可以請求工作隊列函數被延後一個給定的時間間隔。
(4)tasklet 執行的很快, 短時期, 並且在原子態, 而工作隊列函數可能是長週期且不需要是原子的,兩個機制有它適合的情形。







代碼列子:

  1 #include<linux/module.h>
  2 #include<linux/init.h>
  3 #include<linux/interrupt.h>
  4
  5 MODULE_LICENSE("GPL");
  6
  7 struct tasklet_struct tasklet_test;
  8
  9 void tasklet_hander_function(unsigned long data)
 10 {
 11     printk("Hello tasklet\n");
 12     printk("tasklet data is %ld \n",data);
 13 }
 14
 15 static int __init tasklet_test_init(void)
 16 {
 17     printk("tasklet init\n");
 18     tasklet_init(&tasklet_test,tasklet_hander_function,tasklet_test.data);
 19     tasklet_test.data = 10;
 20     tasklet_schedule(&tasklet_test);
 21
 22     return 0;
 23 }
 24
 25 static void __exit tasklet_test_exit(void)
 26 {
 27     printk("tasklet exit\n");
 28     tasklet_kill(&tasklet_test);
 29 }
 30
 31 module_init(tasklet_test_init);

32 module_exit(tasklet_test_exit);

發佈了27 篇原創文章 · 獲贊 19 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章