C / C ++程序如何将自己置于背景中?

时间:2008-09-22 18:44:03

标签: windows linux macos shell background

从命令行启动的正在运行的C或C ++程序的最佳方法是将自身置于后台,相当于用户使用'&'从unix shell启动时在命令的最后? (但用户没有。)这是一个GUI应用程序,不需要任何shell I / O,因此没有理由在启动后绑定shell。但是我想要一个shell命令启动,在没有'&'的情况下自动后台运行(或在Windows上)。

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

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

另一个解决方案是立即启动另一个执行版本(带有'system'或CreateProcess),使用相同的命令行参数,但将子项放在后台然后让父出口。但与将本身置于背景中的过程相比,这似乎很笨拙。

在几个答案后编辑:是的,一个fork()(或系统()或Windows上的CreateProcess)是一种解决这个问题的方法,我在原始问题中暗示过。但是所有这些解决方案都使得SECOND流程成为背景,然后终止原始流程。我想知道是否有办法将现有流程放入后台。一个区别是,如果应用程序是从记录其进程ID的脚本启动的(可能是为了以后的查杀或其他目的),新分叉或创建的进程将具有不同的ID,因此任何启动脚本都无法控制,如果你看到我得到了什么。

编辑#2

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

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

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

20 个答案:

答案 0 :(得分:10)

我的建议:不要这样做,至少不在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调用 - 除非它们明确请求访问命令窗口。

答案 1 :(得分:9)

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

答案 2 :(得分:7)

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

答案 3 :(得分:5)

需要做三件事,

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

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

答案 4 :(得分:4)

在UNIX上,你需要连续两次分叉并让父亲死掉。

答案 5 :(得分:4)

进程无法将自己置于后台,因为它不是负责背景与前景的人。那将是shell,它正在等待进程退出。如果您启动带有&符“&”的流程最后,shell会等待进程退出。

但进程可以逃脱shell的唯一方法是分叉另一个子进程,然后让它自己的原始自退出到等待的shell。

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

答案 6 :(得分:3)

后台进程是shell函数,而不是OS函数。

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

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

答案 7 :(得分:2)

你编辑了你的问题,但是你可能仍然错过了你的问题是一种语法错误的观点 - 如果这个过程没有放在后台开始,你希望PID保持不变,你不能忽视这样一个事实,即启动进程的程序正在等待那个PID,这几乎就是在前台的定义

我认为你需要考虑为什么你想要在背景中放置一些东西并保持PID相同。我建议你可能不需要这两种限制。

答案 8 :(得分:2)

跟进您编辑过的问题:

  

我想知道是否有办法将现有流程放到后台。

在类Unix操作系统中,我知道没有办法做到这一点。 shell被阻止,因为它正在执行wait()调用的一个变体,等待子进程退出。子进程没有办法继续运行,但不知何故导致shell的wait()以“请停止关注我”状态返回。你有子fork并退出原来的原因是shell将从wait()返回。

答案 9 :(得分:2)

以下是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应用程序,并且它在没有控制台的情况下自动运行。如果你想作为一项服务运行,你必须要查看它,因为我从来没有写过它,我真的不知道它们是如何工作的。

答案 10 :(得分:1)

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

在纯Windows世界中,您使用CreateProcess(或其衍生产品之一CreateProcessAsUser,CreateProcessWithLogonW)。

答案 11 :(得分:0)

我正在尝试解决方案。

父进程只需要一个fork。

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

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

这就是诀窍。

答案 12 :(得分:0)

在Unix中,我学会了使用fork()来做到这一点。 如果您想将正在运行的流程放入后台,fork两次。

答案 13 :(得分: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> &) */

答案 14 :(得分:0)

所以,正如你所说的,只是fork()ing不会起作用。您必须做的是fork()然后重新执行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]不一定是完整路径,因此需要修改它。

答案 15 :(得分:0)

在Windows下,我认为你将获得fork()的关闭是将程序作为Windows服务加载。我想。

以下是有关Windows服务的简介文章的链接... CodeProject: Simple Windows Service Sample

答案 16 :(得分:0)

最简单的背景形式是:

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 */
  9. 这应该完全符合您的计划。

答案 17 :(得分:0)

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

fork时,将子进程的PID保存在父进程中。退出父进程时,将PID输出到STD{OUT,ERR}或在return pid;末尾只有main()语句。然后,调用脚本可以获取程序的pid,尽管它需要了解程序的工作方式。

答案 18 :(得分:0)

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

答案 19 :(得分:0)

在Linux下执行此操作的最常见方法是通过forking。同样适用于Mac,对于Windows我不是100%肯定,但我相信他们有类似的东西。

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