fork和(failed)exec后C文件指针改变

时间:2018-06-18 16:51:25

标签: c linux file fork

我制作了制作叉子的程序,我认为孩子不会影响父母。

但文件指针已更改,但我没有对父进行任何更改。

I'm one 21500 20
I'm two 21500 -1

此输出

printf

我想让两个execvp调用之间的文件指针不变。

为什么文件指针会发生变化,即使configuration.node.global should be a boolean.失败,我也可以使文件指针不变;

2 个答案:

答案 0 :(得分:3)

感谢Jonathan Leffler指出我们正确的方向。

虽然您的程序在CentOS 7 / GCC 4.8.5 / GLIBC 2.17上不会产生相同的意外行为,但您可能会发现不同的行为。根据POSIX(您依赖fork),您的程序的行为实际上是 undefined 。以下是the relevant section的一些摘录(强调补充):

  

可以通过文件描述符访问打开的文件描述,   这是使用open()pipe()等功能创建的   流,使用fopen()popen()等函数创建的。   文件描述符或流被称为"句柄"在开放   它所引用的文件描述;一个打开的文件描述可能有   几个把手。

     

[...]

     

涉及任何一个句柄的函数调用的结果("活动的   handle")在本卷POSIX.1-2017的其他地方定义,但是如果   使用两个或更多个句柄,其中任何一个都是一个流,即   申请应确保其行为得到协调   如下面所描述的。 如果不这样做,结果是未定义的

     

[...]

     

要使手柄成为活动手柄,应用程序应确保   以下操作是在最后一次使用之间执行的   handle(当前活动句柄)和第二次使用   handle(未来的活动句柄)。然后第二个手柄成为   主动手柄。 [...]

     

处理这些规则的手柄不需要在同一个过程中。

     

请注意,在fork()之后,存在两个句柄,其中之前存在一个句柄。   如果两个手柄都可以,应用程序应确保   访问,他们都处于另一个可能成为的状态   积极处理第一。 [在符合前述资格的情况下,]申请应准备fork()   就好像它是活动句柄的变化一样。 (如果唯一的行动   由其中一个进程执行的是exec函数之一或   _exit()(不是exit()),永远不会在该过程中访问句柄。

     

对于第一个句柄,适用以下第一个适用条件。   [令人印象深刻的一系列替代方案不适用于OP的情况......]

     
      
  • 如果使用允许读取的模式打开流,则底层打开文件描述指的是能够读取的设备   寻求,申请应执行fflush(),或   溪流应该关闭。
  •   
     

对于第二个句柄:

     
      
  • 如果显式更改了文件偏移量的函数使用了任何先前的活动句柄,则上面的要求除外   第一个句柄,应用程序应执行lseek()fseek()(如   适合于手柄的类型)到适当的位置。
  •   

因此,为了让OP程序在父级和子级中访问相同的流,POSIX要求父级fflush() stdin在分叉之前,以及子级{{1}它开始后。然后,在等待孩子终止后,父母必须fseek()流。然而,鉴于我们知道孩子的执行失败,可以通过让孩子使用fseek()(不访问流)而不是{{1}来避免对所有刷新和寻求的要求。 }}

遵守POSIX的规定会产生以下结果:

  

遵循这些规则,无论句柄顺序如何   使用,实现应确保应用程序,甚至一个   由几个过程组成,应产生正确的结果:没有数据   写作时应丢失或重复,所有数据均为   除非被搜查要求,否则按顺序书写。

但值得注意的是

  

是的   实现 - 定义是否以及在什么条件下所有输入   只看到一次。

我理解,仅仅听到您对计划行为的期望没有得到相关标准的证明,这可能有点令人不满意,但这确实存在。父进程和子进程确实具有一些相关的共享数据,这些共享数据采用公共开放文件描述的形式(它们具有相关的单独句柄),并且这似乎可能是意外的车辆(和未定义的行为,但是没有基础可以预测您看到的具体行为,也没有我在同一程序中看到的不同行为。

答案 1 :(得分:0)

我能够使用gcc 5.4.0在Ubuntu 16.04上重现这一点。这里的罪魁祸首是exit以及子进程的创建方式。

exit的手册页说明了以下内容:

  

exit()函数导致正常的进程终止和值   地位与0377将返回父级(请参阅wait(2))。

     

使用atexit(3)和on_exit(3)注册的所有函数都是   按照其注册的相反顺序调用。 (有可能的   对于其中一个函数,使用atexit(3)或on_exit(3)   注册在退出处理期间执行的附加功能;   新注册被添加到列表的前面   剩下要调用的函数。)如果其中一个函数有   不返回(例如,它调用_exit(2),或用a杀死自己   信号),然后没有剩余的功能被调用,进一步   退出处理(特别是刷新stdio(3)流)   被遗弃了。如果功能已多次注册   使用atexit(3)或on_exit(3),然后调用它多次   它被注册了。

     

刷新并关闭所有打开的stdio(3)流。创建的文件   删除tmpfile(3)。

     

C标准指定了两个常量,EXIT_SUCCESS和   EXIT_FAILURE,可以传递给exit()以表示成功或   不成功终止。

因此,当您在孩子中致电exit时,会关闭FILE所代表的fp

通常,在创建子进程时,它会获取父进程的文件描述符的副本。然而,在这种情况下,孩子的记忆似乎仍然在物理上指向父母的记忆。因此,当exit关闭FILE时,它会影响父级。

如果您将子项更改为_exit,则会关闭子项的文件描述符,但设法不触及FILE对象,并且父项中对ftell的第二次调用将成功。最好在非执行的孩子中使用_exit,因为它会阻止在孩子中调用atexit个处理程序。