(五)進程控制

目錄

1. 有關進程

1.1 什麼是進程

1.2進程ID(PID)

1.3 三個特殊的進程

進程 ID == 0 的進程

進程ID == 1的進程

進程ID == 2的進程

1.4獲取與進程相關的各種ID的函數

2. 程序的運行過程

2.1 程序如何運行起來

2.2 fork

2.2.1 函數原型

2.2.2複製的原理

2.2.3 父子進程各自會執行哪些代碼     

2.3 父子進程共享操作文件

(1)情況1:獨立打開文件

(2)情況2:fork之前打開文件

2.4 子進程會繼承父進程的哪些屬性

2.4.1 子進程繼承如下性質

2.4.2 子進程獨立的屬性

3. exec加載器

3.1 exec的作用

3.2 exec函數族

3.2.1 execve函數原型

3.2.2 exec的作用

3.2.3 在命令行執行./a.out,程序是如何運行起來的

3.2.4 雙擊快捷圖標,程序是怎麼運行起來的

4. system函數

5. 回收進程資源

5.1 爲什麼要回收進程的資源?

5.2 由誰來回收進程資源

5.3 殭屍進程和孤兒進程

5.3.1 殭屍進程

5.3.2 孤兒進程

6. wait函數

6.1 進程的終止

6.1.1 正常終止

6.1.2 異常終止

6.1.3 進程終止狀態

6.2 父進程如何從內核獲取子終止狀態

6.2.1 如何獲取

6.2.2 wait函數原型

6.2.3 從進程終止狀態中提取進程終止的原因、返回值或者信號編號

6. 進程狀態

7. java進程

8.進程關係

8.1 父子關係

8.2 進程組

8.3 會話期關係

9. 守護進程


1. 有關進程

1.1 什麼是進程

1.2進程ID(PID)

OS爲了能夠更好地管理進程,爲每個進程分配了一個唯一的編號(非負整數),這個編號就是PID

  • 如果當前進程結束了,這個PID可以被可以被重複使用,但是所有“活着”的進程,它們的進程ID一定都是唯一的。
  • 因爲ID唯一性,當我們想創建一個名字唯一的文件時,往往可以在文件名中加入PID,這樣就能保證文件名唯一性。

1.3 三個特殊的進程

0、1、2這個三個進程,是OS啓動起來後會一直默默運行的進程,直到關機OS結束運行

進程 ID == 0 的進程

進程ID == 1的進程

進程ID == 2的進程

1.4獲取與進程相關的各種ID的函數

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);
pid_t getppid(void);
uid_t getuid(void);
gid_t getgid(void);

  

2. 程序的運行過程

2.1 程序如何運行起來

(1)在內存中劃出一片內存空間
(2)將硬盤上可執行文件中的代碼(機器指令)拷貝到劃出的內存空間中
(3)pc指向第一條指令,cpu取指運行

在Linux下,OS提供兩個非常關鍵的API,一個是fork,另一個是exec。

    fork  :開闢出一塊內存空間
    exec:將程序代碼(機器指令)拷貝到開闢的內存空間中,並讓pc指向第一條指令,CPU開始執行,進程就運行起來了
              運行起來的進程會與其它的進程切換着併發運行。

 

2.2 fork

2.2.1 函數原型

#include <unistd.h>

pid_t fork(void);

  

 

2.2.2複製的原理

Linux有虛擬內存機制,所以父進程是運行在虛擬內存上的,虛擬內存是OS通過數據結構基於物理內存模擬出來的,因此底層的對應的還是物理內存

複製時子進程時,會複製父進程的虛擬內存數據結構,那麼就得到了子進程的虛擬內存,相應的底層會對應着一片新的物理內存空間,裏面放了與父進程一模一樣代碼和數據,

2.2.3 父子進程各自會執行哪些代碼     

 

 

驗證子進程複製了父進程的代碼和數據

      

      

      printf("befor fork"); 在父子進程中都輸出   因爲沒加換行符 所以 在子進程中遇見換行緩衝區被刷新到屏幕

       

2.3 父子進程共享操作文件

(1)情況1:獨立打開文件

 

獨立打開同一文件時,父子進程各自的文件描述符,指向的是不同的文件表。

因爲擁有不同的文件表,所以他們擁有各自獨立的文件讀寫位置,會出現相互覆蓋情況

如果不想相互覆蓋需要加O_APPEND標誌。

(2)情況2:fork之前打開文件

子進程會繼承父進程已經打開的文件描述符,

          如果父進程的3描述符指向了某個文件,子進程所繼承的文件描述符3也會指向這個文件

由於共享的是相同的文件表,所以擁有共同的文件讀寫位置,不會出現覆蓋的情況。

子進程的0 1 2這三個打開的文件描述符,其實也是從父進程那裏繼承過來的,並不是子進程自己去打開的

         同樣的父進程的 1 2又是從它的父進程那裏繼承過來的,最根溯源的話,都是從最原始的進程哪裏繼承過來的

