進程概述及創建,終止(VC_Win32)


目錄

進程概述
創建進程函數詳解
進程的創建
進程的終止
進程總體執行流程
進程線程優先級

進程通信

(本章節中例子都是用 VS2005 編譯調試的)


進程概述

進程定義:

通常被定義為一個正在運行的程序實例,是一個程序在其自身的地址空間中的一次執行活動

程序相關描述:

  • 定義:  計算機指令集合,它以文件的形式存儲在磁盤上
  • 與進程關系:  一個程序可以對應多個進程
  • windows支持兩種類型的應用程序:GUI程序(Graphical User Interface 圖形用戶界面)和CUI程序(Console User Interface 控制台用戶界面).

進程組成:

  • 內核對象:  內核對象也是用系統用來存放進程的統計信息的地方.內核對象是操作系統內部分配的一個內存塊,該內存塊是一種數據結構,其成員維護該對象的各種信息.由於內核對象的數據結構只能被內核訪問使用,因此應用程序在內存中無法找到該數據結構,並直接改變其內容,只能通過 windows 提供的一些函數來實現內核對象的操作,進程內核對象存活時間至少能和進程本身一樣長.
  • 地址空間:  它包含所有可執行模塊(.exe)或動態鏈接庫模塊(.dll)的代碼和數據,另外,它也包含動態分配的空間

地址空間:

系統賦予每個進程獨立的虛擬的地址空間,每個進程都有自己的私有的地址空間,各自的線程都可以訪問各自進程地址空間中的數據,但是一般情況下各個進程的線程是無法直接訪問其他進程的地址空間的數據

注意:

  • 進程重來不執行任何東西,它只是線程的容器,若要使進程完成某項操作,它必須擁有一個在它環境中運行的線程,此線程負責執行包含在進程的地址空間中的代碼,也就是說,真正完成代碼執行的是線程,而進程只是線程的容器,或者說進程的執行環境.當創建一個進程時,操作系統會自動創建這個進程的第一個線程,也就是主線程,也就是執行 main 函數或 WinMain 函數的線程,可以把 main 函數或 WinMain 函數看做是主線程的進入點函數,此后,主線程可以創建其他線程.所以若沒有線程要執行進程地址空間包含的代碼,進程就失去了繼續存在下去的意義.這時候系統就會自動銷毀經常及其地址空間.
  • 進程在終止后不會泄露任何東西

VC 的編譯鏈接器

集成開發環境會設置各種鏈接器開關,使鏈接器將子系統的正確類型嵌入最終可執行文件.

    • 對應CUI程序,這個連接器的開關是 /SUBSYSTEM:CONSOLE 
    • 對應GUI程序來說則是 /SUBSYSTEM:WINDOWS
    • 當然可以完全從項目中移除/SUBSYSTEM鏈接器開個,一旦這樣做,鏈接器會自動判斷應用程序設置為哪一個子系統

對應程序類型和相應的入口函數

應用程序類型                  入口函數          嵌入可執行文件的啟動函數
處理ANSI字符和字符串的GUI應用程序     _tWinMain(WinMain)   WinMainCRTStartup
處理Unicode字符和字符串的GUI應用程序     _tWinMain(wWinMain)  wWinMainCRTStartup
處理ANSI字符和字符串的GUI應用程序     _tmain(main)      mainCRTStartup
處理Unicode字符和字符串的GUI應用程序     _tmain(wmain)       wmainCRTStartup

實例句柄(HINSTANCE)

加載到進程地址空間的每一可執行文件(EXE)或動態鏈接庫(DLL)文件都被賦予了一個獨一無二的實例句柄(HINSTANCE).(w)WinMain 的 hInstance 參數的實際值是一個內存基址,系統將可執行文件的映像加載到進程空間的這個位置

為了知道一個可執行文件(EXE)或動態鏈接庫(DLL)被加載到進程地址空間的什么位置,可以使用 GetModuleHandle 函數來返回一個句柄/基址

還需注意的是事實上 HMOBULE 和 HINSTANCE 完全是一回事,如果某個文檔指出需要一個HMODULE 參數,我們可以傳入一個 HINSTANCE

進程標識(PID)

