c/c++ 複習筆記 第五天 進程管理

進程管理

#一、 基本概念

1. 進程與程序


1) 進程就是運行中的程序。一個運行着的程序,
   可能有多個進程。進程在操作系統中執行特定的任務。

2) 程序是存儲在磁盤上,
   包含可執行機器指令和數據的靜態實體。
   進程或者任務是處於活動狀態的計算機程序。

## 2.  進程的分類
  1. 進程一般分爲交互進程、批處理進程和守護進程三類。

  2. 守護進程總是活躍的,一般是後臺運行。
    守護進程一般是由系統在開機時通過腳本自動激活啓動,
    或者由超級用戶root來啓動。

3. 查看進程


1) 簡單形式

**# ps**

以簡略方式顯示當前用戶有控制終端的進程信息。

2) BSD風格常用選項

**# ps axu**

a - 所有用戶有控制終端的進程
x - 包括無控制終端的進程
u - 以詳盡方式顯示
w - 以更大列寬顯示

3) SVR4風格常用選項

**# ps -efl**

-e或-A            - 所有用戶的進程
-a                - 當前終端的進程
-u 用戶名或用戶ID - 特定用戶的進程
-g 組名或組ID     - 特定組的進程
-f                - 按完整格式顯示
-F                - 按更完整格式顯示
-l                - 按長格式顯示

4) 進程信息列表

USER/UID: 進程屬主。

PID: 進程ID。

%CPU/C: CPU使用率。

%MEM: 內存使用率。

VSZ: 佔用虛擬內存大小(KB)。

RSS: 佔用物理內存大小(KB)。

TTY: 終端次設備號,“?”表示無控制終端,如後臺進程。

STAT/S: 進程狀態。可取如下值:

O - 就緒。等待被調度。
R - 運行。Linux下沒有O狀態,就緒狀態也用R表示。
S - 可喚醒睡眠。系統中斷,獲得資源,收到信號,
    都可被喚醒,轉入運行狀態。
D - 不可喚醒睡眠。只能被wake_up系統調用喚醒。
T - 暫停。收到SIGSTOP信號轉入暫停狀態,
    收到SIGCONT信號轉入運行狀態。
W - 等待內存分頁(2.6內核以後被廢棄)。
X - 死亡。不可見。
Z - 殭屍。已停止運行,但其父進程尚未獲取其狀態。
< - 高優先級。
N - 低優先級。
L - 有被鎖到內存中的分頁。實時進程和定製IO。
s - 會話首進程。
l - 多線程化的進程。
+ - 在前臺進程組中。

START/STIME: 進程開始時間。

TIME: 進程運行時間。

COMMAND/CMD: 進程指令。

F: 進程標誌。可由下列值取和:

1 - 通過fork產生但是沒有exec。
4 - 擁有超級用戶特權。

PPID: 父進程ID。

NI: 進程nice值,-20到19,可通過系統調用或命令修改。

PRI: 進程優先級。

靜態優先級 = 80 + nice,60到99,值越小優先級越高。
內核在靜態優先級的基礎上,
根據進程的交互性計算得到實際(動態)優先級,
以體現對IO消耗型進程的獎勵,
和對處理器消耗型進程的懲罰。

ADDR: 內核進程的內存地址。普通進程顯示“-”。

SZ: 佔用虛擬內存頁數。

WCHAN: 進程正在等待的內核函數或事件。

PSR: 進程被綁定到哪個處理器。

## 4.  父進程、子進程、孤兒進程和殭屍進程
-------------------------------------

內核進程(0)
  init(1)
    xinetd
      in.telnetd <- 用戶登錄
        login
          bash
            vi

1) 父進程啓動子進程後,
   子進程在操作系統的調度下與其父進程同時運行。

2) 子進程先於父進程結束,
   子進程向父進程發送SIGCHLD(17)信號,
   父進程回收子進程的相關資源。

