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

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

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


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

我們可以寫(xiě)一個(gè)程序來(lái)查看進(jìn)程;

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

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

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

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

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

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

寫(xiě)一個(gè)程序來(lái)調(diào)用接口查看pid;

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

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

發(fā)現(xiàn)在重啟后,PPID竟然改變了!
結(jié)論:Bash(命令行)是機(jī)器啟動(dòng)時(shí)就創(chuàng)建好的進(jìn)程,直至關(guān)機(jī)PID都不會(huì)變 !
六,重點(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)建失敗,就會(huì)返回一個(gè)負(fù)數(shù)
你沒(méi)有聽(tīng)錯(cuò),fork有兩個(gè)返回值!
我們可以寫(xiě)個(gè)程序查看下!


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

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

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

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

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