从另一个程序控制 C 守护进程

Controlling a C daemon from another program

我正在尝试从另一个用户空间程序控制 C 守护程序。

- 简单的 C 守护程序

这个守护进程只是一个 C 程序,它自我守护并通过系统日志每秒记录一条消息。

#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <unistd.h>
#include <signal.h>

void bye();

int main()
{
  printf("Daemon starting ...\n");
  openlog("daemon-test", LOG_PID, LOG_DAEMON);
  signal(SIGTERM, bye);

  if(0 != daemon(0, 0))
  {
    syslog(LOG_ERR, "Can't daemonize\n");
    return EXIT_FAILURE;
  }

  syslog(LOG_INFO, "Daemon started !\n");

  while(1)
  {
    syslog(LOG_INFO, "Daemon alive\n");
    sleep(1);
  }

  return EXIT_SUCCESS;
}

void bye()
{
  syslog(LOG_INFO, "Daemon killed !\n");
  exit(EXIT_SUCCESS);
}

- 从 C 测试程序启动和终止守护进程

出于测试目的,我开发了一个最小示例。我正在使用 popen 来启动守护进程,因为我希望我的程序继续执行。

5 秒后,测试程序应该会终止守护进程。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#define DAEMON_NAME       "daemon-test"

int main()
{
    FILE* pipe = NULL;
    int i = 0;

    printf("Launching '%s' program\n", DAEMON_NAME);
    if(NULL == (pipe = popen(DAEMON_NAME, "re")))
    {
        printf("An error occured launching '%s': %m\n", DAEMON_NAME);
        return EXIT_FAILURE;
    }
    printf("Program '%s' launched\n", DAEMON_NAME);

    while(i<5)
    {
        printf("Program alive !\n");
        sleep(1);
        i++;
    }

    if(NULL == (pipe = popen("killall " DAEMON_NAME, "re")))
    {
        printf("An error occured killing '%s' program: %m\n", DAEMON_NAME);
        return EXIT_FAILURE;
    }
    printf("Program '%s' killed\n", DAEMON_NAME);

    return EXIT_SUCCESS;
}

测试程序日志:

$ ./popenTest 
Launching 'daemon-test' program
Program 'daemon-test' launched
Program alive !
Program alive !
Program alive !
Program alive !
Program alive !
Program 'daemon-test' killed

系统日志:

Jun 25 13:58:15 PC325 daemon-test[4445]: Daemon started !
Jun 25 13:58:15 PC325 daemon-test[4445]: Daemon alive
Jun 25 13:58:16 PC325 daemon-test[4445]: Daemon alive
Jun 25 13:58:17 PC325 daemon-test[4445]: Daemon alive
Jun 25 13:58:18 PC325 daemon-test[4445]: Daemon alive
Jun 25 13:58:19 PC325 daemon-test[4445]: Daemon alive
Jun 25 13:58:20 PC325 daemon-test[4445]: Daemon alive
Jun 25 13:58:20 PC325 daemon-test[4445]: Daemon killed !

所以我可以从我的 C 程序启动和终止守护进程,但是我想改进某些特定情况下的行为。

- 处理守护进程崩溃

守护进程有时可能会失败,在这种情况下,应该通知控制程序以便重新启动。我的问题是检测守护进程已经停止。

虽然我已经考虑通过调用 pclose 来启动等待守护进程终止的线程,但是它不会工作,因为守护进程已经关闭了文件描述符并分离了进程。

所以我正在寻找在守护程序退出时通知程序的最佳方法。

我可以使用 linux 与 exec 家族(例如 pgrep daemon-testps aux | grep daemon-test)的调用进行投票,但我认为有更有效的方法来实现这一点。

- 处理测试程序错误

如果测试程序在杀死守护进程之前被杀死或失败,则在下次执行时,守护进程的两个实例将同时运行。

测试程序日志:

$ ./popenTest 
Launching 'daemon-test' program
Program 'daemon-test' launched
Program alive !
Program alive !
Program alive !
^C
$ ./popenTest 
Launching 'daemon-test' program
Program 'daemon-test' launched
Program alive !
Program alive !
Program alive !
Program alive !
Program alive !
Program 'daemon-test' killed

系统日志:

