C / C ++程序如何使其自身进入后台?

时间:2020-03-06 14:33:26  来源:igfitidea点击:

从命令行启动的正在运行的C或者C ++程序将自身置于后台的最佳方式是什么(等同于用户是否已从unix shell以命令末尾的"&"启动)? (但是用户不需要。)这是一个GUI应用程序,不需要任何外壳I / O,因此没有必要在启动后绑定外壳。但是我希望没有"&"(或者在Windows上)自动启动shell命令。

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

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

另一个解决方案是立即使用相同的命令行参数启动另一个已执行的版本(使用" system"或者CreateProcess),但将子级置于后台,然后使父级退出。但这与将其置于后台的过程相比显得笨拙。

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

编辑#2:

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

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

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

解决方案

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

在Linux下执行此操作的最常见方法是通过分支。在Mac上也应如此,因为我不确定100%是否适用于Windows,但我相信它们具有类似的功能。

基本上发生的是,该进程将自身分为两个进程,然后退出原来的一个进程(将控制权返回给Shell或者其他东西),第二个进程继续在后台运行。

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

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

在UNIX上,我们需要连续两次分叉并使父进程死亡。

我不确定Windows,但是在类似UNIX的系统上,可以先将Fork()再将setsid()分叉的进程移入未连接到终端的新进程组。

需要做三件事

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

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

如果我们需要脚本来获取程序的PID,则仍然可以在派生后获取它。

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

最简单的背景形式是:

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

在Unix中,如果要完全取消与tty的分离关系,可以执行以下操作:

  • 关闭所有可能访问tty的描述符(通常为0、1和2)。
  • 如果if(fork()!= 0)exit(0);`
  • setpgroup(0,getpid()); / *可能有必要防止外壳退出时出现SIGHUP。 * /
  • signal(SIGHUP,SIG_IGN); / *以防万一,与使用nohup启动程序相同。 * /
  • fd = open(" / dev / tty",O_RDWR);`
  • ioctl(fd,TIOCNOTTY,0); / *与终端解除关联* /
  • close(fd);
  • if(fork()!= 0)退出(0); / *只是为了保证* /

那应该完全守护程序。

要对已编辑的问题进行跟进:

I was wondering if there was a way to put the EXISTING process into the background.

在类似Unix的操作系统中,我确实没有办法做到这一点。外壳程序被阻止,因为它正在执行wait()调用的变体之一,等待子进程退出。子进程无法保持运行,但是会以某种方式导致shell的wait()返回"请停止监视我"状态。我们拥有子叉子并退出原始叉子的原因是这样,shell将从wait()返回。

这是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应用程序即可运行,并且该程序无需控制台即可自动运行。如果要作为服务运行,则必须查找该信息,因为我从未编写过任何服务,而且我真的不知道它们是如何工作的。

一个进程不能将自己置于后台,因为它不是后台与前台的负责人。那就是外壳,它正在等待进程退出。如果启动以"&"结尾的进程,那么外壳程序将不等待进程退出。

但是,该过程可以逃脱shell的唯一方法是派生另一个孩子,然后让其原来的自我退出回到等待的shell。

我们可以从Shell中使用Control-Z后台处理进程,然后键入" bg"。

使进程后台运行是Shell函数,而不是OS函数。

如果我们希望应用程序在后台启动,通常的技巧是编写一个Shell脚本来启动它,然后在后台启动它。

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

我认为,在Windows下,我们要使用fork()结束的事情是将程序作为Windows服务加载。

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

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

因此,正如我们所说,仅fork()ing并不能解决问题。我们必须做的是fork()然后重新执行(),如以下代码示例所示:

#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]不一定是完整路径,因此需要对其进行修改。

我的建议:不要这样做,至少在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程序明确要求访问命令窗口,否则Windows程序即使在命令外壳中调用时也会在后台自动运行。

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

在Unix中,我学会了使用fork()来做到这一点。
如果我们想将正在运行的进程置于后台,则将其"分叉"两次。

我正在尝试解决方案。

父进程仅需要一个fork。

最重要的一点是,在派生之后,父进程必须通过调用_exit(0);而不是通过调用exit(0);来终止。

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

这是诀窍。