Alpine Linux在树莓派启动”clock skew”报错

我在 树莓派环境安装Alpine Linux 遇到一个问题,树莓派自身没有RTC时钟设备,重启系统的时钟不准确,导致启动时提示时钟扭曲(时钟比文件系统的时间戳要早很多):

Clock skew detected with `(null)'
Adjusting mtime of '/run/openrc/deptree' to Mon Jan 10 21:44:22 2022
WARNING: clock skew detected!
...
Checking local filesystems ...
Filesystems couldn't be fixed
rc: Aborting!
fsck: caught SIGTERM, aborting
WARNING: clock skew detected!

这个问题导致启动后使用 date 检查时间显示的是上一次 date 启动时设置时间,这个时间和当前重启启动时间相去甚远,所以系统会显示时钟错误,无法读写挂载文件系统,也就导致后续一系列网卡无法启动,服务也无法 启动。问题是树莓派没有本地 rtc 设备,所以无法执行 hwclock -w --localtime 将手工修正的时间记录到BIOS。

这里有一个 悖论 :

  • Alpine Linux启动时会检查系统时间和文件系统时间戳,如果时钟不准确,就会拒绝以 读写模式 挂载文件系统(即使已经在 /etc/fstab 中配置根文件 rw 挂载)

  • 由于文件系统只读挂载, 就会拒绝运行 /etc/init.d 目录下的自动配置启动服务,包括网卡配置IP服务,无线网卡启动配置,同时 chronyd 服务无法启动(需要写入磁盘),这一系列不能启动网络和NTP客户端也就导致了系统无法自动校准时间

  • 无法自动校准时间,也就不能以读写模式挂载根文件系统,所以一切就只是只读,只能让用户手工从终端登陆(因为sshd服务也无法启动,没有网络)

手工恢复网络和服务

  • 先手工修正以下时间:

    date -s "2022-01-10 22:36:04"
    
  • 然后重启一次 chronyd 服务,让服务能够本地保留一份时钟矫正(注意,需要首先把文件系统改正为读写模式再启动):

    mount -o remount,rw /
    /etc/init.d/chronyd restart
    
  • 此时会提示检查文件系统(并挂载)以及启动网络:

    * Checking local filesystems ...
    * Remounting filesystems ...
    * Mounting local filesystems ...
    * Starting networking ...
    *   lo ...
    *   eth0 ...
    * Starting chronyd ...
    
  • 然后启动 设置Alpine Linux无线 通过internet矫正时间

我以为一切都解决了,毕竟 chronyd 也生成了 /var/lib/chrony/chrony.drift ,但是没有想到,树莓派系统重启依然出现 clock skew 报错,同样的问题再次出现

为树莓派添加 rtc 设备(硬件方法,未实践)

我使用的树莓派是 树莓派Raspberry Pi 3 ,这个设备没有提供RTC(Real Time Clock Module),所以树莓派关闭后时钟无法保持。 Alpine Linux on a Raspberry Pi 3 B+ with a RTC module 介绍了一种方法,是在 Raspberry Pi 3 B+ 的 GPIO pins 1, 3, 5, 7 & 9 插上一个 RTC 模块:

../../_images/ds3231-rtc-module-for-raspberry-pi.jpg

然后编辑 usercfg.txt 添加:

dtoverlay=i2c-rtc,ds3231

再重启就行(对于Alpine v3.13)。如果是早期版本,还需要执行以下步骤

  • 安装 mkinitfs 软件包:

    apk add mkinitfs
    
  • /etc/mkinitfs/mkinitfs.conf 添加 rpirtc ,类似:

    features="ata base cdrom ext4 keymap kms mmc raid scsi usb virtio rpirtc"
    

然后重新制作 initramfs 来添加 ds3231 设备内核模块:

# . /etc/lbu/lbu.conf
# ln -s /media/$LBU_MEDIA/boot /boot
# mount /media/$LBU_MEDIA -o remount,rw

# . /etc/mkinitfs/mkinitfs.conf
# mkinitfs -F "$features base squashfs"

# mount /media/$LBU_MEDIA -o remount,ro
  • 并激活 hwclock 服务:

    # rc-update del swclock boot
    # rc-update add hwclock boot
    # hwclock -w
    # lbu commit
    

没有 rtc 的软件解决方法

实际上树莓派硬件都没有包含硬件时钟设备,为何其他操作系统,例如 树莓派4b运行64位Ubuntu 没有遇到类似问题?我检查了 树莓派Raspberry Pi 4 上运行的 Ubuntu 系统:

dmesg -T | grep rtc

结果显示也没有硬件rtc,但是内核参数有一个 fixrtc 参数:

[    0.000000 ] Kernel command line:  coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1 bcm2708_fb.fbwidth=656 bcm2708_fb.fbheight=416 bcm2708_fb.fbswap=1 vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000  net.ifnames=0 dwc_otg.lpm_enable=0 console=ttyS0,115200 console=tty1 root=LABEL=writable rootfstype=ext4 elevator=deadline rootwait fixrtc cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 swapaccount=1 quiet splash

仔细看了

  • 需要激活软件时钟关闭硬件时钟:

    rc-update add swclock boot    # enable the software clock
    rc-update del hwclock boot    # disable the hardware clock
    

但是我实际执行发现原本就是如此:

# rc-update add swclock boot
 * rc-update: swclock already installed in runlevel `boot'; skipping
