c - Pclose 似乎使进程失败

标签 c linux daemon popen pclose

这个问题是这个问题的后续: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. 使用“开始”参数启动脚本
  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 的日志 block 评论

没有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

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

最佳答案

您使用 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 表示。其他程序可以更明智地作为守护进程运行。)

关于c - Pclose 似乎使进程失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51157476/

相关文章:

c - 数组名是C语言中的指针吗?

c - 为什么路由器不转发我的数据包?

c - 尝试在 C 中复制字符串的一部分时的有趣行为

linux - 如果不存在,如何将文本行添加到文件

linux - 在 VIM 上保存文件时自动 rsync 不起作用,因为 rsync 提示密码

php - 保持长时间运行(永远)的 php CLI 进程在 Windows 上运行

python - Python 中的守护线程与守护进程

c - 生产者消费者问题:posix mutex got locked twice when using condition variable?

Python守护进程没有pidfile

linux - 如何在 Linux 中重命名大量文件并更改每个文件的相同文件名元素?