Windows Via C/C++ 讀書筆記 3
1. Job
JOB,翻譯成工作或者任務。JOB是管理多個進程的集合體。如果你需要一次關閉多個進程,並且要在所有進程退出後得到通知,那麼可以使用JOB這種對象。
1.1. Job的使用流程
1. 創建JOB或查找一個已有的JOB
2. 把進程加入到JOB中
3. 關閉JOB
4. 等待JOB結束(JOB結束後會處於Signaled狀態,因此可以用WaitForSingleObject 等待)
2. 線程
線程含有兩個東西:
一個內核對象,用於操作系統管理,含有線程的靜態信息。和進程內核對象類似。
線程這個東西用了太久了,就不廢話了。揀點以前沒注意的死角說說。
2.1. 線程棧(Thread`s Stack)。
線程棧是有大小的,默認是1M。超過的時候會有"overflow"異常,但是操作系統會捕捉異常,給棧更多的空間,使棧能動態增長。
創建線程的時候可以通過參數設置棧大小,也可以在link選項/STACK中設置,最終取兩者中大的一個。
2.2. 線程結束
和進程結束太類似了。有點要注意:如果用"TerminateThread" 殺死另外一個線程,操作系統是不會銷燬它的棧,直到這個進程結束。
"GetExitCodeThread"可以得到線程的退出碼,如果線程沒有退出,那麼這個值是"STILL_ACTIVE"
2.3. 線程內部
線程內核對象:
SP(Stack Pointer)指向棧的頭,棧頂是線程函數的參數,接着是執行代碼的起始地址。
IP(Instruction Pointer) 指向函數"RtluserThreadStart",該函數在"NTDLL.dll"中導出。
還保留了線程執行時CPU寄存器的狀態,用於線程切換的時候,可以恢復切換前的執行狀態。
VOID RtlUserThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam) {
__try {
ExitThread((pfnStartAddr)(pvParam));
}
__except(UnhandledExceptionFilter(GetExceptionInformation())) {
ExitProcess(GetExceptionCode());
}
// NOTE: We never get here.
}
RtlUserThreadStart原型
線程執行肯定會在RtlUserThreadStart中結束,正常結束調用ExitThread(線程結束),異常調用ExitProcess(進程結束),並且會彈出窗口報錯。
該函數的第一個參數,函數地址參數是操作系統顯示寫入線程stack中,使該函數看起來像是被別人調用,實際上它是起始執行函數。
2.4. _beginthreadex & _endthreadex
2.4.1. 用_beginthreadex代替CreateThread
多線程的時候,有些全局變量的訪問會衝突。如:errno, _doserrno, strtok, _wcstok, strerror, _strerror, tmpnam, tmpfile, asctime, _wasctime, gmtime, _ecvt, and _fcvt。
爲了讓各個線程有自己的變量,用_beginthreadex代替CreateThread函數。
在_beginthreadex函數中會給每個線程創建一個變量塊(類型爲_tiddata,這個結構存了這些errno什麼的,還有線程函數的入口地址)。
下面都是僞代碼,描述函數主要過程:
_beginthreadex
{
......
分配一塊內存,結構爲_tiddata,變量名爲pid;
創建一個線程(CreateThread),函數爲_threadstartex,把pid作爲參數傳給這個函數;
......
}
_threadstartex
{
......
把pid與TSL關聯,變成與線程相關的變量
Try{
執行線程函數,線程函數地址存在pid中
執行_endthreadex結束線程,把線程函數執行退出結果作爲_endthreadex的參數。
}catch
{
.......
}
......
}
_endthreadex
{
釋放TSL變量pid
調用ExitThread退出線程
}
如過用CreateThread代替_beginthreadex,會有什麼問題呢?
1.操作系統會在第一次使用pid的時候檢查是否已經分配,如果沒有會創建一個新的並關聯。但是因爲沒有調用_endthreadex,這個變量不會被釋放直到進程退出。
2.原文是First, if the thread uses the C/C++ run-time library's signal function, the entire process terminates because the structured exception handling frame has not been prepared.
這塊還不清楚,在_beginthreadex的try塊會處理一些異常,如果用CreateThread這種異常沒有捕捉會使整個進程退出。
2.5. 不要使用_beginthread 和 _endthread
因爲這兩個函數的參數少,因此功能會比ex版本的函數功能弱,比如不能帶安全參數,不能帶suspend標誌,不能返回線程號。_endthread只能使退出碼硬編碼爲0。
_endthread會關閉線程句柄,因此線程結束後線程句柄不能訪問,而ex版本的不會,例如下面這段代碼,線程句柄在_beginthread後可能已經關閉了,後面的訪問會出錯。
DWORD dwExitCode;
HANDLE hThread = _beginthread(...);
GetExitCodeThread(hThread, &dwExitCode); //error!
CloseHandle(hThread);
2.6. 用DuplicateHandle產生真實句柄
The GetCurrentThread function retrieves a pseudo handle for the current thread.
因此需要用函數轉換一下,否則不能被其它函數使用,也不能傳遞給其它線程。