驅動開發中常常會啟動幾個核心線程,在整個驅動生命週期期間執行某些操作,比如USB驅動的控制線程,一直等待SCSI命令,沒有命令的話睡眠,有命令的話就喚醒線程,解析執行相關的命令。還有USB驅動中的掃描線程,如果有新的裝置串連到USB匯流排,則會啟動掃描過程,平時時候讓出CPU資源休眠。
常用的核心線程建立方法有3個,kernel_thread, kthread_create和kthread_run。使用這些函數或宏需要包括如下標頭檔:
#include <linux/sched.h>//wake_up_process()
#include <linux/kthread.h>//kthread_ceate(), kthread_run()
#include <err.h>//IS_ERR(), PTR_ERR()
這些方法建立的核心線程必須能夠自己放棄CPU資源,即不要產生死迴圈而不主動調用scheduel()函數,否則這樣CPU會一直忙,因為核心進程/線程是不可搶佔的,所以他必須自己能夠主動的放棄資源,不管通過什麼方式。
1. 標頭檔
#include <linux/sched.h> //wake_up_process()
#include <linux/kthread.h> //kthread_create()、kthread_run()
#include <err.h> //IS_ERR()、PTR_ERR()
2. 實現
2.1建立線程
在模組初始化時,可以進行線程的建立。使用下面的函數和宏定義:
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,
const char namefmt[], ...);
#define kthread_run(threadfn, data, namefmt, ...) \
({ \
struct task_struct *__k \
= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__k)) \
wake_up_process(__k); \
__k; \
})
例如:
static struct task_struct *test_task;
static int test_init_module(void)
{
int err;
test_task = kthread_create(test_thread, NULL, "test_task");
if(IS_ERR(test_task)){
printk("Unable to start kernel thread.\n");
err = PTR_ERR(test_task);
test_task = NULL;
return err;
}
wake_up_process(test_task);
return 0;
}
module_init(test_init_module);
2.2線程函數
線上程函數裡,完成所需的商務邏輯工作。主要架構如下所示:
int threadfunc(void *data){
…
while(1){
set_current_state(TASK_UNINTERRUPTIBLE);
if(kthread_should_stop()) break;
if(){//條件為真
//進行業務處理
}
else{//條件為假
//讓出CPU運行其他線程,並在指定的時間內重新被調度
schedule_timeout(HZ);
}
}
…
return 0;
}
2.3結束線程
在模組卸載時,可以結束線程的運行。使用下面的函數:
int kthread_stop(struct task_struct *k);
例如:
static void test_cleanup_module(void)
{
if(test_task){
kthread_stop(test_task);
test_task = NULL;
}
}
module_exit(test_cleanup_module);
3. 注意事項
(1) 在調用kthread_stop函數時,線程函數不能已經運行結束。否則,kthread_stop函數會一直進行等待。
(2) 線程函數必須能讓出CPU,以便能運行其他線程。同時線程函數也必須能重新被調度運行。在例子程式中,這是通過schedule_timeout()函數完成的。
4.效能測試
可以使用top命令來查看線程(包括核心線程)的CPU利用率。命令如下:
top –p 線程號
可以使用下面命令來尋找線程號:
ps aux|grep 線程名
可以用下面的命令顯示所有核心線程:
ps afx
註:線程名由kthread_create函數的第三個參數指定
*****************************************************************************************************
1 使用kthread_create建立線程:
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,
const char *namefmt, ...);
這個函數可以像printk一樣傳入某種格式的線程名
線程建立後,不會馬上運行,而是需要將kthread_create() 返回的task_struct指標傳給wake_up_process(),然後通過此函數運行線程。
2. 當然,還有一個建立並啟動線程的函數:kthread_run
struct task_struct *kthread_run(int (*threadfn)(void *data),
void *data,
const char *namefmt, ...);
3. 線程一旦啟動起來後,會一直運行,除非該線程主動調用do_exit函數,或者其他的進程調用kthread_stop函數,結束線程的運行。
int kthread_stop(struct task_struct *thread);
kthread_stop() 通過發送訊號給線程。
如果線程函數正在處理一個非常重要的任務,它不會被中斷的。當然如果線程函數永遠不返回並且不檢查訊號,它將永遠都不會停止。
參考:Kernel threads made easy
代碼
在執行kthread_stop的時候,目標線程必須沒有退出,否則會Oops。原因很容易理解,當目標線程退出的時候,其對應的task結構也變得無效,kthread_stop引用該無效task結構就會出錯。
為了避免這種情況,需要確保線程沒有退出,其方法如代碼中所示:
thread_func()
{
// do your work here
// wait to exit
while(!thread_could_stop())
{
wait();
}
}
exit_code()
{
kthread_stop(_task); //發訊號給task,通知其可以退出了
}
這種退出機制很溫和,一切盡在thread_func()的掌控之中,線程在退出時可以從容地釋放資源,而不是莫名其妙地被人“暗殺”。