3) 父進程先於子進程結束,子進程成爲孤兒進程,
   同時被init進程收養,即成爲init進程的子進程。

4) 子進程先於父進程結束,
   但父進程沒有回收子進程的相關資源,
   該子進程即成爲殭屍進程。

5. 進程標識符(進程ID)
  1. 每個進程都有一個以非負整數表示的唯一標識,
    即進程ID/PID。

  2. 進程ID在任何時刻都是唯一的,但可以重用,
    當一個進程退出時,其進程ID就可以被其它進程使用。

  3. 延遲重用。

a.out - 1000
a.out - 1010
a.out - 1020

#二、 getxxxid

#include <unistd.h>

getpid	- 獲取進程ID
getppid	- 獲取父進程ID
getuid	- 獲取實際用戶ID
geteuid	- 獲取有效用戶ID
getgid	- 獲取實際組ID
getegid	- 獲取有效組ID

假設a.out文件的屬主和屬組都是root。以其它用戶身份登錄並執行
$ a.out
輸出
進程ID:…
父進程ID:…
實際用戶ID:1000 - 實際用戶ID取父進程(shell)的實際用戶ID
有效用戶ID:1000 - 有效用戶ID取實際用戶ID
實際組ID:1000 - 實際組ID取父進程(shell)的實際組ID
有效組ID:1000 - 有效組ID取實際組ID

執行
# ls -l a.out
輸出
-rwxr-xr-x. 1 root root …
^ ^

爲a.out的文件權限添加設置用戶ID位和設置組ID位
# chmod u+s a.out
# chmod g+s a.out

執行
# ls -l a.out
輸出
-rwsr-sr-x. 1 root root …
^ ^

以其它用戶身份登錄並執行
$ a.out
輸出
進程ID:…
父進程ID:…
實際用戶ID:1000 - 實際用戶ID取父進程(shell)的實際用戶ID
有效用戶ID:0 - 有效用戶ID取程序文件的屬主ID
實際組ID:1000 - 實際組ID取父進程(shell)的實際組ID
有效組ID:0 - 有效組ID取程序文件的屬組ID

進程的訪問權限由其有效用戶ID和有效組ID決定。
通過此方法可以使進程獲得比登錄用戶更高的權限。
比如通過passwd命令修改登錄口令。

執行
ls -l /etc/passwd
輸出
-rw-r–r--. 1 root root 1648 Nov 9 14:05 /etc/passwd
^
該文件中存放所有用戶的口令信息,僅root用戶可寫,
但事實上任何用戶都可以修改自己的登錄口令,
即任何用戶都可以通過/usr/bin/passwd程序寫該文件。

執行
# ls -l /usr/bin/passwd
輸出
-rwsr-xr-x. 1 root root 28816 Feb 8 2011 /usr/bin/passwd
^ ^
該程序具有設置用戶ID位,且其屬主爲root。
因此以任何用戶登錄系統,執行passwd命令所啓動的進程,
其有效用戶ID均爲root,對/etc/passwd文件有寫權限。

三、 fork


#include <unistd.h>

pid_t fork (void);

  1. 創建一個子進程,失敗返回-1。

  2. 調用一次,返回兩次。
    分別在父子進程中返回子進程的PID和0。
    利用返回值的不同,
    可以分別爲父子進程編寫不同的處理分支。

  3. 子進程是父進程的副本,
    子進程獲得父進程數據段和堆棧段(包括I/O流緩衝區)的拷貝,
    但子進程共享父進程的代碼段。

  4. 函數調用後父子進程各自繼續運行,
    其先後順序不確定。
    某些實現可以保證子進程先被調度。

  5. 函數調用後,
    父進程的文件描述符表(進程級)也會被複制到子進程中,
    二者共享同一個文件表(內核級)。

