本章包含內容有:html
每一個進程都有一個惟一的標識符,進程ID(process ID)。面試
進程的ID是可重用的,若是一個進程被終止,那麼它的進程ID會被系統回收,可是會延遲使用,防止該進程ID標識的新進程被誤認爲是之前的進程。緩存
三個特殊ID的進程:app
獲取進程各類ID的相關函數:函數
函數聲明:ui
#include <unistd.h>spa
pid_t getpid(void); // Returns: process ID of calling processunix
pid_t getppid(void); // Returns: parent process ID of calling process指針
uid_t getuid(void); // Returns: real user ID of calling processhtm
uid_t geteuid(void); // Returns: effective user ID of calling process
gid_t getgid(void); // Returns: real group ID of calling process
gid_t getegid(void); // Returns: effective group ID of calling process
這裏的各類ID在前面第三篇中有說明,http://www.cnblogs.com/suzhou/p/4295535.html
fork函數用於一個已存在的進程建立一個新的進程。
函數聲明:
#include <unistd.h>
pid_t fork(void);
函數細節:
Example:
#include "apue.h"
int globvar = 6; /* external variable in initialized data */
char buf[] = "a write to stdout\n";
int
main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
err_sys("write error");
printf("before fork\n"); /* we don't flush stdout */
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { /* child */
globvar++; /* modify variables */
var++;
} else {
sleep(2); /* parent */
}
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
var);
exit(0);
}
執行結果:
pid爲12291的進程爲子進程,對變量glob和var進行了加1。
當把輸出重定向到一個文件時,咱們發現結果和直接輸出到終端中不太同樣:
緣由:
當調用fork函數時,父進程的全部打開的文件描述符都會複製一份到子進程中,包括文件偏移量(file offset)。
因此當父子進程同時寫文件時,他們的操做都會更新同一個文件偏移量(file offset),加入子進程向文件中寫入了一部分數據,同時更新了file offset,那麼父進程進行寫入操做時,會使用跟新之後的offset,從而避免了覆蓋了子進程寫入的數據。
父子進程共享文件以下圖所示:
咱們能夠發現,父子進程擁有相同的文件描述符,又沒有其餘的同步方式,因此他們的輸出可能會混起來(intermixed)。
fork以後,常見的處理父子進程擁有的文件描述符有兩種方式:
除了打開的文件描述,其餘的子進程會繼承自父進程的內容包括:
父子進程不一樣的地方包括:
vfork和fork有相同的返回值。
vfork和fork的不一樣點:
Example:
#include "apue.h"
int globvar = 6; /* external variable in initialized data */
int
main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
printf("before vfork\n"); /* we don't flush stdio */
if ((pid = vfork()) < 0) {
err_sys("vfork error");
} else if (pid == 0) { /* child */
globvar++; /* modify parent's variables */
var++;
_exit(0); /* child terminates */
}
/* parent continues here */
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
var);
exit(0);
}
運行結果:
正常退出:三個函數exit,
若是子進程不正常退出,則內核保證記錄該進程的異常退出狀態,該進程的父進程能夠經過調用wait或者waitpid函數獲取該子進程的異常退出狀態。
若是父進程在子進程以前終止,則init進程成爲該子進程的父進程。從而保證每一個進程都有父進程。
若是子進程先終止(異常終止或者正常退出),內核會保存該子進程的部分信息,包括進程pid,進程終止時的狀態和該進程佔用的CPU時間,同時內核會清除該進程佔用的內存,關閉全部已經打開的文件描述符。父進程能夠經過檢查該信息獲取子進程的終止狀況。
若是子進程先終止,而沒有父進程調用waitpid獲取該子進程的信息,那麼這種進程被成爲殭屍進程。使用ps命令能夠看到殭屍進程的相關信息。
若是父進程爲init進程,那麼子進程異常終止並不會成爲殭屍進程,由於init進程會對它的全部子進程調用wait函數獲取子進程的終止狀態。
子進程終止,內核會向父進程發送SIGCHLD信號。父進程默認的行爲是忽略該信號,父進程也能夠設置一個信號處理函數,當捕捉到該信號時,調用該處理函數,在後面的相關章節會介紹信號相關的概念。
本節介紹的wait和waitpid函數的做用是:
須要注意的一點是,若是咱們在接收到SIGCHLD信號後,調用wait函數,則該函數會馬上返回。在其餘狀況下調用wait函數,則會阻塞。
函數聲明:
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
// Both return: process ID if OK, 0,or -1 on error
兩個函數之間的區別:
函數細節:
返回值檢查:
使用四個宏來檢查wait和waitpid函數來獲取子進程的終止狀態(terminated status),如退出狀態,信號值等信息。
四個宏的具體說明見下表所示:
pid的取值對waitpid函數行爲的影響:
參數option的取值:
waitpid函數提供了三個wait沒有的特性:
#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) { /* first child */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0)
{
exit(0); /* parent from second fork == first child */
}
/*
* We're the second child; our parent becomes init as soon
* as our real parent calls exit() in the statement above.
* Here's where we'd continue executing, knowing that when
* we're done, init will reap our status.
*/
sleep(2);
printf("second child, parent pid = %ld\n", (long)getppid());
exit(0);
}
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
err_sys("waitpid error");
/*
* We're the parent (the original process); we continue executing,
* knowing that we're not the parent of the second child.
*/
exit(0);
}
執行結果:
結果分析:
在這裏咱們fork了兩次,緣由是,當咱們想fork一個子進程出來,而咱們不但願父進程阻塞在wait函數,而且不但願因爲父進程沒有調用wait函數先退出致使子進程成爲殭屍進程,那麼fork兩次,而且退出第一個子進程,可使得父進程及時退出,而且第二個子進程的父進程變成init進程。
本篇主要介紹了fork、vfork、殭屍進程、wait和waitpid函數,這些在unix環境中都是很重要的概念和函數,而且在面試中也常常問到。
下一篇的內容包括:
參考資料:
《Advanced Programming in the UNIX Envinronment 3rd》