意外的分叉行为
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]。不过,如何结束代码对我来说是一件好事。以上是关于意外的分叉行为的主要内容,如果未能解决你的问题,请参考以下文章