1. 程式人生 > >linux進程控制函數詳解

linux進程控制函數詳解

不變 path 原則 同時 建立 返回值 關系 改變 完全

進程控制

fork函數

創建一個子進程。

pid_t fork(void); 失敗返回-1;成功返回:① 父進程返回子進程的ID(非負) ②子進程返回 0

pid_t類型表示進程ID,但為了表示-1,它是有符號整型。(0不是有效進程IDinit最小,為1)

註意返回值,不是fork函數能返回兩個值,而是fork後,fork函數變為兩個,父子需【各自】返回一個。

循環創建n個子進程

一次fork函數調用可以創建一個子進程。那麽創建N個子進程應該怎樣實現呢?

簡單想for(i = 0; i < n; i++) { fork() } 即可。但這樣創建的是N個子進程嗎?

技術分享圖片

循環創建N個子進程

從上圖我們可以很清晰的看到n3時候,循環創建了(2^n)-1個子進程,而不是N的子進程。需要在循環的過程,保證子進程不再執行fork ,因此當(fork() == 0)子進程應該立即break;才正確

練習:通過命令行參數指定創建進程的個數,每個進程休眠1S打印自己是第幾個被創建的進程。如:第1個子進程休眠0秒打印:“我是第1個子進程”;第2個進程休眠1秒打印:“我是第2個子進程”;第3個進程休眠2秒打印:“我是第3個子進程”。 fork1.c

通過該練習掌握框架:循環創建n個子進程,使用循環因子

i對創建的子進程加以區分。

getpid函數

獲取當前進程ID

pid_t getpid(void);

getppid函數

獲取當前進程的父進程ID

pid_t getppid(void);

區分一個函數是“系統函數”還是“庫函數”依據:

是否訪問內核數據結構

② 是否訪問外部硬件資源 二者有任一 → 系統函數;二者均無 → 庫函數

getuid函數

獲取當前進程實際用戶ID

uid_t getuid(void);

獲取當前進程有效用戶ID

uid_t geteuid(void);

getgid函數

獲取當前進程使用用戶組ID

gid_t getgid(void);

獲取當前進程有效用戶組ID

gid_t getegid(void);

進程共享

父子進程之間在fork後。有哪些相同,那些相異之處呢?

fork之後:

父子相同處: 全局變量、.data.text、棧、堆、環境變量、用戶ID、宿主目錄、進程工作目錄、信號處理方式...

父子不同處: 1.進程ID 2.fork返回值 3.父進程ID 4.進程運行時間 5.鬧鐘(定時器) 6.未決信號集

似乎,子進程復制了父進程0-3G用戶空間內容,以及父進程的PCB,但pid不同。真的每fork一個子進程都要將父進程的0-3G地址空間完全拷貝一份,然後在映射至物理內存嗎?

當然不是!父子進程間遵循讀時共享寫時復制的原則。這樣設計,無論子進程執行父進程的邏輯還是執行自己的邏輯都能節省內存開銷。

練習:編寫程序測試,父子進程是否共享全局變。 fork_shared.c

重點註意!躲避父子進程共享全局變量的知識誤區!

【重點】:父子進程共享:1. 文件描述符(打開文件的結構體) 2. mmap建立的映射區 (進程間通信詳解)

特別的,fork之後父進程先執行還是子進程先執行不確定。取決於內核所使用的調度算法。

gdb調試

使用gdb調試的時候,gdb只能跟蹤一個進程。可以在fork函數調用之前,通過指令設置gdb調試工具跟蹤父進程或者是跟蹤子進程。默認跟蹤父進程。

set follow-fork-mode child 命令設置gdbfork之後跟蹤子進程。

set follow-fork-mode parent 設置跟蹤父進程。

註意,一定要在fork函數調用之前設置才有效。 follow_fork.c

exec函數族

fork創建子進程後執行的是和父進程相同的程序(但有可能執行不同的代碼分支),子進程往往要調用一種exec函數以執行另一個程序。當進程調用一種exec函數時,該進程的用戶空間代碼和數據完全被新程序替換,從新程序的啟動例程開始執行。調用exec並不創建新進程,所以調用exec前後該進程的id並未改變。

將當前進程的.text.data替換為所要加載的程序的.text.data,然後讓進程從新的.text第一條指令開始執行,但進程ID不變,換核不換殼。

其實有六種以exec開頭的函數,統稱exec函數:

int execl(const char *path, const char *arg, ...);

int execlp(const char *file, const char *arg, ...);

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

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

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

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

execlp函數

加載一個進程,借助PATH環境變量

int execlp(const char *file, const char *arg, ...); 成功:無返回;失敗:-1

參數1:要加載的程序的名字。該函數需要配合PATH環境變量來使用,當PATH中所有目錄搜索後沒有參數1則出錯返回。

