程序是平臺相關的二進制文件,只佔用磁盤空間。編寫完程序代碼後,編譯爲可執行的二進制文件便可。web
進程是運行中的程序,佔用 CPU、內存等系統資源。shell
經過 Shell 命令,能夠在終端啓動進程,例如執行 ls
命令:api
併發 concurrent:在一個時間段內,處理的請求總數。個數越多,併發越大。
並行 parallel:任意時刻可以同時處理的請求數。一般跟 CPU 內核數量相關。數組
Linux 的進程有如下 6 種狀態:bash
經過 ps aux
能夠查看當前機器上的進程,其中 STAT 列的第一個字符就是進程的狀態:數據結構
# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.2 190764 2140 ? Ss 2018 12:35 /usr/lib/systemd/systemd --system --deserialize 20 root 2 0.0 0.0 0 0 ? S 2018 0:00 [kthreadd] root 3 0.0 0.0 0 0 ? S 2018 1:24 [ksoftirqd/0]
kill 命令用於結束進程,語法以下:併發
kill [-s signal|-p] [-q sigval] [-a] [--] pid... kill -l [signal]
執行 kill 命令,系統會發送一個 SIGTERM 信號給對應的進程,請求進程正常關閉。SIGTERM 是有可能會被阻塞的。kill -9
命令用於強制殺死進程,系統給對應程序發送的信號是 SIGKILL,即 exit。exit 信號不會被系統阻塞。
示例:異步
kill -9 11235
每一個進程在建立的時候,內核都會爲之分配一個全局惟一的進程號。svg
經過 getpid 函數能夠獲取當前進程的 PID。getppid 函數能夠獲取父進程的 PID。函數
經過 ps aux
能夠查看進程的 PID,資源消耗狀況,經過 ps -ef
能夠查看當前進程及其父進程的 PID。經過 pstree
命令能夠以樹狀關係查看全部進程。
函數原型:
#include <sys/types.h> #include <unistd.h> pid_t getpid(void); pid_t getppid(void);
除了經過 main 函數的第三個參數獲取環境變量,還能夠經過 environ 全局變量或 getenv() 函數來獲取。
getenv 函數原型:
#include <stdlib.h> char *getenv(const char *name); char *secure_getenv(const char *name);
#include <stdio.h> #include <stdlib.h> extern char** environ; int main(int argc, char* argv[], char* env[]) { int i = 0; char* myenv = NULL; while(env[i]) { printf("env[%d] is: %s\n", i, env[i++]); } i = 0; while(environ[i]) puts(environ[i++]); myenv = getenv("PATH"); puts(myenv); return 0; }
PCB(Process Control Block,進程控制塊)是每一個進程都有的數據結構,用於保存進程運行所需的信息,例如文件描述符表。
wait() 函數用來幫助父進程獲取其子進程的退出狀態。當進程退出時,內核爲每個進
程保存了退出狀態信息。若是父進程未調用 wait() 函數,則子進程的退出信息將一直保存在內存中。
Linux 中,每一個進程在退出的時候,能夠釋放用戶區空間,可是沒法釋放進程自己的 PCB 所佔用的內存資源。PCB 必須由父進程釋放。
父進程在建立子進程後退出,子進程變成孤兒進程。爲防止內存泄漏,孤兒進程被 init 進程領養,init 進程變成孤兒進程的父進程。
下面示例中,父進程先退出:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> int main() { pid_t pid = fork(); if (pid == 0) { printf("child pid is: %d, ppid is: %d\n", getpid(), getppid()); sleep(1); printf("child pid is: %d, ppid is: %d\n", getpid(), getppid()); } else if (pid > 0) { sleep(0.5); printf("parent pid is: %d, ppid is: %d\n", getpid(), getppid()); printf("parent exit\n"); } return 0; }
輸出:
parent pid is: 3348, ppid is: 713 parent exit child pid is: 3349, ppid is: 1 child pid is: 3349, ppid is: 1
子進程退出了,父進程一直未調用 wait 或 waitpid 函數,子進程就變成了殭屍進程。
可執行的二進制文件,都是從 main 函數開始執行的。main 函數有 3 種原型定義:
int main(); int main(int argc, char *argv[]); int main(int argc, char *argv[], char *env[]);
參數:
注意,Shell 終端沒法檢測進程是否建立了子進程。在進程執行完畢後,Shell 會當即回到交互狀態,此時若是子進程還在輸出數據,會打印在 Shell 的命令提示符以後。能夠在父進程中 sleep 一下。
Linux 中用 fork() 函數建立新進程,函數原型以下:
#include<unistd.h> pid_t fork(void);
返回值:
成功建立進程時,會對父子進程各返回一次,對父進程返回子進程的 PID,對子進程返回 0。經過條件分支語句能夠分別進行不一樣處理。失敗則返回小於 1 的錯誤碼。
fork 函數執行的時候,會將當前正在運行的進程完整的複製一份,提供給子進程。子進程從 fork 函數以後開始執行。
經過 for 循環,能夠建立多個子進程,只須要在每次 fork 以後判斷若是是子進程則結束循環便可。經過循環下標能夠判斷當前子進程是第幾回建立的:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { int i = 0; int number = 5; pid_t pid; for (i = 0; i < number; i++) { pid = fork(); if (pid == 0) break; } // 子進程 if (i == 0) printf("first process, pid = %d\n", getpid()); if (i == 1) printf("second process, pid = %d\n", getpid()); //... // 父進程 if (i == number) printf("parent process, pid = %d\n", getpid()); return 0; }
進程的終止分爲兩種:
exit 函數原型以下:
#include <stdlib.h> void exit(int status);
exit(0)
等價於 return 0
。
fork 函數能夠複製一份父進程,獲得的子進程跟父進程有徹底同樣的代碼跟數據。以後兩個進程各自執行,互不影響。
實際上咱們一般須要子進程執行不一樣的代碼,這時就須要經過 exec 函數加載代碼段,並跳轉到新代碼段的 main 入口執行。
函數原型:
#include <unistd.h> extern char **environ; 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 execvpe(const char *file, char *const argv[], char *const envp[]);
exec 函數組是在 exec 上加 l、v、p、e 四個後綴造成的,這些函數做用相同,只是在參數列表上存在差異。
返回值:exec 族函數報錯時纔有返回值 -1,不然無返回值。若是執行到後面的代碼,就是出錯了。
示例:
char* myenv[] = {"TEST=666", "HOME=/home/kika", NULL}; execle("home/kika/test", "hello", "world", myenv);
完整示例:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { int pid = fork(); if (pid > 0) { exit(0); } else if (pid == 0) { execle("home/kika/test", "hello", "world", myenv); perror("execle error"); exit(-1); } else { perror("fork error"); } return -1; }
一般,在父進程中調用 wait 函數,能夠查看子進程的退出信息,讓子進程撤單結束。wait 函數是阻塞式的,waitpid 能夠設置爲非阻塞的。父進程根據建立的子進程個數,在循環中經過 wait 函數逐個回收子進程。而 waitpid 函數則能夠經過 PID 等待指定的子進程退出。
wait 函數調用一次只會回收一個子進程。多個子進程須要調用屢次 wait。
函數原型:
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options);
wait 參數:
waitpid 參數:
-1
:等價於 wait,等待任意子進程退出0
:等待組 ID 等於調用進程的組 ID 的任一子進程退出> 0
:等待 PID 等於該數值的進程退出< -1
:等待其組 ID 等於該數值的任一子進程退出返回值:成功時返回退出子進程的 PID,沒有子進程時返回 -1.
建立三個子進程,分別運行自定義程序,shell 程序,未定義程序(段錯誤)。而後在父進程中經過 wait 回收全部子進程,並分別判斷退出緣由:
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/stat.h> #include <errno.h> int main(int argc, char* argv[]) { int num = 3; int i; pid_t pid; for (i = 0; i < 3; i++) { pid = fork(); if (pid == 0) break; } if (i == 0) { execlp("ps", "ps", "aux", NULL); perror("execlp ps error"); exit(1); } else if (i == 1) { execl("/root/test/api/process/myls", "", NULL); perror("execl myls error"); exit(1); } else if (i == 2) { execl("./error", "", NULL); perror("execl ./error"); exit(1); } else { int status; pid_t pid; while (pid = wait(&status) != -1) { printf("children PID is: %d\n", pid); if (WIFEXITED(status)) { printf("return value is: %d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("died by signal: %d\n", WTERMSIG(status)); } } } return 0; }