Lan Tian @ Blog

x86 下制作 ARM Docker 镜像,Docker Hub、Travis 自动构建

一般情况下,Docker 的镜像都是在一个已有的镜像内,一步步运行给定的命令,从而生成一个新的镜像。这样的步骤在大多数人使用的 x86 架构计算机上都不是问题,由于架构互相兼容,一台计算机上生成的镜像往往可以被直接复制到其它计算机上运行,除非镜像中的程序使用了 AVX 等较新的指令集。

但是,还有一批基于 ARM 架构的主机也可以运行 Docker,并运行专门编译的 ARM 架构的镜像。这些主机包括树莓派系列,和其它类似树莓派的主机,例如 Cubieboard,Orange Pi,Asus Tinker Board 等等。另外,Scaleway 等主机商也提供基于 ARM 架构的独立服务器。

由于 ARM 架构的系统无法在 x86 架构计算机上运行,因此无法在 x86 计算机上直接通过 Dockerfile 生成 ARM 架构的镜像,一般采用的方法是直接找一台 ARM 主机来 docker build。

但是我在为我的树莓派制作 nginx 的 Docker 镜像时发现这并不是一个很好的方法。由于树莓派的内存只有 1GB,如果开启多线程编译(make -j4 或者 make -j2),内存会不足,gcc 会被杀掉;如果单线程编译(直接 make),编译时间又非常长(几个小时)。

经过查找,另有一种方案可以解决这个问题。这个方案是在 x86 架构计算机上模拟 ARM 环境,即“虚拟机”的方式来编译镜像。虽然 x86 模拟 ARM 没有硬件加速(VT-x,AMD-V 等)支持,效率极低,但是得益于 x86 CPU 的高性能,总体效率还是高于直接在树莓派上编译。

qemu-user-static

第一步是要模拟出一个 ARM 环境。当然,我们可以用 QEMU 直接开一个 ARM 架构的完整 Linux 虚拟机,然后在里面运行 Docker 构建镜像。但是这样做的问题是,需要额外管理一个 Docker,难以将系统资源在主系统和虚拟机之间灵活分配,并且难以使用脚本自动化,即难以整合到 CI、CD 中。

更好的方案是 qemu-user-static,是 QEMU 虚拟机的用户态实现。它可以直接在 amd64 系统上运行 ARM、MIPS 等架构的 Linux 程序,将指令动态翻译成 x86 指令。这样 ARM 系统环境中的进程与主系统的进程一一对应,资源分配灵活,并且易于脚本自动化。

但是还有一个问题:当 ARM 进程尝试运行其它进程时,qemu-user-static 并不会接管新建的进程。如果新的进程仍然是 ARM 架构,那么 Linux 内核就无法运行它。因此,需要开启 Linux 内核的 binfmt 功能,该功能可以让 Linux 内核在检测到 ARM、MIPS 等架构的程序时,自动调用 qemu-user-static。开启该功能,并且注册 qemu-user-static 虚拟机后,运行 ARM 程序就和运行 x86 程序一样,对用户来说毫无差别。

在 x86 Docker 中运行 ARM 镜像

要在 Docker 中运行 ARM 镜像,我们要先在计算机上注册 qemu-user-static 虚拟机:

docker run --rm --privileged multiarch/qemu-user-static:register --reset

另外,Docker 镜像内必须也含有对应的 qemu-user-static 虚拟机。不过,Docker Hub 上已经有了添加 qemu-user-static 的系统镜像,可以在 https://hub.docker.com/u/multiarch/ 获取:

插图

例如,multiarch/alpine 镜像就在不同 tag 下提供了 aarch64(armv8)、armhf、amd64、i386 的镜像:

插图

如果你之前已经注册了虚拟机,那么就可以直接运行了:

docker run -it --rm multiarch/alpine:armhf-edge /bin/sh
docker run -it --rm multiarch/alpine:aarch64-edge /bin/sh

插图

修改 Dockerfile

接下来我们要在 Dockerfile 中调用 ARM 架构的镜像。如果你的 ARM 主机是 armv7l(armhf)架构(树莓派(默认),Tinker Board 等),那么把 Dockerfile 中的第一行修改成 https://hub.docker.com/u/multiarch/ 下对应的 armhf 架构镜像即可。对应关系如下:

  • alpine -> multiarch/alpine:armhf-edge
  • ubuntu:bionic -> multiarch/ubuntu-debootstrap:armhf-bionic
  • debian:stretch -> multiarch/debian-debootstrap:armhf-stretch

如果你的 ARM 主机是 aarch64(armv8)架构(树莓派 3 开始支持,但是需要特殊系统才是这个架构),那么对应关系如下:

  • alpine -> multiarch/alpine:aarch64-edge
  • ubuntu:bionic -> multiarch/ubuntu-debootstrap:arm64-bionic
  • debian:stretch -> multiarch/debian-debootstrap:arm64-stretch

改完后直接重新构建镜像,你就可以在本地生成 ARM 架构的镜像了。

Docker Hub 自动构建

Docker Hub 不仅提供镜像的存储共享服务,也提供简单的镜像自动构建服务。自动构建服务给每个用户分配了一台 2GB 内存、1 核心 CPU、30GB 硬盘的完整虚拟机运行 2 小时(来自 Docker 官方论坛),并且用户具有 root 权限。

默认的自动构建相当于是我们构建镜像时运行的 docker build 那一步,但是我们需要在这之前注册 qemu-user-static 虚拟机。我们可以用 Docker 官方提供的 hook 在构建开始前运行自定义的命令(来自 Docker Cloud 文档)。因为我们分配到的是完整的虚拟机,有 root 权限,所以我们也可以在 hook 中注册虚拟机。

如何创建这样一个 hook?在 Dockerfile 的文件夹下创建 hooks 文件夹,再在 hooks 文件夹下创建 pre_build 文件,内容如下:

#!/bin/sh
docker run --rm --privileged multiarch/qemu-user-static:register --reset

