參考 https://www.cnblogs.com/jackl...html
在Linux中task_struct結構體便是PCB。PCB是進程的惟一標識,PCB由鏈表實現(爲了動態插入和刪除)。
進程建立時,爲該進程生成一個PCB;進程終止時,回收PCB。
PCB包含信息:一、進程狀態(state);二、進程標識信息(uid、gid);三、定時器(time);四、用戶可見寄存器、控制狀態寄存器、棧指針等(tss)
每一個進程都有一個非負的惟一進程ID(PID)。雖然是惟一的,可是PID能夠重用,當一個進程終止後,其餘進程就可使用它的PID了。
PID爲0的進程爲調度進程,該進程是內核的一部分,也稱爲系統進程;PID爲1的進程爲init進程,它是一個普通的用戶進程,可是以超級用戶特權運行;PID爲2的進程是頁守護進程,負責支持虛擬存儲系統的分頁操做。除了PID,每一個進程還有一些其餘的標識符:node
#if defined __USE_XOPEN_EXTENDED || defined __USE_XOPEN2K8 /* Return the session ID of the given process. */ extern __pid_t getsid (__pid_t __pid) __THROW; #endif /* Get the real user ID of the calling process. */ extern __uid_t getuid (void) __THROW; /* Get the effective user ID of the calling process. */ extern __uid_t geteuid (void) __THROW; /* Get the real group ID of the calling process. */ extern __gid_t getgid (void) __THROW; /* Get the effective group ID of the calling process. */ extern __gid_t getegid (void) __THROW;
五種進程之間轉換關係如圖:linux
每一個進程的task_struct和系統空間堆棧存放位置以下:兩個連續的物理頁【《Linux內核源代碼情景分析》271頁】c++
系統堆棧空間不能動態擴展,在設計內核、驅動程序時要避免函數嵌套太深,同時不宜使用太大太多的局部變量,由於局部變量都是存在堆棧中的。程序員
新進程的建立,首先在內存中爲新進程建立一個task_struct結構,而後將父進程的task_struct內容複製其中,再修改部分數據。分配新的內核堆棧、新的PID、再將task_struct 這個node添加到鏈表中。所謂建立,其實是「複製」。算法
子進程剛開始,內核並無爲它分配物理內存,而是以只讀的方式共享父進程內存,只有當子進程寫時,才複製。即「copy-on-write」。
fork都是由do_fork實現的,do_fork的簡化流程以下圖:shell
#include<unistd.h> pid_t fork(void) //子進程返回0,父進程返回子進程ID,出錯返回-1.
fork函數時調用一次,返回兩次。在父進程和子進程中各調用一次。子進程中返回值爲0,父進程中返回值爲子進程的PID。程序員能夠根據返回值的不一樣讓父進程和子進程執行不一樣的代碼。
一個形象的過程:安全
執行下面程序:bash
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid; char *message; int n = 0; pid = fork(); while(1){ if(pid < 0){ perror("fork failed\n"); exit(1); } else if(pid == 0){ n--; printf("child's n is:%d\n",n); } else{ n++; printf("parent's n is:%d\n",n); } sleep(1); } exit(0);
運行結果:服務器
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./a.out parent's n is:1 child's n is:-1 parent's n is:2 child's n is:-2 parent's n is:3 child's n is:-3
能夠發現子進程和父進程之間並無對各自的變量產生影響。
通常來講,fork以後父、子進程執行順序是不肯定的,這取決於內核調度算法。進程之間實現同步須要進行進程通訊。
一個父進程但願子進程同時執行不一樣的代碼段,這在網絡服務器中常見——父進程等待客戶端的服務請求,當請求到達時,父進程調用fork,使子進程處理此請求。
一個進程要執行一個不一樣的程序,通常fork以後當即調用exec
vfork與fork對比:
相同:
返回值相同
不一樣:
fork建立子進程,把父進程數據空間、堆和棧複製一份;vfork建立子進程,與父進程內存數據共享;
vfork先保證子進程先執行,當子進程調用exit()或者exec後,父進程才往下執行
爲何須要vfork?
由於用vfork時,通常都是緊接着調用exec,因此不會訪問父進程數據空間,也就不須要在把數據複製上花費時間了,所以vfork就是」爲了exec而生「的。
運行這樣一段演示程序:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid; char *message; int n = 0; int i; pid = vfork(); for(i = 0; i < 10; i++){ if(pid < 0){ perror("fork failed\n"); exit(1); } else if(pid == 0){ n--; printf("child's n is:%d\n",n); if(i == 1) _exit(0); //return 0; //exit(0); } else{ n++; printf("parent's n is:%d\n",n); } sleep(1); } exit(0); }
執行結果:
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./a.out child's n is:-1 child's n is:-2 parent's n is:-1 parent's n is:0 parent's n is:1 parent's n is:2 parent's n is:3 parent's n is:4
能夠發現子進程先被執行,exit後,父進程才被執行,同時子進程改變了父進程中的數據
子進程return 0 會發生什麼?
運行結果:
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./a.out child's n is:-1 child's n is:-2 parent's n is:32767 parent's n is:32768 parent's n is:32769 parent's n is:32770 parent's n is:32771 parent's n is:32772
從上面咱們知道,結束子進程的調用是exit()而不是return,若是你在vfork中return了,那麼,這就意味main()函數return了,注意由於函數棧父子進程共享,因此整個程序的棧就跪了。 若是你在子進程中return,那麼基本是下面的過程: 1)子進程的main() 函數 return了,因而程序的函數棧發生了變化。 2)而main()函數return後,一般會調用 exit()或類似的函數(如:_exit(),exitgroup()) 3)這時,父進程收到子進程exit(),開始從vfork返回,可是尼瑪,老子的棧都被你子進程給return幹廢掉了,你讓我怎麼執行?(注:棧會返回一個詭異一個棧地址,對於某些內核版本的實現,直接報「棧錯誤」就給跪了,然而,對於某些內核版本的實現,因而有可能會再次調用main(),因而進入了一個無限循環的結果,直到vfork 調用返回 error) 好了,如今再回到 return 和 exit,return會釋放局部變量,並彈棧,回到上級函數執行。exit直接退掉。若是你用c++ 你就知道,return會調用局部對象的析構函數,exit不會。(注:exit不是系統調用,是glibc對系統調用 _exit()或_exitgroup()的封裝) 可見,子進程調用exit() 沒有修改函數棧,因此,父進程得以順利執行。
【《vfork掛掉的一個問題》http://coolshell.cn/articles/...】
可執行文件裝入內核的linux_binprm結構體。
進程調用exec時,該進程執行的程序徹底被替換,新的程序從main函數開始執行。由於調用exec並不建立新進程,只是替換了當前進程的代碼區、數據區、堆和棧。
六種不一樣的exec函數:
當指定filename做爲參數時:
若是filename中包含/,則將其視爲路徑名。
不然,就按系統的PATH環境變量,在它所指定的各個目錄中搜索可執行文件。
*出於安全方面的考慮,有些人要求在搜索路徑中不要包括當前目錄。
在這6個函數中,只有execve是內核的系統調用。另外5個只是庫函數,他們最終都要調用該系統調用,以下圖所示:
execve的實現由do_execve完成,簡化的實現過程以下圖:
運行這樣一段演示程序:
#include <errno.h> #include <stdio.h> #include <stdlib.h> char command[256]; void main() { int rtn; /*child process return value*/ while(1) { printf( ">" ); fgets( command, 256, stdin ); command[strlen(command)-1] = 0; if ( fork() == 0 ) { execlp( command, NULL ); perror( command ); exit( errno ); } else { wait ( &rtn ); printf( " child process return %d\n", rtn ); } } }
a.out爲打印hello,world的執行文件
運行結果:
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./test >./a.out hello,world child process return 0
從main返回,等效於調用exit
調用exit
exit 首先調用各終止處理程序,而後按需屢次調用fclose,關閉全部的打開流。
調用_exit或者_Exit
最後一個線程從其啓動例程返回
最後一線程調用pthread_exit
調用abort
接到一個信號並終止
最後一個線程對取消請求做出響應
wait用於使父進程阻塞,等待子進程退出;waitpid有若干選項,如能夠提供一個非阻塞版本的wait,也能實現和wait相同的功能,實際上,linux中wait的實現也是經過調用waitpid實現的。
waitpid返回值:正常返回子進程號;使用WNOHANG且沒有子進程退出返回0;調用出錯返回-1;
運行以下演示程序
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main() { pid_t pid0,pid1; pid0 = fork(); if(pid0 < 0){ perror("fork"); exit(1); } else if(pid0 == 0){ sleep(5); exit(0);//child } else{ do{ pid1 = waitpid(pid0,NULL,WNOHANG); if(pid1 == 0){ printf("the child process has not exited.\n"); sleep(1); } }while(pid1 == 0); if(pid1 == pid0){ printf("get child pid:%d",pid1); exit(0); } else{ exit(1); } } return 0; } 當把第三個參數WNOHANG改成0時,就不會有上面五個顯示語句了,說明父進程阻塞了。 a.out 的代碼以下: #include <stdio.h> void main() { printf("hello WYJ\n"); } process.c的代碼以下: #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <sys/times.h> #include <sys/wait.h> int main() { pid_t pid_1,pid_2,pid_wait; pid_1 = fork(); pid_2 = fork(); if(pid_1 < 0){ perror("fork1 failed\n"); exit(1); }else if(pid_1 == 0 && pid_2 != 0){//do not allow child 2 to excute this process. if(execlp("./a.out", NULL) < 0){ perror("exec failed\n"); }//child; exit(0); } if(pid_2 < 0){ perror("fork2 failded\n"); exit(1); }else if(pid_2 == 0){ sleep(10); } if(pid_2 > 0){//parent do{ pid_wait = waitpid(pid_2, NULL, WNOHANG);//no hang sleep(2); printf("child 2 has not exited\n"); }while(pid_wait == 0); if(pid_wait == pid_2){ printf("child 2 has exited\n"); exit(0); }else{ // printf("pid_2:%d\n",pid_2); perror("waitpid error\n"); exit(1); } } exit(0); }
運行結果:
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./proess hello,world child 2 has not exited child 2 has not exited child 2 has not exited child 2 has not exited child 2 has not exited child 2 has not exited child 2 has exited
WNOHANG 改成0運行結果:
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./proess hello,world child 2 has not exited child 2 has exited
編寫一個多進程程序:該實驗有 3 個進程,其中一個爲父進程,其他兩個是該父進程建立的子進程,其中一個子進程運行「ls -l」指令,另外一個子進程在暫停 5s 以後異常退出,父進程並不阻塞本身,並等待子進程的退出信息,待收集到該信息,父進程就返回。
#include<stdio.h> #include<string.h> #include<fcntl.h> #include<unistd.h> #include<stdlib.h> #include<sys/types.h> #include<sys/wait.h> int main() { pid_t child1,child2,child; if((child1 = fork()) < 0){ perror("failed in fork 1"); exit(1); } if((child2 = fork()) < 0){ perror("failed in fork 2"); exit(1); } if(child1 == 0){ //run ls -l if(child2 == 0){ printf("in grandson\n"); } else if(execlp("ls", "ls", "-l", NULL) < 0){ perror("child1 execlp"); } } else if(child2 == 0){ sleep(5); exit(0); } else{ do{ sleep(1); printf("child2 not exits\n"); child = waitpid(child2, NULL, WNOHANG); }while(child == 0); if(child == child2){ printf("get child2\n"); } else{ printf("Error occured\n"); } } }
運行結果:
在unix/linux中,正常狀況下,子進程是經過父進程建立的,子進程在建立新的進程。子進程的結束和父進程的運行是一個異步過程,即父進程永遠沒法預測子進程 到底何時結束。 當一個 進程完成它的工做終止以後,它的父進程須要調用wait()或者waitpid()系統調用取得子進程的終止狀態。
一個父進程退出,而它的一個或多個子進程還在運行,那麼那些子進程將成爲孤兒進程。孤兒進程將被init進程(進程號爲1)所收養,並由init進程對它們完成狀態收集工做。
在進程調用了exit以後,該進程並不是立刻就消失掉,而是留下了一個成爲殭屍進程的數據結構,記載該進程的退出狀態等信息供其餘進程收集,除此以外,殭屍進程再也不佔有任何內存空間。
一個進程使用fork建立子進程,若是子進程退出,而父進程並無調用wait或waitpid獲取子進程的狀態信息,那麼子進程的進程描述符仍然保存在系統中。這種進程稱之爲殭屍進程。
子進程結束以後爲何會進入殭屍狀態? 由於父進程可能會取得子進程的退出狀態信息。
如何查看殭屍進程?
linux中命令ps,標記爲Z的進程就是殭屍進程。
執行下面一段程序:
#include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> int main() { pid_t pid; pid = fork(); if (pid < 0) { printf("error occurred\n"); } else if (pid == 0) { exit(0); } else { sleep(60); wait(null); } }
運行結果:
ps -ef|grep defunc能夠找出殭屍進程
ps -l 能夠獲得更詳細的進程信息
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ps PID TTY TIME CMD 6701 pts/8 00:00:00 bash 9756 pts/8 00:00:00 ps
其中S表示狀態:
O:進程正在處理器運行
S:休眠狀態
R:等待運行
I:空閒狀態
Z:殭屍狀態
T:跟蹤狀態
B:進程正在等待更多的內存分頁
C:cpu利用率的估算值
收集殭屍進程的信息,並終結這些殭屍進程,須要咱們在父進程中使用waitpid和wait,這兩個函數可以手機殭屍進程留下的信息並使進程完全消失。
是linux的後臺服務進程。它是一個生存週期較長的進程,沒有控制終端,輸出無處顯示。用戶層守護進程的父進程是init進程。
守護進程建立步驟:
一、建立子進程,父進程退出,子進程被init自動收養;fork exit
二、調用setsid建立新會話,成爲新會話的首進程,成爲新進程組的組長進程,擺脫父進程繼承過來的會話、進程組等;setsid
三、改變當前目錄爲根目錄,保證工做的文件目錄不被刪除;chdir(「/」)
四、重設文件權限掩碼,給子進程更大的權限;umask(0)
五、關閉不用的文件描述符,由於會消耗資源;close
#include<stdio.h> #include<string.h> #include<fcntl.h> #include<unistd.h> #include<sys/types.h> #define MAXFILE 65535 int main() { int fd, len, i; pid_t pid; char* buf = "tick\n"; len = strlen(buf); if ((pid = fork()) < 0) { perror("fork failed"); exit(1); } else if (pid > 0) { exit(0); } setsid(); if (chdir("/") < 0) { perror("chdir failed"); exit(1); } umask(0); for (i = 0; i < MAXFILE; i++) { close(i); } while (1) { if ((fd = open("/tmp/dameon.log", O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0) { perror("open log failed"); exit(1); } write(fd, buf, len + 1); close(fd); sleep(10); } }
#include<stdio.h> #include<string.h> #include<fcntl.h> #include<unistd.h> #include<sys/types.h> #include<syslog.h> #define MAXFILE 65535 int main() { int fd,len,i; pid_t pid,child; char *buf = "tick\n"; len = strlen(buf); if((pid = fork()) < 0){ perror("fork failed"); exit(1); } else if(pid > 0){ exit(0); } openlog("Jack", LOG_PID, LOG_DAEMON); if(setsid() < 0){ syslog(LOG_ERR, "%s\n", "setsid"); exit(1); } if(chdir("/") < 0){ syslog(LOG_ERR, "%s\n", "chdir"); exit(1); } umask(0); for(i = 0; i < MAXFILE; i++){ close(i); } if((child = fork()) < 0){ syslog(LOG_ERR, "%s\n", "fork"); exit(1); } if(child == 0){ //printf("in child\n");//can not use terminal from now on. syslog(LOG_INFO, "in child"); sleep(10); exit(0); } else{ waitpid(child, NULL, 0); //printf("child exits\n");//can not use terminal from now on. syslog(LOG_INFO, "child exits"); closelog(); while(1){ sleep(10); } } }
真正編寫調試的時候會發現須要殺死守護進程。如何殺死守護進程?ps -aux 找到對應PIDkill -9 PID