Linux中的工作隊列

來源:互聯網
上載者:User

轉自:http://tanatseng.blog.163.com/blog/static/174991629201132734828701/

工作隊列一般用來做滯後的工作,比如在中斷裡面要做很多事,但是比較耗時,這時就可以把耗時的工作放到工作隊列。說白了就是系統延時調度的一個自訂函數。

 
工作隊列是實現延遲的新機制,從 2.5 版本 Linux 核心開始提供該功能。不同於微線程一步到位的延遲方法,工作隊列採用通用的延遲機制, 工作隊列的處理常式函數能夠休眠(這在微線程模式下無法實現)。 工作隊列可以有比微線程更高的時延,並為任務延遲提供功能更豐富的 API。 從前,延遲功能通過 keventd 對任務排隊來實現, 但是現在由核心背景工作執行緒 events/X 來管理。
 
工作隊列提供一個通用的辦法將任務延遲到 bottom halves。 處於核心的是工作隊列(結構體 workqueue_struct), 任務被安排到該結構體當中。 任務由結構體 work_struct 來說明, 用來鑒別哪些任務被延遲以及使用哪個延遲函數(參見 圖 3)。 events/X 核心線程(每 CPU 一個)從工作隊列中抽取任務並啟用一個 bottom-half 處理常式(由處理常式函數在結構體 work_struct 中指定)。
 
 
 工作隊列背後的處理過程
 
 
由於 work_struct 中指出了要採用的處理常式函數, 因此可以利用工作隊列來為不同的處理常式進行任務排隊。 現在,讓我們看一下能夠用於工作隊列的 API 函數。
 
工作隊列 API
 
工作隊列 API 比微線程稍複雜,主要是因為它支援很多選項。 我們首先探討一下工作隊列,然後再看一下任務和變體。
 
通過 圖 3 可以回想工作隊列的核心結構體是隊列本身。 該結構體用於將任務安排出 top half ,進入 bottom half ,從而延遲它的執行。 工作隊列通過宏調用產生 create_workqueue,返回一個 workqueue_struct 參考值。當用完一個工作隊列,可以通過調用函數 destroy_workqueue 來去掉它(如果需要):
 
struct workqueue_struct *create_workqueue( name );
struct workqueue_struct *create_singlethread_workqueue(const char *name);
void destroy_workqueue( struct workqueue_struct * );
 
一個工作隊列必須明確的在使用前建立,若使用 create_workqueue, 就得到一個工作隊列它在系統的每個處理器上有一個專用的線程。在很多情況下,過多線程對系統效能有影響,如果單個線程就足夠則使用 create_singlethread_workqueue 來建立工作隊列。
通過工作隊列與之通訊的任務可以由結構體 work_struct 來定義。 通常,該結構體是用來進行任務定義的結構體的第一個元素(後面有相關例子)。 工作隊列 API 提供三個函數來初始化任務(通過一個事先分配的緩衝); 參見 清單 6。 宏 INIT_WORK 提供必需的初始化資料以及處理常式函數的配置(由使用者傳遞進來)。 如果開發人員需要在任務被排入工作隊列之前發生延遲,可以使用宏 INIT_DELAYED_WORK 和 INIT_DELAYED_WORK_DEFERRABLE。
 
清單 6. 任務初始化宏
                           
INIT_WORK( work, func );
INIT_DELAYED_WORK( work, func );
INIT_DELAYED_WORK_DEFERRABLE( work, func );
 
INIT_* 做更加全面的初始化結構的工作,在第一次建立結構時使用。
任務結構體的初始化完成後,接下來要將任務安排進工作隊列。 可採用多種方法來完成這一操作(參見 清單 7)。 首先,利用 queue_work 簡單地將任務安排進工作隊列(這將任務綁定到當前的 CPU)。 或者,可以通過 queue_work_on 來指定處理常式在哪個 CPU 上運行。 兩個附加的函數為延遲任務提供相同的功能(其結構體裝入結構體 work_struct 之中,並有一個 計時器用於任務延遲 )。
 
清單 7. 工作隊列函數
                           
int queue_work( struct workqueue_struct *wq, struct work_struct *work );
int queue_work_on( int cpu, struct workqueue_struct *wq, struct work_struct *work );
int queue_delayed_work( struct workqueue_struct *wq,
                     struct delayed_work *dwork, unsigned long delay );