可以在我的这个 commit 中看到 hook 的示例。

Docker Hub 的自动构建服务会先运行这个脚本注册 qemu-user-static,然后再开始构建。构建完成时 push 上来的就是 ARM 架构

如果你的镜像构建时没有编译操作,构建速度应该相当快,不会比 x86 的镜像慢多少;但是如果有大量的编译操作,例如我的 nginx 镜像,很有可能就超出了 2 小时的时间限制而构建失败。在这种情况下,我们就要换其它不限制时间的自动构建服务,例如 Travis CI。

Travis CI 自动构建

Travis CI 是对开源社区免费的一款自动构建工具。只要你的 Dockerfile 传到了 GitHub 上的 Public Repository(公开代码仓库)里,就可以直接使用它。

对于构建 Docker 镜像来说,Travis 提供的配置是 7.5GB 内存、2 核心 CPU、18GB 硬盘,不限制运行时间。因此编译时可以开启 make -j4 四线程编译来提高速度。

首先到 https://travis-ci.org/ 用 GitHub 账号登录,然后开启你放 Dockerfile 仓库的自动构建功能。

插图

然后在 Settings 页面添加你的 Docker Hub 账户的用户名密码到环境变量,这样后续你就不用在自动构建配置中明文保存密码了。

插图

然后创建一个名为 .travis.yml 的文件到 git 仓库的根目录,这就是 Travis 的 “Dockerfile”,保存你的自动构建指令。

.travis.yml 的语法较复杂,你可以在我的 .travis.yml 的基础上作修改。我的 .travis.yml 可以在 https://github.com/xddxdd/dockerfiles/blob/master/.travis.yml 看到。

如果需要更复杂的修改,可以阅读 Travis 的官方文档自行学习。

编辑 .travis.yml 完成后,把它提交到 GitHub 上,Travis 就会自动开始构建你的镜像,把它们 push 到 Docker Hub 上,并且发邮件告诉你自动构建的情况。

树莓派 3B 折腾笔记:硬件看门狗

在计算机中,“看门狗”指的是一种硬件计时器,用于在计算机失去响应(死机)的时候重启计算机。计算机的系统上要运行一个程序不断和看门狗硬件通信。当通信中断经过一段预设的时间后,看门狗就会通过发送 RESET 信号或者切断再接通电源等方式强制重启,保证计算机上运行的服务不长时间中断。

在折腾树莓派的过程中,我也曾好几次让树莓派失去响应,结果不得不人工开关电源来重启。通过开启树莓派上的硬件看门狗功能,就可以减少这种情况的出现。

加载驱动

由于 Linux “万物皆文件”的特点,可以通过 ls 命令直接查看看门狗驱动的状态:

ls /dev/watchdog

如果有这个文件,可以直接跳到下一部分。如果没有,就要根据树莓派版本加载驱动:

  1. 树莓派 1代的驱动名为:bcm2708_wdog
  2. 树莓派 2代的驱动名为:bcm2709_wdog
  3. 树莓派 3代的驱动名为:bcm2835_wdt

使用 modprobe -v [驱动名] 加载驱动,然后再 ls /dev/watchdog 查看情况。如果驱动加载成功,就要将这个驱动设置为开机加载。编辑 /etc/modules,另起一行填入驱动名就可以了。

安装通信软件

前面提到,硬件看门狗需要和软件通信来确定系统的状态。在 Raspbian 下这个软件是 watchdog,可以直接 apt-get 安装:

apt-get install watchdog

然后由于显而易见的原因,要把它设置为开机启动:

systemctl enable watchdog

然后编辑配置文件 /etc/watchdog.conf,作出如下修改:

  1. 取消 #max-load-1 = 24 的注释(删除开头的 # 号),代表当系统 1 分钟内的负载高于 24(已经非常非常高了),就重启系统
  2. 取消 #watchdog-device = /dev/watchdog 的注释,设置看门狗的路径
  3. 增加一行 watchdog-timeout = 15,代表 15 秒内系统无响应就重启系统,在树莓派 3B 上这个值最高为15。注意不要设置的太小,否则可能造成系统反复重启。

保存修改,重启看门狗服务:

service watchdog restart

看门狗功能就启用了。

测试

可以通过 kill 掉看门狗服务来模拟系统死机的情况:

pkill -9 watchdog
pkill -9 wd_keepalive

过 15 秒后树莓派就会自动重启。

解决树莓派 HW CSum Failure 问题