Jun 25 14:17:25 PC325 daemon-test[4543]: Daemon started !
Jun 25 14:17:25 PC325 daemon-test[4543]: Daemon alive
Jun 25 14:17:26 PC325 daemon-test[4543]: Daemon alive
Jun 25 14:17:27 PC325 daemon-test[4543]: Daemon alive
Jun 25 14:17:28 PC325 daemon-test[4543]: Daemon alive
Jun 25 14:17:29 PC325 daemon-test[4543]: Daemon alive
Jun 25 14:17:29 PC325 daemon-test[4547]: Daemon started !
Jun 25 14:17:29 PC325 daemon-test[4547]: Daemon alive
Jun 25 14:17:30 PC325 daemon-test[4543]: Daemon alive
Jun 25 14:17:30 PC325 daemon-test[4547]: Daemon alive
Jun 25 14:17:31 PC325 daemon-test[4543]: Daemon alive
Jun 25 14:17:31 PC325 daemon-test[4547]: Daemon alive
Jun 25 14:17:32 PC325 daemon-test[4543]: Daemon alive
Jun 25 14:17:32 PC325 daemon-test[4547]: Daemon alive
Jun 25 14:17:33 PC325 daemon-test[4543]: Daemon alive
Jun 25 14:17:33 PC325 daemon-test[4547]: Daemon alive
Jun 25 14:17:34 PC325 daemon-test[4543]: Daemon alive
Jun 25 14:17:34 PC325 daemon-test[4547]: Daemon alive
Jun 25 14:17:34 PC325 daemon-test[4543]: Daemon killed !
Jun 25 14:17:34 PC325 daemon-test[4547]: Daemon killed !

我想通过检查是否已经有守护程序实例来避免这种情况运行ning。如果没有,我可以从控制程序启动守护进程。

否则,如果守护程序的一个或多个实例运行正在运行,我将在启动新的之前杀死它们。

这可以通过调用 killall daemon-test 来实现,但是在每次执行时调用这个命令并不能让我满意,因为它在大多数时候都没有用。

此外,我想明确记录每次执行时的情况,尽管我想知道在那种情况下 运行ning 有多少实例。

再次使用 linux 命令调用可以轻松解决此问题,但我正在寻找最有效的方法。

有谁知道我如何在不依赖 linux 命令调用的情况下实现守护进程控制?


编辑: 2018 年 6 月 26 日

我应该从一开始就把它精确化,但我的目标是能够监控守护进程 而无需 修改其代码。

因此守护进程不会将其 pid 写入文件,并且始终与其调用者分离。

而不是 运行 通过 popen 的程序为什么不使用旧的 POSIX fork + exec?它给了你更多的灵活性。

现在,回答你的问题:

My problem is to detect that the daemon has been stopped.

为此,您必须在 parent/controlling 进程中收听 SIGCHLD 信号。这已经足够好了,因为您直接调用了流程。但是如果你调用了一个 shell 脚本然后分叉你的守护进程,它就会变得困难。这就是为什么大多数守护进程都写了一个叫做 pid 文件的原因——一个由守护进程早期编写的文件,它的 PID 作为该文件中的唯一内容。通常,人们把它写成 /tmp/mydaemon.pid 或类似的东西。

在Linux,你的控制进程可以从这个文件中读取PID,然后每一秒你都可以测试/proc/<pid>/exe文件是否存在。如果没有,你就知道守护进程死了。例如,如果您的子程序的 PID 是 1234,那么 /proc/1234/exe 将软 link 到子程序可执行文件的实际位置。

像这样:

FILE *f;
pid_t pid_child;
char proc_path[256];

f = fopen("/tmp/mydaemon.pid", "r");
fscanf(f, "%d", &pid_child);
fclose(f);
sprintf(proc_path, "/proc/%d/exe", pid_child);

while(1) {
    if (access(proc_path, F_OK) == 0) {
        printf("Program alive !\n");
        sleep(1);
    } else {
        printf("Program dead!\n");
        break;
    }
}

事实上,这大概就是实现了多少个init系统。请参阅 rc、systemd、upstart 等,以更好地了解他们如何更详细地实现这一点。

您可以运行 守护程序中的套接字服务器,然后像普通 CLI 一样使用控制客户端。 CLI 向守护进程发送测试消息或控制命令,守护进程给出响应。根据守护进程的响应,CLI 可以查看守护进程的状态并对其进行控制。