創建一個進程內核對象時,系統會為此對象分配一個獨一無二的標識符.系統中沒有別的進程內核對象會有相同的 ID 編號,這同樣適用於線程內核對象,創建一個線程內核對象時,此對象會被分配一個獨一無二的,系統級別的ID編號,進程 ID 和線程 ID 分享同一個號碼池,這意味着線程和進程不可能有相同的 ID,此外,一個對象分配到的 ID 絕不可能是0.進程和線程ID會被系統立即重用(假定在創建一個進程之后,系統初始化可一個進程對象,並將 ID 值 124 分配給他,如果在建立一個新的進程對象,系統不會將同一個 ID 編號分配給它,但是,如果一個進程對象以及釋放,系統可以將 124 分配給下一個創建的進程對象).

獲得進程(或線程)標識的函數

系統確實會記住每個進程父進程的 ID,但由於 ID 會立即被重用,所以等我們獲得父進程的 ID 的時候,那個 ID 可能已經是系統運行的一個完全不同的進程.要保證一個進程或線程 ID 不被重用,唯一的辦法就是保證進程或線程對象不被銷毀,對應子進程,除非父進程復制了自己的進程或線程對象句柄,並允許子進程繼承這些句柄,否則無法確保父進程的進程ID或線程ID的有效性.

獲取當前進程句柄

獲取當前進程句柄 GetCurrentProcess (這個函數都返回的是一個偽句柄.它不會在主調進程的句柄表中新建句柄.而且調用這個函數,不會影響進程內核對象的使用計數器.如果調用 CloseHandle 函數關閉一個偽句柄,CloseHandle 只是簡單地忽略此調用,並返回 FALSE,將偽句柄轉換為真正的句柄: DuplicateHandle)


創建進程函數詳解(可以參看Windows核心編程第四章有詳細介紹)

顯示相關函數

函數原型

BOOL CreateProcess(
  LPCTSTR lpApplicationName,                 // name of executable module
  LPTSTR lpCommandLine,                      // command line string
  LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD
  BOOL bInheritHandles,                      // handle inheritance option
  DWORD dwCreationFlags,                     // creation flags
  LPVOID lpEnvironment,                      // new environment block
  LPCTSTR lpCurrentDirectory,                // current directory name
  LPSTARTUPINFO lpStartupInfo,               // startup information
  LPPROCESS_INFORMATION lpProcessInformation // process information
);

 

