1.程序:
程序構成:
(1)源代碼
(2)可執行的二進制代碼
程序是指令和數據的有序集合,其本身沒有任何運行的含義,是一個靜態的概念。由操作系統加載其可執行的二進制代碼,分配相應的數據結構:進程控制塊PCB(Process Control Block),進行一些列初始化操作(創建進行ID、分配時間片等)后得到進程。
2.進程:分配資源的最小單位
進程構成:
(1)內核對象:存放進程相關信息
(2)地址空間:可執行模塊、DLL的代碼和數據以及動態分配的內存空間
是一個正在執行的程序;計算機中正在運行的程序實例;可以分配給處理器並由處理器執行的一個實體。
進程是一個具有獨立功能的程序關於某個數據集合的一次運行活動。它可以申請和擁有
系統資源,是一個動態的概念,是一個活動的實體。它不只是程序的
代碼,還包括當前的活動,通過
程序計數器的值和處理
寄存器的內容來表示。
特征
進程的特征:
動態性:進程的實質是程序在
多道程序系統中的一次執行過程,進程是動態產生,動態消亡的。
並發性:任何進程都可以同其他進程一起並發執行
獨立性:進程是一個能獨立運行的基本單位,同時也是系統分配資源和調度的獨立單位;
異步性:由於進程間的相互制約,使進程具有執行的間斷性,即進程按各自獨立的、不可預知的速度向前推進
結構特征:進程由程序、數據和
進程控制塊三部分組成。
多個不同的進程可以包含相同的程序:一個程序在不同的數據集里就構成不同的進程,能得到不同的結果;但是執行過程中,程序不能發生改變。
進程的切換:
進行
進程切換就是從正在運行的進程中收回處理器,然后再使待運行進程來占用處理器。
這里所說的從某個進程收回處理器,實質上就是把進程存放在處理器的
寄存器中的中間數據找個地方存起來,從而把處理器的寄存器騰出來讓其他進程使用。那么被中止運行進程的中間數據存在何處好呢?當然這個地方應該是進程的私有
堆棧。
讓進程來占用處理器,實質上是把某個進程存放在私有堆棧中寄存器的數據(前一次本進程被中止時的中間數據)再恢復到處理器的寄存器中去,並把待運行進程的
斷點送入處理器的程序
指針PC,於是待運行進程就開始被處理器運行了,也就是這個進程已經占有處理器的使用權了。
這就像多個同學要分時使用同一張課桌一樣,所謂要收回正在使用課桌同學的課桌使用權,實質上就是讓他把屬於他的東西拿走;而賦予某個同學課桌使用權,只不過就是讓他把他的東西放到課桌上罷了。
在切換時,一個進程存儲在處理器各寄存器中的中間數據叫做進程的上下文,所以進程的 切換實質上就是被中止運行進程與待運行
進程上下文的切換。在進程未占用處理器時,進程 的上下文是存儲在進程的私有堆棧中的。
進程的狀態:
進程執行時的間斷性,決定了進程可能具有多種狀態。事實上,運行中的進程可能具有以下三種基本狀態。
1)就緒狀態
(Ready)
:
進程已獲得除處理器外的所需資源,等待分配處理器資源;只要分配了處理器進程就可執行。就緒進程可以按多個優先級來划分
隊列。例如,當一個進程由於時間片用完而進入就緒狀態時,排入低
優先級隊列;當進程由I/O操作完成而進入就緒狀態時,排入高優先級隊列。
2)
運行狀態(Running):
進程占用處理器資源;處於此狀態的進程的數目小於等於處理器的數目。在沒有其他進程可以執行時(如所有進程都在
阻塞狀態),通常會自動執行系統的空閑進程。
3)
阻塞狀態(Blocked):
由於進程等待某種條件(如I/O操作或
進程同步),在條件滿足之前無法繼續執行。該事件發生前即使把
處理機分配給該進程,也無法運行。
進程的創建過程:
一旦操作系統發現了要求創建新進程的事件后,便調用進程創建
原語Creat()按下述步驟創建一個新進程。
1) 申請空白PCB。為新進程申請獲得唯一的數字
標識符,並從PCB集合中索取一個空白PCB。
2) 為新進程分配資源。為新進程的程序和數據以及
用戶棧分配必要的內存空間。顯然,此時操作系統必須知道新進程所需要的內存大小。
3) 初始化
進程控制塊。PCB的初始化包括:①初始化標識信息。將系統分配的標識符和
父進程標識符,填入新的PCB中;②初始化
處理機狀態信息。使
程序計數器指向程序的入口地址,使棧
指針指向棧頂;③初始化處理機控制信息。將進程的狀態設置為
就緒狀態或靜止就緒狀態,對於優先級,通常是將它設置為最低優先級,除非用戶以顯式的方式提出高優先級要求。
4) 將新進程插入就緒隊列。如果進程就緒隊列能夠接納新進程,便將新進程插入到就緒隊列中。
3.虛擬地址空間:
虛擬地址空間構成:
(1)內核方式分區:內核代碼、設備驅動、I\O高速緩沖等使用
(2)用戶方式分區:進程的私有地址空間,維護進程數據
操作系統分配給進程的虛擬地址的范圍。32位下為2
32B = 4GB。所以不同進程的同一個內存地址互不相關。
Windows 使用基於
分頁機制的
虛擬內存。每個進程有4GB的虛擬
地址空間。基於分頁機制,這4GB地址空間的一些部分被映射了
物理內存,一些部分映射硬盤上的交換文件,一些部分什么也沒有映射。程序中使用的都是4GB地址空間中的虛擬地址。而訪問物理內存,需要使用
物理地址。
物理地址 (physical address): 放在尋址總線上的地址。放在尋址總線上,如果是讀,電路根據這個地址每位的值就將相應地址的物理內存中的數據放到
數據總線中傳輸。如果是寫,電路根據這個地址每位的值就將相應地址的物理內存中放入數據總線上的內容。物理內存是以
字節(8位)為單位
編址的。
虛擬地址 (virtual address): 4G虛擬地址空間中的地址,
程序中使用的都是虛擬地址。
如果CPU寄存器中的分頁標志位被設置,那么執行內存操作的
機器指令時,CPU會自動根據頁目錄和
頁表中的信息,把虛擬
地址轉換成物理地址,完成該指令。
使用了分頁機制之后,
4G的地址空間被分成了固定大小的頁,
每一頁或者被映射到物理內存,或者被映射到硬盤上的交換文件中,或者沒有映射任何東西。對於一般程序來說,4G的地址空間,只有一小部分映射了物理內存,大片大片的部分是沒有映射任何東西。物理內存也被分頁,來映射地址空間。對於32bit的Win2k,
頁的大小是4K字節。
CPU用來把虛擬地址轉換成物理地址的信息存放在叫做頁目錄和頁表的結構里。
物理內存分頁,一個
物理頁的大小為4K字節,第0個物理頁從物理地址 0x00000000 處開始。
由於頁的大小為4KB,就是0x1000字節,所以第1頁從物理地址 0x00001000 處開始。第2頁從物理地址 0x00002000 處開始。可以看到由於頁的大小是4KB,所以只需要32bit的地址中高20bit來尋址物理頁。
???
頁表,一個
頁表的大小為4K字節,放在一個物理頁中。由1024個4字節的頁表項組成。頁表項的大小為4個字節(32bit),所以一個頁表中有1024個頁表項。頁表中的每一項的內容(每項4個字節,32bit)高20bit用來放一個物理頁的物理地址,低12bit放着一些標志。
頁目錄,一個頁目錄大小為4K字節,放在一個物理頁中。由1024個4字節的頁目錄項組成。頁目錄項的大小為4個字節(32bit),所以一個頁目錄中有1024個頁目錄項。頁目錄中的每一項的內容(每項4個字節)高20bit用來放一個頁表(頁表放在一個物理頁中)的物理地址,低12bit放着一些標志。
4.線程:資源調度的最小單位
線程的構成:
(1)內核對象:存放線程相關信息
(2)線程堆棧:維護執行代碼時所需的參數和變量
通常在一個進程中可以包含若干個線程,它們可以利用進程所擁有的資源。在引入線程的操作系統中,通常把進程作為分配資源的基本單位,而把線程作為獨立運行和獨立調度的基本單位。由於線程比進程更小,基本上不擁有系統資源,故對它的調度所付出的開銷就會小得多,能更高效的提高系統內多個程序間並發執行的程度。
下面看看C++創建進程的相關函數:
1 HANDLE WINAPI CreateThread(
2 __in LPSECURITY_ATTRIBUTES lpThreadAttributes,
3 __in SIZE_T dwStackSize,
4 __in LPTHREAD_START_ROUTINE lpStartAddress,
5 __in LPVOID lpParameter,
6 __in DWORD dwCreationFlags,
7 __out LPDWORD lpThreadId
8 );
1 uintptr_t _beginthreadex(
2 void *security,
3 unsigned stack_size,
4 unsigned ( *start_address )( void * ),
5 void *arglist,
6 unsigned initflag,
7 unsigned *thrdaddr
8 );
兩個函數都是用於創建線程,第一個是Windows API函數,在WinBase.h頭文件中,第二個不是API函數,在process.h頭文件中
參數說明:
1.線程安全性:表示是否可以被子進程所繼承
2.初始堆棧大小:如果為0或者小於默認值,則使用和調用線程同樣大小的空間
3.線程其實地址:一個函數指針,指向線程函數
4.參數:傳遞給線程函數的參數
5.創建選項:如果為CREATE_SUSPENDED表示創建后掛起,如果為0表示創建后立即執行
6.線程ID
兩個函數的區別:
malloc、fopen、ctime等函數需要專門的線程局部存儲數據塊,這個數據塊在創建線程時創建。如果用CreateThread,則不會創建,這樣,函數能夠正常使用,但是會自動創建數據塊,但是函數並不會釋放創建的數據庫,所以並不會將其刪除,就導致內存泄露!!!
而_beginthreadex(內部也調用CreateThread)和_beginthreadex(會自動調用CloseHandle關閉句柄)對這個內存塊做了處理。
代碼演示:
1 #include <iostream>
2 #include <Windows.h>
3 #include <process.h>
4 using namespace std;
5
6
7 DWORD WINAPI CreateFun(LPVOID lParam)
8 {
9 cout << "CreateThread" << endl;
10 return 0;//0表示成功
11
12 }
13
14 UINT _stdcall beginFun(LPVOID lParam)
15 {
16 cout << "beginthreadex" << endl;
17 return 0;
18 }
19 int main(void)
20 {
21
22 DWORD dwID;
23 UINT nID;
24 HANDLE hC;
25 HANDLE hB;
26
27 hC = CreateThread(NULL, 0, CreateFun, NULL, 0, &dwID);
28
29 if (NULL != hC)
30 {
31 CloseHandle(hC);
32 }
33
34
35
36 hB = (HANDLE)_beginthreadex(NULL, 0, beginFun, NULL, 0, &nID);
37 if (NULL != hB)
38 {
39 CloseHandle(hB);
40 }
41
42 Sleep(1000);
43 }
CloseHandle:關閉句柄
調用CloseHandle並不會終止線程的執行,而是遞減線程內核對象句柄計數,線程執行完畢后也會自動遞減,當計數為0時釋放線程內核對象。當進程終止時也會清理內核對象。
但是,如果不關閉,可能導致有些進程擁有的資源無法釋放,導致內存泄露。
線程的相關函數:
(1)CreateThread:創建線程,失敗返回NULL,成功返回線程句柄
(2)SuspendThread:掛起線程
(3)ResumeThread:恢復線程
(4)OpenThread:打開線程,根據線程ID得到線程句柄
(5)ExitThread:退出線程
(6)TerminateThread:終止線程
(7)GetExitCodeThread:獲取線程運行狀態,如果為STILL_ALIVE表示正在運行。
(8)GetCurrentThread:獲取當前線程句柄
(9)GetCurentThreadID:獲取當前線程ID
注意:最好不要顯式的調用ExitThread和TerminateThread,因為可能導致線程無法清理某些東西,導致內存泄露~