int queue_delayed_work_on( int cpu, struct workqueue_struct *wq,
                     struct delayed_work *dwork, unsigned long delay );
 
每個都添加work到給定的workqueue。如果使用 queue_delay_work, 則實際的工作至少要經過指定的 jiffies 才會被執行。 這些函數若返回 1 則工作被成功加入到隊列; 若為0,則意味著這個 work 已經在隊列中等待,不能再次加入。
可以使用全域的核心全域工作隊列,利用 4 個函數來為工作隊列定位。 這些函數(見 清單 8)類比 清單 7,只是不需要定義工作隊列結構體。
 
清單 8. 核心全域工作隊列函數
                           
int schedule_work( struct work_struct *work );
int schedule_work_on( int cpu, struct work_struct *work );
int scheduled_delayed_work( struct delayed_work *dwork, unsigned long delay );
int scheduled_delayed_work_on(
              int cpu, struct delayed_work *dwork, unsigned long delay );
 
還有一些協助函數用於清理或取消工作隊列中的任務。想清理特定的任務項目並阻塞任務, 直到任務完成為止, 可以調用 flush_work 來實現。 指定工作隊列中的所有任務能夠通過調用 flush_workqueue 來完成。 這兩種情形下,調用者阻塞直到操作完成為止。 為了清理核心全域工作隊列,可調用 flush_scheduled_work。
 
int flush_work( struct work_struct *work );
int flush_workqueue( struct workqueue_struct *wq );
void flush_scheduled_work( void );
 
在 flush_workqueue 返回後, 沒有在這個調用前提交的函數在系統中任何地方運行。
還沒有在處理常式當中執行的任務可以被取消。 調用 cancel_work_sync 將會終止隊列中的任務或者阻塞任務直到回調結束(如果處理常式已經在處理該任務)。 如果任務被延遲,可以調用 cancel_delayed_work_sync。
 
int cancel_work_sync( struct work_struct *work );
int cancel_delayed_work_sync( struct delayed_work *dwork );
 
最後,可以通過調用 work_pending 或者 delayed_work_pending 來確定任務項目是否在進行中。
 
work_pending( work );
delayed_work_pending( work );
 
這就是工作隊列 API 的核心。在 ./kernel/workqueue.c 中能夠找到工作隊列 API 的實現方法, API 在 ./include/linux/workqueue.h 中定義。 下面我們看一個工作隊列 API 的簡單例子。
 
工作隊列簡單例子
 
下面的例子說明了幾個核心的工作隊列 API 函數。 如同微線程的例子一樣,為方便起見,可將這個例子部署在核心模組上下文。
 
首先,看一下將用於實現 bottom half 的任務結構體和處理常式函數(參見 清單 9)。 首先您將注意到工作隊列結構體參考的定義 (my_wq)以及 my_work_t 的定義。 my_work_t 類型定義的頭部包括結構體 work_struct 和一個代表任務項目的整數。 處理常式(回呼函數)將 work_struct 指標引用改為 my_work_t 類型。 發送出任務項目(來自結構體的整數)之後,任務指標將被釋放。
 
清單 9. 任務結構體和 bottom-half 處理常式
                           
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/workqueue.h>
 
MODULE_LICENSE("GPL");
 
static struct workqueue_struct *my_wq;
 
typedef struct {
  struct work_struct my_work;
  int    x;
} my_work_t;
 
my_work_t *work, *work2;
 
static void my_wq_function( struct work_struct *work)
{
  my_work_t *my_work = (my_work_t *)work;
  printk( "my_work.x %d\n", my_work->x );
  kfree( (void *)work );
  return;
}
 
清單 10 是 init_module 函數, 該函數從使用 create_workqueue API 函數產生工作隊列開始。 成功產生工作隊列之後,建立兩個任務項目(通過 kmalloc 來分配)。 利用 INIT_WORK 來初始化每個任務項目,任務定義完成, 接著通過調用 queue_work 將任務安排到工作隊列中。 top-half 進程(在此處類比)完成。如同清單 10 中所示,任務有時會晚些被處理常式處理。
 
 
清單 10. 工作隊列和任務建立
                           