參數說明:

  • lpapplicationname

    指向一個NULL結尾的、用來指定可執行模塊的字符串(必須指定文件的擴展名,系統不會自動假設文件有一個 .exe 的擴展名).這個字符串可以使可執行模塊的絕對路徑,也可以是相對路徑,在后一種情況下,函數使用當前驅動器和目錄建立可執行模塊的路徑.
    這個參數可以被設為NULL,在這種情況下,可執行模塊的名字必須處於 lpcommandline 參數的最前面並由空格符與后面的字符分開.這個被指定的模塊可以是一個win32應用程序.
    如果適當的子系統在當前計算機上可用的話,它也可以是其他類型的模塊(如ms-dos 或 os/2).在windows nt中.
    如果可執行模塊是一個16位的應用程序,那么這個參數應該被設置為NULL並且因該在lpcommandline參數中指定可執行模塊的名稱.16位的應用程序是以dos虛擬機或win32上的windows(wow) 為進程的方式運行.

  • lpcommandline

    指向一個NULL結尾的、用來指定要運行的命令行.這個參數可以為空,那么函數將使用參數指定的字符串當作要運行的程序的命令行.
    如果lpapplicationname和lpcommandline參數都不為空,那么lpapplicationname參數指定將要被運行的模塊,lpcommandline參數指定將被運行的模塊的命令行.新運行的進程可以使用getcommandline函數獲得整個命令行.c語言程序可以使用argc和argv參數.
    如果lpapplicationname參數為空,那么這個字符串中的第一個被空格分隔的要素指定可執行模塊名.
    如果文件名不包含擴展名,那么.exe將被假定為默認的擴展名.
    如果文件名以一個點(.)結尾且沒有擴展名,或文件名中包含路徑,.exe將不會被加到后面.
    如果文件名中不包含路徑,windows將按照如下順序尋找這個可執行文件:

      1. 當前應用程序的目錄.
      2. 父進程的目錄.
      3. windows 95:windows系統目錄,可以使用getsystemdirectory函數獲得. windows nt:32位windows系統目錄.可以使用getsystemdirectory函數獲得,目錄名是system32.
      4. 在windows nt中:16位windows系統目錄.不可以使用win32函數獲得這個目錄,但是它會被搜索,目錄名是system.
      5. windows目錄.可以使用getwindowsdirectory函數獲得這個目錄.
      6. 列在path環境變量中的目錄.

    如果被創建的進程是一個以ms-dos或16位windows為基礎的應用程序,lpcommandline參數應該是一個以可執行文件的文件名作為第一個要素的絕對路徑,因為這樣做可以使32位windows程序工作的很好,這樣設置lpcommandline參數是最強壯的.

  • lpprocessattributes

    指向一個security_attributes結構體,這個結構體決定是否返回的句柄可以被子進程繼承.
    如果lpprocessattributes參數為空(NULL),那么句柄不能被繼承.在windows nt中:security_attributes結構的lpsecuritydescriptor成員指定了新進程的安全描述符.
    如果參數為空,新進程使用默認的安全描述符.在windows95中:security_attributes結構的lpsecuritydescriptor成員被忽略.

  • lpthreadattributes

    指向一個security_attributes結構體,這個結構體決定是否返回的句柄可以被子進程繼承.
    如果lpthreadattributes參數為空(NULL),那么句柄不能被繼承.在windows nt中,security_attributes結構的lpsecuritydescriptor成員指定了主線程的安全描述符.
    如果參數為空,主線程使用默認的安全描述符.在windows95中:security_attributes結構的lpsecuritydescriptor成員被忽略.

  • binherithandles

    指示新進程是否從調用進程處繼承了句柄.
    如果參數的值為真,調用進程中的每一個可繼承的打開句柄都將被子進程繼承.被繼承的句柄與原進程擁有完全相同的值和訪問權限.

  • dwcreationflags

    指定附加的、用來控制優先類和進程的創建的標志.以下的創建標志可以以除下面列出的方式外的任何方式組合后指定.
    進程創建標志

      • create_default_error_mode:  新的進程不繼承調用進程的錯誤模式.createprocess函數賦予新進程當前的默認錯誤模式作為替代.應用程序可以調用seterrormode函數設置當前的默認錯誤模式.這個標志對於那些運行在沒有硬件錯誤環境下的多線程外殼程序是十分有用的.對於createprocess函數,默認的行為是為新進程繼承調用者的錯誤模式.設置這個標志以改變默認的處理方式.
      • create_new_console:  新的進程將使用一個新的控制台,而不是繼承父進程的控制台.這個標志不能與detached_process標志一起使用.
      • create_new_process_group:  新進程將使一個進程樹的根進程.進程樹種的全部進程都是根進程的子進程.新進程樹的用戶標識符與這個進程的標識符是相同的,由lpprocessinformation參數返回.進程樹經常使用generateconsolectrlevent函數允許發送ctrl+c或ctrl+break信號到一組控制台進程.
      • create_separate_wow_vdm:  (只適用於windows nt)這個標志只有當運行一個16位的windows應用程序時才是有效的.
        如果被設置,新進程將會在一個私有的虛擬dos機(vdm)中運行.另外,默認情況下所有的16位windows應用程序都會在同一個共享的vdm中以線程的方式運行.單獨運行一個16位程序的優點是一個應用程序的崩潰只會結束這一個vdm的運行;其他那些在不同vdm中運行的程序會繼續正常的運行.同樣的,在不同vdm中運行的16位windows應用程序擁有不同的輸入隊列,這意味着如果一個程序暫時失去響應,在獨立的vdm中的應用程序能夠繼續獲得輸入.
      • create_shared_wow_vdm:  (只適用於windows nt)這個標志只有當運行一個16位的windows應用程序時才是有效的.如果win.ini中的windows段的defaultseparatevdm選項被設置為真,這個標識使得createprocess函數越過這個選項並在共享的虛擬dos機中運行新進程.
      • create_suspended:  新進程的主線程會以暫停的狀態被創建,直到調用resumethread函數被調用時才運行.
      • create_unicode_environment:  如果被設置,由lpenvironment參數指定的環境塊使用unicode字符.如果為空,環境塊使用ansi字符.
      • debug_process:  如果這個標志被設置,調用進程將被當作一個調試程序,並且新進程會被當作被調試的進程.系統把被調試程序發生的所有調試事件通知給調試器.如果你使用這個標志創建進程,只有調用進程(調用createprocess函數的進程)可以調用waitfordebugevent函數.
      • debug_only_this_process:  如果此標志沒有被設置且調用進程正在被調試,新進程將成為調試調用進程的調試器的另一個調試對象.如果調用進程沒有被調試,有關調試的行為就不會產生.
      • detached_process:  對於控制台進程,新進程沒有訪問父進程控制台的權限.新進程可以通過allocconsole函數自己創建一個新的控制台.這個標志不可以與create_new_console標志一起使用.

    進程優先級
    dwcreationflags參數還用來控制新進程的優先類,優先類用來決定此進程的線程調度的優先級.如果下面的優先級類標志都沒有被指定,那么默認的優先類是normal_priority_class,除非被創建的進程是idle_priority_class.在這種情況下子進程的默認優先類是idle_priority_class.可以下面的標志中的一個:

      • high_priority_class :  指示這個進程將執行時間臨界的任務,所以它必須被立即運行以保證正確.這個優先級的程序優先於正常優先級或空閑優先級的程序.一個例子是windows任務列表,為了保證當用戶調用時可以立刻響應,放棄了對系統負荷的考慮.確保在使用高優先級時應該足夠謹慎,因為一個高優先級的cpu關聯應用程序可以占用幾乎全部的cpu可用時間.
      • idle_priority_class :  指示這個進程的線程只有在系統空閑時才會運行並且可以被任何高優先級的任務打斷.例如屏幕保護程序.空閑優先級會被子進程繼承.
      • normal_priority_class :  指示這個進程沒有特殊的任務調度要求.
      • realtime_priority_class :  指示這個進程擁有可用的最高優先級.一個擁有實時優先級的進程的線程可以打斷所有其他進程線程的執行,包括正在執行重要任務的系統進程.例如,一個執行時間稍長一點的實時進程可能導致磁盤緩存不足或鼠標反映遲鈍.
  • lpenvironment

    指向一個新進程的環境塊.
    如果此參數為空,新進程使用調用進程的環境.一個環境塊存在於一個由以NULL結尾的字符串組成的塊中,這個塊也是以NULL結尾的.每個字符串都是name=value的形式.因為相等標志被當作分隔符,所以它不能被環境變量當作變量名.與其使用應用程序提供的環境塊,不如直接把這個參數設為空,系統驅動器上的當前目錄信息不會被自動傳遞給新創建的進程.對於這個情況的探討和如何處理,請參見注釋一節.環境塊可以包含unicode或ansi字符.
    如果lpenvironment指向的環境塊包含unicode字符,那么dwcreationflags字段的create_unicode_environment標志將被設置.
    如果塊包含ansi字符,該標志將被清空.請注意一個ansi環境塊是由兩個零字節結束的:一個是字符串的結尾,另一個用來結束這個快.一個unicode環境塊石油四個零字節結束的:兩個代表字符串結束,另兩個用來結束塊.

  • lpcurrentdirectory

    指向一個以NULL結尾的字符串,這個字符串用來指定子進程的工作路徑.這個字符串必須是一個包含驅動器名的絕對路徑.如果這個參數為空,新進程將使用與調用進程相同的驅動器和目錄.這個選項是一個需要啟動啟動應用程序並指定它們的驅動器和工作目錄的外殼程序的主要條件.

  • lpstartupinfo

    指向一個用於決定新進程的主窗體如何顯示的startupinfo結構體.

    //用於指定新進程的主窗口如何顯示,成員參見msdn
    typedef struct _STARTUPINFO { 
        DWORD   cb; 
        LPTSTR  lpReserved; 
        LPTSTR  lpDesktop; 
        LPTSTR  lpTitle; 
        DWORD   dwX; 
        DWORD   dwY; 
        DWORD   dwXSize; 
        DWORD   dwYSize; 
        DWORD   dwXCountChars; 
        DWORD   dwYCountChars; 
        DWORD   dwFillAttribute; 
        DWORD   dwFlags; 
        WORD    wShowWindow; 
        WORD    cbReserved2; 
        LPBYTE  lpReserved2; 
        HANDLE  hStdInput; 
        HANDLE  hStdOutput; 
        HANDLE  hStdError; 
    } STARTUPINFO, *LPSTARTUPINFO; 
  • lpprocessinformation

    指向一個用來接收新進程的識別信息的process_information結構體.

    typedef struct _PROCESS_INFORMATION { 
        HANDLE hProcess; //進程句柄
        HANDLE hThread; //主線程句柄
        DWORD dwProcessId; //進程標識id
        DWORD dwThreadId;  //主線程標識id
    } PROCESS_INFORMATION; 