# rc-update del hwclock boot
 * rc-update: service `hwclock' is not in the runlevel `boot'

我在 Add hardware clock earlier Raspberry Pi 有一个提示,检查 /run/openrc/* 文件时间戳:

stat -c "%y %s %n" /run/openrc/*

我发现确实有一些文件时间差异很大:

1970-01-01 08:00:06.972999997 +0800 7 /run/openrc/clock-skewed
2022-01-11 06:05:33.950517700 +0800 120 /run/openrc/daemons
1970-01-01 08:00:06.965999997 +0800 11 /run/openrc/depconfig
2022-01-11 04:56:35.000000000 +0800 20183 /run/openrc/deptree
2022-01-11 06:05:33.977517440 +0800 40 /run/openrc/exclusive
1970-01-01 08:00:04.949999998 +0800 40 /run/openrc/failed
1970-01-01 08:00:04.950999998 +0800 40 /run/openrc/hotplugged
1970-01-01 08:00:04.949999998 +0800 40 /run/openrc/inactive
2022-01-11 06:05:33.962517585 +0800 100 /run/openrc/options
1970-01-01 08:00:04.950999998 +0800 40 /run/openrc/scheduled
2022-01-11 05:46:27.000000000 +0800 0 /run/openrc/shutdowntime
2022-01-11 05:46:27.594999999 +0800 7 /run/openrc/softlevel
2022-01-11 06:05:33.976517450 +0800 360 /run/openrc/started
2022-01-11 06:05:33.977517440 +0800 40 /run/openrc/starting
2022-01-11 05:51:21.307224589 +0800 40 /run/openrc/stopping
2022-01-11 05:51:01.161414046 +0800 0 /run/openrc/supervise-wpa_supplicant.ctl
1970-01-01 08:00:04.950999998 +0800 40 /run/openrc/tmp
1970-01-01 08:00:04.949999998 +0800 40 /run/openrc/wasinactive

其中最主要报错应该是 /run/openrc/deptree

  • /media/mmcblk0p1 下有一个系统默认 config.txt ,参考 Alpine boot process on the Raspberry Pi ,按照官方文档,添加一个 /media/mmcblk0p1/usercfg.txt 配置文档来自定义参数:

    enable_uart=1
    gpu_mem=32
    disable_overscan=1
    

但是并没有解决

还是参考 fixrtc kernel option always sets system time 添加内核参数 fixrtc ,也就是修订 /media/mmcblk0p1/cmdline 添加参数:

... fixrtc ...

但是还是没有解决,看来启动时候时间差异不大,但是这个时钟扭曲是因为操作系统关闭时修改了 /run/openrc/deptree 时间戳(启动时间),然后再次启动时钟又返回文件系统最后一次挂载时间,就会比 /run/openrc/deptree 早,系统就会判断问题。

树莓派时钟

树莓派时钟 使用 arch_sys_counter 作为时钟源,我对比了 树莓派4b运行64位Ubuntu 、 能够启动的 和 不能启动的 Alipine Linux for Raspberry Pi

cat /sys/devices/system/clocksource/clocksource0/current_clocksource

都显示:

arch_sys_counter

但是我也发现了一些差异:

  • 能够启动的 Alpine Linux for Raspberry Pi ,执行:

    cat /var/lib/chrony/chrony.drift
    

显示内容是:

5.131288             0.029364

而显示 clock skew 的 Alpine Linux for Raspberry Pi ,执行:

cat /var/lib/chrony/chrony.drift

显示内容:

rtcsync
cmdport 0

使用chrony同步时间(未解决)

使用chrony同步时间 发起一次时钟同步:

chronyd -q 'server pool.ntp.org iburst'

显示信息:

2022-01-11T00:36:09Z chronyd version 4.1 starting (+CMDMON +NTP +REFCLOCK +RTC +PRIVDROP -SCFILTER +SIGND +ASYNCDNS +NTS +SECHASH +IPV6 -DEBUG)
2022-01-11T00:36:15Z System clock wrong by 50630.833026 seconds (step)
2022-01-11T14:40:06Z chronyd exiting

然后再次检查:

cat /var/lib/chrony/chrony.drift

可以看到 drift 内容改为:

9.145591             0.442804
  • chronyd 设置为启动时运行:

    rc-update add chronyd boot
    

提示:

* service chronyd added to runlevel boot

但是,重启依然是 clock skew detected! ,看来不是这个原因。

文件系统修复(未解决)

将SD卡取下来,通过USB读卡器安装另外一台Linux主机上检查:

sudo tune2fs -l /dev/sdf2

显示:

sudo tune2fs -l /dev/sdf2 输出
tune2fs 1.45.5 (07-Jan-2020)
Filesystem volume name:   <none>
Last mounted on:          /
Filesystem UUID:          43be0bb8-720f-414c-a1f6-f5d8ade6a178
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize metadata_csum
Filesystem flags:         unsigned_directory_hash
Default mount options:    user_xattr acl
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              3883008
Block count:              15526144
Reserved block count:     776307
Free blocks:              15159586
Free inodes:              3879891
First block:              0
Block size:               4096
Fragment size:            4096
Group descriptor size:    64
Reserved GDT blocks:      1024
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         8192
Inode blocks per group:   512
Flex block group size:    16
Filesystem created:       Mon Jan 10 20:25:11 2022
Last mount time:          Tue Jan 11 23:06:17 2022
Last write time:          Mon Jan 10 22:02:38 2022
Mount count:              9
Maximum mount count:      -1
Last checked:             Mon Jan 10 20:25:11 2022
Check interval:           0 (<none>)
Lifetime writes:          1379 MB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:	          256
Required extra isize:     32
Desired extra isize:      32
Journal inode:            8
Default directory hash:   half_md4
Directory Hash Seed:      6dba10f5-c298-4026-8d0d-6c407d1fc407
Journal backup:           inode blocks
Checksum type:            crc32c
Checksum:                 0xf4b935de
  • 挂载文件系统:

    mkdir /media/sdf2
    mount /dev/sdf2 /media/sdf2
    
  • 检查该文件系统中文件时间戳,是否存在特异之处:

    cd /media/sdf2
    find . -exec stat -c "%y %s %n" {} \; | sort -k 1 -k 2
    

输出显示:

1970-01-01 08:00:05.000000000 +0800 8 ./etc/apk/arch
1970-01-01 08:00:06.000000000 +0800 11 ./lib/rc/cache/depconfig
2019-02-08 16:54:04.000000000 +0800 8226 ./usr/bin/lddtree
2019-06-12 21:05:11.000000000 +0800 34488 ./usr/sbin/iwgetid
2019-06-12 21:05:11.000000000 +0800 34488 ./usr/sbin/iwpriv
...
2021-11-16 18:44:09.000000000 +0800 58073 ./var/cache/apk/openssh-sftp-server-8.8_p1-r1.f3b53726.apk
2021-11-16 18:44:09.000000000 +0800 783374 ./var/cache/apk/openssh-client-common-8.8_p1-r1.2161786b.apk
2021-11-17 16:12:24.000000000 +0800 42728 ./sbin/fatlabel
2021-11-17 16:12:24.000000000 +0800 51432 ./sbin/mkfs.fat
2021-11-17 16:12:24.000000000 +0800 71408 ./sbin/fsck.fat
...
2022-01-11 23:13:50.000000000 +0800 0 ./lib/rc/cache/shutdowntime
2022-01-11 23:13:50.000000000 +0800 8 ./lib/rc/cache/softlevel
2022-01-11 23:13:51.242999652 +0800 4096 ./lib/rc/cache

比较奇特的是最早的2个文件是UNIX起始时间,其他看起来有比较早的2019年文件

  • 修订一遍文件时间戳:

    find . -exec touch {} \;
    

显示有3个文件不能访问:

touch: cannot touch './etc/localtime': No such file or directory
touch: cannot touch './etc/init.d/functions.sh': No such file or directory
touch: cannot touch './sbin/rc-sstat': No such file or directory

检查了一下,原来是软链接,由于挂载目录不是根目录,所以会无法直接访问到,可以忽略报错

但是,上述尝试并没有解决问题

临时的手工处理方法

虽然启动挂载文件系统问题导致只读,但是可以通过以下步骤暂时恢复:

mount -o remount,rw /
/etc/init.d/wpa_supplicant start
/etc/init.d/networking start
/etc/init.d/sshd start
chronyd -q 'server pool.ntp.org iburst'
/etc/init.d/chronyd start

最终解决方法

解决步骤一: OpenRC “clock skew” workaround

我仔细检查了原先 diskless (没有使用sys方式安装到磁盘) 的Alpine Linux启动信息,发现也是同样出现 clock skew 报错。只不过,对于diskless,默认就是 ro 只读挂载 /dev/mmcblk0p1 。所以,diskless模式启动即使存在 clock skew 也不影响运行。

  • 创建空文件:

创建 /etc/init.d/.use-swclock 空文件
sudo touch /etc/init.d/.use-swclock
  • 修改 /lib/rc/sh/init.sh ,在 mountproc 段落后添加:

在 mountproc 段落后面添加
...

if $mountproc; then
        ebegin "Mounting /proc"
        if ! fstabinfo --mount /proc; then
                mount -n -t proc -o noexec,nosuid,nodev proc /proc
        fi
        eend $?
fi

if [ -e /etc/init.d/.use-swclock ]; then
    "$RC_LIBEXECDIR"/sbin/swclock /etc/init.d
fi

...

备注

上述方法很巧妙,使得OpenRC使用系统时间设置 /etc/inid.d 的时间戳,这样就不会出现启动时候报错 Clock skew detected with '(null)'

解决步骤二: 关闭EXT4文件系统检查workaround

不过,虽然OpenRC不报错,但是在挂载文件系统时候依然出现异常:

Checking local filesystems ...
Filesystems couldn't be fixed
rc: Aborting!
fsck: caught SIGTERM, aborting

既然是文件系统检查错误,我们 可不可以放弃检查呢?

  • 修改 /etc/fstab ,将最后一列指示文件系统fsck的功能关闭:

配置/etc/fstab关闭磁盘fsck绕过文件系统时间扭曲
#UUID=ea1024e0-3e6f-4552-8ebd-18b775e9648b/ext4rw,rel            atime 0 1
UUID=ea1024e0-3e6f-4552-8ebd-18b775e9648b/ext4rw,relatime            0 0

然后重启系统,就不再出现文件系统不能挂载的问题了。不过,这个workaround是绕过了fsck,可能存在隐患

解决步骤三: 使用chrony同步时钟(我的环境需要,你不必)

上述两个步骤解决了alpine的启动问题,但是还是需要注意,一定要正确配置主机 chrony 进行时间同步,否则树莓派的时间不准确会导致各种部署问题。

我的局域网限制所以树莓派无法直接访问internet同步时间,这里设置了局域网内部NTP服务器作为时间同步源。如果你的树莓派能够直接访问internet上的NTP服务器,这步可以忽略

  • 修订 /etc/chrony/chrony.conf :

chrony客户端配置 /etc/chrony/chrony.conf
#pool pool.ntp.org iburst
#initstepslew 10 pool.ntp.org
pool 192.168.7.200 iburst
initstepslew 10 192.168.7.200
driftfile /var/lib/chrony/chrony.drift
rtcsync
cmdport 0

参考