目錄
一、進程與線程概述
1.1 進程查看
1.2 何為進程
1.3 進程的創建
1.4 進程創建實例
1.5 線程查看
1.6 何為線程
1.7 線程的創建
1.8 線程函數
1.9 線程實例
二、內核對象
2.1 何為內核對象
2.2 內核對象的公共特點
2.3 內核對象句柄
2.4 內核對象的跨進程訪問
一、進程與線程概述
1.1 進程查看
方法1
????????在屏幕底部的任務欄上右擊鼠標,從彈出的快捷菜單中選擇“任務管理器”,隨后任務管理器窗口將會打開,您便可以在其中瀏覽當前運行的進程。
方法2
????????點擊開始菜單,在搜索欄中輸入“cmd”并打開命令提示符,然后在命令提示符中輸入“tasklist”命令來查看當前運行的進程。
方法3
????????在命令提示符中,首先鍵入“wmic”并按下回車,接著輸入“process”來查看進程信息。
1.2 何為進程
????????進程是指一個正在運行的程序實例。它最早在20世紀60年代由麻省理工學院和IBM公司的CTSS/360系統引入,并成為操作系統結構的基礎。進程是操作系統進行資源分配和調度的基本單位,代表程序在數據集上的執行過程。一個程序可能同時屬于多個進程。
????????進程本身并不執行代碼,而是作為線程的容器。每個進程在創建時都會生成一個主線程來執行代碼。如果主線程結束,系統會銷毀該進程的內核對象。
????????進程可以分為系統進程和用戶進程。系統進程負責完成操作系統的各種功能,相當于運行狀態下的操作系統本身;用戶進程則是由用戶啟動的程序實例。
????????在Windows系統中,進程進一步細分為線程,一個進程可以包含多個獨立運行的線程。與靜止的程序不同,進程是動態的。為了描述多任務操作系統中多道程序并發執行時的資源分配、程序執行的間斷性、通信可能性以及同步互斥等動態特性,引入了進程的概念來參與系統的并發執行。
1.3 進程的創建
CreateProcess()函數可以用來創建一個進程,原型如下所示:
BOOL WINAPI CreateProcess(_In_opt_ LPCTSTR lpApplicationName, //可執行文件名_Inout_opt LPTSTR lpCommandLine, //命令行_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,_In BOOL bInheritHandles, //句柄繼承是否_In DWORD dwCreationFlags, //創建方式標志_In_opt LPVOID lpEnvironment, //環境字符串塊_In_opt LPCTSTR lpCurrentDirectory, //新進程的當前目錄_In LPSTARTUPINFO lpStartupInfo, //進程配置結構體_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
????????此函數執行成功后將創建一個進程內核對象,但需要注意的是,“進程內核對象”與“進程”所指的并非是同一個東西。因此,CreateProcess()函數可以創建成功并不代表進程本身就能正常加載并運行。
更詳細的參數解釋:
- `lpApplicationName`:要執行的可執行文件名稱。
- `lpCommandLine`:傳遞給新進程的命令行字符串,需要注意的是,此參數類型為PTSTR,這意味著CreateProces()在執行過程中可能會修改我們傳入的值。
- `lpProcessAttributes`:進程安全屬性,這些都屬于內核對象。
- `lpThreadAttributes`:線程安全屬性。
- `bInheritHandles`:表示新創建的子進程是否繼承父進程中的所有句柄,是的話子進程就可以訪問父進程中創建的所有句柄。
- `dwCreationFlags`:子進程的創建方式。
- `lpEnvironment`:指向保存有進程環境字符串的內存塊。
- `lpCurrentDirectory`:新創建子進程的當前目錄。
- `lpStartupInfo`:指向子進程創建配置結構體,此結構可以詳細控制子進程的各種創建狀態。
- `lpProcessInformation`:返回進程創建的詳細信息。
1.4 進程創建實例
bool CreateChildProcess(LPWSTR lpPath, BOOL bWait) {// 初始化 STARTUPINFO 結構體,設置其大小為結構體本身的大小STARTUPINFO si = {0};si.cb = sizeof(si);// 初始化 PROCESS_INFORMATION 結構體,用于接收新進程的信息PROCESS_INFORMATION pi = {0};// 嘗試創建子進程if (!CreateProcess(lpPath, // 可執行文件的路徑NULL, // 命令行參數(未使用)NULL, // 進程句柄不可繼承NULL, // 線程句柄不可繼承FALSE, // 不繼承句柄0, // 無特殊標志NULL, // 使用父進程的環境變量NULL, // 使用父進程的當前目錄&si, // 指向 STARTUPINFO 結構體&pi)) { // 指向 PROCESS_INFORMATION 結構體// 如果創建進程失敗,返回 falsereturn false;}// 如果需要等待子進程執行結束if (bWait) {// 無限等待子進程結束WaitForSingleObject(pi.hProcess, INFINITE);}// 關閉進程句柄CloseHandle(pi.hProcess);// 關閉線程句柄CloseHandle(pi.hThread);// 返回 true 表示成功創建子進程return true;
}
1.5 線程查看
????????使用PCHunter或者火絨劍工具可以查看指定進程的線程及線程的信息。
1.6 何為線程
????????線程是操作系統中的一個內核對象。在Windows操作系統的內核中,并沒有進程的概念,只有線程的概念。進程實際上是從邏輯上對一組線程及其相關資源進行的整合。
????????線程是“進程”中的一個單一順序的控制流,也被稱為輕量級進程(lightweight processes),是程序運行時的調度單位。一個線程可以創建多個子線程,而這些子線程又可以繼續創建更多的線程。
????????從資源占用的角度來看,線程所需的系統資源遠遠少于進程,但它在功能實現上并不遜色。因此,在開發項目時,應優先考慮創建新的線程,而不是啟動一個新的進程。
1.7 線程的創建
創建線程的函數原型如下:
HANDLE WINAPI CreateThread(_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,_In_ SIZE_T dwStackSize, //棧大小,默認1MB_In_ LPTHREAD_START_ROUTINE lpStartAddress, //不能為0_In_opt_ LPVOID lpParameter,_In_ DWORD dwCreationFlags,_Out_opt_ LPDWORD lpThreadId
);
參數解釋:
- `dwStackSize`:指定線程可以擁有多少棧,屬于線程。
- `lpStartAddress`:線程函數起始地址。
- `lpParameter`:線程函數參數。
- `dwCreationFlags`:線程創建標記。
- `lpThreadId`:新創建線程的ID。
1.8 線程函數
????????像主線程的初始函數為mainCRTStartup/wmainCRTStartup一樣,我們創建線程時也需要指定一個線程函數。為了取得一致性,Windows對線程函數做了限定,其原型如下所示:
DWORD WINAPI ThreadProc(LPVOID lpParam) {return 0;
}
????????就像線程可指定線程函數一樣,主線程同樣可以指定自己的線程函數,在VisualStudio下,可以通過以下編譯選項指定主線程函數:
//指定程序入口函數為MyFun()
#pragma comment(linker,"/entry:\"MyFun\"")
1.9 線程實例
// 線程函數,顯示一個消息框
DWORD WINAPI ThreadProc(LPVOID lpParam) {// 顯示消息框MessageBox(NULL, (LPCWSTR)lpParam, L"CreateThread", MB_OK);return 0; // 線程正常退出
}// 創建子線程的函數
bool CreateChildThread() {DWORD dwThreadId = 0; // 用于保存線程IDHANDLE hThread = CreateThread(NULL, // 默認安全屬性0, // 默認堆棧大小ThreadProc, // 線程函數(LPVOID)L"Hello from the new thread!", // 傳遞給線程的參數0, // 默認創建標志&dwThreadId); // 返回線程ID// 檢查線程是否創建成功if (hThread == NULL) {std::cerr << "CreateThread failed: " << GetLastError() << std::endl;return false; // 創建線程失敗,返回false}// 關閉線程句柄,避免資源泄漏CloseHandle(hThread);std::cout << "子線程創建成功,線程ID: " << dwThreadId << std::endl;return true; // 創建線程成功,返回true
}
二、內核對象
2.1 何為內核對象
????????Windows操作系統雖然是用C語言和匯編語言編寫的,但它是一個面向對象的操作系統,系統中廣泛使用了對象的概念。例如,窗口、設備環境、代表DLL的模塊、程序實例、文件、進程、畫筆、畫刷等都是對象。從本質上說,這些對象就是一個個結構體變量。然而,Windows系統不希望程序員直接定義、訪問或修改這些結構體變量,因此它們被完全保護了起來。在使用這些對象時,都有一個共同點:必須先獲取它們的句柄,然后通過調用相應的API來操作這些對象。
????????簡單來說,句柄就像是訪問對象的“鑰匙”,或者用程序員的話來說,句柄是對象的索引。
????????根據對象的不同用途,Windows將對象分為多個類別,主要包括用戶對象(user對象)、圖形設備接口對象(GDI對象)和內核對象。其中,內核對象通常與系統的全局功能相關。
????????常見的Windows內核對象包括:進程、線程、訪問令牌、文件、文件映射、I/O完成端口、郵槽、管道、互斥體、信號量、事件、計時器和線程池等。
2.2 內核對象的公共特點
所有內核對象都遵循統一的使用模式:
(1)第一步:創建對象:創建一個內核對象,一般都是使用CreateXXX的方式,比如:
??? - `CreateProcess`:創建進程。
??? - `CreateThread`:創建線程。
??? - `CreateFile`:創建文件。
??? - `CreateFileMapping`:創建文件映射。
??? - `CreateEvent`:創建事件對象。
??? - `CreateSemaphore`:創建信號量。
(2)第二步:打開對象,得到句柄 (可與第一步合并在一起,表示創建的時后就打開)。
(3)第三步:通過API訪問對象。
(4)第四步:關閉句柄。
(5)第五步:句柄全部關完,對象自動銷毀。
????????所有的內核對象都由操作系統內核管理,并且可以在不同進程之間訪問,這就是所謂的“內核對象是跨進程的”。在許多情況下,我們需要在不同的進程中訪問同一個內核對象,比如實現進程間的同步或共享數據。
????????每個內核對象都有一個引用計數。可以理解為每個內核對象的結構體中都有一個字段用來記錄引用計數。當一個進程創建或打開這個內核對象時,引用計數會增加1;當進程終止或關閉句柄時,引用計數會減少1。當引用計數減到0時,內核對象就會被銷毀。
????????舉個例子,如果內核對象M由進程A創建,之后進程B也使用了這個對象,那么即使進程A退出了,M也不會被銷毀,因為它仍然被進程B使用。只有當沒有任何進程使用這個內核對象時,它才會被自動銷毀。
????????在創建內核對象時,每個對象都會有一個安全屬性。這個安全屬性定義了對象的使用方式,比如是否可讀可寫,以及對象具有的權限等。一旦內核對象以某種安全屬性創建,它就只能在規定的權限范圍內工作。通常,在創建內核對象時,我們需要指定一個`SECURITY_ATTRIBUTES`結構體來設置安全屬性。如果傳入`NULL`,系統會使用默認的安全屬性。
2.3 內核對象句柄
????????在Windows操作系統中,操作對象需要通過句柄來實現,內核對象也不例外。不過,內核對象的句柄是與進程相關的,這意味著同一個內核對象在不同進程中的句柄值是不同的。這一點與GDI對象不同,GDI對象的句柄值是全局有效的,不同進程可以使用相同的句柄值訪問同一個GDI對象。由此可見,不同類型的對象在管理方式上存在差異。
????????每個進程都有一個句柄表,用于記錄該進程打開的所有內核對象。可以簡單地把句柄表理解為一個一維的結構體數組,而句柄值就是這個數組中的索引。因此,內核對象的句柄值僅對當前進程有效。
????????句柄表中的每一項不僅記錄了通過該句柄訪問內核對象的權限,還指明了該句柄是否可以被其子進程繼承。
2.4 內核對象的跨進程訪問
????????要訪問內核對象,進程的句柄表中必須有一個句柄項指向該內核對象。對于創建該內核對象的進程,句柄會直接插入到其句柄表中。而對于其他進程,通常有三種方式可以跨進程訪問內核對象:
(1)父進程繼承給子進程:當父進程創建子進程時,如果指定了句柄可繼承的屬性,子進程會繼承父進程中所有可繼承的句柄。需要注意的是,即使子進程繼承了句柄,它也不知道具體繼承了哪些句柄以及它們的值,這些信息只能由父進程通過進程間通信的方式傳遞給子進程。
(2)通過命名內核對象:在進程A中創建內核對象時,可以為該對象命名。進程B可以通過名稱打開該內核對象。如果內核對象無法命名或沒有唯一標識,則無法使用這種方式。
(3)使用`DuplicateHandle`函數:通過這個函數,可以將一個句柄從一個進程傳遞給另一個進程,從而在目標進程中獲得源進程中內核對象的句柄。
????????通常,獲取內核對象句柄的方式有以下四種:
(1)創建對象:例如,使用`CreateFile`創建新文件時,會同時打開該文件對象并獲得句柄。
(2)繼承句柄:子進程繼承父進程句柄表中的句柄。
(3)顯式打開:例如,使用`OpenFile`、`OpenMutex`、`OpenProcess`等函數顯式打開某個對象。
(4)`DuplicateHandle`:通過該函數間接打開對象并獲得句柄。
????????需要注意的是,句柄的概念可以理解為“具有指定權限和屬性的訪問句柄”。句柄代表對對象的一次打開操作,其權限決定了可以通過該句柄執行的操作。例如,如果打開文件時申請的權限是只讀,那么即使當前用戶擁有寫權限,使用該句柄調用`WriteFile`也會被拒絕訪問。
????????句柄的屬性中,一個重要特性是它是否可以被繼承給子進程。