返回值:

  • 如果函數執行成功,返回非零值.
  • 如果函數執行失敗,返回零,可以使用getlasterror函數獲得錯誤的附加信息

說明:

createprocess函數用來運行一個新程序.winexec和loadmodule函數依舊可用,但是它們同樣通過調用createprocess函數實現.
另外createprocess函數除了創建一個進程,還創建一個線程對象.這個線程將連同一個已初始化了的堆棧一起被創建,堆棧的大小由可執行文件的文件頭中的描述決定.線程由文件頭處開始執行.
新進程和新線程的句柄被以全局訪問權限創建.對於這兩個句柄中的任一個,如果沒有安全描述符,那么這個句柄就可以在任何需要句柄類型作為參數的函數中被使用.當提供安全描述符時,在接下來的時候當句柄被使用時,總是會先進行訪問權限的檢查,如果訪問權限檢查拒絕訪問,請求的進程將不能使用這個句柄訪問這個進程.
這個進程會被分配給一個32位的進程標識符.直到進程中止這個標識符都是有效的.它可以被用來標識這個進程,或在openprocess函數中被指定以打開這個進程的句柄.進程中被初始化了的線程一樣會被分配一個32位的線程標識符.這個標識符直到縣城中止都是有效的且可以用來在系統中唯一標識這個線程.這些標識符在process_information結構體中返回.
當在lpapplicationname或lpcommandline參數中指定應用程序名時,應用程序名中是否包含擴展名都不會影響運行,只有一種情況例外:一個以.com為擴展名的ms-dos程序或windows程序必須包含.com擴展名.
調用進程可以通過waitforinputidle函數來等待新進程完成它的初始化並等待用戶輸入.這對於父進程和子進程之間的同步是極其有用的,因為createprocess函數不會等待新進程完成它的初始化工作.舉例來說,在試圖與新進程關聯的窗口之前,進程應該先調用waitforinputidle.
首選的結束一個進程的方式是調用exitprocess函數,因為這個函數通知這個進程的所有動態鏈接庫(dlls)程序已進入結束狀態.其他的結束進程的方法不會通知關聯的動態鏈接庫.注意當一個進程調用exitprocess時,這個進程的其他縣城沒有機會運行其他任何代碼(包括關聯動態鏈接庫的終止代碼).
exitprocess, exitthread, createthread, createremotethread,當一個進程啟動時(調用了createprocess的結果)是在進程中序列化進行的.在一段地址空間中,同一時間內這些事件中只有一個可以發生.這意味着下面的限制將保留:

    • 在進程啟動和dll初始化階段,新的線程可以被創建,但是直到進程的dll初始化完成前它們都不能開始運行.
    • 在dll初始化或卸下例程中進程中只能有一個線程. *直到所有的線程都完成dll初始化或卸下后,exitprocess函數才返回.
    • 在進程中的所有線程都終止且進程所有的句柄和它們的線程被通過調用closehandle函數終止前,進程會留在系統中.進程和主線程的句柄都必須通過調用closehandle函數關閉.如果不再需要這些句柄,最好在創建進程后立刻關閉它們.

