fork()与vfork()区别

本文详细解释了fork和vfork两种进程创建方式的区别,包括内存段的处理方式、执行顺序及潜在的死锁问题,并通过实例代码展示了这些差异。

转自:http://blog.youkuaiyun.com/jianchi88/article/details/6985326

fork()与vfock()都是创建一个进程,那他们有什么区别呢?总结有以下三点区别: 
1.  fork  ():子进程拷贝父进程的数据段,代码段 
    vfork ( ):子进程与父进程共享数据段 
2.  fork ()父子进程的执行次序不确定 
    vfork 保证子进程先运行,在调用exec 或exit 之前与父进程数据是共享的,在它调用exec
     或exit 之后父进程才可能被调度运行。 
3.  vfork ()保证子进程先运行,在她调用exec 或exit 之后父进程才可能被调度运行。如果在
   调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。 
下面通过几个例子加以说明: 
第一:子进程拷贝父进程的代码段的例子: 

[cpp]  view plain  copy
  1. #include<sys/types.h>  
  2. #include<unistd.h>  
  3. #include<stdio.h>  
  4.   
  5. int main()  
  6. {  
  7.     pid_t pid;  
  8.     pid = fork();  
  9.     if(pid<0)  
  10.         printf("error in fork!\n");  
  11.     else if(pid == 0)  
  12.         printf("I am the child process,ID is %d\n",getpid());  
  13.     else   
  14.         printf("I am the parent process,ID is %d\n",getpid());  
  15.     return 0;  
  16.   
  17. }  


运行结果: 

[cpp]  view plain  copy
  1. [root@localhost fork]# gcc -o fork fork.c   
  2. [root@localhost fork]# ./fork  
[cpp]  view plain  copy
  1. I am the child process,ID is 4711  
  2. I am the parent process,ID is 4710  


为什么两条语 都会打印呢?这是因为fork()函数用于从已存在的进程中创建一个新的进 
程,新的进程称为子进程,而原进程称为父进程,fork ()的返回值有两个,子进程返回0,
父进程返回子进程的进程号,进程号都是非零的正整数,所以父进程返回的值一定大于零,
在pid=fork();语句之前只有父进程在运行,而在pid=fork();之后,父进程和新创建的子进程 
都在运行,所以如果pid==0,那么肯定是子进程,若pid !=0 (事实上肯定大于0),那么是 
父进程在运行。而我们知道fork()函数子进程是拷贝父进程的代码段的,所以子进程中同样 
有 
if(pid<0) 
         printf("error in fork!"); 
     else if(pid==0) 
         printf("I am the child process,ID is %d\n",getpid()); 
     else 
         printf("I am the parent process,ID is %d\n",getpid()); 

这么一段代码,所以上面这段代码会被父进程和子进程各执行一次,最终由于子进程的pid= =0,

而打印出第一句话,父进程的pid>0,而打印出第二句话。于是得到了上面的运行结果。 
再来看一个拷贝数据段的例子: 

[cpp]  view plain  copy
  1. #include<sys/types.h>  
  2. #include<unistd.h>  
  3. #include<stdio.h>  
  4.   
  5. int main()  
  6. {  
  7.     pid_t pid;  
  8.     int cnt = 0;  
  9.     pid = fork();  
  10.     if(pid<0)  
  11.         printf("error in fork!\n");  
  12.     else if(pid == 0)  
  13.     {  
  14.         cnt++;  
  15.         printf("cnt=%d\n",cnt);  
  16.         printf("I am the child process,ID is %d\n",getpid());  
  17.     }  
  18.     else  
  19.     {  
  20.         cnt++;  
  21.         printf("cnt=%d\n",cnt);  
  22.         printf("I am the parent process,ID is %d\n",getpid());  
  23.     }  
  24.     return 0;  
  25. }  

 

大家觉着打印出的值应该是多少呢?是不是2 呢?先来看下运行结果吧 

[cpp]  view plain  copy
  1. [root@localhost fork]# ./fork2  
  2. cnt=1  
  3. I am the child process,ID is 5077  
  4. cnt=1  
  5. I am the parent process,ID is 5076  

 

