C - 在函数中调用 vfork(),对结果感到困惑



这是"APUE"第8章(练习8.2,第2版(中的一个练习。所有描述都是:

回想一下图 7.6 中内存的典型排列。因为堆栈帧 对应的每个函数调用通常都存储在堆栈中,并且因为之后 vfork 子项在父级的地址空间中运行,如果调用 vfork 会发生什么 来自 main 以外的函数,子项在 vfork?编写一个测试程序来验证这一点,并绘制正在发生的事情的图片。

在我的程序中:

static void f1(void), f2(void);
int main(void) {
    printf("main address: %dn", main);
    f1();
    f2();
    _exit(0);
}
static void f1(void) {
    printf("f1 address: %dn", f1);
    pid_t pid;
    if ((pid = vfork()) < 0)
        err_sys("vfork error");
}
static void f2(void) {
    printf("f2 address: %dn", 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. 打印f1 address: xxx,我们调用 vfork((,子进程首先运行。
  2. 打印f2 address: xxx,然后子进程调用 _exit(0(。
  3. 主进度从F1((返回,F1的堆栈帧被F2更改,可能导致分段错误。

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

我不确定你所说的"f1 的统计框架被 f2 更改"是什么意思。

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

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

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

您打印的数字是f1f2本身的地址。为什么您希望f1的地址与f2的地址相同?它们不是,因此它们打印不同的地址。

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

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

vfork(( 与 fork(2( 的不同之处在于调用线程被挂起 直到孩子终止(通常,通过呼叫 _exit(2( 或 异常,在发出致命信号后(,或者它会呼叫 执行(2(. 在此之前,孩子与其共享所有内存 父级,包括堆栈。 儿童不得从 当前函数或调用 exit(3(,但可以调用 _exit(2(。

另外,请注意:

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

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

您还可以查看以下答案

相关内容

  • 没有找到相关文章