当我重新启动 Raspberry Pi(Stretch)时,守护进程因/run/user/1000
不存在而无法启动。这是我的单位文件:
[Unit]
Description=SBFspot Upload Daemon
[Service]
User=pi
Type=forking
TimeoutStopSec=60
ExecStart=/usr/local/bin/sbfspot.3/SBFspotUploadDaemon -p /run/user/1000/sbfspotupload.pid 2>&1> /dev/null
PIDFile=/run/user/1000/sbfspotupload.pid
Restart=no
RestartSec=15
SuccessExitStatus=SIGKILL
[Install]
WantedBy=default.target
当我配置为Restart=on-failure
在几次重试后一切顺利时,但这并不是我真正想要的。我希望守护进程等待/run/user/1000
安装。我尝试过After=run-user-1000.mount
,但仍然失败。
这是可能的还是我必须坚持Restart=on-failure
?
答案1
/run/user/1000
当然,在用户 #1000 登录或显式启动 xyr 每用户服务管理之前,它并不存在,这是一个转移注意力的内容。使用它的整个机制不应该存在。
该程序的错误#215比你想象的要深得多。这个服务单元文件是非常错误的,程序本身的运行也是如此。有很多货物狂热编程是基于没有真正理解 systemd 服务单元的基础知识。
- 服务单元不是 shell 脚本。 系统手册做解释一下。此处的设置
ExecStart
会导致服务程序以一些额外的参数运行,2>&1>
并且/dev/null
. - 服务管理器已经确保只有一项服务运行。 这里添加的所有代码都是不必要的垃圾。
- 摇摇欲坠且危险的PID文件机制应该不是使用。 它在适当的服务管理中没有地位。
- 服务管理器还处理在守护进程上下文中调用服务。 很多其他代码
main()
是还基于恶魔化谬误的不必要的垃圾。- 程序
fork()
根本不应该是 ing,并且服务就绪机制不应该指定为Type=forking
。就像现实世界中的许多程序一样,这个程序是不是首先谈谈分叉准备协议。 - 该计划是已经以超级用户身份运行。
User=root
是不必要的,确实应该重新设计服务不是需要以超级用户权限运行,而是在专用的非特权服务帐户的支持下运行。 - 服务经理是已经记录标准输出和错误,并且比这个程序做得更好。这个自行开发的日志系统只会增加一个日志文件,直到填满整个文件系统,从而消耗为超级用户保留的所有紧急空间。
- 您的日志只是标准错误,可以从 C++ 轻松访问为
std::clog
. - 实际上,
fork()
从到标准错误重定向的所有代码不应该使用。服务管理手柄全部其中,从会话领导到工作目录和 umask 到标准 I/O,并且做得正确。这个程序没有,也不应该尝试这样做任何当在服务管理器下使用时,它本身就是这样的。你从 Boost 获取的一切都是错误的。
- 程序
- 三个服务单元是不必要的维护开销。 它们仅在
After
设置上有所不同,并且可以将它们合并为一个。 - 无礼终止并不是成功。 鉴于有已经终止时清理文件的一个问题
SuccessExitStatus=SIGKILL
是错误的。正常终止应该是优雅的,通过SIGTERM
,并且SIGKILL
应该被认为是异常的。 (当然,output
正如已经解释的那样,整个文件机制是一个实现得很糟糕的本地日志机制,不应该在服务管理下使用。)这是 systemd 的默认设置。 - 数据库对象和其他内容的析构函数应该运行。 不要
main()
离开exit()
。
正确实现并在服务管理器下运行的守护程序(无论是来自 daemontools、runit、s6、nosh、systemd 还是其他东西)要短得多:
……// 到目前为止都一样 无效pvo_上传(无效) { std::clog << "启动守护进程..." << std::endl; 公共服务代码(); std::clog << "停止守护进程..." << std::endl; } int main(int argc, char *argv[]) { 整数c; const char *config_file = ""; /* 解析命令行 */ 同时(1) { 静态结构选项 long_options[] = { {“配置文件”,required_argument,0,'c'}, { 0, 0, 0, 0 } }; int 选项索引 = 0; c = getopt_long (argc, argv, "c:", long_options, &option_index); if (c == -1) 中断; 开关(c) { 案例“c”: 配置文件=optarg; 休息; 默认: 返回 EXIT_FAILURE; 休息; } } if (cfg.readSettings(argv[0], config_file) != 配置::CFG_OK) 返回 EXIT_FAILURE; std::clog <<“正在启动 SBFspotUploadDaemon 版本”<< 版本 << std::endl; // 检查数据库是否可访问 db_SQL_Base db = db_SQL_Base(); db.open(cfg.getSqlHostname(), cfg.getSqlUsername(), cfg.getSqlPassword(), cfg.getSqlDatabase()); if (!db.isopen()) { std::clog <<“无法打开数据库。检查配置。” << std::endl; 返回 EXIT_FAILURE; } // 检查数据库版本 int schema_version = 0; db.get_config(SQL_SCHEMAVERSION, schema_version); db.close(); if (schema_version < SQL_MINIMUM_SCHEMA_VERSION) { std::clog << "将数据库升级到版本 " << SQL_MINIMUM_SCHEMA_VERSION << std::endl; 返回 EXIT_FAILURE; } // 安装我们的信号处理程序。 // 这响应服务管理器发出的服务停止信号。 信号(SIGTERM,处理程序); // 启动守护进程循环 pvo_upload(); 返回退出_成功; }
而且服务单元也更短:
[单元] 描述=SBFspot 上传守护进程 After=mysql.service mariadb.service network.target [服务] 类型=简单 超时停止秒=10 ExecStart=/usr/local/bin/sbfspot.3/SBFspotUploadDaemon 重新启动=成功 [安装] WantedBy=多用户.target
systemctl status
可以使用和查看日志输出journalctl
(-u
如果需要,可以使用选项和服务名称)。
进一步阅读
- 乔纳森·德博因·波拉德 (2016)。在本世纪不要使用
logrotate
or 。newsyslog
。经常给出的答案。 - 乔纳森·德博因·波拉德 (2001)。 设计 Unix 守护程序时要避免的错误。经常给出的答案。
- 乔纳森·德博因·波拉德 (2015)。Unix 守护进程的就绪协议问题。常见答案。
- https://unix.stackexchange.com/a/283739/5132
- https://unix.stackexchange.com/a/321716/5132