今天登录上树莓派,习惯性 df 查看磁盘空间,发现树莓派 TF 卡上的空间所剩无几。最开始我以为我设置错误,把要挂机下载的文件下载到了 TF 卡里而不是移动硬盘里。结果排查下来,/var/log 下的日志文件居然占据了整整 18G 的空间。查看了一下日志,基本上都是类似如下的报错:

Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143274] eth0: hw csum failure
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143281] CPU: 0 PID: 1075 Comm: vncagent Not tainted 4.9.77-v7+ #1081
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143283] Hardware name: BCM2835
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143294] [<8010fa48>] (unwind_backtrace) from [<8010c058>] (show_stack+0x20/0x24)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143303] [<8010c058>] (show_stack) from [<804578e4>] (dump_stack+0xd4/0x118)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143312] [<804578e4>] (dump_stack) from [<80629704>] (netdev_rx_csum_fault+0x44/0x48)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143320] [<80629704>] (netdev_rx_csum_fault) from [<8061c2c4>] (__skb_checksum_complete+0xb4/0xb8)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143328] [<8061c2c4>] (__skb_checksum_complete) from [<806c0be8>] (nf_ip_checksum+0xd4/0x130)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143376] [<806c0be8>] (nf_ip_checksum) from [<7f5b8de0>] (tcp_error+0x1d0/0x21c [nf_conntrack])
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143463] [<7f5b8de0>] (tcp_error [nf_conntrack]) from [<7f5b3674>] (nf_conntrack_in+0xd4/0x984 [nf_conntrack])
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143515] [<7f5b3674>] (nf_conntrack_in [nf_conntrack]) from [<7f5e1488>] (ipv4_conntrack_in+0x28/0x2c [nf_conntrack_ipv4])
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143527] [<7f5e1488>] (ipv4_conntrack_in [nf_conntrack_ipv4]) from [<80663c40>] (nf_iterate+0x74/0x90)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143534] [<80663c40>] (nf_iterate) from [<80663cc4>] (nf_hook_slow+0x68/0xc0)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143541] [<80663cc4>] (nf_hook_slow) from [<8066bac8>] (ip_rcv+0x468/0x55c)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143549] [<8066bac8>] (ip_rcv) from [<806271a4>] (__netif_receive_skb_core+0x2b4/0xbc0)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143556] [<806271a4>] (__netif_receive_skb_core) from [<80629a54>] (__netif_receive_skb+0x20/0x7c)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143564] [<80629a54>] (__netif_receive_skb) from [<80629adc>] (netif_receive_skb_internal+0x2c/0xa4)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143571] [<80629adc>] (netif_receive_skb_internal) from [<80629b78>] (netif_receive_skb+0x24/0x98)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143581] [<80629b78>] (netif_receive_skb) from [<7f618514>] (ifb_ri_tasklet+0xf4/0x29c [ifb])
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143591] [<7f618514>] (ifb_ri_tasklet [ifb]) from [<80123244>] (tasklet_action+0x74/0x10c)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143599] [<80123244>] (tasklet_action) from [<8010169c>] (__do_softirq+0x18c/0x3cc)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143605] [<8010169c>] (__do_softirq) from [<80122ccc>] (irq_exit+0x10c/0x168)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143613] [<80122ccc>] (irq_exit) from [<801738f8>] (__handle_domain_irq+0x70/0xc4)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143622] [<801738f8>] (__handle_domain_irq) from [<8010150c>] (bcm2836_arm_irqchip_handle_irq+0xa8/0xac)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143630] [<8010150c>] (bcm2836_arm_irqchip_handle_irq) from [<8071c13c>] (__irq_svc+0x5c/0x7c)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143633] Exception stack(0xb13b3b60 to 0xb13b3ba8)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143639] 3b60: 00000001 39f65000 20000113 00000001 baae6a4c 80b81a3c 39f65000 00000002
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143645] 3b80: b21cbe20 80c6f440 baefcb74 b13b3c44 80c040a4 b13b3bb0 80214ae0 80214700
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143649] 3ba0: a0000113 ffffffff
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143658] [<8071c13c>] (__irq_svc) from [<80214700>] (get_page_from_freelist+0x258/0xb0c)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143668] [<80214700>] (get_page_from_freelist) from [<8021578c>] (__alloc_pages_nodemask+0xf0/0xe58)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143678] [<8021578c>] (__alloc_pages_nodemask) from [<8021c3f8>] (__do_page_cache_readahead+0xf8/0x270)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143688] [<8021c3f8>] (__do_page_cache_readahead) from [<8020efe4>] (filemap_fault+0x338/0x674)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143696] [<8020efe4>] (filemap_fault) from [<8030db3c>] (ext4_filemap_fault+0x3c/0x50)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143707] [<8030db3c>] (ext4_filemap_fault) from [<8023e2f4>] (__do_fault+0x7c/0x100)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143715] [<8023e2f4>] (__do_fault) from [<80242638>] (handle_mm_fault+0x614/0xd98)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143723] [<80242638>] (handle_mm_fault) from [<8071cbf0>] (do_page_fault+0x338/0x3bc)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143730] [<8071cbf0>] (do_page_fault) from [<801012a8>] (do_PrefetchAbort+0x44/0xa8)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143737] [<801012a8>] (do_PrefetchAbort) from [<8071c6a4>] (ret_from_exception+0x0/0x1c)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143740] Exception stack(0xb13b3fb0 to 0xb13b3ff8)
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143744] 3fa0:                                     0011ff6c 00000001 a63dc0de 0006ac00
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143750] 3fc0: 76c37000 00000001 76c374ac 00000001 00675cd8 00000000 76f2b000 00000000
Jan 25 22:51:15 lantian-rpi3 kernel: [   22.143755] 3fe0: 76f2ace4 7e9b5cc0 76b29dd4 0006ac00 60000010 ffffffff

即,树莓派的有线网卡部分出现了大面积的报错,内核不断的打 Stacktrace 导致日志文件暴涨。而我的树莓派因为用来挂校内 PT 站,常常有 5MB/S 以上的上传下载,日志量可想而知。

报错的“HW CSum”功能全称为“Hardware Checksum Offloading”,即将网络数据包的校验转交给网卡芯片,从而降低 CPU 占用的功能。为了排查问题,我尝试用 ethtool 关闭该功能:

apt-get install ethtool
ethtool --offload eth0 rx off tx off

关闭后 dmesg 中不再出现类似报错。再尝试打开该功能:

ethtool --offload eth0 rx on tx on

dmesg 中再次出现大面积报错,证明该问题由 HW CSum 产生。

当然,关闭 HW CSum 仅是权宜之计,这个问题应该通过更新内核和/或驱动的方式解决。但是我尝试 raspi-update,更新内核和驱动后问题仍未解决,因此只能继续停用 HW CSum。

编辑 /etc/network/interfaces.d/eth0(如果没有就创建),加入以下代码:

allow-hotplug eth0
iface eth0 inet dhcp
    offload-rx off
    offload-tx off

这样之后系统启动时就会自动调用 ethtool 禁用 HW CSum 功能。

树莓派 3B 折腾笔记:硬件随机数发生器

随机数在计算机中有着十分重要的应用,例如常用的 SSL 加密算法就非常依赖随机数。如果随机数不够随机,就很有可能被攻击者猜到,相应的加密验证体系也就土崩瓦解。但是由于计算机说零是零、说一是一的特点,它没有办法产生真正的随机数,只能通过复杂的算法去尽可能模拟随机数。

