内核角落 - 使用笔记本电脑模式延长电池寿命

作者:Bart Samwel

笔记本电脑让您可以随时随地自由自在地做任何想做的事情。但是,当电池耗尽时,乐趣就结束了。幸运的是,有很多方法可以省电并延长电池续航时间。例如,您可以降低处理器速度,调暗显示器的背光并停止硬盘驱动器的旋转。前两个技巧在Linux上效果很好,但直到最近,停止硬盘驱动器的旋转可能仍然非常困难。即使您可以让驱动器停止旋转,它也永远不会停止足够长的时间来节省任何电量。本文解释了如何使用笔记本电脑模式,这是最近添加到Linux内核中的一项功能,来真正停止驱动器的旋转。我在这里只讨论 Linux 2.6;2.4 中也提供了笔记本电脑模式,但它有点不同。

硬盘驱动器和电池寿命

让我们做一些数学计算,找出通过停止硬盘驱动器的旋转可以获得多少额外的电池寿命。如今市场上典型的笔记本电脑都配备了容量为 50–100 瓦时的锂离子电池,这可以持续使用两到四个小时。假设我们有一台电池容量为 50 瓦时的笔记本电脑。如果硬盘驱动器开启时电池可以使用 3.5 小时,那么我们可以计算出平均功耗为 50/3.5 = 14.3 瓦。假设笔记本电脑使用典型的笔记本电脑硬盘驱动器,它在空闲模式下使用约 0.9 瓦,在待机模式下使用约 0.3 瓦。理论上,我们可以将功耗降低 0.6 瓦,降至约 13.7 瓦。这将使电池寿命延长至 50/13.7 = 3 小时 39 分钟。增益始终取决于您相对于总功耗节省了多少电量。在我们的示例中,停止旋转节省了约 4% 的总功耗,因此电池寿命的最大增益也约为 4%。

理论就讲到这里,我想向您展示一些真实数据。我借了一位朋友的 Apple PowerBook G4,安装了 Debian GNU/Linux 和 Linux 2.6.6 内核,然后做了一些实验。我想估计电池寿命的最大增益以及我们需要多长时间的启动间隔才能接近该最大值。我预计会有相当大的增益,因为该笔记本电脑配备了耗电的 5,400 rpm 驱动器,并且我剥离了系统的 X 服务器和所有守护程序。我编写了一个基准测试程序,该程序每小时始终执行相同数量的磁盘 I/O,但在 I/O 突发之间具有可配置的非活动期。在非活动期间,基准测试程序会停止硬盘驱动器的旋转。我使用多种非活动期长度运行了基准测试,并使用 APM 电池信息来计算预期的电池寿命。

Kernel Korner - Extending Battery Life with Laptop Mode

图 1. 电池寿命实验结果

我已经运行了这个实验,磁盘一直处于旋转状态,突发间隔从 12 秒到十分钟不等。结果如图 1 所示。如您所见,一旦您的 I/O 频率低于每 30 秒一次,您就几乎节省了所有可以节省的电量。这似乎很奇怪,因为启动磁盘旋转会消耗大量电量,对吗?不,实际上并非如此。如果驱动器可以在两秒钟内启动旋转,那么它消耗的电量与使其空闲运行(例如)八秒钟所消耗的电量大致相同。因此,如果您可以让驱动器停止旋转九秒钟,您就已经节省了一些电量。30 秒的突发间隔已经为相当长的停止旋转留出了空间,这就是为什么它显示出如此良好的基准测试结果的原因。

笔记本电脑模式

笔记本电脑模式是 Linux 内核的一个设置,它更改了内核随时间分配磁盘 I/O 的方式。Linux 通常以少量的方式执行磁盘 I/O,并在时间上均匀分布。但是,在所有正在进行的 I/O 中,您的硬盘驱动器永远没有机会停止旋转,从而浪费了宝贵的电量。对于笔记本电脑来说,磁盘活动必须集中在短时间内,并在它们之间留出非活动期,就像我在基准测试中所做的那样。当您启用笔记本电脑模式时,Linux 正是这样做的。您可以获得长达十分钟的无磁盘活动时间,这肯定会延长笔记本电脑的电池寿命。

工作原理

让我们看一下笔记本电脑模式是如何实现这种 I/O 行为的。要创建没有磁盘活动的期间,我们需要在活动期间尽可能多地完成工作。之后,我们需要尽可能长时间地阻止磁盘 I/O。在活动期间,我们会执行一些额外的操作。首先,我们执行一些预读;如果非活动期间实际需要这些数据,我们就节省了一次启动旋转。笔记本电脑模式默认将预读设置为 4MB。其次,我们在每个活动期结束时将所有内容同步到磁盘。这可以保证您的数据安全;当驱动器停止旋转后,您可以确保在停止旋转之前完成的所有操作都已安全存储。

在非活动期间,写入是我们唯一可以阻止的磁盘 I/O 类型。我们可以将未写入的数据保存在内存中尽可能长的时间,或者直到内存耗尽。不幸的是,这对我们来说实现起来并不容易,因为 Linux 从许多地方提交写入请求。我们需要调整所有这些地方以阻止它们的写入。

