【问题标题】:How can a C/C++ program put itself into background?C/C++ 程序如何将自己置于后台?
【发布时间】:2010-09-12 02:46:23
【问题描述】:

从命令行启动的正在运行的 C 或 C++ 程序将自身置于后台的最佳方式是什么,相当于用户从命令末尾带有“&”的 unix shell 启动? (但用户没有。)它是一个 GUI 应用程序,不需要任何 shell I/O,所以没有理由在启动后绑定 shell。但我希望在没有“&”(或在 Windows 上)的情况下自动启动 shell 命令。

理想情况下,我想要一个可以在任何 Linux、OS X 和 Windows 上运行的解决方案。 (或者我可以使用#ifdef 选择的单独解决方案。)可以假设这应该在执行开始时完成,而不是在中间的某个地方。

一种解决方案是让主程序成为启动真正二进制文件的脚本,小心地将其置于后台。但是需要这些耦合的 shell/二进制对似乎并不令人满意。

另一个解决方案是立即启动 另一个 执行版本(使用“系统”或 CreateProcess),使用相同的命令行参数,但将子进程置于后台,然后让父进程退出。但这与将自身置于后台的过程相比显得笨拙。

在几个答案后编辑:是的,fork()(或 system(),或 Windows 上的 CreateProcess)是一种方法,我在最初的问题中暗示了这一点.但是所有这些解决方案都会创建一个后台的 SECOND 进程,然后终止原始进程。我想知道是否有办法将现有进程置于后台。一个区别是,如果应用程序是从记录其进程 id 的脚本启动的(可能是为了以后杀死或其他目的),那么新分叉或创建的进程将具有不同的 id,因此任何启动脚本都无法控制,如果你明白我在说什么。

编辑#2

fork() 对于 OS X 来说不是一个好的解决方案,“fork”的手册页说如果正在使用某些框架或库是不安全的。我试过了,我的应用程序在运行时大声抱怨:“进程已经分叉,你不能安全地使用这个 CoreFoundation 功能。你必须 exec()。”

我对 daemon() 很感兴趣,但是当我在 OS X 上尝试它时,它给出了相同的错误消息,所以我认为它只是 fork() 的一个精美包装器,并且具有相同的限制。

请原谅 OS X 中心主义,它恰好是我眼前的系统。但我确实在寻找所有三个平台的解决方案。

【问题讨论】:

  • 为什么不问另一个关于 OS X 问题的问题呢? fork()/exec() 是执行原始问题的 C/C++ 方式。

标签: windows linux macos shell background


【解决方案1】:

我的建议:不要这样做,至少不要在 Linux/UNIX 下。

Linux/UNIX 下的 GUI 程序传统上自动后台运行。虽然这有时会让新手感到厌烦,但它有很多优点:

  • 在发生核心转储/其他需要调试的问题时,可以轻松捕获标准错误。

  • 使 shell 脚本可以轻松地运行程序并等待它完成。

  • 使 shell 脚本可以轻松地在后台运行程序并获取其进程 ID:

    gui-program &
    pid=$!
    # do something with $pid later, such as check if the program is still running
    

    如果你的程序分叉自己,这个行为就会中断。

“Scriptability”在许多意想不到的情况下都很有用,即使是 GUI 程序,我也不愿意明确地破坏这些行为。

Windows 是另一回事。 AFAIK,Windows 程序会自动在后台运行——即使是从命令 shell 调用——除非它们明确请求访问命令窗口。

【讨论】:

  • Windows console 程序不在后台运行。 Windows GUI 程序没有相对于控制台处于前台或后台的概念。它们只是 Z 顺序中的窗口。
  • 大多数时候,当你运行一些你对输出不感兴趣的东西时,当程序可以自己做并且有一个 --foreground 标志时,不得不自己分离它是很烦人的如果你需要的话。
【解决方案2】:

如果你需要一个脚本来获得程序的PID,你仍然可以在fork之后得到它。

fork 时,将子进程的 PID 保存在父进程中。当您退出父进程时,要么将 PID 输出到 STD{OUT,ERR},要么在 main() 的末尾添加一个 return pid; 语句。然后调用脚本可以获取程序的 pid,尽管它需要对程序如何工作有一定的了解。

【讨论】:

    【解决方案3】:

    最简单的背景形式是:

    if (fork() != 0) exit(0);
    

    在 Unix 中,如果你想完全取消与 tty 的关联,你会这样做:

    1. 关闭所有可能访问tty的描述符(通常是012)。
    2. if (fork() != 0) exit(0);
    3. setpgroup(0,getpid()); /* Might be necessary to prevent a SIGHUP on shell exit. */
    4. signal(SIGHUP,SIG_IGN); /* just in case, same as using nohup to launch program. */
    5. fd=open("/dev/tty",O_RDWR);
    6. ioctl(fd,TIOCNOTTY,0); /* Disassociates from the terminal */
    7. close(fd);
    8. if (fork() != 0) exit(0); /* just for good measure */

    这应该完全守护你的程序。

    【讨论】:

      【解决方案4】:

      我正在尝试解决方案。

      父进程只需要一个fork。

      最重要的一点是,在 fork 之后,父进程必须通过调用 _exit(0); 而不是通过调用 exit(0); 而死

      当使用_exit(0); 时,命令提示符会立即在shell 上返回。

      这就是诀窍。

      【讨论】:

        【解决方案5】:

        在 Unix 中,我已经学会了使用 fork() 来做到这一点。 如果你想把一个正在运行的进程放到后台,fork它两次。

        【讨论】:

          【解决方案6】:
          /**Deamonize*/
          
          pid_t pid;
          pid = fork(); /**father makes a little deamon(son)*/
          if(pid>0)
          exit(0); /**father dies*/
          while(1){
          printf("Hello I'm your little deamon %d\n",pid); /**The child deamon goes on*/
          sleep(1)
          }
          
          /** try 'nohup' in linux(usage: nohup <command> &) */
          

          【讨论】:

            【解决方案7】:

            您编辑了您的问题,但您可能仍然遗漏了您的问题是某种语法错误这一点——如果该进程一开始没有放在后台并且您希望 PID 保持不变,您不能忽略这样一个事实,即启动进程的程序正在等待该 PID,而这几乎就是在前台的定义

            我认为您需要考虑为什么要将某些内容放在后台并保持 PID 相同。我建议您可能不需要这两个约束。

            【讨论】:

              【解决方案8】:

              所以,正如你所说,仅仅 fork()ing 是不行的。你必须做的是 fork() 然后 re-exec(),就像这个代码示例所做的那样:

              #include stdio.h>
              #include <unistd.h>
              #include <string.h>
              
              #include <CoreFoundation/CoreFoundation.h>
              
              int main(int argc, char **argv)
              {
                  int i, j;
              
                  for (i=1; i<argc; i++)
                      if (strcmp(argv[i], "--daemon") == 0)
                      {
                          for (j = i+1; j<argc; j++)
                              argv[j-1] = argv[j];
              
                          argv[argc - 1] = NULL;
              
                          if (fork()) return 0;
              
                          execv(argv[0], argv);
              
                          return 0;
                      }
              
              
                  sleep(1);
              
                  CFRunLoopRun();
              
                  CFStringRef hello = CFSTR("Hello, world!");
              
                  printf("str: %s\n", CFStringGetCStringPtr(hello, CFStringGetFastestEncoding(hello)));
              
                  return 0;
              }
              

              循环用于检查 --daemon 参数,如果存在,则在重新执行之前将其删除,以避免无限循环。

              如果将二进制文件放入路径中,我认为这不会起作用,因为 argv[0] 不一定是完整路径,因此需要对其进行修改。

              【讨论】:

                【解决方案9】:

                在 Linux 上,daemon() 是您正在寻找的,如果我理解正确的话。

                【讨论】:

                • 非常接近!在 Linux 上看起来很棒,但在 OS X 上,它似乎只是包装了 fork() 并给出了同样可怕的运行时错误消息,说明在使用某些框架库时分叉是不安全的。
                【解决方案10】:

                在 Windows 下,我认为 fork() 的最后一步是将程序加载为 Windows 服务。

                这里是关于 Windows 服务的介绍文章的链接... CodeProject: Simple Windows Service Sample

                【讨论】:

                  【解决方案11】:

                  三件事需要做,

                  fork
                  setsid
                  redirect STDIN, STDOUT and STDERR to /dev/null
                  

                  这适用于 POSIX 系统(您提到的所有系统都声称是 POSIX(但 Windows 停在声称位)

                  【讨论】:

                  • 我希望它也适用于 Windows 上的 POSIX 应用程序。但是你必须意识到没有人在 Windows 上编写 POSIX 应用程序(即不是操作系统问题)
                  【解决方案12】:

                  后台处理进程是一个 shell 函数,而不是一个 OS 函数。

                  如果您希望应用在后台启动,典型的技巧是编写一个 shell 脚本来启动它,然后在后台启动它。

                  #! /bin/sh
                  /path/to/myGuiApplication &
                  

                  【讨论】:

                  • 这并没有回答最初的问题,即如果没有使用“&”启动您自己的进程,并且您不想使用单独的 shell 脚本来执行此操作,如何将其设置为后台.
                  【解决方案13】:

                  进程不能将自己置于后台,因为它不是负责后台与前台的进程。那将是外壳,它正在等待进程退出。如果你启动一个带有 & 符号的进程,那么 shell 不会等待进程退出。

                  但是进程可以逃离 shell 的唯一方法是派生出另一个子进程,然后让它原来的自己退出到等待的 shell。

                  在 shell 中,您可以使用 Control-Z 使进程后台运行,然后键入“bg”。

                  【讨论】:

                  • 对,我知道这一点。问题的关键在于程序本身是否可以做相当于用户按 Ctrl-Z 然后运行“bg”的操作。
                  • 第一段的回答是“否”
                  • 进程总是可以向自己发送 SUSP 信号,但这只是 Larry 要求的一半;)
                  【解决方案14】:

                  这里是一些 Linux/UNIX 的伪代码:

                  initialization_code()
                  if(failure) exit(1)
                  if( fork() > 0 ) exit(0)
                  setsid()
                  setup_signal_handlers()
                  for(fd=0; fd<NOFILE; fd++) close(fd)
                  open("/dev/null", O_RDONLY)
                  open("/dev/null", O_WRONLY)
                  open("/dev/null", o_WRONLY)
                  chdir("/")
                  

                  恭喜,您的程序作为一个独立的“守护进程”继续运行,没有控制 TTY,也没有任何标准输入或输出。

                  现在,在 Windows 中,您只需使用 WinMain() 而不是 main() 将程序构建为 Win32 应用程序,它会自动在没有控制台的情况下运行。如果您想作为服务运行,则必须查找它,因为我从未编写过它,而且我真的不知道它们是如何工作的。

                  【讨论】:

                  • 代码很糟糕,抱歉。您假设 a) 您可以关闭 stdin/stdout/stderr,b) 0=stdin, 1=stdout|stderr, 2=stdout|stderr 和 c) open() 将从 0 开始再次填充文件描述符.
                  • 不,这是非常标准的守护程序代码。你总是被允许在 Unix'ish 中关闭 0,1,2。 open 函数总是返回第一个可用的 fd,所以如果 0,1,2 被关闭,下一个打开的将是 0,1,2。如果您坚持,您可以使用 dup2() 强制 fds 为 0,1,2,但这不是必需的。
                  • @Thorsten79:你显然没有做过任何 POSIX 编程,抱歉。您可能想找到一种方法来访问 POSIXish 系统上的 shell 并尝试“man 2 open”。通常情况下,描述部分的第一段将是一个令人大开眼界的体验。
                  • POSIX 确实保证 (a) 和 (b),但不保证 (c)。然而,由于这样的代码,几乎所有系统在实践中都会遵守 (c)。
                  • @wnoise:“成功调用返回的文件描述符将是当前未为进程打开的编号最小的文件描述符。”符合 POSIX.1-2001 。文件描述符 0、1 和 2 刚刚关闭,没有其他线程在运行,ergo…
                  【解决方案15】:

                  跟进您编辑的问题:

                  我想知道是否有办法将现有进程置于后台。

                  在类 Unix 操作系统中,据我所知,确实没有办法做到这一点。 shell 被阻塞,因为它正在执行 wait() 调用的变体之一,等待子进程退出。没有办法让子进程保持运行,但会以某种方式导致 shell 的 wait() 返回“请停止观看我”状态。您拥有子 fork 并退出原始版本的原因是 shell 将从 wait() 中返回。

                  【讨论】:

                  • 你可以为 SIGHUP 设置一个监听器,但不能交互
                  • Ctrl-Z 和“bg”命令?我从来没有费心去弄清楚它是如何工作的,但它一直对我有用。
                  【解决方案16】:

                  我不确定 Windows,但在类 UNIX 系统上,您可以 fork() 然后 setsid() 分叉进程将其移动到未连接到终端的新进程组中。

                  【讨论】:

                    【解决方案17】:

                    在 UNIX 上,你需要连续两次 fork 并让父进程死掉。

                    【讨论】:

                    • 这是一种“进入后台”的合法方式,尽管不是习惯的方式(即,如果执行 setsid 等,则不需要双重分叉);所以无论谁投了反对票,我相信他们不应该。这不是“规范”,但很有帮助。
                    • 双 fork() 有一个很好的理由,但你必须至少在两者之间做一个 setsid()。它确保守护进程不是进程组领导,也没有控制终端。
                    【解决方案18】:

                    正如其他人提到的,fork() 是如何在 *nix 上执行此操作的。您可以使用 MingW 或 Cygwin 库在 Windows 上获取 fork()。但这些将要求您切换到使用 GCC 作为编译器。

                    在纯 Windows 世界中,您将使用 CreateProcess(或其派生类 CreateProcessAsUser、CreateProcessWithLogonW 之一)。

                    【讨论】:

                      【解决方案19】:

                      在 Linux 下最常用的方法是通过forking。在 Mac 上也应该这样,至于 Windows,我不确定 100%,但我相信它们有类似的东西。

                      基本上发生的情况是进程将自己分成两个进程,然后原来的进程退出(将控制权返回给 shell 或其他),第二个进程继续在后台运行。

                      【讨论】:

                        【解决方案20】:

                        在类 Unix 操作系统上的典型做法是在开头使用 fork() 并从父级退出。这在 Windows 上不起作用,但比在存在分叉的情况下启动另一个进程要优雅得多。

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2016-03-15
                          • 2010-10-28
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多