轉載自:
https://blog.csdn.net/qqliyunpeng/article/details/53931350
作者: 李雲鵬([email protected])
1. 簡介:
在spi驅動中用到了內核的線程,用的函數就是跟 kthread_worker 和 kthread_work 相關的函數,對於這兩個名詞的翻譯,在網上暫時沒有找到合適的,先翻譯成線程內核線程相關的:工人和工作,這麼直白的翻譯是根據其工作原理相關的,本來想翻譯成別的,一想到他的實現方式,直白的翻譯,更能讓人理解。
此部分介紹的函數主要在 include/linux/kthread.h 文件,這裏可以推測,也許是內核爲了方便我們使用內核的線程,而設計的kthread_work和kthread_worker。
2. 函數:
2.1 先來看這兩個結構體:
kthread_worke和
kthread_worker:
- struct kthread_worker {
- spinlock_t lock;
- struct list_head work_list;
- struct task_struct *task;
- struct kthread_work *current_work;
- };
- struct kthread_work {
- struct list_head node;
- kthread_work_func_t func;
- wait_queue_head_t done; // 等待隊列,內部成員是一個鎖和一個鏈表節點
- struct kthread_worker *worker;
- };
【1】其中的 wait_queue_head_t 結構體需要解析一下:
- struct __wait_queue_head { // 是一個帶鎖的鏈表節點
- spinlock_t lock;
- struct list_head task_list;
- };
- typedef struct __wait_queue_head wait_queue_head_t;
2.2 聲明:
DEFINE_KTHREAD_WORK宏和
DEFINE_KTHREAD_WORKER宏:
- #define KTHREAD_WORKER_INIT(worker) { \
- .lock = __SPIN_LOCK_UNLOCKED((worker).lock), \ // 初始化worker中lock
- .work_list = LIST_HEAD_INIT((worker).work_list), \ // 初始化worker中的鏈表節點 work_list
- }
- #define KTHREAD_WORK_INIT(work, fn) { \
- .node = LIST_HEAD_INIT((work).node), \ // 初始化work中的鏈表節點 node (next和pre指針指向自己的首地址)
- .func = (fn), \ // func成員賦值
- .done = __WAIT_QUEUE_HEAD_INITIALIZER((work).done), \ // 初始化 done 成員 (初始化等待隊列中的鎖和鏈表節點,
- } // 鏈表節點的初始化就是next和pre指針指向節點的首地址)
- #define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \
- .lock = __SPIN_LOCK_UNLOCKED(name.lock), \
- .task_list = { &(name).task_list, &(name).task_list } }
- #define DEFINE_KTHREAD_WORKER(worker) \
- struct kthread_worker worker = KTHREAD_WORKER_INIT(worker)
- #define DEFINE_KTHREAD_WORK(work, fn) \
- struct kthread_work work = KTHREAD_WORK_INIT(work, fn)
2.3 初始化
init_kthread_work宏和
init_kthread_worker宏:
- #define init_kthread_worker(worker) \ // 初始化 kthread_worker
- do { \
- static struct lock_class_key __key; \
- __init_kthread_worker((worker), "("#worker")->lock", &__key); \
- } while (0)
- #define init_kthread_work(work, fn) \ // 初始化 kthread_work
- do { \
- memset((work), 0, sizeof(struct kthread_work)); \
- INIT_LIST_HEAD(&(work)->node); \ // 初始化成員 node 鏈表節點
- (work)->func = (fn); \ // 將回調函數的指針指向fn函數,內核線程將會一直執行的函數
- init_waitqueue_head(&(work)->done); \ // 初始化成員 done (等待隊列)
- } while (0)
- void __init_kthread_worker(struct kthread_worker *worker,
- const char *name,
- struct lock_class_key *key)
- {
- spin_lock_init(&worker->lock);
- lockdep_set_class_and_name(&worker->lock, key, name); // 跟防止死鎖有關,此處不深究
- INIT_LIST_HEAD(&worker->work_list); // 初始化 work_list 鏈表節點
- worker->task = NULL;
- }
2.4 內核線程一直執行的函數
kthread_worker_fn函數:
- /**
- * kthread_worker_fn - kthread 函數目的是執行 kthread_worker中work_list下的work,此函數是作爲內核線程中一直執行的函數
- * @worker_ptr: 指向初始化了的 kthread_worker
- */
- int kthread_worker_fn(void *worker_ptr)
- {
- struct kthread_worker *worker = worker_ptr;
- struct kthread_work *work;
- WARN_ON(worker->task);
- worker->task = current;
- repeat:
- set_current_state(TASK_INTERRUPTIBLE); /* mb paired w/ kthread_stop */
- if (kthread_should_stop()) { // 如果接收到線程停止的信號
- __set_current_state(TASK_RUNNING);
- spin_lock_irq(&worker->lock);
- worker->task = NULL;
- spin_unlock_irq(&worker->lock);
- return 0;
- }
- work = NULL;
- spin_lock_irq(&worker->lock);
- if (!list_empty(&worker->work_list)) { // 如果 worker中的work_list鏈表不是空的
- work = list_first_entry(&worker->work_list, // 取出頭結點後邊的第一個結構體kthread_work
- struct kthread_work, node);
- list_del_init(&work->node); // 刪除鏈表中的入口
- }
- worker->current_work = work; // 將正在處理的work地址賦給 worker中的current_work成員
- spin_unlock_irq(&worker->lock);
- if (work) { // 如果有 work
- __set_current_state(TASK_RUNNING); // 啓動內核線程
- work->func(work); // 運行work中的func函數
- } else if (!freezing(current)) // 如果沒有work要做,並且沒有freeze,則主動請求調度,主動放棄cpu時間片
- schedule();
- try_to_freeze();
- goto repeat; // 無限循環
- }
【2】要知道的是kthread是內核線程,是一直運行在內核態的線程
【3】這個函數一般是作爲回調函數使用,比如spi.c中的如下程序
- master->kworker_task = kthread_run(kthread_worker_fn,
- &master->kworker,
- dev_name(&master->dev));
- /**
- * kthread_run - 創建並喚醒一個內核線程
- * @threadfn: the function to run until signal_pending(current).
- * @data: data ptr for @threadfn.
- * @namefmt: printf-style name for the thread.
- *
- * Description: Convenient wrapper for kthread_create() followed by
- * wake_up_process(). Returns the kthread or ERR_PTR(-ENOMEM).
- */
- #define kthread_run(threadfn, data, namefmt, ...) ...(此處省略)
2.5 隊列化kthread_work
queue_kthread_work 函數:
- /**
- * queue_kthread_work - 隊列化一個 kthread_work,實質是將work中的node節點掛接到worker中的work_list後邊,並嘗試喚醒worker中的任務
- * @worker: 目標 kthread_worker
- * @work: 要隊列化的 kthread_work
- *
- * 隊列化 @work 目的是爲了讓任務異步執行. @task
- * 必須已經被 kthread_worker_create() 創建了.
- * 隊列化成功,返回true,不成功返回false
- */
- bool queue_kthread_work(struct kthread_worker *worker,
- struct kthread_work *work)
- {
- bool ret = false;
- unsigned long flags;
- spin_lock_irqsave(&worker->lock, flags);
- if (list_empty(&work->node)) { // 這裏保證要插入到worker中鏈表節點的work的node節點一定要是一個獨立的,不能是一串
- insert_kthread_work(worker, work, &worker->work_list);
- ret = true;
- }
- spin_unlock_irqrestore(&worker->lock, flags);
- return ret;
- }
- /* 在@worker中的work_list鏈表中的@pos位置的後邊插入@work中的鏈表節點 */
- static void insert_kthread_work(struct kthread_worker *worker,
- struct kthread_work *work,
- struct list_head *pos)
- {
- lockdep_assert_held(&worker->lock);
- list_add_tail(&work->node, pos);
- work->worker = worker; // work中的worker指針指向worker
- if (likely(worker->task))
- wake_up_process(worker->task); // 嘗試喚醒一下 worker 中的task指向的線程來處理work
- }
2.6 執行完worker中的work
flush_kthread_worker 函數:
- struct kthread_flush_work {
- struct kthread_work work;
- struct completion done;
- };
- static void kthread_flush_work_fn(struct kthread_work *work)
- {
- struct kthread_flush_work *fwork =
- container_of(work, struct kthread_flush_work, work);
- complete(&fwork->done); // 喚醒完成量
- }
- /**
- * flush_kthread_worker - flush all current works on a kthread_worker
- * @worker: worker to flush
- *
- * Wait until all currently executing or pending works on @worker are
- * finished.
- */
- void flush_kthread_worker(struct kthread_worker *worker)
- {
- struct kthread_flush_work fwork = {
- KTHREAD_WORK_INIT(fwork.work, kthread_flush_work_fn),
- COMPLETION_INITIALIZER_ONSTACK(fwork.done), // ON_STACK後綴相當於加了static
- };
- queue_kthread_work(worker, &fwork.work); // 將 fwork中的work成員的node節點過接到worker_list下,並嘗試喚醒線程進行kthread_flush_work_fn函數的執行
- wait_for_completion(&fwork.done); // 調用這個函數的線程睡眠等待在這裏,等待執行worker中work_list下的fulsh_kthread_work完kthread_flush_work_fn函數
- }
2.7 總結的一張圖:
說了半天,其實woker和work的關係還是很難理解的,當我們經過一段時間,再次看的時候,難免還要花很長時間,因此,我畫了一張圖:
【1】worker中的task執行的是各自work中的func指定的函數,此規則同樣適用於kthread_flush_work
【2】kthread_flush_work中的函數是kthread.c文件指定的函數,而kthread_work中的函數是用戶自己定義的函數
【3】每次喚醒線程執行的work都是worker中的work_list下掛載的正常順序的第一個
【4】如何實現等待全部的work都執行完呢?調用的是flush_kthread_worker函數中的wait_for_completion(&fwork.done);語句,只有當前邊的work都執行完,才能輪到kthread_flush_work中的kthread_flush_work_fn的執行,此函數就是喚醒kthread_flush_work中的done。從而確定了前邊的work都執行完了