14

从命令行启动的正在运行的 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 中心主义,它恰好是我眼前的系统。但我确实在寻找所有三个平台的解决方案。

4

20 回答 20

11

我的建议:不要这样做,至少不要在 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
    

    如果您的程序自行分叉,则此行为将中断。

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

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

于 2008-09-23T00:14:42.007 回答
9

如果我理解正确的话,在 Linux 上,daemon()就是你要找的东西。

于 2008-09-22T20:09:50.270 回答
7

它通常在类 Unix 操作系统上完成的方式是在开头 fork() 并从父级退出。这在 Windows 上不起作用,但比启动另一个存在分叉的进程要优雅得多。

于 2008-09-22T18:46:05.167 回答
6

需要做的三件事,

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

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

于 2008-09-22T19:05:17.167 回答
4

A process cannot put itself into the background, because it isn't the one in charge of background vs. foreground. That would be the shell, which is waiting for process exit. If you launch a process with an ampersand "&" at the end, then the shell does not wait for process exit.

But the only way the process can escape the shell is to fork off another child and then let its original self exit back to the waiting shell.

From the shell, you can background a process with Control-Z, then type "bg".

于 2008-09-22T19:23:20.320 回答
4

在 UNIX 上,您需要连续两次 fork 并让父节点死亡。

于 2008-09-22T18:56:15.567 回答
3

Backgrounding a process is a shell function, not an OS function.

If you want an app to start in the background, the typical trick is to write a shell script to launch it that launches it in the background.

#! /bin/sh
/path/to/myGuiApplication &
于 2008-09-22T19:24:02.737 回答
2

Here is some pseudocode for 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("/")

And congratulations, your program continues as an independent "daemonized" process without a controlling TTY and without any standard input or output.

Now, in Windows you simply build your program as a Win32 application with WinMain() instead of main(), and it runs without a console automatically. If you want to run as a service, you'll have to look that up because I've never written one and I don't really know how they work.

于 2008-09-22T19:18:03.673 回答
2

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

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

于 2008-09-23T01:50:06.147 回答
2

要跟进您编辑的问题:

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

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

于 2008-09-22T19:10:58.493 回答
1

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

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

于 2008-09-22T18:54:33.760 回答
1

背景的最简单形式是:

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 */

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

于 2008-09-22T19:08:32.180 回答
0

所以,正如你所说,只是 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] 不一定是完整路径,因此需要对其进行修改。

于 2008-09-22T22:48:58.097 回答
0

我认为,在 Windows 下,你将要访问 fork() 的最后一件事是将你的程序作为 Windows 服务加载。

这是一篇关于 Windows 服务的介绍性文章的链接... CodeProject:简单的 Windows 服务示例

于 2008-09-22T19:50:05.950 回答
0

在 Linux 下执行此操作的最常见方法是通过分叉。在 Mac 上也应该这样,至于 Windows,我不确定 100%,但我相信它们有类似的东西。

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

于 2008-09-22T18:47:34.400 回答
0

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

于 2008-09-22T19:05:06.637 回答
0

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

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

于 2008-09-22T19:07:24.900 回答
0

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

于 2009-02-23T12:28:03.370 回答
0
/**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> &) */
于 2008-11-29T14:33:13.130 回答
0

我正在尝试解决方案。

父进程只需要一个分叉。

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

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

这就是诀窍。

于 2010-03-24T10:21:09.377 回答