三:execve系统调用 int execve(const char *filename, char *const argv[],char *const envp[]); fork创建了一个新的进程,产生一个新的PID execve用被执行的程序完全替换了调用进程的映像。 execve启动一个新程序,替换原有进程,所以被执行进程的PID不会改变。 execve函数接受三个参数 --path 要执行的文件完整路径 --argv 传递给程序完成参数列表,包括argv[0],它一般是执行程序的名字,最后一个参数一般是NULL --envp 是指向执行execed程序的环境指针,一般设为NULL
//execve函数的定义 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> int main(int arg, char * args[]) { /* 第一个参数是程序的名字,第二个参数是被调用程序的参数,最后一个参数必须是NULL 这个跟main函数的参数args数组很相似 */ char * argv[]={"/bin/ls","-l",NULL}; execve("/bin/ls",argv,NULL); /* execve函数是替换原来的程序代码,但是进程PID不会变,文件描述符不会变,只是程序代码被替换了 所以execve函数后面的语句都不会被执行 */ printf("program is end! "); return 0; }
wait和waitpid函数可以收集子进程的退出状态 pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); stauts保存子进程的退出状态(本质上就是子进程的返回值,但是我们不能直接查看这个返回值,必须用宏定义转化一下) 在waitpid函数中,参数pid是等待进程的PID,他能够接受一下四种值 "<-1" 等待任何PGID等于PID的绝对值子进程 "-1" 等待任何子进程(这个进程创建的子进程) "0" 等待任何PGID等于调用进程的子进程 ">0" 等待PID等于pid的子进程
//wait函数的定义 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main(int arg, char * args[]) { pid_t child=0; int status=0; child=fork(); if(child==-1) { printf("system is game over! "); return -1; } if(child==0) { //in child process printf("child is begining! "); sleep(3); printf("child is end , now is your turn! "); }else{ //in parent process printf("parent is begining , i am wait my son! "); wait(&status); printf("child status=%d ",WEXITSTATUS(status)); printf("my son is end , now i am go ! "); } printf("all is end! "); return 10; }
用fork或者execve函数创建一个新进程,为了收集新进程的退出状态并且防止出现僵死进程(zombie process),父进程应该调用wait或者waitpid函数等待进程终止。
僵死进程
一个僵死进程是在父进程退出之前就终止的子进程(子进程在父进程之前终止,此时因为父进程还没有退出,所以系统会保留子进程的pid,退出
状态,返回给父进程有wait函数,如果父进程没有wait函数,系统就会一直保持子进程的PID等信息,直到父进程退出)
之所以被称为僵死进程是因为他虽然死掉了,但依然在进程表中存在。
子进程退出后分配给他的内存和其他资源都被释放,但是子进程还在内核进程表保留一条,内核在父进程回收子进程的退出状态前一直保留它。
有一个两个僵死进程不算什么问题,但一旦一个程序频繁执行fork或者execv却又不能手机退出状态,那么最终将会填满进程表(内核进程表是有上限的),这会影响性能,可能导致系统重启
孤儿进程
孤儿进程是一个父进程在调用wait或者waitpid之前就已经退出了(即父进程在子进程之前退出)。此时init进程成为子进程的父进程。
init进程成为子进程的父进程,收集子进程的退出状态,从而避免出现僵死进程。
注意:任何一个进程都必须有父进程,init的父进程是0号进程,父进程与子进程是相对独立的(子进程会拷贝进程控制器和代码区,写时拷贝数据区),父进程退出,子进程不一定会退出