在 Linux 系统上,由于 Linux “万物皆文件”的特点,可以从 /dev/random 读取到由 Linux 内核综合大量数据生成的随机数。但是因为 Linux 基于“安全第一”的原则综合了大量数据,随机数的产生速度很慢。用 rng-tools 软件包中的 rngtest 工具就可以看到:

lantian@lantian-rpi3:~ $ cat /dev/random | rngtest -c 1000
rngtest 2-unofficial-mt.14
Copyright (c) 2004 by Henrique de Moraes Holschuh
This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

rngtest: starting FIPS tests...
rngtest: bits received from input: 20000032
rngtest: FIPS 140-2 successes: 999
rngtest: FIPS 140-2 failures: 1
rngtest: FIPS 140-2(2001-10-10) Monobit: 0
rngtest: FIPS 140-2(2001-10-10) Poker: 1
rngtest: FIPS 140-2(2001-10-10) Runs: 0
rngtest: FIPS 140-2(2001-10-10) Long run: 0
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=167.862; avg=361.389; max=4358.681)Kibits/s
rngtest: FIPS tests speed: (min=2.087; avg=13.116; max=14.309)Mibits/s
rngtest: Program run time: 55507560 microseconds

/dev/random 的读取速度仅仅 361.389 Kbit/s(注意不是千字节)。在需要大量随机数的场景,程序就不得不等待 Linux 产生更多的随机数,造成严重的延迟卡顿。

但是很多时候我们并不需要如此随机的随机数。Linux 内核还提供了 /dev/urandom,它的算法更加简单,相比 /dev/random 有几万倍的速度加成:

lantian@lantian-rpi3:~ $ cat /dev/urandom | rngtest -c 1000
rngtest 2-unofficial-mt.14
Copyright (c) 2004 by Henrique de Moraes Holschuh
This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

rngtest: starting FIPS tests...
rngtest: bits received from input: 20000032
rngtest: FIPS 140-2 successes: 1000
rngtest: FIPS 140-2 failures: 0
rngtest: FIPS 140-2(2001-10-10) Monobit: 0
rngtest: FIPS 140-2(2001-10-10) Poker: 0
rngtest: FIPS 140-2(2001-10-10) Runs: 0
rngtest: FIPS 140-2(2001-10-10) Long run: 0
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=334.623; avg=2492.940; max=9536.743)Mibits/s
rngtest: FIPS tests speed: (min=5.508; avg=12.871; max=14.245)Mibits/s
rngtest: Program run time: 1492273 microseconds

速度是 2492.940 Mbit/s,相比 /dev/random 不知高到哪里去了。但是这个随机数不够随机,存在被攻击者猜到的可能。

如果无法在软件上解决这个问题,那么就可以加入硬件来解决。现在的主板基本上都内置了硬件随机数产生器,它们一般是通过主板的电气信号来产生随机数。由于主板上数据流量很大,电气信号一般很难预测,因此硬件随机数一般被认为是安全的。

树莓派 3B 的 Broadcom 芯片组也有硬件随机数产生器,可以在 /dev/hwrng 看到。它的性能如下:

lantian@lantian-rpi3:~ $ cat /dev/hwrng | rngtest -c 1000
rngtest 2-unofficial-mt.14
Copyright (c) 2004 by Henrique de Moraes Holschuh
This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

rngtest: starting FIPS tests...
rngtest: bits received from input: 20000032
rngtest: FIPS 140-2 successes: 999
rngtest: FIPS 140-2 failures: 1
rngtest: FIPS 140-2(2001-10-10) Monobit: 0
rngtest: FIPS 140-2(2001-10-10) Poker: 0
rngtest: FIPS 140-2(2001-10-10) Runs: 0
rngtest: FIPS 140-2(2001-10-10) Long run: 1
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=18.251; avg=1103.150; max=9765625.000)Kibits/s
rngtest: FIPS tests speed: (min=2.416; avg=13.004; max=14.341)Mibits/s
rngtest: Program run time: 20213577 microseconds

1103.150 Kbit/s,是 /dev/random 的三倍,反映到需要随机数的程序上就有了很大的性能提升。但是一般程序只认 /dev/random,不会去管硬件随机数发生器,怎么办?

前面说到,/dev/random 的速度很慢。Linux 为了解决这个问题,引入了一个“随机数池”:在不需要随机数的时候,Linux 也在后台根据运行的指令等等慢慢地收集随机数,放入 /dev/random 这个“池子”里;在突然需要大量随机数的时候就从池子里取,以应付短时突发的需求而不会卡顿。因此我们只要把 /dev/hwrng 的随机数也放进 /dev/random 这个池子里就可以了。需要的程序在 rng-tools 软件包里就有:

sudo apt-get install rng-tools
sudo nano /etc/default/rng-tools
# 添加以下内容
HRNGDEVICE=/dev/hwrng
RNGDOPTIONS="--fill-watermark=50% --feed-interval=1"
# 保存,最后运行以下命令
sudo service rng-tools restart

HRNGDEVICE 一行的意思是设置额外随机数的来源,这里我们指定到硬件随机数发生器。有些教程建议设置成 /dev/urandom,但是如我们之前所说,urandom 不够安全,这样设置会影响系统的安全性。

RNGDOPTIONS 中 --fill-watermark 是在池子里随机数不足时,一口气将随机数补到给定的容量,这里设置成 50%。不设置成 100%,是因为各家硬件厂商实现随机数发生器的方式不同,并且一般均不公开,存在硬件随机数产生器中含有不为人知的后门的可能性。如果填满,相当于完全信任了硬件随机数发生器,可能会有安全隐患。

--feed-interval 是池子到达指定容量后,缓慢填充池子的间隔时间,一般设为 1 就好。

这样设置后 /dev/random 产生随机数的效率就会大幅提升了。

树莓派 3B 折腾笔记:BT 下载与策略路由

这次就直接进入正题。(其实是不知道该拿什么开头)

