Linux进程实践(1) --Linux进程编程概述
進程?VS.?程序
什么是程序??
? 程序是完成特定任務的一系列指令集合。
什么是進程?
? [1]從用戶的角度來看:進程是程序的一次執行過程
? [2]從操作系統的核心來看:進程是操作系統分配的內存、CPU時間片等資源的基本單位。?
? [3]進程是資源分配的最小單位
? [4]每一個進程都有自己獨立的地址空間與執行狀態。
? [5]像UNIX這樣的多任務操作系統能夠讓許多程序同時運行,每一個運行著的程序就構成了一個進程
?
進程數據結構
? 進程由三部分組成:PCB、程序段和數據段。
? ?進程控制塊PCB:用于描述進程情況及控制進程運行所需的全部信息。
? ?代碼段:是進程中能被進程調度程序在CPU上執行的程序代碼段。
? ?數據段:一個進程的數據段,可以是進程對應的程序加工處理的原始數據,也可以是程序執行后產生的中間或最終數據
?
進程和程序的區別
進程是動態的(進程存在的唯一標志:PCB,?CPU通過PCB來控制進程),程序是靜態的
進程的生命周期是相對短暫的,而程序是永久的。
一個進程只能對應一個程序,一個程序可以對應多個進程。
?
進程三態
? 進程因創建而就緒,因調度而執行;因時間片用完而重新就緒;
? 執行中因I/O請求而阻塞;
? I/O完成而就緒
注意:阻塞以后不能直接執行,必須進入就緒狀態。
?
Linux內核中的進程狀態
? 運行狀態(TASK_RUNNING)
??可中斷睡眠狀態(TASK_INTERRUPTIBLE)
??不可中斷睡眠狀態(TASK_UNINTERRUPTIBLE)
??暫停狀態(TASK_STOPPED)
??僵死狀態(TASK_ZOMBIE)
進程調度
進程調度的任務
? ?保存處理機的現場信息
? ?按某種算法選取進程
? ?把處理器分配給進程
進程調度算法
? 1.先來先服務算法
? 2.短進程優先算法
? 3.時間片輪轉算法
? 4.優先級調度算法
? 5.多級反饋隊列調度算法
?
進程編程相關術語
進程標志:
??每個進程都會分配到一個獨一無二的數字編號,我們稱之為“進程標識”(process?identifier),或者就直接叫它PID.
??是一個正整數,取值范圍從2到32768
??當一個進程被啟動時,它會順序挑選下一個未使用的編號數字做為自己的PID
??1號進程是特殊進程init
??0號進程空閑進程
關于0,1的解釋:
? 進程0:Linux引導中創建的第一個進程,完成加載系統后,演變為進程調度、交換及存儲管理進程;
? 進程1:init?進程,由0進程創建,完成系統的初始化.?是系統中所有其它用戶進程的祖先進程;
?
進程創建
不同的操作系統所提供的進程創建原語的名稱和格式不盡相同,但執行創建進程原語后,操作系統所做的工作卻大致相同,都包括以下幾點:
??(1)給新創建的進程分配一個內部標識,在內核中建立進程結構。
??(2)復制父進程的環境
??(3)為進程分配資源,?包括進程映像所需要的所有元素(程序、數據、用戶棧等),
??(4)復制父進程地址空間的內容到該進程地址空間中。
??(5)置該進程的狀態為就緒,插入就緒隊列。
?
進程撤銷
進程終止時操作系統做以下工作:
??(1)關閉軟中斷:因為進程即將終止而不再處理任何軟中斷信號;
??(2)回收資源:釋放進程分配的所有資源,如關閉所有已打開文件,釋放進程相應的數據結構等;
??(3)寫記帳信息:將進程在運行過程中所產生的記帳數據(其中包括進程運行時的各種統計信息)記錄到一個全局記帳文件中;
??(4)置該進程為僵死狀態:向父進程發送子進程死的軟中斷信號,將終止信息status送到指定的存儲單元中;
??(5)轉進程調度:因為此時CPU已經被釋放,需要由進程調度進行CPU再分配。
?
fork系統調用
復制一個進程映象
??使用fork函數得到的子進程從父進程的繼承了整個進程的地址空間,包括:進程上下文、進程堆棧、內存信息、打開的文件描述符、信號控制設置、進程優先級、進程組號、當前工作目錄、根目錄、資源限制、控制終端等。
子進程與父進程的區別:
? 1、父進程設置的鎖,子進程不繼承
??2、各自的進程ID:?父子進程ID不同
??3、子進程的未決警告被清除;
??4、子進程的未決信號集設置為空集;
?
fork系統調用
#include <unistd.h> pid_t fork(void);創建一個子進程
返回值:
? 如果成功創建一個子進程,對于父進程來說返回子進程ID
? 如果成功創建一個子進程,對于子進程來說返回值為0
? 如果為-1表示創建失敗
?
怎樣理解fork函數一次調用,二次返回?
? ?問題的本質是:兩次返回,是在各自的進程空間中返回的。
? ?子進程和父進程各有自己的內存空間?(fork:代碼段、數據段、堆棧段、PCB進程控制塊的copy)。
//示例: 父子進程中數據的關系(其實基本沒關系) int main(int argc, char *argv[]) {signal(SIGCHLD, SIG_IGN);int count = 10;pid_t pid = fork();if (pid == -1)err_exit("fork error");else if (pid == 0) //子進程{++ count;cout << "In child: pid = " << getpid() << ", ppid = " << getppid() << endl;cout << "count = " << count << endl;}else if (pid > 0) //父進程{++ count;cout << "In parent: pid = " << getpid() << ", child pid = " << pid << endl;cout << "count = " << count << endl;}exit(0); } //深入理解: Hello World 為什么會打印8次 int main(int argc, char *argv[]) {signal(SIGCHLD, SIG_IGN);fork();fork();fork();cout << "Hello World" << endl;exit(0); } //示例: 產生N個子進程 int main(int argc, char *argv[]) {signal(SIGCHLD, SIG_IGN);int processCount;cin >> processCount;for (int i = 0; i < processCount; ++i){pid_t pid = fork();if (pid < 0)err_exit("fork error");else if (pid == 0){cout << "Child ..." << endl;exit(0);}}exit(0); }寫時復制(copy?on?write)
COW初窺:
? ?在Linux程序中,fork()會產生一個和父進程完全相同的子進程,但子進程在此后多會exec系統調用,出于效率考慮,Linux中引入了“寫時復制“技術,也就是只有進程空間的各段的內容要發生變化時,才會將父進程的內容復制一份給子進程。
? ?那么子進程的物理空間沒有代碼,怎么去取指令執行exec系統調用呢?
? ? 在fork之后exec之前兩個進程用的是相同的物理空間(內存區),子進程的代碼段、數據段、堆棧都是指向父進程的物理空間,也就是說,兩者的虛擬空間不同,但其對應的物理空間是同一個。當父子進程中有更改相應段的行為發生時,再為子進程相應的段分配物理空間,如果不是因為exec,內核會給子進程的數據段、堆棧段分配相應的物理空間(至此兩者有各自的進程空間,互不影響),而代碼段繼續共享父進程的物理空間(兩者的代碼完全相同)。而如果是因為exec,由于兩者執行的代碼不同,子進程的代碼段也會分配單獨的物理空間。??????
? ?在網上看到還有個細節問題就是,fork之后內核會通過將子進程放在隊列的前面,以讓子進程先執行,以免父進程執行導致寫時復制,而后子進程執行exec系統調用,因無意義的復制而造成效率的下降。
?
COW詳述:
? ?現在有一個父進程P1,這是一個主體,那么它是有靈魂也就身體的。現在在其虛擬地址空間(有相應的數據結構表示)上有:正文段,數據段,堆,棧這四個部分,相應的,內核要為這四個部分分配各自的物理塊。即:正文段塊,數據段塊,堆塊,棧塊。
1.?現在P1用fork()函數為進程創建一個子進程P2,
內核:
?(1)復制P1的正文段,數據段,堆,棧這四個部分,注意是其內容相同。
?(2)為這四個部分分配物理塊,P2的:正文段->P1的正文段的物理塊,其實就是不為P2分配正文段塊,讓P2的正文段指向P1的正文段塊,數據段->P2自己的數據段塊(為其分配對應的塊),堆->P2自己的堆塊,棧->P2自己的棧塊。
如下圖所示:從左到右大的方向箭頭表示復制內容。
?
2.寫時復制技術:內核只為新生成的子進程創建虛擬空間結構,它們復制于來自父進程的虛擬空間結構,但是不為這些段分配物理內存,它們共享父進程的物理空間,當父子進程中有更改相應段的行為發生時,再為子進程相應的段分配物理空間。
3.?vfork():這個做法更加火爆,內核連子進程的虛擬地址空間結構也不創建了,直接共享了父進程的虛擬空間,當然了,這種做法就順水推舟的共享了父進程的物理空間
?
?
?
小結:?進程是一個主體,那么它就有靈魂與身體,系統必須為實現它創建相應的實體,?靈魂實體與物理實體。這兩者在系統中都有相應的數據結構表示,物理實體更是體現了它的物理意義。
? ? ?傳統的fork()系統調用直接把所有的資源復制給新創建的進程。這種實現過于簡單并且效率低下,因為它拷貝的數據也許并不共享,更糟的情況是,如果新進程打算立即執行一個新的映像,那么所有的拷貝都將前功盡棄。Linux的fork()使用寫時拷貝(copy-on-write)頁實現。寫時拷貝是一種可以推遲甚至免除拷貝數據的技術。內核此時并不復制整個進程地址空間,而是讓父進程和子進程共享同一個拷貝。只有在需要寫入的時候,數據才會被復制,從而使各個進程擁有各自的拷貝。也就是說,資源的復制只有在需要寫入的時候才進行,在此之前,只是以只讀方式共享。這種技術使地址空間上的頁的拷貝被推遲到實際發生寫入的時候。在頁根本不會被寫入的情況下{舉例來說:fork()后立即調用exec()}它們就無需復制了。fork()的實際開銷就是復制父進程的頁表以及給子進程創建惟一的進程描述符。在一般情況下,進程創建后都會馬上運行一個可執行的文件,這種優化可以避免拷貝大量根本就不會被使用的數據(地址空間里常常包含數十兆的數據)。由于Unix強調進程快速執行的能力,所以這個優化是很重要的。這里補充一點:Linux?COW與exec沒有必然聯系
PS:實際上COW技術不僅僅在Linux進程上有應用,其他例如C++的String在有的IDE環境下也支持COW技術,即例如:
string str1 = "hello world"; string str2 = str1;之后執行代碼:
str1[1]='q'; str2[1]='w';在開始的兩個語句后,str1和str2存放數據的地址是一樣的,而在修改內容后,str1的地址發生了變化,而str2的地址還是原來的,這就是C++中的COW技術的應用;
[附]-查看系統能夠支持的最大進程數
cat?/proc/sys/kernel/pid_max
總結
以上是生活随笔為你收集整理的Linux进程实践(1) --Linux进程编程概述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: qt qml scrollbar 移动A
- 下一篇: LINUX正则表达式-- grep