【问题标题】:Pclose seems to make process failPclose 似乎使进程失败
【发布时间】:2018-12-11 22:32:15
【问题描述】:

这个问题是这个问题的后续:Controlling a C daemon from another program

我的目标是从另一个程序控制守护进程的执行。
守护进程的代码非常简单。

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

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

  while(1)
  {
    syslog(LOG_INFO, "Daemon alive - pid=%d, pgid=%d\n", getpid(), getpgrp());
    sleep(1);
  }

  return EXIT_SUCCESS;
}

我已经为这个守护进程实现了一个 SystemV 初始化脚本,如下所示

#!/bin/sh

NAME=daemon-test
DAEMON=/usr/bin/${NAME}
SCRIPTNAME=/etc/init.d/${NAME}
USER=root
RUN_LEVEL=99
PID_FILE=/var/run/${NAME}.pid
RETRY=3

start_daemon()
{
    start-stop-daemon --start --background --name ${NAME} --chuid ${USER} --nicelevel ${RUN_LEVEL} --make-pidfile --pidfile ${PID_FILE} --exec ${DAEMON}
    ret=$?

    if [ "$ret" -eq 0 ]; then
        echo "'${NAME}' started"
    elif [ "$ret" -eq 1 ]; then
        echo "'${NAME}' is already running"
    else
        echo "An error occured starting '${NAME}'"
    fi
    return ${ret}
}

stop_daemon()
{
    start-stop-daemon --stop --retry ${RETRY} --remove-pidfile --pidfile ${PID_FILE} --name ${NAME} --signal 9
    ret=$?

    if [ "$ret" -eq 0 ]; then
        echo "'${NAME}' stopped"
    elif [ "$ret" -eq 1 ]; then
        echo "'${NAME}' is already stopped"
    elif [ "$ret" -eq 2 ]; then
        echo "'${NAME}' not stopped after ${RETRY} tries"
    else
        echo "An error occured stopping '${NAME}'"
    fi
    return ${ret}
}

status_daemon()
{
    start-stop-daemon --status --pidfile ${PID_FILE} --name ${NAME}
    ret=$?

    if [ "$ret" -eq 0 ]; then
        echo "'${NAME}' is running"
    elif [ "$ret" -eq 1 ]; then
        echo "'${NAME}' stopped but pid file exits"
    elif [ "$ret" -eq 3 ]; then
        echo "'${NAME}' stopped"
    elif [ "$ret" -eq 4 ]; then
        echo "Unable to get '${NAME}' status"
    else
        echo "Unknown status : ${ret}"
    fi
    return ${ret}
}

case "$1" in
  start)
    echo "Starting '${NAME}' deamon :"
    start_daemon
    ;;
  stop)
    echo "Stopping '${NAME}' deamon :"
    stop_daemon
    ;;
  status)
    echo "Getting '${NAME}' deamon status :"
    status_daemon
    ;;
  restart|reload)
    "$0" stop
    "$0" start
    ;;
  *)
    echo "Usage: $0 {start|stop|status|restart}"
    exit 1
esac

exit $?

从命令行使用这个脚本来控制守护进程的执行效果很好。


所以现在的目标是使用另一个 c 程序中的这个脚本来启动守护进程并控制它从这个程序的执行。

我已经实现了一个简单的 C 程序:

  1. 使用“start”参数启动脚本
  2. 等待 pid 文件创建
  3. 从 pid 文件中读取守护进程的 pid
  4. 通过检查文件/proc/<daemon_pid>/exec 的存在来定期检查守护进程是否处于活动状态
  5. 如果守护进程被杀死,重新启动它

这是我面临的问题。该程序只有在我不调用pclose时才能正常运行。

这是程序的代码

#define DAEMON_NAME       "daemon-test"
#define DAEMON_START_CMD  "/etc/init.d/" DAEMON_NAME " start"
#define DAEMON_STOP_CMD   "/etc/init.d/" DAEMON_NAME " stop"
#define DAEMON_PID_FILE   "/var/run/" DAEMON_NAME ".pid"

