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