Linux内核之看门狗子系统
概述
linux看门狗子系统由其核心层和设备驱动构成,看门狗核心层向下为看门狗设备提供了注册和注销等接口,向上注册了一个通用的看门狗设备驱动(字符设备或杂项设备),该设备作为看门狗的抽象,屏蔽了不同看门狗设备的硬件操作之差异,向linux应用层提供了统一的读写和设置等通用接口。
看门狗子系统核心
看门狗子系统入口
看门狗核心层初始化代码在drivers/watchdog/watchdog_core.c中,其初始化函数为watchdog_init,该函数由subsys_initcall_sync宏设置为子系统初始化函数,在linux内核子系统初始化阶段调用。在内核中该代码片段如下:
1 | subsys_initcall_sync(watchdog_init); |
在watchdog_init函数中通过调用watchdog_dev_init完成抽象看门狗设备的初始化,紧接着通过watchdog_deferred_registration()函数将推迟注册的看门狗设备注册到看门狗子系统核心。在这里推迟注册的设备指的是在看门狗子系统初始化之前的注册请求,这些设备在注册的时候看门狗核心还未初始化,因此将这些注册请求放入一个链表,在子系统初始化的时候遍历该链表并注册之。以下是内核中看门狗初始化的函数的代码:
1 | static int __init watchdog_init(void) |
抽象设备的初始化
抽象设备是看门狗子系统最核心的一部分,其初始化相当简洁,主要分为三个步骤,第一是申请工作队列,该工作队列主要用于看门狗ping操作。第二步注册看门狗class,第三步调用alloc_chrdev_region为看门狗设备分配主设备号。
至此,看门狗子系统的初始化工作完成,其余工作为看门狗设备驱动的注册和应用层对看门狗的操作。
看门狗设备驱动注册接口
注册接口
看门狗子系统向设备驱动提供了以下两种注册接口:
1 | EXPORT_SYMBOL_GPL(devm_watchdog_register_device); |
1 | EXPORT_SYMBOL_GPL(watchdog_register_device); |
devm_watchdog_register_device接口比watchdog_register_device接口多了资源管理功能,在驱动卸载时会自动调用注销接口watchdog_unregister_device,而不需要驱动程序显示调用。在这里重点关注watchdog_register_device函数。
在watchdog_register_device函数中首先判断看门狗子系统是否初始化,已经初始化则继续注册,否则将要注册的设备驱动加入延迟注册链表,等待看门狗子系统初始化完成后注册之。该函数在内核中的代码如下:
1 | int watchdog_register_device(struct watchdog_device *wdd) |
数据结构
看门狗子系统用struct watchdog_device类型的结构表示一个看门狗设备。该结构在内核中的定义如下:
1 | struct watchdog_device { |
几个重要的成员说明如下:
id:设备编号,主要用作字符设备从设备号,不需要设置,向看门狗子系统在注册设备时自动设置。
info:看门狗信息结构,描述看门狗版本标识和选项,选项主要用来表示该设备驱动支持的操作。
ops:看门狗操作函数,看门狗子系统支持的函数列表在内核中的定义如下:
1 | struct watchdog_ops { |
timeout:看门狗复位时间
min_timeout:看门狗支持的最小复位时间
max_timeout:看门狗支持的最大复位时间
reboot_nb:看门狗复位通知块,系统重启时回调其处理函数
restart_nb:看门狗重启通知块,收到系统重启看门狗指令时执行其处理函数
status:看门狗状态
注册过程
看门狗在注册的时候传入其watchdog_device结构体首地址,在看门狗子系统核心层提供的注册接口中首先会对其进行合法检查,必须实现start函数和stop函数(当max_hw_heartbeat_ms不为零时),以及最小复位时间不能大于当前喂狗时间等。紧接着为其分配从设备id,然后通过执行watchdog_dev_register函数注册该设备,在watchdog_dev_register函数中为设备申请了struct watchdog_core_data类型的结构,该结构用于描述抽象看门狗设备。其wdd成员被赋值为要注册的看门狗设备结构。然后向系统注册了字符设备,该字符设备绑定了统一的抽象设备文件操作接口watchdog_fops,该结构在内核中的初始化代码如下:
1 | static const struct file_operations watchdog_fops = { |
至此设备驱动注册主要功能已经完成,剩余工作还有创建组,注册预超时,注册通知连等。
抽象设备接口
watchdog_open:
watchdog_open函数在用户空间调用open系统调用的时候执行。该函数首先用container_of宏通过inode->i_cdev中取出注册时设置的抽象设备结构wd_data,然后判断看门狗是否已经被打开,如果已经打开则向用户空间返回-EBUSY。否作从wd_data中取出看门狗设备结构wdd,该结构在看门狗设备注册的时候赋值给wd_data的wdd成员。
接下来的操作就是通过wdd的ops成员实际调用设备驱动的操作函数对设备进行操作。在open函数中调用的看门狗设备的操作函数为start函数或ping函数(如果已经在运行)。在完成看门狗设备的start调用后将抽象设备结构赋值给file指针的private_data成员,以供write和ioctl接口使用。
watchdog_write:
watchdog_write函数在用户空间调用write系统调用的时候执行。该函数首先从file结构中获取到抽象设备数据结构wd_data(在open的时候赋值给file结构的private_data成员)。然后在wd_data结构中取出看门狗设备驱动数据结构wdd,然后执行wdd中的操作函数结构中的ping函数或start函数(如果没有ping函数实现)。
watchdog_ioctl:
watchdog_ioctl函数在用户空间调用ioctl系统调用的时候执行,用来设置和获取看门狗参数或直接操作看门狗。其中哪些选项支持由具体的看门狗设备驱动中info成员的options成员决定
看门狗设备驱动
有了看门狗核心层提供的抽象和机制,看门狗设备驱动也比较统一和简洁,下面以ti看门狗驱动omap-wdt为例,结合看门狗子系统框架说明一个看门狗设备驱动的实现过程。
1.申请或定义struct watchdog_device结构,该结构描述在看门狗核心层用来描述一个看门狗设备。
在omap-wdt中该结构定义在omap_wdt_dev结构中:
1 | struct omap_wdt_dev { |
2.设置struct watchdog_device结构,主要是对watchdog_device结构做初始化。
在omap-wdt中一些设置代码如下:
1 | wdev->wdog.info = &omap_wdt_info; |
3.实现硬件相关的操作,主要实现watchdog_device结构中的ops成员。
在omap-wdt中该成员初始化如下:
1 | static const struct watchdog_ops omap_wdt_ops = { |
上面的操作函数在用户空间操作看门狗设备时由看门狗核心层根据用户操作回调。这些接口主要涉及的是看门狗的寄存器操作。
4.注册看门狗设备,向看门狗核心层注册watchdog_device
在omap-wdt中注册代码片段如下:
1 | ret = watchdog_register_device(&wdev->wdog); |