當進程中最后一個線程終止時,下列的事件發生:

    • 所有由進程打開的對象都會關閉.
    • 進程的終止狀態(由getexitcodeprocess函數返回)從它的初始值still_active變為最后一個結束的線程的結束狀態.
    • 主線程的線程對象被設置為標志狀態,供其他等待這個對象的線程使用.
    • 進程對象被設置為標志狀態,供其他等待這個對象的線程使用.

假設當前在c盤上的目錄是/msvc/mfc且有一個環境變量叫做c:,它的值是c:/msvc/mfc,就像前面lpenvironment中提到過的那樣,這樣的系統驅動器上的目錄信息在createprocess函數的lpenvironment參數不為空時不會被自動傳遞到新進程里.一個應用程序必須手動地把當前目錄信息傳遞到新的進程中.為了這樣做,應用程序必須直接創建環境字符串,並把它們按字母順序排列(因為windows nt和windows 95使用一種簡略的環境變量),並把它們放進lpenvironment中指定的環境塊中.類似的,他們要找到環境塊的開頭,又要重復一次前面提到的環境塊的排序.
一種獲得驅動器x的當前目錄變量的方法是調用getfullpathname("x:",..).這避免了一個應用程序必須去掃描環境塊.如果返回的絕對路徑是x:/,就不需要把這個值當作一個環境數據去傳遞了,因為根目錄是驅動器x上的新進程的默認當前目錄.
由createprocess函數返回的句柄對於進程對象具有process_all_access的訪問權限.
由lpcurrentdirectory參數指定的當前目錄室子進程對象的當前目錄.lpcommandline參數指定的第二個項目是父進程的當前目錄.
對於windows nt,當一個進程在指定了create_new_process_group的情況下被創建時,一個對於setconsolectrlhandler(NULL,true)的調用被用在新的進程上,這意味着對新進程來說ctrl+c是無效的.這使得上層的外科程序可以自己處理ctrl+c信息並有選擇的把這些信號傳遞給子進程.ctrl+break依舊有效,並可被用來中斷進程/進程樹的執行.

