【问题标题】:Why execve hang为什么执行挂起
【发布时间】:2017-11-03 00:04:58
【问题描述】:

我有一个在 C++ STL 容器中存储 fileargvenvp 的结构。该结构还具有将它们转换为 execve 调用的 c 样式指针的方法。

struct Foo {
  std::string file;
  std::vector <std::string> argv;
  std::unordered_map <std::string, std::string> envp;

  inline auto c_file() const;
  inline auto c_argv() const;
  inline auto c_envp() const;
}

// Function: c_file
inline auto Foo::c_file() const {
  return file.c_str();
}

// Function: c_argv
inline auto Foo::c_argv() const {

  auto ptr = std::make_unique<char*[]>(argv.size() + 1);
  for(size_t i=0; i<argv.size(); ++i) {
    ptr[i] = const_cast<char*>(argv[i].c_str());
  }
  ptr[argv.size()] = nullptr;

  return ptr;
}

// Function: c_envp
inline auto Foo::c_envp() const {

  std::unique_ptr<char*, std::function<void(char**)>> ptr(
    new char*[envp.size() + 1],
    [sz=envp.size()+1](char** ptr) {
      for(size_t i=0; i<sz; ++i) {
        delete [] ptr[i];
      }
      delete [] ptr;
    }
  );

  auto idx = size_t{0};

  for(const auto& kvp : envp) {
    auto entry = kvp.first + "=" + kvp.second;
    ptr.get()[idx] = new char[entry.size() + 1];
    ::strncpy(ptr.get()[idx], entry.c_str(), entry.size() + 1);
    ++idx;
  }
  ptr.get()[idx] = nullptr;

  return ptr;
}

在我的程序中,我使用如下方式调用execve

void in_parent_process(const Foo& foo) {
  char stack[1024*1024];
  ::clone(child_entry, stack + 1024*1024, myflags, &foo)
}

void child_entry(void* ptr) {
  const auto& foo = *static_cast<Foo*>(ptr);
  ::execve(foo.c_file(), foo.c_argv().get(), foo.c_envp().get());
}

但是,对execve 的调用有时会挂起而没有任何响应。这使我的父进程无法与子进程同步(通过管道关闭执行)。我在这里使用unique_ptr 的想法有什么问题吗?还是我对将堆栈设置为clone 调用的理解有问题?

【问题讨论】:

  • 使用strace(1) 了解正在发生的事情...我不敢相信execve 挂了。
  • 另外,clone 主要用于像nptl(7) 这样的多线程库的实现者,因此对 C++11 线程(使用 pthread)不友好。我建议不要使用clone
  • 你的兆字节 stack 自动变量数组真的很难闻
  • 我需要用克隆创建 Linux 容器。没有克隆,我怎么能做到?
  • 但是你用过strace吗?此外,当execve 失败时,它会继续,所以你应该在它之后使用perror(或者更好的东西,记录errno...)。最后,你为什么不使用现有的容器化机器(例如Docker)或者至少仔细研究它们的源代码(它们是免费软件)?

标签: c++11 fork exec


【解决方案1】:

或者我对将堆栈设置为克隆调用的理解有问题?

是的,您的代码是错误的。您通过将位于当前call stack 中的一些automatic variable 作为堆栈区域传递来滥用clone(2)(因此一旦调用函数返回就会失效;这是典型的undefined behavior)。但该堆栈区通常应该是一些mmap(2)(或至少是一些malloc(3))的结果,并且不应该是调用线程的调用堆栈的一部分。

我认为你不应该使用clone,因为它已经被pthreads(7) 使用(Linux 上的 C++11 线程也使用它)

正如我评论的那样,您应该使用strace(1) 进行调试(特别是要了解“对 execve 的调用有时会挂起”时会发生什么)。此外,当execve(2) 失败时,它会返回并继续,所以你应该在它之后使用perror(或者更好的东西,记录errno...)。否则execve 成功并重新初始化您的进程(包括其整个virtual address space)以运行新程序。最后,您为什么不使用现有的容器化机器(例如 Docker)或者至少仔细研究它们的源代码(它们是免费软件)?

对 execve 的调用有时会挂起而没有任何响应

顺便说一句,execvesystem call。所以它要么成功而不返回(通过重新初始化你的进程来启动一个新程序),要么它很快失败(execve 只在失败时返回)。在任何一种情况下,execve 最多在几毫秒内由kernel 完成。所以我无法想象它如何“挂起”。因此,execve 不能自行挂起

(换句话说,挂起的不是execve而是别的东西)

你需要execve之后添加错误处理,至少需要

void child_entry(void* ptr) {
 const auto& foo = *static_cast<Foo*>(ptr);
 ::execve(foo.c_file(), foo.c_argv().get(), foo.c_envp().get());
 perror("execve");
 exit(EXIT_FAILURE);
}

您可以将perror("execve"); 替换为更详细的名称,例如perror((std::string{"execve of "}+foo.c_file()).c_str());;你也可以考虑使用_exit(2)而不是exit(3),但是你可能应该刷新缓冲的IO(例如使用fflush(3)...)

您可能还想使用日志记录工具,请参阅syslog(3)

注意PATH variable(您可以在environ(7) 中使用getenv(3) 作为getenv("PATH")...进行查询);也许你想要execvp(3) 而不是execve

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-22
    相关资源
    最近更新 更多