0

这是“APUE”第 8 章(练习 8.2,第 2 版)中的练习。全部说明是:

回忆一下图 7.6 中典型的内存排列。因为每个函数调用对应的栈帧通常都存储在栈中,并且因为在 vfork 之后子进程在父进程的地址空间中运行,如果对 vfork 的调用来自除 main 之外的函数并且子进程执行了会发生什么情况在 vfork 之后从这个函数返回?编写一个测试程序来验证这一点,并画出正在发生的事情。

在我的程序中:

static void f1(void), f2(void);

int main(void) {
    printf("main address: %d\n", main);
    f1();
    f2();
    _exit(0);
}

static void f1(void) {
    printf("f1 address: %d\n", f1);
    pid_t pid;

    if ((pid = vfork()) < 0)
        err_sys("vfork error");
}

static void f2(void) {
    printf("f2 address: %d\n", f2);
    char buf[1000];
    int i;

    for (i = 0; i < sizeof(buf); ++i)
        buf[i] = 0;
}

我运行程序,输出为:

main address: 4196560
f1 address: 4196604
f2 address: 4196663
f1 address: 4196604
[1]    12929 segmentation fault  ./a.out

我对输出感到困惑。

  1. print f1 address: xxx,我们调用 vfork() ,子进程首先运行。
  2. print f2 address: xxx,然后子进程调用 _exit(0)。
  3. 主进程从 f1() 返回,f1 的堆栈帧被 f2 更改,可能导致分段错误。

但是为什么要打印f1 address: 4196604两次,为什么 f1 和 f2 的地址不一样呢?

4

2 回答 2

0

我不确定“f1 的 statck 框架被 f2 更改”是什么意思。

中的代码f2()无论如何都可能出现分段错误,无论vfork(). buf未初始化。没有理由相信它包含一个以 null 结尾的字符串。因此,调用strlen()可以读取缓冲区的末尾。

无论如何,我不确定你期望循环做什么。在第一次迭代中,i为 0。如果对 的调用strlen()没有段错误,则循环体将 0 存储在buf[0]. 因此,在循环的下一次迭代中,strlen(buf)将是 0,i将是 1(不小于 0),因此循环将终止。

第二次打印f1 address: 4196604是在vfork()-ed 子进程退出后父进程继续时。父进程继续并调用f1()which 打印它。

您打印的数字是地址f1f2它们本身。为什么您会期望 的地址与f1的地址相同f2?他们不是,所以他们打印不同的地址。

另一方面,f1父进程和子进程的地址是相同的,因为子进程共享父进程的地址空间。因此,f1两次都打印相同的地址。

于 2015-04-05T05:25:31.117 回答
0

根据vfork 文档,您不应该从当前函数返回。

vfork() 与 fork(2) 的不同之处在于调用线程被挂起,直到子进程终止(正常情况下,通过调用 _exit(2),或异常情况下,在传递致命信号后),或者调用 execve( 2)。在此之前,子进程与其父进程共享所有内存,包括堆栈。 子进程不能从当前函数返回或调用 exit(3),但可以调用 _exit(2)

另外,请注意:

vfork() 是 clone(2) 的一个特例。它用于在不复制父进程的页表的情况下创建新进程。它在创建一个子进程的性能敏感应用程序中可能很有用,然后立即发出一个 execve(2)。

由于vfork不从父级复制页表,因此不从当前函数返回很有意义。当子进程退出时,它会弄乱父进程的堆栈帧。

也可以查看以下答案

于 2016-05-12T20:15:56.483 回答