linux的线程要makefile,Linux内核线程之父pid=2的kthreadd线程
這里所謂的內核線程,實際上是由kernel_thread函數創建的一個進程,有自己獨立的task_struct結構并可被調度器調度,這種進程的特殊之處在于它只在內核態運行。
在Linux source code中, init/main.c中的rest_init()中就開始調用kernel_thread來構造內核線程了,比如:
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
我們在源代碼中通過跟蹤kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)的調用來揭示Linux中的這種特殊的內核態進程的背后秘密。
在ARM中,kernel_thread定義如下:
/*
* Create a kernel thread.
*/
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
struct pt_regs regs;
memset(®s, 0, sizeof(regs));
regs.ARM_r1 = (unsigned long)arg;
regs.ARM_r2 = (unsigned long)fn;
regs.ARM_r3 = (unsigned long)do_exit;
regs.ARM_pc = (unsigned long)kernel_thread_helper;
regs.ARM_cpsr = SVC_MODE;
return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
}
[注:這里有個調試方面的小技巧。當初海豚用BDI3000調試BALI板的時候,為了調試生成的內核線程代碼,需要將上述do_fork中的CLONE_UNTRACED flag移除,重新編譯內核,調試器才可停在內核線程函數代碼所設的斷點上]
kernel_thread函數的最后一行是調用do_fork來生成一個進程框架(主體結構是task_struct),在do_fork中會將新生成的進程執行入口點設置為ret_from_fork()。這樣,當新進程被調度器調度時,將從ret_from_fork()函數開始執行。在ret_from_fork中,會調用kernel_thread函數中設置的ARM_pc,也就是說調用kernel_thread_helper.kernel_thread_helper
/*
* Shuffle the argument into the correct register before calling the
* thread function.??r1 is the thread argument, r2 is the pointer to
* the thread function, and r3 points to the exit function.
*/
extern void kernel_thread_helper(void);
asm(? ? ? ? ".section .text\n"
"? ? ? ? .align\n"
"? ? ? ? .type? ? ? ? kernel_thread_helper, #function\n"
"kernel_thread_helper:\n"
"? ? ? ? mov? ? ? ? r0, r1\n"
"? ? ? ? mov? ? ? ? lr, r3\n"
"? ? ? ? mov? ? ? ? pc, r2\n"
"? ? ? ? .size? ? ? ? kernel_thread_helper, . - kernel_thread_helper\n"
"? ? ? ? .previous");
這段匯編代碼將r1賦給r0,r0在函數調用時作為傳遞參數寄存器。在1樓的kernel_thread函數中,regs.ARM_r1 = (unsigned long)arg;
r3給了lr,實際上就是保存內核線程函數返回時的調用地址,在本例中,也就是kthreadd返回后所調用的函數,該函數為do_exit,這意味著當內核線程函數退出后,其所在的進程將會被銷毀。所以,內核線程函數一般都不會輕易退出。
mov pc, r2代碼的執行將會調用kthreadd內核線程函數。kthreadd
int kthreadd(void *unused)
{
struct task_struct *tsk = current;
/* Setup a clean context for our children to inherit. */
set_task_comm(tsk, "kthreadd");
ignore_signals(tsk);
set_user_nice(tsk, KTHREAD_NICE_LEVEL);
set_cpus_allowed(tsk, CPU_MASK_ALL);
current->flags |= PF_NOFREEZE;
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list))
schedule();
__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock);
while (!list_empty(&kthread_create_list)) {
struct kthread_create_info *create;
create = list_entry(kthread_create_list.next,
struct kthread_create_info, list);
list_del_init(&create->list);
spin_unlock(&kthread_create_lock);
create_kthread(create);
spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
}
return 0;
}
kthreadd的核心是一for和while循環體。在for循環中,如果發現kthread_create_list是一空鏈表,則調用schedule調度函數,因為此前已經將該進程的狀態設置為TASK_INTERRUPTIBLE,所以schedule的調用將會使當前進程進入睡眠。如果kthread_create_list不為空,則進入while循環,在該循環體中會遍歷該kthread_create_list列表,對于該列表上的每一個entry,都會得到對應的類型為struct kthread_create_info的節點的指針create.
然后函數在kthread_create_list中刪除create對應的列表entry,接下來以create指針為參數調用create_kthread(create).
在create_kthread()函數中,會調用kernel_thread來生成一個新的進程,該進程的內核函數為kthread,調用參數為create:
kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
kthread--
static int kthread(void *_create)
{
struct kthread_create_info *create = _create;
int (*threadfn)(void *data);
void *data;
int ret = -EINTR;
/* Copy data: it's on kthread's stack */
threadfn = create->threadfn;
data = create->data;
/* OK, tell user we're spawned, wait for stop or wakeup */
__set_current_state(TASK_UNINTERRUPTIBLE);
complete(&create->started);
schedule();
if (!kthread_should_stop())
ret = threadfn(data);
/* It might have exited on its own, w/o kthread_stop.??Check. */
if (kthread_should_stop()) {
kthread_stop_info.err = ret;
complete(&kthread_stop_info.done);
}
return 0;
}
kthread會將其所在進程的狀態設為TASK_UNINTERRUPTIBLE,然后調用schedule函數。所以,kthread將會使其所在的進程進入休眠狀態,直到被別的進程喚醒。如果被喚醒,將會調用create->threadfn(create->data);
其中的kthread_should_stop()如果返回真,表明對于當前進程p,有別的進程調用了kthread_stop(p),否則kthread_should_stop返回假。Summary--
kthreadd?will work on a global list named?kthread_create_list, if the list is empty then the?kthreadd?will sleep until someone else wake it up.
Now let's see which one will update the?kthread_create_listmeaning insert a node into the list.kthread_create()?will insert a node named?create?into the list. After it insert the?create?into thekthread_create_list, it will call?wake_up_process(kthreadd_task)to wake up the process which kernel thread function is?kthreadd. In this case, kthreadd will create a new process which the initial state is TASK_UNINTERRUPTIBLE, so the new process will enter into sleep until someone wake it up.
The work queue make use of the?kthread_create.
Then comes to the last question, who will wake up the process created by the kthreaddd?
The question is, for the work queue, when the driver call __create_workqueue_key, the latter will call start_workqueue_thread to wake up the process created by the kthreadd. worker_thread will sleep on a wait queue until the driver call queue_work to insert a working node into this wait queue, and the worker_thread will be waked up by the queue_work meanwhile...
worker_thread has been woken up, that doesn't mean the worker_thread will be called immediatelly after queque_work being called, queue_work just change the state of worker_thread process to TASK_RUNNING, this worker_thread function will be called until next schedule point, because of the higher schedule priority, so the worker_thread will be called quickly upon the coming schedule point.
So-- from the work queue point of view, __create_workqueue_key() can be divided into 2 major parts: The first one will create a new process in the system with the help of kthreadd, which has a kernel thread function named worker_thread. The new process will enter into sleep state. The second one will call start_workqueue_thread() to wake up the process created in the first part, once woken up, the worker_thread will be executed, but it will enter sleep again because the wait queue is empty.
When driver call queue_work, it will insert a working node into the wait queue and wake up the worker_thread (put the worker_thread process into TASK_RUNNING state). In the coming schedule point, worker_thread will be called to handle all the nodes in the wait queue for which it's waiting.
關于worker_thread內核線程的創立過程:
最開始由void __init?init_workqueues(void)發起(Workqueue.c),依次的關鍵調用節點分別是(為了便于敘述,在內核代碼基礎上略有改動,但不影響核心調用過程)
create_workqueue("events");
__create_workqueue((("events"), 0, 0);
__create_workqueue_key("events", 0, 0, NULL, NULL);
在__create_workqueue_key("events", 0, 0, NULL, NULL)中:
1).首先創建一個struct workqueue_struct指針型變量*wq, wq->name = "events".這個新建的隊列指針將被記錄在全局變量keventd_wq中.
workqueue_struct定義如下:
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq;
struct list_head list;
const char *name;
int singlethread;
int freezeable;? ? ? ? ? ? ? ? /* Freeze threads during suspend */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
2).把wq所在的隊列節點加入到一名為workqueues的全局變量中(list_add(&wq->list, &workqueues)).
3).調用create_workqueue_thread(),最終調用kthread_create(worker_thread, cwq, fmt, wq->name, cpu)來生成內核線程worker_thread.
Linux內核中最終是通過do_fork來生成內核線程(正如前面所說,這其實是個能被調度的進程,擁有自己的task_struct結構),這個過程在內核中是個比較復雜的過程,比較重要的節點總結如下:在當前進程下調用do_fork來生成一個新進程時,會大量copy當前進程的task_struct結構到新進程的task_struct變量中,新進程如果被調度運行,入口點(pc值)是kernel_thread_helper函數,在該函數中會再次將pc值設置為kernel_thread()中的function指針,也就是在調用kernel_thread函數時第一參數所表示的函數。
所以,在Linux系統初始化期間,會生成一個新進程,該進程的執行線程/函數為worker_thread,該進程被創建出來之后的狀態是STOP,這意味著該線程無法進入調度隊列直到針對該進程調用wake_up_process(),該進程才會真正進入調度隊列,如果被調度,則開始運行worker_thread函數。新進程被賦予的調度優先級為KTHREAD_NICE_LEVEL(-5),這個標志的實際含義將在Linux進程調度的帖子里去寫。然后對于worker_thread線程函數本身,會進一步調整調度優先級(set_user_nice(current, -5)),這樣worker_thread所在進程的優先值將為-10,在可搶占式的Linux內核中,如此高的調度優先級極易導致一個調度時點,即使當前進程也許正運行在內核態,也可能被切換出CPU,代之以worker_thread.
在kernel_thread_helper函數中,在調用內核線程函數之前設定返回地址為do_exit()函數,所以當內核線程函數退出的話將導致該進程的消失。
比如ARM中的kernel_thread_helper函數代碼:
extern void kernel_thread_helper(void);
asm(? ? ? ? ".section .text\n"
"? ? ? ? .align\n"
"? ? ? ? .type? ? ? ? kernel_thread_helper, #function\n"
"kernel_thread_helper:\n"
"? ? ? ? mov? ? ? ? r0, r1\n"
"? ? ? ? mov? ? ? ? lr, r3\n"
"? ? ? ? mov? ? ? ? pc, r2\n"
"? ? ? ? .size? ? ? ? kernel_thread_helper, . - kernel_thread_helper\n"
"? ? ? ? .previous");
mov lr, r3設定內核線程函數返回后的返回地址,Linux內核代碼設定為do_exit().
可以想象,象worker_thread這種內核線程函數,一般不會輕易退出,除非對內核線程函數所在的進程上調用kthread_stop函數。
上述的kernel_thread_helper函數中的mov pc, r2將會導致worker_thread()函數被調用,該函數定義如下:
static int worker_thread(void *__cwq)
{
struct cpu_workqueue_struct *cwq = __cwq;
DEFINE_WAIT(wait);
if (cwq->wq->freezeable)
set_freezable();
set_user_nice(current, -5);
for (;;) {
prepare_to_wait(&cwq->more_work, &wait, TASK_INTERRUPTIBLE);
if (!freezing(current) &&
!kthread_should_stop() &&
list_empty(&cwq->worklist))
schedule();
finish_wait(&cwq->more_work, &wait);
try_to_freeze();
if (kthread_should_stop())
break;
run_workqueue(cwq);
}
}
正如前面猜測的那樣,該函數不會輕易退出,其核心是一for循環,如果任務隊列中有新的節點,則執行該節點上的函數(在run_workqueue()內部),否則worker_thread所在的進程將會繼續休眠。
對work queue的深入理解需要了解Linux的調度機制,最基本的是Linux內核的調度時機,因為這關系到驅動程序開發者能對自己注冊到任務隊列函數的執行時機有大體的了解。在2.4內核中,除了進程主動調用schedule()這種主動的調度方式外,調度發生在由內核態向用戶態轉變(從中斷和系統調用返回)的時刻,因為內核不可搶占性,所以內核態到內核態的轉變時調度不會發生。而在2.6內核中,因為內核可搶占已經被支持,這意味著調度的時機除了發生在內核態向用戶態轉變時,運行在內核態的進程也完全有可能被調度出處理器,比如當前進程重新允許搶占(調用preempt_enable())。在內核態的進程允許被搶占,意味著對高優先級進程的調度粒度更細:如果當前進程允許被搶占,那么一旦當前調度隊列中有比當前進程優先級更高的進程,當前進程將被切換出處理器(最常見的情況是在系統調用的代碼中接收到中斷,當中斷返回時,2.4代碼會繼續運行被中斷的系統調用,而2.6代碼的可搶占性會導致一個調度時點,原先被中斷的系統調用所在的進程可能會被調度隊列中更高優先級的進程所取代)。關于進程的調度,會在另外的帖子中詳細介紹。
create_singlethread_workqueue(name)與create_workqueue(name)
Driver調用這兩個宏來創建自己的工作隊列以及相應的內核進程(其內核線程函數為worker_thread,下來為了方便敘述,就簡稱該進程為worker_thread進程)
1. create_singlethread_workqueue(name)
該函數的實現機制如下圖所示,函數返回一個類型為struct workqueue_struct的指針變量,該指針變量所指向的內存地址在函數內部調用kzalloc動態生成。所以driver在不再使用該work queue的情況下調用void destroy_workqueue(struct workqueue_struct *wq)來釋放此處的內存地址。
圖中的cwq是一per-CPU類型的地址空間。對于create_singlethread_workqueue而言,即使是對于多CPU系統,內核也只負責創建一個worker_thread內核進程。該內核進程被創建之后,會先定義一個圖中的wait節點,然后在一循環體中檢查cwq中的worklist,如果該隊列為空,那么就會把wait節點加入到cwq中的more_work中,然后休眠在該等待隊列中。
Driver調用queue_work(struct workqueue_struct *wq, struct work_struct *work)向wq中加入工作節點。work會依次加在cwq->worklist所指向的鏈表中。queue_work向cwq->worklist中加入一個work節點,同時會調用wake_up來喚醒休眠在cwq->more_work上的worker_thread進程。wake_up會先調用wait節點上的autoremove_wake_function函數,然后將wait節點從cwq->more_work中移走。
worker_thread再次被調度,開始處理cwq->worklist中的所有work節點...當所有work節點處理完畢,worker_thread重新將wait節點加入到cwq->more_work,然后再次休眠在該等待隊列中直到Driver調用queue_work...
總結
以上是生活随笔為你收集整理的linux的线程要makefile,Linux内核线程之父pid=2的kthreadd线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: s5pv210 linux内核移植,简单
- 下一篇: u盘安装linux双系统6,用U盘安装C