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