一 进程间调度—— wait()/waitpid()
进程之间的调度(如何让父进程等待子进程),wait() waitpid()就是进程之间的调度函数。
1 wait()函数
pid_t wait(int* status)
用于父进程等待子进程的结束,如果父进程有多个子进程的话,等待任意一个子进程结束都会返回。wait的返回值pid_t 可以获取结束子进程的PID,参数status用于 返回结束子进程的状态和退出码。
wait()可以回收僵尸子进程。
宏函数 WIFEXITED(status)可以判定是否正常结束
宏函数 WEXITSTATUS取退出码(exit(退出码))
(退出码后8位有效)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(){
pid_t pid = fork();
if(pid==0){//子进程
printf("子进程%d开始运行\n",getpid());
sleep(5);
printf("子进程运行结束\n");
exit(100);
} //退出码100
printf("父进程开始运行\n");
int status; pid_t pid2 = wait(&status);
printf("成功等待子进程的结束\n");
printf("pid=%d\n",pid2);
if(WIFEXITED(status)){
printf("子进程正常退出\n");
printf("code=%d\n",WEXITSTATUS(status));
}
printf("父进程结束运行\n");
}
2 waitpid()函数
pid_t waitpid(pid_t pid,int* status,int option)
pid是进程的ID,可以有4种情况:
< -1 等待 进程组ID等于参数绝对值的子进程
-1 等待任意子进程
0 等待 和父进程一个进程组的子进程
大于0 等待进程ID等于参数的子进程(指定一个)
status 和 wait参数一样
option 0 代表会一直等待直到有子进程退出(阻塞)
WNOHANG 代表不会等待,有没有子进程退出都返回(非阻塞)
返回值有三种可能:
返回结束子进程的PID
如果option为WNOHANG,没有子进程结束时返回0
出错返回 -1.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(){
pid_t pid1,pid2;
pid1 = fork();
if(pid1>0) pid2 = fork();//只有父进程fork
if(pid1 == 0){//子进程1
printf("子进程%d开始运行\n",getpid());
sleep(3);
printf("子进程%d结束运行\n",getpid());
exit(100);
}
if(pid2 == 0){//子进程2
printf("子进程%d开始运行\n",getpid());
sleep(1);
printf("子进程%d结束运行\n",getpid());
exit(200);
}
int status;
printf("pid1=%d,pid2=%d\n",pid1,pid2);
pid_t pid = waitpid(-1,&status,0);
printf("pid=%d\n",pid);
if(WIFEXITED(status)){
printf("code=%d\n",WEXITSTATUS(status));
}
printf("end\n");
}
二 vfork+exec
vfork()用法在语法上和fork()一样,区别:
1 vfork() 不复制 父进程任何的内存空间。
2 vfork() 确保子进程先运行。
vfork()创建的子进程占用父进程的内存空间运行,父进程在此时被阻塞,vfork()要和 exec系列函数结合使用才有意义。vfork 负责创建子进程,exec系列函数负责提供新的程序被执行。当vfork()创建的子进程执行新的程序时,父进程的内存空间就会返回给父进程,父子进程同时运行。
经验:
fork()创建子进程和父进程执行相同的代码。
vfork()创建子进程 执行的代码与父进程无关,而是全新的代码。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
pid_t pid = vfork();//fork();
if(pid==0){//子进程
sleep(1);
printf("child end\n");
exit(10);
}
printf("father end\n");
}
exec系列函数不是新建一个进程,而是修改进程。用新的代码区、堆区、栈区、数据区(程序) 替换旧的。
exec系列函数不改变 PID。
#include <stdio.h>
#include <unistd.h>
int main(){
printf("begin\n");
//第一个参数是程序(包括路径),第二个参数是
//命令,第三个参数是选项,第四个参数是参数
//3,4,5,... 以NULL结束
//执行ls -l /home
//execl("/bin/ls","ls","-l","/home",NULL);
//execlp在系统路径PATH中配置的程序省略路径
execlp("ls","ls","-l","/home",NULL);
printf("end\n");//不会被执行,因为新代码区
}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(){
pid_t pid = vfork();
if(pid == 0){
execlp("ls","ls","-l","/home/tarena",NULL);
//exit(100);//exec系列函数会更换代码区
}
int status;
wait(&status);
printf("code=%d\n",WEXITSTATUS(status));
printf("end\n");
}
创建子进程的方法有两种:
1 fork() - 父子进程执行相同的代码段
2 vfork()+exec系列函数 - 子进程执行完全不同的代码段。