? ? ? ?
目錄
? ? ? ? 明確進程的概念:
? ? ? ?
Linux下的進程狀態:
????????虛擬終端的概念:
?????????見一見現象:
????????用途之一 : 結合指令來監控進程的狀態:
和進程強相關的系統調用函數接口:
????????getpid()和getppid():
? ? ? ? fork():
????????fork函數創建子進程的分流邏輯:?
? ? ? ? 進程之間具有獨立性:?
?????????進程中存在的寫時拷貝:
見一見進程狀態:? ? ? ?
??在代碼層面見一見R和S兩種狀態:
????????系統調用函數 getpid() 和 getppid()
? ? ? ? 代碼和實操:?
? ? ? ? ?出現的疑點:
在代碼層面見一見 Z狀態:
????????概念理解:
? ? ? ? ?代碼實踐:
粗略了解一下D狀態:
補充說明X和T狀態:
在文件層面見一見進程:
特殊的進程:孤兒進程
? ? ? ? 驗證代碼:?
? ? ? ? 見一見效果:?
? ? ? ? ?最終的去處:
在籠統的認識了整個操作系統之后,就可以挑一個局部開始學習. 進程就是一個很好的選擇 , 他就想一個不知疲倦的員工,等待著使用者發號施令 . 我們的每一個操作,最終能夠起到作用都是以來著一個個進程 , 所以他也是離我們最近的元素 .?
? ? ? ? 明確進程的概念:
??????? 一切程序最開始都是乖乖待在磁盤里的一堆二進制文件 , 當打開電腦后 , 不管是系統內置的程序 , 還是我們自己的程序 , 加載到內存 , 就變成了進程
? ? ? ? 具體來講 , 當加載一個程序時 , 原本在磁盤里的代碼和數據被加載到內存 , 經過操作系統的無形大手 , 將這一堆內容定義成了統一的格式---結構體(操作系統底層通常都是c語言) .
? ? ? ? 因此 , 真正稱得上進程的并非單純的"程序"這一抽象概念 , 而是進程控制塊PCB(結構體實例化處的對象 Process Control BLOCK) + 代碼和數據 .
? ? ? ? 我們管理一個進程 , 本質上就是對操作系統發號施令,讓他來管理這兩個內容.
? ? ? ?
Linux下的進程狀態:
運行(R) | 指的是處于調度隊列里的進程 |
可中斷睡眠(S) | 又稱作阻塞,通常是在等待各種事件時的狀態 |
不可中斷睡眠(D) | 通常是在等待IO操作時的狀態 |
停止(T) | 比如我們按下Ctrl+z , 一個前臺進程就停止了 |
死亡(X) | 這個狀態一般我們看不到 |
????????虛擬終端的概念:
? ? ? ? ?由于咱通常都是用windows電腦來學習Linux操作系統,所以配置虛擬機啥的比較麻煩 , 索性就用xshell之類的外殼程序來連接云服務器進行操作?.?
? ? ? ? 而一臺云服務器可以同時供多個用戶使用 , 甚至 , 同一個用戶還可以同時打開兩個終端同時對一臺機器進行操作 , 看著是不是有些讓人匪夷所思 ,下面簡單解釋一下子:
?????????見一見現象:
?現象:
?只打開一個終端時:
用同一個用戶多開一個終端時:?
????????用途之一 : 結合指令來監控進程的狀態:
? ? ? ? 比方說我們初次學習進程,了解了一堆概念 , 那么久可以開一個終端窗口用于運行我們的程序 , 開第二個窗口用于監控我的的程序狀態(程序運行起來后就是一個進程)
和進程強相關的系統調用函數接口:
????????getpid()和getppid():
? ? ? ? 這倆哥們很簡單 , getpid()函數返回當前進程的pid ;? getppid()返回父進程的pid
? ? ? ? fork():
? ? ? ? 這個函數相較于c語言的大部分函數來說都很神奇 , 調用它就會為當前進程創建一個子進程 , 而我們知道子進程創建出來是為父進程分擔任務的 , 這就涉及到它的三個返回值了
????????fork函數創建子進程的分流邏輯:?
? ? ? ? fork有三個返回值:
- ?如果是父進程 , 返回整數
- 如果是子進程 , 返回0
- 如果創建子進程失敗 , 返回負數
? ? ? ? 因此雖然父子進程擁有一樣的代碼 , 但fork的返回值不會騙人 , 只要我們自己在代碼里對fork()的返回值進行判斷 , 根據返回值的不同執行不同的代碼 , 就可以達到代碼分流的目的了
? ? ? ? 簡單來說
? ? ? ? 進程之間具有獨立性:?
? ? ? ? 上圖中的父子進程似乎執行的是同一段代碼 , 只是不同的部分 , 其實父進程和子進程是完全獨立的 , 他們具有不同的pid.
? ? ? ? 之所以可以執行同一塊代碼 , 只是因為代碼本身在程序運行期間是只讀的 , 存放在代碼區, 不屬于任何進程自己的的內容?, 因此父子進程都可以訪問同一段代碼(保存同樣的指針)
?????????進程中存在的寫時拷貝:
? ? ? ?盡管進程之前是獨立的,但是在剛剛創建子進程時 , 為了提升效率 , 子進程新創建的PCB的內容幾乎是從父進程那完完全全拷貝而來的(除了pid等等比較特殊的屬性才會不同).
????????當調用fork函數時,會創建一個當前進程的子進程 , 子進程會擁有和當前進程同樣的代碼和數據.
? ? ? ? 代碼是存在于代碼區帶只讀內容 , 父、子進程只需要保存相同的指針變量指向對應的代碼即可
? ? ? ? 而數據不同 ,尤其是變量 ,可能需要被頻繁的修改 ,為了避免多個進程各自修改同一個變量對其他進程造成影響 , 就有了寫時拷貝的設計
? ? ? ? 一旦涉及數據的修改 , 操作系統就會為修改數據的進程新開辟一塊空間 , 拷貝一份原來的數據讓他進行自己的操作 .?
? ? ? ? 這就像"借鑒"同學的作業一樣 , 你只是看看還好 , 如果你想要修改 , 哪怕只是改一下名字 , 也得你自己另外弄一份來改!!!
見一見進程狀態:? ? ? ?
??在代碼層面見一見R和S兩種狀態:
????????系統調用函數 getpid() 和 getppid()
? ? 定義在<unistd.h>頭文件中 , getpid會返回當前進程(運行的程序)的進程pid , pid是一個進程的唯一標識符, 類似于一個學生的學號 .
? ? getppid()則會返回當前進程(運行的程序)的父進程的pid , 當前的進程稱為子進程 , 由父進程來創建這個進程. 就好像上級領導吩咐了一個下屬來幫自己干活.
? ? ? ? 接下來寫一個死循環程序 , 其中使用了一個死循環來不停的調用getpid()和getppid()來打印子進程和父進程的pid( )
? ? ? ? 接著打開第二個窗口來監視他的狀態
????????用于監視的指令 :?while true; do ps -ajx | head - | ps -ajx | grep code; sleep 1; done
????????其中code是我將要執行的程序
? ? ? ? 代碼和實操:?
#include<unistd.h> //類unix操作系統相關函數的頭文件,此處用于getpid()和getppid()while(1){printf("我是一個進程,我的pid:%d , 我的ppid:%d\n",getpid(),getppid());sleep(1);}
? ? ? ? ?出現的疑點:
- ? ? ? ?在剛才的程序里 , 明明是好像一直在一秒一秒的打印內容 , 為啥進程的狀態是S+ 呢? 其實和S狀態的定義和sleep函數有關:
- ? ? ? ? S狀態稱為可中斷睡眠狀態 , 通常發生在進程等待某種指令的間隙?
- ? ? ? ? 我的程序里有sleep函數 , 是的每次執行完一次printf后就會等待一秒
- ? ? ? ? 可是printf進行打印的操作相較于sleep的1秒來說實在是太短了,以至于ps指令幾乎沒法捕捉到進程進行輸出時的狀態(R+)
- ? ? ? ? 如果想要看到程序為運行狀態 , 只需要去掉sleep ,讓進程頻繁的進行打印.
? ? ? ? ?當去掉了代碼和監控指令里的sleep后,總算是可以觀察到R+狀態了哈哈哈哈哈.
在代碼層面見一見 Z狀態:
????????概念理解:
? ? ? ? Z(zombie)狀態叫做僵尸狀態 , 十分形象 :
????????想象一個人突然倒在路邊 , 沒有了呼吸 , 可以當叫來警察和救護車后 , 并不會直接把人抬走然后通知家屬 , 而是讓醫生先檢測和搶救一下 , 如果沒有這個過程 , 貿然帶走尸體甚至是違法的 .?
? ? ? ? 醫生進行檢測和搶救的過程 , 就是父進程回收子進程退出(死亡)信息的過程 , 如果沒有回收 , 誰也不敢貿然處理(即不能釋放這個進程的PCB) , 因此尸體在躺在地上的時候就會污染環境和占據空間(即遲遲不釋放的PCB會占用內存空間).
? ? ? ? 結論 : 當一個進程的使命結束 , 就會變成僵尸狀態 , 如果沒有父進程來獲取他的退出信息 , 這個僵尸進程就會一直占用內存空間 ,造成空間浪費.
? ? ? ? ?代碼實踐:
? ? ? ? 下面的代碼讓父進程一直運行 , 而子進程執行一條printf函數后就結束:
int ret = fork();if(ret > 0)//父進程{while(1){printf("我是父進程,pid:%d,我在😪\n",getpid());sleep(1);}}else if(ret == 0)//子進程{printf("我是子進程,pid:%d,我很快就要掛了....💀\n",getpid());}else //創建子進程失敗{//失敗的情況很少見}
?????????下面是運行的內容和進程的監控內容?
粗略了解一下D狀態:
定義 : 不可中斷休眠 , 通常回出現在一個進程等待IO操作時 , 可以在一定程度上避免數據丟失
??????????這個很難驗證 , 但可以文字敘述一下(情況之一):
- ????????系統的內存資源是有限的 , 因此操作系統會在系統資源告急時采取一些策略 , 將不那么重要的進程的大部分內容給暫時移走
- ? ? ? ? 當系統的資源告急時很嚴重了 , 為了表面上給用戶一個良好的使用體驗 , 除了看得見的前臺進程?, 其他的不分青紅皂白就會給殺掉!!!
- ? ? ? ? 可是有的進程可能正在對磁盤里面寫入數據 , 接著等待磁盤的處理結果 , 這時如果進程被殺掉了 , 不管寫入數據是否成功 , 磁盤的處理結果可能就會丟失 , 用戶層面對此是全然不知的!!!
- ? ? ? ? 因此對于進行IO操作的進程 , 比如開個后門 , 讓它們可以平平安安的獲取操作的結果 , 也就有了D狀態 , 故而也叫磁盤休眠狀態.?
補充說明X和T狀態:
? ? ? ? X狀態 : 一個死透了的進程 , 通常上層用戶看不見 , 畢竟后事處理完后就沒有為他留著內存空間的必要了 . (上面提到過的僵尸進程就是還沒死透但還需要被處理的進程)
? ? ? ? ?T狀態:一個暫停了的進程 , 比如當使用Ctrl +z時就會導致進程停止 , 如果放著不管就會一直占用空間 , 造成資源浪費!!!
在文件層面見一見進程:
? ? ? ? Linux的一大設計理念---一切皆文件 . 連時而創建,時而銷毀的進程也不例外
?系統根目錄下的一個proc目錄存放了和進程相關的文件:
如果隨便查看一個管理進程的目錄文件的內容 , 結果如下 :
當然嘍,如果是查看自己的進程的路徑(比如自己運行的c語言程序) , 情況會變化
特殊的進程:孤兒進程
? ? ? ? 在驗證僵尸進程時 , 情況是子進程先于父進程結束 , 而如果是父進程先結束后留下孤零零的子進程會怎么樣呢? 答案是孤兒進程,名字也很形象啦.
? ? ? ? 驗證代碼:?
//父進程立馬結束,子進程死循環一直干活
int ret = fork();if(ret > 0)//父進程{printf("我是父進程,pid:%d,我馬上溜啦🏃?♂?💨\n",getpid());}else if(ret == 0)//子進程{while(1){printf("我是子進程,pid:%d,我在等我爸開路虎來接我😎\n",getpid());sleep(1);}}else //創建子進程失敗{//失敗的情況很少見}
? ? ? ? 見一見效果:?
? ? ? ? 當運行程序 , 我們可以看到子進程的父進程的pid馬上變成了1 , 并且 , 無法用Ctrl+c來殺掉這個進程!!!如下圖:?
? ? ? ? ?pid為1的進程是最重要的系統進程(Linux下叫做init) , 甚至可以把他就當做操作系統本身 .(其實還有pid為0的進程,只不過在創建1號進程后很快就結束了)
? ? ? ? 當父進程先于子進程退出 , 那后續子進程結束后就沒有進程可以獲取他的退出信息 , 從而就永遠無法被銷毀 , 這時就會由pid為1的系統進程來收養他 , 因此父進程提前退出的子進程也叫做孤兒進程
? ? ? ? ?最終的去處:
? ? ? ? 孤兒進程被pid為1的init系統進程收養后 , 還會變成后臺進程 , 無法被ctrl+c(針對前臺進程)給終止 , 但可以使用 kill -9 pid來殺掉 .否則 , 系統不停止運行 , 這個子進程就會一直占用內存資源