安装 Transmission 挂 PT

作为一个可以自由连接各种传感器的小型电脑,树莓派的可玩性相当高。即使你不想在 GPIO 上接一大堆传感器(或者像我一样觉得另外的传感器暂时没什么用),你也可以利用它低功耗的特点,让它 24 小时运行,做一些不需要大量 CPU 运算,但是因为其它因素需要较长时间才能完成的任务,例如……挂机下载。

我所在的大学有一个内网的 PT(Private Tracker)站。PT 站就是一个 BT 种子的发布网站,但是它在传统 BT 的基础上增加了用户管理功能,并且通过限制客户端种类、强制要求上传率等方式,解决了传统 BT 下各类客户端吸血(只下载不上传,例如迅雷)和种子很快失效(因为一段时间后就没人继续上传了)的问题。

但因为有了这些要求,PT 站用户往往需要长时间挂机上传下载,而这刚好是树莓派擅长的事。

上篇文章里,我弄好了一个简单的 NAS。在此基础上装一个 BT 下载软件就可以挂 PT 了。这个 PT 站在 Linux 下仅允许 Deluge,Rufus,Transmission 和 rTorrent。我一开始准备用 Deluge,但是 PT 站提示不允许使用 Raspbian 软件源内最新的 Deluge 1.3.13。该站推荐旧版本的 Deluge 1.3.3,可以在 Debian 7 软件源内找到,但是可能是因为 Debian 9 基于 systemd,我装上旧版后无法启动。

总之我选择了 Transmission。首先 apt-get:

apt-get install transmission-daemon

然后编辑 /etc/transmission-daemon/settings.json

# 屏蔽吸血客户端
"blocklist-enabled": true,
"blocklist-url": "http://john.bitsurge.net/public/biglist.p2p.gz",
# 修改默认下载位置
"download-dir": "/mnt/usb/Transmission",
# 用 PT 站一定要把这几项都关掉,如果用传统 BT 这里不用改
"dht-enabled": false,
"lpd-enabled": false,
"pex-enabled": false,
# 远程 Web 管理
"rpc-enabled": true,
"rpc-authentication-required": true,
"rpc-username": "用户名",
"rpc-password": "密码,启动 Transmission 后会被自动加密",

最后 service transmission-daemon start 启动后,访问 [树莓派 IP]:9091 可以查看 Web 管理界面,可以上传种子、调整限速等。

针对学校网络环境的调整

上篇文章中我提到过,学校提供了一个有线网和两个 Wi-Fi,它们各自有如下特点:

  1. 有线网,限速 1.5M,网页方式登录,其它设备局域网内可访问
  2. 无线网,限速 1.5M,网页方式登录,其它设备若通过无线网连接则无法访问(Wi-Fi 设备隔离,通过有线网仍可访问)
  3. eduroam,不限速,WPA2 企业级登录,其它设备若通过无线网连接则无法访问(Wi-Fi 设备隔离,通过有线网仍可访问)

由于树莓派有一个有线网卡和一个无线网卡,因此最佳的方案是,树莓派平时通过 eduroam 进行 PT 下载,我用自己电脑访问树莓派有线网卡的 IP 进行管理和访问文件。

但是如果你直接把有线网和无线网都连上,你会发现只有一张网卡有流量,另外一张网卡甚至无法 ping 通。这是因为 Linux 内核在收到连接请求(例如 TCP SYN)时,并不是“从哪来回哪去”,直接从连接请求来源网卡继续建立连接(TCP ACK)。相反,Linux 会根据内核的路由表来确定从哪张网卡回复。这样就会造成从有线网卡进入的请求被从无线网卡回复,然后回复因为设备隔离策略或是来源 IP 与网卡 IP 不符而被丢弃。

因此我们需要设置一下 Linux 的策略路由功能,做到“从哪来回哪去”。策略路由可以指定符合某些条件的数据包(例如从某张网卡进入的数据包)不经主路由表处理,而是单独开一张路由表处理它。这个功能早在《OpenVZ 配置 Hurricane Electric IPv6 隧道,开启整个地址池并与原生 IPv6 共同使用》里我就用过了。虽然当时是用在了 IPv6 地址上,但是 Linux 下 IPv4 和 IPv6 的网络命令都大致相同,因此稍微改一下就可以使用。

首先输入 route 命令查看路由表,你会看到这样的输出:

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         10.106.65.1     0.0.0.0         UG    202    0        0 eth0
default         10.107.128.1    0.0.0.0         UG    303    0        0 wlan0
10.106.65.0     0.0.0.0         255.255.255.0   U     400    0        0 eth0
10.107.128.0    0.0.0.0         255.255.240.0   U     303    0        0 wlan0

此处可以看到,有两条 Destination(目标)为 default(默认)的路由,一条对应有线网卡(eth0),一条对应无线网卡(wlan0)。它们的网关分别是 10.106.65.1 和 10.107.128.1,记录好它们。

再输入 ip addr 看一下现在的 IP。你会看到类似如下输出:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq state UP group default qlen 1000
    link/ether b8:27:eb:7c:5b:07 brd ff:ff:ff:ff:ff:ff
    inet 10.106.65.213/24 brd 10.106.65.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::62f6:bc87:f1f5:533a/64 scope link
       valid_lft forever preferred_lft forever
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq state UP group default qlen 1000
    link/ether b8:27:eb:29:0e:52 brd ff:ff:ff:ff:ff:ff
    inet 10.107.134.208/20 brd 10.107.143.255 scope global wlan0
       valid_lft forever preferred_lft forever
    inet6 fe80::f013:e96e:8451:7a94/64 scope link
       valid_lft forever preferred_lft forever

很长?其实重要的就几句话。我们只关心各张网卡的地址:

2: eth0: [...]
    inet 10.106.65.213/24 [...]
3: wlan0: [...]
    inet 10.107.134.208/20 [...]

