树莓派高级教程(一)

原文:Advanced Raspberry Pi

协议:CC BY-NC-SA 4.0

一、树莓派

Raspberry Pi 在两个层面上令人惊叹——信用卡大小的 SBC(单板计算机)的高级功能和它的价格。即使有今天的圆周率的竞争对手,树莓圆周率仍然是至高无上的,因为很少有人能击败它的价格。此外,它还享有强大的软件和社区支持。

价格是 Pi 的一个重要优势,而竞争对手并不总是欣赏这一点。爱好者和制造者正在以新的、有时是冒险的方式应用圆周率。一些刚开始的人不想因为一个新手的失误而失去他们的 SBC。在低 Pi 价位,亏损可以不灰心的吸收。想象一下,一名学生以 349 美元的价格购买了一台英特尔焦耳 1 (当它被提供时),并意外地举杯庆祝。这足以让大多数人放弃了!价格让每个人在学习中无所畏惧。

SBC 库存

在考虑关于 Raspberry Pi 中资源的细节之前,做一个高级清单是有用的。在这一章中,让我们列出当你购买 Pi 时你会得到什么。

在本书中,您将从两个角度研究每种资源:

  • 硬件本身——它是什么以及它是如何工作的

  • 背后的驱动软件和 API

在某些情况下,硬件背后会有一个或多个内核模块,形成设备驱动程序层。它们公开了应用和硬件设备之间的接口软件 API。例如,应用通过使用ioctl(2)调用与驱动程序通信,而驱动程序与总线上的 I2C 设备通信。/sys/class文件系统是设备驱动程序向应用公开自己的另一种方式。在第十二章研究 GPIO(通用输入/输出)时,你会看到这一点。

有些情况下,Raspbian Linux 中不存在驱动程序,这需要您使用“裸机”方法。例如,使用软件创建 PWM 信号。通过将 GPIO 寄存器映射到应用存储空间,可以直接从应用中获得所需的结果。直接访问和驱动程序访问各有利弊。

因此,虽然汇总清单仅列出了硬件设备,但您将在前面的章节中更详细地检查每个资源。

模型

硬件清单直接受到被检查设备型号的影响。这些年来,已经生产了几种型号,从型号 B 开始,然后是型号 a。从那时起,其他几种装置也开始出现,这些总结在表 1-1 中。更多细节可以在网上看到。 2

表 1-1

树莓 Pi 模型综述

|

模型

|

介绍

|

价格

|

中央处理器

|

社会学

|

混杂的

|
| — | — | — | — | — | — |
| 模型 A | 2013 年 2 月 | $25 | ARMv6Z 战斗机 | BCM2835 | 32 位 |
| 模型 A | 2014 年 11 月 | $20 | ARMv6Z 战斗机 | BCM2835 | 32 位 |
| B 型 | 2012 年 4 月 | $35 | ARMv6Z 战斗机 | BCM2835 | 32 位 |
|   | 2014 年 7 月 | $25 | ARMv6Z 战斗机 | BCM2835 | 32 位 |
| B 2 型 | 2015 年 2 月 | $35 | ARMv7-A 战斗机 | BCM2836 | 四路 32 位 |
| 型号 B 2 (1.2) | 2016 年 10 月 | $35 | ARMv8-A 突击步枪 | BCM2837 | 四路 32/64 位 |
| 模型 B 3 | 2016 年 2 月 | $35 | ARMv8-A 突击步枪 | BCM2837 | 四路 32/64 位 |
| 型号 B 3+ | 2018 年 3 月 | $35 | ARMv8-A 突击步枪 | BCM2837B0 | 四路 32/64 位 |
| 计算模块 1 | 2016 年 1 月 | $30 | ARMv6Z 战斗机 | BCM2835 | 32 位 |
| 计算模块 3 | 2017 年 1 月 | $30 | ARMv8-A 突击步枪 | BCM2837 | 四路 64 位 |
| 计算模块 3 Lite | 2017 年 1 月 | $25 | ARMv8-A 突击步枪 | BCM2837 | 四路 64 位 |
| 零(1.2) | 2015 年 11 月 | $5 | ARMv6Z 战斗机 | BCM2834 | 32 位 |
| 零(1.3) | 2016 年 5 月 | $5 | ARMv6Z 战斗机 | BCM2834 | 32 位 |
| 零瓦特 | 2017 年 2 月 | $10 | ARMv6Z 战斗机 | BCM2834 | 无线 32 位 |

树莓 Pi 型号 B

图 1-1 展示了树莓 Pi 模型 B,第 1 代。这款主板于 2012 年 4 月发布,售价 35 美元。请注意,它使用了大 SDHC(安全数字高容量)卡,显示在照片的左边。下面的插座如图 1-2 所示。GPIO 当时是一个 26 针的插头,和后来的 A 型一样。还有一个标记为 P5 的 4x2 接头,它有电源、接地和四个 GPIO 引脚。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-1

树莓 Pi 型号 B(顶部),第 1 代

采用的 ARM 架构是 ARMv6Z。单个 32 位内核运行速度为 700 MHz,使用 256 MB SDRAM。2016 年 5 月,这一数字增加到了 512 MB。该板包括 2 个 USB 端口、15 针 MIPI 摄像头接口、LCD MIPI 接口、HDMI 和 RCA 复合视频输出、3.5 毫米音频插孔和 GPIOs。网络接口由一个 10/100 Mbit/s 的以太网适配器组成。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-2

树莓 Pi 型号 B(下图),第 1 代

额定功率约为 700 毫安(3.5 瓦),取自微型 USB 连接器或接头带。

树莓 Pi 2 型号 B

树莓 Pi 2 型号 B 于 2015 年 2 月上市,售价 35 美元。该型号采用 ARMv7A 32 位架构。主要改进是支持四个 CPU(中央处理器)内核,运行频率为 900 MHz。另一个改进是 1 GB 的 SDRAM,允许更大的应用混合。图 1-3 示出了 pcb 的顶部,而图 1-4 示出了底部。

其他值得注意的变化包括用于 GPIO 的 Raspberry Pi 标准化 40 引脚排线。提供了四个 USB 端口,并将安装孔移到了 pcb(印刷电路板)上。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-3

树莓 Pi 2 型号 B 的顶部

主板还使用微 SDHC 插槽来存储文件。图 1-3 显示其从 pcb 下方的左中部伸出。空闲时功耗降至 220 毫安(1.1 瓦),但在压力下会跃升至 820 毫安(4.1 瓦)。这就需要一个更大的电源适配器来为设备供电。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-4

树莓 Pi 2 型号 B 的底部

树莓 Pi 3 型号 B

2016 年 2 月,树莓 Pi 3 Model B 上市,售价也是 35 美元。这提供了 arm V8-64/32 位架构。四核以 1.2 GHz 的速度运行,并配有 1 GB 的 SDRAM。另一个礼物是增加了 IEEE 802.11n-2009 无线支持和蓝牙 4.1。图 1-5 示出了 pcb 的顶侧,而图 1-6 示出了底部。

空闲时功耗为 300 mA (1.5 W),但在压力下会增加到 1.34 A (6.7 W)。图中显示了添加到 CPU 的散热器,但不包括在内。添加散热器可以防止内核降低时钟速度来调节温度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-6

树莓 Pi 3 型号 B 的底部

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-5

树莓 Pi 3 型号 B 的顶部

树莓 Pi 3 型号 B+

这款机型于 2018 年 3 月上市,价格也是 35 美元。是一款 64 位,1.4 GHz 四核,1gb SDRAM。网络端口支持 10/100/1000 Mbit/s 以太网,尽管由于其内部使用 USB 集线器,最高速度被限制在 300 Mbit/s 左右。无线支持现在包括用于双频 2.4/5 GHz 操作的 802.11ac。蓝牙升级到蓝牙 4.2 LS BLE。

空闲时功耗为 459 毫安(2.295 瓦),满负荷时增加到 1.13 安(5.661 瓦)。注意图 1-7 中 CPU 芯片上的金属帽。这有助于在不需要散热器的情况下散热(尽管使用散热器可能仍然是有益的)。pcb 的底面如图 1-8 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-8

树莓 Pi 3 型号 B+的底部

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-7

树莓 Pi 3 型号 B+的顶部

树莓派零度

并非每个 maker 项目都需要 64 位四核和 1gb SDRAM 的全部资源。首款树莓 Pi Zero 于 2015 年 11 月问世,后来于 2016 年 5 月升级。单价为 5 美元,是许多小型项目的理想 SBC。

Zero 是一款 ARMv6Z 架构(32 位)器件,以 1 GHz 运行单核。SDRAM 限制在 512 MB,对于大多数项目来说还是很够用的。第一个零缺少 MIPI 相机接口,该接口是在 2016 年修订版中添加的。

为了节省成本,没有焊接头带或连接器。如果最终用户需要,pcb 上还有复合视频的标记点。HDMI 输出通过迷你 HDMI 连接器提供,立体声音频通过 PWM(脉宽调制)GPIO 提供。零上也没有有线以太网端口。它可以通过使用一个微型 USB 端口和一个以太网适配器来提供。

电源通过另一个 Micro-USB 连接器提供,空闲时的功耗为 100 mA (0.5 W),压力下为 350 mA (1.75 W)。图 1-9 和 1-10 显示了树莓派零和树莓派零 w

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-10

树莓 Pi Zero(底部)和树莓 Pi Zero W(顶部)的底部

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-9

树莓派 Zero(底部)和树莓派 Zero W(顶部)的顶部

树莓派零度 W

Raspberry Pi Zero W 名称中的“W”是一个赠品,它通过 Zero 上的无线功能得到增强。它的定价是 10 美元。支持的无线标准有 802.11n 和蓝牙 4.1。与 Zero 一样,Zero W 没有有线以太网连接器,只有一个 Micro-USB 端口(另一个仅用于电源)。拥有 WIFI (WIFI 是 Wi-Fi 联盟的商标)接入大大增加了设备的通信多功能性。

哪个型号?

自然产生的问题是“买哪个型号?”答案很像买车——视情况而定。如果你正在寻找一台可以连接键盘、鼠标和显示器的廉价电脑,那么就买一台功能最强大的设备,比如 Raspberry Pi 3 型号 B+另一类涉及 AI(人工智能)或视频识别的项目是强大硬件的另一个例子。

为了建造一些必须在室外经受天气考验并拍摄鸟巢中鸟类的照片,那么带有 WIFI 连接的 Raspberry Pi Zero W 似乎是合适的。也许有其他项目根本不需要网络接入,在这些项目中,像零这样的最低价格是适用的。最好的消息是,你有很多低价的选择。

二、准备

虽然假设您已经开始使用 Raspberry Pi,但是在阅读本书的其余部分之前,您可能还需要做一些事情。例如,如果您通常使用笔记本电脑或台式计算机,您可能更喜欢从那里访问您的 Pi。

如果你计划完成本书中的大部分或全部项目,我强烈推荐你使用 Adafruit Pi T-Cobbler(本章后面会讲到)。该硬件以一种你可以在试验板上访问的方式断开 GPIO 线。

静态 IP 地址

标准的 Raspbian 映像提供了一个有能力的 Linux 系统,当插入网络时,它使用 DHCP(动态主机配置协议)自动为它分配一个 IP 地址。如果您想从台式机或笔记本电脑远程连接到它,那么 DHCP 分配的动态 IP 地址是有问题的。