該函數通常用來調用系統程序。如:lsdatecpcat等命令。

execl函數

加載一個進程, 通過 路徑+程序名 來加載。

int execl(const char *path, const char *arg, ...); 成功:無返回;失敗:-1

對比execlp,如加載"ls"命令帶有-l-F參數

execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在PATH中搜索。

execl("/bin/ls", "ls", "-l", "-F", NULL); 使用參數1給出的絕對路徑搜索。

execvp函數

加載一個進程,使用自定義環境變量env

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

變參形式:... argv[] (main函數也是變參函數,形式上等同於 int main(int argc, char *argv0, ...))

變參終止條件:NULL結尾 ② 固參指定

execvpexeclp參數形式不同,原理一致。

練習:將當前系統中的進程信息,打印到文件中。 exec_ps.c

exec函數族一般規律

exec函數一旦調用成功即執行新的程序,不返回。只有失敗才返回,錯誤值-1。所以通常我們直接在exec函數調用後直接調用perror()exit(),無需if判斷。

l (list) 命令行參數列表

p (path) 搜素file時使用path變量

v (vector) 使用命令行參數數組

e (environment) 使用環境變量數組,不使用進程原有的環境變量,設置新加載程序運行的環境變量

事實上,只有execve是真正的系統調用,其它五個函數最終都調用execve,所以execveman手冊第2節,其它函數在man手冊第3節。這些函數之間的關系如下圖所示。

技術分享圖片

exec函數族

回收子進程

孤兒進程

孤兒進程: 父進程先於子進程結束,則子進程成為孤兒進程,子進程的父進程成為init進程,稱為init進程領養孤兒進程。

orphan.c

僵屍進程

僵屍進程: 進程終止,父進程尚未回收,子進程殘留資源(PCB)存放於內核中,變成僵屍(Zombie)進程。

zoom .c

特別註意,僵屍進程是不能使用kill命令清除掉的。因為kill命令只是用來終止進程的,而僵屍進程已經終止。思考!用什麽辦法可清除掉僵屍進程呢?

wait函數

一個進程在終止時會關閉所有文件描述符,釋放在用戶空間分配的內存,但它的PCB還保留著,內核在其中保存了一些信息:如果是正常終止則保存著退出狀態,如果是異常終止則保存著導致該進程終止的信號是哪個。這個進程的父進程可以調用waitwaitpid獲取這些信息,然後徹底清除掉這個進程。我們知道一個進程的退出狀態可以在Shell中用特殊變量$?查看,因為Shell是它的父進程,當它終止時Shell調用waitwaitpid得到它的退出狀態同時徹底清除掉這個進程。

父進程調用wait函數可以回收子進程終止信息該函數有三個功能

① 阻塞等待子進程退出

② 回收子進程殘留資源

③ 獲取子進程結束狀態(退出原因)

pid_t wait(int *status); 成功:清理掉的子進程ID;失敗:-1 (沒有子進程)

當進程終止時,操作系統的隱式回收機制會:1.關閉所有文件描述符 2. 釋放用戶空間分配的內存。內核的PCB仍存在。其中保存該進程的退出狀態。(正常終止→退出值;異常終止→終止信號)

可使用wait函數傳出參數status來保存進程的退出狀態。借助宏函數來進一步判斷進程終止的具體原因。宏函數可分為如下三組:

1. WIFEXITED(status) 為非0 → 進程正常結束

WEXITSTATUS(status) 如上宏為真,使用此宏 → 獲取進程退出狀態 (exit的參數)

2. WIFSIGNALED(status) 為非0 → 進程異常終止

WTERMSIG(status) 如上宏為真,使用此宏 → 取得使進程終止的那個信號的編號。

*3. WIFSTOPPED(status) 為非0 → 進程處於暫停狀態

WSTOPSIG(status) 如上宏為真,使用此宏 → 取得使進程暫停的那個信號的編號。

WIFCONTINUED(status) 為真 → 進程暫停後已經繼續運行

wait1.cwait2.c

waitpid函數

作用同wait,但可指定pid進程清理,可以不阻塞。

pid_t waitpid(pid_t pid, int *status, in options);成功:返回清理掉的子進程ID;失敗:-1(無子進程)

特殊參數和返回情況

參數pid:

> 0 回收指定ID的子進程

-1 回收任意子進程(相當於wait

0 回收和當前調用waitpid一個組的所有子進程

< -1 回收指定進程組內的任意子進程

返回0:參3WNOHANG,且子進程正在運行。

註意:一次waitwaitpid調用只能清理一個子進程,清理多個子進程應使用循環。 waitpid.c

作業:父進程fork 3 個子進程,三個子進程一個調用ps命令, 一個調用自定義程序1(正常),一個調用自定義程序2(會出段錯誤)。父進程使用waitpid對其子進程進行回收。

linux進程控制函數詳解