|
吾爱游客
发表于 2021-7-23 10:31
1、申 请 I D:Quinx
2、个人邮箱:1092284356
3、原创技术文章:systemd-wathdog代码机制分析
软硬兼施
在linux系统的init流程被systemd取代之后,对于监视系统运行的方式依然保持着软硬两个方向的看门狗设计。硬件看门狗在systemd中会定期的给watchdog发送信号,以至于系统或者systemd挂起之后能自动重启进行复原;软件看门狗则是systemd为了保持系统的完善性,对systemd下各种各样的服务进行监控提供的软狗,且每个服务独立配置,当服务挂起无响应能被重启。
这两种软硬兼施的兼施的手段在systemd_v183版本之后,对监控系统的所有部件进行全面支持。
硬狗代码逻辑
硬件看门狗则是systemd使用了内核提供的/dev/watchdog设备文件,systemd在启动了硬件看门狗之后会定期的向设备文件中发送信号,如果系统挂住或者systemd卡死,一段时间未向/dev/watchdog发送信号,这时候看门狗会使系统进行重启。
在系统中,如果想设置硬狗,那么只需要在/etc/systemd/system.conf中反注释并修改下列三个修改项:
[Shell] 纯文本查看 复制代码 RuntimeWatchdogSec=10,时间可以自行修改,默认未开启,值为0(单位s,ms, min, h, d, w)。
ShutdownWatchdogSec=10min,这个设置项作用在系统重启时候提供保障,默认10分钟,如果在系统重启过程中卡住或者挂着,10分钟超时再次通过看门狗进行强制重启。
WatchdogDevice=bool,配置运行和关闭硬狗设备文件,默认"/dev/watchdog",如果系统硬件上看门狗不可用,那么设置无效。
同时在systemd中还会去读取内核命令行参数检查用户配置设备文件和内核支持的硬狗是否一致,以内核支持的为主,如下:
[Shell] 纯文本查看 复制代码 systemd.watchdog_device,获取内核选项指定的路径,一般为"/dev/watchdog"
硬狗代码分析
[Java] 纯文本查看 复制代码 static int load_configuration(int argc, char **argv, const char **ret_error_message) {
...
// 解析 "/etc/systemd/system.conf", 和
// 用户自定义配置项"/etc/systemd/system.conf.d/*"
parse_config_file()
...
// parse_proc_cmdline_item解析内核参数,一般为"/dev/watchdog"
if (arg_system) {
r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0);
if (r < 0)
log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
...
}
...
}
在parse_proc_cmdline_item()解析内核选项参数,解析看门狗的参数:
image1
接下来对硬狗配置进行配置,在函数initialize_runtime()进行设置:
image2
watchdog_set_device(path)函数设置watchdog_device全局变量,设置硬狗设备文件路径,为开启硬狗做准备
watchdog_set_timeout(usec)函数检查硬狗设备文件是否打开,如果打开了只用更新超时时间update_timeout(),通过ioctl向"/dev/watchdog"写入WDIOC_SETTIMEOUT命令+超时时间。到此,对硬狗的配置加载完毕,接下来就是systemd经常性的向/dev/watchdog中发送信号,在systemd的事件循环函数manager_loop(m)中进行处理。[Java] 纯文本查看 复制代码 int manager_loop(Manager *m) {
RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 50000);
...
while (m->objective == MANAGER_OK) {
usec_t wait_usec;
// 对系统设置的看门狗配置进行检查
if (m->runtime_watchdog > 0 && m->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m))
watchdog_ping();
...
}
...} watchdog_ping()先检查硬狗设备文件是否打开,然后同样通过ioctl向/dev/watchdog中写入WDIOC_KEEPALIVE命令,这样就完成了喂狗。软狗代码逻辑
软狗的工作在于可以对每一个服务都可以进行配置,那么分为三步操作,第一步设置.service的软狗配置
[Shell] 纯文本查看 复制代码 [Unit]
Description=My Little Daemon
Documentation=man:mylittled(8)
[Service]
ExecStart=/usr/bin/mylittled
WatchdogSec=30s
Restart=on-failure
StartLimitInterval=5min
StartLimitBurst=4
StartLimitAction=reboot-force
在需要监管的服务.service中:WatchdogSec=即等于开启了服务的软狗(数值>=0),同时设置了环境变量WATCHDOG_USEC;Restart=存在no,on-success,on-failure,on-abnormal,on-watchdog,on-abort,always。
no即为服务不会重新启动;
on-success即为服务完全退出的时候才进行重启;或SIGHUP,SIGINT,SIGTERM,SIGPIPE退出信号;
on-failure即为由非上述四种信号,或服务挂起,操作超时,导致的看门狗超时,该服务重启;
on-abnormal即为非上述四种信号,操作超时,导致的看门狗超时,该服务重启;
on-watchdog即仅仅为软件看门狗超时才服务重启;
on-abort即为仅当由于未捕获的信号未指定为干净退出状态而导致服务进程退出时,服务才会重新启动;
always即为无论服务是否干净退出,被信号异常终止或超时,都将重新启动该服务;如下表:[td]Restart= | no | always | on-success | on-failure | on-abnormal | on-abort | on-watchdog | 清除退出代码或信号 |
| X | X |
| |
| | 不干净的退出代码 |
| X |
| X |
| |
| 信号不干净 |
| X |
| X | X | X |
| 超时 |
| X |
| X | X |
| | 看门狗 |
| X |
| X | X |
| X |
StartLimitBurst=和StartLimitInterval=用来设置给定时间内最多重启的次数。如果超过该次数,则根据StartLimitAction=的设置进行动作;
StartLimitAction=可以设置为重启(reboot)、强制重启(reboot-force),立即重启(reboot-immediate)。
软狗代码分析
[Shell] 纯文本查看 复制代码 对于如何启动服务的看门狗,首先需要知道一个服务是如何启动的。
每一个服务的启动,在systemd中都是作为job进行的处理,那么每一个服务称为unit单元,在systemd运行时,首先会去创建一个manager管理类,通过**manager_new()**,其中会运行函数**manager_setup_signals(m)**;
这里systemd运用了信号来定义即将加载到job中的特殊的unit单元,sd_event_add_io()在manager的event事件中,给EPOLLIN添加了回调函数。
当event事件检测到,信号发生变化就会去调用manager_dispatch_signal_fd回调函数,对信号进行dispath处理,接下来会对特殊的target单元(default.target)进行loading处理,其中会将default.target所依赖的所有单元以及单元依赖的单元添加到uints中成一个cgroup。
这里需要说明一下,当我们自定义一个service的时候,想要开机自启动,则需要在安装的时候或者手动运行systemctl enable xxx.service,这里根据你service中指定的wants,软链到指定的特殊target的wants目录下,然后在上面说到的流程中,会将依赖单元下的隐式目录下的unit加入到cgroup中,且按照每个unit所描述的依赖顺序。
如下图:
image3
event事件处理job任务,启动每个unit服务,在事件循环event中还通过sd_event_add_io(),还新增了notify的服务管理器状态的检测,并设置回调函数manager_dispatch_notify_fd(),检查notify的消息。
​ 在每个服务启动的时候会调用service_start()其中会设置软狗的超时时间;
[Java] 纯文本查看 复制代码 static int service_start(Unit *u) {
Service *s = SERVICE(u);
assert(s);
...
s->watchdog_original_usec = s->watchdog_usec;
s->watchdog_override_enable = false;
s->watchdog_override_usec = USEC_INFINITY;
...
}
当每个服务在事件循环中收到notify消息时候,会触发回调函数manager_dispatch_notify_fd()此时如果服务开启了软件看门狗,则会调用manager_invoke_notify_message(),回调每个unit预设的notify_message函数,即service_notify_message(),其中会检查消息是否为WATCHDOG=1,如果是则服务会重置看门狗。等待下一个看门狗时间周期内的notify信息;
[Java] 纯文本查看 复制代码 static void service_notify_message(
Unit *u,
const struct ucred *ucred,
char **tags,
FDSet *fds) {
Service *s = SERVICE(u);
bool notify_dbus = false;
const char *e;
char **i;
int r;
assert(u);
assert(ucred);
...
/* Interpret WATCHDOG= */
if (strv_find(tags, "WATCHDOG=1"))
service_reset_watchdog(s);
e = strv_find_startswith(tags, "WATCHDOG_USEC=");
if (e) {
usec_t watchdog_override_usec;
if (safe_atou64(e, &watchdog_override_usec) < 0)
log_unit_warning(u, "Failed to parse WATCHDOG_USEC=%s", e);
else
service_override_watchdog_timeout(s,
watchdog_override_usec);
}
...
}
那么这个notify信息从哪里发送出来的呢,在主线程的事件循环中看出来manager_loop(),对每个事件都去检查是否开启了看门狗:
[Java] 纯文本查看 复制代码 int manager_loop(Manager *m) {
int r;
...
/* Sleep for half the watchdog time */
if (m->runtime_watchdog > 0 && m->runtime_watchdog != USEC_INFINITY &&
MANAGER_IS_SYSTEM(m)) {
wait_usec = m->runtime_watchdog / 2;
if (wait_usec <= 0)
wait_usec = 1;
} else
wait_usec = USEC_INFINITY;
r = sd_event_run(m->event, wait_usec);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
}
​当服务开启了软件看门狗,则wait_usec=1,且会在用户设置的软狗时间一半的时间进行sd_event_run()任务,最后会调用process_watchdog(),调用sd_notify(false,"WATCHDOG=1"),发送notify消息。
​当如果进程被挂起,或者卡死的时候,不会对守护程序进行通知,当超过了看门狗的时间,则会根据.service中设置的Restart=行为进行相应的处理,重启服务,或者多次之后强制重启系统。
小结
systemd代码过于庞大,很多机制需要仔细的钻研才能搞明白,动手实操才能明白。
|
|
发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。 |
|
|
|
|