一,進程概念
在我們打開電腦之前,我們的文件都是儲存在磁盤上的,而當我們打開電腦,第一個要加載的軟件就是操作系統本身,然后再次在此基礎上,我們使用的各種軟件都要先加載到內存中經過cpu的調度才能正常運行,而正在運行的軟件可以簡單的理解為進程;
值得注意的是,OS上打開的不只有一個進程,而是多個進程,那么OS是如何管理這些進程的呢?
—-管理一個對象我們還是遵循以往的套路:先組織,再描述;
二,簡單理解進程管理2.1描述進程

我們寫好的C/c++程序保存在磁盤上,當我們要使用的時候,OS會將此程序的代碼和數據加載到內存中,而這個時候其實就可以叫做是一個進程塊了;
但是需要注意的是一個進程可并不是只有代碼和數據,還要還要包括對應的屬性,還需要管理;所以!本質上進程=內核數據結構(task_struct)+程序的代碼和數據;
當一個程序的數據和代碼加載到內存中時雖然是一個進程塊,但是并不完整!OS還要在內核區單獨開辟空間還要創建一個描述此進程的對象–task_struct
task_struct是封裝了一個進程的屬性的結構體,OS通過task_struct來管理進程什么時候給CPU調度,進程的優先級,什么時候進程等待,什么時候阻塞等等各種狀態以及對其的各種操作….
OS通過把描述進程的對象task_struct以鏈表的形式串起來,本質上就是對數據結構的增刪查改!
我們來看一下task_struct里面有什么?

2.3對進程組織管理
我們再強調一遍:一個完整的進程=內核數據結構+代碼和數據!
對于OS來講一個進程的代碼和數據并不重要,OS關心的是這個進程的PCB數據結構;因為每一個進程的代碼和數據都不一樣(學校是不管你平時怎么學習,只會根據你的成績給予你獎勵!);而OS有了PCB數據結構就可以找到進程的代碼塊和數據;
但是往往加載的進程并不是一個兩個,而是很多的進程,所以使用一種合適的數據結構在復雜的場景中更好的調度各個數據就顯得尤為重要!
在我們的Linux中task_struct主要是以雙鏈表的形式組織起來,你可能會疑惑,使用一個順序表來存儲不是更好嗎?
比如HR在篩選簡歷的時候,會把優秀建立單獨按照優秀程度放在一邊,這個過程可能會多次對數據刪除和插入,使用線性表就顯得十分不友好了!而使用鏈表只需要通過改變指針,就可以靈活的操作!
當然對進程管理工作取決于你把他放入哪個正在被組織的數據結構中,因為不同的數據結構有不同的特點,所以背后對應的就是不同的算法,而不同的算法對應的就是不同的應用場景。
三,查看進程
我們電腦開機,其實就是把OS從外設加載到內存中,因為只有在內存中才能對進程管理!
3.1Windows查看所有進程
在Windows上我們可以直接打開任務管理器進行查看正在運行的進程;


我們也能清楚的看到,各個進程的屬性(CPU,內存,磁盤…)這不就是我們剛才說的OS對進程的PCB管理嗎?
3.2 ps -ajx
在Linux上使用指令
代碼語言:JavaScript代碼運行次數:0運行復制
ps -ajx--查看所有進程

我們可以寫一個程序來查看進程;

這里我寫了個死循環程序來查看正在運行的code進程 ;

我們會發現有兩個code進程,為什么呢?
對于死循環的程序是一直會進行下去的,我們可以使用指令來”殺掉他”!

四,進程PID目錄-proc

/proc目錄里面存儲都是內存級的文件??!在關機時會消失,開機時又會出現,他是對動態運行的所有進程的一個可視化信息??!
其中以數字命名的文件夾就是對應進程的PID,里面包含進程的各種信息;

五,獲取進程標識符5.1.理解PPID和PID

5.2.調用系統接口–getpid
我們可以使用系統接口來獲取當前進程的PID;
先看一下接口說明:

寫一個程序來調用接口查看pid;

這里我每隔一秒打印一行PID和PPID;

結論:每次執行程序,分配的PID都不一樣,但是父進程PPID是一樣的,其實都是Bash進程;
之后,我重新啟動了機器!

發現在重啟后,PPID竟然改變了!
結論:Bash(命令行)是機器啟動時就創建好的進程,直至關機PID都不會變 !
六,重點:使用系統接口fork創建子進程


fork的功能是創建子進程,如果創建成功給子進程的返回值是0,給父進程的返回值是子進程的PID,如果子進程創建失敗,就會返回一個負數
你沒有聽錯,fork有兩個返回值!
我們可以寫個程序查看下!


我們竟然發現,if和else if竟然在同時運行!這也驗證了fork的確有兩個返回值,雖然if 和else if 同時執行了,但是卻是在不同的進程中;
6.1為什么需要創建子進程?
目的:讓父子進程執行不同的事情
6.2fork的返回值分析
fork為什么給子進程返回0,其實對于子進程來說只是一個標識作用,他可以使用ps 查看自己的PID和父進程的PID;
fork為什么給父進程返回子進程的PID;因為父進程需要對創建的子進程進行管理,因此就需要拿到子進程的PID(標識子進程的唯一性);
6.3fork函數究竟干了什么?
fork進程創建了一個子進程->進程=內核數據結構+數據和代碼塊;
什么是寫時拷貝?
6.4為什么fork會有兩個返回值?
我們現在分析一下fork函數->

我們知道fork函數是拷貝父進程的代碼和數據,創建一個新的task_struct,所以這里就有了先后順序問題;
是先執行完函數返回值之后才創建好了子進程還是在返回值之前就創還能好了子進程呢?
實際上在fork函數內部return id之前,就已經為子進程準備好了一些工作,也就是說在fork結束,return 之前子進程就已經開始執行了,而這時父子進程的fork就會各自執行fork函數的return id語句;
所以!有了兩次返回值;
6.5一個變量為什么會有兩個值?
本質是發生了寫實拷貝!
fork給id變量返回的值并不相同,也就是子進程的fork返回值與父進程的不相同,正好與我們上面提到的寫實拷貝一致,修改了父進程的數據,就會單獨開辟空間儲存新數據;
6.6fork語句之前的語句是否還會執行?
答案是不會,但是會發生拷貝到子進程中!

按照推測”執行此處”只會打印一次

為什么出現了兩次”執行此處”呢?
原因是printf默認是行刷新,也就是遇到回車才會刷新緩沖區,而我們打代碼中中并沒有回車,數據只是寫入了緩沖區中沒有刷出來,fork執行完,子進程會把緩沖區的內容也拷貝過去,所以各自在進程結束的時候就會把緩沖區刷新,因此出現了兩次”執行此處”,并不是子進程執行了printf語句;
下面我們加上n

父進程會自動把”執行此處”從緩沖區中刷新,而子進程是不會執行fork之前的語句的,所以只打印了一次”執行此處”!;
6.7通過fork理解Bash命令行工作