有可下载的扫描网络的 Windows 程序。如果你用的是 Linux 或者 Mac 主机,可以用nmap扫描一下。下面是一个来自 Devuan Linux 的会话示例,使用了nmap命令。这里扫描的 IP 地址范围从 1 到 250:

root@devuan:~# nmap -sP 192.168.1.1-250

Starting Nmap 6.47 ( http://nmap.org ) at 2018-06-01 19:59 EDT
Nmap scan report for 192.168.1.1
Host is up (0.00026s latency).
MAC Address: C0:FF:D4:95:80:04 (Unknown)
Nmap scan report for 192.168.1.2
Host is up (0.044s latency).
MAC Address: 00:1B:A9:BD:79:02 (Brother Industries)
Nmap scan report for 192.168.1.77
Host is up (0.15s latency).
MAC Address: B8:27:EB:ED:48:B1 (Raspberry Pi Foundation)
Nmap scan report for 192.168.1.121
Host is up (0.00027s latency).
MAC Address: 40:6C:8F:11:B8:AE (Apple)
Nmap scan report for 192.168.1.80
Host is up.
Nmap done: 250 IP addresses (4 hosts up) scanned in 7.54 seconds
root@devuan:~#

在本例中,Raspberry Pi 被标识在 192.168.1.77 上,并带有完整的 MAC 地址(这些地址出现在报告“Raspberry Pi Foundation”的行上方)。虽然这种发现方法是可行的,但是它需要时间并且不方便。

如果您更喜欢将您的 Raspberry Pi 更改为使用静态 IP 地址,请参见第八章中的“有线以太网”部分以获取说明。

使用 SSH

如果您知道您的 Raspberry Pi 的 IP 地址,使用 nmap 发现了它,或者在您的 hosts 文件中注册了名称,您可以使用 SSH 登录到它。在本例中,我们以用户pi的身份从 Devuan Linux 机器登录到主机 192.168.1.77:

$ ssh pi@192.168.1.77
pi@192.168.1.77's password:
Linux raspberrypi 4.14.34-v7+ #1110 SMP Mon Apr 16 15:18:51 BST 2018 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law.
Last login: Fri Jun  1 20:07:24 2018 from 192.168.1.80
$

还可以使用scp命令将文件复制到 Raspberry Pi 或从 Raspberry Pi 复制文件。在 Raspberry Pi 上执行man scp操作,了解使用信息。

如果有 X-Window 服务器在运行,也可以在您的笔记本电脑/台式机上显示 X-Window 系统(X-Window)图形。(Windows 用户可以为此使用 Cygwin,从www.cygwin.com开始提供。)以 Linux 为例,首先配置 X-Window 服务器的安全性以允许请求。在这里,我将采用懒惰的方法,通过使用xhost命令允许所有主机(在不是 Pi 或者是另一个 Pi 的 Linux 机器上):

$ xhost +
access control disabled, clients can connect from any host
$

现在使用带-Y 选项的 ssh 登录到远程 Pi:

$ ssh pi@192.168.1.77 -Y
pi@192.168.1.77's password:
Warning: No xauth data; using fake authentication data for X11 forwarding.
Linux raspberrypi 4.14.34-v7+ #1110 SMP Mon Apr 16 15:18:51 BST 2018 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law.
Last login: Fri Jun  1 20:14:40 2018 from 192.168.1.80
$

从 Raspberry Pi 会话中,我们可以启动 xpdf,以便它在本地 Linux 机器上打开一个窗口:

$ xpdf &

如果失败,尝试在遥控器(pi)上导出一个显示变量,以通知软件 X-Window 服务器和屏幕的位置:

$ export DISPLAY=192.168.1.80:0

这里,我已经指定了 Devuan Linux 地址(或者,可以使用一个/etc/hosts名称),并指示 Raspberry Pi 使用 Linux 的显示号:0。我们在后台运行xpdf命令,这样我们就可以在当前的 SSH 会话中继续发出命令。同时,xpdf 窗口将在 Linux 屏幕上打开,而 xpdf 程序在 Raspberry Pi 上运行。

这并不能让您以图形方式访问 Pi 的桌面,但是对于开发人员来说,SSH 通常就足够了。如果你想远程图形访问树莓的桌面,一个选择是使用 VNC。

远程桌面

如果你已经在使用笔记本电脑或你最喜欢的台式电脑,你可以通过网络方便地访问你的 Raspberry Pi 的图形桌面。一旦 Raspberry Pi 的 VNC 服务器配置完毕,您所需要的就是在您的访问计算机上安装一个 VNC 客户端。这消除了连接到 Raspberry Pi 的键盘、鼠标和 HDMI 显示设备的需要。换句话说,您可以“无头”地运行 Pi。

让 VNC 工作需要一点设置。Raspbian Linux 已经采取措施让它变得简单。如果设置不正确,当您尝试登录时,VNC 查看器只会显示黑屏。

要使用 VNC,您必须安装桌面软件(GUI)。这也将使您更容易配置它。如果你安装了一个 Raspbian Lite 发行版,它将包括必要的桌面服务器软件。

启动图形桌面,然后从树莓图标(左上角),下拉菜单,选择“首选项”,并选择“树莓 Pi 配置。”这将弹出一个如图 2-1 所示的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-1

树莓 Pi 配置对话框。请注意 Boot:选项是如何选中单选按钮“To Desktop”的。

您可能已经将“引导”选项设置为“到桌面”,但是现在单击它。这将导致桌面软件在启动后启动,以便您可以通过 VNC 连接到它。

将桌面配置为重启后启动后,您还需要如图 2-2 所示,通过点击“接口”选项卡来启用 VNC 服务器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-2

VNC 在对话框的接口选项卡中启用

在“接口”对话框中,单击标有“启用”的 VNC 单选按钮单击右下角的确定保存您的设置。然后重启你的 Pi。请等待重新启动和图形桌面启动的时间。

vnc 查看器

为了访问 VNC 服务器,在客户端需要一个相应的 VNC 浏览器。一种解决方案是使用来自以下网站的免费 realvnc 查看器:

https://www.realvnc.com/en/connect/download/viewer/

从该网站上,您可以找到您最喜爱的台式机的下载链接,如图 2-3 所示。忽略网站对“VNC 连接”的引用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-3

VNC 浏览器的各种下载选项。忽略本页的“VNC 连接”信息。

根据您的桌面平台下载并安装。

当您启动查看器时,您会看到一个类似于图 2-4 所示的小对话框。一旦您成功登录,图标就会出现(如图所示)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-4

初始 VNC 浏览器对话框和一个以前使用的登录图标

成功连接并登录后,您应该会看到您的 Pi 桌面。图 2-5 举例说明了一个桌面窗口(在 Mac 上)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-5

在 Mac 上连接的树莓 Pi VNC 会话

如果您的 VNC 浏览器连接并似乎挂起,请耐心等待。VNC 屏幕更新的速度将取决于您的网络传输图形数据的能力。我发现我可以通过 WIFI 连接在 Pi 3 B+上使用 VNC,没有太多延迟。

虽然 VNC 的设施非常适合提供远程图形桌面访问,但不言而喻,其性能不适合观看视频或快速动作游戏。

黑色 VNC 屏幕

如果您将 Pi 的配置更改为引导至命令行模式(参见图 2-1 按钮“至 CLI”),而不是桌面模式,当您使用 VNC 查看器时,您将会遇到黑屏(参见图 2-6 )。选择命令行模式会导致桌面软件不运行,即使您可能启用了 VNC(图 2-2 “启用”)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-6

黑色的 VNC 浏览器屏幕表示桌面软件没有运行

要重新获得 VNC 桌面访问,您必须将配置更改为“至桌面”(图 2-1 ),然后重新启动。另一种方法是转到命令行(控制台或 ssh)并手动启动 X 服务器。从控制台登录,只需执行以下操作:

$ startx

从 ssh 会话中,您需要成为 root 用户(使用 sudo):

$ sudo -i
root@pi3bplus:~# startx

让服务器软件有时间启动后,VNC 查看器应该能够连接到您的桌面。当然,如果您注销 ssh 会话,服务器也会退出并终止桌面访问。

试验板设置

没有试验板和 Pi T-Cobbler 适配器也可以工作,但是你会发现有了高质量的试验板和适配器,实验会有趣得多。推荐的 T-Cobbler 可以从 Adafruit 购买,图 2-7 中有一个便宜的副本。

https://www.adafruit.com/product/2028

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-7

两个 Pi T-Cobbler 试验板适配器。请注意,来自中国(左)的单位需要一个电缆扭曲。推荐 Adafruit 单位(右)。

摘要

抛开这些细节不谈,本书的其余部分可以专注于 Raspberry Pi 必须提供的各种资源。Pi 提供了很多内容,让我们开始吧。

三、电源

系统中最常被忽视的部分之一往往是电源——至少在一切正常的情况下是如此。只有当事情出错时,电源才开始受到一些审查。

树莓派的主人需要给电源额外的尊重。与许多 AVR 级电路板不同,原始输入电压后接一个板载 5 V 调节器,Pi 期望其功率在输入端得到调节。Pi 确实包含板载调节器,但这些调节器可调节较低的电压(3.3 V 及更低)。

图 3-1 显示了相当脆弱的微型 USB 电源输入连接器。最初的型号 B 在连接器后面有一个大的圆形电容器,经常被抓住以发挥杠杆作用。避免这样做,因为许多人报告说是不小心“突然关掉”的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-1

微型 USB 电源输入

自最初的型号 B 以来的这些年里,已经生产了没有大电容器的其他型号。但它们都使用脆弱的微型 USB 电源输入,如图 3-1 所示。插入电源连接器时,请小心轻放。

计算能力

有时,电源是根据电压和功率处理能力(瓦特)来指定的。Pi 的 5 V 输入电压必须支持根据所用型号而变化的输入电流。表 3-1 总结了模型功率需求最小值。

表 3-1

Raspberry Pi 最低功耗要求汇总

|

模型

|

低值电流

|

电源

|
| — | — | — |
| Pi 模型 B | 700 毫安 | 3.5 瓦 |
| Pi 2 模型 B | 820 毫安 | 4.1 瓦 |
| Pi 3 模型 B | 1.34 A | 6.7 瓦 |
| Pi 3 型号 B+ | 1.13 A | 5.65 瓦 |
| 圆周率零和 W | 350 毫安 | 1.75 瓦 |

让我们以瓦特为单位验证 Raspberry Pi 3 型号的电源数字(这不包括任何添加的外围设备):

)

5.65 W 代表最低要求,因此我们应该额外超额配置 50%:

)

额外的 50%产生大约 8.5 W 的功率需求

小费

为您的电源预留 50%的额外容量。电源变坏可能会导致损坏或其他一些问题。Pi 的一个常见电源相关问题是 SD 卡上的数据丢失。

当前需求

由于正在寻找的电源产生一个输出电压(5 V),您可能会看到具有广告的电流额定值而不是功率的电源适配器。在这种情况下,您可以简单地考虑 50%的额外电流(对于 Pi 3):

)

为了仔细检查我们的工作,让我们看看这是否与我们之前计算的额定功率一致:

)

结果一致。根据这一评估,您可以得出这样的结论:您至少需要一个 5 V 电源来产生以下结果之一:

  • 8.475 瓦或以上

  • 1.695 A 或更高(忽略外设)

能够满足任一要求的电源应该足够了。然而,你应该意识到,并不是所有的广告收视率是他们所看到的。廉价的供应品往往不能满足他们自己的要求,因此必须考虑额外的利润。

外围电源