int init_module( void )
{
  int ret;
 
  my_wq = create_workqueue("my_queue");
  if (my_wq) {
 
   /* Queue some work (item 1) */
    work = (my_work_t *)kmalloc(sizeof(my_work_t), GFP_KERNEL);
    if (work) {
      INIT_WORK( (struct work_struct *)work, my_wq_function );
      work->x = 1;
      ret = queue_work( my_wq, (struct work_struct *)work );
    }
 
    /* Queue some additional work (item 2) */
    work2 = (my_work_t *)kmalloc(sizeof(my_work_t), GFP_KERNEL);
    if (work2) {
      INIT_WORK( (struct work_struct *)work2, my_wq_function );
      work2->x = 2;
      ret = queue_work( my_wq, (struct work_struct *)work2 );
    }
  }
 
  return 0;
}
 
最終的元素在 清單 11 中展示。 在模組清理過程中,會清理一些特別的工作隊列(它們將保持阻塞狀態直到處理常式完成對任務的處理), 然後銷毀工作隊列。
 
清單 11. 工作隊列清理和銷毀
                           
void cleanup_module( void )
{
  flush_workqueue( my_wq );
  destroy_workqueue( my_wq );
  return;
}
 
參考:
核心的工作隊列使用方法
 
工作隊列一般用來做滯後的工作,比如在中斷裡面要做很多事,但是比較耗時,這時就可以把耗時的工作放到工作隊列。說白了就是系統延時調度的一個自訂函數。
 
1、定義struct work_struct irq_queue;
 
2、初始化INIT_WORK(&irq_queue,do_irq_queuework);
 
3、調用方法:schedule_work(&rq_queue);
 
注,調用完畢後系統會釋放此函數,所以如果想再次執行的話,就再次調用schedule_work()即可。
 
另外,核心必須掛載檔案系統才可以使用工作隊列。我的理解是:工作隊列也屬於調度,如果核心掛了,他就不調度了,當然就不能用工作隊列了。
 
工作隊列介面
  工作隊列介面是在2.5的開發過程中引入的,用於取代任務隊列介面(用於調度核心任務)。每個工作隊列有一個專門的線程,
所有來自運行隊列的任務在進程的上下文中運行(這樣它們可以休眠)。驅動程式可以建立並使用它們自己的工作隊列,或者使用核心的一個工作隊列。
工作隊列用以下方式建立:
  struct workqueue_struct *create_workqueue(const char *name); 在這裡 name 是工作隊列的名字。
 
  工作隊列任務可以在編譯時間或者運行時建立。任務需要封裝為一個叫做 work_struct 的結構體。在編譯期初始化一個工作隊列任務時要用到:
  DECLARE_WORK(name, void (*function)(void *), void *data); 在這裡 name 是 work_struct 的名字,function 是當任務被調度時調用的函數,data 是指向那個函數的指標。
 
  在運行期初始化一個工作隊列時要用到:
  INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
 
    用下面的函數調用來把一個作業(一個類型為work_struct 結構的工作隊列作業/任務)加入到工作隊列中:
  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 delay);
  在queue_delay_work()中指定delay,是為了保證至少在經過一段給定的最小延遲時間以後,工作隊列中的任務才可以真正執行。
 
  工作隊列中的任務由相關的背景工作執行緒執行,可能是在一個無法預期的時間(取決於負載,中斷等等),或者是在一段延遲以後。任何一個在工作隊列中等待了無限長的時間也沒有啟動並執行任務可以用下面的方法取消:
  int cancel_delayed_work(struct work_struct *work);
 
    如果當一個取消操作的調用返回時,任務正在執行中,那麼這個任務將繼續執行下去,但不會再加入到隊列中。清空工作隊列中的所有任務使用:
  void flush_workqueue(struct workqueue_struct *queue);
   
    銷毀工作隊列使用:
  void destroy_workqueue(struct workqueue_struct *queue);
    不是所有的驅動程式都必須有自己的工作隊列。驅動程式可以使用核心提供的預設工作隊列。由於這個工作隊列由很多驅動程式共用,
任務可能會需要比較長一段時間才能開始執行。為瞭解決這一問題,工作函數中的延遲應該保持最小或者乾脆不要。
 
  需要特別注意的是預設隊列對所有驅動程式來說都是可用的,但是只有經過GP許可的驅動程式可以用自訂的工作隊列:
  int schedule_work(struct work_struct *work); -- 向工作隊列中添加一個任務
    int schedule_delayed_work(struct work_struct *work, unsigned long delay); -- 向工作隊列中添加一個任務並順延強制
 
  當模組被缷載時應該去調用一個 flash_scheduled_work() 函數,這個函數會使等待隊列中所有的任務都被執行。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.