Linux中進程控制是編寫Linux(或Unix)程序時必備的技能,這篇文章概述了Linux中與進程控制相關的函數調用。Linux手冊中詳細地描述了每個系統調用,感興趣的話可以參考。
獲取進程ID
Linux中每個進程都有一個唯一的正數進程ID,通過getpid
以及getppid
可以得到調用進程的PID以及其父進程的PID。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
// 返回:調用者或其父進程的PID
終止進程
進程可以調用exit
函數終止自己,status
表示進程的退出狀態,從主程序返回一個數值同樣可以設置程序的退出狀態。
#include <stdlib.h>
exit(int status);
// 該函數不返回
創建進程
父進程通過調用fork
函數創建一個新的運行的子進程。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
// 返回:子進程返回0,父進程返回子進程的PID,如果出錯,則爲-1
fork
函數創建出的新進程會得到與父進程相同的用戶級虛擬地址空間,包括代碼、數據段、堆、共享庫以及用戶佔,也會獲得相同的打開的文件描述符的副本。fork
函數也非常令人迷惑,它只被調用一次,卻返回兩次,一次在父進程中(返回子進程PID),一次在子進程中(返回0)。因爲父進程中的返回值總是不爲0(子進程PID或-1),因此可以以此判斷程序是在父進程還是子進程中執行。調用fork
的程序往往有如下特點:
- 調用一次,返回兩次
- 併發執行
- 相同但是獨立的地址空間
- 共享文件
另外,子進程中的fork
依然會創建子進程(即子進程的子進程):
int main() {
printf("hello world!\n"); // only one process
fork();
printf("hello world2!\n"); // tow processes now
fork();
printf("hello world3!\n"); // four processes now
}
上述代碼中,第一個fork
被父進程執行,創建了一個子進程CHILD_1;第二個fork
被父進程和子進程CHILD_1執行,創建了子進程CHILD_2以及子進程CHILD_3,因此共創建了三個子進程,加上一個父進程共四個進程。其輸出結果如下:
hello world!
hello world2!
hello world2!
hello world3!
hello world3!
hello world3!
hello world3!
回收子進程
終止的子進程需要被父進程回收,否則會成爲僵死(zombie)進程。父進程終止時,init
進程(所有進程的祖先)會成爲其未結束或回收的孤兒進程的養父。若父進程未回收其僵死進程就終止,那麼init
進程會回收它。即便僵死進程沒有運行,它也會消耗系統資源,因此長時間運行的程序總是應當回收它的僵死進程。一個進程通過調用waitpid
函數燈帶它的子進程終止或停止:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statusp, int options);
pid_t wait(int *statusp); // 等價於 waitpid(-1, &status, 0)
// 返回:若成功回收,則爲子進程PID,如果 WNOHANG,則爲0,其它錯誤,則爲-1
一般情況(options
爲0)下,父進程會掛起,直到其等待集合(wait set)中的某個子進程終止,並返回其PID;若等待集合中的某個子進程已經停止,那麼父進程會立即返回其PID。返回後,子進程PID已經被回收,內核會刪除該子進程的痕跡。waitpid
的三個參數較爲複雜,可以參考waitpid獲得更爲詳細的信息:
- pid 指定等待集合成員
- pid>0,等待集合是一個PID=pid的子進程
- pid=-1,等待集合是其所有子進程
- statusp 檢查已回收子進程的退出狀態,包括
WIFEXITED WEXITSTATUS WIFSIGNALED
等多個值,見參考。 - options 可以用來修改函數行爲,這些選項可以通過或運算組合起來
- WNOHANG 若等待集合中的任何子進程都未終止,那麼立即返回
- WUNTRACED 掛起調用進程,若等待集合中的一個進程終止或停止,則返回其PID
- WCONTINUED 掛起調用進程,直到等待集合中一個正在運行的進程終止或等待集合中一個被停止的進程收到
SIGCONT
信號重新執行。
休眠進程
想必大家都見過長得差不多的函數:
#include <unistd.h>
unsigned int sleep(unsigned int secs);
// 返回:剩下的要休眠的秒數
int pause(void);
// 返回:-1
sleep
函數會掛起當前進程直到時間到或者被信號中斷,而pause
則掛起進程直到進程收到一個信號。
加載並運行程序
execve
函數在當前進程上下文加載並運行一個新程序,與fork
不一樣,該函數調用成功後從不返回:
#include <unistd.h>
int execve(const char *filename, const char *argv[], const char *envp[]);
// 如果成功,則不返回,如果錯誤,返回-1
參數中的argv
表示待執行程序的命令行參數,envp
表示環境變量,與waitpid
一樣,該函數也有許多版本,詳細信息可以參考(execve)[http://man7.org/linux/man-pages/man2/execve.2.html]。如下代碼展示了使用fork
在子進程中通過execve
調用linux下ps
命令(程序未包含錯誤處理,僅供參考):
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t pid;
printf("this process will run ps -ef in child process:\n");
if ((pid = fork()) == 0) {
char *nargv[] = {"/bin/ps", "-ef", NULL};
char *nenv[] = {NULL};
execve("/bin/ps", nargv, nenv);
// execve不返回,控制流不會到這裏
printf("!!!this printf will not run!!!");
}
// 休眠等待子進程運行
sleep(2);
if (pid == waitpid(-1, NULL, 0))
printf("child process: %ld recycled, exit now...\n", (long)pid);
return 0;
}
上述程序的輸出:
this process will run ps -ef in child process:
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 21:31 ? 00:00:00 /init
root 7 1 0 21:31 tty1 00:00:00 /init
eric 8 7 0 21:31 tty1 00:00:00 -bash
eric 94 8 0 22:40 tty1 00:00:00 ./a.sh
eric 95 94 0 22:40 tty1 00:00:00 /bin/ps -ef
child process: 95 recycled, exit now...
可以看到,pid爲94的進程創建了子進程95,並在子進程中運行了/bin/ps -ef
命令打印出了系統中的進程信息。
總結
本文講述了linux中進程控制較爲基本的函數,這些函數能夠用來創建、休眠、終止、回收進程等。進程控制往往需要與信號(signal)以及進程間通信(IPC InterProcess Communication)如消息隊列等配合使用,相關博客也會在接下來的學習過程中寫一寫。