fork函數實現進程複製,相似於動物界的單性繁殖,fork函數直接建立一個子進程。這是Linux建立進程最經常使用的方法。在這一小節中,子進程概念指fork產生的進程,父進程指主動調用fork的進程。程序員
fork後,子進程繼承了父進程不少屬性,包括:shell
文件描述符:至關與dup,標準輸入標準輸出標準錯誤三個文件數組
帳戶/組ID:session
進程組ID多線程
會話ID函數
控制終端this
set-user-ID和set-group-ID標記spa
當前工做目錄命令行
根目錄線程
umask
信號掩碼
文件描述符的close-on-exec標記
環境變量
共享內存
內存映射
資源限制
可是也有一些不一樣,包括:
fork返回值
進程ID
父進程
進程運行時間記錄,在子進程中被清0
文件鎖沒有繼承
鬧鐘
信號集合
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { printf("before fork\n"); // 在父進程中打開的文件描述符 // int fd = open("a.txt", O_RDWR|O_CREAT, 0777); // FILE* fp = fopen("a.txt", "r"); int fd = open("a.txt", O_RDWR); pid_t pid = fork(); // 建立一個新進程 if(pid == 0) { // 子進程可使用父進程的描述符 // write(fd, "hello", 5); // char ch = fgetc(fp); char ch; read(fd, &ch, 1); printf("ch is %c\n", ch); printf("this is in child, ppid=%d\n", (int)getppid()); } else if(pid > 0) { // write(fd, "world", 5); char ch; read(fd, &ch, 1); printf("ch is %c\n", ch); // 當fork返回值大於0時,說明該進程是父進程 // 此時,返回值就是子進程的pid printf("this is in parent, pid=%d\n", (int)getpid()); } else { printf("error fork\n"); } printf("hello fork\n"); }
#include <stdio.h> #include <unistd.h> int global_var = 0;//fork()出來的子進程的值改變,不會影響父進程 由於開開闢了新的空間 int main() { int var = 0; int* p = (int*)malloc(sizeof(int)); *p = 0; pid_t pid = fork(); if(pid == 0) { global_var = 100; *p = 100; var = 100; printf("set var\n"); } else if(pid > 0) { sleep(1); // 肯定的結果,就是0 printf("%d\n", global_var); printf("var is %d\n", var); // 0 printf("*p = %d\n", *p); } printf("hello world\n"); }
#include <stdio.h> #include <unistd.h> void forkn(int n) { int i; for(i=0; i<n; ++i) { pid_t pid = fork(); if(pid == 0) break; } } int main() { forkn(10); printf("hello world\n"); }
進程有許多終止方法:
方法 | |
---|---|
main函數return | 正常退出 |
調用exit或者_Exit或者_exit | 正常退出 |
在多線程程序中,最後一個線程例程結束 | 正常退出 |
在多線程程序中,最後一個線程調用pthread_exit | 正常退出 |
調用abort | 異常退出 |
收到信號退出 | 異常退出 |
多線程程序中,最後一個線程響應pthread_cancel | 異常退出 |
當進程退出時,內核會爲進程清除它申請的內存,這裏的內存是指物理內存,好比棧空間、堆、代碼段、數據段等,而且關閉全部文件描述符。
通常來講,進程退出時,須要告訴父親進程退出的結果,若是是正常退出,那麼這個結果保存在內核的PCB中。若是是異常退出,那麼PCB中保存退出結果的字段,是一個不肯定的值。所以程序員應該避免程序的異常退出。
進程退出時,除了它的PCB所佔內存,其餘資源都會清除。
一個進程終止後,其實這個進程的痕跡尚未徹底被清除,由於還有一個PCB在內核中,若是不回收,那麼會致使內存泄漏。父進程能夠調用wait函數來回收子進程PCB,並獲得子進程的結果。
wait
是一個阻塞調用,它的條件是一個子進程退出或者一個子進程有狀態變化。wait
獲得的status,包含了子進程的狀態變化緣由和退出碼信息等等。
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main() { pid_t pid = fork(); if(pid == 0) { sleep(1); printf("child process\n"); return 18; } else if(pid > 0) { printf("parent process\n"); // 等待子進程結束,而且回收子進程的PCB int status; wait(&status); // 如何獲得子進程的返回值 if(WIFEXITED(status)) { printf("normal child process exit\n"); // 正常退出 int code =WEXITSTATUS(status); printf("code is %d\n", code); } else if(WIFSIGNALED(status)) { printf("signal\n"); } else if(WIFSTOPPED(status)) { printf("child stopped\n"); } else if(WIFCONTINUED(status)) { printf("child continue...\n"); } printf("after wait\n"); } return 0; }
wait和waitpid可能會阻塞父進程,因此通常使用SIGCHLD信號來監控子進程
是指已經退出的進程,可是父進程沒有調用wait回收的子進程。殭屍進程沒有任何做用,惟一的反作用就是內存泄漏。若是父進程退出,那麼它的全部殭屍兒子會獲得清理,所以殭屍進程通常指那些用不停歇的後臺服務進程的殭屍兒子。
程序員應該避免殭屍進程的產生。
#include <stdio.h> #include <unistd.h> int main() { pid_t pid = fork(); if(pid == 0) { // 子進程什麼事兒都不幹,退出了,此時子進程是殭屍進程 } else if(pid > 0) { getchar(); // 父進程不退出 } return 0; }
父進程退出了,而子進程沒有退出,那麼子進程就成了沒有父親的孤兒進程。孤兒進程不會在系統中出現很長時間,由於系統一旦發現孤兒進程,就會將其父進程設置爲init進程。那麼未來該進程的回收,由init來負責。
exec函數執行一個進程,當一個進程調用exec後,調用該函數的進程的虛擬地址空間的代碼段、數據段、堆、棧被釋放,替換成新進程的代碼段、數據段、堆、棧,而PCB依舊使用以前進程的PCB。這個函數用中文來講就是鳩佔鵲巢。
exec後使用的是同一個PCB,因此exec以後和以前,由不少進程屬性是相同的,包括:
進程ID和父進程ID
帳戶相關
進程組相關
定時器
當前目錄和根目錄
umask
文件鎖
信號mask
未決的信號
資源限制
進程優先級
進程時間
沒有close-on-exec屬性的文件描述符
使用fork和exec來執行一個新程序
#include <unistd.h> #include <stdio.h> // execle, e表示環境變量environ // int main(int argc, char* argv[]) { char* args[] = { "/bin/ls", "-a", "-l", NULL }; execv("/bin/ls", args); } int main2(int argc, char* argv[]) { // p表示在PATH的環境變量中尋找這個程序 execlp("ls", "ls", NULL); } int main1(int argc, char* argv[]) { // 執行一個程序 execl("/bin/ls", "/bin/ls", "-a", "-l", NULL); // 該函數不會被執行 printf("hello world\n"); }
#include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <unistd.h> int main() { // fd is 3 int fd = open("exec.txt", O_RDWR|O_CREAT|O_CLOEXEC, 0777); execl("./exec_test", "./exec_test", NULL); }
函數後綴 | 解析 |
---|---|
l | list 用不定參數列表來表示命令參數,若是用不定參數列表,那麼用NULL表示結束 |
v | vector 用數組來傳遞命令行參數 |
p | path 表示程序使用程序名便可,在$PATH中搜索該程序,不帶p的須要提供全路徑 |
e | environ 表示環境變量 |
不定參數函數定義:
int main() { int a = add(3, 12, 13, 14); int b = add(2, 12, 13); int c = add(4, 12, 13, 14, 15); printf("%d, %d, %d\n", a, b, c); char* p = concat("abc", "bcd", NULL); printf("p is %s\n", p); // 最後的NULL,被稱之爲哨兵 p = concat("aaaa", "bbbb", "cccc", NULL); printf("p is %s\n", p); }
#include <stdio.h> #include <fcntl.h> #include <sys/types.h> // 若是沒有__VA_ARGS__不帶##,表示__VA_ARGS__至少要表示一個參數 // #define mylog(fmt, ...) printf("[%s:%d] "fmt, __FILE__, __LINE__, __VA_ARGS__) // __VA_ARGS__若是有##,表示能夠沒有參數 #define mylog(fmt, ...) printf("[%s:%d] "fmt, __FILE__, __LINE__, ##__VA_ARGS__) int main() { int fd = open("a.txt", O_RDWR); if(fd < 0) { mylog("error open file\n"); } }
#include <stdio.h> // 轉字符串 abc "abc" #define STR(a) #a // 拼接標識符 #define CC(a, b) a##b int main() { int abcxyz = 100; printf("%d\n", CC(abc, xyz)); }
在Linux系統中,進程間除了有父子關係,還有組關係、Session關係、進程和終端進程關係。設計這些關係是爲了更好的管理進程。
一次登錄算一個session,exit命令能夠退出session,session包括多個進程組,一旦session領導退出,那麼一個session內全部進程退出(它的全部進程收到一個信號)。
#include <unistd.h> int main() { pid_t pid = fork(); if(pid == 0) { // 獨立一個session setsid(); } while(1) { sleep(1); } }
在終端執行進程,就會生成一個進程組。執行的進程fork以後,子進程和父進程在一個組中。
進程組長退出後,進程組的其餘進程的組號依舊沒有變化。
使用-job
定義進程數量,加速文件拷貝。
#include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> // ls // mkdir aaa // cp ../aa bb // cd void handle_cmd(char* cmd) { char* args[1024]; char* p = strtok(cmd, " "); int i = 0; while(p) { args[i++] = p; p = strtok(NULL, " "); } args[i] = NULL; // 表示參數結束位置 if(strcmp(args[0], "cd") == 0) { // 切換當前目錄 chdir(args[1]); return; } pid_t pid = fork(); if(pid == 0) { execvp(args[0], args); // 若是命令執行失敗,應該讓子進程退出 printf("invalid command\n"); exit(0); } else { wait(NULL); } } int main() { while(1) { printf("myshell> "); // 等待用戶輸入 char buf[4096]; fgets(buf, sizeof(buf), stdin); buf[strlen(buf)-1] = 0; // remove \n if(strlen(buf) == 0) { continue; } handle_cmd(buf); } }
fork:建立子進程exec:執行新的程序wait/waitpid:等待子進程結束,回收子進程PCB內存。va_list:va_start:定義指向不定參數的第一個參數的地址va_arg:從參數列表中獲取一個參數,而且讓指針指向下一個參數va_end:清除ap