以前寫的東西,感覺寫的過於繁瑣,從這以後儘量寫的簡潔明朗一點,也有助於我自己以後再看。(內核版本linux-3.2.36)
我們要解決的問題。
1. 簡單描述內核線程創建過程。
2. 爲什麼kthread_create()調用後,我們還要調用wake_up_process來喚醒調用我們的線程函數,用戶態並不需要。
kthread_create(threadfn, data, namefmt,arg...);
這是創建內核進程的主要函數。
它會喚醒kthread線程去建立線程,然後通過一個完成量等待完成,如果創建成功會設置調度方法(默認用nomal)和優先級(默認爲0)。
kthread最終會調用kernel_thread,其實你可以用kernel_thread直接創建線程,可以上網看看如何使用,也很簡單。
kernel_thread是依賴處理器架構的,但是最終都是調用do_fork(),我看了x86和arm都是一樣的調用參數
do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0,®s, 0, NULL, NULL);
所謂的依賴處理機主要體現在regs,regs在arm中是什麼,我們下面再看。先記住它。
下面看do_fork,網上有do_fork的分析,但是和我這個版本好像都不一樣。
我會省略一些代碼。只做說明
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
struct task_struct *p;
int trace = 0;
long nr;
在開始分配東西之前,做一些初步的論證和權限檢查
確定那個事件報告給tracer。但下面的條件要成立。
(likely(user_mode(regs)) && !(clone_flags & CLONE_UNTRACED))
條件的意思是用戶模式且沒有CLONE_UNTRACED,即不是通過kernel_thread創建。
在arm上,user_mode爲(((regs)->ARM_cpsr & 0xf) ==0) 看看arm的cpsr你就知道了,用戶模式判斷
p = copy_process(clone_flags, stack_start, regs, stack_size,
child_tidptr, NULL,trace);
下面會判斷是否有效,如果有效p,向下
然後分配PID。
根據CLONE_PARENT_SETTID,新創建的子進程的ID號登記到父進程的相關字段中。
根據CLONE_VFORK初始化完成量,這個完成量在mm_release時向父進程發送信號,喚醒父進程。
audit_context初始化,其實和父進程的一樣,這是審計上下文,
/*
每個進程的進程結構(或者進程上下文)含有審計上下文結構audit_context指針,審計上下文記錄進程上下文的審計信息,當進程從進入系統調用和 退出系統調用時,使用審計上下文結構audit_context記錄系統調用進入和退出的各種屬性數據,如:時間戳、參數、調用號等。審計上下文還通過輔 助數據結構鏈表記錄進程運行中的各種關鍵數據結構的審計信息。詳細在此http://book.51cto.com/art/200712/62881.htm
*/
爲了防止在creation時被追蹤,我們設置了PF_STARTING標準,現在清除它。
調用wake_up_new_task():喚醒一個新的task。這個函數主要是將新的進程加入到進程調度隊列並設此進程爲可被調度的,以後這個進程可以被進程調度模塊調度執行。
但是現在執行的不是我們的線程函數,我們的線程還要等到調用wake_up_process()再運行。
下面會解釋。
根據tace標誌,告知ptracer
根據CLONE_VFORK,讓父進程freezer。
}
現在我們還有p =copy_process(clone_flags, stack_start, regs, stack_size,
child_tidptr, NULL, trace);
這個函數也有好多網上的分析,我分析我關心的,其他的自己看看:
http://hi.baidu.com/zengzhaonong/item/df005e13633205a4feded523
我說一些:
調用p = dup_task_struct(current);
這個爲新進程創建一個內核棧、thread_info結構和task_struct,這些值與當前進程的值相同。
還有一個copy_mm(clone_flags,p);
這個函數在這我們先記住就可以了,因爲設置了CLONE_VM,這個函數只是把父進程的mm和mm_active賦值給子進程。內核線程是沒有用戶空間的mm。mm爲空。即使是用戶線程,在這也是空,因爲寫時拷貝機制。
下面還有一句
retval = copy_thread(clone_flags, stack_start,stack_size, p, regs);
這個要對應於kernel_thread。上面說了這個基於平臺。我會以arm平臺作爲講解。沒有興趣的就不要往下看了
copy_thread是使用參數regs初始化子進程的內核堆棧空間
我們先看kernel_thread
調用時pid =kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
pid_t kernel_thread(int (*fn)(void *), void*arg, unsigned long flags)
{
struct pt_regs regs;
memset(®s, 0, sizeof(regs));
regs.ARM_r4 = (unsigned long)arg; //這個arg是一個結構體,裏面記錄我們的線程函數,傳入參數,還有創建線程時要等待的完成量
regs.ARM_r5 = (unsigned long)fn;//這個是kthread,內核提供的,先記住它。
regs.ARM_r6 = (unsignedlong)kernel_thread_exit;//我的就是do_exit
regs.ARM_r7 = SVC_MODE | PSR_ENDSTATE | PSR_ISETSTATE;//看下面
regs.ARM_pc = (unsigned long)kernel_thread_helper;
regs.ARM_cpsr = regs.ARM_r7 | PSR_I_BIT;//結合上面就是禁止IRQ,系統模式。有個E位,我的arm920t沒有這個位。
return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL,NULL);
}
kernel_thread_helper。我們看看它幹了什麼。
asm( ".pushsection .text\n"//這是elf段堆棧操作命令,它將當前段(及子段)推入段堆棧的頂部。
" .align\n"//對齊
" .type kernel_thread_helper,#function\n"//函數符號
"kernel_thread_helper:\n"
#ifdef CONFIG_TRACE_IRQFLAGS//這個不看
" bl trace_hardirqs_on\n"
#endif
" msr cpsr_c, r7\n"//設置cpsr_c,這個和cpsr區別是屏蔽0~7位。即只保留N Z C V位
" mov r0, r4\n"//傳入函數的參數
" mov lr, r6\n"//退出時的函數。
" mov pc, r5\n"//執行r5函數
" .size kernel_thread_helper, . -kernel_thread_helper\n"//設置內存大小,爲0
" .popsection");//堆棧頂段出棧
從上面看kernel_thread_helper會執行我們自定義的線程函數。
它是如何調用,這個問題應該是調度的問題。不過還是簡單說一下
我們看看copy_thread,就看幾句
struct thread_info *thread = task_thread_info(p);
struct pt_regs *childregs = task_pt_regs(p);
*childregs = *regs;
childregs->ARM_r0 = 0;
childregs->ARM_sp = stack_start;//傳入的棧地址
從上面看除了棧地址不一樣,其他都一樣。
memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save));
thread->cpu_context.sp = (unsigned long)childregs;//讓這個進程的thread_info的sp指向這個regs,這個就是上面kernel_thread賦值的。
thread->cpu_context.pc = (unsigned long)ret_from_fork;
…
if (clone_flags & CLONE_SETTLS)
thread->tp_value =regs->ARM_r3;
CLONE_SETTLS是子進程創建新的TLS(thread-local storage);
上面的structthread_info:每個任務都有一個thread_info結構,放在內核棧的尾端。
看到上面的cpu_context.pc了吧,它是進程下一次調度時的指令開始地址。所以下一次會執行ret_form_fork,不細看了,它會從堆棧中找到kernel_thread_helper地址並執行。
現在讓我們弄清楚爲什麼內核線程要調用wake_up_process後再能運行。爲什麼上面已經調用wake_up_new_task(),但是沒有運行我們的線程函數。在調度時運行的是kthread。
static int kthread(void *_create)
{
/* Copy data: it's on kthread's stack */
struct kthread_create_info *create = _create;
int (*threadfn)(void *data) = create->threadfn;//我們的模塊線程函數
void *data = create->data;//傳入參數
struct kthread self;
int ret;
self.should_stop = 0;
self.data = data;
init_completion(&self.exited);
current->vfork_done = &self.exited;
/* OK, tell user we're spawned, wait for stop or wakeup */
__set_current_state(TASK_UNINTERRUPTIBLE);//在這設置爲睡眠,所以你要通過調用wake_up_process來喚醒。
create->result = current;
complete(&create->done);//這個會通知上面的kthread_create(),沒有它就會死機了。
schedule();//再次調度,當然不會掉當前的函數。因爲被設置爲TASK_UNINTERRUPTIBLE
ret = -EINTR;
if (!self.should_stop)
ret = threadfn(data);//等到什麼時候可以運行了,纔會到這。
/* we can't just return, we must preserve "self" on stack */
do_exit(ret);
}