功耗预算中必须考虑每个额外的耗电电路,尤其是 USB 外设。根据其类型,插入 USB 2 端口的给定 USB 外设可以期望高达 500 mA 的电流,假设它可以获得它。

众所周知,无线适配器非常耗电。使用时不要忘记键盘和鼠标,因为它们也会增加功耗。如果您已经连接了一个 RS-232 电平转换器电路(可能使用 MAX232CPE),您也应该在 3.3 V 电源预算中为这一小部分做预算。这将间接增加你的+5 V 预算,因为 3.3 V 稳压器是由它供电。(USB 端口使用+5 V 电源。)任何从你的树莓派汲取能量的东西都应该被记录下来。

3.3 伏电源

Raspberry Pi 的输入是调节后的 5 伏电压。但是 Pi 本身依赖于 3.3 V 电源,该电源由板载调节器提供。板载稳压器可为其他支持 IC 提供额外的较低电压,具体取决于型号。由于 3.3 V 电源是从 5 V 输入电源间接获得的,因此原始型号 B 的最大过量电流为 50mA;Pi 耗尽了调节器的剩余容量。

规划设计时,需要仔细预算 3.3 V 电源。每个 GPIO 输出引脚从该电源获得额外的 3 至 16 mA 电流,具体取决于其使用方式。电流预算较高的项目可能需要包括自己的 3.3 V 稳压器,从输入 5 V 输入供电。

供电 USB 集线器

如果您的电源预算因 USB 外设而捉襟见肘,您可能会考虑使用由供电的 USB 集线器。这样,集线器而不是你的树莓派为下游外设提供必要的电力。

并非所有的 USB 集线器都能与(Raspbian) Linux 兼容。内核需要与连接的 USB 集线器协作,因此软件支持至关重要。以下网页列出了已知工作正常的 USB 集线器:

http://elinux.org/RPi_Powered_USB_Hubs

电源适配器

对于树莓派的高电流四核型号,您只需购买一个合适的适配器。不要在你的高性能装备上浪费廉价或劣质的供应品。

对于低功耗 Pi,如旧型号 B、Zero 或 Zero W,您可能会尝试一些更便宜的解决方案。让我们检查一些选项。

不合适的供应

图 3-2 所示的例子是在易贝花 1.18 美元购买的,免运费(见即将发布的假货警告)。因此,使用它是很有诱惑力的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-2

型号 A1265 苹果适配器

这是一个具有以下额定值的适配器/充电器:

  • 型号 : A1265

  • 输入:100–240 伏交流电

  • 输出 : 5 V,1 A

当插上电源时,Raspberry Pi 的电源 LED 立即亮起,这是适配器(相对于充电器)的一个好迹象。电源的快速上升时间导致成功的上电复位。测量电压时,在+5 V 电源上的读数为+4.88 V。虽然不理想,但在可接受的电压范围内。(电压应在+5V-4.75 和 5.25 V 的 10%以内。)

当没有使用 HDMI 显卡时(使用串行控制台、SSH 或 VNC),苹果设备似乎工作得相当好。然而,我发现当使用 HDMI 并且 GPU 有工作要做时(例如,在桌面上移动一个窗口),系统往往会卡住。这清楚地表明适配器没有完全输送或调节得足够好。

警告

非常小心假冒的苹果充电器/适配器。Raspberry Pi 基金会已经看到被这些损坏的返回单元。有关视频和更多信息,请参见 www.raspberrypi.org/archives/2151

电子书适配器

一些人报告说使用电子书电源适配器是成功的。我也成功使用过 2 A 的 Kobo 充电器。

电源质量

虽然低价购买 USB 电源适配器是可能的,但更明智的做法是花更多的钱购买高质量的设备。为了节省几美元而扔掉你的树莓派或者经历随机的失败是不值得的。

如果你没有示波器,你将无法检查你的电源电流是干净还是肮脏。一个好的电源适配器比示波器便宜。不稳定/有噪音的电源会导致各种不明显的间歇性问题。

一个很好的开始是简单地谷歌“推荐电源树莓派。”做你的研究,并把你的 USB 外设包括在电力预算中。请记住,无线 USB 适配器消耗大量电流,高达 500 mA。

注意

一项随机互联网调查显示,无线 USB 适配器的功耗范围为 330 mA 至 480 mA。

电压测试

如果您有一个 DMM(数字万用表),在 Pi 上电后进行测试是值得的。如果您遇到问题,这可能是您应该做的第一件事。

按照以下步骤执行电压测试(假设现在标准化的 40 引脚排线用于引脚编号):

  1. 将 Raspberry Pi 的微型 USB 端口插入电源适配器的 USB 端口。

  2. 插入电源适配器。

  3. 测量 P1-02 (+5 V)和 P1-39 或 P1-06(接地)间的电压:预期+4.75 至+5.25 V

  4. 测量 P1-01 (+3.3 V)和 P1-39 或 P1-06(接地)间的电压:预期+3.135 至+3.465 V

警告

小心你的万用表表笔绕过 P1 的针脚。特别注意不要将+5 V 短接到+3.3 V 引脚,哪怕是几分之一秒。这样做会使你的 Pi 崩溃!为了安全起见,请使用杜邦线。

图 3-3 显示了在 Raspberry Pi 3 型号 B+上测试+5 V 电源。请注意+5V 电源(P1-02 引脚)的接头带上使用了红色杜邦导线。一根蓝色的杜邦线连接到地线。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-3

测试 Raspberry Pi 3 型号 B+的+5V 电源

图 3-4 同样显示了从 Pi 的板载调节器测量调节后的 3.3 V 电源。图中红色的杜邦线连接到出现调节输出的 P1-01。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-4

测试 Raspberry Pi 3 B+的+3.3 V 电源

附录 B 列出了 ATX 电源标准电压等级,包括+5±0.25V 和+3.3±0.165V,作为比较指南。

电池电量

由于 Raspberry Pi 的尺寸较小,因此可能需要使用电池电源来运行它(尤其是对于零 W 或零 W)。这样做需要一个监管机构和一些仔细的规划。为了满足 Raspberry Pi 的要求,您必须制定功耗预算。一旦你知道你的最大电流需求,你就可以充实其余的。以下示例假设需要 1 A。

要求

为了清楚起见,让我们列出电池电源必须满足的功率要求:

  • 电压 5 V,在 0.25 V 范围内

  • 电流 1 A

净空高度

最简单的方法是使用线性 LM7805 作为 5 V 调节器。但是这种方法也有缺点:

  • 输入电压之上必须有一定的裕量(约 2 V)。

  • 允许过多的净空会增加调节器的功耗,导致浪费电池电量。

  • 也可能导致较低的最大输出电流。

您的电池应提供最低 5+2 V (7 V)的输入。调节器的任何较低输入电压都会导致调节器“下降”并降至 5 V 以下,显然,6 V 电池输入是不行的。

LM7805 法规

图 3-5 显示了一个使用 LM7805 线性稳压器的非常简单的电池电路。电池电源进入中的 V ,从右边的引脚 1 输出+5 V 的调节输出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-5

使用 LM7805 稳压芯片的电池稳压电路

8.4 V 电池电源可以由 7 个 NiCad 电池串联而成,每个电池产生 1.2 V 电压,8.4 V 输入允许电池电压降至 7 V,不会超过 2 V 的最小裕量。

根据所选的确切 7805 调节器器件,典型的散热参数设置可能如下:

  • 输入电压:7–25v

  • 输出电压 : 1.5 A(散热)

  • 工作温度:125 摄氏度

务必在调节器上使用散热器,以便将热能散发到周围空气中。没有一个,调节器可以进入热关断状态,减少电流以防止损坏。发生这种情况时,输出电压将降至+5 V 以下。

请记住,电池消耗的功率大于负载接收的功率。如果我们假设 Raspberry Pi Zero 的功耗为 350 mA,则通过调节器从电池中汲取的电流最少也有 350 mA(这可能略高)。要知道,由于输入电压较高,稳压器正在消耗额外的能量。调节器(P R )和负载(P L )消耗的总功率如下:

)

调节器必须消除输入和输出电压之间的差异(1.19 W)。这些额外的能量加热调节器,并通过散热器散发出去。由于这个问题,设计者避免在线性调节器电路上使用高输入电压。

如果调节器在 7 V(输入)时的最大额定电流为 1.5 A,则调节器的最大功率约为 10.5 W,如果我们施加 8.4 V 而不是 7V 的输入电压,则我们可以得出 5 V 最大电流:

)

由此我们发现,8.4 V 电池稳压器电路可以在输出端提供最大 1.25 A 的电流,而不会超过稳压器的额定功率。用 8.4 V 乘以 1.25 A 来说服自己这等于 10.5 W。

DC-DC 降压转换器

例如,如果应用是为数据采集而设计的,则希望它在给定的一组电池或充电周期内运行尽可能长的时间。开关调节器可能比线性调节器更合适,因为其效率更高。

图 3-6 显示了一个非常小的 pcb,长度约为 1.5 SD 卡。这个单位是从易贝购买的 1.40 美元,免费送货。以这样的价格,你为什么要建一个呢?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-6

DC-DC 降压转换器

它们使用起来也很简单。转换器提供+和–输入连接以及+和–输出连接。在一个电压下输入功率,在另一个电压下输出功率。

但是不要马上把它接到你的树莓 Pi 上,直到你校准了输出电压。虽然它可能来预校准为 5 V,最好不要指望它。如果该装置产生更高的电压,您可能会烧坏 Pi。

通过 pcb 上的多匝调整电位计,可以轻松调整调节后的输出电压。当你读你的数字万用表的时候调整锅。

表 3-2 提供了我购买的设备的规格,供您参考。请注意宽范围的输入电压,以及它可以在低至–40°c 的温度下工作的事实,宽范围的输入电压和高达 3 A 的电流显然使这款器件非常适合连接到电压可能变化很大的太阳能电池板。

表 3-2

DC-DC 降压转换器规格

|

参数

|

福建话

|

最大

|

单位

|

参数

|

福建话

|

最大

|

单位

|
| — | — | — | — | — | — | — | — |
| 输入电压 | Four | Thirty-five | 伏特 | 输出脉动 |   | Thirty point nine | 妈 |
| 输入电流 |   |  3.0 | 安培数 | 负载调节 | ±0.5 | % |   |
| 输出电压 | One point two three | Thirty | 伏特 | 电压调整 | ±2.5 | % |   |
| 换能效率 |   | Ninety-two | % | 工作温度 | –40 | +85 | C |
| 开关频率 |   | One hundred and fifty | 千赫 | PCB 尺寸 |   | 45×20×12 | 毫米 |
|   |   |   |   | 净重 |   | Ten | g |

该规范声称高达 92%的转换效率。在输入端使用 15 V 电压,我进行了自己的测量实验。将装置调整到在输出端产生 5.1 V,读取表 3-3 中所示的读数。

表 3-3

从实验中获得的读数

|

参数

|

投入

|

输出

|

单位

|
| — | — | — | — |
| 电压 | Fifteen point one three | Five point one | 伏特 |
| 目前的 | Zero point one nine | Zero point four one | 安培数 |
| 电源 | Two point eight seven | Two point zero nine | 瓦茨 |

从表中我们可以看到输入端使用了更多的功率(2.87 W)。输出端使用的功率为 2.09 W。效率就变成了除法问题:

)

由此我们可以得出结论,测得的转换效率约为 72.8%。

