文章目錄
核心線程和普通的進程間的區別在於核心線程沒有獨立的地址空間,它只在 核心空間運行,從來不切換到使用者空間去;並且和普通進程一樣,可以被調度,也可以被搶佔。
一 線程的建立
struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...); 線程建立後,不會馬上運行,而是需要將kthread_create() 返回的task_struct指標傳給wake_up_process()才能驅動線程。 也可以使用kthread_run建立線程並啟動線程 struct task_struct *kthread_run(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; \})
可見kthread_run是在調用了kthread_create後執行了wake_up_process.
在非核心線程中調用kernel_thread,必須在調用daemonize(...)來釋放資源,成為真正的核心線程,kthread_create實際調用kernel_thread但是內部已經做了處理,不需要自己調用daemonize。
二 線程的退出
kthread_stop:設定線程的退出標記(線程函數內應用int kthread_should_stop(void)函數,當返回真時應退出函數),kthread_stop會一直等待至線程結束,線程結束前會發送完成結束給kthread_stop,如果直接使用do_exit直接退出線程那麼kthread_stop不會收到完成訊號將一直等待下去。如果線程已經退出那麼kthread_stop會先設定退出標記再喚醒一下thread,喚醒線程後會判斷退出標記因此設定的處理函數不會被調用。如果線程已經被喚醒並已經退出那麼kthread_stop會一直等待。
int kthread_stop(struct task_struct *thread); 如果處理函數沒用kthread_should_stop判斷退出,那麼 kthread_stop會一直等待處理函數主動退出。
三 源碼分析 這裡使用的核心版本是2.6.21.5
3.1 管理調度其它的核心線程kthread
使用ps命令可以查看有個名叫kthread的進程,它在核心初始化的時候被建立。
static __init int helper_init(void) { //建立一個單線程的共用列隊 helper_wq = create_singlethread_workqueue("kthread"); BUG_ON(!helper_wq); return 0; } core_initcall(helper_init);
就是這個共用列隊kthread_create會定義一個工作,在工作內建立建立具體的線程。
3.2 kthread_create建立線程
再看kthread_create前先看下kthread_create_info結構,每個線程建立時使用。
struct kthread_create_info{/* Information passed to kthread() from keventd. */int (*threadfn)(void *data); //線程處理函數void *data; //線程參數struct completion started; //在工作中等待kernel_thread建立線程完成,線程建立完後線程會通知工作繼續。/* Result passed back to kthread_create() from keventd. */struct task_struct *result; // started當收到線程建立完訊號started後,用來存放建立的任務結構體struct completion done; // 工作者線程加入一個工作後會等待工作做完,這個工作只是建立線程。 struct work_struct work; // 建立線程的工作,具體工作看後面源碼};
/** * kthread_create - 建立一個線程. * @threadfn: the function to run until signal_pending(current). * @data: data ptr for @threadfn. * @namefmt: printf-style name for the thread. * * 描述:這個協助函數建立並命名一個核心線程,線程建立後並不運行,使用wake_up_process() 函數來運行,參考kthread_run(), kthread_create_on_cpu() * *被喚醒後,線程調用threadfn()函數data作為參數,如果是獨立線程沒有其他線程調用 kthread_stop()那麼可以直接使用do_exit(),或當檢測到kthread_should_stop()返回真時(kthread_stop()已被調用了)返回處理函數 , 應返回0或負數,傳回值會傳給 kthread_stop()返回。 */struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...){struct kthread_create_info create; //下面五行初始化kthread_create_infocreate.threadfn = threadfn; create.data = data;init_completion(&create.started);init_completion(&create.done);INIT_WORK(&create.work, keventd_create_kthread); //可見建立的工作是在keventd_create_kthread函數內進行/*The workqueue needs to start up first:*/if (!helper_wq) //這個系統啟動後正常是已經初始化了的create.work.func(&create.work); //如沒初始化那隻有在當前進程下完成工作了而不是在kthread 裡else {queue_work(helper_wq, &create.work); //將工作加入列隊並調度wait_for_completion(&create.done); //等待工作執行完,執行完後create.result返回建立的任務結構或錯誤,由於工作是在kthread 裡執行所以必須等待工作做完才能返回}if (!IS_ERR(create.result)) {va_list args;va_start(args, namefmt);vsnprintf(create.result->comm, sizeof(create.result->comm), namefmt, args);va_end(args);}return create.result;}
上面看到建立工作是在keventd_create_kthread函數裡,那麼看下keventd_create_kthread函數
/* We are keventd: create a thread. 這個函數工作在keventd核心線程中*/static void keventd_create_kthread(struct work_struct *work){struct kthread_create_info *create =container_of(work, struct kthread_create_info, work);int pid;/* We want our own signal handler (we take no signals by default)*/ /*我們使用自己的訊號處理,預設不處理訊號*/ pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);//在這裡建立函數,線程處理函數為kthread函數,參數為struct kthread_create_info指標create。if (pid < 0) {create->result = ERR_PTR(pid);} else {wait_for_completion(&create->started); //等待建立的線程執行,線程執行後會發送完成訊號create->startedread_lock(&tasklist_lock);create->result = find_task_by_pid(pid);read_unlock(&tasklist_lock);}complete(&create->done);}
這時kthread_create在等待create->done訊號,核心線程keventd在等待線程建立完create->started。上面建立了線程,處理函數為kthread
static int kthread(void *_create){struct kthread_create_info *create = _create;int (*threadfn)(void *data);void *data;sigset_t blocked;int ret = -EINTR;kthread_exit_files();/* Copy data: it's on keventd's stack */threadfn = create->threadfn;data = create->data;/* Block and flush all signals (in case we're not from keventd). 阻塞全部訊號*/sigfillset(&blocked);sigprocmask(SIG_BLOCK, &blocked, NULL);flush_signals(current);/* By default we can run anywhere, unlike keventd. 允許線程在任意CPU上運行 keventd值在1個CPU上運行*/set_cpus_allowed(current, CPU_MASK_ALL);/* OK, tell user we're spawned, wait for stop or wakeup */__set_current_state(TASK_INTERRUPTIBLE);complete(&create->started); //這裡通知keventd完成線程初始化,keventd收到後擷取新線程的任務結構,然後發出工作完成的訊號後kthread_create返回。schedule();if (!kthread_should_stop()) //判斷先前是否調用過kthread_stopret = threadfn(data); //這裡才真正執行定義的線程函數/* It might have exited on its own, w/o kthread_stop. Check. */if (kthread_should_stop()) { //判斷是否執行過kthread_stopkthread_stop_info.err = ret; //ret是線程函數的返回,後面會經過kthread_stop函數返回complete(&kthread_stop_info.done); //如執行過kthread_stop 還要通知kthread_stop線程完成結束了,如果使用者定義的處理函數使用了do_exit那麼就不會通知kthread_stop,造成kthread_stop一直等待。}return 0;}
至此我們看到kthread_create是如何建立線程,和線程是如何工作的了
3.3 kthread_stop線程的停止
先看下停止相關的結構
struct kthread_stop_info{ struct task_struct *k; //要停止的線程結構 int err; //傳回值 struct completion done; //線程完成結束的等待訊號};/* Thread stopping is done by setthing this var: lock serializes multiple kthread_stop calls. *//* 線程結束鎖 kthread_stop在整個系統內一次只能被一個線程調用*/static DEFINE_MUTEX(kthread_stop_lock);static struct kthread_stop_info kthread_stop_info;
/** * kthread_should_stop - should this kthread return now? * When someone calls kthread_stop() on your kthread, it will be woken * and this will return true. You should then return, and your return * value will be passed through to kthread_stop(). */int kthread_should_stop(void){return (kthread_stop_info.k == current);}
這個函數在kthread_stop()被調用後返回真,當返回為真時你的處理函數要返回,傳回值會通過kthread_stop()返回。所以你的處理函數應該有判斷kthread_should_stop然後退出的代碼。
/** * kthread_stop - stop a thread created by kthread_create(). * @k: thread created by kthread_create(). * * Sets kthread_should_stop() for @k to return true, wakes it, and * waits for it to exit. Your threadfn() must not call do_exit() * itself if you use this function! This can also be called after * kthread_create() instead of calling wake_up_process(): the thread * will exit without calling threadfn(). * * Returns the result of threadfn(), or %-EINTR if wake_up_process() * was never called. */int kthread_stop(struct task_struct *k){int ret;mutex_lock(&kthread_stop_lock); //系統一次只能處理一個結束線程申請/* It could exit after stop_info.k set, but before wake_up_process. */get_task_struct(k); //增加線程引用計數 /* Must init completion *before* thread sees kthread_stop_info.k */init_completion(&kthread_stop_info.done);smp_wmb();/* Now set kthread_should_stop() to true, and wake it up. */kthread_stop_info.k = k;//設定了這個之後 kthread_should_stop() 會返回真wake_up_process(k); //不管線程有沒運行 先叫醒再說(如果已經喚醒過並結束了,該線程是喚醒不了的,這樣會造成後面一直等待kthread_stop_info.done訊號),即便沒運行叫醒後也不會運行使用者定義的函數。put_task_struct(k);/* Once it dies, reset stop ptr, gather result and we're done. */wait_for_completion(&kthread_stop_info.done);//等待線程結束kthread_stop_info.k = NULL; ret = kthread_stop_info.err; //傳回值 mutex_unlock(&kthread_stop_lock);return ret;}
注意如果調用了kthread_stop你的處理函數不能調用do_exit(),函數返回你處理函數的傳回值,如果建立的線程還沒調用過wake_up_process()那麼會返回-EINTR .
四 測試代碼
struct task_struct *mytask;/*代碼中要有kthread_should_stop()判斷 至於傳回值只對kthread_stop才有意義*/int func(void* data){ while(1 ) { if( kthread_should_stop()) return -1; printk(KERN_ALERT "func running\n"); set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(1*HZ); } return 0;}線程建立和驅動mytask=kthread_create(func,0,"mykthread");wake_up_process(mytask);在需要結束的地方調用 kthread_stop(mytask);
通過幾個函數可以很容易的建立核心線程,但線程建立出來之後我們更關注的是有多線程帶來的並發和競爭問題。並發的管理是作業系統編程的核心問題之一,引起的錯誤是一些最易出現又最難發現的問題.