在《UNIX环境高级编程》一书的第八章中,有一道课后习题如下:
回忆图7-3典型的存储空间布局。由于对应于每个函数调用的栈帧通常存储在栈中,并在调用 vfork后,子进程运行在父进程的地址空间中,如果不是在main函数中而是在另一个函数中调用vfork,以后子进程从该函数返回时,将会发生什么情况?
作者Rich Stevens是一位大师,留下这么一题必有其深意,于是结合《深入理解计算机系统》中的知识,写了个程序验证了下,受益良多。
首先回忆下程序运行的栈帧结构(见下图):
从图中可知,如果一个函数调用用了另外一个函数,那么被调用者的栈帧则会被压入栈顶被设置为“当前帧”,首先执行被调用者,执行完成后,调用者的栈帧被弹出程序栈,然后从“返回地址”返回到调用者的地址空间中。
于是猜想,如果在main函数中,调用了一个函数foo,则“当前帧”为foo的栈帧,这时,若调用vfork创建一个子进程,那么根据vfork的语义,子进程不会完全复制父进程的地址空间,它会在父进程的地址空间中运行(这也是为什么vfork能保证子进程先运行,而fork不能保证。因为vfork创建的子进程是与父进程共享地址空间,为了避免竞争,所以就让子进程先运行,而父进程后运行;而fork创建的子进程是父进程的副本,所以不会带来竞争问题,谁先谁后也就无所谓了),所以它共享的是“当前帧”的地址空间,因此当子进程返回时,只会改变foo的数据,而不会改变main栈帧中的数据。
下面就来写个程序验证一下:
view plain
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int glob = 88; //a global var
void foo(int);
int main(int argc,char *arg[])
{
int var = 100; //a local var in main
foo(var);
if(printf("In main var:%d glob:%d pid:%d/n",var,glob,getpid())<0)
perror("main printf");
exit(0);
}
void foo(int var)
{
pid_t pid;
int loc = 66; //a local var in foo
printf("Before vfork/n");
if((pid = vfork())<0)
perror("vfork");
else if(pid == 0) //child process
{
loc++;
var++;
glob++;
printf("pid:%d/n",getpid());
exit(0);
}
/*parent process continues here*/
printf("In foo var:%d glob:%d loc:%d pid:%d/n",var,glob,loc,getpid());
}
运行此程序,得到结果为(见下图):
果然,可以看到在foo和main中,进程号都是一样的,也就说明foo和main在同一进程中。但是各个变量的值却有差异:在foo返回后mian函数中的局部变量var依然是初始值,并没有增加,推其原因,就是因为子进程共享的是foo的栈帧数据,而非main函数的栈帧,所以自然也就不会改变main栈帧中的数据。
书中正文中说:子进程不会完全复制父进程的地址空间,它会在父进程的地址空间中运行。因此可以进一步得出一个结论:vfork创建的子进程,共享的是父进程当前栈帧的地址空间。
原文链接:http://blog.csdn.net/litingli/article/details/5122853
文章2:
本文是涉及到fork,vfork,exec和进程通信,父子进程数据共享这几个方面的讨论。
第一点,
Linux中,创建进程的方式,只有一种,那就是调用fork(或者vfork)。 当然,系统的交换进程,init进程除外,它们是操作系统自举时用特殊方式创建的最初的进程。
第二点,
举个例子,父进程A 创建子进程B 后,进程B 就拥有了A 的所有数据(包括父进程的数据空间、堆和栈)的相同副本,并且共享代码片段(正文段)。父子进程的运行路线仅靠fork的返回值来区分。子进程从调用fork 之后的代码行继续执行,这点很重要。它意味着,之前的代码只有父进程会执行,fork之后的代码靠其返回值导向不同的流向。当然,如果fork 外部嵌套了结构控制语句,情况可能会更麻烦一点。这种情况下,父子进程可能仍有机会在跳出fork语句块之后,执行一段相同的代码。
第三点,
我们之所以用fork 调用,大多数情况是在子进程中调用exec 函数来启动另一个新的程序。即运行另一个可能是其他人所编写的程序(含有main函数的完整程序)。这时,子进程B 若调用了exec,那么就意味着进程B 被kill了,进程B 从进程A 那里继承的代码从调用exec 那行开始都无效,所有的数据也被清空。所以,在调用exec 后所编写的代码都是毫无意义的。唯一保留的是B的pid号,exec 所调用的那个程序会继承B 的pid 继续执行,直到结束。
第四点,
有了上面的这些概念,下面就开始说说vfork和父子进程的数据共享。这是我现在所关心的。首先,vfork产生的子进程共享父进程的所有地址空间(无论是正文段还是数据段),这就是程序中我使用了vfork的重要原因。因为,你要知道,若不是这样,父子进程之间的通信和数据共享的代码就又够我折腾一阵子了。不用vfork的话,我可能就不得不借助于管道通信,共享内存,乃至多线程的编写来解决这个问题。另外,vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行(exec之后父进程还是可以运行的)。有一点要提醒的是,网上有人说,在Linux中vfork已经丧失了其功能,变得和fork功能一样,还好,在我实验的RedHat 和Debian版本中vfork 还保留着共享父进程数据的能力。(实际上,UNIX系统都还保留着两者的不同之处)
这篇文章是自己在编写一个Linux小程序时,遇到问题有感而发的。
vfork用于创建一个新进程,而该新进程的目的是exec一个新进程,vfork和fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,不会复制页表。因为子进程会立即调用exec,于是也就不会存放该地址空间。不过在子进程中调用exec或exit之前,他在父进程的空间中运行。
为什么会有vfork,因为以前的fork当它创建一个子进程时,将会创建一个新的地址空间,并且拷贝父进程的资源,而往往在子进程中会执行exec调用,这样,前面的拷贝工作就是白费力气了,这种情况下,聪明的人就想出了vfork,它产生的子进程刚开始暂时与父进程共享地址空间(其实就是线程的概念了),因为这时候子进程在父进程的地址空间中运行,所以子进程不能进行写操作,并且在儿子“霸占”着老子的房子时候,要委屈老子一下了,让他在外面歇着(阻塞),一旦儿子执行了exec或者exit后,相当于儿子买了自己的房子了,这时候就相当于分家了(子进程有了自己的地址空间,不再用父进程的地址空间)。
vfork和fork之间的另一个区别是: vfork保证子进程先运行,在她调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
由此可见,这个系统调用是用来启动一个新的应用程序。其次,子进程在vfork()返回后直接运行在父进程的栈空间,并使用父进程的内存和数据。这意味着子进程可能破坏父进程的数据结构或栈,造成失败。
为了避免这些问题,需要确保一旦调用vfork(),子进程就不从当前的栈框架中返回,并且如果子进程改变了父进程的数据结构就不能调用exit函数。子进程还必须避免改变全局数据结构或全局变量中的任何信息,因为这些改变都有可能使父进程不能继续。
通常,如果应用程序不是在fork()之后立即调用exec(),就有必要在fork()被替换成vfork()之前做仔细的检查。
用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序,当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行,因为调用exec并不创建新进程,所以前后的进程id 并未改变,exec只是用另一个新程序替换了当前进程的正文,数据,堆和栈段。
原文链接:http://www.linuxidc.com/Linux/2011-09/43401.htm
特别参考: http://alexrenguoly.blog.163.com/blog/static/1205055972011910111322226/
相关推荐
在Linux操作系统中,`fork()`、`vfork()` 和 `clone()` 都是用于创建新进程的函数,但它们在实现机制和使用场景上存在显著差异。 1. **fork()** `fork()` 是最传统的进程创建方法。它创建一个与父进程完全独立的新...
然而,`vfork()` 的这种特性也带来了一些限制和潜在问题。由于子进程与父进程共享内存,任何一方对内存的修改都会影响到另一方。因此,在 `vfork()` 后,父进程应暂停执行,以免干扰子进程的操作。同时,子进程也不...
这个程序是为了分析fork()函数和vfork()函数的区别,使用的系统环境是linux
fork 函数与 vfork 函数的区别在于:vfork 函数创建的子进程会在父进程的地址空间中执行,而不是在自己的地址空间中执行。这意味着,vfork 函数创建的子进程不能修改父进程的地址空间。 在使用 fork 函数时,为什么...
进程间的交互和控制是通过一系列特定的系统调用来实现的,其中`fork()`、`vfork()`和`execX()`(包括`execl`, `execle`, `execv`, `execve`, `execlp`, 和 `execvp`)是非常重要的进程控制函数。下面我们将深入解析...
- `fork()`通常比`vfork()`更安全,因为它避免了父子进程间的资源竞争问题。 - `vfork()`的优点在于效率高,因为它避免了内存复制,适合快速启动子进程并执行`exec()`的情况。 3. **C语言实现示例**: 我们可以...
然而,`vfork`和`fork`之间存在着一些显著的区别。 1. **fork基础了解**: `fork`函数创建子进程时,它会为子进程分配新的内存空间,并复制父进程的数据结构,包括堆、栈、全局变量等。这意味着父进程和子进程拥有...
在实际开发中,由于`vfork()`的复杂性和潜在问题,一般推荐使用更安全、更稳定的`fork()`函数。然而,在某些特定场景,比如当子进程只需要执行`exec()`而不需要额外的初始化工作时,`vfork()`的效率优势可能会被考虑...
在这段代码中,可以看到 do_fork 函数首先会做一些基本的权限检查,确保进程有权利创建新进程。随后,它会尝试分配内存以存放新的 task_struct 结构体,并复制父进程的 task_struct 结构体到新进程中。 #### 四、...
当将fork替换为vfork时,情况有所不同。vfork创建的子进程与父进程共享地址空间,直到子进程调用exec或exit为止。这意味着子进程可以立即开始执行,无需等待父进程复制内存,从而提高效率。但是,这也带来了一些限制...
`vfork()` 与 `fork()` 的主要区别在于,`vfork()` 创建的子进程与父进程共享物理内存,直到子进程调用 `exec()` 或者 `exit()`。这意味着,子进程对共享内存的修改会影响到父进程,直到子进程执行完 `exec()` 或 `...
- **vfork()和clone()**:除了`fork()`,Linux还有`vfork()`和`clone()`这两个相关函数。`vfork()`创建的子进程共享父进程的地址空间,直到子进程调用`exec()`或`_exit()`;`clone()`则允许更细粒度的控制进程之间的...
一、描述进程——PCB ·进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合 ·我们称为PCB,Linux操作系统下的PCB是:task struct 2、task_struct——PCB的一种 ·在Linux中描述进程的结构体...
在内核代码 2.6.15.5中/kernel/fork.c第1255-1261中有如下代码: 1. p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid); 2. if (!IS_ERR(p)) { 3. struct ...
系统调用函数fork()是创建一个新进程的方式,当然vfork()也可以创建进程,但是实际上其还是调用了fork()函数。fork()函数是Linux系统中一个比较特殊的函数,其一次调用会有两个返回值,下面是fork()函数的声明: ...