如果使用 LM7805 调节器,我们能做得多好?以下是最好的情况估计,因为我没有这种情况下的实际电流读数。但我们知道,从调节器流出的电流至少有同样多的电流必须流入调节器(可能更多)。那么,LM7805 调节器理论上能做到的最好水平是什么呢?让我们在 5.10 V 下对 Raspberry Pi 应用同样的 410 mA 电流消耗,如表 3-4 所示。(这是在没有使用 HDMI 输出的情况下进行的。)

表 3-4

假设的 LM7805 功耗

|

参数

|

投入

|

输出

|

单位

|
| — | — | — | — |
| 电压 | Seven point one | Five point one | 伏特 |
| 目前的 | Zero point four one | Zero point four one | 安培数 |
| 电源 | Two point nine one | Two point zero nine | 瓦茨 |

这种最佳情况下的功效相当于:

)

LM7805 调节器的绝对最佳效率为 71.8%。但这是在其最佳输入电压下实现的。将输入电压提高至 12 V 会导致功耗大幅上升,从而产生 42.5%的效率(这一计算留给读者作为练习)。试图在 15.13 V 下操作 LM7805 调节器,就像我们对降压转换器所做的那样,会导致效率下降到 33.7%以下。显然,降压转换器在转换来自更高电压源的功率时效率要高得多。

电力不足的迹象

在论坛中,有报道称 ping 有时在桌面上无法工作(使用 HDMI),但在控制台模式下可以正常工作。此外,我发现如果移动桌面窗口,它们会冻结(HDMI)。例如,当您开始移动终端窗口时,移动会在中途停止,就好像鼠标停止工作一样。

这些都是树莓派电力不足的迹象。GPU 在活动时消耗更多的功率,执行加速图形。桌面冻结(GPU 饥饿)或网络接口失败(ping)。可能还有其他与 HDMI 活动相关的症状。

另一个被报道的问题是在启动后不久,树莓 Pi 被重置。随着内核启动,主板开始消耗更多的功率,这可能导致 Pi 被耗尽。 3

如果您在插入 USB 设备时失去了以太网连接,这也可能是电量不足的迹象。 4

虽然看起来 1 A 电源应该足以提供 700 mA 的 Raspberry Pi,但使用 2 A 电源会更好。许多电源根本达不到其宣传的额定功率。

微型 USB 电缆是另一个值得怀疑的东西。有些是用细导体制造的,这可能导致显著的电压降。如前面“电压测试”一节所示测量电压可能有助于诊断。尝试更高质量的电缆,看看是否有所改善。

摘要

本章简要介绍了您在使用 Raspberry Pi 时可能会遇到的一些电源问题。现在,您应该准备好做出关于电源适配器或电池供电选项的明智选择。

四、LED 和标题条

本章介绍 Raspberry Pi LEDs 和标题条。这些是从 Pi 到外部世界的重要接口。您可能希望为表 4-5 使用书签,该表概述了现代 P1 上的 GPIO(通用输入/输出)引脚。

状态指示灯

根据型号的不同,Raspberry Pi 有不同组的 LED 指示灯。表 4-1 提供了总体参考图表。 5 树莓 Pi Zero 和 Zero W 已经被混为一谈。

表 4-1

树莓 Pi LED 指示灯表

|

发光二极管

|

颜色

|

A

|

A+

|

B

|

B+

|

π2

|

圆周率 3

|

零/W

|
| — | — | — | — | — | — | — | — | — |
| okcat | 格林(姓氏);绿色的 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
| 压水反应堆 | 红色 | 是 | 是 | 是 | 是 | 是 | 是 | 不 |
| 联邦快递 | 格林(姓氏);绿色的 | 不 | 不 | 是 | 是 | 插口 | 插口 | 不 |
| 环 | 格林(姓氏);绿色的 | 不 | 不 | 是 | 插口 | 插口 | 插口 | 不 |
| 10M/100 | 黄色 | 不 | 不 | 是 | 不 | 不 | 不 | 不 |

正常/动作指示灯

这是 SD(安全数字)卡活动指示器。

电源 LED 灯

在最初的型号 B 和随后的型号 A 上,该 LED 连接到 3.3 V 调节器,并指示调节器有电。在以后的型号中,当输入功率低于 4.63 V 时,该 LED 将闪烁。

FDX 领导

这表明网络是全双工连接的。

链接 LED

链路指示灯指示 LAN(局域网)上是否有网络活动。

10M/100 LED

当网络以 100 Mbit/s 的速度运行时,这个黄色的 LED 会显示出来。

原始标题 P1

最初的 Raspberry Pi 型号 A 和 B 使用标识为 P1 的 13 x 2 排线,露出 GPIO 引脚。这包括 I2C、SPI 和 UART 外设以及电源连接。表 4-2 列出了这种老式收割台的连接。

表 4-2

原装 Pi GPIO 接头连接器 P1(俯视图)

|

左下角

|

|

左上角

|
| — | — | — |
| 3.3 V 电源 | P1-01 | P1-02 | 5 V 电源 |
| GPIO 0 (I2C0 SDA)+ R1=1.8k | P1-03 | P1-04 | 5 V 电源 |
| GPIO 1 (I2C0 SCL)+ R2=1.8k | P1-05 | P1-06 | 地面 |
| GPIO 4 (GPCLK 0/1 线) | P1-07 | P1-08 | GPIO 14 (TXD) |
| 地面 | P1-09 | P1-10 | GPIO 15 (RXD) |
| GPIO 17 | P1-11 | P1-12 | GPIO 18 (PCM_CLK) |
| GPIO 21 (PCM_DOUT) | P1-13 | P1-14 | 地面 |
| GPIO 22 | P1-15 | P1-16 | GPIO 23 |
| 3.3 V 电源 | P1-17 | P1-18 | GPIO 24 |
| GPIO 10 (MOSI) | P1-19 | P1-20 | 地面 |
| GPIO 9(美索不达米亚) | P1-21 | P1-22 | GPIO 25 |
| GPIO 11 (SCKL) | P1-23 | P1-24 | GPIO 8 (CE0) |
| 地面 | P1-25 | P1-26 | GPIO 7 (CE1) |

修订的 GPIO 标题 P1

表 4-3 显示了对原 a 13 x 2 顶梁 P1 的修改。请注意,I2C 外设从 I2C0 变为 I2C1。

表 4-3

修改后的原始 Pi GPIO 接头连接器 P1(俯视图)

|

左下角

|

|

左上角

|
| — | — | — |
| 3.3 V 电源,最大 50 mA | P1-01 | P1-02 | 5 V 电源 |
| GPIO 2 (I2C1 SDA1)+R1=1.8k | P1-03 | P1-04 | 5 V 电源 |
| GPIO 3 (I2C1 SCL1)+R2=1.8k | P1-05 | P1-06 | 地面 |
| GPIO 4 (GPCLK 0/1 线) | P1-07 | P1-08 | GPIO 14 (TXD0) |
| 接地 | P1-09 | P1-10 | GPIO 15 (RXD0) |
| GPIO 17(第 0 代) | P1-11 | P1-12 | GPIO 18(PCM _ CLK/EN1) |
| GPIO 27(EN2) | P1-13 | P1-14 | 接地 |
| GPIO 22(第三代) | P1-15 | P1-16 | GPIO 23(第四代) |
| 3.3 伏电源,最大 50 毫安 | P1-17 | P1-18 | GPIO 24(第五代) |
| GPIO 10 (SPI_MOSI) | P1-19 | P1-20 | 接地 |
| GPIO 9 (SPI_MISO) | P1-21 | P1-22 | GPIO 25(第六代)) |
| GPIO 11 (SPI_SCKL) | P1-23 | P1-24 | GPIO 8 (CE0_N) |
| 地面 | P1-25 | P1-26 | GPIO 7 (CE1_N) |

在表 4-3 中有 P1 连接器的旧型号也有显示在 vin 表 4-4 中的 P5 接头。

表 4-4

版本 2.0 P5 集管(俯视图)

|

左下角

|

|

左上角

|
| — | — | — |
| (方形)5 V | P5-01 | P5-02 | 3.3 伏,50 毫安 |
| GPIO 28 | P5-03 | P5-04 | GPIO 29 |
| GPIO 30 | P5-05 | P5-06 | GPIO 31 |
| 地面 | P5-07 | P5-08 | 地面 |

修订版 2.0 GPIO 头 P1

从型号 B+和 A+开始,GPIO 接头被标准化为使用 40 针(20 x 2)接头。GPIO 引脚 ID_SD (GPIO0,P1-27)和 ID_SC (GPIO1,P1-28)保留用于板(HAT)检测/识别。这些引脚用于读取 HAT 中的 24Cxx 型 3.3 V 16 位可寻址 I2C EEPROM。

表 4-5

标准化 40 引脚 Raspberry Pi GPIO 接头(所有现代设备)

|

左下角

|

|

左上角

|
| — | — | — |
| 3.3 V 电源,最大 50 mA | P1-01 | P1-02 | 5 V 电源 |
| GPIO 2 (I2C1 SDA1)+R1=1.8k | P1-03 | P1-04 | 5 V 电源 |
| GPIO 3 (I2C1 SCL1)+R2=1.8k | P1-05 | P1-06 | 地面 |
| GPIO 4 (GPCLK 0/1 线) | P1-07 | P1-08 | GPIO 14 (TXD0) |
| 接地 | P1-09 | P1-10 | GPIO 15 (RXD0) |
| GPIO 17(第 0 代) | P1-11 | P1-12 | GPIO 18(PCM _ CLK/EN1) |
| GPIO 27(EN2) | P1-13 | P1-14 | 地面 |
| GPIO 22(第三代) | P1-15 | P1-16 | GPIO 23(第四代) |
| 3.3 V 电源 | P1-17 | P1-18 | GPIO 24(第五代) |
| GPIO 10 (SPI_MOSI) | P1-19 | P1-20 | 接地 |
| GPIO 9 (SPI_MISO) | P1-21 | P1-22 | GPIO 25(第六代)) |
| GPIO 11 (SPI_SCKL) | P1-23 | P1-24 | GPIO 8 (CE0_N) |
| 接地 | P1-25 | P1-26 | GPIO 7 (CE1_N) |
| GPIO0 (ID_SD) | P1-27 | P1-28 | GPIO1 (ID_SC) |
| GPIO5 | P1-29 | P1-30 | 地面 |
| GPIO6 | P1-31 | P1-32 | GPIO12 (PWM0) |
| GPIO13 (PWM1) | P1-33 | P1-34 | 地面 |
| GPIO19(味噌) | P1-35 | P1-36 | GPIO16 |
| GPIO26 | P1-37 | P1-38 | GPIO20 (MOSI) |
| 接地 | P1-39 | P1-40 | GPIO21 (SCLK) |

安全模式

Raspbian Linux 曾经支持一个安全模式,但是在 2014 年 3 月已经被移除。然而,NOOBS 可能仍然支持它。 6

引脚 P1-05,GPIO3 专用于引导序列。将此引脚接地或将其跳线至 P1-06(地)会导致引导序列使用安全模式引导程序。如果引脚用于其他目的,您可以通过配置参数来防止这种情况:

avoid_safe_mode=1

当你使用电源插头(如 P1-01 或 P1-02)时,请小心不要意外接地。如果您的 Pi 无法响应安全模式,可能是由于制造错误或缺少固件支持。看到这条消息:

www.raspberrypi.org/phpBB3/viewtopic.php?f=29&t=12007

当跳线调用安全模式时,config.txt文件被忽略,除了avoid_safe_mode参数。此外,这种模式会覆盖内核命令行,并加载kernel_emergency.img。如果该文件不可用,则使用kernel.img代替。

