之前有聽到別人的面試題是問系統創建進程的具體過程是什么,首先想到的是CreateProcess,但是對於具體過程卻不是很清楚,今天整理一下。
從操作系統的角度來說
創建進程步驟:
1.申請進程塊
2.為進程分配內存資源
3.初始化進程塊
4.將進程塊鏈入就緒隊列
課本上的知識。。。
從CreateProcess的具體流程來說:
CreateProcess它首先創建一個執行體進程對象,即EPROCESS 對象,然后創建一個初始線程,為初始線程建立一個棧,並設置好它的初始執行環境。完成這些工作以后,該線程就可以參與系統的線程調度了。然而,通過Windows API 函數創建的進程也要接受Windows 子系統的管理,在這種情況下,僅僅內核部分的工作還不夠,系統在創建進程過程中,還需要跟子系統打交道。另外,還需建立起獨立的內存地址空間。
CreateProcess通過內核創建進程的步驟,大致分為六個階段:
NtCreateProcess,它只是簡單地對參數稍作處理,然后把創建進程的任務交給NtCreateProcessEx 函數,所以我們來看NtCreateProcessEx 的原型及其流程。
NTSTATUS
NtCreateProcessEx(
__out PHANDLE ProcessHandle,
__in ACCESS_MASK DesiredAccess,
__in_opt POBJECT_ATTRIBUTES ObjectAttributes,
__in HANDLE ParentProcess,
__in ULONG Flags,
__in_opt HANDLE SectionHandle,
__in_opt HANDLE DebugPort,
__in_opt HANDLE ExceptionPort,
__in ULONG JobMemberLevel
);
NtCreateProcessEx 函數的代碼只是簡單地檢查ProcessHandle 參數代表的句柄是否可寫,然后把真正的創建工作交給PspCreateProcess 函數,所以,PspCreateProcess 才是真正創建進程的函數。
PsCreateSystemProcess 可用於創建系統進程對象,它創建的進程都是PsInitialSystemProcess 的子進程。所以,PspCreateProcess函數負責創建系統中的所有進程,包括System 進程。下面介紹此函數的基本流程。
第一階段:打開目標映像文件
第二階段:創建內核中的進程對象
第三階段:創建初始線程
第四階段:通知windows子系統進程csrss.exe進程來對新進程進行管理
第五階段:啟動初始線程
第六階段:用戶空間的初始化和Dll連接
具體內容:
在Windows中,CreateProcess要先通過系統調用NtCreateProcess創建進程,成功以后就立即通過系統調用NtCreateThread創建其第一個線程。
第一階段:打開目標映像文件
首先用CreateProcess(實際上是CreateProcessW)打開指定的可執行映像文件,並創建一個內存區對象。注意,內存區對象並沒有被映射到內存中(由於目標進程尚未建立起來,不可能完成內存映射),但它確實是打開了。
第二階段:創建內核中的進程對象
實際上就是創建以EPROCESS為核心的相關數據結構,主要包括:
調用內核中的NtCreateProcessEx 系統服務,實際的調用過程是這樣的:kernel32.dll 中的CreateProcessW調用ntdll.dll 中的存根函數NtCreateProcessEx,而ntdll.dll的NtCreateProcessEx 利用處理器的陷阱機制切換到內核模式下;在內核模式下,系統服務分發函數KiSystemService 獲得控制,它利用當前線程指定的系統服務表,調用到執行體層的NtCreateProcessEx 函數。然后,執行體層的NtCreateProcessEx 函數執行前面介紹的進程創建邏輯,包括創建EPROCESS 對象、初始化其中的域、創建初始的進程地址空間、創建和初始化句柄表,並設置好EPROCESS 和KPROCESS 中的各種屬性,如進程優先級、安全屬性、創建時間等。到這里,執行體層的進程對象已經建立起來,進程的地址空間已經初始化,並且EPROCESS 中的PEB 也已初始化。
第三階段:創建初始線程
這個階段是通過調用NtCreateThread()完成的,主要包括:
現在,雖然進程對象已經建立起來,但是它沒有線程,所以,它自己還不能做任何事情。接下來需要創建一個初始線程,在此之前,首先要構造一個棧以及一個可供運行的環境。初始線程的棧的大小可以通過映像文件獲得,而創建線程則可以通過調用ntdll.dll 中的NtCreateThread 函數來完成。
創建和設置目標線程的ETHREAD數據結構,並處理好與EPROCESS的關系(例如進程塊中的線程計數等等)。
在目標進程的用戶空間創建並設置目標線程的TEB。
將目標線程在用戶空間的起始地址設置成指向Kernel32.dll中的BaseProcessStart()或BaseThreadStart(),前者用於進程中的第一個線程,后者用於隨后的線程。
用戶程序在調用NtCreateThread()時也要提供一個用戶級的起始函數(地址), BaseProcessStart()和BaseThreadStart()在完成初始化時會調用這個起始函數。
ETHREAD數據結構中有兩個成份,分別用來存放這兩個地址。
調用KeInitThread設置目標線程的KTHREAD數據結構並為其分配堆棧和建立執行環境。
特別地,將其上下文中的斷點(返回點)設置成指向內核中的一段程序KiThreadStartup,使得該線程一旦被調度運行時就從這里開始執行。
系統中可能登記了一些每當創建線程時就應加以調用的“通知”函數,調用這些函數。
第四階段:通知windows子系統
每個進程在創建/退出的時候都要向windows子系統進程csrss.exe進程發出通知,因為它擔負着對windows所有進程的管理的責任,
注意,這里發出通知的是CreateProcess的調用者,不是新建出來的進程,因為它還沒有開始運行。
至此,CreateProcess的操作已經完成,但子進程中的線程卻尚未開始運行,它的運行還要經歷下面的第五和第六階段。
第五階段:啟動初始線程
在內核中,新線程的啟動例程是KiThreadStartup函數,這是當PspCreateThread 調用KeInitThread 函數時,KeInitThread 函數調用KiInitializeContextThread(參見base\ntos\ke\i386\thredini.c 文件)來設置的。
KiThreadStartup 函數首先將IRQL 降低到APC_LEVEL,然后調用系統初始的線程函數PspUserThreadStartup。這里的PspUserThreadStartup 函數是PspCreateThread 函數在調用KeInitThread 時指定的,。注意,PspCreateThread函數在創建系統線程時指定的初始線程函數為PspSystemThreadStartup 。線程啟動函數被作為一個參數傳遞給PspUserThreadStartup,在這里,它應該是kernel32.dll 中的BaseProcessStart。
PspUserThreadStartup 函數被調用。邏輯並不復雜,但是涉及異步函數調用(APC)機制。
新創建的線程未必是可以被立即調度運行的,因為用戶可能在創建時把標志位CREATE_ SUSPENDED設成了1;
如果那樣的話,就需要等待別的進程通過系統調用恢復其運行資格以后才可以被調度運行。否則現在已經可以被調度運行了。至於什么時候才會被調度運行,則就要看優先級等等條件了。
第六階段:用戶空間的初始化和Dll連接
PspUserThreadStartup 函數返回以后,KiThreadStartup 函數返回到用戶模式,此時,PspUserThreadStartup 插入的APC 被交付,於是LdrInitializeThunk 函數被調用,這是映像加載器(image loader)的初始化函數。LdrInitializeThunk 函數完成加載器、堆管理器等初始化工作,然后加載任何必要的DLL,並且調用這些DLL 的入口函數。最后,當LdrInitializeThunk 返回到用戶模式APC 分發器時,該線程開始在用戶模式下執行,調用應用程序指定的線程啟動函數,此啟動函數的地址已經在APC 交付時被壓到用戶棧中。
DLL連接由ntdll.dll中的LdrInitializeThunk()在用戶空間完成。在此之前ntdll.dll與應用軟件尚未連接,但是已經被映射到了用戶空間
函數LdrInitializeThunk()在映像中的位置是系統初始化時就預先確定並記錄在案的,所以在進入這個函數之前也不需要連接。
涉及到了Windows內核的知識,細節處還有待理解。。。
參考資料:
http://www.cnblogs.com/csyisong/archive/2010/10/22/1858115.html
http://www.cnblogs.com/Gotogoo/p/5262536.html
http://book.51cto.com/art/201011/235767.htm
《Windows內核原理與實現》潘愛民