附加說明

CreateProcess 時,系統將會創建一個進程內核對象,其初始化使用計數器為1,經常內核對象不是進程本身,而是操作系統用來管理進程的一個小型數據結構(可以把進程內核對象想象成有進程統計信息構成的一個小心的數據結構).然后系統為新進程創建一個虛擬地址空間.並將可執行文件(和所有必有的動態鏈接庫(DLL))的代碼以及數據加載到進程的地址空間中.

CreateProcess 在進程完全初始化好之前就返回 TRUE.這意味着操作系統加載程序尚未嘗試定位所有必要的動態鏈接庫(DLL).如果有一個動態鏈接庫(DLL)找不到或者不能正確初始化.進程就會終止.因為 CreateProcess 返回 TRUE.所以父進程不會注意到子進程的任何初始化問題


進程的創建

創建過程:

  • 定義兩個結構體變量,用於新進程的主界面出現的樣式和存放進程創建后的相關信息
  • 創建進程
  • 在進程創建后進行后續的清理工作

  具體流程圖

注意:

在創建一個進程時,系統會為該進程建立一個警察內核對象和一個線程內核對象,而該內核對象有有一個計數器,系統會為這兩個對象賦予初始的計數為 1,在 CreateProcess 函數返回之前,它將打開創建的進程對象和線程對象,並將每個對象與進程和線程相關的句柄放在其最后一個參數 PROCESS_INFORMATION 結構體變量的對應成員中.當 CreateProcess 函數在其內部打開這些對象時,每個對象的使用計數就變為2,如果在父進程中不需要使用子進程的這兩個句柄則可以調用 CloseHandle 函數關閉它們(關閉一個進程或線程的句柄.是不會強迫系統"殺死"此進程或線程的.關閉句柄只是告訴系統我們隊進程或線程的統計數據不再感興趣了.進程或線程會繼續運行直到自行終止),系統會將子進程的進程內核對象和線程對象的計數器減1,當子進程終止運行時,系統會將這些使用計數器減 1,這時子進程的進程內核對象和線程內核對象都為 0,這兩個內核對象就能夠被釋放了,所以在編程中,當不需要這些內核對象時,總應該調用 CloseHandle 函數關閉它們

代碼樣例:

定義必要結構體變量:

//指定新進程的主界面出現的樣式
STARTUPINFO sui;
//用於接收創建新進程后新進程的一些信息
PROCESS_INFORMATION pi;

初始化 startupinfo:

ZeroMemory(&sui,sizeof(STARTUPINFO));

創建進程:

//打開 vim 編輯器
CreateProcess("c:\\program files\\vim\\vim73\\gvim.exe",NULL,NULL,NULL,
        true,0,NULL,NULL,&sui,&pi);

進程創建后的清理操作:

//關閉進程句柄
CloseHandle(pi.hProcess);
//關閉主線程句柄
CloseHandle(pi.hThread);

 程序源碼:

