这个问题是这个问题的后续:从另一个程序控制一个C守护进程
我的目标是控制来自另一个程序的守护进程执行。
守护程序的代码非常简单。
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=%dn", getpid(), getpgrp());
sleep(1);
}
return EXIT_SUCCESS;
}
我已经为这个守护程序实现了一个SystemV init脚本,如下所示
#!/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' programn", DAEMON_NAME);
if(NULL == (daemon_pipe = popen(DAEMON_START_CMD, "r")))
{
printf("An error occured launching '%s': %mn", DAEMON_START_CMD);
return EXIT_FAILURE;
}
#ifdef USE_PCLOSE
else if(-1 == (ret = pclose(daemon_pipe)))
{
printf("An error occured waiting for '%s': %mn", DAEMON_START_CMD);
return EXIT_FAILURE;
}
#endif
else
{
printf("Script exit status : %dn", ret);
while(0 != access(DAEMON_PID_FILE, F_OK))
{
printf("Waiting for pid file creationn");
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=%dn", 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': %mn", DAEMON_START_CMD);
return EXIT_FAILURE;
}
#ifdef USE_PCLOSE
else if(-1 == (ret = pclose(daemon_pipe)))
{
printf("An error occured waiting for '%s': %mn", DAEMON_START_CMD);
return EXIT_FAILURE;
}
#endif
else
{
printf("Script exit status : %dn", ret);
while(0 != access(DAEMON_PID_FILE, F_OK))
{
printf("Waiting for pid file creationn");
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=%dn", 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 宏返回真
WEXITSTATUS 宏返回 141
通过进一步调试,我已经重新表示修改 init 脚本以将输出记录到文件中可以使其工作......为什么?
您使用popen(DAEMON_START_CMD, "r")
. 这意味着您的"守护程序观察程序"正在读取"守护程序启动器"脚本的标准输出。 如果pclose()
该管道,脚本将写入标准输出并获取 SIGPIPE,因为管道的读取端已关闭。 这是否发生在实际守护进程启动之前是有争议的 - 以及时间问题。
在知道守护程序启动器已通过某种方式退出之前,不要pclose()
管道。 就个人而言,我会直接使用pipe()
、fork()
和execv()
(或exec
系列函数的其他变体。 我不认为popen()
是这项工作的正确工具。但是,如果您要使用popen()
,请读取输入,直到不再获得(EOF(,然后安全地使用pclose()
。 您不必打印所阅读的内容,尽管这样做是传统且明智的 - "守护程序启动器"脚本会告诉您有用的信息。
检查进程 ID 是否仍在运行的经典方法是使用kill(daemon_pid, 0)
。 如果执行的进程具有适当的特权(与进程相同的 UID,或root
特权(,则此方法有效。 如果您无法向 PID 发送活动信号,这将无济于事。
(我假设start-stop-daemon
是一个程序,可能是一个 C 程序而不是一个 shell 脚本,它启动另一个程序作为守护进程。 我有一个类似的程序,我称之为daemonize
- 它也用于将未专门设计为守护程序的程序转换为作为守护进程运行的程序。 许多程序不能像守护进程那样工作——考虑一下守护进程ls
、grep
、ps
或sort
意味着什么。 其他程序可以更明智地作为守护程序运行。