这样就可以清楚地看到,有线网卡的地址是 10.106.65.213/24。因为 IPv4 的地址用 32 bit 表示,所以把最后(32-24=)8 bit 清零,获得 10.106.65.0/24。也就是说,你 DHCP 自动获取到的 IP 必然在 10.106.65.0/24 这个 IP 段以内。同理,无线网卡的地址是 10.107.134.208/20,清掉最后(32-20=)12 bit,获得 10.107.128.0/20。记好,等会也要用。

然后修改 /etc/iproute2/rt_tables,这里记录了路由表的列表,我们需要在这里加两张路由表,分别给两张网卡使用:

#
# reserved values
#
255    local
254    main
253    default
0    unspec
#
# local
#
#1    inr.ruhep

# 上面的都是 Raspbian 默认的设置,在文件末尾添加这两行:
100    university_eth
101    university_wlan

然后设置策略路由:

# 对于 university_eth 这张路由表,设置流量默认从 10.106.65.1(就是有线网卡的网关)走
ip route add default via 10.106.65.1 dev eth0 table university_eth
# 对于 university_wlan 这张路由表,设置默认走 10.107.128.1(无线网卡网关)
ip route add default via 10.107.128.1 dev wlan0 table university_wlan
# 对于来自 10.106.65.0/24(也就是刚才算出的有线网卡的 IP 段),走 university_eth 路由表
ip rule add from 10.106.65.0/24 table university_eth
# 对于来自 10.107.128.0/20(也就是刚才算出的无线网卡的 IP 段),走 university_wlan 路由表
ip rule add from 10.107.128.0/20 table university_wlan

输完这些命令,你的两张网卡就都能 ping 通了,“从哪来到哪去”完工。你可以把上面这四条命令加入 /etc/rc.local 以开机自动设置。如果开机时没有生效,在四行命令上面再加一行 sleep 5 就可以了——开机的时候有可能 Linux 网络功能还没启动完,因此先等 5 秒让网络功能启动了再继续设置。

最后一个问题,在同时连接了有线和无线的情况下,Linux 的 DHCP 客户端会给有线网卡设置更低的 Metric,使得流量优先从有线网卡走。Metric 就是 Linux 下每条路由的优先级,Metric 越低,优先级越高。但是我希望优先使用无线网卡,因此就要修改 DHCP 客户端的设置,给有线网卡更高的 Metric。

编辑 /etc/dhcpcd.conf

interface eth0    # 对于网卡 eth0(有线网卡)
metric 400        # 将它的 Metric 设置成 400(无线网卡默认是 303,任何比 303 大的数都可以)

保存重启,然后网络请求就都优先通过无线网卡发送了。

最终效果

树莓派下所有的下载软件均通过不限速的 eduroam Wi-Fi 进行下载。我用自己的电脑通过连接较慢的有线网卡的 IP 操作 SSH、Transmission,访问共享等,不影响 Wi-Fi 的传输速度,同时 1.5M 的限速也足以观看 1080p 分辨率的视频(特别高清的除外)。

树莓派 3B 折腾笔记

国庆放假期间我在某宝上买了一只树莓派 3B 和一堆传感器,准备搞一波事情。因为是国庆,所以各家店发货都有不同的延迟,导致我国庆长假后又过了好几天东西才到齐。

先上张完成体的图片:

插图

我分了五家店买了这些东西:

  1. 树莓派 3B(含外壳、风扇、散热片、电源套装)
  2. 闪迪 32G TF卡
  3. 树莓派传感器套装(支持树莓派的 3.3V 电平,总共 16 个)
  4. 5 寸 800x480 触摸屏
  5. DS3231 时钟模块和 GPIO 针脚标记板(后买)

树莓派本体

最先到的是树莓派 3B 本体,以及配套的外壳风扇散热片等等。然而我买的 TF 卡还没到……所以这只树莓派暂时什么都干不了。不过我还是可以把外壳风扇等等先组装起来。

然而我很快发现了问题:店家的外壳内支撑柱的位置不准,同一侧的两根柱子同时只能有一根插在树莓派电路板的孔里,另一根只能顶在电路板上,树莓派装不进去。

然后我就拿起剪刀把那两根柱子剪了。

插图

树莓派很顺利地放了进去。因为外壳大小刚好,所以没了柱子不影响树莓派的稳定性。

散热片居然是用双面胶粘在芯片上的,我不禁怀疑它们甚至会影响散热。或许之后我应该用硅脂把双面胶替换掉。

风扇要接在树莓派的 4、6 针脚上(分别是 5V 和接地),它也给我一种仅仅作为安慰剂存在的感觉。

上一下电,树莓派主板红灯亮起,风扇开始转动,说明启动成功。但因为 TF 卡还没到,树莓派什么都干不了,所以还得等等。

TF 卡

TF 卡在第二天到达。我只需要把树莓派的 Raspbian 系统 dd 进 TF 卡里就大功告成了。

首先去 https://www.raspberrypi.org/downloads/raspbian/ 下载 Raspbian。因为我打算自己装桌面,所以我下载了 Lite 版。

因为我自己用的是 Mac OS X,所以不需要装额外的软件。先是打开磁盘工具把 TF 卡卸载了,然后:

sudo dd bs=1m if=raspbian.img of=/dev/rdisk2 conv=sync

把卡从电脑上拔下来插进树莓派,上电,开机,一气呵成。

学校网络

但是……我是在学校里,所以我遇到了尴尬的情况:

  1. 学校的有线网络和无线网络 A 需要通过网页登录。
  2. 学校有 eduroam 无线网络,但是 eduroam 是 WPA2-EAP 企业级认证,需要输入用户名和密码。
  3. 我的显示屏还没到。
  4. 我有无线鼠标,但是没有无线键盘。

当务之急是先连进树莓派。那么用电脑把 Wi-Fi 共享到有线网端口就行了。不幸的是,Mac OS X 拒绝共享 WPA2-EAP 的无线网络,所以我只能先用一下 Windows。