int main()
{
    char daemon_proc_path[256];
    FILE* daemon_pipe = NULL;
    int daemon_pid = 0;
    FILE* fp = NULL;
    int ret = 0;
    int i = 0;

    printf("Launching '%s' program\n", DAEMON_NAME);
    if(NULL == (daemon_pipe = popen(DAEMON_START_CMD, "r")))
    {
        printf("An error occured launching '%s': %m\n", DAEMON_START_CMD);
        return EXIT_FAILURE;
    }
    #ifdef USE_PCLOSE
    else if(-1 == (ret = pclose(daemon_pipe)))
    {
        printf("An error occured waiting for '%s': %m\n", DAEMON_START_CMD);
        return EXIT_FAILURE;
    }
    #endif
    else
    {
        printf("Script exit status : %d\n", ret);

        while(0 != access(DAEMON_PID_FILE, F_OK))
        {
            printf("Waiting for pid file creation\n");
            sleep(1);
        }
        if(NULL == (fp = fopen(DAEMON_PID_FILE, "r")))
        {
            printf("Unable to open '%s'\n", DAEMON_PID_FILE);
            return EXIT_FAILURE;
        }
        fscanf(fp, "%d", &daemon_pid);
        fclose(fp);
        printf("Daemon has pid=%d\n", daemon_pid);
        sprintf(daemon_proc_path, "/proc/%d/exe", daemon_pid);
    }

    while(1)
    {
        if(0 != access(daemon_proc_path, F_OK))
        {
            printf("\n--- Daemon (pid=%d) has been killed ---\n", daemon_pid);
            printf("Relaunching new daemon instance...\n");
            if(NULL == (daemon_pipe = popen(DAEMON_START_CMD, "r")))
            {
                printf("An error occured launching '%s': %m\n", DAEMON_START_CMD);
                return EXIT_FAILURE;
            }
            #ifdef USE_PCLOSE
            else if(-1 == (ret = pclose(daemon_pipe)))
            {
                printf("An error occured waiting for '%s': %m\n", DAEMON_START_CMD);
                return EXIT_FAILURE;
            }
            #endif
            else
            {
                printf("Script exit status : %d\n", ret);

                while(0 != access(DAEMON_PID_FILE, F_OK))
                {
                    printf("Waiting for pid file creation\n");
                    sleep(1);
                }
                if(NULL == (fp = fopen(DAEMON_PID_FILE, "r")))
                {
                    printf("Unable to open '%s'\n", DAEMON_PID_FILE);
                    return EXIT_FAILURE;
                }
                fscanf(fp, "%d", &daemon_pid);
                fclose(fp);
                printf("Daemon has pid=%d\n", daemon_pid);
                sprintf(daemon_proc_path, "/proc/%d/exe", daemon_pid);
            }
        }
        else
        {
            printf("Daemon alive (pid=%d)\n", daemon_pid);
        }
        sleep(1);
    }

    return EXIT_SUCCESS;
}

据我了解pclose 应该等待子进程终止,只有当子进程返回时,它才会关闭管道。

所以我不明白为什么我使用pclose 的实现在不调用它的情况下不起作用。

这里是带有和不带有 pclose 块注释的日志

没有pclose调用:

# ./popenTest 
Launching 'daemon-test' program
Script exit status : 0
Waiting for pid file creation
Daemon has pid=435
Daemon alive (pid=435)
Daemon alive (pid=435)
Daemon alive (pid=435)
Daemon alive (pid=435)

pclose 通话:

# ./popenTest 
Launching 'daemon-test' program
Script exit status : 36096
Waiting for pid file creation
Waiting for pid file creation
Waiting for pid file creation
Waiting for pid file creation

如您所见,守护进程永远不会启动,pid 文件也永远不会创建。

