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

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

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


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

我們可以寫一個程序來查看進(jìn)程;

這里我寫了個死循環(huán)程序來查看正在運(yùn)行的code進(jìn)程 ;

我們會發(fā)現(xiàn)有兩個code進(jìn)程,為什么呢?
對于死循環(huán)的程序是一直會進(jìn)行下去的,我們可以使用指令來”殺掉他”!

四,進(jìn)程PID目錄-proc

/proc目錄里面存儲都是內(nèi)存級的文件!!在關(guān)機(jī)時會消失,開機(jī)時又會出現(xiàn),他是對動態(tài)運(yùn)行的所有進(jìn)程的一個可視化信息!!
其中以數(shù)字命名的文件夾就是對應(yīng)進(jìn)程的PID,里面包含進(jìn)程的各種信息;

五,獲取進(jìn)程標(biāo)識符5.1.理解PPID和PID

我們會發(fā)現(xiàn)我們可執(zhí)行程序的父進(jìn)程是 -bash命令行。
5.2.調(diào)用系統(tǒng)接口–getpid
我們可以使用系統(tǒng)接口來獲取當(dāng)前進(jìn)程的PID;
先看一下接口說明:

寫一個程序來調(diào)用接口查看pid;

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

結(jié)論:每次執(zhí)行程序,分配的PID都不一樣,但是父進(jìn)程PPID是一樣的,其實(shí)都是Bash進(jìn)程;
之后,我重新啟動了機(jī)器!

發(fā)現(xiàn)在重啟后,PPID竟然改變了!
結(jié)論:Bash(命令行)是機(jī)器啟動時就創(chuàng)建好的進(jìn)程,直至關(guān)機(jī)PID都不會變 !
六,重點(diǎn):使用系統(tǒng)接口fork創(chuàng)建子進(jìn)程


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


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

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

按照推測”執(zhí)行此處”只會打印一次

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

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