为什么不是2 呢?因为我们一次强调fork ()函数子进程拷贝父进程的数据段代码段,所以 
cnt++; 
    printf("cnt= %d\n",cnt);

    return 0 
将被父子进程各执行一次,但是子进程执行时使自己的数据段里面的(这个数据段是从父进 
程那copy 过来的一模一样)count+1,同样父进程执行时使自己的数据段里面的count+1, 
他们互不影响,与是便出现了如上的结果。


那么再来看看vfork ()吧。如果将上面程序中的fork ()改成vfork(),运行结果是什么 
样子的呢? 

[cpp]  view plain  copy
  1. [root@localhost fork]# gcc -o fork3 fork3.c   
  2. [root@localhost fork]# ./fork3  
  3. cnt=1  
  4. I am the child process,ID is 4711  
  5. cnt=1  
  6. I am the parent process,ID is 4710  
  7. 段错误  

 

本来vfock()是共享数据段的,结果应该是2,为什么不是预想的2 呢?先看一个知识点: 
vfork 和fork 之间的另一个区别是:vfork 保证子进程先运行,在她调用exec 或exit 之 
后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动 
作,则会导致死锁。 
这样上面程序中的fork ()改成vfork()后,vfork ()创建子进程并没有调用exec 或exit,
所以最终将导致死锁。 
怎么改呢?看下面程序: 

[cpp]  view plain  copy
  1. #include<sys/types.h>  
  2. #include<unistd.h>  
  3. #include<stdio.h>  
  4.   
  5. int main()  
  6. {  
  7.     pid_t pid;  
  8.     int cnt = 0;  
  9.     pid = vfork();  
  10.     if(pid<0)  
  11.         printf("error in fork!\n");  
  12.     else if(pid == 0)  
  13.     {  
  14.         cnt++;  
  15.         printf("cnt=%d\n",cnt);  
  16.         printf("I am the child process,ID is %d\n",getpid());  
  17.        _exit(0);  
  18.     }  
  19.     else  
  20.     {  
  21.         cnt++;  
  22.         printf("cnt=%d\n",cnt);  
  23.         printf("I am the parent process,ID is %d\n",getpid());  
  24.     }  
  25.     return 0;  
  26.   
  27. }  


 

如果没有_exit(0)的话,子进程没有调用exec 或exit,所以父进程是不可能执行的,在子 
进程调用exec 或exit 之后父进程才可能被调度运行。 
所以我们加上_exit(0);使得子进程退出,父进程执行,这样else 后的语句就会被父进程执行, 
又因在子进程调用exec 或exit之前与父进程数据是共享的,所以子进程退出后把父进程的数 
据段count改成1 了,子进程退出后,父进程又执行,最终就将count变成了2,看下实际 
运行结果: 

[cpp]  view plain  copy
  1. [root@localhost fork]# gcc -o fork3 fork3.c   
  2. [root@localhost fork]# ./fork3  
  3. cnt=1  
  4. I am the child process,ID is 4711  
  5. cnt=2  
  6. I am the parent process,ID is 4710  

 

网上抄的一段,可以再理解理解: 
为什么会有vfork,因为以前的fork 很傻, 它创建一个子进程时,将会创建一个新的地址 
空间,并且拷贝父进程的资源,而往往在子进程中会执行exec 调用,这样,前面的拷贝工 
作就是白费力气了,这种情况下,聪明的人就想出了vfork,它产生的子进程刚开始暂时与 
父进程共享地址空间(其实就是线程的概念了),因为这时候子进程在父进程的地址空间中 
运行,所以子进程不能进行写操作,并且在儿子 霸占”着老子的房子时候,要委屈老子一 
下了,让他在外面歇着(阻塞),一旦儿子执行了exec 或者exit 后,相 于儿子买了自己的 
房子了,这时候就相 于分家了。


