目錄
一:進程概念?
二:Linux中的進程概念
三:用getpid(),getppid()獲取該進程的PID,PPID
四:用fork()來創建子進程
五:操作系統學科的進程狀態?
?六:Linux中的進程狀態
接下來的日子會順順利利,萬事勝意,生活明朗-----------林辭憂
一:進程概念?
1.操作系統學科層面的概念
一個加載到內存中的程序,叫做進程;? ?正在運行的程序叫做進程
2.深層次的進程概念
對于一個操作系統不僅僅只能運行一個進程,而是可以運行很多個進程,為了更好的管理這些進程,就需要用先描述再組織的思想
因此對于任何一個進程在加載到內存當中的時候,形成真正的進程時,操作系統要先創建描述該進程(屬性)的struct結構體對象,即PCB(進程屬性的集合)----進程控制塊
PCB是在操作系統中定義的struct結構體類型。當加載進程的時候,本質上不僅僅是把對應的數據和代碼加載到內存,而且操作系統會根據描述該進程的PCB類型為當前進程創建對應的PCB對象,把該進程的相關屬性值填充完成初始化。這個PCB結構體變量是由操作系統自己形成的。
進程=內核PCB數據結構對象(描述該進程所有的屬性值)+對應的代碼和數據
在操作系統中,對于所有進程的PCB結構體對象使用雙向鏈表的方式 進行連接,在每個PCB結構體對象中包含相應的指針字段指向該進程對應的代碼和數據,此后操作系統只需對PCB對象進行管理,便可以達到管理進程的目的,這樣對于進程的管理就轉化為對這個鏈表的增刪查改
二:Linux中的進程概念
1.在Linux中描述進程的結構體叫做task_struct(Linux系統中的PCB)。
2.task_struct是Linux內核的一種數據結構,它會被裝載到RAM(內存)里并且包含著進程的信息
3.根據task_struct實例化進程對象,創建一個具體的PCB對象,再把所有對象的PCB組織起來
4.task_struct中包含的進程屬性
? ? ?標示符: 描述本進程的唯一標示符,用來區別其他進程(PID)
? ??狀態: 任務狀態,退出代碼,退出信號等
? ??優先級: 相對于其他進程的優先級
? ??程序計數器: 程序中即將被執行的下一條指令的地址
? ??內存指針: 包括程序代碼和進程相關數據的指針,還有和其他進程共享的內存塊的指針
? ??上下文數據: 進程執行時處理器的寄存器中的數據[休學例子,要加圖CPU,寄存器]
? ??I/O狀態信息: 包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表
? ??其他信息
6.查詢進程信息相關指令
? ps axj 查看進程信息
? top 查看正在運行的進程信息
? ls /proc -l? :在系統當中啟動的所有進程默認在/proc目錄里,就會給相應進程在/proc目錄里創建以該進程PID命名的文件夾/目錄,在該目錄里保存了進程的大多屬性;在關機后,/proc目錄下的所有數據就都沒有了,在開機時,操作系統會自動創建這些目錄/文件,這上面顯示的所有信息是Linux操作系統用文件系統的方式把內存當中的文件/進程信息可視化出來,而且這些信息都是內存級別的
ls? /proc/進程PID -l 顯示系統當中具體動態運行的進程的相關信息
注:1.進程所對應的可執行程序是在磁盤exe目錄下的這個文件當中,所以PID對應的進程的可執行程序是可以被找到的
2.cwd:當進程在啟動時,該進程的PCB屬性里就記錄了當前Linux所在的絕對路徑,也就是當前進程的工作目錄;在當前特定目錄下工作的一個進程,默認自己的進程在哪個目錄下,這個目錄就是這個進程的當前目錄,創建文件/搜索文件都是在這個當前目錄下完成的
三:用getpid(),getppid()獲取該進程的PID,PPID
1.創建一個進程,就要操作系統創建對應的PCB,在Linux中稱為task_struct結構體對象,當存在多個時,就要用雙向鏈表方式進行連接管理,通過找到頭結點來訪問各個進程,為了區分這些進程,操作系統就分配了對應的PID,對應的PID是存在于task_struct結構體當中
2.進程在運行時,都會有獨屬于自己的PID,對于PID當每個進程在系統運行期間終止了后再啟動,操作系統分配的PID標識符多數情況下是會改變的
注:這里檢索指令也可以寫為 ps axj | head -1 && ps axj | grep test?
當grep自己過濾時,也要變為一個進程,而grep命名的進程中也含有關鍵字test,所以當它在執行過濾系統當中的進程時,首先得把grep變為一個進程,然后被cpu調度執行過濾代碼,在過濾時也就會把自己帶上,所以所有指令運行時都是進程
ps axj | head -1 ; ps ajx | grep test | grep -v grep? ? ? ?這樣的話就不會帶自己了
3.ps指令的本質:遍歷鏈表,在task_struct中將對應屬性格式化打印出來
4.kill -9 進程PID? 殺掉進程?
5.PID存在于task_struct結構體當中,這個結構體是屬于操作系統所維護的,是系統內的數據,所以用戶不能直接通過task_struct結構體對象以.的方式訪問PID,而是要通過對應的系統調用接口來獲取進程的PID,對于PPID也是如此
6.當每次重新登錄時,系統會為我們單獨創建一個bash進程,即創建一個命令行解釋的進程,在顯示器中打印出對應的對話框終端?
7.當運行一個進程時,命令行解釋器會將后面的指令變為bash的子進程,由子進程執行對應的命令,當子進程出現問題時,不影響bash進程,即在命令行中輸入的所有指令都是bash進程的子進程,bash進程只負責命令行解釋
四:用fork()來創建子進程
fork()是用來創建一個子進程,創建成功的話,給父進程返回子進程的PID,給自己返回0;失敗的話,給自己返回-1?
?
這里我們會發現fork()之后的代碼執行了兩次,即fork()之后會變成兩個進程,執行不同的代碼塊?
?1.為什么fork要給子進程返回0,給父進程返回子進程的PID?
返回不同的返回值,是為了區分不同的執行流,執行不同的代碼塊
給父進程返回子進程的PID,是為了通過父進程的PID去明確控制訪問的是哪個子進程(控制并區分子進程),PID也是用來標定子進程的唯一性的
而子進程只需要調用getpid()就能獲取進程的PID/PPID,所以找到父進程很容易,只需要返回0來標識成功即可
2.fork()函數究竟干了什么?
? ? ?當執行到fork()時,就要創建對應的子進程,而進程=內核task_struct數據結構對象+對應的代碼和數據,而創建對應的task_struct結構體對象是以原先老進程的task_struct為模板拷貝一份并用父進程的相應字段來初始化子進程,做相應修改,但對于代碼是沒有的,所以只能共享父進程的代碼;而對于數據,如果子進程也把對應的和父進程共享的數據拷貝一份的話,由操作系統自動完成,但這樣必然會造成子進程對這部分數據只修改一部分或者不修改,造成有兩份相同的數據形成冗余,占有資源,因此當子進程要訪問父進程的某一部分數據,并且操作系統識別到要對父進程的這部分數據做修改時,這時就在系統的內存位置重新開辟一塊空間將數據拷貝過去,寫入。這樣對于子進程想對父進程修改的數據,就再開辟空間,在新空間寫入==>父子進程間數據層面的寫時拷貝
?
對于fork本質上是一個函數,也是一個系統調用,在操作系統當中是有實現的?
所以這樣同一個函數就返回了兩次
3.一個變量是怎么會有不同的內容的?
?
五:操作系統學科的進程狀態?
1.cpu負責把一個進程放到cpu中去執行,由調度器決定被調度的具體進程,所有進程參與到操作系統的調度過程,所有的進程之間對于cpu資源本質是一種競爭關系,所以存在調度器是為了保證進程間的公平調度
2.為合理調度,每個cpu都要維護一個runqueue的結構體,cpu以運行隊列的形式對進程進行調度
?
3.對于每個進程都有時間片的概念,在時間片的時間中被cpu調度執行,時間結束,自動切換到下一個進程,如果這個進程還需要被調度執行的話,就會被鏈接到隊列最后面排隊等待調度執行
4.在一個時間段內,所有進程的代碼都會被調度執行------->并發執行
把進程放到cpu上去執行,再拿下來--------->進程切換
?
5.對于底層的硬件設備也可以采用先組織,再描述的方式進行管理,即每個外設都維護一個對應的結構體對象。這樣操作系統對于底層設備的管理也就轉換為在系統當中對某種設備結構體的管理,如果當前要訪問的資源不存在/未準備好,此時進程卡住了,這就是阻塞狀態
此時該進程 就在對應外設的等待隊列中排隊等待資源就緒,一旦資源就緒就可以鏈接到運行隊列中等待被cpu調度執行
6.當進程資源不足時,操作系統會將進程的代碼和數據置換到外設當中,這樣數據和代碼并未在內存當中,這時所處的狀態就是掛起狀態
?六:Linux中的進程狀態
1.Linux內核中的定義
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
2.?R運行狀態(running): 并不意味著進程一定在運行中,它表明進程要么是在運行中要么在運行隊列里
當無IO,純運行時,就會變為R狀態?
3.S睡眠狀態(sleeping): 意味著進程在等待事件完成(這里的睡眠有時候也叫做可中斷睡眠
(interruptible sleep)
這里當printf是要訪問顯示器?的,當死循環打印時,設備不是處于一個正寫入的狀態,進程有很大可能是一直在等待的,所以是S+而不是R+;對于這里有+的表示是在前臺執行的(當運行起來程序時,bash命令行解釋器對輸入的指令不再有反應),可以用ctrl+c終止進程,可以采用./test & 這樣就會變為S,變為后臺執行,此時就必須用kill -9 PID來終止進程
4.D磁盤休眠狀態(Disk sleep)有時候也叫不可中斷睡眠狀態(uninterruptible sleep),在這個狀態的進程通常會等待IO的結束
只要進程當前正在有寫入任務交給磁盤時,如果磁盤沒有辦法立馬響應的話,需要等待進程,這個進程就不能以S淺度睡眠的狀態去等待,必須把自己設為D狀態,D狀態進程任何人都不能殺掉,包括操作系統,這樣可以保證向磁盤中寫入數據時,數據不會丟失。當磁盤把數據寫完之后返回時,此時進程的狀態就由D轉變為R,供cpu調度執行
即進程在等待磁盤進行寫入時,此時進程所處狀態為D狀態,是不響應操作系統的任何請求的
5.T停止狀態(stopped): 可以通過發送 SIGSTOP 信號給進程來停止(T)進程。這個被暫停的進程可以通過發送 SIGCONT 信號讓進程繼續運行
kill -l 查看信號
kill -19 PID 讓當前進程停下? kill -18 PID 恢復當前進程由T變為R狀態
S狀態一定是在等待某種資源,而T狀態可能在等待某種資源/被其他進程控制?
6.X死亡狀態(dead):這個狀態只是一個返回狀態,你不會在任務列表里看到這個狀態
7.Z(zombie)-僵尸狀態
當一個進程死亡的時候,在死亡之時,并不會立即進入dead的狀態,而是先進入Z狀態
當一個進程退出時,它并不是退出之后立即將所有資源進行釋放,而是操作系統需要把當前進程退出之時的退出信息維持一段時間,當對應的父進程讀取到進城的信息,進程的資源才會被釋放,這段時間所處的狀態為Z狀態,即我們把這樣已經死掉的,但是當前需要由父進程來關心,此時進程所維持的狀態
進程一般退出的時候,如果父進程沒有主動收回子進程信息,子進程會讓自己一直處于Z狀態進程的相關資源尤其是task_struct結構體不能釋放,所以會一直占用資源而且不釋放,就會導致內存泄露,父進程是bash進程的子進程,退出時會被bash所回收
父子進程當父進程先退出,子進程的父進程會被改為1號進程(操作系統),即父進程是1號進程的子進程也叫做孤兒進程,該進程被操作系統所領養,為未來該進程退出時的釋放資源
僵死狀態(Zombies)是一個比較特殊的狀態。當進程退出并且父進程使用wait()系統調用,后面講沒有讀取到子進程退出的返回代碼時就會產生僵死(尸)進程
僵死進程會以終止狀態保持在進程表中,并且會一直在等待父進程讀取退出狀態代碼
只要子進程退出,父進程還在運行,但父進程沒有讀取子進程狀態,子進程進入Z狀態