【发布时间】: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 程序:
- 使用“start”参数启动脚本
- 等待 pid 文件创建
- 从 pid 文件中读取守护进程的 pid
- 通过检查文件
/proc/<daemon_pid>/exec的存在来定期检查守护进程是否处于活动状态 - 如果守护进程被杀死,重新启动它
这是我面临的问题。该程序只有在我不调用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,WEXITSTATUSetc. 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