此功能的目的是允许用户克服配置问题,而不必在另一台机器上编辑 SD 卡来进行纠正。被引导的紧急内核是一个 BusyBox 映像,挂载了/boot以便进行调整。此外,如果有必要,可以修复或挂载/dev/mmcblk0p2根文件系统分区。

逻辑电平

用于 GPIO 引脚的逻辑电平为 3.3 V,并且不能容忍 5 V TTL 逻辑电平。Raspberry Pi pcb 设计用于插入 pcb 扩展卡(HATs ),或与 3.3 V 逻辑精心接口。输入电压参数 V IL 和 V IH 在第十一章中有更详细的描述。

复位时的 GPIO 配置

Raspberry Pi GPIO 引脚可以通过软件控制配置为输入或输出,具有上拉或下拉电阻,或者承担一些特殊的外设功能。复位后,只有 GPIO14 和 15 被分配了特殊功能(UART)。然而,启动后,软件甚至可以根据需要重新配置 UART 引脚。

当 GPIO 引脚配置为输出时,它可以驱动的电流量有限(源电流或吸电流)。默认情况下,当引脚配置为输出时,每个 P1 GPIO 配置为使用 8 mA 驱动器。第十一章有更多关于这个的信息。

注意

Raspbian 默认单线总线是 GPIO 4 (GPCLK0)引脚 P1-07。

单线驱动器

单线驱动器的默认 GPIO 引脚过去被硬编码为使用 GPIO4。现在可以进行配置了。

摘要

本章介绍了树莓 Pi 上的 LED 指示灯和标题条。虽然所有新的 Pi 现在都使用 20 x 2 GPIO 头条,但最初的头是为了参考目的而描述的。一个 Pi 永远不会老到不能工作!

五、同步动态随机访问内存

最初的 Model B Rev 2.0 Raspberry Pi 有 512 MB 的 SDRAM(同步动态随机访问存储器),而旧版本和 Model A 有 256 MB。现代的 Pi 现在配备了 1 GB 的内存,除了 Raspberry Pi Zero,它有 512 MB。相比之下,AVR 类 ATmega328P 有 2 KB 的静态 RAM!

一般 Pi 开发人员并不太关心内存硬件,因为操作系统为管理程序使用提供了丰富的工具。然而,让我们研究一些有用的 Raspbian Linux 接口,它们告诉我们内存是如何被利用的。您还将研究如何直接从您的 Linux 应用访问内存映射的 ARM 外设。

/proc/meminfo

伪文件/proc/meminfo为我们提供了关于内存使用的信息。这些信息因架构和内核使用的编译选项而有所不同。让我们研究一个由运行 Raspbian Linux 的 Raspberry Pi 3 Model B+生成的示例:

$ cat /proc/meminfo
MemTotal:         949452 kB
MemFree:          631676 kB
MemAvailable:     756320 kB
Buffers:           20532 kB
Cached:           158004 kB
SwapCached:            0 kB
Active:           170108 kB
Inactive:         107020 kB
Active(anon):      99576 kB

Inactive(anon):    13172 kB
Active(file):      70532 kB
Inactive(file):    93848 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:        102396 kB
SwapFree:         102396 kB
Dirty:                40 kB
Writeback:             0 kB
AnonPages:         98596 kB
Mapped:            79016 kB
Shmem:             14152 kB
Slab:              22444 kB
SReclaimable:      10640 kB
SUnreclaim:        11804 kB
KernelStack:        1768 kB
PageTables:         3376 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      577120 kB
Committed_AS:     809528 kB
VmallocTotal:    1114112 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
CmaTotal:           8192 kB
CmaFree:            6796 kB

所有显示的内存值右边都有单位 KB ,表示千(1,024)字节。这些报告值在网上有详细描述。 7 但是让我们总结一些有趣的价值。

记忆总数

MemTotal行表示可用内存的总量,减去一些保留的二进制区域。注意,分配给 GPU 的内存不计入MemTotal。一些所有者可能会选择分配最少 16 MB 给 GPU,以便在不使用图形桌面时获得更多可用内存。默认分配 64 MB。

如果进一步细分,考虑分配给 GPU 的内存,我们会发现大约有 32 MB 的内存未计算在内,如表 5-1 所示。由于 GPU 寻址内存的方式不同,这将因 Pi 型号和分配的 GPU 内存而异。

表 5-1

GPU 和主内存故障

|

记忆

|

B 型

|

评论

|
| — | — | — |
| 同步动态随机访问内存(Synchronous Dynamic random access memory) | 1,048,576 KB | 配备硬件的存储器 |
| 记忆总数 | 949,452 KB | /proc/meminfo |
| 差异 | 99,124 KB | 剩余内存 |
| gpu_mem | 65,536 KB | /boot/config.txt(默认情况下 gpu_mem=64) |
| 差异 | 33,588 KB | 下落不明 |

记忆自由

MemFree通常表示英特尔 x86 平台上以千字节为单位的LowFree + HighFree内存的总和。对于 ARM,这只是表示用户空间程序可用的内存量。

缓冲

该值表示内核中用于原始磁盘块等的临时缓冲区。这个值不应该比大约 20 MB 大太多。 8

藏起

该值表示已缓存的读取文件内容(页面缓存)。这不包括为SwapCached报告的内容。

交换的

显示的SwapCached值代表换出的内存,现在又换回。为了提高效率,这些内存页面仍然由交换磁盘空间表示,以备再次需要。该值被报告为零的事实是一个可喜的迹象,表明没有发生交换或者交换不再相关。

活跃的

Active内存值表示最近使用的不回收的内存,除非绝对必要。

不活动的

该值表示不活动的内存,并且可能在需要内存时被回收。

活动(匿名)

该值表示不是由文件备份且处于活动状态的内存。除非绝对必要,否则不会回收活动内存。

不活动(匿名)

该值表示没有文件备份且不活动的内存。如果需要内存,非活动内存可以回收。

活动(文件)

该值表示活动的文件支持内存。只有绝对需要时,才会回收活动内存。

不活动(文件)

该值表示由文件支持的非活动内存。当需要内存时,非活动内存可以回收。

不可战胜的

此数量反映了无法回收的内存总量。例如,被锁定的内存不能被回收。

锁定的

该值报告锁定的内存量。

交换总量

该值以千字节为单位报告可用的交换空间总量。

交换自由

该值以千字节为单位报告剩余的可用交换空间量。

肮脏的

该值表示已修改并等待写入磁盘的内存的千字节数。

回复

该值报告写回磁盘的内存量(以千字节为单位)。

匿名页面

这表示映射到用户空间的非文件支持的内存页面。

计划

该值报告已经映射到内存中的文件。这可能包括库代码。

施姆

这个参数似乎没有被很好地记录。但是,它表示以千字节为单位的共享内存量。

平板

这个参数被称为“内核数据结构缓存”

可索赔的

这个参数被描述为“可能被回收的Slab的一部分,例如缓存。”

阳光回收

这个参数被描述为“在内存压力下不能被回收的部分”

内核堆栈

该值报告内核堆栈使用的内存。

页面表格

该值报告内核中使用的页表所需的内存量。显然,随着需要管理的内存越来越多,就有更多的内存专用于页表。

NFS _ 不稳定

该值表示“发送到服务器,但尚未提交到稳定存储的 NFS 页面”该示例数据表明没有使用 NFS。

活力

这将报告用于“块设备反弹缓冲区”的内存

写回 Tmp

此参数报告 FUSE 用于“临时写回缓冲区”的内存

承诺限制

文件指出:

根据过量使用比率(vm.overcommit_ratio),这是系统上当前可分配的内存总量。只有在启用了严格过量使用会计时,才遵守该限制(模式 2vm.overcommit_memory)。CommitLimit通过以下公式计算:

)

例如,一个具有 1 GB 物理 RAM 和 7 GB 交换空间且vm.overcommit_ratio为 30 的系统将产生 7.3 GB 的CommitLimit。有关更多细节,请参见vm/overcommitaccounting中的内存过量使用文档。

该公式可以写成如下形式:

)

此公式的元素描述如下:

  • C 是超量承诺限制。

  • R 是可用的物理 RAM(MemTotal)。

  • S 是可用的交换空间(SwapTotal)。

  • r 是超量承诺比率百分比(以分数表示)。

/proc/meminfo数据中未报告过量使用比率 r。为了获得这个比率,我们查阅另一个伪文件。这个例子取自 2.0 版的模型 B,但它似乎是所有 Pi 的通用值:

$ cat /proc/sys/vm/overcommit_ratio
50

值 50 应解释为 r = 0.50 (50%)。

使用超量使用公式,可以计算出可用交换空间的 S 值:

)

用户可以通过在伪文件中写入一个值来配置超量使用比率。本例将比率更改为 35%:

$ sudo -i
# echo 35 >/proc/sys/vm/overcommit_ratio
# cat /proc/sys/vm/overcommit_ratio
35

已提交 _ 作为

该参数描述如下:

系统上当前分配的内存量。提交的内存是由进程分配的所有内存的总和,即使它还没有被它们“使用”。malloc()的 1 GB 内存,但只使用了 300 MB 的进程只会显示为使用了 300 MB 的内存,即使它有为整个 1 GB 分配的地址空间。这 1 GB 是由虚拟机“提交”的内存,分配应用可以随时使用。在系统上启用严格过量使用时(vm.overcommit_memory中的模式 2),将不允许超过CommitLimit(详见上文)的分配。如果需要保证在成功分配内存后,进程不会因为缺少内存而失败,这是非常有用的。

VmallocTotal

这表示分配的虚拟内存地址空间的总量。

虚拟专用

这是正在使用的虚拟内存量,以千字节为单位。

VmallocChunk

该值报告了vmalloc区域的最大大小,单位为千字节。

物理内存

通常,物理内存不是应用程序员关心的问题,因为操作系统及其驱动程序提供了一种抽象的、通常是可移植的访问方式。然而,如果没有这种支持,则需要直接访问 PWM 控制器等外设。

图 5-1 展示了原始 Pi 模型 B 在 Raspberry Pi 上使用的物理寻址(为简单起见)。SDRAM 从物理地址零开始,直到 ARM/GPU 分割点(第十七章定义了分割点)。在 Pi Model B 上,ARM 外设从地址 0x20000000 开始映射到物理存储器,这个起始地址是程序员非常感兴趣的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5-1

物理内存布局

对于现代 Pi 模型,我们不能假定起始外设地址为常数。在本书的后面,将展示一种在运行时确定该值的方法。

在标有外设的区域,表 5-2 中所示的偏移量和地址是我们感兴趣的。

表 5-2

Raspberry Pi 型的外设失调

|

外围的

|

抵消

|

地址

|

描述

|

c 偏移宏

|
| — | — | — | — | — |
| 基地 | 0x00000000 | 0x20000000 | 起始地址 | bcm 2708 _ 基本周长 |
| PADS_GPIO | 0x00100000 | 0x20100000 | 衬垫底座 | PADS_GPIO_BASE |
| GPIO 00…27 | 0x0010002C | 0x2010002C | GPIO 00…27 个垫子 | PADS_GPIO_00_27 |
| GPIO 28…45 | 0x00100030 | 0x20100030 | GPIO 28…45 个垫子 | PADS_GPIO_28_45 |
| GPIO 46…53 | 0x00100034 | 0x20100034 | GPIO 46…53 个垫子 | PADS_GPIO_46_53 |
| 时钟 | 0x00101000 | 0x20101000 | 时钟寄存器 | CLK_BASE |
| GPIO | 0x00200000 | 0x20200000 | GPIO 寄存器 | GPIO_BASE |
| gppu | 0x00200025 | 0x20200025 | 上拉使能 |   |
| gppuclk 0 | 0x00200026 | 0x20200026 | 上拉时钟 0 |   |
| gppuclk 1 | 0x00200027 | 0x20200027 | 上拉时钟 1 |   |
| 脉宽调制 | 0x0020C000 | 0x2020C000 | PWM 寄存器 | PWM_BASE |

