瀏覽創(chuàng)建進程的相關(guān)關(guān)鍵代碼
看一下do_fork????? /linux-3.18.6/kernel/fork.c#do_fork
1651 p = copy_process(clone_flags, stack_start, stack_size, // 創(chuàng)建進程的主要代碼 1652 child_tidptr, NULL, trace);
看一下copye_process????? /linux-3.18.6/kernel/fork.c#copy_process
1240 p = dup_task_struct(current); // 復制PCB
看一下dup_task_struct????? /linux-3.18.6/kernel/fork.c#dup_task_struct
320 err = arch_dup_task_struct(tsk, orig); // 執(zhí)行復制双抽,orig 當前進程
316 ti = alloc_thread_info_node(tsk, node); // 實際就是alloc一個內(nèi)核堆棧
324 tsk->stack = ti; // 把alloc后返回的地址賦給stack
看一下arch_dup_task_struct???? /linux-3.18.6/kernel/fork.c#arch_dup_task_struct
290int __weak arch_dup_task_struct(struct task_struct *dst, 291 struct task_struct *src) 292{ 293 *dst = *src; // 就是把數(shù)據(jù)結(jié)構(gòu)加*,原來它是數(shù)據(jù)結(jié)構(gòu)的指針八堡,加*夺姑,表示它的值 294 return 0; 295}
看一下alloc_thread_info_node???? /linux-3.18.6/kernel/fork.c#alloc_thread_info_node
150static struct thread_info *alloc_thread_info_node(struct task_struct *tsk, 151 int node) 152{ 153 struct page *page = alloc_kmem_pages_node(node, THREADINFO_GFP, 154 THREAD_SIZE_ORDER); 155 156 return page ? page_address(page) : NULL; 157}
做了實際分配內(nèi)核堆棿冈辏空間的效果涯呻,實際的代碼是alloc_kmem_pages_node,創(chuàng)建了一定大小的頁面面徽,頁面有一部分用來存放thread_info蟆技,另一部分從高地址向低地址就是內(nèi)核堆棧
回到dup_task_struct,現(xiàn)在已經(jīng)把父進程的PCB斗忌,也就是task_struct數(shù)據(jù)結(jié)構(gòu)復制過來了,也就是由p所指向的子進程的PCB(進程描述符)
1240 p = dup_task_struct(current); // 復制PCB
往后的代碼旺聚,有大量地修改子進程內(nèi)容的代碼织阳,做初始化,這些都可以抽象掉
1375 retval = copy_files(clone_flags, p); 1378 retval = copy_fs(clone_flags, p); // 初始化文件系統(tǒng) 1381 retval = copy_sighand(clone_flags, p); 1384 retval = copy_signal(clone_flags, p); // 初始化信號 1387 retval = copy_mm(clone_flags, p); // 初始化內(nèi)存 1390 retval = copy_namespaces(clone_flags, p); 1393 retval = copy_io(clone_flags, p); // 初始化IO
1396 retval = copy_thread(clone_flags, stack_start, stack_size, p); // 關(guān)鍵的內(nèi)容
看一下copy_thread??? /linux-3.18.6/arch/x86/kernel/process_32.c#132
135 struct pt_regs *childregs = task_pt_regs(p);
從這里可以看到砰粹,從子進程的pid唧躲,也就是內(nèi)核堆棧的位置,找到了椉盍В空間弄痹,SAVE_ALL的一些內(nèi)容,SAVE_ALL的地址
139 p->thread.sp = (unsigned long) childregs; // 調(diào)度到子進程時的內(nèi)核棧底
把棧底賦上
拷貝內(nèi)核堆棧數(shù)據(jù)和指定新進程的第一條指令地址
159 *childregs = *current_pt_regs(); // 復制內(nèi)核堆棧
當前進程嵌器,也就是父進程肛真,因為我們這個執(zhí)行過程還在父進程的執(zhí)行上下文當中。父進程的內(nèi)核堆棧的棧底爽航,也就是SAVE_ALL的內(nèi)容蚓让,把它拷貝過來,這個地方實際就是做內(nèi)核堆棧里已有數(shù)據(jù)的拷貝
值得注意的是:在復制內(nèi)核堆棧的時候讥珍,只復制了與SAVE_ALL相關(guān)的那一部分历极,只復制了struct pt_regs
看一下struct pt_regs數(shù)據(jù)結(jié)構(gòu)的內(nèi)容??? /linux-3.18.6/arch/x86/include/asm/ptrace.h
9#ifdef __i386__ 10 11struct pt_regs {
// SAVE_ALL壓到內(nèi)核堆棧里的內(nèi)容
12 unsigned long bx; 13 unsigned long cx; 14 unsigned long dx; 15 unsigned long si; 16 unsigned long di; 17 unsigned long bp; 18 unsigned long ax; // 傳遞的系統(tǒng)調(diào)用號 19 unsigned long ds; 20 unsigned long es; 21 unsigned long fs; 22 unsigned long gs; 23 unsigned long orig_ax; // 原來的eax
// 執(zhí)行int 0x80指令的時候,CPU自動壓到內(nèi)核堆棧里面的內(nèi)容
24 unsigned long ip; 25 unsigned long cs; 26 unsigned long flags; 27 unsigned long sp; 28 unsigned long ss; 29}; 30 31#else /* __i386__ */
在復制內(nèi)核堆棧的時候衷佃,i386只復制了內(nèi)核堆棧最棧底的那一部分內(nèi)容趟卸,也就是系統(tǒng)調(diào)用壓棧的過程,int 0x80指令(CPU自動)和SAVE_ALL壓到內(nèi)核堆棧里的內(nèi)容
160 childregs->ax = 0; // 為什么子進程的fork返回0,這里就是原因锄列!
返回值存放在eax图云,pid=0就是在這賦值的。因為子進程的返回值是0右蕊,所以拷貝完還需要修改一下內(nèi)核堆棧里壓入的返回值
161 if (sp) 162 childregs->sp = sp; // sp是傳遞給copy_thread的第二個參數(shù)stack_start
包括棧底的數(shù)據(jù)
164 p->thread.ip = (unsigned long) ret_from_fork; // 調(diào)度到子進程時的第一條指令地址
賦值thread.ip的內(nèi)容為ret_from_fork琼稻,子進程得到進程調(diào)度,得到CPU的時候饶囚,是從這個位置開始執(zhí)行的
看一下entry_32.S ?? /linux-3.18.6/arch/x86/kernel/entry_32.S
系統(tǒng)調(diào)用總控程序帕翻,找到ret_from_fork
290ENTRY(ret_from_fork) 291 CFI_STARTPROC 292 pushl_cfi %eax 293 call schedule_tail 294 GET_THREAD_INFO(%ebp) 295 popl_cfi %eax 296 pushl_cfi $0x0202 # Reset kernel eflags 297 popfl_cfi 298 jmp syscall_exit // 在這里會跳轉(zhuǎn)到syscall_exit 299 CFI_ENDPROC 300END(ret_from_fork)
syscall_exit 在哪個地方呢?
490ENTRY(system_call) 493 pushl_cfi %eax # save orig_eax 494 SAVE_ALL // 這里進行SAVE_ALL 501syscall_call: // 這里進行system_call 502 call *sys_call_table(,%eax,4)
503syscall_after_call: // 這里call返回了萝风,返回到內(nèi)核堆棧 // 也就是內(nèi)核堆棧怎么壓棧嘀掸,它就又怎么出來了
504 movl %eax,PT_EAX(%esp) # store the return value 505syscall_exit:
call返回,返回到內(nèi)核堆棧规惰,也就是內(nèi)核堆棧怎么壓棧睬塌,它就又怎么出來了,所以到syscall_exit的時候歇万,實際上和sys_call之前它的堆棧狀態(tài)是一樣的
所以ret_from_fork跳到syscall_exit來揩晴,就可以繼續(xù)往下執(zhí)行,就可以正常地返回到用戶態(tài)
也就是說當子進程獲得CPU控制權(quán)贪磺,開始運行的時候硫兰,它的ret_from_fork可以把后面的堆棧出棧出棧,從iret返回到用戶態(tài)寒锚,這時候返回到用戶態(tài)劫映,就不是原來父進程的進程空間了,而是子進程的進程空間了
(下篇完)