第7篇 zephyr kernel之工作隊列Workqueue

目錄

摘要

1 工作隊列線程 Workqueue Threads

2 工作項生命週期Work Item Lifecycle

3 延時工作隊列 Delayed Work

4 觸發工作項Triggered Work

5 系統工作隊列System Workqueue

6 用戶定義一個工作隊列

7 提交一個工作項

8 提交一個延時工作項

9 參考鏈接


本學筆記基於zephyr 工程版本 2.2.99,主機環境爲ubuntu18.04,開發平臺 nrf52840dk_nrf52840

 

摘要

    熟悉Linux的童鞋可能對工作隊列比較熟悉,zephyr中的工作隊列與Linux的工作隊列功能類似,用於實現中斷的底半部。也就是說中斷ISR中比較耗時的操作,放到工作隊列中去執行。zephyr中工作隊列是基於線程的,簡單來說,就是有一個線程一直在等待工作隊列的api發來的工作項,當有工作項時(一個待執行的函數)就處理(把函數調用了),當有多個工作項時就按順序處理,沒有工作項時就休眠。

1 工作隊列線程 Workqueue Threads

   工作隊列是一個內核對象,用專用的線程以先進先出(first in, first out)的方式去處理被提交的工作元素(work item)。每個被處理的工作項會調用這個工作項指定的函數(通俗來講,工作項就是一個一個等待調用的函數)。工作隊列通常用於ISR或者高優先級線程把比較複雜的,非緊急的事情交給對時間不敏感的低於先級線程去處理。對Linux驅動比較瞭解的童鞋理解起來來叫簡單,使用方式類似於Linux的工作隊列和Tasklet。

    工作隊列使用之前必須被初始化,並且清空他的隊列創建一個工作隊列線程(都是初始化函數來完成的)。

2 工作項生命週期Work Item Lifecycle

    一個工作項(或者叫工作元素,我也不知道怎麼翻譯好,隨便叫吧 )使用之前必須被初始化,初始化就是把處理函數賦值給工作項,並且標記工作項是爲掛起的,等着工作隊列線程去調用。

    一個工作項可以從ISR或者線程中被提交,提交以後就是把工作項追加到工作隊列內部的隊列中,加入工作隊列以後,當工作隊列的線程會從他自己的隊列中按順序取出掛起的工作項,然後在執行這個工作項指定的函數。

   工作項中可是使用任何的內核的API,但是注意使用那些導致線程阻塞的API,比如睡眠,獲取信號量之類的API,這樣會導致工作隊列線程睡眠,那麼同時處於這個工作隊列的其他工作項,也將會被延時執行,因爲工作項是按順序,一個一個被串行處理的。如果ISR或者線程重複提交一個已經在工作隊列中已經掛起的工作項,那麼這個工作項不會受到影響,同時在工作隊列中的位置將不會受到影響。

  被提交的工作在沒有被處理之前,是不能被重新初始化的。

3 延時工作隊列 Delayed Work

   當一個ISR或者線程提交一個工作項,但是不想讓工作項立即被執行,想讓工作項等待一段時間在執行。這時可以使用延時工作項。簡單理解就是,一個工作項提交的時候不是直接提交給工作隊列,而是指定一個超時,當超時發生的時候,再由內核將這個工作項提交給工作隊列,並保持工作項爲掛起狀態等着工作隊列線程去處理。

4 觸發工作項Triggered Work

  觸發工作項是和POLL機制相關的工作項,用的比較少。POLL機制開發Linux應用的童鞋比較瞭解,在Linux中POLL機制是在一個線程中等待多個未就緒的文件描述有效,而zephyr是在一個線程中等待多個內核對象有效,比如等待信號量,FIFO等。

5 系統工作隊列System Workqueue

   在文中要區分工作隊列和工作項的區別,工作項(work item)只是一個被提交到工作隊列(work queue)的一個元素。如果內核使能了工作隊列的功能,內核會自動創建的一個被稱爲system workqueuede 的工作隊列。這個系統工作隊列的線程的優先級可以通過menuconfig去配置。相比於系統通過隊列,用戶也可以創建自己的工作隊列。比如有比較複雜然後特別耗時的事情需要做,那麼用戶可以自己創建一個工作隊列,然後把時間不敏感,不着急處理的事扔給自己創建的工作隊列。但是zephyr官方不推薦自己創建工作隊列,因爲工作隊列會消耗大量資源(RAM, flash),畢竟創建一個工作隊列就會創建一個線程,一個線程就會包括何種內核對象,私有棧空間等,一般zephyr是跑在資源受限的MCU上的,所以不太推薦。

6 用戶定義一個工作隊列

 工作隊列使用struct k_work_q類型去定義,工作隊列需要使用自己定義的棧,然後調用k_work_q_start()去初始化:

#define MY_STACK_SIZE 512
#define MY_PRIORITY 5

K_THREAD_STACK_DEFINE(my_stack_area, MY_STACK_SIZE);

struct k_work_q my_work_q;

k_work_q_start(&my_work_q, my_stack_area,
               K_THREAD_STACK_SIZEOF(my_stack_area), MY_PRIORITY);

7 提交一個工作項

    一個工作項在被提交之前需要調用k_work_init()去初始化,然後一個被初始化的工作項可以使用 k_work_submit()函數把工作項提交到系統工作隊列,也可以使用k_work_submit_to_queue()函數提交到用戶自己定義的工作隊列。

   下面代碼示例,一個ISR把打印信息提交給系統工作隊列去執行:

struct device_info {
    struct k_work work;
    char name[16]
} my_device;

void my_isr(void *arg)
{
    ...
    if (error detected) {
        k_work_submit(&my_device.work);
    }
    ...
}

void print_error(struct k_work *item)
{
    struct device_info *the_device =
        CONTAINER_OF(item, struct device_info, work);
    printk("Got error on device %s\n", the_device->name);
}

/* initialize name info for a device */
strcpy(my_device.name, "FOO_dev");

/* initialize work item for printing device's error messages */
k_work_init(&my_device.work, print_error);

/* install my_isr() as interrupt handler for the device (not shown) */
...

8 提交一個延時工作項

   一個延時工作項通過struct k_delayed_work去定義,並通過 k_delayed_work_init()去初始化,通過調用k_delayed_work_submit()把延時工作項提交給系統工作隊列,通過k_delayed_work_submit_to_queue()把延時工作項提交給用戶自己定義的工作隊列。當想取消一個超時工作項,可以使用 k_delayed_work_cancel()函數,但是需注意,取消只能在工作項指定的超時沒發生之前,否則不能被取消。

9 參考鏈接

https://docs.zephyrproject.org/latest/reference/kernel/threads/workqueue.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章