嵌入式Linux的看门狗与电源管理
1. 看门狗的添加
在嵌入式设备中,看门狗是常见需求。若关键服务停止工作,通常需采取行动,一般是重置系统。多数嵌入式片上系统(SoC)有硬件看门狗,可通过
/dev/watchdog
设备节点访问。看门狗在启动时会设置超时时间,之后必须在该时间段内重置,否则会触发看门狗,导致系统重启。与看门狗驱动的接口在内核源码的
Documentation/watchdog
中描述,驱动代码位于
drivers/watchdog
。
若有两个或更多关键服务需由看门狗保护,就会出现问题。
systemd
有个实用功能,可在多个服务间分配看门狗。
systemd
可配置为期望服务定期发送保活调用,若未收到则采取行动,从而为每个服务创建软件看门狗。要实现这一点,需在守护进程中添加代码以发送保活消息。代码需检查
WATCHDOG_USEC
环境变量是否为非零值,然后在该时间内(建议为看门狗超时时间的一半)调用
sd_notify(false, "WATCHDOG=1")
。
systemd
源码中有相关示例。
要在服务单元中启用看门狗,可在
[Service]
部分添加如下内容:
WatchdogSec=30s
Restart=on-watchdog
StartLimitInterval=5min
StartLimitBurst=4
StartLimitAction=reboot-force
在这个示例中,服务期望每30秒收到一次保活消息。若未收到,服务将重启;若在五分钟内重启次数超过四次,
systemd
将强制立即重启。
systemd.service(5)
手册页中有这些设置的完整描述。
这种看门狗可照顾单个服务,但如果
systemd
本身故障、内核崩溃或硬件锁定,该怎么办?在这些情况下,需告知
systemd
使用看门狗驱动:只需在
/etc/systemd/system.conf
中添加
RuntimeWatchdogSec=NN
。
systemd
会在该时间段内重置看门狗,因此若
systemd
因某种原因故障,系统将重置。
2. 对嵌入式Linux的影响
systemd
在嵌入式Linux中有很多实用功能,包括本文未提及的,如使用切片进行资源控制(在
systemd.slice(5)
和
systemd.resource-control(5)
手册页中有描述)、设备管理(
udev(7)
)和系统日志功能(
journald(5)
)。
不过,要考虑其大小:即使仅构建核心组件
systemd
、
udevd
和
journald
的最小版本,包括共享库,也接近10 MiB的存储空间。
还需记住,
systemd
的开发与内核紧密相关,因此它无法在比
systemd
发布时间早一两年以上的内核上运行。
3. 不同初始化程序的选择
每个Linux设备都需要某种初始化程序。若设计的系统在启动时只需启动少量守护进程,且之后状态相对稳定,那么
BusyBox init
就能满足需求。若使用Buildroot作为构建系统,它通常是不错的选择。
另一方面,若系统在启动或运行时服务间存在复杂依赖关系,且有足够存储空间,那么
systemd
是最佳选择。即便没有这些复杂性,
systemd
在处理看门狗、远程日志等方面也有实用功能,值得认真考虑。
同时,System V init依然存在。它易于理解,且针对每个重要组件都已有初始化脚本。它仍是Yocto项目的Poky发行版的默认初始化程序。
在减少启动时间方面,对于类似工作负载,
systemd
比System V init更快。然而,若追求极快启动,没有什么能比得上带有最少启动脚本的简单
BusyBox init
。
4. 电源管理的重要性
对于使用电池供电的设备,电源管理至关重要。任何能降低功耗的措施都能延长电池寿命。即使是使用市电供电的设备,降低功耗也有助于减少散热需求和能源成本。本文将介绍电源管理的四个原则:
- 不必匆忙时就不要着急
- 不要羞于闲置
- 关闭不用的设备
- 无事可做时就休眠
用更专业的术语来说,这些原则意味着电源管理系统应努力降低CPU时钟频率;在闲置期间,应选择最深的睡眠状态;通过关闭未使用的外设来减轻负载;并且应能将整个系统置于挂起状态。
Linux具备解决这些问题的功能。下面将逐一描述,并给出示例和建议,说明如何将它们应用于嵌入式系统,以实现电源的最佳利用。
5. 测量功耗
本文示例需使用真实硬件而非虚拟硬件,这意味着需要一个具备正常电源管理功能的BeagleBone Black。遗憾的是,
meta-yocto-bsp
层附带的BeagleBone板级支持包(BSP)不包含电源管理集成电路(PMIC)所需的固件,因此需构建带有定制内核的镜像。具体步骤如下:
1. 获取Yocto Project Morty版本,并从代码存档中添加
meta-bbb-pm
层:
$ git clone -b morty git://git.yoctoproject.org/poky.git
$ cd poky
$ cp -a MELP/chapter_11/poky/meta-bbb-pm
$ source oe-init-build-env build-bbb
$ bitbake-layers add-layer ../meta-bbb-pm
-
编辑
conf/local.conf,并在文件开头添加以下行:
MACHINE = "beaglebone"
PREFERRED_PROVIDER_virtual/kernel = "ti-linux-kernel"
IMAGE_INSTALL_append = " powertop dropbear rt-tests amx3-cm3 kernel-vmlinux"
-
构建镜像,例如
core-image-minimal:
$ bitbake core-image-minimal
- 格式化微型SD卡,并使用MELP存档中的脚本将镜像复制到卡上:
$ MELP/format-sdcard.sh [your sdcard reader]
$ MELP/copy-yoctoproject-image-to-sdcard.sh \
beaglebone core-image-minimal
- 启动BeagleBone Black并检查电源管理是否正常工作:
# cat /sys/power/state
freeze standby mem disk
若看到所有四种状态,则一切正常;若只看到
freeze
,则电源管理子系统未正常工作,需返回并仔细检查前面的步骤。
测量功耗有两种方法:外部测量和内部测量。从系统外部进行外部测量时,只需用电流表测量电流,用电压表测量电压,然后将两者相乘得到功率。可以使用能给出读数的基本仪表,然后记录下来;也可以使用更复杂的、具备数据记录功能的仪表,以便能看到负载变化时功率的毫秒级变化。本文中,使用迷你USB端口为BeagleBone供电,并使用了一个售价几美元的廉价USB电源监视器。
另一种方法是使用Linux内置的监控系统。通过
sysfs
可获取大量信息。还有一个非常实用的程序
PowerTOP
,它能从多个来源收集信息并集中展示。
PowerTOP
是Yocto Project和Buildroot的软件包。在前面提供的Yocto Project配置中,已将
powertop
作为附加软件包包含在内。以下是
PowerTOP
在BeagleBone Black上运行的示例,从截图中可看到系统处于安静状态,CPU使用率仅为0.4%。
6. 时钟频率的缩放
跑一千米比走一千米消耗更多能量。类似地,以较低频率运行CPU或许能节省能量。CPU执行代码时的功耗是静态组件和动态组件之和,静态组件由门泄漏电流等因素引起,动态组件由门的开关引起:
[P_{cpu} = P_{static} + P_{dyn}]
动态功率组件取决于被切换的逻辑门的总电容、时钟频率和电压的平方:
[P_{dyn} = CfV^2]
由此可见,仅改变频率本身不会节省任何功率,因为执行给定子程序所需的CPU周期数是固定的。若将频率降低一半,完成计算所需的时间将加倍,但动态功率组件消耗的总功率不变。实际上,降低频率可能会增加功率预算,因为CPU进入空闲状态所需的时间更长。因此,在这种情况下,最好使用尽可能高的频率,以便CPU能快速返回空闲状态,这称为“争分夺秒进入空闲状态”。
降低频率还有另一个动机:为了将封装温度控制在范围内,可能需要以较低频率运行。但这不是本文的重点。
因此,若要节省功率,必须能够改变CPU核心的工作电压。但对于任何给定电压,都有一个最大频率,超过该频率,门的开关将变得不可靠。更高的频率需要更高的电压,因此两者需要一起调整。许多SoC实现了这样的功能,称为动态电压和频率缩放(DVFS)。制造商计算出核心频率和电压的最佳组合,每个组合称为操作性能点(OPP)。高级配置和电源接口(ACPI)规范将它们称为P状态,其中P0是频率最高的OPP。尽管OPP是频率和电压的组合,但通常仅通过频率组件来引用它们。
7. CPUFreq驱动
Linux有一个名为
CPUFreq
的组件,用于管理OPP之间的转换。它是每个SoC软件包板级支持的一部分。
CPUFreq
由
drivers/cpufreq/
中的驱动程序和一组实现切换策略的调节器组成,驱动程序用于实现从一个OPP到另一个OPP的转换。每个CPU通过
/sys/devices/system/cpu/cpuN/cpufreq
目录进行控制,其中N是CPU编号。在该目录中,有许多文件,其中最有趣的如下:
-
cpuinfo_cur_freq
、
cpuinfo_max_freq
和
cpuinfo_min_freq
:该CPU的当前频率以及最大和最小频率,以KHz为单位。
-
cpuinfo_transition_latency
:从一个OPP切换到另一个OPP所需的时间,以纳秒为单位。若值未知,则设置为 -1。
-
scaling_available_frequencies
:该CPU可用的OPP频率列表。
-
scaling_available_governors
:该CPU可用的调节器列表,具体如下:
-
powersave
:始终选择最低频率。
-
performance
:始终选择最高频率。
-
ondemand
:根据CPU利用率改变频率。若CPU空闲时间少于20%,则将频率设置为最高;若空闲时间超过30%,则将频率降低5%。
-
conservative
:与
ondemand
类似,但以5%的步长切换到更高频率,而不是立即切换到最高频率。
-
userspace
:频率由用户空间程序设置。
-
scaling_governor
:当前使用的
CPUFreq
调节器。
-
scaling_max_freq
和
scaling_min_freq
:调节器可用的频率范围,以KHz为单位。
-
scaling_setspeed
:当调节器为
userspace
时,可通过写入该文件手动设置频率。
调节器设置改变OPP的策略,可在
scaling_min_freq
和
scaling_max_freq
的限制范围内设置频率。
ondemand
调节器用于决定何时改变OPP的参数可通过
/sys/devices/system/cpu/cpufreq/ondemand/
查看和修改。
ondemand
和
conservative
调节器都会考虑改变频率和电压所需的努力,该参数在
cpuinfo_transition_latency
中。这些计算适用于具有正常调度策略的线程;若线程按实时调度,则两者都会立即选择最高OPP,以便线程能满足其调度期限。
userspace
调节器允许用户空间守护进程执行选择OPP的逻辑,例如
cpudyn
和
powernowd
,但它们都更适用于基于x86的笔记本电脑,而非嵌入式设备。
8. 使用CPUFreq
以BeagleBone Black为例,其OPP编码在设备树中。以下是
am33xx.dtsi
的摘录:
cpu0_opp_table: opp-table {
compatible = "operating-points-v2-ti-cpu";
syscon = <&scm_conf>;
opp50@300000000 {
opp-hz = /bits/ 64 <300000000>;
opp-microvolt = <950000 931000 969000>;
opp-supported-hw = <0x06 0x0010>;
opp-suspend;
};
opp100@600000000 {
opp-hz = /bits/ 64 <600000000>;
opp-microvolt = <1100000 1078000 1122000>;
opp-supported-hw = <0x06 0x0040>;
};
opp120@720000000 {
opp-hz = /bits/ 64 <720000000>;
opp-microvolt = <1200000 1176000 1224000>;
opp-supported-hw = <0x06 0x0080>;
};
oppturbo@800000000 {
opp-hz = /bits/ 64 <800000000>;
opp-microvolt = <1260000 1234800 1285200>;
opp-supported-hw = <0x06 0x0100>;
};
oppnitro@1000000000 {
opp-hz = /bits/ 64 <1000000000>;
opp-microvolt = <1325000 1298500 1351500>;
opp-supported-hw = <0x04 0x0200>;
};
};
可通过查看可用频率来确认运行时使用的OPP:
# cd /sys/devices/system/cpu/cpu0/cpufreq
# cat scaling_available_frequencies
300000 600000 720000 800000 1000000
通过选择用户空间调节器,可通过写入
scaling_setspeed
来设置频率,从而测量每个OPP的功耗。这些测量并不十分准确,不必过于当真。
首先,在系统空闲时,结果为70mA @ 4.6V = 320 mW,这与频率无关,因为这是该特定系统功耗的静态组件。
现在,通过运行如下计算密集型负载,可了解每个OPP的最大功耗:
# dd if=/dev/urandom of=/dev/null bs=1
结果如下表所示,其中“Delta power”是相对于空闲系统的额外功耗:
| OPP | Freq, KHz | Power, mW | Delta power, mW |
| — | — | — | — |
| OPP50 | 300,000 | 370 | 50 |
| OPP100 | 600,000 | 505 | 185 |
| OPP120 | 720,000 | 600 | 280 |
| Turbo | 800,000 | 640 | 320 |
| Nitro | 1,000,000 | 780 | 460 |
这些测量显示了不同OPP下的最大功耗,但这并非公平测试,因为CPU以100%的利用率运行,因此在更高频率下执行的指令更多。若保持负载恒定但改变频率,可得到以下结果:
| OPP | Freq, KHz | CPU utilization, % | Power, mW |
| — | — | — | — |
| OPP50 | 300,000 | 94 | 320 |
| OPP100 | 600,000 | 48 | 345 |
| OPP120 | 720,000 | 40 | 370 |
| Turbo | 800,000 | 34 | 370 |
| Nitro | 1,000,000 | 28 | 370 |
这表明在最低频率下有明显的节能效果,约为15%。使用
PowerTOP
可查看在每个OPP上花费的时间百分比。通常情况下,
ondemand
调节器是最佳选择。要选择特定的调节器,可通过配置内核设置默认调节器,例如
CPU_FREQ_DEFAULT_GOV_ONDEMAND
,也可使用启动脚本来在启动时更改调节器。在
MELP/chapter_11/sysvinit-ondemand.sh
中有一个来自Ubuntu 14.04的System V(SysV)初始化脚本示例。若需了解更多关于
CPU-freq
驱动的信息,可查看内核源码中
Documentation/cpu-freq
目录。
9. 选择最佳空闲状态
前面关注的是CPU忙碌时的功耗,接下来将探讨CPU空闲时如何节省功率。
当处理器没有更多工作要做时,会执行暂停指令并进入空闲状态。空闲时,CPU功耗降低。当发生硬件中断等事件时,CPU会退出空闲状态。大多数CPU有多个空闲状态,功耗各不相同。通常,功耗和退出状态所需的延迟(即时间长度)之间存在权衡。在ACPI规范中,这些状态称为C状态。
在更深的C状态下,更多电路会关闭,但会丢失一些状态,因此恢复到正常操作所需的时间更长。例如,在某些C状态下,CPU缓存可能会断电,因此当CPU再次运行时,可能需要从主内存重新加载一些信息,这代价高昂,因此只有在CPU可能长时间处于该状态时才值得这么做。不同系统的状态数量不同,每个状态从睡眠恢复到完全活动都需要一定时间。
选择正确空闲状态的关键是要大致了解CPU将静止多长时间。预测未来总是很困难,但有一些因素可以提供帮助。一是当前的CPU负载:若当前负载较高,短期内可能仍会如此,那么深度睡眠就没有好处。即使负载较低,也值得查看是否有即将到期的定时器事件。若没有负载且没有定时器,那么选择更深的空闲状态是合理的。
Linux中选择最佳空闲状态的部分是
CPUIdle
驱动,内核源码的
Documentation/cpuidle
目录中有关于它的大量信息。
10. 外设的断电管理
除了调整CPU的频率和选择合适的空闲状态,关闭未使用的外设也是电源管理的重要一环。许多嵌入式系统配备了各种各样的外设,如USB接口、SD卡插槽、蓝牙模块、Wi-Fi模块等。当这些外设不被使用时,让它们保持通电状态会白白消耗电能。
在Linux系统中,可以通过sysfs接口来控制外设的电源状态。每个外设通常在
/sys/bus
目录下有对应的设备节点,通过向这些节点写入特定的值,可以开启或关闭外设的电源。例如,对于某些USB设备,可以通过以下步骤进行电源管理:
1. 找到USB设备对应的sysfs节点,通常在
/sys/bus/usb/devices
目录下。
2. 进入该设备的目录,查找名为
power/control
的文件。
3. 若要关闭设备电源,向
power/control
文件写入
auto
;若要开启设备电源,写入
on
。
# 假设USB设备的sysfs节点为/sys/bus/usb/devices/1-1
# 关闭设备电源
echo auto > /sys/bus/usb/devices/1-1/power/control
# 开启设备电源
echo on > /sys/bus/usb/devices/1-1/power/control
对于其他类型的外设,如SD卡插槽、蓝牙模块等,也有类似的控制方法,只是对应的sysfs节点和控制文件可能不同。在实际应用中,需要根据具体的硬件和驱动来查找和使用相应的控制接口。
11. 系统休眠策略
当系统没有任何任务需要处理时,将整个系统置于休眠状态是最有效的节能方式。Linux支持多种休眠状态,不同的休眠状态在功耗和恢复时间上有所不同。
在ACPI规范中,常见的休眠状态有
freeze
、
standby
、
mem
和
disk
:
-
freeze
:系统冻结,暂停所有用户空间进程,关闭部分设备,但内存保持通电,恢复速度较快。
-
standby
:除了冻结用户空间进程和关闭部分设备外,CPU也进入低功耗状态,恢复时间稍长。
-
mem
:将系统状态保存到内存中,关闭大部分设备,只有内存保持通电,恢复速度较快,功耗较低。
-
disk
:将系统状态保存到磁盘中,关闭所有设备,恢复时需要从磁盘重新加载系统状态,恢复时间较长,但功耗最低。
可以通过向
/sys/power/state
文件写入相应的状态名称来使系统进入休眠状态:
# 使系统进入freeze状态
echo freeze > /sys/power/state
# 使系统进入mem状态
echo mem > /sys/power/state
在选择休眠状态时,需要根据实际需求进行权衡。如果预计系统很快会被唤醒,如几分钟内,那么
freeze
或
standby
状态可能更合适;如果系统长时间不需要运行,如几小时甚至几天,那么
mem
或
disk
状态可以显著节省电能。
12. 电源管理的综合应用
在实际的嵌入式系统中,需要综合运用上述电源管理技术,根据系统的负载情况和使用场景动态调整电源策略。以下是一个简单的流程图,展示了如何根据CPU负载和定时器事件来选择合适的电源管理策略:
graph TD;
A[系统启动] --> B{CPU负载高?};
B -- 是 --> C[保持高频率运行];
B -- 否 --> D{有即将到期的定时器事件?};
D -- 是 --> E[选择较浅的空闲状态];
D -- 否 --> F[选择较深的空闲状态];
C --> G{长时间无负载?};
G -- 是 --> H[关闭未使用的外设];
G -- 否 --> C;
E --> I{长时间无负载?};
I -- 是 --> H;
I -- 否 --> E;
F --> J{长时间无负载?};
J -- 是 --> H;
J -- 否 --> F;
H --> K{系统长时间空闲?};
K -- 是 --> L[进入休眠状态];
K -- 否 --> M[继续监测负载和定时器];
L --> N{有唤醒事件?};
N -- 是 --> O[恢复系统运行];
N -- 否 --> L;
O --> M;
M --> B;
在这个流程图中,系统首先判断CPU负载是否高。如果负载高,则保持高频率运行;如果负载低,则进一步判断是否有即将到期的定时器事件,根据情况选择合适的空闲状态。当系统长时间无负载时,关闭未使用的外设。如果系统长时间空闲,则进入休眠状态。当有唤醒事件发生时,系统恢复运行,继续监测负载和定时器,循环执行上述过程。
13. 总结与建议
通过对嵌入式Linux系统的电源管理技术的介绍,我们了解到可以通过多种方式来降低系统的功耗,延长电池寿命或减少能源成本。以下是一些总结和建议:
-
选择合适的初始化程序
:根据系统的需求和特点,选择合适的初始化程序,如
BusyBox init
、
systemd
或System V init。如果系统简单且对启动时间要求极高,可选择
BusyBox init
;如果系统复杂且需要更多的功能支持,
systemd
是更好的选择。
-
合理调整CPU频率
:根据系统的负载情况,合理调整CPU的频率。在负载较低时,降低频率可以节省电能;在负载较高时,提高频率可以保证系统的性能。可以使用
CPUFreq
驱动和相应的调节器来实现频率的动态调整。
-
选择最佳空闲状态
:在CPU空闲时,选择合适的空闲状态,以降低功耗。
CPUIdle
驱动可以帮助我们根据系统的情况选择最佳的空闲状态。
-
关闭未使用的外设
:定期检查系统中未使用的外设,并将其电源关闭,以减少不必要的功耗。
-
适时进入休眠状态
:当系统长时间无任务时,将系统置于休眠状态,以最大程度地节省电能。
通过综合运用这些电源管理技术,可以使嵌入式Linux系统在性能和功耗之间取得良好的平衡,满足不同应用场景的需求。在实际开发中,需要根据具体的硬件平台和应用需求进行测试和优化,以达到最佳的电源管理效果。
超级会员免费看
99

被折叠的 条评论
为什么被折叠?



