0

On Windows in C#, I can launch an external program from within my code by calling Process.Start(), which starts the process and returns its ID. This is important because I am not forking my own process, and I might have to kill the process later.

I have looked at exec(), fork() and many other things under Linux in C++ but none of those do quite the same thing. For example, system() blocks while the program runs, and fork() duplicates my whole process just so I can run another task.

Can someone tell me what the equivalent is?

Derf Skren
  • 479
  • 2
  • 22

3 Answers3

1

The way to start a new process on Linux is by using fork and exec. This you can wrap in your own System class that holds the PID and provides methods to kill the process etc. An example of such a class is QProcess from the Qt library.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
  • Thanks for the quick response. Do I really have to fork, creating a duplicate of my entire multimedia application, in order to run the command "unzip" though? – Derf Skren Jul 28 '20 at 06:17
  • @DerfSkren: Unfortunately, there's several decades of software which absolutely relies on this, so yes. – MSalters Jul 28 '20 at 07:11
  • 1
    @DerfSkren Re. duplicating the *entire* application, the situation isn't as bad as you think. See [this post](https://stackoverflow.com/questions/32130868/does-fork-duplicate-all-the-memory-of-the-parent/32130919#32130919). – G.M. Jul 28 '20 at 07:24
  • 1
    Also note that there is also vfork, which does the bare minimum to create a copy of your process before you exec something new. – Botje Jul 28 '20 at 07:48
  • 1
    @Botje It's "Copy On Write" anyway, so for a forked process that goes straight to `exec` there's not much actual copying happening in the kernel at all. – Jesper Juhl Jul 28 '20 at 14:39
  • Thanks everyone for the added information. If it is copy on write though, that will mean that I have to use other mechanisms to report back the child PID or any other information once I have forked. I'm hoping not to have to go down that path. – Derf Skren Jul 29 '20 at 01:03
  • @Derf What do you mean? The fact that your child processes memory is COW (with 4K page granularity) after `fork` is transparent to you. It's an OS implementation detail. It changes nothing regarding how you'd handle the PID. – Jesper Juhl Jul 29 '20 at 05:21
  • @JesperJuhl If it's copy on write, then how can I write to any memory in the original process? – Derf Skren Jul 29 '20 at 08:33
1

There is no equivalent. This is difference between Windows and Linux (and any other Unix-like system). In Unix-like system, new process is always started using fork() + exec(). Higher level C library APIs like system() use fork() + exec() system calls under the hood, because there is absolutely no other way. But Windows always creates new process from scratch. But you should not be discouraged by the fact that fork() creates copy of the current process. In fact it does it in a very specific way, so that actually almost nothing copied until write to memory occurs. And even in this case, only modified memory page is copied. Otherwise, creating new processes on Unix/Linux would be over-expensive (for example, assume a process which consumes 4GB of memory wants to execute some shell command). And in fact, exec() does the job which CreateProcess() does on Windows - assigns new executable image to process, re-initializes heap and stack.

ivan.ukr
  • 2,853
  • 1
  • 23
  • 41
  • Thanks for the more detailed explanation. I may have to use fork in the end, but the comment above about `posix_spawn()` what what I was looking for. – Derf Skren Jul 29 '20 at 01:01
  • According to man page that's in fact wrapper around combination of the `fork()` and `execve()`, so basically you still employing them, maybe in a bit more convenient way. – ivan.ukr Jul 29 '20 at 10:48
  • Yeah from what I read it was created to give a nicer interface but also includes some safety and housekeeping that works better in Posix environments that don't fork as nicely as a modern Linux distro. – Derf Skren Jul 29 '20 at 22:29
0

Thanks to @user4581301 for the comment above. posix_spawn() was what I was looking for.

Here is the code I ended up using though.

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

class ProcessCancelHelper {
 private:
  pid_t process_id_;

 public:
  ProcessCancelHelper()
  : process_id_(0) {}

  int StartProcess(const char* command_buffer) {
    const char* command_argv[4];
    command_argv[0] = "sh";
    command_argv[1] = "-c";
    command_argv[2] = command_buffer;
    command_argv[3] = nullptr;

    process_id_ = fork();
    if (process_id_ == -1) return -1;
    if (process_id_ == 0) {
      execvp(command_argv[0], const_cast<char* const*>(command_argv));
      assert(false && "execvp did not work");
      exit(-1);
    }

    int wait_status;
    if (waitpid(process_id_, &wait_status, 0))
    {
      process_id_ = 0;
      if (WIFEXITED(wait_status)) return (WEXITSTATUS(wait_status));
      return -1;
    }

    process_id_ = 0;
    return -1;
  }

  bool CancelProcess() {
    return (process_id_ != 0 && kill(process_id_, SIGKILL) == 0);
  }
}

This works and I have not yet tried switching to using posix_spawn instead, but as discussed that is just a wrapper.

Derf Skren
  • 479
  • 2
  • 22