###圖示 :ftab
這裏寫圖片描述

  1. 總進程數或實際用戶ID所擁有的進程數,
    超過系統限制,該函數將失敗。

  2. 一個進程如果希望創建自己的副本並執行同一份代碼,
    或希望與另一個程序併發地運行,都可以使用該函數。

  3. 孤兒進程與殭屍進程。

注意:fork之前的代碼只有父進程執行,
fork之後的代碼父子進程都有機會執行,
受代碼邏輯的控制而進入不同分支。

#四、 vfork

#include < unistd.h >

pid_t vfork (void);

該函數的功能與fork基本相同,二者的區別:

  1. 調用vfork創建子進程時並不複製父進程的地址空間,
    子進程可以通過exec函數族,
    直接啓動另一個進程替換自身,
    進而提高進程創建的效率。

  2. vfork調用之後,子進程先被調度。

#五、 進程的正常退出

  1. 從main函數中return。
int main (...) {
    ...
    return x;
}

等價於:

int main (...) {
    ...
    exit (x);
}
  1. 調用標準C語言的exit函數。

#include < stdlib.h >

void exit (int status);

  1. 調用進程退出,
    其父進程調用wait/waitpid函數返回status的低8位。

  2. 進程退出之前,
    先調用所有事先通過atexit/on_exit函數註冊的函數,
    沖刷並關閉所有仍處於打開狀態的標準I/O流,
    刪除所有通過tmpfile函數創建的文件。

#include <stdlib.h>

int atexit (void (*function) (void));

function - 函數指針,
指向進程退出前需要被調用的函數。
該函數既沒有返回值也沒有參數。

成功返回0,失敗返回非零。

int on_exit (void (function) (int, void), void* arg);

function - 函數指針,
指向進程退出前需要被調用的函數。
該函數沒有返回值但有兩個參數:
第一參數來自exit函數的status參數,
第二個參數來自on_exit函數的arg參數。

arg - 任意指針,
將作爲第二個參數被傳遞給function所指向的函數。

成功返回0,失敗返回非零。

  1. 用EXIT_SUCCESS/EXIT_FAILURE常量宏
    (可能是0/1)作參數,調用exit()函數表示成功/失敗,
    提高平臺兼容性。

  2. 該函數不會返回。

  3. 該函數的實現調用了_exit/_Exit函數。

  1. 調用_exit/_Exit函數。

#include < unistd.h >

void _exit (int status);

  1. 調用進程退出,
    其父進程調用wait/waitpid函數返回status的低8位。

  2. 進程退出之前,
    先關閉所有仍處於打開狀態的文件描述符,
    將其所有子進程託付給init進程(PID爲1的進程)收養,
    向父進程遞送SIGCHILD信號。

  3. 該函數不會返回。

  4. 該函數有一個完全等價的標準C版本:

#include <stdlib.h>

void _Exit (int status);

  1. 進程的最後一個線程執行了返回語句。

  2. 進程的最後一個線程調用pthread_exit函數。

###圖示 :exit
這裏寫圖片描述


#六、 進程的異常終止

  1. 調用abort函數,產生SIGABRT信號。

  2. 進程接收到某些信號。

  3. 最後一個線程對“取消”請求做出響應。

#七、 wait / waitpid

等待子進程終止並獲取其終止狀態。

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

pid_t wait (int* status);

pid_t waitpid (pid_t pid, int* status, int options);

成功返回終止子進程的PID,失敗返回-1。

  1. 當一個進程正常或異常終止時,
    內核向其父進程發送SIGCHLD信號。
    父進程可以忽略該信號,
    或者提供一個針對該信號的信號處理函數,默認爲忽略。

  2. 父進程調用wait函數:

  1. 若所有子進程都在運行,則阻塞。

  2. 若有一個子進程已終止,
    則返回該子進程的PID和終止狀態(通過status參數)。

  3. 若沒有需要等待子進程,則返回失敗,errno爲ECHILD。

  1. 在任何一個子進程終止前,wait函數只能阻塞調用進程,
    而waitpid函數可以有更多選擇。

  2. 如果有一個子進程在wait函數被調用之前,
    已經終止並處於殭屍狀態,wait函數會立即返回,
    並取得該子進程的終止狀態。

  3. 子進程的終止狀態通過輸出參數status返回給調用者,
    若不關心終止狀態,可將此參數置空。

  4. 子進程的終止狀態可藉助
    sys/wait.h中定義的參數宏查看:

