Pclose 似乎使进程失败

Pclose seems to make process fail

这个问题是这个问题的后续问题:

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

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 "" 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)
    "[=11=]" stop
    "[=11=]" start
    ;;
  *)
    echo "Usage: [=11=] {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 会使程序在行为良好而不调用它时失败?


编辑:

以下是错误案例的更多信息

errno 是 Success
WIFEXITED 宏 returns true
WEXITSTATUS 宏 returns 141

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

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

在您知道守护程序启动程序已通过某种方式退出之前,不要pclose()管道。就个人而言,我会直接使用 pipe()fork()execv()(或 exec 系列函数的其他变体。我不认为 popen()是完成这项工作的正确工具。但是如果您要使用 popen(),则读取输入直到您不再获得 (EOF),然后安全地使用 pclose()。您不必打印您阅读的内容,尽管这样做是常规且明智的 — 'daemon starter' 脚本告诉您有用的信息。

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

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