進(jìn)程的狀態(tài)
Linux進(jìn)程有7種基礎(chǔ)狀態(tài)(兩種running算一種),除了traced都可以用$ps命令查看,$ps可以查看的進(jìn)程狀態(tài)如下,更多進(jìn)程狀態(tài)信息參見(jiàn)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ū)參見(jiàn)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)程的有:
- 實(shí)際用戶ID,實(shí)際組ID,有效用戶ID,有效組ID
- 附屬組ID
- 進(jìn)程組ID
- 會(huì)話ID
- 控制終端
- 設(shè)置用戶ID標(biāo)志和設(shè)置組ID標(biāo)志
- 當(dāng)前工作目錄
- 根目錄
- 文件模式和安排
- 信號(hào)屏蔽和安排
- 對(duì)任一打開(kāi)fd的close-on-exec
- 環(huán)境
- 連接的共享存儲(chǔ)段
- 存儲(chǔ)映像
- 資源限制
與父進(jìn)程有區(qū)別的有
- fork的返回值
- PID
- PPID
- 子進(jìn)程的tms_utime,tms_stime,tms_cutime,tms_ustime被設(shè)置為0
- 不繼承文件鎖
- 子進(jìn)程未處理鬧鐘被清除
- 子進(jìn)程未處理信號(hào)集設(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共享同一個(gè)物理地址了, 剩下的Data,Heap,Stack待定
- 如果one of them 改變了這三個(gè)段的內(nèi)容, 就把原來(lái)的數(shù)據(jù)復(fù)制一份給P2, 這樣P2就有了相應(yīng)的新的物理地址
//創(chuàng)建任意多個(gè)進(jìn)程:子進(jìn)程干活,父進(jìn)程創(chuàng)建?一個(gè)爹一堆兒子int i=0;for(i=0;i<10;i++){ //創(chuàng)建10個(gè)進(jìn)程, 只有parent在執(zhí)行for()因?yàn)閏hild在每次循環(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)建一個(gè)空的子進(jìn)程,父進(jìn)程會(huì)等待子進(jìn)程退出之后在繼續(xù)執(zhí)行,在子進(jìn)程執(zhí)行期間,父進(jìn)程被掛起,此期間子進(jìn)程和父進(jìn)程共享所有的內(nèi)存資源//vfork()多用在在不拷貝父進(jìn)程頁(yè)表的情況下創(chuàng)建新的進(jìn)程,單獨(dú)使用沒(méi)有多線程的價(jià)值, 主要和exec()搭配使用。//子進(jìn)程終止時(shí)不能從當(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)來(lái) 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í)行一個(gè)完全不同的文件,可以幫助vfork()實(shí)現(xiàn)多進(jìn)程,//父進(jìn)程結(jié)束了,系統(tǒng)就會(huì)顯示[~/Desktop/160512/Code]$,此時(shí)發(fā)現(xiàn)從已經(jīng)終結(jié)的子進(jìn)程跳轉(zhuǎn)出的的文件還沒(méi)執(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()
用一個(gè)新的進(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搭配使用, 主要用語(yǔ)子進(jìn)程執(zhí)行與父進(jìn)程完全不同代碼段的場(chǎng)合中, 其中vfork()專門(mén)用于創(chuàng)建進(jìn)程, exec family 專門(mén)用于跳轉(zhuǎn)執(zhí)行
, fork()雖然也可以和exec family 搭配使用, 但是fork()會(huì)復(fù)制父進(jìn)程的內(nèi)存空間, 復(fù)制完了又跳出去, 沒(méi)什么意義, 效率不如(vfork(), exec family)
#include
7種進(jìn)程終止- 正常終止:
- 從 main() 返回
- 調(diào)用 exit() / _exit() / _Exit()
- 最后一個(gè)線程從其啟動(dòng)例程返回
- 最后一個(gè)線程調(diào)用pthread_exit()
- 異常終止:
- 調(diào)用abort()
- 接到一個(gè)信號(hào)并終止
- 最后一個(gè)線程對(duì)取消請(qǐng)求作出響應(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)核也會(huì)用一個(gè)指示其異常終止原因的終止?fàn)顟B(tài)來(lái)表示進(jìn)程,當(dāng)然,這種終止?fàn)顟B(tài)也可以由父進(jìn)程的wait(),waitpid()進(jìn)程捕獲。
exit()
//引起進(jìn)程的正常終止,所謂正常終止是按照注冊(cè)的反順序依次調(diào)用atexit()和on_exit()里注冊(cè)的函數(shù)。VS _exit()和_Exit()會(huì)立即終止進(jìn)程//進(jìn)程終止后會(huì)傳遞退出碼給父進(jìn)程,這個(gè)"退出碼&0377"可以被父進(jìn)程的wait()系列函數(shù)捕獲并解析。//系統(tǒng)使用8位二進(jìn)制表示進(jìn)程退出號(hào),就是0~255,這也是為什么exit()返回status&0377給父進(jìn)程的原因, 其實(shí)是取低八位二進(jìn)制. 如果exit(10000),實(shí)際返回的就是16. void exit(int status);
atexit()
//注冊(cè)一個(gè)正常終止進(jìn)程時(shí)執(zhí)行的函數(shù),這個(gè)函數(shù)的參數(shù)必須是void,注冊(cè)成功返回0,失敗返回非0int atexit(void (*function)(void)); //參數(shù)是函數(shù)指針
on_exit()
//和atexit()類似,用于注冊(cè)exit()時(shí)執(zhí)行的函數(shù), 不同之處是on_exit注冊(cè)的函數(shù)可以帶參數(shù),這個(gè)function的兩個(gè)形參分別是通過(guò)exit()傳入的int型 和 通過(guò)on_exit()傳入的*arg//同一個(gè)函數(shù)可以被多次注冊(cè),注冊(cè)一次退出進(jìn)程時(shí)就會(huì)被執(zhí)行一次//fork()出的子進(jìn)程會(huì)繼承父進(jìn)程的注冊(cè)函數(shù),但一旦調(diào)用了exec(),所有注冊(cè)的函數(shù)都會(huì)被移除//成功返回0,失敗返回非0int on_exit(void (*function)(int , void *), void *arg);
#include
_exit()/_Exit():
//立即終止調(diào)用的進(jìn)程,所有的子進(jìn)程都會(huì)掛到PID1下,父進(jìn)程會(huì)收到SIGCHLD信號(hào),還可以用wait()接收退出碼void _exit(int status); //
#include
Orphan VS Zombie
Orphan Process:一個(gè)parent退出,而它的一個(gè)或多個(gè)child還在運(yùn)行,那么這些child將成為orphan。將被init(PID==1)收養(yǎng),并由init對(duì)它們完成狀態(tài)收集工作。init會(huì)循環(huán)地wait()直到這些child完成了他們的工作. 即當(dāng)一個(gè)孤兒進(jìn)程凄涼地結(jié)束了其生命周期的時(shí)候,init進(jìn)程就會(huì)代表黨和政府出面處理它的一切善后工作。因此孤兒進(jìn)程并不會(huì)有什么危害。
Zombie Process:?一個(gè)使用fork()創(chuàng)建的child,如果child退出,而parent并沒(méi)有調(diào)用wait/waitpid獲取child的狀態(tài)信息,那么child的process descriptor、PID和PCB等資源仍然保存在系統(tǒng)中。此時(shí)的child就變成了zombie。因?yàn)橄到y(tǒng)的PID總數(shù)是有限的, parent不斷的創(chuàng)建child而不去wait,系統(tǒng)早晚會(huì)被拖垮.
總結(jié):
- Orphan/Zombie都是因?yàn)樵趐arent中沒(méi)有wait掉child, 不同之處是orphan的parent已經(jīng)沒(méi)了, 由init來(lái)接管了,而zombie有個(gè)缺德的parent, 不wait還不撒手,拖累了系統(tǒng)
- $ps 一下Zombie的進(jìn)程狀態(tài)是’Z’
wait(), waitpid(), waitid()
//wait for process to change state//wait()掛起父進(jìn)程,直到一個(gè)子進(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 //如果沒(méi)有子進(jìn)程終止就立即返回return immediately if no child has exited.WUNTRACED //如果一個(gè)子進(jìn)程stoped且沒(méi)有被traced,那么立即返回WCONTINUED (since Linux 2.6.10) //如果stoped的子進(jìn)程通過(guò)SIGCONT復(fù)蘇,那么立即返回 *//*如果退出不是NULL,wait()會(huì)使用形參指針帶出退出碼,這個(gè)退出碼可以使用下列宏解讀WIFEXITED(status) //如果子進(jìn)程正常退出返回真WEXITSTATUS(status) //返回子進(jìn)程的退出碼,當(dāng)且僅當(dāng)WIFEXITED為真時(shí)有效WIFSIGNALED(status) //如果子進(jìn)程被一個(gè)信號(hào)終止時(shí)返回真WTERMSIG(status) //返回終止子進(jìn)程的信號(hào)編號(hào),當(dāng)且僅當(dāng)WIFSIGNALED為真時(shí)有效WCOREDUMP(status) //如果子進(jìn)程導(dǎo)致了"核心已轉(zhuǎn)儲(chǔ)"則返回真,當(dāng)且僅當(dāng)WIFSIGNALED為真時(shí)有效rWIFSTOPPED(status) //如果子進(jìn)程被一個(gè)信號(hào)暫停時(shí)返回真,當(dāng)且僅當(dāng)調(diào)用進(jìn)程使用WUNTRACED或子進(jìn)程正在traced時(shí)有效WSTOPSIG(status) //返回引起子進(jìn)程暫停的信號(hào)編號(hào),當(dāng)且僅當(dāng)WIFSTOPPED為真時(shí)有效WIFCONTINUED(status)(since Linux 2.6.10)//如果子進(jìn)程收到SIGCONT而復(fù)蘇時(shí)返回真*/
if(0==pid){ … exit(100); //把child的退出狀態(tài)信息設(shè)為100}int status=0;int res=wait(&status); //status用來(lái)接收結(jié)果if(-1==res) perror("wait"),exit(-1);if(WIFEXITED(status)) //ATTENTION:這個(gè)宏要int不是int*,和wait不一樣 printf("child%d end normally, status is:%d\n",res,WEXITSTATUS(status)); //將打印出exit()里的狀態(tài)
例子
/*------file.c------*/#include
Note:
- 運(yùn)行結(jié)果a.txt兩個(gè)進(jìn)程沒(méi)有覆蓋=>父子進(jìn)程使用的讀寫(xiě)位置信息是同一份=>文件表是同一份=>但是兩個(gè)是不同的fd, 所以fork()創(chuàng)建子進(jìn)程也會(huì)復(fù)制一個(gè)文件描述符總表
- 正是因?yàn)槭褂米x寫(xiě)一次 offset會(huì)向后移, 所以沒(méi)有覆蓋, 因?yàn)楹髞?lái)的是使用前面留下的offset的位置, 所以使用的讀寫(xiě)信息是一樣的
/*--------------------------------------------child終止時(shí)自動(dòng)釋放malloc()----------------------------------------------*/#include
Note:
- 用全局變量做橋梁
- atexit()里面的函數(shù)一定是int *(void)?函數(shù)的形參列表變了也不行
- ATTENTION:?vfork()的child雖然整個(gè)內(nèi)存區(qū)都是和parent共享的, 但是變量的作用域還是在啊, 所以你跨函數(shù)使用變量肯定要傳參的啊
/*--------------------------------------------on_exit.c, child終止時(shí)自動(dòng)釋放malloc()----------------------------------------------*/#include
評(píng)論