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