Linux內核實現透視---kthread_work
內核線程工作隊列
內核線程工作隊列和普通工作隊列看著十分相似,很多抽象概念如work和worker等都很相同并且執行對象也都是內核線程。不同的是內核線程工作隊列沒有普通工作隊列的線程池概念一個 worker(工作者(工作組))對應到一個實際的內核線程,這個內核線程會按順序依次執行worker上的每一個work。這個worker 對應的內核線程是當前CPU節點上的,因此可以推導出這個worker下的work都是在同一個CPU上執行的--TODO1:負載均衡會遷移嗎?來看下數據結構:
工作組(工作者)
struct kthread_worker {
unsigned int flags;
// 隊列操作的鎖
spinlock_t lock;
// work 隊列
struct list_head work_list;
// delay work 隊列
struct list_head delayed_work_list;
//worker 對應的內核線程
struct task_struct *task;
// 當前正在處理的work
struct kthread_work *current_work;
};
一個工作(work)
struct kthread_work {
struct list_head node;
kthread_work_func_t func;
struct kthread_worker *worker;
/* Number of canceling calls that are running at the moment. */
int canceling;
};
一個工作組中包括兩個工作list,一個是普通的工作list另一個是延時執行的工作隊列。使用時user可以自己定義內核線程工作者線程的實現,而默認的內核工作隊列處理程序由內核在kernel\kthread.c 文中實現在kthread_worker_fn 中如下,當調用kthread_create_worker_on_cpu 創建內核線程時實際上這個就是這個內核線程的執行體,當然也可以通過kthread_run 宏函數并將這個函數作為第一個入參傳入,此時內核線程的執行體也是如下的接口。
內核線程工作隊列處理
int kthread_worker_fn(void *worker_ptr)
{
struct kthread_worker *worker = worker_ptr;
struct kthread_work *work;
/*
* FIXME: Update the check and remove the assignment when all kthread
* worker users are created using kthread_create_worker*() functions.
*/
WARN_ON(worker->task && worker->task != current);
worker->task = current;
//休眠相關的
if (worker->flags & KTW_FREEZABLE)
set_freezable();
//未持有鎖時內核線程可以被中斷
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
work = NULL;
spin_lock_irq(&worker->lock);
if (!list_empty(&worker->work_list)) {
work = list_first_entry(&worker->work_list,
struct kthread_work, node);
list_del_init(&work->node);
}
worker->current_work = work;
spin_unlock_irq(&worker->lock);
//找到了第一個work開始處理
if (work) {
__set_current_state(TASK_RUNNING);
work->func(work);
//沒有work需要處理則放棄時間片
} else if (!freezing(current))
schedule();
//未使能KTW_FREEZABLE時可能調用這里
try_to_freeze();
cond_resched();
goto repeat;
}
這是內核工作線程的工作的處理過程,細心點就揮發下這里僅僅處理了普通的work,而delay_workw未進行處理。實際上delay work不需要內核線程處理,相反的延時工作的調用依賴內核的定時器timer由定時器到期時回調從而進行處理。
使用
內核線程的工作隊列使用時可以直接使用內核定義的worker,也可以自己創建對應的kworker 使用獨立內核線程處理works 從而保證work的及時性。
示例:
struct kthread_worker kworker;
struct task_struct *kworker_task;
struct kthread_work work;
//初始化工作組
kthread_init_worker(&kworker);
//創建一個內核線程并運行kthread_worker_fn 參數是kworker 后面的參數是線程名稱
kworker_task = kthread_run(kthread_worker_fn, &kworker,"%s", "xxxx");
//初始化線程
kthread_init_work(&work, kthread_work_func_t fun);
//向工作組添加一個工作,這個接口可能喚醒內核線程,執行完成后這個工作就被刪除了
kthread_queue_work(kworker, &work);
//添加一個延時內核工作
kthread_queue_delayed_work(kworker, &work,delay)
//停止內核線程,依賴內核線程中調用(kthread_should_stop)檢查,這個接口會阻塞直到內核線程退出
kthread_stop(kworker_task)
內部實現
- kworker的創建
當新建一個內核工作線程時,如kthread_run 接口,此時會喚醒內內核的kthreadadd 線程,有這個線程完成新內核工作線程的創建。 - kworker 銷毀
當驅動 退出需要銷毀kthread時,通過調用 kthread_destroy_worker接口來完成,這個接口的內部實現流程是
a. flush 這個worker queue上的所有work
b. 停止worker對應的線程
c. 釋放worker mem
a 步驟的實現就是通過在當前worker上添加一個特殊的work,等待這個work處理完成,此時就說明所有的work都被處理完成了。然后就可以執行后續
的銷毀動作。
API
除了上面的經常使用API接口外還有一些不常用的API 接口這里簡單記錄下
//釋放進程控制塊內存
void free_kthread_struct(struct task_struct *k);
//將線程綁定到指定cpu上
void kthread_bind(struct task_struct *k, unsigned int cpu);
//將線程綁定到幾個cpu上用掩碼
void kthread_bind_mask(struct task_struct *k, const struct cpumask *mask);
//標記內核線程停止并等待線程停止
int kthread_stop(struct task_struct *k);
//檢查線程是否要停止
bool kthread_should_stop(void);
//檢查線程是否要park
bool kthread_should_park(void);
//睡眠使用停止模式
bool kthread_freezable_should_stop(bool *was_frozen);
//獲取線程data
void *kthread_data(struct task_struct *k);
//
void *kthread_probe_data(struct task_struct *k);
//標記線程park
int kthread_park(struct task_struct *k);
//取消標記park
void kthread_unpark(struct task_struct *k);
// 等待park標記是否清除
void kthread_parkme(void);
//內核線程的管理接口
int kthreadd(void *unused);
總結
總的來說內核線程隊列的實現和普通工作隊列[http://www.rzrgm.cn/w-smile/p/13499279.html]的相比實現上簡單的一些。使用內核線程的更加清晰明了,內核在kthreadd維護了內核線程隊列,各個worker的執行體維護了自己的work list。內核線程工作隊列和普通工作隊列相比沒有線程池的概念因此內核線程工作隊列中的work是不支持并發,因此設計work 函數時不用考慮重入的場景。普通工作隊列的的實現上增加了工作組的線程池的概念從而支持并發(不同work并發),二者的區別和共同點都很明顯。
共同點:本質上都是內核線程來處理的;也都支持延遲工作。
不同點:增加了線程池的管理,所以工作隊列中work可能會被多個內核線程處理,因此會出現work(不同work)并發,同時消耗的系統資源也更加多,但是對于多work并發的能力更強。

浙公網安備 33010602011771號