概述 多進程代碼區模型(其他區參見copy on write): getpid()、getppid() getuid()、geteuid() getgid(),getegid() fork() include include if(0==pid){ int res=execl("./proc","p ...
概述
多進程代碼區模型(其他區參見copy-on-write):
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
getpid()/getuid()/getgid() //獲得PID/UID/GID
fork()/vfork() //創建子進程
exec() family //替代子進程
atexit()/on_exit()/exit()/_exit()/_Exit() //退出子進程
wait()/waitpid() //獲得子進程退出狀態
getpid()、getppid()
//getpid() 返回調用進程的PID
//getppid() 返回調用進程的父進程的PID
pid_t getpid(void); //pid_t是int
pid_t getppid(void);
getuid()、geteuid()
getuid()返回調用進程的UID
geteuid()返回調用進程的effective UID
uid_t getuid(void); //uid_t是unsigned int
uid_t geteuid(void);
getgid(),getegid()
//getgid()返回調用進程的real GID
//getegid()返回調用進程的effective GID
id_t getgid(void); //gid_t是unsigned int
gid_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,失敗在父進程中返回-1
pid_t fork(void);
#include<unistd.h>
#include<stdlib.h>
pid_t pid=fork();
if(-1==pid)
perror(“fork”),exit(-1);
if(0==pid){
//child process
exit(0);
}
int main(){
pid_t pid=fork();
if(-1==pid)
perror("fork"),exit(-1);
printf("pid=%d\n",pid);
if(0==pid){
printf("I'm child,my PID:%d,my parent's PID:%d\n",getpid(),getppid());
sleep(3);
printf("I'm child,my PID:%d,my parent's PID:%d\n",getpid(),getppid());
}else{
sleep(1);
printf("I'm parent,my PID:%d, my child's PID:%d\n",getpid(),pid);
}
return 0;
}
$./a.out
pid=2915
pid=0
I'm child, my PID:2915, my parent's PID:2914
I'm parent,my PID:2914, my child's PID:2915
I'm child, my PID:2915, my parent's PID:1 #一個Orphan
#卡在這, 因為兩個進程搶一個終端,不是死迴圈,直接[ENTER]就行
//創建任意多個進程:子進程幹活,父進程創建一個爹一堆兒子
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()
}
}
父子進程代碼區執行次序
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就有了相應的新的物理地址
vfork()
//創建子進程,父進程會等待子進程退出之後在繼續執行,還是順序執行, 在子進程執行期間,父進程被掛起,此期間子進程和父進程共用所有的記憶體資源,vfork()多用在在不拷貝餓哦父進程頁表的情況下創建新的進程,單獨使用沒有多線程的價值, 主要和exec()搭配使用。
//子進程終止時不能從當前函數返回/調用exit函數, 可以調用_exit(), 該函數保證了子進程先於父進程執行
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 starts
parent starts
parent 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設errno
extern 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<unistd.h>
#include<sys/types.h>
if(0==pid){
int res=execl("./proc","proc",NULL); //"ls"就是執行方式, 以字元串的形式傳進來, “proc”也是
if(-1==res)
perror("execl"),_exit(-1); //ATTENTION,vfork用_exit()
}
7種進程終止
- 正常終止:
- 從 main() 返回
- 調用 exit() / _exit() / _Exit()
- 最後一個線程從其啟動常式返回
- 最後一個線程調用pthread_exit()
- 異常終止:
- 調用abort()
- 接到一個信號並終止
- 最後一個線程對取消請求作出響應
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,失敗返回非0
int atexit(void (*function)(void)); //參數是函數指針
on_exit()
//和atexit()類似,用於註冊exit()時執行的函數, 不同之處是on_exit註冊的函數可以帶參數,這個function的兩個形參分別是通過exit()傳入的int型 和 通過on_exit()傳入的*arg
//同一個函數可以被多次註冊,註冊一次退出進程時就會被執行一次
//fork()出的子進程會繼承父進程的註冊函數,但一旦調用了exec(),所有註冊的函數都會被移除
//成功返回0,失敗返回非0
int on_exit(void (*function)(int , void *), void *arg);
#include<stdlib.h>
void fa(int status,void* pv){
printf("obtained status=%d\n",status);
free(pv);
pv=NULL;
}
int main(){
pid_t pid=fork();
if(0==pid){
…
int *pi=(int*)malloc(sizeof(int)); //沒有錯誤處理
if(0!=on_exit(fa,pi))
perror("on_exit"),exit(-1);
…
exit(100); //terminate child and free dynamic memory automatically
}
}
_exit()/_Exit():
//立即終止調用的進程,所有的子進程都會掛到PID1下,父進程會收到SIGCHLD信號,還可以用wait()接收退出碼
void _exit(int status); //<unistd.h>
void _Exit(int status); //<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
void fa(){
printf("所有需要善後處理的工作都可以交給我哦!\n");
}
int main(){
int res=atexit(fa);
if(0!=res)
perror("atexit"),exit(-1);
printf("main start\n");
_exit(0); //立即終止,如果這句話留著fa()不會執行
printf("main end\n");
return 0; //把_exit()註釋掉就會執行fa()
}
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仍然保存在系統中。此時的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設errno
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
/* pid in waitpid() and kill()
pid>0 //指定pid
pid=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為真時有效r
WIFSTOPPED(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<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
int fd=open("a.txt",O_RDWR|O_TRUNC|O_CREAT,0664);
pid_t pid=fork();
if(-1==pid)
perror("fork"),exit(-1);
if(0==pid){
printf("child:%d start\n",getpid());
// char buf[]="world"; //這個變數沒必要,常量就行
int res=write(fd,"hello",sizeof("hello"));
if(-1==res)
perror("child write"),exit(-1);
res=close(fd); //child把parent的數據複製過來,所以fd需要關閉
if(-1==res)
perror("child close"),exit(-1);
printf("parent:%d end\n",getpid());
exit(0);
}
printf("parent:%d start\n",getpid());
sleep(1);
int res=write(fd,"world",sizeof("world"));
if(-1==res)
perror("parent write"),exit(-1);
res=close(fd);
if(-1==res)
perror("parent close"),exit(-1);
printf("parent:%d end\n",getpid());
return 0;
}
Note:
- 運行結果a.txt兩個進程沒有覆蓋=>父子進程使用的讀寫位置信息是同一份=>文件表是同一份=>但是兩個是不同的fd, 所以fork()創建子進程也會複製一個文件描述符總表
- 正是因為使用讀寫一次 offset會向後移, 所以沒有覆蓋, 因為後來的是使用前面留下的offset的位置, 所以使用的讀寫信息是一樣的
/*--------------------------------------------
child終止時自動釋放malloc()
----------------------------------------------*/
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#define PI 3.141592657
int *ipdata=NULL;
///*
void fa(){
free(ipdata);
ipdata=NULL;
}
//*/
int main(){
pid_t pid=fork();
if(-1==pid)
perror("fork"),exit(-1);
if(0==pid){
printf("1");
printf("child starts\n");
ipdata=(int*)malloc(sizeof(int));
if(NULL==ipdata)
printf("malloc fails\n");
// perror("malloc"),exit(-1); //???
// *ipdata=data;
int res=atexit(fa);
if(0!=res){
printf("atexit fails\n");
exit(EXIT_FAILURE);
}
printf("Please input a radius:");
scanf("%d",ipdata);
printf("primeter is:%lf\n",2*PI*(*ipdata));
exit(EXIT_SUCCESS);
}
printf("parent starts\n");
int stat=0;
pid=wait(&stat);
if(WIFEXITED(stat))
printf("child has terminated,its status:%d\n",WEXITSTATUS(stat));
return 0;
}
Note:
- 用全局變數做橋梁
- atexit()裡面的函數一定是int *(void)函數的形參列表變了也不行
- ATTENTION: vfork()的child雖然整個記憶體區都是和parent共用的, 但是變數的作用域還是在啊, 所以你跨函數使用變數肯定要傳參的啊
/*--------------------------------------------
on_exit.c, child終止時自動釋放malloc()
----------------------------------------------*/
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void fa(int status,void* pv){
printf("obtained status=%d\n",status);
free(pv);
pv=NULL;
}
int main(){
//創建子進程,使用fork()
pid_t pid=fork();
if(-1==pid){
printf("parent starts%d\n",pid);
}
if(0==pid){
printf("child starts%d\n",getpid());
int *pi=(int*)malloc(sizeof(int));
if(NULL==pi)
printf("malloc error"),exit(-1);
//use on_exit()register function
if(0!=on_exit(fa,pi))
perror("on_exit"),exit(-1);
printf("please input a radius(int)\n");
scanf("%d",pi);
printf("the primeter is:%lf\n",2*3.14*(*pi));
//terminate child and free dynamic memory automatically
exit(100);
}
//父進程等待子進程終止, 獲取退出狀態
int status=0;
int res=waitpid(pid,&status,0);
if(-1==res)
perror("waitpid"),exit(-1);
if(WIFEXITED(status))
printf("status of child:%d\n",WEXITSTATUS(status));
return 0;
}