这个问题是这个问题的后续: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 程序,它:
- 使用“开始”参数启动脚本
- 等待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
的日志 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
— 它也用于将不是专门设计为守护进程的程序转换为作为守护进程运行的程序。许多程序不能很好地作为守护进程运行 — 考虑一下守护进程 ls
, grep
、ps
或 sort
表示。其他程序可以更明智地作为守护进程运行。)
关于c - Pclose 似乎使进程失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51157476/