即使我的程序在没有pclose 的情况下也能正常工作,我也想了解调用pclose 的根本问题。

为什么使用pclose会导致程序在行为良好而不调用它时失败?


编辑:

这里是错误案例的更多信息

错误号是Success
WIFEXITED 宏返回 true
WEXITSTATUS 宏返回 141

通过进一步调试,我指出修改 init 脚本以将输出记录到文件使其工作... 为什么?

【问题讨论】:

  • 我不明白你为什么使用popen(),如果你实际上没有从管道中读取,以及为什么你调用脚本而不是直接启动你的“守护进程”(这将如果你让它成为一个真正的守护进程,分离自己并开始一个新会话,那就非常简单了)——但无论如何,请尝试检查errno并使用WIFEXITED, WEXITSTATUS etc. macros来获取有关孩子如何退出的更多信息。
  • 我这样做是因为我不会修改守护进程的代码。如果不使用start-stop-daemon,我无法获得它的pid,除非重新实现fork + exec 以返回它的pid。此外,它允许我在控制应用程序停止时从命令行管理守护程序(这可能会发生,在这种情况下守护程序仍将运行)。而且它更符合 BSP 上已有的内容
  • 您不需要“重新实现”fork/exec,只需使用它们。相反,你所做的看起来像一台 Rube Goldberg 机器。 真正的守护进程会自行分离并写入pid文件,顺便说一句。
  • 我的意思是用fork + exec重新实现popen
  • 当您使用pclose() 时,您激活的已注释掉的代码块是吗?为什么你的代码不能在运行时选择是否执行pclose()?那么我们就不必猜测了——你会说“当我运行不带参数的命令时,pclose() 没有被执行,输出是这样的;当我带任何参数运行它时,pclose() 被执行,输出就是这个”。而且您无需重新编译即可测试这两种变体。

标签: c linux daemon popen pclose


【解决方案1】:

您使用popen(DAEMON_START_CMD, "r")。这意味着您的“守护程序观察者”正在读取您的“守护程序启动器”脚本的标准输出。如果您pclose() 该管道,脚本将写入标准输出并获得 SIGPIPE,因为管道的读取端已关闭。这是否在实际的守护进程启动之前发生,还有待商榷——还有时间问题。

在您知道守护进程启动器已通过某种方式或其他方式退出之前,请不要pclose() 管道。就个人而言,我会直接使用pipe()fork()execv()(或exec 系列函数的其他变体。我不认为popen() 是适合这项工作的工具。但如果你将使用popen(),然后读取输入直到你没有更多(EOF),然后安全地使用pclose()。你不必打印你读到的内容,尽管这样做是传统和明智的所以——'daemon starter' 脚本告诉你有用的信息。

检查进程 ID 是否仍在运行的经典方法是使用kill(daemon_pid, 0)。如果执行的进程具有适当的特权(与进程相同的 UID,或root 特权),则此方法有效。如果您无法向 PID 发送活动信号,这将无济于事。

(我假设 start-stop-daemon 是一个程序,可能是一个 C 程序而不是一个 shell 脚本,它启动另一个程序作为守护进程。我有一个类似的程序,我称之为 daemonize — 它也用于转换不是专门设计为守护进程的程序进入作为守护进程运行的程序。许多程序不能很好地作为守护进程运行——考虑守护进程lsgreppssort 意味着什么。其他程序可以更多明智地作为守护进程运行。)

【讨论】:

  • 感谢您提供这些信息,我已经能够找到有关 popenSIGPIPE 的更多文档。所以我认为你是对的,popen 似乎不是好的解决方案,我将尝试使用 forkexec
  • 我终于用forkexeclwaitpid 重新实现了它,它就像一个魅力。问题确实来自未读的管道。谢谢
猜你喜欢
  • 2016-04-05
  • 1970-01-01
  • 1970-01-01
  • 2014-09-27
  • 2012-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多