第一个也是最重要的调整与修改或脏数据有关。通常,当缓存的磁盘页面被修改超过 30 秒前,它会过期,pdflush 守护程序会将其写入磁盘。幸运的是,过期间隔可以通过 /proc/sys/vm/dirty_expire_centisecs 进行配置。笔记本电脑模式将其设置为十分钟,以便更改在写入磁盘之前在内存中保留长达十分钟。由于每个活动期都以同步结束,因此非活动期开始时没有任何脏页。因此,在非活动期的前十分钟内,我们可以确保不会因为页面过期而被写回。

第二个调整涉及日志文件系统,它们本身会执行大量磁盘 I/O。在笔记本电脑模式支持的大多数日志文件系统上,对文件系统的更改会在五秒钟内触发写入操作。例如,在 ext3 文件系统中,文件系统事务具有最大生命周期,然后会自动提交,提交意味着写入磁盘。可以使用 commit 挂载选项配置此最大生命周期。通过使用此选项设置为十分钟重新挂载文件系统,我们阻止了 ext3 在非活动期间提交事务。同样,我们以同步开始每个非活动期,因此当非活动期开始时,没有任何事务处于打开状态。笔记本电脑模式对其他受支持的文件系统 ReiserFS 和 XFS 也进行了类似的对待。

最后的调整发生在 Linux 的内存管理中。如果在非活动期间分配了大量内存,则内存管理器最终必须选择一些需要丢弃的内存页面。可以选择一个在丢弃之前需要写入磁盘的页面,例如修改过的磁盘页面或需要写入交换空间的页面。但是,它必须启动驱动器旋转才能执行该写入,而我们不希望这种情况发生。Andrew Morton 调整了内存管理器,以便当我们在笔记本电脑模式下运行时,内存管理器首先尝试选择不需要写入的页面。

使用这些调整,笔记本电脑模式可以创建长达十分钟的无磁盘活动时间。当您根本不更改任何文件时,您可以获得更长的无磁盘启动时间。毕竟,如果没有什么要写入的,就没有理由启动磁盘旋转。不幸的是,当您使用默认选项挂载文件系统时,情况会自行改变;文件系统会记录访问时间。即使您只是读取文件,访问时间也会更新,并且最终必须将其写入磁盘。为了避免这个问题,笔记本电脑模式使用 noatime 挂载选项重新挂载所有文件系统。这使它们停止记录访问时间,因此您实际上可以获得超过十分钟的无磁盘 I/O 时间。

您可能已经注意到,我们正在做一些通常从用户空间完成的事情,例如调整 /proc。实际上,我们将笔记本电脑模式分为内核组件和用户空间脚本。您可以使用脚本来启用笔记本电脑模式,它通过设置 /proc/sys/vm/laptop_mode 来启用内核支持。然后,它会重新挂载您的文件系统并调整 /proc 中的一些其他设置。

设置

要在您的系统上设置笔记本电脑模式,首先请确保您拥有支持它的内核版本。笔记本电脑模式包含在 Linux 2.6 中,从 2.6.6 版本开始。在内核源代码树中,您可以在 Documentation/laptop-mode.txt 中找到笔记本电脑模式的文档。控制脚本嵌入在文档中,您必须将其提取并另存为 /sbin/laptop_mode。授予脚本执行权限chmod 700 /sbin/laptop_mode.

要启用笔记本电脑模式,请运行(以 root 身份)/sbin/laptop_mode start。这将完成所有必要的操作,但不会使您的硬盘驱动器停止旋转。要做到这一点,您必须使用以下命令设置硬盘驱动器的空闲超时时间hdparm -S 4 /dev/hda。值 4 表示 20 秒的空闲超时时间。如果您想禁用笔记本电脑模式,只需运行/sbin/laptop_mode stop.

您可能希望配置笔记本电脑模式,以便在笔记本电脑使用电池供电时启动它。如果您有一台支持 ACPI 的笔记本电脑,您可以像这样设置它:从笔记本电脑模式文档中提取文件 ac_adapter 和 battery.sh,并将它们安装在指示的位置。编辑 battery.sh 以配置硬盘驱动器的设备名称和您首选的空闲超时时间,您就可以开始使用了。

启动旋转调试

有时您的驱动器会因您不理解的原因而启动旋转。当这种情况发生时,就该开始调试了。笔记本电脑模式包括用于调试磁盘活动的模式,即块转储模式。在启用它之前,您必须首先停止 syslogd 记录内核消息或完全停止它。如何做到这一点取决于您的发行版。如果您不停止 syslogd,您可能会使您的机器陷入无限循环;调试输出被 syslogd 拾取,syslogd 将其写入磁盘,从而导致更多的调试输出,依此类推。

要启用块转储模式,请运行(以 root 身份)echo 1 > /proc/sys/vm/block_dump。内核输出(您现在必须使用以下命令读取)dmesg因为 syslogd 处于非活动状态,应该显示如下消息

