为什么execve挂起

时间:2017-11-03 00:04:58

标签: c++11 fork exec

我在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的调用有时会挂起而没有任何回复。这使得我的父进程无法与子进程同步(通过管道close-on-exec)。我在这里使用unique_ptr的想法有什么问题吗?或者我对将堆栈设置为clone调用的理解有问题吗?

1 个答案:

答案 0 :(得分:0)

  

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

是的,你的代码错了。您错误地使用了clone(2)作为堆栈区域automatic variable,它位于当前call stack中(因此一旦调用函数返回就会失效;这是典型的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的调用有时会挂起而没有任何响应

BTW,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("PATH")作为execvegetenv(3)中查询... {也许你想要execvp(3)而不是Command