進程管理
#一、 基本概念
1. 進程與程序
1) 進程就是運行中的程序。一個運行着的程序,
可能有多個進程。進程在操作系統中執行特定的任務。
2) 程序是存儲在磁盤上,
包含可執行機器指令和數據的靜態實體。
進程或者任務是處於活動狀態的計算機程序。
## 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)
-
每個進程都有一個以非負整數表示的唯一標識,
即進程ID/PID。 -
進程ID在任何時刻都是唯一的,但可以重用,
當一個進程退出時,其進程ID就可以被其它進程使用。 -
延遲重用。
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。
-
調用一次,返回兩次。
分別在父子進程中返回子進程的PID和0。
利用返回值的不同,
可以分別爲父子進程編寫不同的處理分支。 -
子進程是父進程的副本,
子進程獲得父進程數據段和堆棧段(包括I/O流緩衝區)的拷貝,
但子進程共享父進程的代碼段。 -
函數調用後父子進程各自繼續運行,
其先後順序不確定。
某些實現可以保證子進程先被調度。 -
函數調用後,
父進程的文件描述符表(進程級)也會被複制到子進程中,
二者共享同一個文件表(內核級)。
###圖示 :ftab
-
總進程數或實際用戶ID所擁有的進程數,
超過系統限制,該函數將失敗。 -
一個進程如果希望創建自己的副本並執行同一份代碼,
或希望與另一個程序併發地運行,都可以使用該函數。 -
孤兒進程與殭屍進程。
注意:fork之前的代碼只有父進程執行,
fork之後的代碼父子進程都有機會執行,
受代碼邏輯的控制而進入不同分支。
#四、 vfork
#include < unistd.h >
pid_t vfork (void);
該函數的功能與fork基本相同,二者的區別:
-
調用vfork創建子進程時並不複製父進程的地址空間,
子進程可以通過exec函數族,
直接啓動另一個進程替換自身,
進而提高進程創建的效率。 -
vfork調用之後,子進程先被調度。
#五、 進程的正常退出
- 從main函數中return。
int main (...) {
...
return x;
}
等價於:
int main (...) {
...
exit (x);
}
- 調用標準C語言的exit函數。
#include < stdlib.h >
void exit (int status);
-
調用進程退出,
其父進程調用wait/waitpid函數返回status的低8位。 -
進程退出之前,
先調用所有事先通過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,失敗返回非零。
-
用EXIT_SUCCESS/EXIT_FAILURE常量宏
(可能是0/1)作參數,調用exit()函數表示成功/失敗,
提高平臺兼容性。 -
該函數不會返回。
-
該函數的實現調用了_exit/_Exit函數。
- 調用_exit/_Exit函數。
#include < unistd.h >
void _exit (int status);
-
調用進程退出,
其父進程調用wait/waitpid函數返回status的低8位。 -
進程退出之前,
先關閉所有仍處於打開狀態的文件描述符,
將其所有子進程託付給init進程(PID爲1的進程)收養,
向父進程遞送SIGCHILD信號。 -
該函數不會返回。
-
該函數有一個完全等價的標準C版本:
#include <stdlib.h>
void _Exit (int status);
-
進程的最後一個線程執行了返回語句。
-
進程的最後一個線程調用pthread_exit函數。
###圖示 :exit
#六、 進程的異常終止
-
調用abort函數,產生SIGABRT信號。
-
進程接收到某些信號。
-
最後一個線程對“取消”請求做出響應。
#七、 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。
-
當一個進程正常或異常終止時,
內核向其父進程發送SIGCHLD信號。
父進程可以忽略該信號,
或者提供一個針對該信號的信號處理函數,默認爲忽略。 -
父進程調用wait函數:
-
若所有子進程都在運行,則阻塞。
-
若有一個子進程已終止,
則返回該子進程的PID和終止狀態(通過status參數)。 -
若沒有需要等待子進程,則返回失敗,errno爲ECHILD。
-
在任何一個子進程終止前,wait函數只能阻塞調用進程,
而waitpid函數可以有更多選擇。 -
如果有一個子進程在wait函數被調用之前,
已經終止並處於殭屍狀態,wait函數會立即返回,
並取得該子進程的終止狀態。 -
子進程的終止狀態通過輸出參數status返回給調用者,
若不關心終止狀態,可將此參數置空。 -
子進程的終止狀態可藉助
sys/wait.h中定義的參數宏查看:
WIFEXITED(): 子進程是否正常終止,
是則通過WEXITSTATUS()宏,
獲取子進程調用exit/_exit/_Exit函數,
所傳遞參數的低8位。
因此傳給exit/_exit/_Exit函數的參數最好不要超過255。
WIFSIGNALED(): 子進程是否異常終止,
是則通過WTERMSIG()宏獲取終止子進程的信號。
WIFSTOPPED(): 子進程是否處於暫停,
是則通過WSTOPSIG()宏獲取暫停子進程的信號。
WIFCONTINUED(): 子進程是否在暫停之後繼續運行
- 如果同時存在多個子進程,又需要等待特定的子進程,
可使用waitpid函數,其pid參數:
-1 - 等待任一子進程,此時與wait函數等價。
0 - 等待由該參數所標識的特定子進程。
0 - 等待其組ID等於調用進程組ID的任一子進程,
即等待與調用進程同進程組的任一子進程。
<-1 - 等待其組ID等於該參數絕對值的任一子進程,
即等待隸屬於特定進程組內的任一子進程。
- waitpid函數的options參數可取0(忽略)或以下值的位或:
WNOHANG - 非阻塞模式,
若沒有可用的子進程狀態,則返回0。
WUNTRACED - 若支持作業控制,且子進程處於暫停態,
則返回其狀態。
WCONTINUED - 若支持作業控制,且子進程暫停後繼續,
則返回其狀態。
八、 exec
-
exec函數會用新進程完全替代調用進程,
並開始從main函數執行。 -
exec函數並非創建子進程,新進程取調用進程的PID。
-
exec函數所創建的新進程,
完全取代調用進程的代碼段、數據段和堆棧段。 -
exec函數若執行成功,則不會返回,否則返回-1。
-
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);
-
標準C函數。執行command,
成功返回command對應進程的終止狀態,失敗返回-1。 -
若command取NULL,返回非零表示shell可用,
返回0表示shell不可用。 -
該函數的實現,
調用了fork、exec和waitpid等函數,
其返回值:
-
如果調用fork或waitpid函數出錯,則返回-1。
-
如果調用exec函數出錯,則在子進程中執行exit(127)。
-
如果都成功,則返回command對應進程的終止狀態
(由waitpid的status輸出參數獲得)。
- 使用system函數而不用fork+exec的好處是,
system函數針對各種錯誤和信號都做了必要的處理。