Skip to content

Latest commit

 

History

History
788 lines (511 loc) · 31.9 KB

File metadata and controls

788 lines (511 loc) · 31.9 KB

十、降低功耗

嵌入式系统有很多需要电池供电的应用。 从小型物联网(物联网)从传感器收集数据并将其推送到云中进行处理的设备,到自动车辆和机器人-这些系统应该尽可能高能效,以便它们可以在没有稳定的外部电源的情况下长时间运行。

能效是指对系统所有部件(从外部设备到存储器和处理器)的功耗进行智能控制。 功率控制的效率在很大程度上取决于硬件组件的选择和系统设计。 如果处理器不支持动态电压控制,或者外部设备在空闲时不能进入省电模式,那么在软件方面就无能为力了。 然而,如果硬件组件实现标准规范,例如高级配置和电源接口(ACPI),那么电源管理的许多负担可以卸载到操作系统内核。

在本章中,我们将探讨现代硬件平台的不同省电模式以及如何利用它们。 我们将学习如何管理外部设备的电源状态,并通过编写更高效的软件来降低处理器的功耗。

我们将介绍以下主题:

  • Linux 下的节电模式探索
  • 使用RTC(实时时钟的缩写)唤醒
  • 控制 USB 设备的自动挂起
  • 配置 CPU 频率
  • 使用事件等待
  • 使用 PowerTOP 分析功耗

本章中的配方将帮助您有效地利用现代操作系统的节能功能,并编写针对电池供电设备进行优化的代码。

技术要求

要运行本章中的代码示例,您需要有 Raspberry PI Box 修订版 3 或更高版本。

Linux 下的节电模式探索

当系统处于空闲状态并且没有工作要做时,可以将其置于休眠状态以节省电能。 与人类睡眠类似,在被外部事件(例如闹钟)唤醒之前,它什么也做不了。

Linux 支持多种睡眠模式。 休眠模式的选择和可节省的电量取决于硬件支持以及进入该模式和从该模式唤醒所需的时间。

支持的模式如下:

  • 挂起到空闲(S2I):这是一种完全可以在软件中实现的轻度休眠模式,不需要硬件的任何支持。 设备被置于低功率模式,计时被挂起,以使处理器在节能空闲状态中花费更多时间。 系统被来自任何外部设备的中断唤醒。
  • STANDBY:这类似于 S2I,但是通过使所有非引导 CPU 离线来提供更多的节能。 某些设备的中断可能会唤醒系统。
  • 挂起至 RAM(STRS3):系统的所有组件(内存除外),包括 CPU,都进入低功耗模式。 系统状态一直保持在内存中,直到被来自有限设备集的中断唤醒。 此模式需要硬件支持。
  • 休眠挂起到磁盘:这提供了最大的节能,因为所有系统组件都可以关闭电源。 进入此状态时,将拍摄内存快照并将其写入永久存储(磁盘或闪存)。 在那之后,系统就可以关闭了。 作为引导过程的一部分,在唤醒时,将恢复保存的快照,系统将恢复其工作。

在本指南中,我们将学习如何查询特定系统支持的睡眠模式,以及如何切换到其中一种模式。

怎么做……

在本食谱中,我们将使用简单的 bash 命令访问在QEMU(快速仿真器的缩写)中运行的 Linux 系统支持的休眠模式。

  1. 按照第 3 章使用不同架构中的说明运行 Raspberry Pi QEMU。
  2. 以用户pi身份登录,使用密码raspberry
  3. 运行sudo以获得超级用户访问权限:
$ sudo bash
#
  1. 要获取支持的休眠模式列表,请运行以下命令:
 # cat /sys/power/state
  1. 现在切换到支持的模式之一:
 # echo freeze > /sys/power/state
  1. 系统进入睡眠状态,但我们没有指示它如何唤醒。 现在关闭 QEMU 窗口。

它是如何运作的..。

电源管理是 Linux 内核的一部分;这就是为什么我们不能使用 Docker 容器来处理它。 Docker 虚拟化是轻量级的,并且使用主机操作系统的内核。

我们也不能使用真正的 Raspberry PI 板,因为由于硬件限制,它根本不提供任何睡眠模式。 然而,QEMU 提供了完全虚拟化,包括我们用来模拟 Raspberry PI 的内核中的电源管理。

