ninesunqian
??
[
資料
] [
站內(nèi)短信
] [
Blog
]
|
2樓
發(fā)表于 2008-9-28 09:05?
|
進程創(chuàng)建: clone, fork, vfork系統(tǒng)調(diào)用
-
??clone系統(tǒng)調(diào)用
-
? ?參數(shù):
-
? ? 執(zhí)行函數(shù)(fn), 參數(shù)(arg)
-
? ? flags|死亡時給父進程發(fā)的信號 (clone_flags): 以下介紹clone_flags
-
? ???資源共享
-
? ?? ?段,頁,打開文件共享:
-
? ?? ? 頁表(不是頁, CLONE_VM),
-
? ?? ? 打開文件(clone_files),
-
? ?? ? 建一個新tls段(clone_settls)
-
? ?? ?路徑和權(quán)限設(shè)置:
-
? ?? ? clone_fs: 共享根目錄, 當(dāng)前目錄, 創(chuàng)建文件初始權(quán)限.
-
? ?? ? clone_newns: 新的根路徑, 自己的視野看文件系統(tǒng)
-
? ?? ?線程通信
-
? ?? ? clone_sighand: 信號處理action, 阻塞和懸掛的信號
-
? ?? ? clone_sysvsem: 共享undoable信號量操作
-
? ???進程關(guān)系
-
? ?? ?同父: clone_parent 創(chuàng)建進程與新進程是兄弟 (同父), 新進程不是創(chuàng)建進程的子進程
-
? ?? ? 為了方便期間, 以下討論暫時不考慮這一因素(它很容易實現(xiàn)), 認(rèn)為創(chuàng)建進程就是父進程
-
? ?? ?同一個線程組: clone_thread. 屬于同一個進程(線程組)
-
? ?? ?都被trace: clone_ptrace
-
? ?? ?子進程不被trace: clone_untrace (內(nèi)核設(shè)置, 覆蓋clone_ptrace)
-
? ???返回tid
-
? ?? ?向父進程返回tid: clone_parent_settid
-
? ?? ?向子進程返回tid: clone_child_settid
-
? ???子進程的狀態(tài):
-
? ?? ?子進程開始就stop: clone_stopped
-
? ???進程死亡或exec通知:
-
? ?? ?啟動內(nèi)核機制: 如果子進程死亡或exec, 它自己空間內(nèi)的tid(*ctid)清零, 并喚醒等待子進程死亡的進程.
-
? ? 賦給子進程的資源
-
? ???子進程的棧(父進程alloc的內(nèi)存地址)
-
? ???線程局部倉庫段(tls)
-
? ? 返回子進程tid的地址
-
? ???父進程用戶空間內(nèi)的地址
-
? ???子進程用戶空間的地址
-
??clone, fork, vfork實現(xiàn)方式
-
??大致相同:
-
? ? 系統(tǒng)調(diào)用服務(wù)例程sys_clone, sys_fork, sys_vfork三者最終都是調(diào)用do_fork函數(shù)完成.
-
? ? do_fork的參數(shù)與clone系統(tǒng)調(diào)用的參數(shù)類似, 不過多了一個regs(內(nèi)核棧保存的用戶模式寄存器). 實際上其他的參數(shù)也都是用regs取的
-
? ?區(qū)別在于:
-
? ? clone:
-
? ???clone的API外衣, 把fn, arg壓入用戶棧中, 然后引發(fā)系統(tǒng)調(diào)用. 返回用戶模式后下一條指令就是fn.
-
? ???sysclone: parent_tidptr, child_tidptr都傳到了 do_fork的參數(shù)中
-
? ???sysclone: 檢查是否有新的棧, 如果沒有就用父進程的棧 (開始地址就是regs.esp)
-
? ? fork, vfork:
-
? ???服務(wù)例程就是直接調(diào)用do_fork, 不過參數(shù)稍加修改
-
? ???clone_flags:
-
? ?? ?sys_fork: SIGCHLD|0;
-
? ?? ?sys_vfork: SIGCHLD| (clone_vfork | clone_vm)
-
? ???用戶棧: 都是父進程的棧.
-
? ???parent_tidptr, child_ctidptr都是NULL.
-
具體實現(xiàn)函數(shù)do_fork() (內(nèi)核函數(shù))的工作流程:
-
??分配PID, 確定子進程到底是否traced.
-
? ?分配空閑的PID
-
? ?確定clone_ptrace位. (確定子進程到底要不要被trace, 而不是參數(shù)所說的希望被trace)
-
? ? 設(shè)置該位: 參數(shù)已設(shè)該位, 且創(chuàng)建線程被trace中
-
? ? 清除該位: 父進程沒有被trace, 或 clone_untrace已經(jīng)設(shè)置.
-
??復(fù)制進程描述符(copy_process)
-
? ?檢查clone_flags是否兼容, 是否安全
-
? ? clone_newns 與 clone_fs 互斥
-
? ? clone_sighand 是 clone_thread 的必要條件: 線程必須共享信號處理
-
? ? clone_vm 是 clone_sighand 的必要條件 : 共享信號處理, 首先要共享信號處理的代碼(在進程頁面里)
-
? ? 附加的安全檢查: security_task_create(clone_flags)
-
? ?復(fù)制進程描述符
-
? ? 在父進程的thread_info里保存浮點寄存器: __unlazy_fpu()
-
? ? 分配新的進程pd(alloc_task_struct), 并拷貝父進程pd
-
? ? 分配新的thread_info(alloc_thread_info), 并拷貝父進程的thread_info.
-
? ? 新的thread_info和新分配的pd 相互引, 新pd的引用計數(shù)設(shè)為2 (表示:新pd有用, 且不是僵尸進程)
-
? ?相關(guān)計數(shù)加1: (此處先相關(guān)計數(shù)檢查, 都通過后再都加1)
-
? ? 檢查并增加: 用戶擁有進程數(shù), 系統(tǒng)總共進程數(shù).
-
? ???一般來說, 所有進程的thread_info總和, 不超過物理內(nèi)存的1/8
-
? ? 新進程的可執(zhí)行格式的引用計數(shù)(FIXME: pd里標(biāo)有可執(zhí)行個數(shù)嗎)
-
? ? 系統(tǒng)執(zhí)行fork總數(shù).
-
? ?進程pd的關(guān)鍵域的設(shè)置(順序與源碼可能不一致):
-
? ? 進程關(guān)系
-
? ???設(shè)置父子關(guān)系 (parent, real_parent, 考慮被trace的情況)
-
? ???設(shè)置新pd的PID
-
? ???設(shè)置tgid, 線程組長的pd(pd->group_leader). (根據(jù)是不是線程組長, 即clone_thread位是否為0)
-
? ???加入PID哈希表(pid, tgid, 如果是進程組長加入pgid和sid表),(調(diào)attach_pid())
-
? ???拷貝tid到父進程的用戶空間(parent_tidptr)
-
? ? 拷貝資源(如果clone_flags沒標(biāo)明共享):
-
? ???文件,目錄,內(nèi)存:copy_files, copy_mm, copy_namespace,
-
? ???進程通信: copy_signal, copy_sighand, copy_semundo
-
? ? 設(shè)置子進程的內(nèi)核棧(thread_info), 內(nèi)核態(tài)相關(guān)寄存器(thread_struct, 不知道這個結(jié)構(gòu)的具體用處): copy_thread()
-
? ???子進程的thread_struct:
-
? ?? ?esp, esp0 - 內(nèi)核棧頂, 內(nèi)核棧底
-
? ?? ?eip - ret_from_fork()的地址 (用戶態(tài)切到內(nèi)核態(tài)的第一條指令)
-
? ?? ?I/O許可位圖 - 如果父進程有, 就拷貝一份過來
-
? ?? ?TLS - 如果用戶空間提供了TLS段, 拷貝過來
-
? ???設(shè)置子進程的內(nèi)核棧:
-
? ?? ?child_regs.esp = 傳入的棧地址參數(shù);
-
? ?? ?child_regs.eax = 0, 給用戶態(tài)的返回值是0
-
? ?? ?清除thread_info中的, TIF_SYSCALL_TRACE位, 防止運行ret_from_fork時, 系統(tǒng)通知調(diào)試進程
-
? ?? ?設(shè)置子進程的thread_info的cpuid
-
? ? 設(shè)置調(diào)度信息(sched_fork())
-
? ???設(shè)置task_running狀態(tài),
-
? ???初始化調(diào)度參數(shù)(時間片),
-
? ???子進程禁止內(nèi)核搶占(thread_info.preempt_cout = 1)
-
? ? 其他:
-
? ???如果沒有被trace,pd->ptrace = 0;
-
? ???設(shè)置pd->exit_signal:
-
? ?? ?有clone_thread位: 設(shè)為參數(shù)clone_flags中的退出信號
-
? ?? ?沒有clone_thread位: 設(shè)為-1 (表示進程終止時, 該LWP不給父進程發(fā)信號)
-
? ???pd->flags: 清除PF_SUPERPRIV , 設(shè)置PF_FORKNOEXEC
-
? ???大內(nèi)核鎖 pd->lock_depth = -1
-
? ???exec次數(shù): pd->did_exec = 0
-
? ???拷貝child_tidptr到pd->set_child_tid. 以備子進程開始執(zhí)行時, 把tid放到自己內(nèi)存空間的child_tidptr
-
? ?返回pd
-
??設(shè)置父子進程的運行狀態(tài), 調(diào)度信息
-
? ?設(shè)置子進程的狀態(tài).
-
? ? 掛信號: 如果創(chuàng)建出來的是停止(clone_stopped)或被trace(pd->ptrace里有PT_PTRACE位)的進程, 懸掛一個SIGSTOP信號.
-
? ???只有debugger發(fā)出SIGCONT信號后, 才能進入運行狀態(tài)
-
? ? 設(shè)狀態(tài),入列隊:如果有clone_stopped位, 子進程設(shè)為stopped狀態(tài); 否則調(diào)用wake_up_new_task(), 把子進程加入就緒列隊:
-
? ???調(diào)整父進程和子進程的調(diào)度參數(shù) (主要是時間片)
-
? ???如果父子在同一CPU上運行, 且頁表不同享, 子進程在插在父進程前
-
? ?? ?子進程很可能exec, 不與父進程共享頁. 這樣防止父進程無用的copy on write.
-
? ???如果不同CPU上運行, 或者共享頁表, 子進程放在列隊最后
-
? ?如果父進程處于被調(diào)試狀態(tài), 程通知調(diào)試器
-
? ? 當(dāng)前進程給debugger進程發(fā)信號, 告知自己創(chuàng)建了子進程; 并停止自己(進入traced狀態(tài)), 使debugger運行.
-
? ???子進程的pid保存在current->ptrace_message中, 供debugger用
-
? ???調(diào)試器發(fā)信號, 使父進程繼續(xù)后, 再進行下一步; 否則父進程一直處于traced狀態(tài)
-
? ?設(shè)置父進程狀態(tài)
-
? ? 如果有clone_vfork, 把自己放到一個等待列隊.
-
? ???內(nèi)核處理完系統(tǒng)調(diào)用后, 會執(zhí)行調(diào)度, 這樣就阻塞父進程了.
-
? ???直到子進程釋放了它的內(nèi)存地址空間, 即子進程終止或exec新程序, 用信號喚醒父進程.
-
??返回子進程的pid.
-
??子進程被調(diào)度后,執(zhí)行pd.thread.eip(ret_from_fork). 調(diào)用關(guān)系(=>): ret_from_fork=>schedule_tail=>finish_task_switch.
-
? ?schedule_tail的另一件事就是: 把pid保存到地址pd->set_child_tid (創(chuàng)建進程使的parent_tidptr)
-
? ?finish_task_switch的動作是: 裝載內(nèi)核棧保存的寄存器(regs->eax為0),返回到用戶態(tài)。系統(tǒng)調(diào)用返回值就是eax(0)
-
內(nèi)核線程:
-
??只運行于kernel模式,只能訪問大于3G的空間。而普通進程在內(nèi)核模式時,能訪問整個4G空間
-
??創(chuàng)建方法, 類似于clone
-
? ?準(zhǔn)備返回地址fn: 構(gòu)造一個regs. 里面有fn, args, __KERNEL_CS等. regs->eip是匯編函數(shù)kernel_thread_helper
-
? ?do_fork (flags|CLONE_VM|clone_untraced, 0, ®s, 0, NULL, NULL)
-
? ? 創(chuàng)建線程, 與父進程共享頁. 用上步構(gòu)造的regs初始化新程的內(nèi)核棧
-
? ?新線程被調(diào)度后. 由ret_from_fork, 用regs恢復(fù)寄存器, 開始執(zhí)行kernel_thread_helper
-
? ?kernel_thread_helper: 把args壓入棧, call fn(args, fn都寄存器中)
-
??典型的內(nèi)核線程:
-
? ?進程0: 所有進程的祖先
-
? ? 編譯時存在.
-
? ???pd, 內(nèi)核棧: init_task, init_thread_union
-
? ???資源: init_mm, init_files, init_fs.??信號: init_signals, init_sighand
-
? ???頁表: swapper_gd_dir
-
? ? 功能
-
? ???初始化系統(tǒng)數(shù)據(jù),
-
? ?? ?多CPU系統(tǒng)中, 開始時BIOS禁用其他CPU.
-
? ?? ?初始化系統(tǒng)數(shù)據(jù)后, 進程0拷貝自己到其他CPU的調(diào)度列隊上, 啟動其他CPU, 所有的PID都是0.
-
? ???使能中斷
-
? ???創(chuàng)建內(nèi)核線程1, (函數(shù)是init)
-
? ???進入idle
-
? ?進程1:
-
? ? init函數(shù) exec可執(zhí)行文件init, 使內(nèi)核線程變成了普通進程.
-
? ? 管理其他進程, 稱為托孤進程
-
? ?其他內(nèi)核線程:
-
? ? 執(zhí)行工作列隊:
-
? ???ksoftirqd: 執(zhí)行 softlets
-
? ???kblockd: 執(zhí)行工作列隊 kblockd_workqueue, 定期激活塊設(shè)備驅(qū)動
-
? ???keventd (又叫events): 處理工作列隊 keventd_wq
-
? ? 管理資源:
-
? ???kapmd: 電源管理
-
? ???kswapd: 交換進程, 用于回收內(nèi)存資源
-
? ???pdflush: flush臟的磁盤緩存
進程銷毀
-
進程終止
-
??系統(tǒng)調(diào)用
-
? ?整個進程終止: exit_group(), 由do_group_exit處理系統(tǒng)調(diào)用. c函數(shù) exit()也是用的這系統(tǒng)調(diào)用
-
? ?某個線程終止: _exit(), 由do_exit處理. C函數(shù)中用到此系統(tǒng)調(diào)用的API: pthread_exit
-
??do_group_exit流程: (整個組內(nèi)至少有一個線程調(diào)用它, 用于整組協(xié)調(diào))
-
? ?檢查線程組的退出過程是否啟動: 檢查signal_group_exit(線程組內(nèi)的公共數(shù)據(jù))是否非零. 如果沒有啟動, 執(zhí)行一下操作來啟動退出過程:
-
? ? 設(shè)置啟動標(biāo)志signal_group_exit.
-
? ? 存儲終止碼(exit_group的參數(shù)), 在current->signal->group_exit_cold
-
? ? 向其他線程發(fā)SIG_KILL信號, (它們收到信號后, 調(diào)do_exit())
-
? ?調(diào)用do_exit, 使本線程退出
-
??do_exit流程:
-
? ?設(shè)置線程的終止標(biāo)志, 退出碼
-
? ? 設(shè)置PF_EXITING, 標(biāo)明要被終止
-
? ? 設(shè)置pd->exit_code
-
? ???系統(tǒng)調(diào)用參數(shù)
-
? ???或是內(nèi)核提供的錯誤碼, 表示異常終止
-
? ?釋放資源:
-
? ? 刪除該進程的定時器
-
? ? 去除對資源的引用:
-
? ???exit_mm, __exit_files;
-
? ???__exit_fs(root路徑,工作路徑, 創(chuàng)建文件權(quán)限), exit_namespace(掛載的文件系統(tǒng)的視野);
-
? ???exit_thread(thread_struct), exit_sem,
-
? ?如果這個線程的函數(shù)實現(xiàn)了一種可執(zhí)行格式, 可執(zhí)行格式數(shù)的引用計數(shù)--; FIXME: 還沒看到這塊兒, 湊合翻譯的不一定對
-
? ?改變父子關(guān)系, 并向父進程發(fā)信號, 改變自己的狀態(tài)(exit_notify)
-
? ? 托付終止線程創(chuàng)建的子進程:
-
? ???如果終止線程還有同組線程: 終止線程創(chuàng)建的子進程, 作為與同組線程的子進程.
-
? ???否則: 終止線程創(chuàng)建的子進程, 作為孤兒進程, 由init進程托管
-
? ? 向父進程發(fā)信號
-
? ???exit_signal有意義 && 最后線程 :??發(fā)exit_signal
-
? ???否則:
-
? ?? ?被trace : 發(fā)SIGCHLD
-
? ?? ?沒被trace : 不發(fā)信號
-
? ? 僵尸自己或直接死亡,??并設(shè)置PF_DEAD位
-
? ???exit_signal沒意義 && 沒被trace : 直接死亡 (這種情況沒有發(fā)信號)
-
? ?? ?變成EXIT_DEAD狀態(tài),
-
? ?? ?release_task() (后面介紹). pd引用計數(shù)變?yōu)?, 不會馬上釋放
-
? ???否則: 僵尸
-
? ?? ?exit_signal有意義 || 被trace : 僵尸
-
? ???整理"僵尸"與"發(fā)臨僵尸信號"的關(guān)系:
-
? ?? ?將發(fā)信號的條件中"最后線程"去掉, 可簡化為(exit_signal有意義)||(被trace) == (發(fā)信號)
-
? ?? ?可得出后: (發(fā)信號) == (僵尸)
-
? ?? ?又可推出: (沒有trace && exit_signal有意義 && 不是最后進程) == (僵尸了,但沒法信號) , 這種情況在移除死進程時, 會給其父進程發(fā)信號 (FIXME: 待驗證)
-
? ?調(diào)度. 調(diào)度函數(shù)會忽略僵尸進程, 但會減少僵尸進程的pd的使用計數(shù); 會檢查PF_DEAD位, 把它變成exit_dead狀態(tài)
|
|