原文出處:http://blog.csdn.net/fontlose/article/details/8291674
內核線程和普通的進程間的區別在於內核線程沒有獨立的地址空間,它只在內核空間運行,從來不切換到用戶空間去;並且和普通進程一樣,可以被調度,也可以被搶佔。
一線程的創建
structtask_struct*kthread_create(int(*threadfn)(void*data),void*data,constcharnamefmt[],...);
線程創建後,不會馬上運行,而是需要將kthread_create()返回的task_struct指針傳給wake_up_process()才能驅動線程。
也可以使用kthread_run創建線程並啓動線程
structtask_struct*kthread_run(int(*threadfn)(void*data),void*data,constchar*namefmt,...);
#definekthread_run(threadfn,data,namefmt,...)\
({\
structtask_struct*__k\
=kthread_create(threadfn,data,namefmt,##__VA_ARGS__);\
if(!IS_ERR(__k))\
wake_up_process(__k);\
__k;\
})
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:設置線程的退出標記(線程函數內應用intkthread_should_stop(void)函數,當返回真時應退出函數),kthread_stop會一直等待至線程結束,線程結束前會發送完成結束給kthread_stop,如果直接使用do_exit直接退出線程那麼kthread_stop不會收到完成信號將一直等待下去。如果線程已經退出那麼kthread_stop會先設置退出標記再喚醒一下thread,喚醒線程後會判斷退出標記因此設定的處理函數不會被調用。如果線程已經被喚醒並已經退出那麼kthread_stop會一直等待。intkthread_stop(structtask_struct*thread);
如果處理函數沒用kthread_should_stop判斷退出,那麼kthread_stop會一直等待處理函數主動退出。
int kthread_stop(struct task_struct *thread); 如果處理函數沒用kthread_should_stop判斷退出,那麼 kthread_stop會一直等待處理函數主動退出。
三源碼分析這裏使用的內核版本是2.6.21.5
3.1管理調度其它的內核線程kthread
使用ps命令可以查看有個名叫kthread的進程,它在內核初始化的時候被創建。
static__initinthelper_init(void)
{
//創建一個單線程的共享列隊
helper_wq=create_singlethread_workqueue("kthread");
BUG_ON(!helper_wq);
return0;
}
core_initcall(helper_init);
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.2kthread_create創建線程
再看kthread_create前先看下kthread_create_info結構,每個線程創建時使用。
structkthread_create_info
{
/*Informationpassedtokthread()fromkeventd.*/
int(*threadfn)(void*data);//線程處理函數
void*data;//線程參數
structcompletionstarted;//在工作中等待kernel_thread創建線程完成,線程創建完後線程會通知工作繼續。
/*Resultpassedbacktokthread_create()fromkeventd.*/
structtask_struct*result;//started當收到線程創建完信號started後,用來存放創建的任務結構體
structcompletiondone;//工作者線程加入一個工作後會等待工作做完,這個工作只是創建線程。
structwork_structwork;//創建線程的工作,具體工作看後面源碼
};
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:thefunctiontorununtilsignal_pending(current).
*@data:dataptrfor@threadfn.
*@namefmt:printf-stylenameforthethread.
*
*描述:這個幫助函數創建並命名一個內核線程,線程創建後並不運行,使用wake_up_process()函數來運行,參考kthread_run(),kthread_create_on_cpu()
*
*被喚醒後,線程調用threadfn()函數data作爲參數,如果是獨立線程沒有其他線程調用kthread_stop()那麼可以直接使用do_exit(),或當檢測到kthread_should_stop()返回真時(kthread_stop()已被調用了)返回處理函數,應返回0或負數,返回值會傳給kthread_stop()返回。
*/
structtask_struct*kthread_create(int(*threadfn)(void*data),void*data,constcharnamefmt[],...)
{
structkthread_create_infocreate;
//下面五行初始化kthread_create_info
create.threadfn=threadfn;
create.data=data;
init_completion(&create.started);
init_completion(&create.done);
INIT_WORK(&create.work,keventd_create_kthread);//可見創建的工作是在keventd_create_kthread函數內進行
/*Theworkqueueneedstostartupfirst:*/
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_listargs;
va_start(args,namefmt);
vsnprintf(create.result->comm,sizeof(create.result->comm),
namefmt,args);
va_end(args);
}
returncreate.result;
}
/** * 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_info create.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函數
/*Wearekeventd:createathread.這個函數工作在keventd內核線程中*/
staticvoidkeventd_create_kthread(structwork_struct*work)
{
structkthread_create_info*create=container_of(work,structkthread_create_info,work);
intpid;
/*Wewantourownsignalhandler(wetakenosignalsbydefault)*/
/*我們使用自己的信號處理,默認不處理信號*/
pid=kernel_thread(kthread,create,CLONE_FS|CLONE_FILES|SIGCHLD);//在這裏創建函數,線程處理函數爲kthread函數,參數爲structkthread_create_info指針create。
if(pid<0){
create->result=ERR_PTR(pid);
}else{
wait_for_completion(&create->started);//等待創建的線程執行,線程執行後會發送完成信號create->started
read_lock(&tasklist_lock);
create->result=find_task_by_pid(pid);
read_unlock(&tasklist_lock);
}
complete(&create->done);
}
/* 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->started read_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
staticintkthread(void*_create)
{
structkthread_create_info*create=_create;
int(*threadfn)(void*data);
void*data;
sigset_tblocked;
intret=-EINTR;
kthread_exit_files();
/*Copydata:it'sonkeventd'sstack*/
threadfn=create->threadfn;
data=create->data;
/*Blockandflushallsignals(incasewe'renotfromkeventd).阻塞全部信號*/
sigfillset(&blocked);
sigprocmask(SIG_BLOCK,&blocked,NULL);
flush_signals(current);
/*Bydefaultwecanrunanywhere,unlikekeventd.允許線程在任意CPU上運行keventd值在1個CPU上運行*/
set_cpus_allowed(current,CPU_MASK_ALL);
/*OK,telluserwe'respawned,waitforstoporwakeup*/
__set_current_state(TASK_INTERRUPTIBLE);
complete(&create->started);//這裏通知keventd完成線程初始化,keventd收到後獲取新線程的任務結構,然後發出工作完成的信號後kthread_create返回。
schedule();
if(!kthread_should_stop())//判斷先前是否調用過kthread_stop
ret=threadfn(data);//這裏才真正執行定義的線程函數
/*Itmighthaveexitedonitsown,w/okthread_stop.Check.*/
if(kthread_should_stop()){//判斷是否執行過kthread_stop
kthread_stop_info.err=ret;//ret是線程函數的返回,後面會經過kthread_stop函數返回
complete(&kthread_stop_info.done);//如執行過kthread_stop還要通知kthread_stop線程完成結束了,如果用戶定義的處理函數使用了do_exit那麼就不會通知kthread_stop,造成kthread_stop一直等待。
}
return0;
}
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_stop ret = threadfn(data); //這裏才真正執行定義的線程函數 /* It might have exited on its own, w/o kthread_stop. Check. */ if (kthread_should_stop()) { //判斷是否執行過kthread_stop kthread_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.3kthread_stop線程的停止
先看下停止相關的結構
structkthread_stop_info
{
structtask_struct*k;//要停止的線程結構
interr;//返回值
structcompletiondone;//線程完成結束的等待信號
};
/*Threadstoppingisdonebysetthingthisvar:lockserializesmultiplekthread_stopcalls.*/
/*線程結束鎖kthread_stop在整個系統內一次只能被一個線程調用*/
staticDEFINE_MUTEX(kthread_stop_lock);
staticstructkthread_stop_infokthread_stop_info;
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-shouldthiskthreadreturnnow?
*Whensomeonecallskthread_stop()onyourkthread,itwillbewoken
*andthiswillreturntrue.Youshouldthenreturn,andyourreturn
*valuewillbepassedthroughtokthread_stop().
*/
intkthread_should_stop(void)
{
return(kthread_stop_info.k==current);
}
/** * 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-stopathreadcreatedbykthread_create().
*@k:threadcreatedbykthread_create().
*
*Setskthread_should_stop()for@ktoreturntrue,wakesit,and
*waitsforittoexit.Yourthreadfn()mustnotcalldo_exit()
*itselfifyouusethisfunction!Thiscanalsobecalledafter
*kthread_create()insteadofcallingwake_up_process():thethread
*willexitwithoutcallingthreadfn().
*
*Returnstheresultofthreadfn(),or%-EINTRifwake_up_process()
*wasnevercalled.
*/
intkthread_stop(structtask_struct*k)
{
intret;
mutex_lock(&kthread_stop_lock);//系統一次只能處理一個結束線程申請
/*Itcouldexitafterstop_info.kset,butbeforewake_up_process.*/
get_task_struct(k);//增加線程引用計數
/*Mustinitcompletion*before*threadseeskthread_stop_info.k*/
init_completion(&kthread_stop_info.done);
smp_wmb();
/*Nowsetkthread_should_stop()totrue,andwakeitup.*/
kthread_stop_info.k=k;//設置了這個之後kthread_should_stop()會返回真
wake_up_process(k);//不管線程有沒運行先叫醒再說(如果已經喚醒過並結束了,該線程是喚醒不了的,這樣會造成後面一直等待kthread_stop_info.done信號),即便沒運行叫醒後也不會運行用戶定義的函數。
put_task_struct(k);
/*Onceitdies,resetstopptr,gatherresultandwe'redone.*/
wait_for_completion(&kthread_stop_info.done);//等待線程結束
kthread_stop_info.k=NULL;
ret=kthread_stop_info.err;//返回值
mutex_unlock(&kthread_stop_lock);
returnret;
}
/** * 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.
四測試代碼
structtask_struct*mytask;
/*代碼中要有kthread_should_stop()判斷至於返回值只對kthread_stop纔有意義*/
intfunc(void*data)
{
while(1)
{
if(kthread_should_stop())return-1;
printk(KERN_ALERT"funcrunning\n");
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(1*HZ);
}
return0;
}
線程創建和驅動
mytask=kthread_create(func,0,"mykthread");
wake_up_process(mytask);
在需要結束的地方調用
kthread_stop(mytask);
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);
通過幾個函數可以很容易的創建內核線程,但線程創建出來之後我們更關注的是有多線程帶來的併發和競爭問題。併發的管理是操作系統編程的核心問題之一,引起的錯誤是一些最易出現又最難發現的問題。