fork函数、wait函数与exec函数族
概述
fork函数是用于创建进程的函数,它是一个系统函数。若要使用fork函数,需要先包含<unistd.h>头文件。
使用fork
先看一段代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hello (pid:%d)\n", (int) getpid());
//返回子进程的PID
int rc = fork();
if (rc < 0)//返回值小于0,说明创建进程失败
{
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0)//返回值等于0,说明在运行子进程
{
printf("child (pid:%d)\n", (int) getpid());
}
else
{
printf("parent of %d (pid:%d)\n",
rc, (int) getpid());
}
return 0;
}
fork函数实际上只是把父进程的代码拷贝了一份到子进程的空间中,然后返回。也就是说,子进程和父进程的代码是一模一样的,父进程调用完fork函数之后返回,子进程是从调用fork函数处开始执行,并不是从main函数开始执行。
但是要注意,这种拷贝并不是一种“精确”的拷贝,因为在父进程和子进程中,rc的值是不同的。子进程中rc的值是0,父进程中rc的值是其子进程的PID。通过rc的值我们可以区分父进程和子进程。
运行程序,输出结果如下:

父进程调用fork函数之后,子进程就创建了,此时有两个可以处于活跃状态的进程:父进程及其子进程。到底哪个进程先执行,是没有确切答案的。哪个进程先运行取决于CPU调度程序。
等待(wait)
有时候我们需要让某个进程等待另一个进程执行完毕后再执行,这时就需要用到wait()函数。下面的示例代码让父进程等待子进程运行结束后再执行自身的代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
printf("hello (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0)
{
printf("child (pid:%d)\n", (int) getpid());
}
else
{
int rc_wait = wait(NULL);
printf("parent of %d (rc_wait:%d) (pid:%d)\n",rc, rc_wait, (int) getpid());
}
return 0;
}
在父进程的代码中,调用了wait函数,该函数原型如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
其中,status参数用于保存结束进程的状态信息,它是一个指向整型的指针。如果调用者不关心子进程的结束状态,可以将status设置为NULL。如果wait函数成功,它会返回结束子进程的进程ID;如果调用进程没有子进程,wait函数会失败并返回-1,同时errno会被设置为ECHILD。
上述程序运行后,我们可以发现是子进程先运行,再运行父进程。
exec族函数
fork函数只能创建父进程的拷贝,通过使用exec族函数,我们可以为自己想要运行的程序创建进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
printf("hello (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0)
{
printf("child (pid:%d)\n", (int) getpid());
char *myargs[2];
myargs[0] = strdup("./my_code.out");
myargs[1] = NULL;
execvp(myargs[0], myargs);
//如果my_code.out成功运行,就不会执行下面的printf语句
printf("this shouldn’t print out");
}
else
{
int rc_wait = wait(NULL);
printf("parent of %d (rc_wait:%d) (pid:%d)\n",
rc, rc_wait, (int) getpid());
}
return 0;
}
execvp函数的原型定义在unistd.h头文件中,因此在使用前需要包含这个头文件。函数的基本用法如下:
#include <unistd.h>
int execvp(const char *file, char *const argv[]);
需要注意的是第一个参数,execvp函数会从所谓的环境变量中寻找可执行文件,如果自己的程序没有加入到环境变量中,execvp函数就不会正常执行,而是报没有找到文件的错。所以想要运行不在环境变量中的文件,需要使用完整路径。
此外还需要注意,execvp函数不会创建新的进程,而是用第一个参数指定的可执行文件覆盖掉当前进程的代码。

浙公网安备 33010602011771号