2.4 子進程會繼承父進程的哪些屬性

2.4.1 子進程繼承如下性質

  • (1)用戶ID,用戶組ID
  • (2)進程組ID
  • (3)會話期ID
  • (4)控制終端
  • (5)當前工作目錄
  • (6)根目錄
  • (7)文件創建方式屏蔽字
  • (8)環境變量
  • (9)打開的文件描述符
  •        等等

2.4.2 子進程獨立的屬性

  • (1)進程ID。
  • (2)不同的父進程ID。
  • (3)父進程設置的鎖,子進程不能被繼承。
  •      等等

3. exec加載器

3.1 exec的作用

父進程fork複製出子進程的內存空間後,子進程內存空間的代碼和數據和父進程是相同的,這樣沒有太大的意義,我們需要在子進 程空間裏面運行全新的代碼,這樣纔有意義。

有了exec後,我們可以單獨的另寫一個程序,將其編譯好後,使用exec來加載即可。

3.2 exec函數族

exec的函數有很多個,它們分別是execve、execl、execv、execle、execlp、execvp,都是加載函數。

其中execve是系統函數,其它的execl、execv、execle、execlp、execvp都是基於execve封裝得到的庫函數,

3.2.1 execve函數原型

#include <unistd.h>

int execve(const char *filename, char **const argv, char **const envp);

  

 

3.2.2 exec的作用

將新程序代碼加載(拷貝)到子進程的內存空間,替換掉原有的與父進程一模一樣的代碼和數據,讓子進程空間運行全新的程序。

3.2.3 在命令行執行./a.out,程序是如何運行起來的

(1)窗口進程先fork出子進程空間
(2)調用exec函數加載./a.out程序,並把命令行參數和環境變量表傳遞給新程序的main函數的形參

3.2.4 雙擊快捷圖標,程序是怎麼運行起來的

(1)圖形界面進程fork出子進程空間
(2)調用exec函數,加載快捷圖標所指向程序的代碼
            以圖形界面方式運行時,就沒有命令行參數了,但是會傳遞環境變量表

4. system函數

如果我們需要創建一個進子進程,讓子進程運行另一個程序的話,可以自己fork、execve來實現,但是這樣的操作很麻煩

所以就有了system這個庫函數,這函數封裝了fork和execve函數

調用時會自動的創建子進程空間,並把新程序的代碼加載到子進程空間中,然後運行起來。

#include <stdlib.h>
int system(const char *command);

(1)功能:創建子進程,並加載新程序到子進程空間,運行起來。

(2)參數:新程序的路徑名
 

5. 回收進程資源

進程運行終止後,不管進程是正常終止還是異常終止的,必須回收進程所佔用的資源。

5.1 爲什麼要回收進程的資源?

(1)程序代碼在內存中動態運行起來後,纔有了進程,進程既然結束了,就需要將代碼佔用的內存空間讓出來(釋放)。

(2)OS爲了管理進程,爲每個進程在內存中開闢了一個task_stuct結構體變量,進程結束了,那麼這個結構體所佔用的內存空間也需要被釋放。

(3)等其它資源                    

5.2 由誰來回收進程資源

由父進程來回收,父進程運行結束時,會負責釋放子進程資源。

  • R 正在運行
  • S 處於休眠狀態
  • Z 殭屍進程,進程運行完了,等待被回收資源

5.3 殭屍進程和孤兒進程

ps查看到的進程狀態

5.3.1 殭屍進程

子進程終止了,但是父進程還活着,父進程在沒有回收子進程資源之前,子進程就是殭屍進程

 

爲什麼子進程會變成殭屍進程?

子進程已經終止不再運行,但是父進程還在運行,它沒有釋放子進程佔用的資源,所以就變成了佔着不拉屎殭屍進程。

 

就好比人死後不腐爛,身體佔用的資源得不到回收是一樣的,像這種情況就是所謂的殭屍。

5.3.2 孤兒進程

沒爹沒媽的孩子就是孤兒,子進程活着,但是父進程終止了,子進程就是孤兒進程。

 

爲了能夠回收孤進程終止後的資源,孤兒進程會被託管給我們前面介紹的pid==1的init進程

每當被託管的子進程終止時,init會立即主動回收孤兒進程資源

回收資源的速度很快所以孤兒進程沒有變成殭屍進程的機會。

6. wait函數

作用:父進程調用這個函數的功能有兩個

  • (1)主動獲取子進程的“進程終止狀態”。
  • (2)主動回收子進程終止後所佔用的資源。

wait函數,在實際開發中用的很少

6.1 進程的終止

6.1.1 正常終止

(1)main調用return
(2)任意位置調用exit
(3)任意位置調用_exit
不管哪種方式來正常終止,最終都是通過_exit返回到OS內核的。

6.1.2 異常終止

如果是被某個信號終止的,就是異常終止。

