1、進程標識app
每一個進程都有一個非負整型標識的惟一進程ID。由於進程ID標識符老是惟一的,常將其用作其餘標識符的一部分以保證其惟一性。進程ID雖然是惟一的, 可是倒是能夠複用的。ID爲0的進程一般是調度進程,經常被稱爲交換進程(swapper)。該進程是內核的一部分,它並不執行任何磁盤上的程序,所以也被稱爲系統進程。進程ID爲1一般是init進程,在自舉過程結束時由內核調用。此進程負責在自舉內核後啓動一個UNIX系統,init一般讀取與系統有關的初始化文件,並將系統引導一個狀態。init進程毫不會終止。它是一個普通的用戶進程,可是它以超級用戶特權運行。進程ID爲2是頁守護進程,此進程負責支持虛擬存儲器系統的分頁操做。異步
#include <unistd.h> pid_t getpid(void); //返回進程的進程ID pid_t getppid(void); //返回進程的父進程ID pid_t getuid(void); //返回進程的實際用戶ID pid_t geteuid(void); //返回調用進程的有效用戶ID pid_t getgid(void); //返回進程的實際組ID pid_t getepid(void); //返回進程的有效組ID
2、函數fork函數
#include <unistd.h> pid_t fork(void);
由fork建立的新進程被稱爲子進程(child process)。fork函數被調用一次,但返回兩次。兩次返回的區別是子進程的返回值是0,而父進程的返回值則是新建子進程的進程ID。緣由:由於一個進程的子進程能夠有不少個,而且沒有一個函數使一個進程能夠得到其全部子進程的進程ID;一個進程只會有一個父進程,因此子進程老是能夠調用getppid以得到其父進程的進程ID。子進程是父進程的副本,子進程得到父進程數據空間、堆和棧的副本,父進程和子進程不共享這些存儲空間部分。父進程和子進程只共享正文段。因爲在fork以後常常跟隨着exec,因此如今的不少實現並不執行一個父進程數據段和棧和堆的徹底副本,做爲替代,使用了寫時複製(Copy-On-Write)技術。ui
#include "apue.h" int globvar = 0; char buff[] = "a write to stdout\n"; int main(void) { int var; pid_t pid; var = 88; if(write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1) { err_sys("write error"); } printf("before fork\n"); if((pid = fork()) < 0) { err_sys(" fork error"); } else if(pid == 0) { globvar ++; var ++; } else { sleep(2); } printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var); exit(0); }
8-1:fork函數實例spa
fork的一個特性是父進程的全部打開文件描述符都被複制到子進程中。咱們說賦值是由於對每一個文件描述符來講,就好像執行了dup函數,父進程和子進程每一個相同的打開描述符共享一個文件表項。重要的一點是:父進程和子進程共享同一個文件偏移量。code
8-2: fork以後父進程和子進程之間對打開文件的共享orm
除了打開文件以外,父進程的不少其餘屬性也由子進程繼承:實際用戶ID、實際組ID、有效用戶ID、有效組ID;附屬組ID;進程組ID;會話ID;控制終端;設置用戶ID標誌和設置組ID標誌;當前工做目錄;根目錄;文件模式建立屏蔽字;信號屏蔽和安排;對任一打開文件描述符的執行時關閉標誌;環境;鏈接的共享存儲段;存儲映像;資源限制。父進程和子進程之間的區別在於:fork的返回值不一樣;進程ID不一樣;兩個進程的父進程ID不一樣;本身稱tms_utime、tms_stime、tms_ustime的值設置爲0;子進程不繼承父進程設置的文件所;子進程的未處理鬧鐘被清除;子進程的未處理信號集設置爲空集。blog
fork失敗的兩個主要緣由:系統中已經有了太多的進程;該實際用戶ID的進程總數超過了系統限制。fork有如下兩個用法:一個父進程但願複製本身,使父進程和子進程執行不一樣的代碼段;一個進程要執行一個不一樣的程序。繼承
3、函數vfork進程
vfork與fork同樣都建立一個子進程,可是它並不將父進程的地址空間徹底複製到子進程中,由於子進程會當即調用exec,預示也就不會引用該地址空間;vfork和fork之間的另外一個區別是:vfork保證子進程先運行,在它調用exec或exit以後父進程纔可能被調度運行,當子進程調用這兩個函數中的任意一個時,父進程會恢復運行。
#include "apue.h" int globvar = 6; int main(void) { int var; pid_t pid; var = 88; pirntf("before vfork\n"); if((pid = vfork()) < 0 ) { err_sys("vfork error"); } else if(pid == 0) { globvar ++; var ++; _exit(0); } pirntf("pid = %ld, glob = %d, var = %d\n",(long)getpid(), globvar, var); exit(0); }
8-3: vfork函數實例
4、函數exit
在大多數UNIX系統實現中,exit是標準C庫中的一個函數,而_exit則是一個系統調用。無論進程如何終止,最後都會執行內核中的同一段代碼。這段代碼爲相應進程關閉全部打開描述符,釋放它所使用的存儲器等。在任意一種狀況下,該終止進程的父進程都能用wait或waipid函數取得其終止狀態。對於父進程已經終止的全部進程,它們的父進程都改變爲init進程。咱們稱這些進程由init進程手癢。在UNIX術語中,一個已經終止、可是父進程還沒有對其進行善後處理(獲取終止子進程的有關信息、釋放它仍佔用的資源)的進程被稱爲僵死進程。
5、函數wait和waitpid
當一個進程正常或異常終止時,內核就向父進程發送SIGCHLD信息。由於子進程終止是個異步事件,因此這種信號也是內核向父進程發的異步通知。父進程能夠選擇忽略該信號,或者提供一個該信號發生時即被調用執行的函數,對這種信號的系統默認動做是忽略它。
#include <sys/wait.h> pid_t wait(int *statloc); pid_t waitpidd(pid_t pid, int *statloc, int options);
若是其全部子進程都還在運行,則阻塞;若是一個子進程已終止,正等待父進程獲取其終止狀態,則取得該子進程的終止狀態當即返回;若是它沒有任何子進程,則當即出錯返回。
#include "apue.h" #include <sys/wait.h> void pr_exit(int status); int main(void) { pid_t pid; int status; if((pid = fork) < 0) { err_sys("fork error"); } else if(pid == 0) { exit(7); } if(wait(&status) != pid) { err_sys("wait error"); } pr_exit(status); if((pid = fork) < 0) { err_sys("fork error"); } else if(pid == 0) { abort(); } if(wait(&status) != pid) { err_sys("wait error"); } pr_exit(status); if((pid = fork) < 0) { err_sys("fork error"); } else if(pid == 0) { status /= 0; } if(wait(&status) != pid) { err_sys("wait error"); } pr_exit(status); exit(0); } void pr_exit(int status) { if(WIFEXITED(status)) { printf("normal termination, exit status = %d\n", WEXITSTATUS(status)); } else if(WIFSIGNALED(status)) { printf("abnormal termination, signal number = %d\n", WTERMSIG(status), #ifdef WCORDUMP WCORDUMP(status)? "core file generated":""); #else ""); #endif } else if(WIFSTOPPED(status)) { printf("child stopped, signal number = &d\n", WSTOPSIG(status)); } }
8-6:演示不一樣的exit值
若是一個進程有幾個子進程,那麼只要有一個子進程終止,wait就返回。waitpid函數提供了wait函數沒有提供的3個功能:一、waitpid可等待一個特定的進程,而wait則返回任一終止子進程的狀態;二、waitpid提供了一個wait的非阻塞版本;三、waitpid經過WUNTRACED和WCONTINUED選項支持做業控制。
#include "apue.h" #include <sys/wait.h> int main(void) { pid_t pid; if((pid = fork()) < 0) { err_sys("fork error"); } else if(pid == 0) { if((pid = fork()) < 0) { err_sys("fork error"); } else if(pid > 0) { exit(0); } sleep(2); printf("second child, parent pid = %ld\n",(long) getppid()); exit(0); } if(waitpid(pid, NULL, 0) != pid) { err_sys("waitpid error"); } exit(0); }
8-8:fork兩次以免僵死進程
6、函數exec
當進程調用一種exec函數時,該進程執行的程序徹底替換爲新程序,而新程序則從其main函數開始執行。由於調用exec並不建立新進程,因此先後的進程ID並未改變。exec只是用磁盤上的一個新程序替換了當前進程的正文段、數據段、堆段和棧段。用fork能夠建立新進程,用exec能夠初始化執行新的程序。exit函數和wait函數處理終止和等待終止。這些是咱們須要的基本的進程控制原語。
#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 execve(const char *file, char *const argv[], char *const envp[]);
8-15:7個exec函數之間的關係
#include "apue.h" #include <sys/wait.h> char *env_init[] = {"USER=unknow", "PATH=/tmp", NULL}; int main(void) { pid_t pid; if((pid = fork()) < 0) { err_sys("fork error"); } else if(pid == 0) { if(execle("/home/sar/bin/echoall", "echoall", "myarg1", "MY ARG2", (char *)0, env_init) < 0) { err_sys("execle error"); } } if(waitpid(pid, NULL, 0) < 0) { err_sys("waitpid error"); } if((pid == fork()) < 0) { err_sys("fork error"); } else if(pid == 0) { if(execlp("echoall", "echoall", "only 1arg", (char *)0) < 0) { err_sys("execle error"); } } exit(0); }
8-16:exec函數實例