Linux 通过 sysfs 接口提供对其电源管理功能的访问。 应用可以读写/sys/power目录中的文本文件。 Root 用户对电源管理功能的访问是受限的;这就是我们需要在登录到系统后获取 root shell 的原因:

$ sudo bash

现在我们可以获得支持的睡眠模式列表。 为此,我们读取/sys/power/state文件:

$ cat /sys/power/state

该文件由一行文本组成。 每个单词代表一种受支持的休眠模式,模式之间用空格分隔。 我们可以看到,QEMU 内核支持两种模式:freezemem

冻结表示我们在上一节中讨论的 S2I 状态。 mem的含义由/sys/power/mem_sleep文件的内容定义。 在我们的系统中,它只包含[s2idle],表示与freeze相同的 S2I 状态。

让我们将模拟器切换到freeze模式。 我们将单词freeze写入/sys/power/state,QEMU 窗口立即变黑并冻结:

我们能够让模拟的 Linux 系统进入睡眠状态,但无法唤醒它--它无法理解中断的来源。 我们了解了不同的休眠模式以及使用它们的内核 API。 根据您的嵌入式系统的要求,您可以使用这些模式来降低功耗。

还有更多的..。

有关睡眠模式的更多信息,请参见位于https://www.kernel.org/doc/html/v4.19/admin-guide/pm/sleep-states.htmlLinux 内核指南的相应部分。

使用 RTC 唤醒

在前面的配方中,我们可以让我们的 QEMU 系统进入睡眠状态,但无法唤醒它。 我们需要一种设备,它可以在系统的大部分内部组件断电时向系统发送中断。

RTC(**实时时钟)**就是这样的设备之一。 它的功能之一是在系统关闭时保持内部时钟运行,为此,它有自己的电池。 RTC 的耗电量类似于电子表;它使用相同的 3V 电池,可以使用它的电源工作数年。

RTC 可以作为闹钟工作,在给定时间向 CPU 发送中断。 这使其成为按时唤醒系统的理想设备。

在本食谱中,我们将学习如何使用内置的 RTC 在特定时间唤醒 Linux 系统。

怎么做……

在此配方中,我们将提前 1 分钟设置唤醒时间,并使系统进入睡眠状态:

  1. 登录任何有 RTC 时钟的 Linux 系统-任何 Linux 笔记本电脑都可以工作。 不幸的是,Raspberry Pi 没有板载 RTC,在没有额外硬件的情况下无法唤醒。

  2. 使用sudo获取 root 权限:

$ sudo bash
#
  1. 指示 RTC 在1分钟内唤醒系统:
# date '+%s' -d '+1 minute' > /sys/class/rtc/rtc0/wakealarm
  1. 使系统进入睡眠状态:
# echo freeze > /sys/power/state
  1. 请稍等片刻。 您的系统将会苏醒。

它是如何运作的..。

与 Linux 内核公开的许多其他函数一样,可以通过 sysfs 接口访问 RTC。 要设置将向系统发送唤醒中断的报警,我们需要向/sys/class/rtc/rtc0/wakealarm文件写入POSIX(可移植操作系统接口的缩写)时间戳。

POSIX 时间戳(我们将在第 11 章时间点和间隔中详细讨论)定义为自大纪元(即 1970 年 1 月 1 日 00:00)以来经过的秒数。

虽然我们可以使用time函数编写程序来读取当前时间戳,添加 60,并将结果写入wakealarm文件,但是我们可以使用 Unix shell 和date命令(在任何现代 Unix 系统上都可以使用)在一行中完成这一任务。

Date 实用程序不仅可以使用不同的格式格式化当前时间,还可以解释不同格式的日期和时间。

我们指示date解释时间字符串+1 minute,并使用格式化模式%s将其输出为 POSIX 时间戳。 我们将其标准输出重定向到wakealarm文件,有效地将其传递给 RTC 驱动程序:

date '+%s' -d '+1 minute' > /sys/class/rtc/rtc0/wakealarm

现在,知道警报将在 60 秒后响起,我们可以让系统进入睡眠状态。 与前面的配方一样,我们将所需的休眠模式写入/sys/power/state文件:

# echo freeze > /sys/power/state