View Code
#include<windows.h>
#include<cstdlib>
using namespace std;
void main()
{
    STARTUPINFO sui;
    PROCESS_INFORMATION  pi;
    
    ZeroMemory(&sui,sizeof(STARTUPINFO));

    if(!CreateProcess("c:\\program files\\vim\\vim73\\gvim.exe",NULL,NULL,NULL,
        true,0,NULL,NULL,&sui,&pi))
    {
        MessageBox(NULL,"創建子進程失敗!","警告",MB_OK);
        return;
    }
    else
    {
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
    system("pause");
}

運行結果:


進程的終止

終止進程的 4 中方式

  1. 主線程的入口點函數返回(推薦)
  2. 進程中的一個線程調用 ExitProcess 函數(避免)
  3. 另一個進程中的線程調用 TerminateProcess 函數(避免)
    (被終止的進程得不到自己要被終止的通知,而且應用程序不能正確清理,也不能阻止它自己被強行終止(除非通過正常的安全機制))
  4. 進程中所有的線程都自然死亡

ExitProcess / ExitProcess 說明:

調用 TerminateProcess 或 ExitProcess 會導致進程或線程直接終止運行,C/C++ 應用程序應該避免調用這些函數,因為C/C++ 運行庫也許不能正確執行清理工作

調獲取進程退出代碼

GetExitCodeProcess 來獲得已經終止的一個進程退出代碼

設置進程響應嚴重錯誤

每個進程都關聯了一組標志,這些標志的作用是讓系統知道進程如何響應嚴重錯誤(磁盤介質錯誤,未處理異常,文件查找錯誤,數據對其錯誤等),進程可以調用 SetErrorMode 函數來告訴系統如何處理這些錯誤.默認情況下子進程是繼承父進程的錯誤模式的標志

代碼樣例

用 ExitProcess / ExitProcess 退出進程樣例

View Code
#include <windows.h>
#include <iostream>
#include <cstdlib>
using namespace std;
class TEST{
public:
    ~TEST(){cout<<"this is TEST destructor!"<<endl;}
};
void main()
{
    TEST test;

    //通過 TerminateProcess 殺死進程
    //DWORD nID;
    //HANDLE hPro;
    //獲得進程 ID,后通過進程 ID 獲得進程句柄
    //nID = GetCurrentProcessId();
    //hPro = OpenProcess(PROCESS_ALL_ACCESS,FALSE,nID);
    //TerminateProcess(hPro,0);
    
    //通過 ExitProcess 殺死進程
    ExitProcess(0);

    //下面這句話永遠無法執行
    system("pasue");
}

參考資料


進程執行流程

執行流程(下圖是看Windows核心編程的個人理解,若發現者錯誤若發現懇請提出)

執行流程相關解釋

啟動函數用途簡單總結:(即上圖的C/C++運行庫啟動代碼)

  1. 獲取執行新進程的完整命令行的一個指針
  2. 獲取指向新進程的環境變量的一個指針
  3. 初始化C/C++運行庫的全局變量(如果包含了stdlib.h,代碼就可以訪問到這些變量<Windows 核心編程的 P69 頁有詳細介紹>)
  4. 初始化C運行庫內存分配函數
  5. 調用所有全局變量和靜態C++類的構造函數

主函數返回以后,啟動函數將調用 C 運行庫函數 exit,向其傳遞返回值(nMainRetVal).exit 函數執行以下任務

  1. 調用 _onexit 函數調用所注冊的任何一個函數
  2. 調用所有全局和靜態 C++ 類對象的析構函數
  3. 清理進程使用的全部 C 運行時資源
    (C/C++ 運行庫為應用程序采取了一個不同的策略:不管進程中是有其他線程在運行,只要應用程序主線程從它的入口函數返回,C/C++ 運行庫就會調用 ExitProcess 來終止進程.不管如果在入口函數中調用的是 ExitThread,而不是 ExitProcess 或者入口點函數直接返回,應用程序的主線程將停止執行,但是進程中還有其他線程正在運行,進程就不會終止.)
  4. 在 DEBUG 生成中.如果設置了_CRTDBG_LEAK_CHECK_DF 標志,就通過調用 _CtrDumpMemoryLeaks 函數來生成內存泄露報告
  5. 調用操作系統的 ExitProcess 函數,向其傳入 nMainRetVal.這會導致操作系統"殺死"我們的進程.並設置它的退出代碼

一個進程終止時,系統會依次執行以下操作:(即為上圖的進程清理工作)

  1. 終止進程中遺留的任何線程
  2. 釋放經常分配的所有用戶對象和 GDI 對象,關閉所有內核對象(如果沒有其他進程打開這些內核對象的句柄,那么他們也會被銷毀,不過,如果其他進程打開了它們的句柄,那么他們就不會被銷毀)
  3. 操作系統正確釋放線程所棧使用的內存
  4. 進程的退出代碼從 STILL_ACTIVE 變為傳給 ExitProcess 或 TerminateProcess 函數的代碼
  5. 經常內核對象的狀態變為以觸發狀態
  6. 進程計數器對象遞減 1


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM