進(jìn)程的狀態(tài)
Linux進(jìn)程有7種基礎(chǔ)狀態(tài)(兩種running算一種),除了traced都可以用$ps命令查看,$ps可以查看的進(jìn)程狀態(tài)如下,更多進(jìn)程狀態(tài)信息參見Linux Process VS Thread VS LWP
R?running or runnable (on run queue)
D?uninterruptible sleep (usually IO)
S?interruptible sleep (waiting for an event to complete)
T?stopped, either by a job control signal or because it is being traced.W?paging (not valid since the 2.6.xx kernel)
X?dead (should never be seen)
Z?defunct ("zombie") process, terminated but not reaped by its parent.
模型
多進(jìn)程代碼區(qū)模型(其他區(qū)參見copy-on-write):
#include
getpid()、getppid()
//getpid() 返回調(diào)用進(jìn)程的PID//getppid() 返回調(diào)用進(jìn)程的父進(jìn)程的PIDpid_t getpid(void); //pid_t是intpid_t getppid(void);
getuid()、geteuid()
getuid()返回調(diào)用進(jìn)程的UIDgeteuid()返回調(diào)用進(jìn)程的effective UIDuid_t getuid(void); //uid_t是unsigned intuid_t geteuid(void);
getgid(),getegid()
//getgid()返回調(diào)用進(jìn)程的real GID//getegid()返回調(diào)用進(jìn)程的effective GIDid_t getgid(void); //gid_t是unsigned intgid_t getegid(void);
printf("pid=%d\n",getpid()); printf("ppid=%d\n",getppid()); printf("uid=%d\n",getuid()); printf("gid=%d\n",getgid()); }
fork()
//創(chuàng)建子進(jìn)程,在父進(jìn)程中返回子進(jìn)程的PID,在子進(jìn)程中返回0,失敗在父進(jìn)程中返回-1pid_t fork(void);
fork()創(chuàng)建的子進(jìn)程繼承父進(jìn)程的有:
- 實際用戶ID,實際組ID,有效用戶ID,有效組ID
- 附屬組ID
- 進(jìn)程組ID
- 會話ID
- 控制終端
- 設(shè)置用戶ID標(biāo)志和設(shè)置組ID標(biāo)志
- 當(dāng)前工作目錄
- 根目錄
- 文件模式和安排
- 信號屏蔽和安排
- 對任一打開fd的close-on-exec
- 環(huán)境
- 連接的共享存儲段
- 存儲映像
- 資源限制
與父進(jìn)程有區(qū)別的有
- fork的返回值
- PID
- PPID
- 子進(jìn)程的tms_utime,tms_stime,tms_cutime,tms_ustime被設(shè)置為0
- 不繼承文件鎖
- 子進(jìn)程未處理鬧鐘被清除
- 子進(jìn)程未處理信號集設(shè)置為空集
父子進(jìn)程代碼區(qū)執(zhí)行次序
fork()產(chǎn)生的所有進(jìn)程共享代碼區(qū),copy-on-write其他區(qū))
- fork()之前的代碼, 由parent執(zhí)行一次
- fork()之后的代碼, 由父子進(jìn)程各執(zhí)行一次
- fork()的返回值由父子進(jìn)程各自返回一次
copy-on-write:
fork()一下干的幾件事:
- 給P2分配Text段, Data段, Heap段, Stack段的虛擬地址,都指向P1中相應(yīng)的物理地址
- P2的Text段是鐵定和P1共享同一個物理地址了, 剩下的Data,Heap,Stack待定
- 如果one of them 改變了這三個段的內(nèi)容, 就把原來的數(shù)據(jù)復(fù)制一份給P2, 這樣P2就有了相應(yīng)的新的物理地址
//創(chuàng)建任意多個進(jìn)程:子進(jìn)程干活,父進(jìn)程創(chuàng)建?一個爹一堆兒子int i=0;for(i=0;i<10;i++){ //創(chuàng)建10個進(jìn)程, 只有parent在執(zhí)行for()因為child在每次循環(huán)體內(nèi)就exit()了 pid_t pid=fork(); if(-1==pid) perror("fork"),exit(-1); if(0==pid){ … exit(0); //終止子進(jìn)程, 自然也就跳出了循環(huán),防止再fork() }}
#include
vfork()
//創(chuàng)建一個空的子進(jìn)程,父進(jìn)程會等待子進(jìn)程退出之后在繼續(xù)執(zhí)行,在子進(jìn)程執(zhí)行期間,父進(jìn)程被掛起,此期間子進(jìn)程和父進(jìn)程共享所有的內(nèi)存資源//vfork()多用在在不拷貝父進(jìn)程頁表的情況下創(chuàng)建新的進(jìn)程,單獨使用沒有多線程的價值, 主要和exec()搭配使用。//子進(jìn)程終止時不能從當(dāng)前函數(shù)返回/調(diào)用exit函數(shù), 可以調(diào)用_exit(), 該函數(shù)保證了子進(jìn)程先于父進(jìn)程執(zhí)行//當(dāng)下很多系統(tǒng)已經(jīng)不再支持vfork()函數(shù)pid_t vfork(void);
int main(){ pid_t pid=vfork(); if(-1==pid) perror("vfork"),exit(-1); if(0==pid){ printf("child %d starts\n",getpid()); sleep(2); //跳轉(zhuǎn)出去, 調(diào)用execl() int res=execl("./proc","proc",NULL); //"ls"表示執(zhí)行方式, 以字符串的形式傳進(jìn)來 if(-1==res) perror("execl"),_exit(-1);//ATTENTION,用_exit() } printf("parent starts\n"); printf("parent ends\n"); return 0;}//execl()可以跳出當(dāng)前進(jìn)程(VS fork()), 去執(zhí)行一個完全不同的文件,可以幫助vfork()實現(xiàn)多進(jìn)程,//父進(jìn)程結(jié)束了,系統(tǒng)就會顯示[~/Desktop/160512/Code]$,此時發(fā)現(xiàn)從已經(jīng)終結(jié)的子進(jìn)程跳轉(zhuǎn)出的的文件還沒執(zhí)行完, 再打印ls -l的內(nèi)容$./a.out child 4258 startsparent startsparent ends[~/Desktop/160512/Code]$total 20-rw-rw-r-- 1 tarena tarena 754 5月 12 11:03 01waitpid.c-rw-rw-r-- 1 tarena tarena 449 5月 12 10:31 02vfork.c-rw-rw-r-- 1 tarena tarena 489 5月 12 11:28 03execl.c-rwxrwxr-x 1 tarena tarena 7499 5月 12 11:28 a.out*/
exec()
用一個新的進(jìn)程影像替代當(dāng)前的進(jìn)程映像,失敗返回-1設(shè)errnoextern 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[]);
ATTENTION:?vfork()主要與exec family搭配使用, 主要用語子進(jìn)程執(zhí)行與父進(jìn)程完全不同代碼段的場合中, 其中vfork()專門用于創(chuàng)建進(jìn)程, exec family 專門用于跳轉(zhuǎn)執(zhí)行
, fork()雖然也可以和exec family 搭配使用, 但是fork()會復(fù)制父進(jìn)程的內(nèi)存空間, 復(fù)制完了又跳出去, 沒什么意義, 效率不如(vfork(), exec family)
#include
7種進(jìn)程終止- 正常終止:
- 從 main() 返回
- 調(diào)用 exit() / _exit() / _Exit()
- 最后一個線程從其啟動例程返回
- 最后一個線程調(diào)用pthread_exit()
- 異常終止:
- 調(diào)用abort()
- 接到一個信號并終止
- 最后一個線程對取消請求作出響應(yīng)
exit status VS termination status
退出狀態(tài)exit status是我們傳入到exit(),_exit(),_Exit()函數(shù)的參數(shù)。進(jìn)程正常終止的情況下,內(nèi)核將退出狀態(tài)轉(zhuǎn)變?yōu)榻K止?fàn)顟B(tài)以供父進(jìn)程使用wait(),waitpid()等函數(shù)獲取。終止?fàn)顟B(tài)termination status除了上述正常終止進(jìn)程的情況外,還包括異常終止的情況,如果進(jìn)程異常終止,那么內(nèi)核也會用一個指示其異常終止原因的終止?fàn)顟B(tài)來表示進(jìn)程,當(dāng)然,這種終止?fàn)顟B(tài)也可以由父進(jìn)程的wait(),waitpid()進(jìn)程捕獲。
exit()
//引起進(jìn)程的正常終止,所謂正常終止是按照注冊的反順序依次調(diào)用atexit()和on_exit()里注冊的函數(shù)。VS _exit()和_Exit()會立即終止進(jìn)程//進(jìn)程終止后會傳遞退出碼給父進(jìn)程,這個"退出碼&0377"可以被父進(jìn)程的wait()系列函數(shù)捕獲并解析。//系統(tǒng)使用8位二進(jìn)制表示進(jìn)程退出號,就是0~255,這也是為什么exit()返回status&0377給父進(jìn)程的原因, 其實是取低八位二進(jìn)制. 如果exit(10000),實際返回的就是16. void exit(int status);
atexit()
//注冊一個正常終止進(jìn)程時執(zhí)行的函數(shù),這個函數(shù)的參數(shù)必須是void,注冊成功返回0,失敗返回非0int atexit(void (*function)(void)); //參數(shù)是函數(shù)指針
on_exit()
//和atexit()類似,用于注冊exit()時執(zhí)行的函數(shù), 不同之處是on_exit注冊的函數(shù)可以帶參數(shù),這個function的兩個形參分別是通過exit()傳入的int型 和 通過on_exit()傳入的*arg//同一個函數(shù)可以被多次注冊,注冊一次退出進(jìn)程時就會被執(zhí)行一次//fork()出的子進(jìn)程會繼承父進(jìn)程的注冊函數(shù),但一旦調(diào)用了exec(),所有注冊的函數(shù)都會被移除//成功返回0,失敗返回非0int on_exit(void (*function)(int , void *), void *arg);
#include
_exit()/_Exit():
//立即終止調(diào)用的進(jìn)程,所有的子進(jìn)程都會掛到PID1下,父進(jìn)程會收到SIGCHLD信號,還可以用wait()接收退出碼void _exit(int status); //
#include
Orphan VS Zombie
Orphan Process:一個parent退出,而它的一個或多個child還在運行,那么這些child將成為orphan。將被init(PID==1)收養(yǎng),并由init對它們完成狀態(tài)收集工作。init會循環(huán)地wait()直到這些child完成了他們的工作. 即當(dāng)一個孤兒進(jìn)程凄涼地結(jié)束了其生命周期的時候,init進(jìn)程就會代表黨和政府出面處理它的一切善后工作。因此孤兒進(jìn)程并不會有什么危害。
Zombie Process:?一個使用fork()創(chuàng)建的child,如果child退出,而parent并沒有調(diào)用wait/waitpid獲取child的狀態(tài)信息,那么child的process descriptor、PID和PCB等資源仍然保存在系統(tǒng)中。此時的child就變成了zombie。因為系統(tǒng)的PID總數(shù)是有限的, parent不斷的創(chuàng)建child而不去wait,系統(tǒng)早晚會被拖垮.
總結(jié):
- Orphan/Zombie都是因為在parent中沒有wait掉child, 不同之處是orphan的parent已經(jīng)沒了, 由init來接管了,而zombie有個缺德的parent, 不wait還不撒手,拖累了系統(tǒng)
- $ps 一下Zombie的進(jìn)程狀態(tài)是’Z’
wait(), waitpid(), waitid()
//wait for process to change state//wait()掛起父進(jìn)程,直到一個子進(jìn)程結(jié)束//waitpid()掛起父進(jìn)程,直到指定的子進(jìn)程終止//wait()相當(dāng)于waitpid(-1, &status, 0)//成功返回子進(jìn)程的PID,失敗返回-1設(shè)errnopid_t wait(int *status);pid_t waitpid(pid_t pid, int *status, int options);/* pid in waitpid() and kill() pid>0 //指定pidpid=0 //GID是調(diào)用進(jìn)程PID的子進(jìn)程pid=-1 //任何子進(jìn)程pid<-1 //GID是PID的子進(jìn)程*//*options(Bitwaise Or) WNOHANG //如果沒有子進(jìn)程終止就立即返回return immediately if no child has exited.WUNTRACED //如果一個子進(jìn)程stoped且沒有被traced,那么立即返回WCONTINUED (since Linux 2.6.10) //如果stoped的子進(jìn)程通過SIGCONT復(fù)蘇,那么立即返回 *//*如果退出不是NULL,wait()會使用形參指針帶出退出碼,這個退出碼可以使用下列宏解讀WIFEXITED(status) //如果子進(jìn)程正常退出返回真WEXITSTATUS(status) //返回子進(jìn)程的退出碼,當(dāng)且僅當(dāng)WIFEXITED為真時有效WIFSIGNALED(status) //如果子進(jìn)程被一個信號終止時返回真WTERMSIG(status) //返回終止子進(jìn)程的信號編號,當(dāng)且僅當(dāng)WIFSIGNALED為真時有效WCOREDUMP(status) //如果子進(jìn)程導(dǎo)致了"核心已轉(zhuǎn)儲"則返回真,當(dāng)且僅當(dāng)WIFSIGNALED為真時有效rWIFSTOPPED(status) //如果子進(jìn)程被一個信號暫停時返回真,當(dāng)且僅當(dāng)調(diào)用進(jìn)程使用WUNTRACED或子進(jìn)程正在traced時有效WSTOPSIG(status) //返回引起子進(jìn)程暫停的信號編號,當(dāng)且僅當(dāng)WIFSTOPPED為真時有效WIFCONTINUED(status)(since Linux 2.6.10)//如果子進(jìn)程收到SIGCONT而復(fù)蘇時返回真*/
if(0==pid){ … exit(100); //把child的退出狀態(tài)信息設(shè)為100}int status=0;int res=wait(&status); //status用來接收結(jié)果if(-1==res) perror("wait"),exit(-1);if(WIFEXITED(status)) //ATTENTION:這個宏要int不是int*,和wait不一樣 printf("child%d end normally, status is:%d\n",res,WEXITSTATUS(status)); //將打印出exit()里的狀態(tài)
例子
/*------file.c------*/#include
Note:
- 運行結(jié)果a.txt兩個進(jìn)程沒有覆蓋=>父子進(jìn)程使用的讀寫位置信息是同一份=>文件表是同一份=>但是兩個是不同的fd, 所以fork()創(chuàng)建子進(jìn)程也會復(fù)制一個文件描述符總表
- 正是因為使用讀寫一次 offset會向后移, 所以沒有覆蓋, 因為后來的是使用前面留下的offset的位置, 所以使用的讀寫信息是一樣的
/*--------------------------------------------child終止時自動釋放malloc()----------------------------------------------*/#include
Note:
- 用全局變量做橋梁
- atexit()里面的函數(shù)一定是int *(void)?函數(shù)的形參列表變了也不行
- ATTENTION:?vfork()的child雖然整個內(nèi)存區(qū)都是和parent共享的, 但是變量的作用域還是在啊, 所以你跨函數(shù)使用變量肯定要傳參的啊
/*--------------------------------------------on_exit.c, child終止時自動釋放malloc()----------------------------------------------*/#include
評論
查看更多