系统进入休眠状态。 您会注意到屏幕关闭了。 如果使用Secure Shell(SSH)连接到 Linux 计算机,命令行将冻结。 然而,一分钟后它就会苏醒,屏幕打开,终端再次响应。

这项技术对于定期、不频繁地从传感器收集数据(例如每小时或每天)的任务非常有效。 系统大部分时间都处于断电状态,醒来时只是收集数据并存储或将其发送到云,然后再次进入休眠状态。

还有更多的..。

设置 RTC 报警的另一种方法是使用rtcwake实用程序。

控制 USB 设备的自动挂起

关闭外部设备是最有效的省电方法之一。 然而,当设备可以安全关闭时,并不总是很容易理解。 网卡或存储卡等外部设备可以执行内部数据处理;否则,在任意点对设备进行缓存和断电可能会导致数据丢失。

为了缓解此问题,许多通过 USB 连接的外部设备可以在主机请求时切换到低功耗模式。 这样,他们就可以在进入挂起状态之前执行所有必要的步骤来安全地处理内部数据。

由于 Linux 仅通过其 API 提供对外部设备的访问,因此它知道设备何时被应用和内核服务使用。 如果设备在一段时间内没有使用,Linux 内核中的电源管理系统可以指示设备自动进入省电模式-不需要来自用户空间应用的显式请求。 此功能称为自动暂停。 然而,内核允许应用控制设备的空闲时间,之后自动暂停生效。

在本食谱中,我们将学习如何启用自动暂停和修改特定 USB 设备的自动暂停间隔。

怎么做……

我们将为连接到您的 Linux 设备的 USB 设备启用自动暂停并修改其自动暂停时间:

  1. 登录您的 Linux 机器(Raspberry PI、Ubuntu 和 Docker 容器不起作用)。
  2. 切换到 root 帐户:
$ sudo bash
#
  1. 获取已连接的所有 USB 设备的当前autosuspend状态:
# for f in /sys/bus/usb/devices/*/power/control; do echo "$f"; cat $f; done
  1. 为其中一个设备启用autosuspend
# echo auto > /sys/bus/usb/devices/1-1.2/power/control
  1. 读取设备的autosuspend间隔:
# cat /sys/bus/usb/devices/1-1.2/power/autosuspend_delay_ms 
  1. 修改autosuspend间隔:
# echo 5000 > /sys/bus/usb/devices/1-1.2/power/autosuspend_delay_ms 
  1. 检查设备的当前电源模式:
# cat /sys/bus/usb/devices/1-1.2/power/runtime_status

可以使用标准文件 API 在 C++ 中编写相同的操作。

它是如何运作的..。

Linux 通过 sysfs 文件系统公开其电源管理 API,这使得使用标准文件读写操作读取任何设备的当前状态和修改其设置成为可能。 因此,我们可以使用任何支持基本文件操作的编程语言来控制 Linux 中的外部设备。

为了简化我们的示例,我们将使用 Unix shell,但在必要时可以用 C++ 编写完全相同的逻辑。

首先,我们检查所有连接的 USB 设备的autosuspend设置。 在 Linux 中,每个 USB 设备的参数都公开为/sysfs/bus/usb/devices/文件夹下的一个目录。 每个设备目录又有一组表示设备参数的文件。 所有与电源管理相关的参数都分组在power子目录中。

要读取autosuspend的状态,我们需要读取设备power目录中的control文件。 使用 Unix 外壳通配符替换,我们可以读取所有 USB 设备的以下文件:

# for f in /sys/bus/usb/devices/*/power/control; do echo "$f"; cat $f; done

对于与通配符匹配的每个目录,我们显示控制文件的完整路径及其内容。 结果取决于连接的设备,可能如下所示:

报告的状态可以是自动暂停或on。 如果状态报告为 autosuspend,则启用自动电源管理;否则,设备将始终处于打开状态。

在我们的示例中,设备usb11-1.11-1.2处于打开状态。 让我们修改1-1.2的配置以使用 autosuspend。 为此,我们只需将字符串_auto_写入相应的_control_文件。

# echo auto > /sys/bus/usb/devices/1-1.2/power/control

在所有设备上再次运行读取循环显示1-1.2设备现在处于autosuspend模式:

什么时候停运? 我们可以从power子目录中的autosuspend_delay_ms文件中读取:

# cat /sys/bus/usb/devices/1-1.2/power/autosuspend_delay_ms 

显示设备将在空闲2000毫秒后挂起:

让我们将其更改为5秒。 我们在autosuspend_delay_ms文件中写入5000

# echo 5000 > /sys/bus/usb/devices/1-1.2/power/autosuspend_delay_ms 

再次阅读它会显示新值被接受:

现在让我们检查一下设备的当前电源状态。 我们可以从runtime_status文件中读取:

# cat /sys/bus/usb/devices/1-1.2/power/runtime_status

状态报告为active

Please note that the kernel does not control the power state of devices directly; it only requests them to change the state. Even if a device is requested to switch into suspend mode, it may refuse to do it for various reasons—for example, it may not support the power-saving mode at all.

通过 sysfs 界面访问任何设备的电源管理设置是调整运行 Linux 操作系统的嵌入式系统功耗的有效方法。

还有更多的..。

没有直接的方法可以立即关闭 USB 设备;但是,在许多情况下,可以通过将0写入autosuspend_delay_ms文件来实现。 内核将零自动暂停间隔解释为对设备的立即挂起请求。

关于 linux 中 usb 电源管理的更多细节可以在 linux 内核文档的相应部分中找到,可以在https://www.kernel.org/doc/html/v4.13/driver-api/usb/power-management.html上找到。

配置 CPU 频率

CPU 频率是决定系统性能和功耗的重要参数。 频率越高,CPU 每秒执行的指令就越多。 但这是有代价的。 更高的频率意味着更高的功耗,这反过来又意味着需要散失更多的热量,以避免处理器过热。

现代处理器能够根据负载使用不同的工作频率。 对于计算密集型任务,它们使用最高频率来实现最高性能,但当系统大部分空闲时,它们会切换到较低频率以降低功耗和热影响。

正确的频率选择由操作系统管理。 在本食谱中,我们将学习如何在 Linux 中设置 CPU 频率范围和选择频率调节器,以根据您的需要微调 CPU 频率。

怎么做……

我们将使用简单的 shell 命令来调整 Raspberry PI 盒上的 CPU 频率参数:

  1. 登录到 Raspberry PI 或其他非虚拟化 Linux 系统。
  2. 切换到 root 帐户:
$ sudo bash
#
  1. 获取系统中所有可用 CPU 核心的当前频率:
# cat /sys/devices/system/cpu/*/cpufreq/scaling_cur_freq
  1. 获取 CPU 支持的所有频率:
# cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
  1. 获取可用的 CPU 频率调节器:
# cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
  1. 现在让我们检查一下当前使用的是哪个频率调节器:
# cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 
  1. 将 CPU 的最低频率调整为支持的最高频率:
# echo 1200000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq
  1. 再次显示当前频率以了解效果:
# cat /sys/devices/system/cpu/*/cpufreq/scaling_cur_freq
  1. 将最低频率调整为支持的最低频率:
# echo 600000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_fre
  1. 现在,让我们检查一下 CPU 频率如何取决于正在使用的调控器。 选择performance调速器,获取当前频率:
# echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# cat /sys/devices/system/cpu/*/cpufreq/scaling_cur_freq
  1. 选择powersave调速器并观察结果:
# echo powersave > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# cat /sys/devices/system/cpu/*/cpufreq/scaling_cur_freq

您可以使用常规文件 API 在 C++ 中实现相同的逻辑。

它是如何运作的..。

与 USB 电源管理类似,CPU 频率管理系统 API 通过 sysfs 公开。 我们可以将其参数作为常规文本文件进行读取和修改。

我们可以在/sys/devices/system/cpu/目录下找到与 CPU 核心相关的所有设置。 配置参数在以每个代码索引命名的子目录(如cpu1cpu2等)中按 CPU 核心进行分组。

我们感兴趣的是与 CPU 频率管理相关的几个参数,这些参数位于每个内核的cpufreq子目录中。 让我们读出所有可用内核的当前频率:

# cat /sys/devices/system/cpu/*/cpufreq/scaling_cur_freq

我们可以看到,所有内核都有相同的频率,600 MHz(cpufreq子系统使用 kHz 作为频率测量单位):

接下来,我们计算出 CPU 支持的所有频率:

# cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies

Raspberry PI 3 的 ARM 处理器仅支持两个频率,600 MHz 和 1.2 GHz:

我们不能直接设置所需的频率。 Linux 通过所谓的调控器在内部管理 CPU 频率,并且只允许我们调整两个参数:

  • 可供调速器使用的频率范围
  • 调速器的类型

虽然这看起来是一个限制,但这两个参数为实现相当复杂的策略提供了足够的灵活性。 让我们检查一下这两个参数的修改如何影响 CPU 频率。

首先,让我们找出哪些调控器受支持,哪些调控器当前正在使用:

当前调控器为ondemand 它根据系统负载调整频率。 目前,Raspberry Pi 板相当空闲,因此它使用最低频率,600 MHz。 但是如果我们让最低频率等于最高频率呢?

# echo 1200000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq

在我们更新了一个内核的scaling_min_freq参数后,所有内核的频率都被更改为最大值:

因为所有四个核心都属于同一个 CPU,所以我们不能单独更改它们的频率;更改一个核心的频率会影响所有核心。 然而,我们可以独立控制独立 CPU 的频率。

现在我们将最低频率恢复到 600 MHz 并更改调速器。 我们选择了performance调控器,而不是调整频率的ondemand调控器,旨在无条件地提供最高性能:

echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_g;overnor

毫不奇怪,它将频率提高到了支持的最高频率:

另一方面,powersave调控器的目标是尽可能多地节省电能,因为它始终坚持支持的最低频率,而不管负载是什么:

如您所见,通过调整频率范围和频率调节器,您可以根据系统的性质灵活地微调频率,并降低 CPU 的功耗。

还有更多的..。

除了ondemandperformancepowersave之外,还有其他调控器可以从用户空间应用中提供更灵活的 CPU 频率调优。 您可以在 Linux CPUFreq 的相应部分https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt中找到有关可用调控器及其属性的更多详细信息

使用事件等待

等待是软件开发中非常常见的模式。 应用必须等待用户输入或数据准备好进行处理。 嵌入式程序与外部设备通信,需要知道何时可以从设备读取数据,以及设备何时准备好接受数据。

通常,开发人员使用轮询技术的变体来等待。 它们在循环中检查特定于设备的可用性标志,当设备将其设置为 true 时,它们继续读取或写入数据。

虽然这种方法很容易实现,但从功耗的角度来看,它的效率很低。 当处理器不断忙于标志检查时,操作系统电源管理器不能将其置于更省电的模式。 基于负载,我们前面讨论的 Linuxondemand频率调控器甚至可以决定增加 CPU 频率,尽管这实际上是一种变相的等待。 此外,轮询请求可能会阻止目标设备或设备总线保持在省电模式,直到数据就绪。

这就是为什么它应该依赖操作系统产生的中断和事件,而不是关注能效的轮询程序。

在本食谱中,我们将学习如何使用操作系统事件来等待连接特定的 USB 设备。

怎么做……

我们将创建一个可以监视 USB 设备并等待特定设备出现的应用:

  1. 在您的工作~/test目录中,创建一个名为udev的子目录。
  2. 使用您喜欢的文本编辑器在udev子目录中创建udev.cpp文件。
  3. 将 Essential Includes 和namespace定义放入udev.cpp文件:
#include <iostream>
#include <functional>

#include <libudev.h>
#include <poll.h>

namespace usb {
  1. 现在,让我们定义Device类:
class Device {
  struct udev_device *dev{0};

  public:
    Device(struct udev_device* dev) : dev(dev) {
    }

    Device(const Device& other) : dev(other.dev) {
      udev_device_ref(dev);
    }

    ~Device() {
        udev_device_unref(dev);
    }

    std::string action() const { 
        return udev_device_get_action(dev);
     }

    std::string attr(const char* name) const {
      const char* val = udev_device_get_sysattr_value(dev,
             name);
      return val ? val : "";
    }
};
  1. 之后,添加Monitor类的定义:
class Monitor {
  struct udev_monitor *mon;

  public:
    Monitor() {
      struct udev* udev = udev_new();
      mon = udev_monitor_new_from_netlink(udev, "udev");
      udev_monitor_filter_add_match_subsystem_devtype(
           mon, "usb", NULL);
      udev_monitor_enable_receiving(mon);
    }

    Monitor(const Monitor& other) = delete;

    ~Monitor() {
      udev_monitor_unref(mon);
    }

    Device wait(std::function<bool(const Device&)> process) {
      struct pollfd fds[1];
      fds[0].events = POLLIN;
      fds[0].fd = udev_monitor_get_fd(mon);

      while (true) {
          int ret = poll(fds, 1, -1);
          if (ret < 0) {
            throw std::system_error(errno, 
                std::system_category(),
                "Poll failed");
          }
          if (ret) {
            Device d(udev_monitor_receive_device(mon));
            if (process(d)) {
              return d;
            };
          }
      }
    }
};
};
  1. usb名称空间中定义了DeviceMonitor之后,添加一个简单的main函数来说明如何使用它们:
int main() {
  usb::Monitor mon;
  usb::Device d = mon.wait([](auto& d) {
    auto id = d.attr("idVendor") + ":" + 
              d.attr("idProduct");
    auto produce = d.attr("product");
    std::cout << "Check [" << id << "] action: " 
              << d.action() << std::endl;
    return d.action() == "bind" && 
           id == "8086:0808";
  });
  std::cout << d.attr("product")
            << " connected, uses up to "
            << d.attr("bMaxPower") << std::endl;
  return 0;
}
  1. 创建包含我们程序的构建规则的CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.5.1)
project(udev)
add_executable(usb udev.cpp)
target_link_libraries(usb udev)
  1. 使用sshudev目录复制到 Linux 机器上的主目录中。
  2. 登录到 Linux 机器,将目录更改为udev,然后使用cmake构建程序:
$cd ~/udev; cmake. && make

现在您可以构建和运行应用了。

它是如何运作的..。

为了获得有关 USB 设备上事件的系统通知,我们使用了一个名为libudev的库。 它只提供一个纯 C 接口,所以我们创建了简单的 C++ 包装器来简化编码。

对于我们的包装类,我们声明了名为usbnamespace

namespace usb {

它包含两个类。 第一个类是Device,它为我们提供了一个指向名为udev_device的低级libudev对象的 C++ 接口。

我们定义了一个从udev_device指针创建Device实例的构造函数和一个释放udev_device的析构函数。 在内部,libudev对其对象使用引用计数,因此我们的析构函数调用一个函数来减少udev_device的引用计数:

    ~Device() {
        udev_device_unref(dev);
    }
    Device(const Device& other) : dev(other.dev) {
      udev_device_ref(dev);
    }

这样,我们就可以复制Device个实例,而不会泄露内存或文件描述符。

除了构造函数和析构函数外,Device类只有两个方法:actionattraction方法返回最新的 USB 设备操作:

    std::string action() const { 
        return udev_device_get_action(dev);
     }

attr方法返回与设备关联的任何 sysfs 属性:

    std::string attr(const char* name) const {
      const char* val = udev_device_get_sysattr_value(dev,
             name);
      return val ? val : "";
    }

Monitor类也有一个构造函数和一个析构函数,但我们通过禁用复制构造函数使其不可复制:

    Monitor(const Monitor& other) = delete;

构造函数使用静态变量初始化libudev实例,以确保它只初始化一次:

      struct udev* udev = udev_new();

它还设置监控过滤器并启用监控:

      udev_monitor_filter_add_match_subsystem_devtype(
           mon, "usb", NULL);
      udev_monitor_enable_receiving(mon);

wait方法包含最重要的监控逻辑。 它接受一个类似函数的process对象,该对象在每次检测到事件时被调用:

Device wait(std::function<bool(const Device&)> process) {

如果事件及其所源自的设备是我们需要的,则函数应返回true;否则,它返回false以指示wait应继续工作。

在内部,wait函数创建用于将设备事件传递给程序的文件描述符:

      fds[0].fd = udev_monitor_get_fd(mon);

然后它建立了监控环路。 尽管名为poll,但poll函数并不经常检查设备的状态;它等待指定文件描述符上的事件。 我们将-1作为超时传递,表示我们打算永远等待事件:

int ret = poll(fds, 1, -1);

poll函数仅在出现错误或新的 USB 事件时返回。 我们通过抛出异常来处理错误情况:

          if (ret < 0) {
            throw std::system_error(errno, 
                std::system_category(),
                "Poll failed");
          }

对于每个事件,我们创建Device的一个新实例,并将其传递给process。 如果process返回true,我们退出等待循环,将Device的实例返回给调用方:

            Device d(udev_monitor_receive_device(mon));
            if (process(d)) {
              return d;
            };

让我们看看如何在我们的应用中使用这些类。 在main函数中,我们创建Monitor的一个实例并调用它的wait函数。 我们使用 lambda 函数来处理每个操作:

usb::Device d = mon.wait([](auto& d) {

在 lambda 函数中,我们打印所有事件的信息:

    std::cout << "Check [" << id << "] action: " 
              << d.action() << std::endl;

我们还检查特定操作和设备id

    return d.action() == "bind" && 
           id == "8086:0808";

找到后,我们会显示有关其功能和电源要求的信息:

  std::cout << d.attr("product")
            << " connected, uses up to "
            << d.attr("bMaxPower") << std::endl;

最初运行此应用不会产生任何输出:

但是,一旦我们插入 USB 设备(在我的例子中是 USB 麦克风),我们可以看到以下输出:

应用可以等待特定的 USB 设备,并在其连接后进行处理。 它依赖于操作系统提供的信息,在不进行繁忙循环的情况下完成此操作。 因此,当操作系统阻止poll调用时,应用大部分时间处于休眠状态。

还有更多的..。

libudev有许多 C++ 包装器。 您可以使用其中之一,也可以使用食谱中的代码创建自己的代码作为起点。

使用 PowerTOP 分析功耗

在运行多个用户空间和内核空间服务并同时控制多个外部设备的复杂操作系统(如 Linux)中,并不总是很容易找到可能导致过度耗电的组件。 即使发现效率低下,修复它也可能是困难的。

解决方案之一是使用 POWER PROFILE 工具,如 PowerTOP。 它可以诊断 Linux 系统中的功耗问题,并允许用户调整系统参数以节省电力。

在本食谱中,我们将学习如何在 Raspberry PI 系统上安装和使用 PowerTOP。

怎么做……

在本配方中,我们将在交互模式下运行 PowerTOP 并分析其输出:

  1. 以用户pi的身份使用密码raspberry登录到您的 Raspberry PI 系统。
  2. 运行sudo以获得超级用户访问权限:
$ sudo bash
#
  1. 从存储库安装 PowerTOP:
 # apt-get install powertop
  1. 停留在根 shell 中,运行 PowerTOP:
 # powertop

PowerTOP UI 将显示在您的终端中。 使用Tab键在其屏幕之间导航。

它是如何运作的..。

PowerTOP 是英特尔开发的一款工具,用于诊断 Linux 系统中的电源问题。 它是 Raspbian 发行版的一部分,可以使用apt-get命令进行安装:

# apt-get install powertop

当我们在没有参数的情况下运行它时,它会以交互模式启动,并列出所有进程和内核任务,按照它们的功耗和它们生成的事件的频率排序。 正如我们在Using Events for Waiting配方中所讨论的,程序唤醒处理器的频率越高,它的能效就越低:

使用Tab键,我们可以切换到其他报告模式。 例如,设备统计信息显示设备消耗的能量或 CPU 时间:

另一个有趣的标签是 Tunab。 PowerTOP 可以检查一些影响功耗的设置,并标记那些不是最优的设置:

如您所见,其中两个 USB 设备被标记为Bad,因为它们不使用 autosuspend。 通过按Enter键,PowerTOP 启用自动暂停,显示可从脚本使用的命令行,使其成为永久性的。 启用 autosuspend 后,可调状态更改为Good

可以调整许多系统参数以节省电力。 有时它们是显而易见的,比如在 USB 设备上使用 autosuspend。 有时它们不是这样的,例如在内核上使用超时,用于将文件缓存刷新到磁盘。 使用电源诊断和优化工具(如 PowerTOP)可帮助您调整系统以实现最高能效。

还有更多的..。

除了交互模式外,PowerTOP 还有其他模式可以帮助您优化功耗使用,例如校准、工作负载和自动调优。 有关 PowerTOP 特性、使用场景和结果解释的更多信息,请参阅位于https://01.org/sites/default/files/page/powertop_users_guide_201412.pdfPowerTOP 用户指南**指南