1. 程式人生 > >Linux裝置驅動之workqueue----中斷底半部機制

Linux裝置驅動之workqueue----中斷底半部機制

文章為本人學習筆記和總結,如有錯誤,請多多指教;

引言:

linux實現中斷底半部的機制主要有tasklet、工作佇列、軟中斷和執行緒化;

本文主要介紹下工作佇列---workqueue

1、工作佇列(workqueue)簡介

Linux中的Workqueue機制就是為了簡化核心執行緒的建立。通過呼叫workqueue的介面就能建立核心執行緒。並且可以根據當前系統CPU的個數建立執行緒的數量,使得執行緒處理的事務能夠並行化。workqueue是核心中實現簡單而有效的機制,他顯然簡化了核心daemon的建立,方便了使用者的程式設計.

      工作佇列(workqueue)是另外一種將工作推後執行的形式.工作佇列可以把工作推後,交由一個核心執行緒去執行,也就是說,這個下半部分可以在程序上下文中執行。最重要的就是工作佇列允許被重新排程甚至是睡眠;

由於每一個工作佇列都有一個或者多個專用的程序(核心執行緒),這些程序執行提交到該佇列的函式;

從表面看工作佇列(workqueue)類似於tasklet,它們都允許核心程式碼請求某個函式在將來的時間被呼叫。但兩者有很大差別:

  • tasklet在軟中斷上下文中執行,因此所有的tasklet程式碼必須是原子的;而工作佇列函式是在特殊的核心程序上下文中執行,因此具有更好的靈活性,如工作佇列可以呼叫和休眠
  • tasklet始終執行在被初始提交的同一CPU上,但這只是工作佇列的預設方式
  • 核心程式碼可以請求延時給定的時間間隔後執行工作佇列函式

兩者關鍵區別:tasklet會在很短時間段內執行,並且是原子模式的,而工作佇列函式具有更長延時並且不必原子化;

2、工作佇列(workqueue)使用

  •  工作(work)資料結構:
struct work_struct {  
    atomic_long_t data;       /*工作處理函式func的引數*/  
#define WORK_STRUCT_PENDING 0        /* T if work item pending execution */  
#define WORK_STRUCT_STATIC 1        /* static initializer (debugobjects) */  
#define WORK_STRUCT_FLAG_MASK (3UL)  
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)  
    struct list_head entry;        /*連線工作的指標*/  
    work_func_t func;              /*工作處理函式*/  
#ifdef CONFIG_LOCKDEP  
    struct lockdep_map lockdep_map;  
#endif  
}; 
//工作佇列執行函式的原型:
void (*work_func_t)(struct work_struct *work);
//該函式會由一個工作者執行緒執行,因此其在程序上下文中,可以睡眠也可以中斷。但只能在核心中執行,無法訪問使用者空間。
  • 工作將以佇列形式組織成工作佇列(workqueue),其資料結構如下:
struct workqueue_struct {
 struct cpu_workqueue_struct *cpu_wq;
 struct list_head list;
 const char *name;   /*workqueue name*/
 int singlethread;   /*是不是單執行緒 - 單執行緒我們首選第一個CPU -0表示採用預設的工作者執行緒event*/
 int freezeable;  /* Freeze threads during suspend */
 int rt;
}; 
  • 多CPU多執行緒工作佇列cpu_workqueue_struct 
truct cpu_workqueue_struct {
 spinlock_t lock;/*因為工作者執行緒需要頻繁的處理連線到其上的工作,所以需要枷鎖保護*/
 struct list_head worklist;
 wait_queue_head_t more_work;
 struct work_struct *current_work; /*當前的work*/
 struct workqueue_struct *wq;   /*所屬的workqueue*/
 struct task_struct *thread; /*任務的上下文*/
} ____cacheline_aligned;

綜上所述:工作有struct work_struct的型別,工作佇列有struct workqueue_struct的型別,該結構定義在<linux/workqueue.h>中。在使用前,我們必須顯示的建立一個工作佇列,介紹如下:

  • 建立工作佇列workqueue

在使用工作佇列之前,首先我們需要建立工作佇列,可以使用下面兩個函式之一:

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

兩個函式的區別:

使用create_workqueue()函式,核心則會在系統中的每一個CPU上為該工作佇列建立專用的執行緒;這種情況下,可能會對系統性能造成一定影響。

而如果單個執行緒能夠滿足要求,則建議使用create_singlethread_workqueue()函式建立工作佇列。

  • 提交工作work

1)、建立好工作佇列後,那麼如何提交工作到一個工作佇列,首先需要填充work_struct結構體,可以有如下方法:

DECLARE_WORK(name, void (*func)(void *), void *data)
INIT_WORK(struct work_struct *work, void (*func)(void *), void *data)
PREPARE_WORK(struct work_struct *work, void (*func)(void *), void *data)

區別如下:

DECLARE_WORK()是一個巨集定義,並且該巨集定義是在編譯時完成的;

如果要在執行時構造work_struct結構,應該使用後兩個巨集。INIT_WORK完成更加徹底的初始化工作,因此在首次使用該結構時,建議使用這個巨集。而PREPARE_WORK完成幾乎相同的工作,但是它不會初始化連結work_struct結構的工作佇列的指標;因此如果工作work_struct已經被提交到工作佇列中,而只是需要修改該結構,則應該使用PREPARE_WORK。

2)、然後要將工作提交到工作佇列,則使用下面兩個函式之一:

int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work,
                unsigned long delat);

它們都會將work新增到指定的workqueue。但是如果使用queue_delayed_work,則實際的工作至少會在經過指定的jiffies(由delay指定)之後才會被執行;如果工作被成功新增到佇列,則上述函式返回值為1,。返回值為非零時,則意味著給定的work_struct結構已經等待在該佇列中,從而不能新增兩次。