存储器交换

要在 Linux 下获得对物理内存的访问,您需要使用/dev/mem字符设备和mmap(2)系统调用。这里显示的是/dev/mem节点:

$ ls -l /dev/mem
crw-r----- 1 root kmem 1, 1 Jun  5 20:24 /dev/mem

从显示的所有权信息来看,很明显您需要 root 权限来访问它。这是明智的,因为一个进程可能会导致对物理内存的直接访问。在应用如何处理这种内存访问时要小心。

这里显示了mmap(2)系统调用 API:

#include <sys/mman.h>

void ∗mmap(
  void     ∗addr,    /∗Address to use ∗/
  size_t    length,   /∗Number of bytes to access ∗/
  int       prot,     /∗Memory protection ∗/
  int       flags,    /∗Option flags ∗/
  int       fd,       /∗Opened file descriptor ∗/
  off_t     offset    /∗Starting off set ∗/
) ;

与其查看这个有点复杂的系统调用可用的所有选项和标志,不如让我们看看我们在下面的代码中使用的选项和标志:

static char ∗map = 0;

static void
gpio_init() {
    int fd;
    char ∗map;

    fd = open("/dev/mem",O_RDWR|O_SYNC) ;   /∗Needs root access ∗/
    if ( fd < 0 ) {
        perror("Opening /dev/mem") ;
        exit(1) ;
    }

    map = (char ∗) mmap(
        NULL,                  /∗ Any address ∗/
        BLOCK_SIZE,            /∗ # of bytes ∗/
        PROT_READ|PROT_WRITE,
        MAP_SHARED,            /∗Shared ∗/
        fd,                    /∗ /dev/mem ∗/
        GPIO_BASE              /∗ Offset to GPIO ∗/
    ) ;

    if ( (long)map == −1L ) {
        perror("mmap(/dev/mem)");
        exit(1) ;
    }

    close(fd);
    ugpio = (volatile unsigned ∗)map;
}

这段代码执行的第一件事是打开设备驱动程序节点/dev/mem。它被打开用于读和写(O_RDWR),选项标志O_SYNC要求对这个文件描述符的任何write(2)调用都会导致阻塞调用程序的执行,直到它完成。

地址

接下来,调用mmap(2)调用。address 参数提供了NULL(零),这样内核就可以选择将它映射到调用者地址空间的何处。如果应用要指定一个要使用的起始地址,而内核不能使用它,那么系统调用将会失败。返回起始地址,并将其分配给前面清单中的字符指针map

长度

在本例中,参数 2 由宏BLOCK_SIZE提供。这是您希望映射到地址空间的字节数。这在前面的程序中定义为 4 KB:

#define BLOCK_SIZE (4∗1024)

虽然应用可能不需要映射完整的 4 KB 物理内存,但mmap(2)可能坚持使用页面大小的倍数。这可以通过命令行进行验证,如下所示:

$ getconf PAGE_SIZE
4096

程序可以通过使用sysconf(2)系统调用直接确定这一点:

#include <unistd.h>

    ...
    long sz = sysconf(_SC_PAGESIZE);

保护

第三个mmap(2)参数由标志PROT_READPROT_WRITE提供。这表明应用希望对内存映射区域进行读写访问。

旗帜

flags参数提供值MAP_SHARED。这允许对底层映射的非独占访问。

文件描述符

此参数提供了要映射到内存中的基础打开文件。在这种情况下,我们通过使用打开的设备驱动程序节点/dev/mem将一个物理 ARM 内存区域映射到我们的应用中。

抵消

最后一个参数指定了物理内存中开始访问的位置。对于 GPIO 寄存器,它是原始 Pi 模型 b 上的地址 0x20200000。

返回值

如果成功,返回值将是一个应用地址,指向我们请求的物理内存区域。应用员不需要关心这个地址是什么,除了保存和使用它进行访问。

返回值也用于指示失败,因此应对此进行检查和处理:

if ( (long) map == –1L )  {
    perror("mmap(/dev/mem)");
    exit(1);
}

返回的地址(指针)map被转换成一个长整数并与-1L进行比较。这是指示发生错误的神奇值。错误代码在errno中找到。

不稳定的

GPIO 初始化代码的最后一部分将地址map分配给另一个变量ugpio,如下所示:

ugpio = (volatile unsigned ∗)map;

ugpio已在程序中提前定义:

static volatile unsigned ∗ugpio = 0;

关于这一点有两件事值得注意:

  • 数据类型是无符号的int(Pi 上的 32 位)。

  • 指向的数据被标记为 volatile。

由于 Pi 寄存器的大小为 32 位,因此将其作为 32 位字来访问通常更方便。无符号数据类型非常适合这种情况。但是在使用这个指针时要小心偏移量,因为它们是而不是字节偏移量。

关键字volatile告诉编译器不要通过指针变量优化对内存的访问。想象这样一段代码,它读取一个外设寄存器,然后再次读取同一个寄存器,看看是否有事件发生。一个优化编译器可能会对自己说,“我已经在 CPU 寄存器 R 中有了这个值,所以我就用它,因为它更快。”但这段代码的效果是,它永远不会看到外设寄存器中的位发生变化,因为数据没有被取回到寄存器中。volatile关键字强制编译器检索该值,即使使用仍然在寄存器中找到的值会更快。

虚拟内存

在上一节中,您了解了如何在应用中访问物理内存,前提是您拥有访问权限(root 或setuid)。Broadcom Corporation PDF 手册“BCM2835 ARM 外设”第 5 页也在右侧显示了一个虚拟内存布局。这不应与之前检查的物理内存布局相混淆。使用mmap(2).可以通过/dev/kmem驱动节点访问虚拟内存

摘要

一些参数如Buffers会影响 Raspbian Linux 在 Pi 上的性能。例如,为从磁盘读取的文件数据分配了多少内存?其他配置参数决定了有多少 SDRAM 专用于 GPU。

可能内存分配最重要的方面是有多少内存可供开发者应用使用。因此,MemFree的值是一个有用的度量。当超过物理内存限制时,交换参数就会变成感兴趣的度量。

最后,介绍了使用mmap(2)直接访问 Raspberry Pi 外设。在 Raspbian Linux 获得 PWM 等外设的设备驱动程序之前,直接访问技术将是必要的。即使有驱动程序支持,有时也有充分的理由直接访问外设寄存器。

六、中央处理器

自从第一个 B 型和 A 型后继者以来,已经出现了几个树莓派的型号。在本章中,将介绍 ARM 架构以及您的 Pi 所支持的 CPU 特性。然后将介绍用于管理应用中 CPU 的 Linux API(应用编程接口)(线程)。

/proc/cpuinfo

Raspbian Linux 在/proc/cpuinfo提供了一个不错的字符设备,用来列出关于你的 CPU 的信息。清单 6-1 中提供了一个取自树莓 Pi 3 型号 B+的样本。您不需要 root 访问权限来读取这些信息。

$ cat /proc/cpuinfo
processor        : 0
model name       : ARMv7 Processor rev 4 (v7l)
BogoMIPS         : 38.40
Features         : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 \ idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer  : 0x41
CPU architecture : 7
CPU variant      : 0x0
CPU part         : 0xd03
CPU revision     : 4
...
Hardware         : BCM2835
Revision         : a020d3
Serial           : 00000000d4b81de4

Listing 6-1Session output listing /proc/cpuinfo on a Raspberry Pi 3 model B+

有四组处理器标识为 0 到 3(图中只显示了第一组)。文件底部列出了硬件、版本和序列号。

处理器组中有一行标记为“型号名称”在本例中,我们看到列出了“ARMv7 处理器版本 4 (v7l)”。同样在底部,列出了值为“BCM2835”的“硬件”。让我们花点时间来讨论一下架构名称的含义。

ARM 架构

架构是一种设计。在这种情况下,它定义了 ARM 程序员的模型,包括寄存器、寻址、存储器、异常和操作的所有方面。在 Raspberry Pi 环境中,使用了不同的 ARM 架构,如表 6-1 所列。

表 6-1

Raspberry Pi ARM 架构和实现

|

架构名称

|

总线尺寸

|

指令集

|

社会学

|
| — | — | — | — |
| ARMv6Z 战斗机 | 32 位 | 手臂和拇指(16 位) | BCM2835 |
| ARMv7-A 战斗机 | 32 位 | 手臂和拇指(16 位) | BCM2836 |
| ARMv8-A 突击步枪 | 32/64 位 | AArch32(与 ARMv7-A 兼容)和 AArch64 执行状态。 | BCM2837 |

总线尺寸和指令集列中总结了设计和一般功能。每一种新的架构都为指令集和其他处理器特性增加了新的功能。

SoC(片上系统)列标识了 Broadcom 架构的实现

新的 ARMv8-A 架构有两种可能的运行状态:

  • AArch32,与 ARMv7-A 架构兼容。

  • AArch64,采用全新 ARM 64 位指令集。

执行状态必须在系统启动时选择,这就是为什么 Raspbian Linux 在 Raspberry Pi 3 模型 B+上报告以下内容:

$ uname -m
armv7l

它运行 AArch32 执行状态,以便与 32 位 Raspbian Linux 代码兼容。希望有一天,我们能看到真正的 64 位 Raspbian Linux。

建筑后缀

较新的架构有一个后缀来标识 Cortex 系列:

  • “A”代表 Cortex-一系列应用处理器。

  • “R”代表 Cortex-R 系列的实时处理器。

  • “M”代表 Cortex-M 系列低功耗、微控制器处理器。

在架构名称 ARMv7-A 或 ARMv8-A 中,我们看到它们属于应用处理器系列。这些是完全有能力的成员,而 Cortex-R 和 Cortex-M 家族通常是子集或专门从事少数领域。

特征

再次查看/proc/cpuinfo 输出,注意标有“Features”的那一行。它有一个识别特征的名称列表,这些特征对于 CPU(中央处理器)是唯一的。表 6-2 列出了一些你可能会看到的手臂特征。

表 6-2

可能在/proc/cpuinfo 中列出的 ARM 特性

|

功能名称

|

描述

|
| — | — |
| 一半 | 半字加载和存储 |
| 拇指 | 16 位 Thumb 指令集支持 |
| 法斯特穆特 | 32x32 产生 64 位乘法支持 |
| 心室充盈压 | 早期 SIMD 向量浮点指令 |
| 处理器 | DSP 扩展 |
| 氖 | 高级 SIMD/氖支持 |
| vfpv3 | VFP 版本 3 支持 |
| 坦克激光瞄准镜(Tank Laser-Sight 的缩写) | TLS 寄存器 |
| vfpv4 | 具有快速上下文切换的 VFP 版本 4 |
| 伊迪瓦 | ARM 模式下的 SDIV 和 UDIV 硬件部门 |
| idivt | Thumb 模式下的 SDIV 和 UDIV 硬件部门 |
| vfpd32 | 具有 32 个 D 寄存器的 VFP |
| LPA(巴勒) | 大型物理地址扩展(32 位架构上的 4 GB 以上物理内存) |
| evtstrm | 使用通用架构定时器的内核事件流 |
| crc32 | CRC-32 硬件加速支持 |