(1)自殺:自己調用abort函數,自己給自己發一個SIGABRT信號將自己殺死。
(2)他殺:由別人發一個信號,將其殺死。

6.1.3 進程終止狀態

(1)退出狀態與“進程終止狀態”

return、exit、_exit的返回值稱爲“進程終止狀態”,嚴格來說應該叫“退出狀態”,
return(退出狀態)、exit(退出狀態)或_exit(退出狀態)

當退出狀態被_exit函數交給OS內核,OS對其進行加工之後得到的纔是“進程終止狀態”,父進程調用wait函數便可以得到這個“進程終止狀態”。

                                   

(2)OS是怎麼加工的?

1)正常終止

          進程終止狀態 = 終止原因(正常終止)<< 8 | 退出狀態的低8位

2)異常終止

          進程終止狀態 = 是否產生core文件位 | 終止原因(異常終止)<< 8 | 終止該進程的信號編號

(3)父進程調用wait函數,得到“進程終止狀態”有什麼用

父進程得到進程終止狀態後,就可以判斷子進程終止的原因是什麼,如果是正常終止的,可以提取出返回值,如果是異常終止的,可以提取出異常終止進程的信號編號。

 

當有OS支持時,進程return、exit、_exit正常終止時,所返回的返回值(退出狀態),最終通過“進程終止狀態”返回給了父進程。

 

這有什麼用,比如,父進程可以根據子進程的終止狀態來判斷子進程的終止原因,返回值等等,以決定是否重新啓動子進程, 或則做一些其它的操作,不過一般來說,子進程的終止狀態對父進程並沒有太大意義。

6.2 父進程如何從內核獲取子終止狀態

6.2.1 如何獲取

(1)父進程調用wait等子進程結束,如果子進程沒有結束的話,父進程調用wait時會一直休眠的等(或者說阻塞的等)。

(2)子進程終止返回內核,內核構建“進程終止狀態”

           

(3)內核向父進程發送SIGCHLD信號,通知父進程子進程結束了,你可以獲取子進程的“進程終止狀態”了。

           

6.2.2 wait函數原型

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

(1)功能:獲取子進程的終止狀態,主動釋放子進程佔用的資源
        
(2)參數:用於存放“進程終止狀態”的緩存

(3)返回值:成功返回子進程的PID,失敗返回-1,errno被設置。
 

6.2.3 從進程終止狀態中提取進程終止的原因、返回值或者信號編號

(1)進程狀態中所包含的信息

(2)如何提取裏面的信息

系統提供了相應的帶參宏,使用這個帶參宏就可以從“進程終止狀態”中提取出我們要的信息。
    
提取原理:相應屏蔽字&進程終止狀態,屏蔽掉不需要的內容,留下的就是你要的信息。        


(3)wait的缺點

如果父進程fork創建出了好多子進程,wait只能獲取最先終止的那個子進程的“終止”狀態,其它的將無法獲取,

如果你想獲取所有子進程終止狀態,或者只想獲取指定子進程的進程終止狀態,需要使用wait的兄弟函數waitpid,

它們的原理是相似的

6. 進程狀態

每個進程與其它進程併發運行時,該進程會在不同的“進程狀態”之間進行轉換

7. java進程

 如何運行編譯型和解釋型語言的程序

(1)java程序的運行

1)父進程(命令行窗口、圖形界面)會fork複製出子進程空間
2)調用exec加載java虛擬機程序,將虛擬機程序的代碼拷貝到子進程空間中


其實最簡單的理解就是,java虛擬機就代表了java進程。

 

當你運行另一個java程序時,又會自動地啓動一個虛擬機程序來解釋java字節碼,此時另一個java進程又誕生了。

也就是說你執行多少個java進程,就會運行多少個java虛擬機,當然java虛擬機程序在硬盤上只有一份,只不過被多次啓動而已。

(2)java虛擬機怎麼得到

當我們運行java程序時,虛擬機會被自動啓動。

 虛擬機一般是運行在OS上的,不過其實虛擬機也可以運行在沒有OS的裸機上

(3)在java程序裏面,也可以調用java庫提供的類似的fork和exec函數,我們自己來創建一個java子進程,並執行新程

java庫提供的類似的fork、exec函數,下層也是調用OS的fork、exec函數。

8.進程關係

進程間的關係,大致有三種,即父子關係、進程組關係、會話期關係。

8.1 父子關係

已有進程調用fork創建出一個新的進程,那麼這兩個進程之間就是父子進程關係,子進程會繼承和父進程的屬性。

8.2 進程組

8.2.1 什麼是進程組

   多個進程可以在一起組成一個進程組,其中某個進程會擔任組長,組長進程的pid就是整個進程組的組ID。

8.2.2 進程組的生命週期

   就算進程組的組長終止了,只要進程中還有一個進程存在,這個進程組就存在。

8.3 會話期關係

多個進程組在一起,就組成了會話期。

9. 守護進程

守護進程也被稱爲精靈進程。

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章