需要打上這么多斷點辨嗽,do_fork世落、copy_process、sys_clone糟需、copy_thread屉佳、dup_task_struct等。
- 在執(zhí)行fork之后洲押,可以發(fā)現(xiàn)武花,停在了syd_clone處,顯然是因為前面的
#ifdef CONFIG_CLONE_BACKWARDS
生效了诅诱。
- 當然髓堪,實際執(zhí)行的還是do_fork
- 因為我們關注的是進程的創(chuàng)建,這其中比較關鍵的一個地方是copy_process娘荡,那么就跟蹤進去看看究竟如何干旁。
前面進行了一大堆的參數(shù)檢查,直到dup_task_struct
跟蹤dup_task_struct進去看
- alloc_task_struct_node開辟進程內(nèi)存炮沐,如果開辟成功争群,則繼續(xù)向下進行,否則直接返回空(這個有些像我們用的malloc函數(shù))大年。
- alloc_thread_info_node為thread分配內(nèi)存
- 關鍵的一步又來了换薄,arch_dup_task_struct玉雾,最主要的工作是
*dst = *src;
- 接下來setup_thread_stack設置thread_stack
- 略過我們不關心的這些步驟辣苏,接著執(zhí)行返回創(chuàng)建好的task_struct(p)到copy_process晚胡,做好參數(shù)判斷之后函似,
接下來的很長的一段代碼都是根據(jù)特定的環(huán)境設置剛剛我們復制出來的task_struct(p)仔沿,有關于跟蹤調(diào)試的档玻,有關于中斷的译荞。背犯。脆烟。
copy_files凡恍、copy_fs志秃、copy_mm、copy_signial等等嚼酝,這種種拷貝表明浮还,子進程和父進程的很多東西是一樣的。
當運行到copy_thread的時候闽巩,系統(tǒng)停了下來钧舌。可以看到又官,該函數(shù)對p的sp以及sp0進行設置延刘。對cpu的其他一些寄存器進行設置。
- copy_thread中有下面一片代碼六敬,其中碘赖,
p->thread.ip = (unsigned long) ret_from_kernel_thread;
這也是很關鍵的一句,可能是從父進程返回外构?
下面的unlikely只是為了編譯器優(yōu)化(表明if的語句塊執(zhí)行的可能性衅张荨),將后面這段二進制代碼不放在前面的代碼之后审编。之所以會有這樣的優(yōu)化是因為撼班,系統(tǒng)啟動的時候,do_fork就會被調(diào)用垒酬,自認會有從kernel_thread返回的時候砰嘁,但是這也僅限于系統(tǒng)剛啟動,為了以后在創(chuàng)建新進程時候的執(zhí)行速度能夠加快勘究,在系統(tǒng)剛剛啟動的時候有一些性能損失沒什么矮湘。
143 if (unlikely(p->flags & PF_KTHREAD)) {
144 /* kernel thread */
145 memset(childregs, 0, sizeof(struct pt_regs));
146 p->thread.ip = (unsigned long) ret_from_kernel_thread;
147 task_user_gs(p) = __KERNEL_STACK_CANARY;
148 childregs->ds = __USER_DS;
149 childregs->es = __USER_DS;
150 childregs->fs = __KERNEL_PERCPU;
151 childregs->bx = sp; /* function */
152 childregs->bp = arg;
153 childregs->orig_ax = -1;
154 childregs->cs = __KERNEL_CS | get_kernel_rpl();
155 childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
156 p->thread.io_bitmap_ptr = NULL;
157 return 0;
158 }
當然,如果上述語句塊未執(zhí)行到口糕,那么會執(zhí)行下面的:
159 *childregs = *current_pt_regs();
160 childregs->ax = 0;
161 if (sp)
162 childregs->sp = sp;
163
164 p->thread.ip = (unsigned long) ret_from_fork;
165 task_user_gs(p) = get_user_gs(current_pt_regs());
最后是設置子進程的返回值為0缅阳,thread的ip為ret_from_fork,這也就是我們子進程返回的執(zhí)行點景描。
process_32.c中有如下定義十办,顯然是嵌入式匯編秀撇,入口在entry_32.S中。
58 asmlinkage void ret_from_fork(void) __asm__("ret_from_fork");
59 asmlinkage void ret_from_kernel_thread(void) __asm__("ret_from_kernel_thread");
- entry_32.S中有這樣的東東:
290 ENTRY(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
299 CFI_ENDPROC
300 END(ret_from_fork)
301
302 ENTRY(ret_from_kernel_thread)
303 CFI_STARTPROC
304 pushl_cfi %eax
305 call schedule_tail
306 GET_THREAD_INFO(%ebp)
307 popl_cfi %eax
308 pushl_cfi $0x0202 # Reset kernel eflags
309 popfl_cfi
310 movl PT_EBP(%esp),%eax
311 call *PT_EBX(%esp)
312 movl $0,PT_EAX(%esp)
313 jmp syscall_exit
314 CFI_ENDPROC
315 ENDPROC(ret_from_kernel_thread)
- schedule_tail的實現(xiàn)向族,具體原理暫不深究
2305 /**
2306 * schedule_tail - first thing a freshly forked thread must call.
2307 * @prev: the thread we just switched away from.
2308 */
2309 asmlinkage __visible void schedule_tail(struct task_struct *prev)
2310 __releases(rq->lock)
2311 {
2312 struct rq *rq = this_rq();
2313
2314 finish_task_switch(rq, prev);
2315
2316 /*
2317 * FIXME: do we need to worry about rq being invalidated by the
2318 * task_switch?
2319 */
2320 post_schedule(rq);
2321
2322 if (current->set_child_tid)
2323 put_user(task_pid_vnr(current), current->set_child_tid);
2324 }
- copy_process之后呵燕,如果沒有錯誤,那么wake_up_new_task將會第一次喚醒新創(chuàng)建的任務(也就是做一些調(diào)度統(tǒng)計管理炸枣,然后將這個任務放到運行隊列中)虏等,如下面代碼弄唧。顯然适肠,當子進程獲得cpu的使用權之后,系統(tǒng)就會從ret_from_fork處返回執(zhí)行候引。
這里面有很多奇怪的編譯器指令或者是宏侯养,待有時間再深究。
1657 if (!IS_ERR(p)) {
1658 struct completion vfork;
1659 struct pid *pid;
1660
1661 trace_sched_process_fork(current, p);
1662
1663 pid = get_task_pid(p, PIDTYPE_PID);
1664 nr = pid_vnr(pid);
1665
1666 if (clone_flags & CLONE_PARENT_SETTID)
1667 put_user(nr, parent_tidptr);
1668
1669 if (clone_flags & CLONE_VFORK) {
1670 p->vfork_done = &vfork;
1671 init_completion(&vfork);
1672 get_task_struct(p);
1673 }
1674
1675 wake_up_new_task(p);
1676
1677 /* forking complete and child started to run, tell ptracer */
1678 if (unlikely(trace))
1679 ptrace_event_pid(trace, pid);
1680
1681 if (clone_flags & CLONE_VFORK) {
1682 if (!wait_for_vfork_done(p, &vfork))
1683 ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
1684 }
1685
1686 put_pid(pid);
1687 } else {
1688 nr = PTR_ERR(p);
1689 }
1690 return nr;
很明顯
nr = pid_vnr(pid);
這一語句獲取了進程的pid澄干,在do_fork的最后返回逛揩。很顯然,當do_fork返回之后麸俘,一切戛然而止辩稽,返回的nr就是子進程的pid,內(nèi)核通過這種方式來區(qū)分父子進程从媚,真是奇妙逞泄。
其實在fork系統(tǒng)調(diào)用的執(zhí)行過程中,你會發(fā)現(xiàn)拜效,究竟是子進程先返回還是父進程先返回喷众,這是不一定的。作為程序員也不能假定返回的時刻紧憾。如果設置好子進程的一切(我理解是
wake_up_new_task(p);
執(zhí)行完成)到千,調(diào)度點發(fā)生在do_fork返回之前,那么子進程先返回赴穗;如果是do_fork返回之后憔四,調(diào)度點才到,父進程先返回般眉,奇妙的很了赵。至于子進程的返回過程,分析如下:
在copy_process中煤篙,乃至其中的copy_thread都對子進程的各種狀態(tài)信息做了充分的設置斟览。我們關注ip以及eax的保存。
調(diào)度點到了之后辑奈,系統(tǒng)會恢復eax為0苛茂,將ip恢復為ret_from_fork已烤,首先保存eax,然后做一些必要的準備工作妓羊,重置內(nèi)核eflags胯究,最后跳轉(zhuǎn)到syscall_exit,
你看躁绸,這樣裕循,這樣,然后再這樣净刮,Linux就把進程創(chuàng)建出來了剥哑。
那么問題來了,為什么子進程的syscall_exit之后淹父,就能返回到fork的下面一句代碼執(zhí)行呢株婴?
思考中。暑认。困介。