程序和進(jìn)程
程序是計算機(jī)指令的集合,它以文件的形式存儲在磁盤上达皿。
進(jìn)程通常被定義為一個正在運行的程序的實例天吓。是一個程序在其自身的地址空間中的一次執(zhí)行活動。
一個程序可以對應(yīng)多個進(jìn)程峦椰。進(jìn)程是資源申請失仁、調(diào)度和獨立運行的單位,因此们何,它使用系統(tǒng)中的運行資源。
程序不能申請系統(tǒng)資源控轿,不能被系統(tǒng)調(diào)度冤竹,也不能作為獨立運行的單位拂封。因此它不占用系統(tǒng)的運行資源。
真正完成代碼執(zhí)行的是線程鹦蠕,而進(jìn)程只是線程的容器冒签,或者說是線程的執(zhí)行環(huán)境。主線程钟病,也就是執(zhí)行main函數(shù)或者WinMain函數(shù)的線程萧恕,可以把main函數(shù)或者WinMain函數(shù)看做是主線程的進(jìn)入點函數(shù)。此后肠阱,主線程可以創(chuàng)建其他線程票唆。
系統(tǒng)賦予每個進(jìn)程獨立的虛擬地址空間。32位進(jìn)程屹徘,4GB走趋。
磁盤緩存、虛擬內(nèi)存噪伊、頁面文件和物理內(nèi)存的關(guān)系
磁盤緩存分為讀緩存和寫緩存簿煌。讀緩存是指,操作系統(tǒng)為已讀取的文件數(shù)據(jù)鉴吹,在內(nèi)存較空閑的情況下留在內(nèi)存空間中(這個內(nèi)存空間被稱之為“內(nèi)存池”)姨伟,當(dāng)下次軟件或用戶再次讀取同一文件時就不必重新從磁盤上讀取,從而提高速度豆励。寫緩存實際上就是將要寫入磁盤的數(shù)據(jù)先保存于系統(tǒng)為寫緩存分配的內(nèi)存空間中夺荒,當(dāng)保存到內(nèi)存池中的數(shù)據(jù)達(dá)到一個程度時,便將數(shù)據(jù)保存到硬盤中肆糕。這樣可以減少實際的磁盤操作般堆,有效的保護(hù)磁盤免于重復(fù)的讀寫操作而導(dǎo)致的損壞,也能減少寫入所需的時間诚啃。
虛擬內(nèi)存是用硬盤空間做內(nèi)存來彌補(bǔ)計算機(jī)RAM空間的缺乏淮摔。當(dāng)實際RAM滿時(實際上,在RAM滿之前)始赎,虛擬內(nèi)存就在硬盤上創(chuàng)建了和橙。當(dāng)物理內(nèi)存用完后,虛擬內(nèi)存管理器選擇最近沒有用過的造垛,低優(yōu)先級的內(nèi)存部分寫到交換文件上魔招。這個過程對應(yīng)用是隱藏的,應(yīng)用把虛擬內(nèi)存和實際內(nèi)存看作是一樣的五辽。虛擬內(nèi)存文件也就是頁面文件办斑。
線程有兩個部分組成
- 線程的內(nèi)核對象。操作系統(tǒng)用它來對線程實施管理。關(guān)于線程的統(tǒng)計信息組成的一個小型數(shù)據(jù)結(jié)構(gòu)乡翅。
- 線程棧鳞疲。它用于維護(hù)線程在執(zhí)行代碼時需要的所有函數(shù)參數(shù)和局部變量。
新線程可以訪問進(jìn)程的內(nèi)核對象的所有句柄蠕蚜、進(jìn)程中的所有內(nèi)存和在這個相同的進(jìn)程中的所有其他線程的堆棧尚洽。
進(jìn)程、線程及內(nèi)核對象
內(nèi)核對象
每個內(nèi)核對象只是內(nèi)核分配的一個內(nèi)存塊靶累,并且只能由該內(nèi)核訪問腺毫,這個內(nèi)存塊是一種數(shù)據(jù)結(jié)構(gòu),他的成員負(fù)責(zé)維護(hù)該對象的各種信息挣柬,如進(jìn)程對象有一個進(jìn)程ID潮酒、一個基本優(yōu)先級和一個退出代碼。
由于內(nèi)核對象的數(shù)據(jù)結(jié)構(gòu)只能被內(nèi)核訪問凛忿,so應(yīng)用程序是無法在內(nèi)存中找到這些數(shù)據(jù)結(jié)構(gòu)的并直接改變其內(nèi)容的澈灼。Windows提出這個限制為了確保內(nèi)核對象結(jié)構(gòu)保持狀態(tài)的一致,也是為了保證Microsoft能夠在不破壞應(yīng)用程序的情況下在這些內(nèi)核對象的結(jié)構(gòu)中添加店溢、刪除叁熔、修改這些數(shù)據(jù)成員;
內(nèi)核對象使用引用計數(shù)
內(nèi)核對象由內(nèi)核所有床牧,而不是進(jìn)程所有荣回,舉例說明,在做單進(jìn)程限制時戈咳,我們一般會CreateMutex來創(chuàng)建一個命名的Mutex心软,再另外一個進(jìn)程中再來創(chuàng)建或者打開相同命名的Mutex來檢驗有相同進(jìn)程被創(chuàng)建。也可以這么說進(jìn)程調(diào)用一個創(chuàng)建的內(nèi)核對象函數(shù)著蛙,進(jìn)程終止了但是內(nèi)核對象不一定被撤銷删铃;
每個線程都有自己的一組cpu寄存器和堆棧,每個進(jìn)程至少有一個線程踏堡,來執(zhí)行進(jìn)程的地址空間中的代碼猎唁。如果沒有線程來執(zhí)行進(jìn)程地址空間中的代碼,那么這個進(jìn)程就沒有存在的理由顷蟆,系統(tǒng)會自動撤銷該進(jìn)程诫隅;
進(jìn)程的實例句柄
加載到進(jìn)程地址空間的每個可執(zhí)行文件或者dll均被賦予了一個獨一無二的實例句柄≌寿耍可執(zhí)行的文件的實例作為(w)WinMain的第一個參數(shù)hinstExe來傳遞逐纬;
WinMain的hinstExe參數(shù)的實際值是系統(tǒng)將可執(zhí)行文件的映像加載到進(jìn)程的地址空間時使用的基本地址空間。例如削樊,如果系統(tǒng)打開了可執(zhí)行文件并將它的內(nèi)容加載到地址的0x00400000豁生,那么WinMain的hinstExe的參數(shù)值就是0x00400000;
可執(zhí)行文件的映像加載到的基地址是由連接程序決定的,不同的鏈接程序可以使用不同的默認(rèn)基地址沛硅。VC++鏈接程序使用的默認(rèn)基地址是0x00400000眼刃;只是運行在windows98時可執(zhí)行文件的映像可以加載到的最低地址。
進(jìn)程的前一個實例句柄
C/C++運行期啟動代碼總是將NULL傳遞給WinMain的hinstExePrev參數(shù)摇肌,該參數(shù)使用在16位windows中的,并且保留了WinMain的一個參數(shù)仪际,目的僅僅是為了能夠容易的轉(zhuǎn)用16位windows應(yīng)用程序围小,so絕不應(yīng)該在代碼中引用這個參數(shù)。線程有兩部分組成:
一個是線程的內(nèi)核對象树碱,操作系統(tǒng)用他來對線程實施管理肯适。內(nèi)核對象也是系統(tǒng)用來存放線程統(tǒng)計信息的地方;
一個是線程堆棧成榜,用于維護(hù)線程在執(zhí)行代碼時需要的所有函數(shù)參數(shù)和局部變量框舔;
進(jìn)程是不活潑的,進(jìn)程從不執(zhí)行任何東西赎婚,它只是線程的容器刘绣。線程總是在某個進(jìn)程環(huán)境中創(chuàng)建的,而且它的整個壽命期都在該進(jìn)程中挣输。那么這意味著線程在他的進(jìn)程地址空間中執(zhí)行代碼纬凤,并在進(jìn)程的地址空間中對數(shù)據(jù)進(jìn)行操作。so在單進(jìn)程中撩嚼,如果存在多個線程的運行停士,那么這些線程將共享單個地址空間。這些線程能夠執(zhí)行相同的代碼對相同數(shù)據(jù)進(jìn)行操作完丽。而且他們還能共享內(nèi)核對象句柄恋技,因為句柄表依賴于每個進(jìn)程而非每個線程存在的。
進(jìn)程使用的系統(tǒng)資源要比線程對很多逻族,原因是他需要更多的地址空間蜻底。為進(jìn)程創(chuàng)建一個虛擬的地址空間需要系統(tǒng)很多的資源。系統(tǒng)中要保留大量的記錄瓷耙,這需要占用大量的內(nèi)存朱躺。另外,由于exe和dll文件需要加載到一個地址空間搁痛,因此也需要文件資源长搀,而線程使用的系統(tǒng)資源就小很多了,他只需要一個內(nèi)核對象和一個堆棧就ok鸡典,保留的記錄也很少源请,so只需要很少的內(nèi)存。
So能用線程解決的問題,要避免創(chuàng)建新進(jìn)程來解決谁尸。
注:CreateThread函數(shù)是用來創(chuàng)建線程的windows函數(shù)舅踪,如果你正在編寫C/C++代碼,絕不應(yīng)該調(diào)用CreateThread良蛮,相反抽碌,應(yīng)該使用VC++運行庫函數(shù)_beginthreadex。原因是决瞳,標(biāo)準(zhǔn)C/C++運行庫在最初設(shè)計的時候并沒有考慮到多線程的問題货徙。若要使多線程C/C++程序能夠正常的運行起來,必須創(chuàng)建一個數(shù)據(jù)結(jié)構(gòu)皮胡,并將它與使用的C/C++運行庫函數(shù)的每一個線程關(guān)聯(lián)起來痴颊,當(dāng)你調(diào)用C/C++運行庫時,這些函數(shù)必須知道查看調(diào)用線程的數(shù)據(jù)塊屡贺,這樣就不會對別的線程產(chǎn)生不良影響蠢棱。那么系統(tǒng)是否知道在創(chuàng)建新線程時分配該數(shù)據(jù)塊呢?no甩栈,系統(tǒng)是無法知道你的應(yīng)用程序使用C/C++編寫的泻仙,自然不知道你調(diào)用的線程本身不安全,所以不要調(diào)用操作系統(tǒng)的CreateThread函數(shù)谤职,而需要調(diào)用_beginthreadex.
線程的運行時間
有時要計算線程執(zhí)行某個任務(wù)需要多長時間饰豺,很多時候我們會采用如下代碼:
//start time
DWORD starttime = GetTickCount();
//complex algorithm here;
//subtract start time from current time to get duration;
DWORD dwElapsedTime = GetTickCount() – dwStartTime;
這個代碼有個簡單的假設(shè):運行不會被中斷;但是在搶占式的windows操作系統(tǒng)是不可能存在的允蜈,so我們需要用另外一個方式來計算冤吨,該函數(shù)為GetThreadTime;
內(nèi)存映射文件
Windows提供了3種進(jìn)行內(nèi)存管理的方法:
虛擬內(nèi)存饶套,最適合用來管理大型對象或結(jié)構(gòu)數(shù)組漩蟆。
內(nèi)存映射文件,最適合用來管理大型數(shù)據(jù)流(通常來自文件)以及在單個計算機(jī)上運行的多個進(jìn)程之間共享數(shù)據(jù)妓蛮。
內(nèi)存堆棧怠李,最適合用來管理大量的小對象。
這里將先講述一下關(guān)于內(nèi)存映射文件的使用情況蛤克。
與虛擬內(nèi)存一樣捺癞,內(nèi)存映射文件可以用來保留一個地址空間的區(qū)域,并將物理存儲器提交給該區(qū)域构挤,他們之間的差別是物理存儲器來自一個已經(jīng)位于磁盤上的文件髓介,而不是系統(tǒng)的頁文件。一旦該文件被映射筋现,就可以訪問它唐础,就像是整個文件已經(jīng)加載到內(nèi)存中一樣箱歧。
內(nèi)存映射文件的3個目的:
系統(tǒng)使用內(nèi)存映射文件,以便快速加載和執(zhí)行exe和dll文件一膨。
可以使用內(nèi)存映射文件快速訪問磁盤上的數(shù)據(jù)文件呀邢,這樣使得你不必對文件執(zhí)行I/O操作,并且不必對文件內(nèi)容進(jìn)行緩存豹绪。
可以使用內(nèi)存映射文件來進(jìn)行進(jìn)程間的數(shù)據(jù)共享价淌。
內(nèi)存映射文件的步驟:
若要使用內(nèi)存映射文件,必須執(zhí)行下列操作步驟:
創(chuàng)建或打開一個文件內(nèi)核對象森篷,該對象用于標(biāo)識磁盤上你想用作內(nèi)存映射文件的文件输钩。(CreateFile)
創(chuàng)建一個文件映射內(nèi)核對象,告訴系統(tǒng)該文件的大小和你打算如何訪問該文件仲智。(CreateFileMapping)
讓系統(tǒng)將文件映射對象的全部或一部分映射到你的進(jìn)程地址空間中。(MapViewOfFile)
當(dāng)完成對內(nèi)存映射文件的使用時姻氨,必須執(zhí)行下面這些步驟將它清除:
告訴系統(tǒng)從你的進(jìn)程的地址空間中撤消文件映射內(nèi)核對象的映像钓辆。(UnmapViewOfFile)
關(guān)閉文件映射內(nèi)核對象和文件內(nèi)核對象。(CloseHandle)
CreateThread,該函數(shù)將創(chuàng)建一個線程肴焊。
很多程序在創(chuàng)建線程都這樣寫的:
ThreadHandle = CreateThread(NULL,0,.....);
CloseHandle(ThreadHandle );
1前联,線程和線程句柄(Handle)不是一個東西,線程句柄是一個內(nèi)核對象娶眷。我們可以通過句柄來操作線程似嗤,但是線程的生命周期和線程句柄的生命周期不一樣的。線程的生命周期就是線程函數(shù)從開始執(zhí)行到return届宠,線程句柄的生命周期是從CreateThread返回到你CloseHandle()烁落。
2,線程句柄是一種內(nèi)核對象,系統(tǒng)維護(hù)著每一個內(nèi)核對象,當(dāng)每個內(nèi)核對象引用記數(shù)為0時,系統(tǒng)就從內(nèi)存中釋放該對象,CloseHandle就是將該線程對象的引用記數(shù)減1豌注。所有的內(nèi)核對象(包括線程Handle)都是系統(tǒng)資源伤塌,用了要還的,也就是說用完后一定要closehandle關(guān)閉之轧铁,如果不這么做每聪,你系統(tǒng)的句柄資源很快就用光了。
只是關(guān)閉了一個線程句柄對象齿风,表示我不再使用該句柄药薯,即不對這個句柄對應(yīng)的線程做任何干預(yù)了。并沒有結(jié)束線程.
如果線程需要訪問共享資源救斑,就需要進(jìn)行線程之間的同步處理童本。
利用互斥對象實現(xiàn)線程同步
互斥對象mutex屬于內(nèi)核對象。它能確保線程擁有對單個資源的互斥訪問權(quán)系谐。
互斥對象包含一個使用數(shù)量巾陕,一個線程ID和一個計數(shù)器讨跟。
ID用于標(biāo)識系統(tǒng)中的哪個對象擁有互斥對象。
計數(shù)器用于指明該線程擁有互斥對象的次數(shù)鄙煤。
HANDLE hMutex; 互斥對象的句柄
hMutex=CreateMutex(NULL,FALSE,NULL);
WaitForSingleObject(hMutex,INFINITE);
ReleaseMutex(hMutex);
對互斥對象來說晾匠,誰擁有誰釋放。
如果多次在同一線程中請求同一個互斥對象梯刚,那么就需要相應(yīng)的多次調(diào)用
ReleaseMutex函數(shù)釋放該互斥對象凉馆。
注:主線程擁有該互斥對象時,該對象就處于未通知狀態(tài)了亡资。主線程通過WaitForSingleObject函數(shù)再次請求該互斥對象的所有權(quán)時澜共,因為ID相同,所以仍然能夠請求到這個互斥對象锥腻。操作系統(tǒng)通過互斥對象內(nèi)部的計數(shù)器來維護(hù)同一個線程請求到該互斥對象的次數(shù)嗦董。
保證程序只有一個實例運行
對這種同時只能有應(yīng)用程序的一個實例運行的功能,可以通過命名的互斥對象來實現(xiàn)瘦黑。
CreateMutex
GetLastError 返回值 ERROR_ALREADY_EXISTS 或其他LPVOID,是一個沒有類型的指針京革,也就是說你可以將任意類型的指針賦值給LPVOID類型的變量(一般作為參數(shù)傳遞),然后在使用的時候再轉(zhuǎn)換回來幸斥。
不能使用全局函數(shù)和全局變量匹摇,我們就可以采用靜態(tài)成員函數(shù)和靜態(tài)成員變量的方法來解決上述問題。
PVOID是void*的別名甲葬。
在windef.h中廊勃,LPVOID是這么定義的:typedef void far *LPVOID。
和void*的區(qū)別是遠(yuǎn)指針经窖,因為win32編程中坡垫,經(jīng)常要調(diào)用外部DLL堆變量。但現(xiàn)在的大部分平臺已經(jīng)無所謂了钠至,因為尋址方式成flat了葛虐。