WIFEXITED(): 子進程是否正常終止,
是則通過WEXITSTATUS()宏,
獲取子進程調用exit/_exit/_Exit函數,
所傳遞參數的低8位。
因此傳給exit/_exit/_Exit函數的參數最好不要超過255。

WIFSIGNALED(): 子進程是否異常終止,
是則通過WTERMSIG()宏獲取終止子進程的信號。

WIFSTOPPED(): 子進程是否處於暫停,
是則通過WSTOPSIG()宏獲取暫停子進程的信號。

WIFCONTINUED(): 子進程是否在暫停之後繼續運行

  1. 如果同時存在多個子進程,又需要等待特定的子進程,
    可使用waitpid函數,其pid參數:

-1 - 等待任一子進程,此時與wait函數等價。

0 - 等待由該參數所標識的特定子進程。

0 - 等待其組ID等於調用進程組ID的任一子進程,
即等待與調用進程同進程組的任一子進程。

<-1 - 等待其組ID等於該參數絕對值的任一子進程,
即等待隸屬於特定進程組內的任一子進程。

  1. waitpid函數的options參數可取0(忽略)或以下值的位或:

WNOHANG - 非阻塞模式,
若沒有可用的子進程狀態,則返回0。

WUNTRACED - 若支持作業控制,且子進程處於暫停態,
則返回其狀態。

WCONTINUED - 若支持作業控制,且子進程暫停後繼續,
則返回其狀態。

八、 exec


  1. exec函數會用新進程完全替代調用進程,
    並開始從main函數執行。

  2. exec函數並非創建子進程,新進程取調用進程的PID。

  3. exec函數所創建的新進程,
    完全取代調用進程的代碼段、數據段和堆棧段。

  4. exec函數若執行成功,則不會返回,否則返回-1。

  5. exec函數包括六種形式:

#include < unistd.h >

int execl (
const char* path,
const char* arg,

);

int execv (
const char* path,
char* const argv[]
);

int execle (
const char* path,
const char* arg,
…,
char* const envp[]
);

int execve (
const char* path,
char* const argv[],
char* const envp[]
);

int execlp (
const char* file,
const char* arg,

);

int execvp (
const char* file,
char* const argv[]
);

l: 新程序的命令參數以單獨字符串指針的形式傳入
(const char* arg, …),參數表以空指針結束。

v: 新程序的命令參數以字符串指針數組的形式傳入
(char* const argv[]),數組以空指針結束。

e: 新程序的環境變量以字符串指針數組的形式傳入
(char* const envp[]),數組以空指針結束,
無e則從調用進程的environ變量中複製。

p: 若第一個參數中不包含“/”,則將其視爲文件名,
根據PATH環境變量搜索該文件。

九、 system


#include < stdlib.h >

int system (const char* command);

  1. 標準C函數。執行command,
    成功返回command對應進程的終止狀態,失敗返回-1。

  2. 若command取NULL,返回非零表示shell可用,
    返回0表示shell不可用。

  3. 該函數的實現,
    調用了fork、exec和waitpid等函數,
    其返回值:

  1. 如果調用fork或waitpid函數出錯,則返回-1。

  2. 如果調用exec函數出錯,則在子進程中執行exit(127)。

  3. 如果都成功,則返回command對應進程的終止狀態
    (由waitpid的status輸出參數獲得)。

  1. 使用system函數而不用fork+exec的好處是,
    system函數針對各種錯誤和信號都做了必要的處理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章