【问题标题】:call vfork() in function, confused about the result在函数中调用 vfork(),对结果感到困惑
【发布时间】:2015-04-05 05:16:41
【问题描述】:

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

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

在我的程序中:

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. 打印f1 address: xxx,我们调用vfork(),子进程先运行。
  2. 打印f2 address: xxx,然后子进程调用_exit(0)。
  3. 主进程从f1()返回,f1的栈帧被f2改变了,可能导致segmentation fault。

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

【问题讨论】:

  • buf 未初始化,因此 strlen(buf) 是未定义的行为(可能是段错误的原因)。这是故意的吗?
  • vfork 是你平台上 fork() 的别名吗?
  • @rici sizeof(buf) 是一样的
  • 它甚至不相似。 strlen 对字节进行计数,直到找到 0。
  • @rici 你是对的,我已经使用 sizeof() 改变了 strlen()。

标签: c unix fork vfork


【解决方案1】:

我不确定您所说的“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() 打印出来。

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

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

【讨论】:

  • 我将 strlen() 更改为 sizeof()。该代码是“UNIX 环境中的高级编程”的练习。
  • 而且,在做出改变之后,它仍然是分段错误吗?
  • 我更新问题,你可以看看。更改后,它仍然存在分段错误。
【解决方案2】:

根据vforkdocumentation,你不应该从当前函数返回。

vfork() 与 fork(2) 的不同之处在于调用线程被挂起 直到孩子终止(通常通过调用 _exit(2) 或 异常,在发出致命信号后),或者它调用 执行(2)。在那之前,孩子与他的孩子分享所有的记忆 父级,包括堆栈。 孩子不得从 当前函数或调用 exit(3),但可能调用 _exit(2)

另外,请注意:

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

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

您也可以查看following answer

【讨论】:

    猜你喜欢
    • 2021-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多