执行环境

与 CPU 的概念相联系的是程序执行本身。在查看程序执行之前,先从高层次上了解一下执行上下文。图 6-1 显示了正在执行的程序的运行环境。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-1

程序执行上下文

在地址空间的最低端是包含程序代码的“文本”区域。虚拟内存的这个区域是只读的,除了可执行代码之外,它还保存只读的程序常量。

下一个区域(地址递增)包含未初始化数组、缓冲区、静态 C 变量和extern存储的块。

在内存的高端是程序的环境变量,比如PATH。通过使用getenv("PATH")并打印返回的地址,您可以很容易地检查这一点。它的地址可能是您的 Raspberry Pi 应用中最高的地址,除非是另一个环境变量。

在此之下,主程序的堆栈开始向下增长。每个函数调用都会导致在当前堆栈框架下创建一个新的堆栈框架。

如果你现在给程序添加一个线程,就必须为它分配一个新的堆栈。在 Pi 上的实验表明,第一个线程堆栈是在主堆栈的起点以下大约 123 MB 处创建的。第二个线程的堆栈分配比第一个线程低大约 8 MB。每个新线程的堆栈(默认情况下)被分配 8 MB 的堆栈空间。

动态分配的内存是从位于static / extern区域和堆栈底端之间的中分配的。

线

每个程序都有一个主执行线程。但是有时需要多线程的性能优势,尤其是在具有四个内核的 Pi 上。

pthread 标题

所有pthread函数都需要以下头文件:

#include <pthread.h>

当使用pthreads链接程序时,添加链接器选项:

  • -lpthread

  • to链接 pthread 库。

pthread 错误处理

pthread 例程成功时返回零,失败时返回错误代码。值errno是用于这些调用的而不是

这背后的原因可能是人们认为传统的 Unix errno方法将在不久的将来被淘汰(那时 POSIX 线程正在被标准化)。errno的最初用途如下:

extern int errno;

然而,这种方法不适用于线程程序。想象两个线程同时用open(2)打开文件,失败时设置errno值。两个线程不能共享同一个errnoint值。

与其以这种方式改变大量已经使用errno的代码,不如实现其他方法,为每个线程提供自己的私有errno副本。这就是今天使用errno的程序必须包含头文件errno.h的原因之一。头文件负责定义对errno的线程特定引用。

因为 pthread 标准是在errno解决方案普遍出现之前开发的,所以 pthread 库直接返回错误代码,成功时返回零。如果今天要重写 Unix,所有的系统调用可能都是这样的。

pthread_create(3)

函数pthread_create(3)用于创建一个新的执行线程。函数调用看起来比实际情况更令人畏惧:

int pthread_create(
  pthread_t ∗thread,
  const pthread_attr_t ∗attr,
  void ∗(∗start_routine)(void ∗),
  void ∗arg
);

pthread_create(3)的调用创建了一个新的堆栈,设置了寄存器,并执行其他内务处理。让我们来描述一下论点:

|

错误

|

描述

|
| — | — |
| 再一次 | 资源不足,无法创建另一个线程,或者遇到了系统对线程数量的限制。 |
| 埃因瓦尔 | 属性中的设置无效。 |
| 草莓!草莓 | 没有权限设置在属性中指定的调度策略和参数。 |

  • thread:第一个参数只是一个指向pthread_t变量的指针,用来接收创建的线程的 ID 值。ID 值允许您查询和控制创建的线程。如果调用成功,线程 ID 将返回给调用程序。

  • attr:这是一个指向提供各种选项和参数的pthread_attr_t属性对象的指针。如果您可以接受默认值,只需提供零或NULL

  • start_routine:如下面的代码所示,这只是一个接受空指针并返回空指针的开始例程的名称。

  • arg:这个通用指针被传递给start_routine。它可能指向线程函数感兴趣的任何东西(start_routine)。通常这是一个包含值的结构,或者在 C++程序中,它可以是一个对象的指针。如果不需要参数值,则提供零(或NULL)。

  • returns:功能成功返回零;否则,返回错误号(不在errno中)。

参数 3 的 C 语言语法对于初学 C 的程序员来说有点讨厌。让我们展示参数 3 的函数是什么样子的:

void ∗
start_routine(void ∗arg) {
    ...
    return some_ptr;
}

下面可能是最简单的线程创建示例:

static void ∗
my_thread(void ∗arg) {
    ...                                    // thread execution
    return 0;
}

