【问题标题】:Calling fork before main在 main 之前调用 fork
【发布时间】:2016-06-14 16:36:57
【问题描述】:

POSIX 标准是否允许在 main() 之前调用 fork() - 例如,在 C++ static 实例中,或在 __attribute__((constructor)) C 函数中?

【问题讨论】:

  • __atribute__ 不是标准 C,也不是 POSIX。而 C++ 是一种不同的语言。
  • 为什么你甚至想要main()之前打电话给fork()

标签: c++ c fork posix language-lawyer


【解决方案1】:

fork 联机帮助页中没有任何迹象表明它是被禁止的,我也想不出它被禁止的原因。

确实,就 POSIX 而言,main 并没有什么特别之处。只是 C 选择以具有该名称的函数开始其程序,而 C++ 几乎也是如此。但就 POSIX 而言,一旦你的进程启动,你的进程就启动了。它可以用任何旧语言编写,fork 仍然可以工作。

在 C 中(不是 C++!)不可能编写在 main 之前执行的代码(因为 static 变量的初始化器在那个上下文),所以对于 C 来说,这有点争议。然而,暂时离开 C 抽象,POSIX 中仍然没有任何东西阻止您的编译器供应商在 C 运行时中包含在输入 main 之前执行 fork 的代码。回想一下,“真正的”入口点实际上并不是main; “true”入口点在调用 main 开始您的程序部分之前会执行一些库初始化等操作。

【讨论】:

    【解决方案2】:

    POSIX 标准是否允许在main() [...] 之前调用fork()

    这取决于你的意思。

    C 程序

    POSIX 通过其对 C 标准的结合,指定 C 程序启动发生在环境(操作系统)调用main() 时。 C 程序的 POSIX 定义的语义从该点开始;根据定义,它们不包括对任何其他函数的先前调用。如果确实发生了任何此类调用,那么从这个意义上讲,它们不是“程序”的一部分,因此程序不能在main() 之前调用fork()

    另一方面,POSIX 并未明确禁止 C 程序运行的进程在定义的 C 语义开始之前执行 fork() 或任何其他函数。从某种意义上说,不禁止的东西是允许的,POSIX 确实允许它。如果您的问题是关于 GCC 的 __attribute__((constructor)) 或类似设施是否违反 POSIX,那么不,他们没有,尽管 POSIX 没有定义它们的行为。

    另一方面,POSIX 没有肯定地允许它,也没有为 C 源代码定义任何方式来指定它应该发生。如果您的问题是关于 POSIX 是否定义了 __attribute__((constructor)) 或类似设施用来提供其广告行为的特定设施,或者它是否要求符合标准的系统必须提供一种机制来使此类设施可以运行,那么不,它没有.不提供这样的机制本身不会导致系统不符合 POSIX。

    C++ 程序

    POSIX 是根据 shell 命令语言和 ISO C 定义的。它根本不需要实现来提供任何 C++ 支持,当然它也不禁止它。因此,上一节的第二和第三段适用于 C++ 程序的所有方面。

    然而,就其价值而言,与 C 一样,C++ 指定程序以 main() 开头。这在 C++ 中与在 C 中具有相似的含义,但与 C 不同的是,C++ 实现可能提供了可以使用户提供的代码在main() 的第一条语句之前运行的机制。具体来说,具有静态持续时间的变量的构造函数可能在执行main() 的第一条语句之前运行,但是否这样做是实现定义的(C++11, 3.6.2/4 )。因此,从这个意义上说,C++ 程序语义是否可以在main() 的第一条语句之前包含对fork() 的调用是实现定义的。

    另请注意,C++ 标准的措辞是谨慎的,以避免提及此类初始化是否可以在 main()输入之前进行 - 这可能无关紧要,但至少有几种方法这将是可能的区别。

    其他程序

    POSIX 对其他类型的程序的评价甚至比对 C++ 程序的评价还要少,除了 shell 脚本。当然,shell 脚本通常根本没有main(),并且它们无法直接访问C 库,通过该库可以显式地fork()

    【讨论】:

      【解决方案3】:

      从 libc 完全加载和初始化的那一刻起,您就可以使用 fork。

      一个简单的测试程序可以证明这是真的:

       #include <stdio.h>
      
       void before(void) __attribute__ (( constructor ));
       void after(void) __attribute__ (( destructor ));
      
       int main(void)
       {
         puts("main");
      
         return 0;
       }
      
       void before(void)
       {
         puts("before");
       }
      
       void after(void)
       {
         puts("after");
       }
      

      输出是:

       $ LD_DEBUG=files ./a.out
            26032:    
            26032:    file=libc.so.6 [0];  needed by ./a.out [0]
            26032:    file=libc.so.6 [0];  generating link map
            26032:      dynamic: 0x00007f58c7703ba0  base: 0x00007f58c7341000   size: 0x00000000003c8a00
            26032:        entry: 0x00007f58c7361950  phdr: 0x00007f58c7341040  phnum:                 10
            26032:    
            26032:    
            26032:    calling init: /lib/x86_64-linux-gnu/libc.so.6
            26032:    
            26032:    
            26032:    initialize program: ./a.out
            26032:    
       before
            26032:    
            26032:    transferring control: ./a.out
            26032:    
       main
            26032:    
            26032:    calling fini: ./a.out [0]
            26032:    
       after
      

      已加载库,已完成初始化,因此使用带有构造函数标记函数的 fork 必须是安全的。

      【讨论】:

      • 问题是关于 POSIX 允许什么。您的程序在特定环境中表现出所需的行为并不能说明 POSIX 是否允许该行为,也不能证明它所展示的做法在任何有意义的意义上是“安全的”。
      • @JohnBollinger:问题要简单得多,问题是«在 POSIX 环境中使用 gcc 或 clang 定义的构造函数属性之前,我可以安全地使用库(POSIX 或 stdc 或其他)函数吗»。更多的是关于加载机制而不是 POSIX 问题。
      • 考虑到问题文本以“POSIX 标准是否允许 [...]”开头并且它被标记为 [language-lawyer],我有点不知所措将其解释为与 POSIX 的规定无关。
      猜你喜欢
      • 2013-03-09
      • 2017-08-26
      • 2011-07-10
      • 2017-05-14
      • 1970-01-01
      • 2012-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多