进入控制面板-网络与共享中心-WiFi连接-属性,选上“允许其它用户通过此计算机的 Internet 连接来连接”,再选择你的有线网卡,类似下图:(图片来自网络)

插图

然后把树莓派用网线连到电脑上就可以了。那么怎么获知树莓派的 IP 呢?打开命令提示符,输入 arp -a,然后去找 IP 为 192.168.137.xxx 的设备,它就是树莓派,用 PuTTY 等工具连上去吧。默认用户名 pi,密码 raspberry。

连接 eduroam

又一个问题出现了:我的树莓派必须能不依赖电脑自己上网,否则我买它没什么意义,还不如直接买只 Arduino 接在 24 小时运行的电脑上。于是我 Google 了一通 wpa_supplicant 连接 WPA2-EAP 网络的配置,但是全都连不上。

我突然想到,我的手机是 Android 系统,Android 也是 Linux,连 Wi-Fi 同样是用 wpa_supplicant,把它的配置文件抄出来不就行了?于是我把手机的 /data/misc/wifi/wpa_supplicant.conf 里对应的段落抄进树莓派的 /etc/wpa_supplicant/wpa_supplicant.conf,成功连上了 eduroam。

配置文件如下:

network={
    ssid="eduroam"
    key_mgmt=WPA-EAP IEEE8021X
    eap=PEAP
    identity="用户名"
    password="密码"
    phase2=""
}

端口映射

如同大多数公共 Wi-Fi 一样,学校的无线网络禁止无线设备之间访问。所以我需要一个工具来帮我映射一下 SSH 端口,保证最基本的访问。

花生壳?看起来还可以,1M 的免费带宽给 SSH 也够了,但是即使是免费套餐也要付 6 块钱开通。

于是我找了两家国内的免费 ngrok 服务提供商,把 22 端口映射了出去。类似的服务商都有详细的步骤说明,且各家操作方法有一定区别,不再赘述。它们保证了基础访问,但是还是不太稳定。

最后我还是在自己的腾讯云主机上装了 frp 来进行端口映射。直接参照官方文档 操作即可。我还顺手在上面装了一个本博客之前经常提到的 ZeroTier One。

传感器

传感器终于到了。我先拿了一只 DHT11 温湿度传感器出来准备连上去。DHT11 总共有三个针脚,分别是 Vcc(3.3V 供电)、GND(地线)和 DATA(信号)。

和 Arduino 不同,树莓派的电源和数据针脚全部混在了一起,并且没有标示各个针脚用途。不过,树莓派里有软件可以查询。

首先 apt-get install wiringpi,再 gpio readall,你就能看到这样的输出:

 +-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 | ALT0 | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 | ALT0 | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |   IN | 1 |  7 || 8  | 1 | ALT5 | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | ALT5 | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI | ALT0 | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO | ALT0 | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK | ALT0 | 0 | 23 || 24 | 1 | OUT  | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | OUT  | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+

这就是各个针脚的意义,它们的状态,以及通过不同方式调用时的编号。你也可以买一块 GPIO 针脚标记板,它可以套在树莓派的 GPIO 上,标示出各个针脚的用途。

总之,把 Vcc 接到 1 号脚,GND 接到 9 号脚,DATA 接到 GPIO.7,传感器和树莓派就连通了。

下一步是读取数据。由于 DHT11 输出数据时以微秒为单位变化输出,并且规律有些复杂,我先用了现成的代码,例如 https://github.com/szazo/DHT11_Python 这个。git clone 下来后,编辑 dht11_example.py,改掉针脚编号,就能看到这样的输出:

Last valid input: 2017-10-13 18:37:13.232685
Temperature: 22 C
Humidity: 63 %

搞定。

触摸屏

触摸屏是最晚发货的一个,也是最晚到的。这块屏上有一个两排共 26 个针脚口(母座),可以直接插到树莓派上提供触摸信号,然后通过一个 HDMI 转接头连接树莓派和屏幕的 HDMI 接口就可以显示图像。

不过,要使用这块屏幕还是要费一些功夫的,例如这块屏幕支持且仅支持 800x480 分辨率,因此必须在 /boot/config.txt 里修改分辨率:

disable_overscan=1
framebuffer_width=800
framebuffer_height=480
hdmi_force_hotplug=1
hdmi_group=2
hdmi_mode=87
hdmi_cvt=800 480 60 6 0 0 0

并在 /etc/lightdm/lightdm.conf 里加上这句话,强制 800x480 分辨率:

display-setup-script=xrandr --output default --mode 800x480

另外这块屏幕设置触摸也要在 config.txt 里加几句话,但是店家产品详情里给的代码是!错!的!我启用触摸失败后 Google 了好久,直到我在树莓派官方论坛上看到了一个同样被坑的外国人的帖子。触摸芯片的型号是 ADS7846,而店家给成了 ADS7856,驱动加载失败,自然无法触摸。

总之,加上这两句话:

dtoverlay=ads7846,penirq=22,speed=100000,xohms=150
dtparam=spi=on

然后装个触摸驱动,并启动 LightDM:

sudo apt-get install xserver-xorg-input-evdev
sudo service lightdm start

触摸屏就可以用了。最后,配置一下长按作右键处理,就像手机上一样:

Section "InputClass"
    Identifier    "calibration"
    MatchProduct    "ADS7846 Touchscreen"
    Option    "Calibration"    "254 3911 153 3962"
    Option    "SwapAxes"    "0"

    Option "EmulateThirdButton" "1"
    Option "EmulateThirdButtonTimeout" "700"
    Option "EmulateThirdButtonMoveThreshold" "100"
EndSection

不过这块屏幕还是有一些不足的地方:

  1. 有时候会拖影。这个算小问题,毕竟屏幕还算便宜。
  2. 耗电巨大,树莓派上只连接了屏幕时,右上角还会时不时出现黄色闪电图标(代表电压不足),如果插上移动硬盘,闪电图标就常亮了。我可能需要一块额外的电源板来给它供电,但我某宝上暂时没找到合适的。
  3. 把 GPIO 的所有供电针脚全占了,而且在一大堆针脚空置的前提下整整占了 26 脚,导致我无法在无外部电源并且不焊接的情况下接传感器。