<think> 首先,用户的问题是关于forkvfork区别及用法。我需要基于用户提供的引用内容来回答问题。用户引用了几个来源,我需要整合这些信息。 关键点从引用中提取: - 引用[1]: vfork的底层实现有问题,可能导致段错误,但使用exit退出可以避免。fork父子进程交替运行,vfork则父进程阻塞直到子结束。fork有写时拷贝,vfork没有。vfork必须用exit或exec来恢复父进程vfork性能更高但不建议使用。 - 引用[2]: fork创建进程,有两个返回值(父返回子PID,子返回0)。vfork不同:fork完全复制资源,调度不确定;vfork保证子先运行。 - 引用[3]: vfork保证子先运行,在exec或exit后父才运行;否则可能死锁。vfork共享地址空间。 - 引用[4]: fork有独立地址空间,vfork共享地址空间,所以子可修改父变量。 总结区别: 1. **地址空间管理**: - fork: 使用写时拷贝(copy-on-write),父子有独立地址空间。 - vfork: 共享地址空间,没有写时拷贝;子进程修改数据会影响父进程。 2. **执行顺序**: - fork: 父子进程调度不确定,取决于系统。 - vfork: 保证子进程先运行,父阻塞直到子exit或exec。 3. **返回值**: - fork: 父返回子PID,子返回0。 - vfork: 类似fork,返回值相同。 4. **安全性和用法**: - vfork: 必须立即调用exec或exit;否则可能导致死锁或段错误。不建议使用,因为有底层问题。 - fork: 更安全,适合通用多进程场景。 5. **性能**: - vfork: 性能更高,因为不复制地址空间。 - fork: 由于写时拷贝,开销较高,但优化后较好。 用法: - fork: 用于创建进程,继承父资源,常用于多进程编程。 - vfork: 主要用于exec新程序;创建后立即exec,避免地址空间问题。 用户指令: - 回答结构清晰,逐步解决问题。 - 使用中文回答。 - 行内数学表达式用$...$,但我看这个问题可能不需要数学表达式,除非涉及算法。 - 独立公式用$$...$$,但似乎不相关。 - 生成相关问题。 - 引用的段落末尾添加引用标识,如[^1]。 我的回答结构: 1. **介绍forkvfork**:简述两者都是创建进程的系统调用。 2. **区别**:列表或分段描述核心区别。 3. **用法示例**:给出简单代码示例(用代码块)。 4. **建议**:基于引用,建议使用fork,避免vfork。 5. **生成相关问题**:在末尾添加相关问题。 代码示例:我需要提供一个简单的例子。引用中没有代码,但用户的问题是关于用法,所以我可以添加一个forkvfork的简单用法代码。 在Python中,fork可以通过os.fork实现,但vfork在一些系统上不存在或相同;通常用C语言演示。用户之前的示例是关于排序的Python代码,但这里涉及系统调用,我应该用C语言,因为fork/vfork是POSIX系统调用。 我的回答应使用中文。 生成相关问题: - 类似用户提供的示例:快速排序相关问题。 - 相关问题可以包括: 1. 为什么vfork不建议使用? 2. forkvfork在性能上的具体差异? 3. 如何安全地使用vfork? 4. exec函数的用法是什么? 5. fork的应用场景。 引用标识:在回答中,当我引用信息时,在末尾加[^1],[^2]等。用户提供了引用[1]到[4],我需要对应。 例如,当说vfork有底层问题时,引用[^1]。 完整的回答大纲: 1. 开头:解释forkvfork的作用。 2. 主要区别: - 地址空间管理 [^4] - 执行顺序 [^1], [^3] - 返回值 [^2] - 安全性和限制 [^1], [^3] - 性能 [^1] 3. 用法示例:提供C语言代码片段。 - fork示例 - vfork示例(强调必须exit或exec) 4. 建议:优先使用fork,避免vfork除非特定场景 [^1] 5. 相关问题:列出3-5个相关问题。 确保回答真实可靠:基于提供的引用和标准知识。 </think> ### fork函数vfork函数的区别用法详解 在Unix/Linux系统编程中,`fork`和`vfork`都是用于创建进程的系统调用,但它们在行为、性能和安全性上有显著差异。以下是基于核心引用内容的详细解析(引用来源见末尾标识)。 #### 一、核心区别总结 1. **地址空间管理**: - `fork`:使用写时拷贝(copy-on-write)技术,创建进程时父子进程拥有独立的地址空间。子进程修改数据不会影响父进程[^4]。 - `vfork`:父子进程共享相同的地址空间,没有写时拷贝机制。子进程可以直接修改父进程的变量;如果子进程写入数据而不调用`exec`或`exit`,可能导致父进程数据损坏或段错误(segmentation fault)[^4][^1]。 2. **执行顺序**: - `fork`:父子进程的运行顺序不确定,取决于系统调度算法;两者可能交替执行[^1][^2]。 - `vfork`:保证子进程先运行,父进程被阻塞(暂停),直到子进程调用`exec`或`exit`后才恢复[^1][^3]。如果子进程在`exec`或`exit`前依赖父进程操作,可能导致死锁[^3]。 3. **返回值**: - 两者返回值相同:在父进程中返回子进程的PID(进程ID),在子进程中返回0[^2]。这可用于代码分支: ```c pid_t pid = fork(); // 或 vfork() if (pid == 0) { // 子进程代码 } else if (pid > 0) { // 父进程代码 } else { // 错误处理 } ``` 4. **性能**: - `vfork`:性能更高,因为它不复制地址空间(即使是写操作也不拷贝)。这在资源受限的系统上更有优势[^1]。 - `fork`:由于写时拷贝的开销,性能略低,但现代系统通过优化减小了差距[^1]。 5. **安全性可靠**: - `vfork`:底层实现不稳定,在不同系统中可能存在固有缺陷(如段错误风险)。除非子进程立即调用`exec`或`exit`,否则不建议使用[^1][^3]。 - `fork`:更安全可靠,适用于大多数多进程场景,如并行任务处理或守护进程创建。 #### 二、典型用法示例 以下是C语言代码示例,展示`fork`和`vfork`的基本用法(编译时需包含`<unistd.h>`和`<sys/wait.h>`): **1. fork用法示例(安全通用)** 创建独立子进程,父子进程交替打印消息: ```c #include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t pid = fork(); // 创建进程 if (pid == 0) { printf("子进程 PID: %d\n", getpid()); // 子进程输出 } else if (pid > 0) { wait(NULL); // 父进程等待子进程结束 printf("父进程 PID: %d, 子进程 PID: %d\n", getpid(), pid); } else { perror("fork失败"); return 1; } return 0; } ``` - **输出可能**:子进程输出后父进程输出,或反之(顺序不确定)。 - **优点**:独立地址空间防止数据冲突,适合长期运行的子进程。 **2. vfork用法示例(需谨慎)** 仅用于子进程立即执行新程序(如`exec`系列函数),父进程阻塞等待: ```c #include <stdio.h> #include <unistd.h> #include <stdlib.h> // 包含exit int main() { pid_t pid = vfork(); // 创建进程,共享地址空间 if (pid == 0) { printf("子进程修改共享变量\n"); execlp("ls", "ls", "-l", NULL); // 立即执行新程序 // 如果execlp失败,必须调用exit避免死锁 exit(1); // 安全退出 } else if (pid > 0) { wait(NULL); // 父进程阻塞,直到子进程结束 printf("父进程恢复运行\n"); } else { perror("vfork失败"); return 1; } return 0; } ``` - **关键点**: - 子进程必须调用`exec`或`exit`,否则父进程可能无法恢复或导致段错误[^1][^3]。 - 此处`execlp`执行`ls -l`命令后自动终止子进程。 - **适用场景**:高性能要求下的快速进程创建,且子进程立即替换为新程序(如脚本执行)。 #### 三、使用建议 - **优先使用`fork`**:在大多数场景下(如并行计算、服务器进程管理),`fork`更安全可靠。现代系统的写时拷贝优化使其性能接近`vfork`[^1]。 - **限制`vfork`使用**:仅在子进程需要立即调用`exec`时考虑`vfork`,且必须确保调用`exit`或`exec`以避免风险[^1][^3]。由于系统兼容性问题(某些系统可能存在固有缺陷),强烈建议测试验证。 - **替代方案**:在需要高性能进程创建时,可考虑`posix_spawn`或线程(pthread)等更现代的API。 #### 四、引用标识 - vfork的底层问题必须通过exit避免段错误[^1]。 - forkvfork的执行顺序差异[^1][^2][^3]。 - 地址空间共享是vfork的核心风险点[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值