qq自动发货平台网站怎么做,萧江做网站,自建购物网站,装修在线设计平台目录 一、watchdog 子系统二、关键数据结构2.1 watchdog_device2.2 watchdog_ops2.3 watchdog_info 三、重要流程3.1 watchdog 初始化3.2 watchdog 设备注册3.3 watchdog 设备文件操作函数3.4 watchdog 喂狗用户空间 watchdog#xff08;busybox#xff09;内核空间喂狗疑问 … 目录 一、watchdog 子系统二、关键数据结构2.1 watchdog_device2.2 watchdog_ops2.3 watchdog_info 三、重要流程3.1 watchdog 初始化3.2 watchdog 设备注册3.3 watchdog 设备文件操作函数3.4 watchdog 喂狗用户空间 watchdogbusybox内核空间喂狗疑问 or bug? 3.5 watchdog pretimeout 和 governor 四、softdog4.1 softdog_init4.2 softdog_ping4.3 softdog_fire 一、watchdog 子系统
linux 中 watchdog 子系统是用于防止系统发生长时间故障、将系统从死循环或者死锁等异常状态中退出并重启的一种机制。
linux 内核支持基于 hrtimer 的 softdog 和基于硬件的硬件看门狗创建 /dev/watchdog* 设备文件与用户空间程序进行交互。
用户空间的 watchdog 程序会通过/dev/watchdog* 设备进行周期性喂狗。
二、关键数据结构
2.1 watchdog_device
watchdog_device 表示一个 watchdog 设备保存了 watchdog 的各种操作参数和操作 ops。
struct watchdog_device {int id;struct device *parent;const struct attribute_group **groups; // 创建watchdog device的sysfs属性列表const struct watchdog_info *info; // 记录watchdog的特性例如WDIOF_SETTIMEOUT WDIOF_KEEPALIVEPINGconst struct watchdog_ops *ops; // watchdog 操作接口const struct watchdog_governor *gov; // pretimeout使用的governorunsigned int bootstatus; // boot 时 watchdog 的状态unsigned int timeout;unsigned int pretimeout;unsigned int min_timeout;unsigned int max_timeout;unsigned int min_hw_heartbeat_ms;unsigned int max_hw_heartbeat_ms;struct notifier_block reboot_nb;struct notifier_block restart_nb;void *driver_data;struct watchdog_core_data *wd_data;unsigned long status; // watchdog 状态active\running……
/* Bit numbers for status flags */
#define WDOG_ACTIVE 0 /* Is the watchdog running/active */
#define WDOG_NO_WAY_OUT 1 /* Is nowayout feature set ? */
#define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */
#define WDOG_HW_RUNNING 3 /* True if HW watchdog running */
#define WDOG_STOP_ON_UNREGISTER 4 /* Should be stopped on unregister */struct list_head deferred;
};2.2 watchdog_ops
watchdog_ops 定义了 watchdog device 操作函数。
struct watchdog_ops {struct module *owner;/* mandatory operations */int (*start)(struct watchdog_device *);int (*stop)(struct watchdog_device *);/* optional operations */int (*ping)(struct watchdog_device *);unsigned int (*status)(struct watchdog_device *);int (*set_timeout)(struct watchdog_device *, unsigned int);int (*set_pretimeout)(struct watchdog_device *, unsigned int);unsigned int (*get_timeleft)(struct watchdog_device *);int (*restart)(struct watchdog_device *, unsigned long, void *);long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
};2.3 watchdog_info
struct watchdog_info {__u32 options; /* Options the card/driver supports */__u32 firmware_version; /* Firmware version of the card */__u8 identity[32]; /* Identity of the board */
};三、重要流程
3.1 watchdog 初始化
watchdog_init 接口对 watchdog 子系统做初始化
watchdog_init-watchdog_dev_init-kthread_create_worker(0, watchdogd)-sched_setscheduler // 设置watchdog_kworker的线程调度策略为SCHED_FIFO, 优先级MAX_RT_PRIO - 1-class_register-alloc_chrdev_region-watchdog_deferred_registration // 如果watchdog设备驱动早于watchdog_init则将设备加入到wtd_deferred_reg_list此处集中进行列表上watchdog设备注册。3.2 watchdog 设备注册
devm_watchdog_register_device 接口用于注册 watchdog 设备:
int devm_watchdog_register_device(struct device *dev, struct watchdog_device *wdd)- watchdog_register_device(wdd);- __watchdog_register_device- // 参数检查- watchdog_check_min_max_timeout- watchdog_dev_register- watchdog_cdev_register- kthread_init_work // 创建pingwork任务- hrtimer_init // 创建一个高精度定时器定时器结束后调用的接口是 watchdog_timer_expired- misc_register // if (wdd-id 0),如果是注册第一个watchdog则注册misc设备- device_initialize- cdev_init- cdev_device_add // 创建并初始化watchdog设备设备名为/dev/watchdog*- watchdog_register_pretimeout- watchdog_reboot_notifier // if (test_bit(WDOG_STOP_ON_REBOOT, wdd-status))- register_restart_handler // if (wdd-ops-restart)3.3 watchdog 设备文件操作函数
watchdog创建的misc 设备和 cdev 的文件操作函数都是 watchdog_fops
static const struct file_operations watchdog_fops {.owner THIS_MODULE,.write watchdog_write,.unlocked_ioctl watchdog_ioctl,.open watchdog_open,.release watchdog_release,
};static struct miscdevice watchdog_miscdev {.minor WATCHDOG_MINOR,.name watchdog,.fops watchdog_fops,
};watchdog_open 接口中调用 watchdog_start 接口start watchdog 和更新内核喂狗定时器。
static int watchdog_open(struct inode *inode, struct file *file)- watchdog_start(wdd);- wdd-ops-start(wdd)- watchdog_update_worker // 更新喂狗定时器- stream_open(inode, file);watchdog_write 接口实现喂狗功能写任意值都能喂狗 “V” 是 magic 字符写 “V” 之后使能 watchdog 的魔法关闭功能:
static ssize_t watchdog_write(struct file *file, const char __user *data,size_t len, loff_t *ppos)-set_bit(_WDOG_ALLOW_RELEASE, wd_data-status) // if (c V)- watchdog_ping(wdd)watchdog_ioctl 支持一系列 watchdog 设置和信息获取操作。
static long watchdog_ioctl(struct file *file, unsigned int cmd,unsigned long arg)case WDIOC_GETSUPPORT: // 返回watchdog_info结构体case WDIOC_GETSTATUS: // 返回watchdog状态case WDIOC_GETBOOTSTATUS:case WDIOC_SETOPTIONS: // start 和stop操作case WDIOC_KEEPALIVE: // 喂狗case WDIOC_SETTIMEOUT: // 设置超时时间case WDIOC_GETTIMEOUT: // 获取超时时间case WDIOC_GETTIMELEFT: // 获取超时剩余时间case WDIOC_SETPRETIMEOUT: // 设置pretimeout case WDIOC_GETPRETIMEOUT: // 获取pretimeout 3.4 watchdog 喂狗
用户空间 watchdogbusybox
watchdog 是 busybox 中的一个工具通过watchdog -T 60 -t /dev/watchdog0每 30秒喂一次狗60秒没有喂狗则重启。
watchdog 是 busybox 中的一个工具通过watchdog -T 60 -t /dev/watchdog0每 30秒喂一次狗60秒没有喂狗则重启。
Usage: watchdog [-t N[ms]] [-T N[ms]] [-F] DEVPeriodically write to watchdog device DEV-T N Reboot after N seconds if not reset (default 60)-t N Reset every N seconds (default 30)-F Run in foregroundUse 500ms to specify period in millisecondswatchdog 内部大概实现如下
watchdog_main-shutdown_on_signal // watchdog收到异常信号退出时调用关闭watchdog。-shutdown_watchdog // 关闭watchdog。-watchdog_open // 打开 /dev/watchdog* 设备-WDIOC_SETOPTIONS // 启动watchdog。-WDIOC_SETTIMEOUT // 设置timeout。-while(1) // 循环写空内容然后睡眠。内核空间喂狗
在内核空间也有一个定时器定时喂狗流程如下
1、在 watchdog_dev_init 接口中注册了一个优先级为 MAX_RT_PRIO - 1、调度策略为 SCHED_FIFO、名为 watchdogd 的内核线程
struct sched_param param {.sched_priority MAX_RT_PRIO - 1,};
watchdog_kworker kthread_create_worker(0, watchdogd);
sched_setscheduler(watchdog_kworker-task, SCHED_FIFO, param);2、在注册 watchdog 设备时初始化了一个 kthread_work 结构体这个结构体表示一个内核线程工作项用于调度和执行异步任务。kthread_init_work 函数将工作项与一个 watchdog_ping_work 关联起来这个处理函数将在工作项被执行时调用
kthread_init_work(wd_data-work, watchdog_ping_work);3、 在注册 watchdog 设备时还会创建一个定时器定时器超时接口是 watchdog_timer_expired定时器在 watchdog_start 时会被开启
hrtimer_init(wd_data-timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD);
wd_data-timer.function watchdog_timer_expired;
hrtimer_start(wd_data-timer, t, HRTIMER_MODE_REL_HARD);4、在定时器超时接口 watchdog_timer_expired 中使用 kthread_queue_work 接口将 kthread_work 工作项 wd_data-work 添加到了 watchdog 工作线程 watchdog_kworker 的工作队列中使得工作线程可以异步执行该工作项。
static enum hrtimer_restart watchdog_timer_expired(struct hrtimer *timer)
{struct watchdog_core_data *wd_data;wd_data container_of(timer, struct watchdog_core_data, timer);kthread_queue_work(watchdog_kworker, wd_data-work);return HRTIMER_NORESTART;
}5、 wd_data-work 调用接口为 watchdog_ping_work 改接口中执行看门狗的 ping 或者 start 动作实现看门狗喂狗然后在watchdog_update_worker 接口中计算合适的下次喂狗时间并重启定时器。
static void watchdog_ping_work(struct kthread_work *work)- __watchdog_ping // if (watchdog_worker_should_ping(wd_data))- wdd-ops-ping(wdd); // if (wdd-ops-ping)- wdd-ops-start(wdd); // else- watchdog_update_worker(wdd);- hrtimer_start总结一下watchdog 驱动中通过创建一个定时器并在定时器的超时函数中将看门狗喂狗函数添加到看门狗线程的工作队列中实现当定时器超时时调用喂狗函数。并在喂狗函数中计算合适的下次喂狗时间并重启定时器由此实现了反复喂狗。
疑问 or bug?
上面提到的内核定时器喂狗需要配合用户空间喂狗时才能生效单独内核空间定时器喂狗一段时间后定时器将不再刷新只有在用户空间喂狗的前提下内核空间喂狗的定时器才会刷新那么内核空间定时器喂狗保留的意义是什么这地方是个 bug 还是设计的就是如此有大佬清楚的话可以一起讨论下。
代码分析如下
上面提到内核喂狗定时器任务 watchdog_ping_work 接口中执行看门狗的 ping 或者 start 动作实现看门狗喂狗然后在watchdog_update_worker 接口中计算合适的下次喂狗时间并重启定时器。
static inline void watchdog_update_worker(struct watchdog_device *wdd)- ktime_t t watchdog_next_keepalive(wdd); // 计算下次喂狗时间t- hrtimer_start(wd_data-timer, t, HRTIMER_MODE_REL_HARD) // if (t 0)更新定时器下次喂狗时间通过 watchdog_next_keepalive 接口计算
static ktime_t watchdog_next_keepalive(struct watchdog_device *wdd)
{struct watchdog_core_data *wd_data wdd-wd_data;unsigned int timeout_ms wdd-timeout * 1000;ktime_t keepalive_interval;ktime_t last_heartbeat, latest_heartbeat;ktime_t virt_timeout;unsigned int hw_heartbeat_ms;if (watchdog_active(wdd))virt_timeout ktime_add(wd_data-last_keepalive,ms_to_ktime(timeout_ms));elsevirt_timeout wd_data-open_deadline;hw_heartbeat_ms min_not_zero(timeout_ms, wdd-max_hw_heartbeat_ms);keepalive_interval ms_to_ktime(hw_heartbeat_ms / 2);/** To ensure that the watchdog times out wdd-timeout seconds* after the most recent ping from userspace, the last* worker ping has to come in hw_heartbeat_ms before this timeout.*/last_heartbeat ktime_sub(virt_timeout, ms_to_ktime(hw_heartbeat_ms));latest_heartbeat ktime_sub(last_heartbeat, ktime_get());if (ktime_before(latest_heartbeat, keepalive_interval))return latest_heartbeat;return keepalive_interval;
}分析该接口主要有这么几个变量
virt_timeout ktime_add(wd_data-last_keepalive, ms_to_ktime(timeout_ms));
// 下次超时时间是 last_keepalive 时间 watchdog timeout 时间last_heartbeat ktime_sub(virt_timeout, ms_to_ktime(hw_heartbeat_ms));
// 理论最近一次的喂狗时间要根据 max_hw_heartbeat_ms 时间和 timeout 时间稍微提前一点返回值1latest_heartbeat ktime_sub(last_heartbeat, ktime_get());
// 理论时间减去当前时间是还差多久需要喂狗的时间返回值2keepalive_interval ms_to_ktime(hw_heartbeat_ms / 2);
// keepalive_interval是根据看门狗设置的 max_hw_heartbeat_ms 计算的还差多久需要喂狗的时间该接口返回值当前时间到下次喂狗时间的差值有两种可能的返回值
返回值 keepalive_interval 是根据watchdog驱动设置的 wdd-timeout 和 wdd-max_hw_heartbeat_ms 计算出的timeout 和 max_hw_heartbeat_ms 设置确定后该值就不变了返回值 latest_heartbeat 是先根据上次更新 wd_data-last_keepalive 值的时间计算出一个理论下次喂狗时间 last_heartbeat该理论时间减去当前时间 ktime_sub(last_heartbeat, ktime_get()) 得到 latest_heartbeat返回值是判断 latest_heartbeat 和 keepalive_interval 的较小值返回较小值
问题 但是wd_data-last_keepalive 只在 wdg_start 和用户空间喂狗函数 watchdog_ping 接口中会更新在内核定时器喂狗任务接口中不会更新 wd_data-last_keepalive 值
static int watchdog_ping(struct watchdog_device *wdd)
{struct watchdog_core_data *wd_data wdd-wd_data;if (!watchdog_active(wdd) !watchdog_hw_running(wdd))return 0;set_bit(_WDOG_KEEPALIVE, wd_data-status);wd_data-last_keepalive ktime_get(); // 更新 wd_data-last_keepalivereturn __watchdog_ping(wdd);
}// 内核定时器的喂狗接口接口内不会更新 wd_data-last_keepalive
static void watchdog_ping_work(struct kthread_work *work)
{struct watchdog_core_data *wd_data;wd_data container_of(work, struct watchdog_core_data, work);mutex_lock(wd_data-lock);if (watchdog_worker_should_ping(wd_data))__watchdog_ping(wd_data-wdd);mutex_unlock(wd_data-lock);
}当 last_keepalive 不再更新通过 latest_heartbeat ktime_sub(last_heartbeat, ktime_get()); 计算出的值将是个负值linux 中 ktime_t 类型是一个有符号数watchdog_next_keepalive 接口返回一个负值hrtimer_start(wd_data-timer, t, HRTIMER_MODE_REL_HARD); 设置的定时器超时时间入参 t 是一个负值
对于 hrtimer_start 设置一个负值的超时时间是怎么处理的没有再细追但是实际测试下来的确没有再继续喂狗
所以如果没有用户空间的喂狗情况下只靠内核空间是无法实现定时喂狗的。
3.5 watchdog pretimeout 和 governor
pretimeout 区别于 timeout某些 watchdog 触发 timeout 后立即重启了来不及做一些现场保存等动作。pretimeout 是指在 timeout 之前的设置的一个超时时间此时软件还处于可控状态可以做一些预警和提前保存的动作。触发 pretimeout 调用指定的 governor 可以执行不同动作。支持 pretimeout 的 watchdog options 包含 WDIOF_PRETIMEOUT。
int watchdog_register_governor(struct watchdog_governor *gov);--governor的注册和注销接口。
void watchdog_unregister_governor(struct watchdog_governor *gov);int watchdog_register_pretimeout(struct watchdog_device *wdd);--pretimeout注册和注销接口。
void watchdog_unregister_pretimeout(struct watchdog_device *wdd);
int watchdog_pretimeout_available_governors_get(char *buf);
int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf);--设置/获取pretimeout对应的governor。
int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,const char *buf);注册governor
int watchdog_register_governor(struct watchdog_governor *gov)- list_add(priv-entry, governor_list) // 将gov添加到governor_list// 如果gov是default gov将所有wdd的gov设置为当前govif (!strncmp(gov-name, WATCHDOG_PRETIMEOUT_DEFAULT_GOV,WATCHDOG_GOV_NAME_MAXLEN)) {default_gov gov;list_for_each_entry(p, pretimeout_list, entry)if (!p-wdd-gov)p-wdd-gov default_gov;}设置 watchdog 设备的 governor
int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,const char *buf)- find_governor_by_name(buf);- wdd-gov priv-gov;调用 governor
void watchdog_notify_pretimeout(struct watchdog_device *wdd)- wdd-gov-pretimeout(wdd);四、softdog
softdog 驱动通过软件模拟 watchdog 实现看门狗功能。
4.1 softdog_init
softdog_init 流程与正常硬件驱动的 watchdog 类似只是实现了一个 softwatchdog 的定时器该定时器在 ping 接口会被反复重启当定时器超时也就说明软件没有及时喂狗触发定时器超时函数在超时函数里执行重启或者 panic 操作。
static int __init softdog_init(void)// 正常看门狗设备初始化watchdog_init_timeout(softdog_dev, soft_margin, NULL);watchdog_set_nowayout(softdog_dev, nowayout);watchdog_stop_on_reboot(softdog_dev); // softdog使用的定时器hrtimer_init(softdog_ticktock, CLOCK_MONOTONIC, HRTIMER_MODE_REL);softdog_ticktock.function softdog_fire;// 设置pretimeout的定时器超时接口softdog_pretimeout中调用watchdog_notify_pretimeoutif (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT)) {softdog_info.options | WDIOF_PRETIMEOUT;hrtimer_init(softdog_preticktock, CLOCK_MONOTONIC,HRTIMER_MODE_REL);softdog_preticktock.function softdog_pretimeout;}// 看门狗设备注册ret watchdog_register_device(softdog_dev);if (ret)return ret;
}
module_init(softdog_init);4.2 softdog_ping
softdog_ping 实现 softdog 喂狗喂狗函数里是重新启动 softdog timeout 和 pretimeout 的定时器。
static int softdog_ping(struct watchdog_device *w)
{if (!hrtimer_active(softdog_ticktock))__module_get(THIS_MODULE);hrtimer_start(softdog_ticktock, ktime_set(w-timeout, 0),HRTIMER_MODE_REL);if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT)) {if (w-pretimeout)hrtimer_start(softdog_preticktock,ktime_set(w-timeout - w-pretimeout, 0),HRTIMER_MODE_REL);elsehrtimer_cancel(softdog_preticktock);}return 0;
}4.3 softdog_fire
softdog_fire 是 softdag timeout 定时器超时函数函数内部执行重启或者 panic 动作。
static enum hrtimer_restart softdog_fire(struct hrtimer *timer)
{module_put(THIS_MODULE);if (soft_noboot) {pr_crit(Triggered - Reboot ignored\n);} else if (soft_panic) {pr_crit(Initiating panic\n);panic(Software Watchdog Timer expired);} else {pr_crit(Initiating system reboot\n);emergency_restart();pr_crit(Reboot didnt ?????\n);}return HRTIMER_NORESTART;
}参考 Linux watchdog子系统概述