我又没法在寝室里焊接,对吧?emmmmm……(C)

于是我把它拔了然后暂时放了起来。等到需要用到的时候再装上用吧。

连接有线网

接下来我要把我的移动硬盘接上去,做一个简易 NAS。由于 NAS 流量较大,因此不能通过外网服务转发,必须直连。由于 Wi-Fi 下各设备有隔离,因此我只能把树莓派接到有线网上。

但是学校网络有登录页面,直接插上网线不登陆是连不上网的。并且即使 Wi-Fi 还连着,一旦插上网线,所有流量都会从有线网络走,端口映射等等也就断了。emmmmm……

我用自己的电脑登录学校网络,然后用浏览器的开发者工具监测网络,记录下登陆请求提交时 POST 的内容和地址。然后在树莓派上建立一个脚本,如果 ping 不通外网,就用 curl 模拟提交一次。然后 crontab 设置每分钟执行。

脚本内容如下:

#!/bin/bash
ping -c 1 -W 1 114.114.114.114 >/dev/null 2>/dev/null
if [ $? -eq 1 ]
then
    curl http://登陆页面 --connect-timeout 1 -F "key1=value1" -F "key2=value2"
fi

插上网线一分钟后,腾讯云主机的 frp 显示客户端连了上来。登进树莓派关掉无线网络(就是注释掉 wpa_supplicant.conf)里的那几条,搞定。

做 NAS

树莓派的有线网卡是 100M 而非 1G 的,而且它的 USB 都是 2.0 的,这也使得它不怎么适合做正经的 NAS。不过做一个玩玩级别的还是一点问题都没有的。

拔了屏幕后,我就有足够的电力去接移动硬盘了。插上移动硬盘,

mkfs.ext4 -E lazy_itable_init=0,lazy_journal_init=0 /dev/sda1
mount /dev/sda1 /mnt

分区挂载完成。然后是安装 Netatalk 做 AFP 文件共享和 Time Machine 备份盘。Raspbian 软件源自带的 netatalk 太老了,不能用。

你可以下载现成的 deb 或是自行编译。如果你要现成的,从 https://monal.im/netatalk/ 下载 netatalk 和 libatalk16 的 deb 文件并 dpkg -i *.deb 安装。

如果你要自己编译,参考 https://samuelhewitt.com/blog/2015-09-12-debian-linux-server-mac-os-time-machine-backups-how-to 这篇文章操作:

sudo apt-get install build-essential devscripts debhelper cdbs autotools-dev dh-buildinfo libdb-dev libwrap0-dev libpam0g-dev libcups2-dev libkrb5-dev libltdl3-dev libgcrypt11-dev libcrack2-dev libavahi-client-dev libldap2-dev libacl1-dev libevent-dev d-shlibs dh-systemd
git clone https://github.com/adiknoth/netatalk-debian
cd netatalk-debian
debuild -b -uc -us
cd ..

然后同样安装 netatalk 和 libatalk16 两个 deb。

安装完 Netatalk 后,再安装:

sudo apt-get install avahi-daemon libc6-dev libnss-mdns

修改 /etc/netatalk/afp.conf

[Global]
vol preset = default_for_all
log file = /var/log/netatalk.log
uam list = uams_dhx2.so,uams_clrtxt.so
save password = no

[default_for_all]
file perm = 0664
directory perm = 0775
cnid scheme = dbd
valid users = 用户名

[Homes]
basedir regex = /home

[TimeMachine]
time machine = yes
spotlight = no
path = /mnt/timemachine

然后在 Mac 的终端下运行:

defaults write com.apple.systempreferences TMShowUnsupportedNetworkVolumes 1

并在 Time Machine 里选择树莓派即可。如果你在 Time Machine 里找不到树莓派,那么在 Mac 下:

sudo tmutil setdestination "afp://用户名:密码@树莓派/Time Machine"

完工。不过,我睡前(12 点左右)开始备份,第二天早上 7:30 查看状态,还剩 13 小时……应该是学校网络限速的锅。

DS3231 时钟模块和 GPIO 针脚标记板

你的电脑关机再开机仍然保持准时,是因为主板上有一个时钟模块,在断电的情况下也在默默地走时。但是树莓派上没有,它每次开机都要网络对时。如果没有网络,时间就错乱了。

我买的那堆传感器里包括了一个 DS1307 时钟模块,但是没焊排针。我在学校里也没有电烙铁用,所以干脆再买一块 DS3231(8 块钱),顺便买了块 GPIO 针脚标记板(7 块钱,上面提到过)和一些杜邦线(5 块钱),解决针脚太多、担心数错的问题。

DS3231 安装极其简单,往 1、3、5、7、9 针脚上一插就行,不过 DHT11 就得换个位置:

插图

进入树莓派系统后,首先删掉 fake-hwclock:

sudo apt-get purge fake-hwclock

然后继续改 /boot/config.txt

dtoverlay=i2c-rtc,ds3231

注意如果你原来已经有了 dtoverlay,那么就要把这里的 dtoverlay 内容加到原有的 dtoverlay 里面去。例如我触摸屏+时钟模块的配置是:

dtoverlay=i2c-rtc,ds3231,ads7846,penirq=22,speed=100000,xohms=150 dtparam=spi=on

重启,sudo hwclock -r 可以读取时钟模块中的时间,这个时间肯定是错误的,因为没有和外界对过时。执行 sudo hwclock -w 写入时间后,再次读取,时间就正确了。之后即使树莓派在断网的情况下重启,也能保持走时准确。

总结

目前为止,我在树莓派上就折腾了这些。接下来我应该会往上面接更多的传感器,然后搞一个状态页面或者通知系统来监控它们。

不过,树莓派终究只能玩玩,不适合运行正式的应用。