linux操作系统中,这两个函数都是用来创建子进程
# include <unistd.h>
pid_t fork(void);
pid_t vfork(void);
成功:子进程中返回 0,父进程中返回子进程 ID。pid_t,为无符号整型。
失败:返回 -1。
fork和vfork的区别
fork特点:fork创建子进程的时候是完全拷贝一份父进程的资源,子进程独立于父进程
fork优点:父子进程相互独立,子进程对父进程中同名变量进行修改并不会影响其在父进程中的值。
vfork特点:vfork创建的子进程和父进程共享地址空间,子进程完全运行在父进程的地址空间上,子进程的修改同样对父进程可见,用 vfork创建子进程后,父进程会被阻塞,直到子进程调用exec(进程替换)或exit(进程退出);
注意:vfork时父进程不是只有在子进程exit后才会执行,当子进程执行exec后父进程也会执行。
所以会有一个下面现象现象
- #include <stdio.h>
- #include <unistd.h>
- int main()
- {
- int childpid;
- if( (childpid=vfork()) ==0) //子进程
- {
- printf("child is begin\n");
- sleep(10);
- execl("./hello",(char*)0); //hello程序仅仅输出”hello,world”
- } //父进程
- printf("parent is end\n");
- return 0;
- }
运行结果为:
可以看到子进程虽然先运行了,但是父进程没有等到子进程结束就开始运行了。
vfork时父进程不是只有在子进程exit后才会执行,当子进程执行exec后父进程也会执行
代码可以改为:
- #include <stdio.h>
- #include <unistd.h>
- #include <wait.h>
- int main()
- {
- int childpid;
- if( (childpid=vfork()) ==0) //子进程
- {
- printf("child is begin\n");
- sleep(10);
- execl("./hello",(char*)0); //hello程序仅仅输出”hello,world”
- } //父进程
- wait(NULL); //等待子进程结束
- printf("parent is end\n");
- return 0;
- }
vfork优点:vfork避免了(fork函数子进程被创建后,仅仅为调用exec执行另一个程序,它对地址空间的复制是多余的)这个问题,减少 了不必要的开销。
vfork保证子进程先运行,它调用exec或exit后父进程才能调度运行,fork的父子进程运行顺序不定,取决于内核的调度算法。
父进程中的数据空间和堆、栈可能会产生副本,具体情况要看使用的是fork还是vfork,fork产生副本,vfork则共享这部分内存。
另外linux还有第三个创建进程的系统调用,Linux上创建线程一般使用的是pthread库 实际上linux也给我们提供了创建线程的系统调用,就是clone,linux中的pthread_create最终调用clone
clone函数功能强大,带了众多参数,因此由他创建的进程要比前面2种方法要复杂。
clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。
先有必要说下这个函数的结构 :
int clone(int (fn)(void ), void *child_stack, int flags, void *arg);
exec族函数:
exec 族函数一共有六个:
#include <unistd.h>
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 execve(const char *path, char *const argv[], char *const envp[]);
例如:
/* 执行 /bin/ls -al /ect/passwd */
// 执行/bin目录下的ls, 第一参数为程序名ls, 第二个参数为"-al", 第三个参数为"/etc/passwd"
execl("/bin/ls", "ls", "-al", "/etc/passwd", (char *) 0);
函数字中带字母 "l" 的表示其参数个数不确定,带字母 "v" 的表示使用字符串数组指针 argv 指向参数列表。
函数名字中含有字母 "p" 的表示可以自动在环境变量 PATH 指定的路径中搜索要执行的程序。
函数名字中含有字母 "e" 的函数比其它函数多一个参数 envp。该参数是字符串数组指针,用于指定环境变量。调用这样的函数时,可以由用户自行设定子进程的环境变量,存放在参数 envp 所指向的字符串数组中
事实上,只有 execve 是真正的系统调用,其它五个函数最终都调用 execve。这些函数之间的关系如下图所示:
exec 族函数的特征:调用 exec 族函数会把新的程序装载到当前进程中。在调用过 exec 族函数后,进程中执行的代码就与之前完全不同了,所以 exec 函数调用之后的代码是不会被执行的。
例子:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
if((pid = vfork()) < 0)
{
printf("vfork error!\n");
exit(1);
}
else if(pid == 0) //子进程
{
printf("Child process PID: %d.\n", getpid());
char *argv[ ]={"ls", "-al", "/home", NULL};
char *envp[ ]={"PATH=/bin", NULL};
if(execve("/bin/ls", argv, envp) < 0)
{
printf("subprocess error");
exit(1);
}
// 子进程要么从 ls 命令中退出,要么从上面的 exit(1) 语句退出
// 所以代码的执行路径永远也走不到这里,下面的 printf 语句不会被执行
printf("You should never see this message.");
}
else //父进程
{
printf("Parent process PID: %d.\n", getpid());
sleep(1);
}
return 0;
}