bash(273): READ block 3242 on hda1
bash(273): dirtied inode 10237 (.bash_history) on hda1
pdflush(6): WRITE block 3242 on hda1

您可以按如下方式读取此输出。进程名称为 bash,进程 ID 为 273,已读取设备 /dev/hda1 上编号为 3242 的块。同一进程随后弄脏了一个名为 .bash_history 的文件;该文件已更改,但更改尚未写入磁盘。然后 pdflush 守护程序写入块 3242,这很可能是 bash 之前修改的块。

当您获得调试输出时,就该诊断您的问题了。如果您在某处看到 READ 消息,则无需再进一步查找。找出进程为什么需要读取此数据,并决定您是否要停止该进程,更改应用程序的设置使其不再需要该数据,或者可能在驱动器启动旋转时预先读取数据。预先读取文件并不比执行以下操作更困难cat /my/file >/dev/null,最好两次,这样 Linux 的只读一次逻辑就不会再次将文件丢弃。现在,如果您只看到脏文件消息,则无需担心太多。没有人向磁盘写入任何内容;这些消息仅告诉您一个进程正在进行更改,这些更改最终需要写入。如果您收到这些消息,您的磁盘每十分钟启动旋转一次以写回修改,仅此而已。

如果您看到 WRITE 消息的频率高于每十分钟一次,并且您没有看到任何可能触发活动期的 READ,则很可能是某个进程正在显式同步文件。syslogd 因这样做而臭名昭著。如果您看到 syslogd 在意外的时间写入,您应该调整您的 syslog.conf。它可能包含如下行

kern.*    /var/log/kern.log

此行告诉 syslogd 在每个与 kern.* 匹配的日志消息之后调用 fsync()。如果您将该行更改为

kern.*    -/var/log/kern.log

并重新启动 syslog,syslog 将不再在这些日志消息上调用 fsync()。但是,请注意您为此更改了哪些日志文件。例如,如果您关心安全性,您可能希望保持 auth.log 同步。

技巧和窍门

MP3 播放

如果您想在驱动器暂停的情况下播放 MP3,您可以尝试增加 /sbin/laptop_mode 中的 READAHEAD 设置。或者,您可以将整套 MP3 复制到小型 tmpfs RAM 磁盘。确保您不要将 RAM 磁盘做得太大,以至于它被推送到交换空间,否则您就达不到目的了。小型 RAM 磁盘非常适合您不希望发生磁盘活动的任何事情——但前提是您的数据并不重要。

自定义停止旋转时间

可以调整笔记本电脑模式的最大停止旋转时间。为此,请在 /sbin/laptop_mode 中将变量 MAX_AGE 设置为您希望数据在不保存到磁盘的情况下在内存中保留的最大秒数。默认情况下,它设置为 600 秒或十分钟。将设置增加到更高的数字实际上并没有什么意义,正如您可以从我在 PowerBook 上获得的基准测试结果中看到的那样。如果您更关心您的数据,而不太关心额外的两分钟,您可以将其设置为较低的值。如果您想防止丢失您刚刚保存的一些非常重要的数据,您只需执行sync。笔记本电脑模式尊重您的同步请求,因此这实际上会将所有内容写入磁盘,就像通常情况一样。同步还会重置所有超时,以便在同步后,磁盘可以再次停止旋转长达十分钟。

使用 cpudyn 停止旋转

如果您正在使用 cpudyn 动态控制 CPU 频率,您可能会有兴趣知道它也可以停止硬盘驱动器的旋转。我见过一些驱动器,如果它们的空闲超时设置对于它们的口味来说太低,它们就会直接忽略它们。此外,空闲超时设置以五秒为增量完成,这意味着您无法将其设置为八秒之类的值。cpudyn 派上用场,因为它不依赖于您的硬盘驱动器来检测空闲期,并且它可以随时停止旋转。

智能停止旋转

笔记本电脑模式在活动期结束时执行同步,然后等待磁盘自行停止旋转。最好在驱动器停止旋转之前进行同步,因为这样您可以保存最多 20 秒的最新数据。笔记本电脑模式无法做到这一点,因为它无法主动停止驱动器的旋转。我编写了一个脚本来执行此操作,名为 Smart Spindown,它与笔记本电脑模式协同工作。它是一个未经润色的脚本,但如果您真的想节省每一丁点电量,或者如果您想保持 20 秒的额外数据安全,这可能适合您;请参阅在线资源部分。

致谢

即使我自己没有笔记本电脑,我也很享受在笔记本电脑模式上的工作。我要感谢为笔记本电脑模式做出贡献的其他人的努力,包括 Jens Axboe、Micha Feigin、Andrew Morton 和 Kiko Piris。我还要感谢 Jeroen Kruis 允许我滥用他全新的 PowerBook G4 进行我的实验。

本文的资源: /article/7647

Bart Samwel 发起了将笔记本电脑模式放入 Linux 2.6 的工作,即使他自己没有笔记本电脑。他在荷兰莱顿大学学习计算机科学,并通过编写专有软件谋生。可以通过 bart@samwel.tk 与他联系。

加载 Disqus 评论