意外的分叉行为

Posted

技术标签:

【中文标题】意外的分叉行为【英文标题】:Unexpected fork behavior 【发布时间】:2015-07-27 14:01:20 【问题描述】:

我有一个无限期运行的程序。出于测试目的,我制作了一个包装程序,该程序在指定的时间后杀死另一个(通过命令行/终端参数指定)。被分叉的程序要求它传递两个具有相同名称的文件夹(我无法控制),所以我只需将相同的 arg 传递给它两次,如下所示:

pid_t pid = fork();
if(pid == 0)

    //build the execution string
    char* test[2];
    test[0] = argv[2];
    test[1] = argv[2];
    test[2] = NULL;
    cout << "test[0] is " << test[0] << endl;
    cout << "test[1] is " << test[1] << endl;
    cout << "argv[1] is " << argv[1] << endl;
    execvp(argv[1],test);

问题是在 argv[1] 中传递的程序不断出现分段错误。如果我通过终端自己调用它,它运行没有问题。在这两种情况下,我都传递了同一个文件夹。谁能告诉我为什么它不适用于 execvp?

我应该提到一个同事也在他的电脑上运行它,第一次它会正常运行,但之后每次都会出现故障。

编辑:我添加了一个空词来测试,但是,这并没有解决问题。

命令的格式是:

<executable> <wrapped prog> <folder> <duration>

在相对路径中是:

Intel/debug/Tester.exe <program> test 10

【问题讨论】:

您要执行的确切命令行是什么? test 数组应该以可执行文件的名称开头并以 NULL 结尾。 它应该以文件名开头,但这只是为了约定。 使用strace(1) -as strace -f 了解正在发生的事情 您应该至少有一个exit() 调用,并且最好在execvp() 之后打印一条错误消息。它仅在失败时返回,但您不希望孩子在失败时继续做其他事情。 【参考方案1】:

如果数组的长度是静态的,你可能会更好

execlp

execlp(argv[1], argv[1], argv[2], argv[2], (char*)0);

对于execvp,数组应该以可执行文件的名称开头,以NULL结尾。

execvp

char* args[] =  argv[1], argv[2], argv[2], NULL ;
execvp(argv[1], args);

runWithTimeout

在任何情况下,如果您想要的只是一个简单的包装器,它可以运行具有超时的单个子节点,那么只要您愿意从 timeout 参数开始,您的程序就可以非常简单和通用:

/*runWithTimeout.c
  compile with: make runWithTimeout
  run with: ./runWithTimeout seconds program arguments...
*/
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>

int main(int argc, char** argv)

  assert(argc >= 1+2);
  int pid, status = 1;
  if((pid = fork()) == 0) 
    alarm(atoi(argv[1]));
    execvp(argv[2], argv + 2); 
    /*^the child ends here if execvp succeeds,
    otherwise fall-through and return the default error status of 1
    (once in child (which has no one to wait on) and 
    then in the parent (which gets the status from the child))*/
    perror("Couldn't exec");
  else if(pid < 0) perror("Couldn't fork"); ;
  wait(&status);
  return status;

【讨论】:

出于调试目的,我有一个 for 循环打印所有传递的参数。它们都正确显示,并且 argc 的大小为 4:1)它本身 2)要调用的程序 3)要传递的文件夹 4)运行的最长时间 我刚试了execlp,结果和execvp一样。 这就是重点。无论如何,我添加了一个完整的功能示例。 请注意,execlp() 需要一个 (char *)0 参数作为其最后一个参数。 是的,我明白你的意思。我不热衷于它,但它确实与这段代码一起工作。但是,作为一般规则,您不应该让孩子退出其语句块——当然也有例外,但大多数情况下,提前退出是合适的。 (有些人想要使用_exit()_Exit()quick_exit() 之一而不是常规exit();我通常只使用具有非零状态的exit()。)【参考方案2】:

作为参数传递的数组应该以空值结尾。例如:

char *test[3]=0;
...

【讨论】:

供参考; linux.die.net/man/3/execvp "... execvp()... 函数... 指针数组必须以 NULL 指针终止。" 我刚刚再次运行该程序,但它并没有解决问题(虽然它确实变得更远了)。 尝试将你想调用的命令与/bin/bash(或任何你的bash二进制文件)字符串连接起来,这样它看起来像:/bin/bash command【参考方案3】:

您可以打开核心转储(确保在完成后将其关闭)ulimit -c unlimited。在运行主进程之前运行它。 (虽然你可能可以,但我很担心在 fork 中运行它。)

当您的程序崩溃时,这将产生一个核心转储,您可以使用 gdb 对其进行检查。

如需有关核心文件的帮助,您只需 google 即可。

除此之外。您可以制作一个启动文件的脚本。您可以使用脚本来记录内容。

【讨论】:

【参考方案4】:

你想要:

char* test[3];
test[0] = argv[2];
test[1] = argv[2];
test[2] = NULL;

您需要一个 NULL 参数来标记参数列表的结尾。

【讨论】:

【参考方案5】:

给定规范:

命令的格式是:

<executable> <wrapped prog> <folder> <duration>

在相对路径中是:

Intel/debug/Tester.exe <program> test 10

还有:

被分叉的程序要求它传递两个同名的文件夹......

那么,假设您检查了包装器是否传递了 4 个参数,您需要的代码是:

pid_t pid = fork();
if (pid == 0)

    //build the execution string
    char  *test[4];      // Note the size!
    test[0] = argv[1];   // Program name: argv[0] in exec'd process
    test[1] = argv[2];   // Directory name: argv[1] …
    test[2] = argv[2];   // Directory name: argv[2] …
    test[3] = NULL;      // Null terminator
    cout << "test[0] is " << test[0] << endl;
    cout << "test[1] is " << test[1] << endl;
    cout << "test[2] is " << test[2] << endl;
    execvp(test[0], test);
    cerr << "Failed to exec '" << test[0] << "': " << strerror(errno) << endl;
    exit(1);  // Or throw an exception, or …

除了对argv 中的参数数组使用习语execvp(argv[0], argv) 之外,很少(但并非从来没有)调用execvp() 的理由。

请注意,此代码确保控制流不会从应该代表子项的语句块中逃逸。让子进程在之后继续,通常实际上认为它是父进程,这会导致混乱。始终确保孩子执行或退出。 (这是夸大其词的言辞——是的;但这个想法背后也有很大的道理。)另外,由于这是 C++,你可能需要考虑How to end C++ code?。这使生活复杂化。关键是如果子进程执行失败,它就不会像父进程一样继续。

【讨论】:

我的父进程正在处理时间,并保证在子进程之后结束,所以我不需要担心。孩子唯一需要的 args 是 argv[1] 和 argv[2]。不过,如何结束代码对我来说是一件好事。

以上是关于意外的分叉行为的主要内容,如果未能解决你的问题,请参考以下文章

Qt 应用程序在分叉到后台时意外收到 HUP 信号

Java Native Interface 偷偷摸摸的分叉行为

混沌动力学行为研究-分叉图

分叉一个不使用它自己的内存副本的子进程

Node.js 被分叉出一个项目 — Ayo.js,肿么了

第159篇 笔记-区块链的升级与分叉