int
main(int argc, char ∗∗argv) {
    pthread_t tid;                         // Thread   ID
    int rc;

    rc = pthread_create(&tid,0,my_thread,0);
    assert(!rc);

此示例不使用线程属性(参数 2 为零)。我们也不关心传入my_thread()的值,所以参数 4 被提供了一个零。参数 3 只需要告诉系统调用执行什么函数。如果线程成功创建(由assert(3)宏测试),则rc的值将为零。

此时,主线程和函数my_thread()并行执行。因为 Raspberry Pi 上只有一个 CPU,所以在任何时刻都只有一个 CPU 执行。但是它们同时执行,以抢先的方式交换执行时间块。当然,每个都使用自己的堆栈运行。

线程my_thread()通过返回。

pthread_attr_t

有几个线程属性可以获取和设置。为了使这个速成课程简短,您将只看可能是最重要的属性(堆栈大小)。有关属性和函数的完整列表,可以查看手册页:

$ man pthread_attr_init

要初始化一个新的属性,或者释放一个先前初始化的 pthread 属性,使用这一对例程:

|

错误

|

描述

|
| — | — |
| 伊诺梅 | 资源(内存)不足 |

  • attr:初始化/销毁的pthread_attr_t变量的地址

  • returns:成功时为零,失败时为错误代码(不在errno中)

int pthread_attr_init(pthread_attr_t ∗attr);
int pthread_attr_destroy(pthread_attr_t ∗attr);

pthread_attr_init(3)的 Linux 实现可能永远不会返回ENOMEM错误,但是其他 Unix 平台可能会。

以下是创建和销毁属性对象的简单示例:

pthread_attr_t attr;

pthread_attr_init(&attr);    // Initialize attr
...
pthread_attr_destroy(&attr); // Destroy attr

线程最重要的属性之一可能是堆栈大小属性:

  • attr:指向要从中获取值或在其中建立属性的属性的指针。

  • stacksize:设置属性时的栈大小值,取栈大小时指向接收size_t变量的指针。

  • returns:如果调用成功,返回零;否则,返回错误号(不在errno中)。

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

pthread_attr_setstacksize(3)可能出现以下错误:

|

错误

|

描述

|
| — | — |
| 埃因瓦尔 | 堆栈大小小于 PTHREAD_STACK_MIN (16,384)字节。 |

Linux 手册页进一步指出:

*在某些系统上,如果堆栈大小不是系统页面大小的倍数,*pthread _ attr _ setstacksize()可能会失败,并出现错误 EINVAL

以下简单示例获取系统默认堆栈大小,并将其增加 8 MB:

pthread_attr_t attr;
size_t         stksiz;

pthread_attr_init(&attr);                     // Initialize attr
pthread_attr_getstacksize (&attr,&stksiz);    // Get stack size
stksiz  += 810241024;                   // Add 8 MB
pthread_attr_setstacksize(&attr,stksiz);      // Set stack size

系统默认值由attr的初始化提供。然后就是从attr对象中“获取”一个值,然后在对pthread_attr_setstacksize()的调用中放入一个新的堆栈大小。

注意,这组操作只是简单地准备了属性对象attr用于pthread_create()调用。实际创建线程时,该属性在新线程中生效:

pthread_attr_t attr;

...
rc = pthread_create(&tid,&attr,my_thread,0);

pthread_join(3)

在前面的pthread_create()示例中,主程序创建了my_thread()并开始执行它。在某个时刻,主程序将要结束,想要退出(或返回)。如果主程序在my_thread()完成前退出,整个进程和其中的线程都会被破坏,即使它们还没有完成。

为了使主程序等待直到线程完成,使用函数pthread_join(3):

  • thread:要连接的螺纹的螺纹 ID。

  • retval:指向void *变量的指针,用于接收返回值。如果您对返回值不感兴趣,可以给该参数提供零(或NULL)。

  • returns:函数成功返回零;否则,返回错误号(不在errno中)。

int pthread_join(pthread_t thread, void **retval);

下面的例子增加了pthread_join(3),这样主程序直到my_thread()退出后才退出。

int
main(int argc,char ∗∗argv) {
      pthread_t tid;                            // Thread ID
      void ∗retval = 0;                         // Returned value pointer
      int rc;

      rc = pthread_create(&tid,0,my_thread,0);
      assert(!rc);
      rc = pthread_join(tid,&retval);           // Wait for my_thread()
      assert(!rc);
      return 0;
}

pthread_detach(3)

函数pthread_join(3)使调用者等待,直到指示的线程返回。然而,有时一个线程被创建后就不再被检查了。当该线程退出时,它的一些资源被保留,以允许对它进行连接操作。如果永远不会有连接,那么最好在线程退出时将其遗忘,并立即释放其资源。

pthread_detach(3)函数用于指示在指定的线程上不会执行任何连接。这样,命名线程就被配置为在退出时自动释放自己。

int pthread_detach(pthread_t thread);

参数和返回值如下:

|

错误

|

描述

|
| — | — |
| 埃因瓦尔 | 线程不是可连接的线程。 |
| esrsch(欧洲核子研究中心) | 找不到 ID 为 thread 的线程。 |

  • thread:要改变的线程的线程 ID,以便它在完成时不会等待加入。它的资源将在指定线程终止时立即释放。

  • returns:如果呼叫成功,则为零;否则返回错误代码(不在errno中)。

pthread_detach函数只需要线程 ID 值作为它的参数:

pthread_t tid;             // Thread ID
int rc;

rc = pthread_create(&tid,0,my_thread,0);
assert(!rc);
pthread_detach(tid);      // No joining with this thread

pthread_self(3)

有时候,在一段代码中找出当前的线程 ID 是什么是很方便的。pthread_self(3)功能是这项工作的合适工具:

pthread_t pthread_self(void);

此处显示了其使用示例:

pthread_t tid;

tid = pthread_self();

pthread_kill(3)

pthread_kill(3)函数允许调用者向另一个线程发送信号。线程信号的处理超出了本文的范围。但是这个函数有一个非常有用的应用,我们很快就会看到:

#include <signal.h>

int pthread_kill(pthread_t thread, int sig);

注意,函数原型和信号定义需要signal.h的头文件。

|

错误

|

描述

|
| — | — |
| 埃因瓦尔 | 指定了无效信号。 |
| esrsch(欧洲核子研究中心) | 找不到 ID 为 thread 的线程。 |

  • thread:这是你要发信号(或测试)的线程 ID。

  • 这是你想发出的信号。或者,提供零来测试线程是否存在。

  • returns:如果调用成功,返回零,或者返回一个错误代码(不在errno)。

函数的一个有用的应用是测试另一个线程是否存在。如果sig参数被设置为 0,则不会传递实际信号,但仍会执行错误检查。如果函数返回零,你知道线程仍然存在

但是当线程存在的时候意味着什么呢?是不是说现在还是在执行?还是意味着它没有作为pthread_join(3)的一部分被回收,或者作为pthread_detach(3)清理的结果?

原来,当线程存在时,意味着它还在执行。换句话说,它还没有从启动的线程函数中返回。如果线程已经返回,则认为它不能接收信号。

基于此,您知道当线程仍在执行时,您将得到一个零返回。当返回错误代码ESRCH时,您知道线程已经完成。

互斥体

虽然严格来说互斥锁不是 CPU 的主题,但它与线程的讨论是分不开的。一个互斥体是一个锁定装置,它允许软件设计者停止一个或多个线程,而另一个线程正在处理一个共享资源。换句话说,一个线程接收独占访问。这是促进线程间通信所必需的。我在这里将简单地描述互斥 API,而不是互斥应用背后的理论。

pthread_mutex_create(3)

互斥体通过对pthread_mutex_init(3)的系统调用进行初始化:

|

错误

|

描述

|
| — | — |
| 再一次 | 系统缺少必要的资源(内存除外)来初始化另一个互斥体。 |
| 伊诺梅 | 内存不足,无法初始化互斥体。 |
| 草莓!草莓 | 调用方没有执行该操作的权限。 |
| 电子布西 | 该实现检测到有人试图重新初始化 mutex 引用的对象,mutex 是以前初始化过但尚未销毁的 mutex。 |
| 埃因瓦尔 | attr 指定的值无效。 |

  • mutex:指向要初始化的pthread_mutex_t object的指针。

  • attr:指向pthread_mutexattr_t对象的指针,描述互斥选项。如果您可以接受默认值,请提供零(或NULL)。

  • returns:如果调用成功,返回零;否则,返回错误代码(不在errno中)。

int pthread_mutex_init(
    pthread_mutex_t ∗mutex,
    const pthread_mutexattr_t ∗attr
);

这里提供了一个互斥体初始化的示例:

pthread_mutex_t mutex;
int rc;

rc = pthread_mutex_init(&mutex,0);
assert (!rc);

pthread_mutex_destroy(3)

当应用不再需要互斥体时,它应该使用pthread_mutex_destroy(3)来释放它的资源:

|

错误

|

描述

|
| — | — |
| 电子布西 | 互斥体被锁定或与pthread_cond_wait(3)pthread_cond_timedwait(3)一起使用。 |
| 埃因瓦尔 | 互斥体指定的值无效。 |

  • mutex:要释放资源的互斥体的地址。

  • returns:成功返回零,失败返回错误码(不在errno)。

pthread_mutex_t mutex ;
int rc;

...
rc = pthread_mutex_destroy(&mutex);
assert(!rc);

pthread_mutex_lock(3)

当一个线程需要独占访问一个资源时,它必须锁定该资源的互斥体。只要协作线程遵循相同的先锁定过程,它们就不能同时访问共享对象。

|

错误

|

描述

|
| — | — |
| 埃因瓦尔 | 互斥体是用值为 PTHREAD_PRIO_PROTECT 的协议属性创建的,调用线程的优先级高于互斥体的当前优先级上限。或者互斥体指定的值没有引用初始化的互斥体对象。 |
| 再一次 | 已超过互斥体的最大递归锁数。 |
| EDEADLK | 当前线程已经拥有互斥体。 |

  • mutex:指向要锁定的互斥体的指针。

  • returns:如果互斥锁成功,返回零;否则返回错误代码(不在errno中)。

int pthread_mutex_lock(pthread_mutex_t ∗mutex);

下面显示了正在调用的函数:

pthread_mutex_t mutex;
int rc;

...
rc = pthread_mutex_lock(&mutex);

pthread_mutex_unlock(3)

当不再需要对资源的独占访问时,互斥体被解锁:

|

错误

|

描述

|
| — | — |
| 埃因瓦尔 | mutex 指定的值没有引用初始化的 mutex 对象。 |
| 草莓!草莓 | 当前线程不拥有互斥体。 |

  • mutex:指向要解锁的互斥体的指针。

  • returns:如果互斥锁解锁成功,返回零;否则返回错误代码(不在errno中)。

int pthread_mutex_unlock(pthread_mutex_t ∗mutex);

这里提供了一个解锁互斥体的简单示例:

pthread_mutex_t mutex;
int rc;

...
rc = pthread_mutex_unlock(&mutex);

条件变量

有时,互斥体本身不足以在不同线程之间高效地调度 CPU。互斥体和条件变量经常一起使用来促进线程间的通信。起初,新来者可能会对这个概念感到困惑。

当我们有互斥时,为什么我们需要条件变量?

考虑在构建一个最多可以容纳八个项目的软件队列时需要做些什么。在我们对某些东西进行排队之前,我们需要首先查看队列是否已满。但是在我们锁定队列之前,我们无法测试它——否则,另一个线程可能会在我们眼皮底下改变事情。

所以我们锁定了队列,但是发现它已经满了。我们现在要干嘛?我们只是解锁并再试一次吗?这是可行的,但是会浪费 CPU 时间。如果我们有某种方法在队列不再满的时候得到提醒,那不是更好吗?

条件变量与互斥体和“信号”(各种类型)协同工作。用伪代码的术语来说,试图将一个项目放入队列的程序将执行以下步骤:

  1. 锁定互斥锁。在锁定队列之前,我们无法检查队列中的任何内容。

  2. 检查队列的容量。我们能在里面放一个新项目吗?如果是这样:

    1. 将新项目放入队列中。

    2. 解锁并退出。

  3. 如果队列已满,则执行以下步骤:

    1. 使用一个条件变量,用相关的互斥体“等待”它。

    2. 当控制从等待中返回时,返回到步骤 2。

条件变量对我们有什么帮助?考虑以下步骤:

  1. 互斥体被锁定(1)。

  2. 执行等待(3a)。这将导致内核执行以下操作:

    1. 将调用线程置于睡眠状态(置于内核等待队列中)。

    2. 解锁在步骤 1 中锁定的互斥锁。

在步骤 2b 中解锁互斥体是必要的,以便另一个线程可以对队列做一些事情(希望从队列中取出一个条目,这样它就不再满了)。如果互斥锁保持锁定,那么没有线程能够移动。

在未来的某个时间点,另一个线程将执行以下操作:

  1. 锁定互斥体。

  2. 在队列中查找条目(当前队列已满),并从中取出一个条目。

  3. 解锁互斥体。

  4. 向“服务员”正在使用的条件变量发出信号,以便它能够被唤醒。

等待线程随后醒来:

  1. 内核让“等待”线程准备就绪。

  2. 互斥体被成功重新锁定。

一旦该线程在互斥锁被锁定的情况下醒来,它就可以重新检查队列,看看是否有空间对某个项目进行排队。注意,只有当线程已经重新获得互斥锁时,它才会被唤醒。这就是为什么条件变量在使用时与互斥体成对出现的原因。

pthread_cond_init(3)

像任何其他对象一样,条件变量需要初始化:

|

错误

|

描述

|
| — | — |
| 再一次 | 该系统缺乏必要的资源。 |
| 伊诺梅 | 内存不足,无法初始化条件变量。 |
| 电子布西 | 实现检测到有人试图重新初始化 cond 引用的对象,cond 是一个先前已初始化但尚未销毁的条件变量。 |
| 埃因瓦尔 | attr 指定的值无效。 |

  • cond:指向要初始化的pthread_cond_t结构的指针。

  • attr:指向cond变量属性的指针,如果提供了一个变量属性,则提供零(或NULL)。

  • returns:如果调用成功,返回零;否则返回错误代码(不在errno中)。

int pthread_cond_init(
  pthread_cond_t             ∗cond,
  const pthread_condattr_t   ∗attr
);

pthread_cond_destroy(3)

当不再需要条件(cond)变量时,应通过以下调用释放其资源:

|

错误

|

描述

|
| — | — |
| 电子布西 | 检测到试图销毁 cond 引用的对象,而该对象正被另一个线程中的 pthread_cond_wait()或 pthread_cond_timedwait()引用。 |
| 埃因瓦尔 | cond 指定的值无效。 |

  • cond:待释放的条件变量。

  • returns:如果呼叫成功,则为零;否则,返回错误代码(不在errno中)。

int pthread_cond_destroy(pthread_cond_t ∗cond);

pthread_cond_wait(3)

这个函数是队列解决方案的一半。调用pthread_cond_wait(3)函数时互斥体已经被锁定。然后内核会让调用线程休眠(在等待队列上)来释放 CPU,同时解锁互斥体。调用线程保持阻塞状态,直到条件变量cond以某种方式发出信号(稍后将详细介绍)。

当线程被内核唤醒时,系统调用返回互斥锁。此时,线程可以检查应用的条件(比如队列长度),如果情况良好就继续执行,或者再次调用pthread_cond_wait(3)继续等待。

|

错误

|

描述

|
| — | — |
| 埃因瓦尔 | cond,mutex 指定的值无效。或者为同一条件变量上的并发 pthread_cond_timedwait()或 pthread_cond_wait()操作提供了不同的互斥体。 |
| 草莓!草莓 | 在调用时,互斥体不属于当前线程。 |

  • cond:指向用于唤醒调用的条件变量的指针。

  • mutex:指向与条件变量相关联的互斥体的指针。

  • returns:成功返回零;否则返回错误代码(不在errno中)。

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

下面的代码片段显示了排队函数如何使用它。(假设初始化mutexcond。)

pthread_mutex_t mutex;
pthread_cond_t cond;

...
pthread_mutex_lock(&mutex);

while ( queue.length >=max_length )
    pthread_cond_wait(&cond,&mutex);

// queue the item
...
pthread_mutex_unlock(&mutex);

while循环重试测试以查看队列是否“未满”当多个线程插入到队列中时,while循环是必要的。根据时间的不同,另一个线程可能会比当前线程先排队一个项目,使队列再次变满。

pthread_cond_signal(3)

当从队列中取出一个条目时,需要一种机制来唤醒试图将一个条目放入整个队列的线程。一个唤醒选项是pthread_cond_signal(3)系统调用:

|

错误

|

描述

|
| — | — |
| 埃因瓦尔 | cond 值不是指初始化的条件变量。 |

  • cond:指向用于向一个线程发送信号的条件变量的指针。

  • returns:如果函数调用成功,返回零;否则,返回错误号(不在errno中)。

int pthread_cond_signal(pthread_cond_t ∗cond);

如果没有其他线程在等待,那么这就不是错误。但是,如果一个或多个线程正在等待指定的条件变量,此函数会唤醒一个等待线程。

如果发出一个线程将“工作”的信号,那么出于性能的原因,这个调用是首选的当存在一些特殊情况,一些线程可能成功,而另一些可能失败时,您需要一个广播调用来代替。当它可以使用时,唤醒一个线程可以节省 CPU 周期。

pthread_cond_broadcast(3)

这是pthread_cond_signal(3)的广播变体。如果多个服务员有不同的测试,应该使用广播让所有服务员醒来并考虑发现的情况。

|

错误

|

描述

|
| — | — |
| 埃因瓦尔 | cond 值不是指初始化的条件变量。 |

  • cond:指向条件变量的指针,条件变量发信号,唤醒所有等待线程。

  • returns:调用成功返回零;否则,返回错误号(不在errno中)。

int pthread_cond_broadcast(pthread_cond_t ∗cond);

没有服务员的时候广播是不是错误。

摘要

本章介绍了 CPU 是一种有待开发的资源。描述了/proc/cpuinfo 驱动程序,它提供了您的 CPU 能力(和处理器数量)的快速摘要。

还介绍了 ARM 架构,让您了解架构与实现的不同,例如,BCM2837 是 Broadcom 对 ARMv8-A 架构的实现。对于 C 程序员来说,本章以对 Linux 支持的 pthread API 的快速浏览结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值