晚上好 世界

晚上好 世界

早安

本文翻译自 https://web.archive.org/web/20230502010825/https://01.org/blogs/rzhang/2015/best-practice-debug-linux-suspend/hibernate-issues ,原文发布于2015年9月25日,请注意时效性。

作者 张锐 (Zhang Rui)陈宇 (CHEN, YU) 于 2015 年 9 月 25 日

1 概述

当谈论 Linux* 挂起/休眠时,我们指的是以下三种受支持的系统睡眠状态:

  • STI (挂起到空闲) 是一种通用的、纯软件的、轻量级的系统睡眠状态。结合平台特定的驱动程序增强功能,此状态可用于在 Intel® 平台上达到 S0i3 状态。要进入此状态,运行 shell 命令:echo freeze > /sys/power/state
  • STR (挂起到内存) 可显著节省功耗,因为系统中除内存外的所有部件都进入低功耗状态。内存应置于自刷新模式以保留其内容。ACPI 平台在挂起到 RAM 后处于 S3 状态。要进入此状态,运行 shell 命令:echo mem > /sys/power/state
  • 休眠 (挂起到磁盘) 的操作方式与挂起到 RAM 类似,但包括一个将内存内容写入磁盘的最终步骤。在恢复时,会读取此内容并将内存恢复到挂起前的状态。通常,ACPI 平台在挂起到磁盘后处于 S4 状态。要进入此状态,运行 shell 命令:echo disk > /sys/power/state

有关每个系统低功耗状态的更多详细信息,请参阅 https://www.kernel.org/doc/Documentation/power/states.txt

有关 Linux 挂起/休眠的 sysfs 接口的更多详细信息,例如上面提到的 /sys/power/state,请参阅 https://www.kernel.org/doc/Documentation/power/interface.txt

英特尔开源技术中心 (OTC) 中国内核电源团队多年来一直致力于改进 Linux 电源管理子系统的质量。我们处理通过 Linux 邮件列表中的电子邮件或通过 https://bugzilla.kernel.org/ 提交的问题的经验表明:

  • 效率低下。这是因为在大多数情况下,开发人员无法在本地重现问题。因此,我们依赖用户提供问题症状的详细描述、必要的调试信息以及一些测试结果(如果需要)。由于不同的时区,准备好进一步调试可能需要数天甚至数周时间。
  • 额外工作。这是因为 Linux STI/STR/HTD 是系统级的低功耗状态,需要硬件、固件、Linux PM 核心以及许多其他内核组件(尤其是设备驱动程序)良好地协同工作。这意味着任何单个组件中的单个错误都可能破坏系统挂起/休眠。对于由其他组件引起的问题,即使我们找到了根本原因,在大多数情况下我们仍然需要依赖组件所有者提供适当的修复。

因此,本文档旨在帮助用户执行一些调试例程,以便他们能够:

  • 尽可能找到根本原因或缩小问题范围。
  • 通过提供以下内容来正确地提出问题:
    • 问题的精确描述。
    • 必要调试例程的测试结果。
    • 精确的组件和所有者,例如:Linux PM 核心、驱动程序、BIOS 等。
    </li>

本文档结构

第 2 章介绍了一些可在第 4 章中使用的常用调试方法。

第 3 章基于我们维护 Linux PM 的经验,介绍了一些可能导致 Linux 挂起/休眠失败的典型问题。

第 4 章根据问题的不同症状提供了分步说明。

因此,当用户遇到与挂起/休眠相关的问题时,我们建议用户在第 4 章中找到描述所遇问题的相应部分,并直接按照该章节的分步说明进行操作。

2 常见的挂起/休眠调试方法

这里列出了一些对遇到挂起/休眠相关问题的人员有用的常见调试方法。

2.1 initcall_debug

initcall_debug 启动选项添加到内核命令行(cmdline)将跟踪启动、挂起和恢复期间的初始化调用(initcalls)和驱动程序电源管理(pm)回调。它对于检查任何指定的驱动程序/组件是否失败非常有用。在调试 STI/STR/HTD 相关问题时,请确保始终启用它。通常,正向结果意味着回调没有错误/警告,并在合理的时间内返回 0。如下所示:

[   76.201970] calling 0000:00:02.0+ @ 2298, parent: pci0000:00
[   76.217006] call 0000:00:02.0+ returned 0 after 14677 usec

负向结果意味着回调要么包含一些错误/警告,要么需要不合理的长长时间才能完成。

2.2 no_console_suspend

no_console_suspend 启动选项添加到内核命令行可禁用挂起/休眠期间控制台的挂起。一旦添加此选项,调试消息可以在系统其余部分进入睡眠状态时到达各种控制台。这可能无法与所有控制台可靠地工作,但已知可与串行和 VGA 控制台一起工作。

2.3 ignore_loglevel

ignore_loglevel 启动选项添加到内核命令行会将所有内核消息打印到控制台,无论当前日志级别是什么,这对调试很有用。

2.4 dynamic debug (动态调试)

对于 STI/STR,添加 dyndbg="file suspend.c +p" 启动选项;对于 HTD,添加 file swap.c +p;file snapshot.c +p;file hibernate.c +p 启动选项可以启用动态调试。这样我们可以在休眠期间收集更多信息。

2.5 serial console (串行控制台)

串行控制台输出对于跟踪某些系统挂起问题非常有用,特别是在 VGA 控制台关闭或无法记录完整崩溃日志时。要启用串行控制台,在内核命令行中添加 console=ttyS0,115200no_console_suspend,然后在另一台机器上使用 minicom 或 GNU screen 等程序将串行端口也设置为 115200 8-N-1 以捕获日志。更多详细信息,请参阅 https://www.kernel.org/doc/Documentation/serial-console.txt

2.6 rtc trace (RTC 跟踪)

rtc trace 是一种功能,用于在挂起/恢复期间以“文件+行号”的形式捕获有问题的驱动程序,将其保存到 CMOS SRAM 中,并在下次启动时恢复。要启用此功能,请确保您的内核使用 CONFIG_PM_TRACE_RTC=y 构建,并且通过 echo 0 > /sys/power/pm_async 禁用 pm 异步。更多详细信息,请参考 https://www.kernel.org/doc/Documentation/power/s2ram.txt

测试结果:从失败的 STI/STR/HTD 重启后的 dmesg 输出。注意,重启必须在三分钟内发生,否则保存在 SRAM 中的数据将被破坏。

2.7 pm_async

目前,Linux 并行地挂起和恢复所有设备以节省时间。通过 echo 0 > /sys/power/pm_async 禁用异步挂起/恢复可用于检查挂起/休眠失败是否由某些未知的设备依赖性问题引起。这也可用于调整驱动程序挂起/恢复延迟。

测试结果:检查禁用异步挂起/恢复后问题是否仍然存在。

2.8 pm_test

pm_test 是一种调试方法,系统可以部分执行 STR/HTD,可用于缩小导致 STI/STR/HTD 失败的代码范围。请参阅 https://www.kernel.org/doc/Documentation/power/basic-pm-debugging.txt 获取更多详细信息。

测试结果:检查哪些测试模式有问题,哪些测试模式没有问题。如果可能,记录失败模式的 dmesg 输出。

2.9 ACPI wakeup (ACPI 唤醒)

/proc/acpi/wakeup 列出了所有 ACPI 设备的唤醒能力,如果可用,还引用了物理设备。如下例所示操作此文件可以启用/禁用设备的唤醒能力。例如,您可以通过执行以下操作来禁用/启用“enabled”/“disabled”设备:

root@linux-machine# cat /proc/acpi/wakeup
Device  S-state   Status   Sysfs node
...
EHC1      S4    *enabled   pci:0000:00:1d.0
...
root@linux-machine# echo EHC1 > /proc/acpi/wakeup
root@linux-machine# cat /proc/acpi/wakeup
Device  S-state   Status   Sysfs node
...
EHC1      S4    *disabled  pci:0000:00:1d.0
...

测试结果:禁用和启用每个设备的唤醒能力是否能解决问题。

2.10 acpidump

acpidump 是一个可用于转储 BIOS 提供的 ACPI 表的工具。这在调试可能由 ACPI 引起的挂起/休眠问题时很有帮助。要获取该工具,只需进入内核源代码的 tools/power/acpi/ 目录并运行 “make”。

2.11 rtcwake

rtcwake 是一个可用于进入系统睡眠状态(挂起/休眠)直到指定唤醒时间的工具。您可以轻松地使用它来执行挂起/休眠压力测试。例如,您可以使用这个简单的脚本轻松运行 1000 次 STR 循环:

for i in $(seq 1000); do
    rtcwake –m mem –s 30
done

2.12 analyze_suspend

analyze_suspend 工具为系统开发人员提供了可视化挂起和恢复之间活动的能力,使他们能够识别低效环节和瓶颈。例如,您可以使用以下命令启动:

./analyze_suspend.py -rtcwake 30 -f -m mem

30 秒后系统会自动恢复,并在 ./suspend-yymmddyy-hhmmss 目录中生成 3 个文件:

mem_dmesg.txt  mem_ftrace.txt  mem.html

您可以首先用浏览器打开 mem.html 文件,然后深入研究 mem_ftrace.txt 以获取数据细节。您可以通过 git 获取 analyze_suspend 工具:

git clone https://github.com/01org/suspendresume.git

更多详细信息,请访问主页:https://01.org/suspendresume

测试结果:mem_dmesg.txt mem_ftrace.txt mem.html

2.13 disk mode (磁盘模式)

这仅用于 HTD。尝试运行 echo {shutdown/reboot} > /sys/power/disk 以在运行 echo disk > /sys/power/state 之前跳过某些可能存在错误的平台特定代码。在 ACPI 平台上,使用此选项时,系统实际上进入的是 S5 状态而不是 S4 状态。

测试结果:当 /sys/power/disk 等于 shutdown 或 reboot 时,问题是否仍然存在。

3 典型的挂起/休眠问题

3.1 回归 (Regression)

回归是指挂起/休眠以前工作良好,但在升级内核后失败。在这种情况下,找到根本原因并解决问题的最快、最有效的方法是使用 git bisect 找出是哪个提交引入了问题,然后将问题报告给提交作者/组件所有者。

3.2 驱动程序特定问题

有问题的驱动程序可能导致:

  • 系统无法挂起(挂起命令返回错误代码)。
  • 系统在挂起/恢复期间挂起。
  • 系统挂起/恢复时间过长。
  • 挂起/恢复后在 dmesg 中生成调试跟踪/错误日志。
  • 驱动程序本身无法工作。

因此,当您遇到挂起/休眠相关问题时,通常首先要验证问题是否是驱动程序特定问题。如果在恢复/挂起失败后系统有响应(您可以通过 VGA/串行控制台、ssh 等访问系统),请尝试使用 initcall_debugno_console_suspend 启动选项重现问题。如果有,请执行以下操作:

  1. 将驱动程序构建为模块
  2. 卸载驱动程序
  3. 挂起并恢复机器
  4. 使用以下命令重新加载驱动程序:modprobe -r foo && echo mem > /sys/power/state & modprobe foo
  5. 如果驱动程序不可卸载,则通过 Kconfig 设置完全不构建该驱动程序。

如果问题无法重现,并且错误/警告在此之后不再出现,那么这就是一个驱动程序特定问题。

如果系统无响应,请执行以下操作:

  1. 启用串行控制台并使用 initcall_debugno_console_suspend 启动。
  2. 检查串行控制台输出中是否有驱动程序引起的错误/警告。
  3. 如果发现任何错误/警告,将驱动程序构建为模块。
  4. 卸载驱动程序。
  5. 如果驱动程序无法在运行时卸载,则通过 Kconfig 设置完全不构建该驱动程序。
  6. 挂起并恢复机器。
  7. 重新加载驱动程序:modprobe -r foo && echo mem > /sys/power/state & modprobe foo

如果问题无法重现,并且错误/警告在这些步骤之后不再出现,那么就是这些驱动程序导致了问题。

如果系统恢复后工作正常,但某些驱动程序不再工作,请执行以下操作:

  1. 将驱动程序构建为模块。
  2. 卸载驱动程序
  3. 挂起并恢复机器
  4. 重新加载驱动程序:modprobe -r foo && echo mem > /sys/power/state & modprobe foo

如果问题不可重现,则这是一个驱动程序特定问题。

对于这些问题,请直接向驱动程序/组件所有者提交错误报告,因为这些问题表明有问题的驱动程序/组件破坏了 Linux 挂起/休眠。

以下部分讨论了一些可能破坏挂起/休眠的典型驱动程序特定问题。

3.2.1 图形问题

每当系统在挂起/休眠期间挂起,或者恢复后显示器不再显示时,请尝试以下步骤:

  1. 禁用您正在使用的内核图形驱动程序。在 Intel 平台上,设置 CONFIG_DRM_I915=n
  2. 注意: 不要在命令行中使用 nomodeset modprobe.blacklist=i915,因为有时 i915 驱动程序可能会被其他组件探测到。
  3. 再次尝试挂起/恢复。
  4. 如果恢复后看到黑屏且没有图形显示,请尝试通过串行控制台或 SSH 访问系统。如果系统仍在工作,则这是一个图形问题。
  5. 如果串行控制台和 SSH 都不可用,请按 PS/2 键盘上的 Num Lock 键。如果按下该键时 Num Lock LED 灯亮起,则使用键盘键入 reboot。虽然显示器没有显示,但如果系统成功重启,则很可能系统工作正常,这是一个图形问题。

对于图形问题,请在 https://bugs.freedesktop.org/ 提交错误报告。例如,我们通过 dmesg 输出中的以下提示行将挂起/休眠问题的根本原因隔离到图形驱动程序:

 

[  218.176542] ------------[ cut here ]------------
[  218.176550] WARNING: CPU: 1 PID: 221 at drivers/gpu/drm/i915/intel_lrc.c:1100 gen8_init_rcs_context+0x15d/0x160()
[  218.176552] WARN_ON(w->count == 0)
[  218.176553] Modules linked in:
[  218.176556] CPU: 1 PID: 221 Comm: kworker/u16:3 Tainted: G        W      3.18.0-eywa-dirty #30
[  218.176558] Hardware name: Intel Corporation Skylake Client platform/Skylake Y LPDDR3 RVP3, BIOS SKLSE2P1.86C.B060.R01.1411140050 11/14/2014
[  218.176564] Workqueue: events_unbound async_run_entry_fn
[  218.176567]  0000000000000009 ffff88009a96fad8 ffffffff82431683 0000000000000001
[  218.176570]  ffff88009a96fb28 ffff88009a96fb18 ffffffff8111e001 00000000fffe6b2d
[  218.176573]  ffff88009b42f300 0000000000000005 ffff88009b670000 ffff88009b671ce8
[  218.176574] Call Trace:
...
[  218.176657] ---[ end trace f587fddb962240b1 ]---

3.2.2 音频问题

我们尚未发现音频驱动程序会破坏挂起/休眠,但音频驱动程序本身可能需要很长时间才能恢复,如下例所示:

[   61.273112] calling  hdaudioC0D2+ @ 1236, parent: 0000:00:1f.3
[   64.295757] snd_hda_intel 0000:00:1f.3: azx_get_response timeout, switching to polling mode: last cmd=0x208f8100
[   65.303916] snd_hda_intel 0000:00:1f.3: No response from codec, disabling MSI: last cmd=0x208f8100
[   66.312147] snd_hda_intel 0000:00:1f.3: azx_get_response timeout, switching to single_cmd mode: last cmd=0x208f8100
[   66.312402] azx_single_wait_for_response: 153 callbacks suppressed
[   66.323406] snd_hda_codec_hdmi hdaudioC0D2: Unable to sync register 0x2f0d00. -5
[   66.323791] snd_hda_codec_hdmi hdaudioC0D2: HDMI: invalid ELD buf size -1
[   66.324179] snd_hda_codec_hdmi hdaudioC0D2: HDMI: invalid ELD buf size -1
[   66.324565] snd_hda_codec_hdmi hdaudioC0D2: HDMI: invalid ELD buf size -1
[   66.324571] call hdaudioC0D2+ returned 0 after 4932188 usecs

在这种情况下,请提交一个音频错误报告

3.2.3 USB 问题

通常,USB 问题不会导致系统挂起,但某些 USB 设备可能在系统恢复后停止工作。如果某些 USB 设备(如 USB 鼠标/键盘/网络)在系统恢复后停止工作,而其他设备(如 PS/2 键盘)似乎工作正常,请检查 dmesg/串行日志中是否有任何 USB 警告。如果发现警告,这很可能是一个 USB 问题。例如:

[   21.203077] xhci-hcd: probe of xhci-hcd.1.auto failed with error -110

在这种情况下,提交一个USB 错误报告

3.2.4 mmc 问题

MMC 故障可能导致挂起操作,尤其是挂起到磁盘,失败。检查挂起期间 dmesg 和串行控制台输出中的 MMC 警告。例如:

[   90.373541] mmc1: Timeout waiting for hardware interrupt.
[  160.493633] mmc1: error -110 during resume (card was removed?)

如果根文件系统(rootfs)不在 MMC 上,请尝试使用 "modprobe.blacklist=mmc_block" 启动选项再次测试。如果根文件系统在 MMC 上,请使用类似 USB 驱动器的方式启动相同的内核/发行版,并在命令行中使用 modprobe.blacklist=mmc_block 再次尝试。如果在黑名单中排除 MMC 驱动程序后问题消失,请提交一个MMC 错误报告

3.2.5 i2c 问题

我们也遇到过几个由 I2C 引起的错误。例如:

calling i2c_designware.0+ @ 2867, parent: 0000:00:15.0
------------[ cut here ]------------
WARNING: CPU: 1 PID: 2867 at drivers/clk/clk.c:845 __clk_disable+0x5b/0x60()
[] warn_slowpath_null+0x1a/0x20
[] __clk_disable+0x5b/0x60
[] clk_disable+0x37/0x50
[] dw_i2c_suspend+0x29/0x40

在这种情况下,提交一个I2C 错误报告

4 调试挂起/休眠问题

4.1 状态不可用

4.1.1 症状

运行 cat /sys/power/state 的输出中没有列出 "freeze"/"mem"/"disk",因此系统无法进入 STI/STR/HTD 状态。

4.1.2 调试步骤

  1. 对于 STI:
    • 检查内核是否使用 CONFIG_SUSPEND 构建。如果没有,请设置它。
    • 检查内核是否使用内核参数 relative_sleep_states=1 启动。如果是,请删除它。
  2. 对于 STR:
    • 检查内核是否使用 CONFIG_SUSPEND 构建。如果没有,请设置它。
    • 检查启动后的 dmesg 输出中是否有类似 ACPI: (supports S0 S3 S4 S5) 的行。如果未列出 S3,通常意味着平台不支持 S3。如果不确定,请提交一个挂起/休眠错误报告。
  3. 对于 HTD:
    • 检查内核是否使用 CONFIG_HIBERNATION 构建。如果没有,请使用 CONFIG_HIBERNATION=y 重新构建。
    • 检查启动后的 dmesg 输出中是否有类似 ACPI: (supports S0 S3 S4 S5) 的行。如果未列出 S4,通常意味着平台不支持 S4。如果不确定,请提交一个挂起/休眠错误报告。
  4. 如果上述测试后问题仍然存在,请提交一个挂起/休眠错误报告 [platform] {STI, STR, HTD}: state not available,并附上您正在使用的 dmesg 输出、acpidump 输出和内核配置文件。

4.2 挂起/休眠失败

4.2.1 症状

命令 echo freeze/mem/disk > /sys/power/state 返回错误代码。

4.2.2 调试步骤

  1. 检查这是否是回归问题。
  2. 如果不是,启用 initcall_debug 和 no_console_suspend。
  3. 如果发现某些设备回调返回错误代码而不是 0,这意味着该设备无法挂起,导致系统挂起/休眠中止。在这种情况下,向驱动程序/组件所有者提交错误报告。
  4. 如果不是,请提交一个挂起/休眠错误报告 [Platform] {STI, STR, HTD}: failed to suspend,并附上挂起失败后的 dmesg 输出以及返回的错误代码。

4.3 系统在挂起/休眠或恢复期间挂起

4.3.1 症状

发出 echo freeze/mem/disk > /sys/power/state 后,屏幕变黑,像成功挂起/休眠一样,但随后系统变得无响应,并且无法唤醒到工作状态。这可能发生在挂起或恢复期间。或者,系统按预期进入睡眠状态,但在恢复时没有响应,只显示黑屏或崩溃日志。例如,风扇开始旋转,任何现有的电源按钮背光发生变化,但系统永远不会恢复到工作状态。

4.3.2 调试步骤

  1. 检查这是否是回归问题。
  2. 如果不是,检查这是否是图形问题。
  3. 如果不是,检查这是否是驱动程序特定问题。
  4. 如果问题仍然存在,请提交一个挂起/休眠错误报告 [Platform] {STI, STR, HTD}: system hangs during suspend/resume,并附上问题重现后的串行控制台输出/屏幕截图、/var/log/kern.log 的内容以及磁盘模式(仅 HTD)、pm_test、pm_async 和 rtc trace 的测试结果。

4.4 系统在挂起/恢复期间重启

4.4.1 症状

屏幕变黑,并且在没有用户操作的情况下开始全新启动。

4.4.2 步骤

  1. 检查这是否是回归问题。
  2. 检查这是否是图形问题。
  3. 检查这是否是驱动程序特定问题。
  4. 如果不是,请提交一个挂起/休眠错误报告 [Platform] {STI, STR, HTD}: system reboot during suspend/resume,并附上问题重现后的串行控制台输出:/var/log/kern.log,以及磁盘模式(仅 HTD)、pm_test、pm_async 和 rtc trace 的测试结果。

4.5 恢复后系统错误

4.5.1 症状

系统可以挂起到 STI/STR/HTD 状态并从其恢复,但某些组件或驱动程序可能无法正常工作,或者在恢复后的 dmesg 中可能有错误或警告。

4.5.2 调试步骤

  1. 检查这是否是回归问题。
  2. 如果不是,检查这是否是驱动程序特定问题。
  3. 如果问题仍然存在,请提交一个挂起/休眠错误报告 [Platform] {STI, STR, HTD}: xxx broken after resume,并附上恢复后的 dmesg 输出。

4.6 挂起后系统立即唤醒

4.6.1 症状

系统在挂起后不久即返回工作状态,用户未进行任何操作。并且 dmesg 显示系统已完成完整的挂起/恢复周期。

4.6.2 调试步骤

  1. 检查这是否是回归问题。
  2. 通过移除任何不必要的外设(如 USB 驱动器、网线等)来检查这是否是驱动程序特定问题。
  3. 如果不是,请提交一个挂起/休眠错误报告 [Platform] {STI, STR, HTD}: system wakes up immediately after suspend,并附上 acpidump 输出、立即恢复后的 dmesg、挂起前后 /proc/interrupts 的内容以及 ACPI wakeup 的测试结果。

4.7 系统无法唤醒

4.7.1 症状

当您按下电源按钮、使用键盘等时,系统不会唤醒。

4.7.2 调试步骤

  1. 检查这是否是回归问题。对于 STI,检查驱动程序是否有自己管理的中断。如果有,检查是否为驱动程序使用的中断调用了 enable_irq_wake()。如果没有,请在驱动程序的 .probe() 或 .suspend() 回调中添加它。例如:
    static int bu21013_suspend(struct device *dev)
    {
        ...
        if (device_may_wakeup(&client->dev))
            enable_irq_wake(bu21013_data->irq);
        else
            disable_irq(bu21013_data->irq);
        ...
        return 0;
    }
  2. 如果问题仍然存在,请为 [Platform] {STI, STR, HTD}: XXX cannot wake up the system 提交一个挂起/休眠错误报告。确保在错误报告中包含 dmesg、acpidump 和 ACPI wakeup 的测试结果。

4.8 挂起/恢复期间延迟过长 (STI/STR/休眠)

4.8.1 症状

整体/特定驱动程序的挂起/恢复时间比预期/要求的要长。

4.8.2 调试步骤

  1. 如果您已经确定某个特定驱动程序挂起/恢复花费了太多时间,请向驱动程序所有者提交错误报告。
  2. 如果整体挂起/恢复延迟很高,请执行以下操作:
    1. 使用 initcall_debug 和 no_console_suspend 启动选项重启。
    2. 禁用异步挂起/恢复。
    3. 使用 analyze_suspend 重新进行挂起/恢复。
    4. 检查测试结果,找出在挂起/恢复期间花费最多时间的驱动程序和组件,并将问题连同 analyze_suspend 的测试结果一起报告给驱动程序/组件所有者。
    </li>

4.9 挂起/休眠偶尔失败

4.9.1 症状

症状可以是上面列出的任何症状,但区别在于这些问题不是 100% 可重现的。有时可能需要数十次甚至数百次挂起/休眠循环才能重现。

4.9.2 调试步骤

  1. 对于 HTD,
    1. 检查用于交换分区的驱动程序。
    2. 如果驱动程序构建为模块,请将驱动程序内置并检查问题是否仍然存在。
    3. 如果不存在,请提交一个挂起/休眠错误报告 [Platform] HTD: hibernate fails when disk driver is built as module
    4. 如果存在,请继续使用以下步骤进行调试。
    </li>
    <li>使用 initcall_debug、no_console_suspend、ignore_loglevel、dynamic debug 和 serial console 重启。</li>
    <li>使用 rtcwake 执行压力测试。</li>
    <li>当挂起/休眠失败时,按照上面最符合症状的章节中的说明进行验证并提出问题。</li>

5 提出问题

如果您已经缩小了问题范围,或者发现问题的根本原因是驱动程序特定问题:

  • 如果您知道在哪里为指定的驱动程序提出问题,请直接在那里提出。例如,对于图形问题,请在 https://bugs.freedesktop.org/ 提交错误报告。
  • 如果不清楚在何处提交问题,推荐的方式是在查阅http://vger.kernel.org/vger-lists.html后订阅相关子系统/驱动邮件列表,并在该列表中提出问题。提交问题时,请抄送[email protected]

如果不是驱动程序特定的问题,或者在调试后仍不确定问题所在,请在 https://bugzilla.kernel.org/enter_bug.cgi?product=Power%20Management 提交错误报告。将组件设置为“Hibernation/Suspend”,并包含所需的调试信息和测试结果。


在Linux系统中,分区文件系统是存储管理的两个关键层级:

  1. 分区:物理磁盘的逻辑划分,由分区表(GPT/MBR)定义边界

    • 操作工具:fdisk, cfdisk, parted
  2. 文件系统:在分区上构建的数据组织结构(ext4, Btrfs, XFS等)

    • 操作工具:mkfs, resize2fs, btrfs

cfdisk的本质:分区表手术刀

重要警告cfdisk 只操作分区表,不触及文件系统!它如同城市规划师重划地块边界,但不管地块上建筑物(文件系统)的死活。

危险操作示例

# 灾难性操作流程:
cfdisk /dev/sda      # 将sda1分区从100G缩小到30G
reboot               # 重启后系统崩溃
e2fsck /dev/sda1     # 报错:超级块损坏!

原因:文件系统实际占用50G空间,但分区被缩小到30G,尾部20G数据被物理截断。


安全分区调整四步法

步骤1:检查文件系统

sudo e2fsck -f /dev/sda1  # 强制检查ext4
sudo btrfs check /dev/sda2 # Btrfs检查

步骤2:缩小文件系统(如需缩小分区)

# ext4示例(缩小到40G):
sudo resize2fs /dev/sda1 40G

# Btrfs示例(先缩小文件系统):
sudo btrfs filesystem resize 40G /mount/point

步骤3:计算最小安全值

# ext4获取最小尺寸
MIN_BLOCKS=$(sudo dumpe2fs /dev/sda1 | grep "Block count" | awk '{print $3}')
BLOCK_SIZE=$(sudo dumpe2fs /dev/sda1 | grep "Block size" | awk '{print $3}')
MIN_SIZE=$((MIN_BLOCKS * BLOCK_SIZE / 1024**2)) # 转换为MB
echo "最小安全尺寸: ${MIN_SIZE}MB"

步骤4:使用cfdisk调整分区

sudo cfdisk /dev/sda

操作指南:

  1. 选择目标分区 → Resize
  2. 输入尺寸 ≥ MIN_SIZE (单位可用G/M,如+40G)
  3. 确认:WriteQuit

分区与文件系统操作对比表

操作类型工具示例影响范围风险等级必备前置操作
分区调整cfdisk fdisk修改分区表⚠️ 高危文件系统缩小/备份
文件系统调整resize2fs btrfs修改数据结构⚠️ 中危e2fsck/btrfs check
文件操作rm cp mv用户数据✅ 低危
元数据操作chmod chown文件属性✅ 低危

灾难恢复方案

情况1:分区误缩小但未重启

# 恢复分区表(需提前备份)
sudo sfdisk /dev/sda < partition-backup.txt

# 或使用testdisk恢复
sudo testdisk /dev/sda

情况2:文件系统已损坏

# ext4恢复超级块
sudo e2fsck -b 32768 /dev/sda1  # 使用备份超级块

# Btrfs恢复
sudo btrfs rescue zero-log /dev/sda2
sudo btrfs rescue fix-device-size /dev/sda2

终极数据抢救

# 创建磁盘镜像
sudo dd if=/dev/sda of=/mnt/external/sda.img conv=noerror,sync

# 使用Photorec扫描
sudo photorec /d /mnt/recovery/ /mnt/external/sda.img

最佳实践清单

  1. 分区操作黄金法则

    备份 → 检查 → 缩文件系统 → 缩分区 → 验证
  2. 必备备份命令

    # 分区表备份
    sudo sfdisk -d /dev/sda > sda-partition-table.bak
    
    # 文件系统备份
    sudo dd if=/dev/sda1 of=sda1-backup.img bs=4M status=progress
    
    # 数据备份
    sudo rsync -aAXv /home/ /backup/home/
  3. 安全调整工作流

    graph LR
    A[备份分区表] --> B[备份数据]
    B --> C[缩小文件系统]
    C --> D[计算最小尺寸]
    D --> E[cfdisk调整分区]
    E --> F[检查文件系统]
    F --> G[重启验证]

专家建议

"操作分区如同在飞行中更换引擎 - 必须遵循严格规程。永远记住:分区是地基,文件系统是建筑。重划地基时,必须先拆除上层的建筑(缩小文件系统)"

  • Linux存储工程师准则

最后忠告:对于生产系统:

  1. 优先考虑LVM动态分区
  2. 使用Btrfs/ZFS等支持在线调整的文件系统
  3. 虚拟机中先测试操作流程
  4. 确保UPS供电(笔记本充满电+插电)

理解分区与文件系统的层次关系,遵循安全操作流程,就能避免"分区缩小惨案"。存储无小事,操作需谨慎!


昨天我开开心心地写着代码,想删除文件夹下全部内容,但是在文件夹下敲了 rm -rf ~/*,回车,隔了几秒还没运行完,反而出现了某些文件无法删除的警告,意识过来,我艹,天塌了。

那种从云顶直接跌入谷底的感觉,唉 555555555555555 TwT.....

试图补救

问问 ai 如何恢复,ai给了一些工具,这里可以用能恢复文件名的 ext4magic 、extundelete 。

由于我没有及时 立刻关闭我的电脑(只要长按电源键或直接拔插头即可,不要使用系统的关机功能) ,这些工具没有起到作用。

其实之前我是经常用 Pika备份 的。但备份到了一个 U盘 ,备份时得插 U 盘,久而久之就懒得插,懒得备份了。

所以备份这个事情是需要长期坚持下去的,总是懒可不行。。。

为了让此类烦心事不再发生:

  • 弃用 rm 。

    # /home/voyage200/.config/fish/config.fish
    alias rm="echo 不要使用 rm 。请使用 gio trash (别名giot)代替。"
    alias giot="gio trash"
    
  • 哪怕管理系统时要用 rm ,也不加 -f 参数。使用 -I
  • 为/home专门分区,从 ext4 改用可以打快照的btrfs文件系统
  • 在此之上,最最重要的文件使用 Pika备份 定时备份到电脑的第二块物理硬盘上
  • 重要项目放 github 等等云端上,不可以只留在本地

用中国银行Visa外币卡 绑定google cloud时,会要求你验证六位数验证码:

在8月5日或前后几天的卡交易记录中,找到Google向Visa****收取的临时费用。输入GOOGLE*XRK旁边的6位数验证码。

收到了银行的短信,但是并没有在中国银行APP里看到对应账单:

您的借记卡3326于0806发生双信息无卡消费交易,金额8.00 港币,已冻结8.16港币。中国银行

这个时候,你可以打开

https://support.google.com/cloud/contact/verify?hl=zh-Hans

发起申诉,如实填写即可。

申诉


文章由 ds-r1 翻译自 https://github.com/tevador/RandomX/blob/master/doc/design.md

RandomX 设计

为了最小化专用硬件的性能优势,工作量证明 (PoW) 算法必须通过针对现有通用硬件的特定功能来实现设备绑定。这是一项复杂的任务,因为我们必须针对来自不同制造商、具有不同架构的一大类设备。

通用处理设备分为两大类:中央处理器 (CPU) 和图形处理器 (GPU)。RandomX 针对 CPU,原因如下:

  • CPU 作为专用性较低的设备,更为普及且易于访问。受 CPU 限制的算法更具平等性,允许更多参与者加入网络。这是原始 CryptoNote 白皮书 [1] 中阐述的目标之一。
  • 不同 CPU 架构之间存在一个庞大的通用原生硬件指令子集。而 GPU 则无法做到这一点。例如,NVIDIA 和 AMD GPU 之间没有通用的整数乘法指令 [2]。
  • 所有主要的 CPU 指令集都有完善的文档记录,并有多个开源编译器可用。相比之下,GPU 指令集通常是专有的,并且可能需要特定供应商的闭源驱动程序才能获得最大性能。

1. 设计考量

受 CPU 限制的工作量证明最基本的思想是,“工作”必须是动态的。这利用了 CPU 接受两种输入的事实:数据(主要输入)和代码(指定对数据执行什么操作)。

相反,典型的加密哈希函数 [3] 并不代表适合 CPU 的工作,因为它们唯一的输入是数据,而操作序列是固定的,可以通过专用的集成电路更高效地执行。

1.1 动态工作量证明

一个动态工作量证明算法通常可以由以下 4 个步骤组成:

1) 生成一个随机程序。
2) 将其翻译成 CPU 的原生机器码。
3) 执行该程序。
4) 将程序的输出转换为加密安全的值。

实际的“有用”的受 CPU 限制的工作在第 3 步中执行,因此必须调整算法以最小化其余步骤的开销。

1.1.1 生成随机程序

早期动态工作量证明设计的尝试基于用高级语言(如 C 或 Javascript [4, 5])生成程序。然而,这效率非常低,主要有两个原因:

  • 高级语言语法复杂,因此生成有效程序相对较慢,因为它需要创建抽象语法树 (AST)。
  • 一旦生成程序源代码,编译器通常会将文本表示解析回 AST,这使得生成源代码的整个过程变得多余。

生成随机程序最快的方法是使用无逻辑生成器——简单地用随机数据填充缓冲区。这当然需要设计一种无语法的编程语言(或指令集),其中所有随机比特串都代表有效的程序。

1.1.2 将程序翻译成机器码

这一步是不可避免的,因为我们不希望将算法限制在特定的 CPU 架构上。为了尽可能快地生成机器码,我们需要我们的指令集尽可能接近原生硬件,同时仍然足够通用以支持不同的架构。在代码编译期间没有足够的时间进行昂贵的优化。

1.1.3 执行程序

实际的程序执行应尽可能利用 CPU 的多个组件。程序中应利用的一些功能包括:

  • 多级缓存 (L1, L2, L3)
  • μop 缓存(微操作缓存)[6_cache)]
  • 算术逻辑单元 (ALU)
  • 浮点单元 (FPU)
  • 内存控制器
  • 指令级并行 (ILP) [7]

    • 超标量执行 [8]
    • 乱序执行 [9]
    • 推测执行 [10]
    • 寄存器重命名 [11]

第 2 章描述了 RandomX 虚拟机 (VM) 如何利用这些特性。

1.1.4 计算最终结果

Blake2b [12] 是一种加密安全的哈希函数,专门设计为在软件中快速运行,尤其是在现代 64 位处理器上,其速度大约是 SHA-3 的三倍,并且可以以每字节输入约 3 个时钟周期的速度运行。该函数是用于对 CPU 友好的工作量证明的理想候选者。

对于以加密安全的方式处理更大量的数据,高级加密标准 (AES) [13] 可以提供最快的处理速度,因为许多现代 CPU 支持这些操作的硬件加速。有关 AES 在 RandomX 中使用的更多详细信息,请参见第 3 章。

1.2 “简单程序问题”

当生成一个随机程序时,人们可能会选择仅在有利时才执行它。这种策略是可行的,主要有两个原因:

  1. 随机生成的程序的运行时间通常遵循对数正态分布 [14](另见附录 C)。可以对生成的程序进行快速分析,如果它可能具有高于平均水平的运行时间,则可以跳过程序执行并生成一个新程序。这可以显著提高性能,特别是在运行时间分布具有重尾(许多运行时间长的异常值)且程序生成成本低廉的情况下。
  2. 实现可以选择针对程序执行所需功能的子集进行优化。例如,可以放弃对一些操作(如除法)的支持,或者可以更高效地实现某些指令序列。然后会对生成的程序进行分析,并且仅当它们符合优化实现的特定要求时才执行。

这些寻找具有特定属性程序的策略偏离了此工作量证明的目标,因此必须消除。这可以通过要求执行一系列 N 个随机程序链来实现,其中每个程序都是由前一个程序的输出生成的。然后使用最终程序的输出作为结果。

          +---------------+     +---------------+               +---------------+     +---------------+
          |               |     |               |               |               |     |               |
输入 --> |   程序 1      | --> |   程序 2      | -->  ...  --> | 程序 (N-1)   | --> |   程序 N      | --> 结果
          |               |     |               |               |               |     |               |
          +---------------+     +---------------+               +---------------+     +---------------+

其原理是,在执行完第一个程序后,矿工必须要么承诺完成整个链(其中可能包含不利的程序),要么重新开始并浪费在未完成链上所付出的努力。附录 A 给出了这如何影响不同挖矿策略的哈希率示例。

此外,这种链式程序执行具有均衡整个链运行时间的好处,因为相同分布运行时间之和的相对偏差减小了。

1.3 验证时间

由于工作量证明的用途是在无需信任的点对点网络中使用,网络参与者必须能够快速验证证明是否有效。这对工作量证明算法的复杂性设定了上限。特别是,我们为 RandomX 设定了一个目标:其验证速度至少要与它旨在取代的 CryptoNight 哈希函数 [15] 一样快。

1.4 内存硬度

除了纯粹的计算资源(如 ALU 和 FPU)之外,CPU 通常还可以访问大量 DRAM(动态随机存取存储器)[16] 形式的内存。内存子系统的性能通常被调整以匹配计算能力,例如 [17]:

  • 嵌入式低功耗 CPU:单通道内存
  • 桌面 CPU:双通道内存
  • 工作站 CPU:三通道或四通道内存
  • 高端服务器 CPU:六通道或八通道内存

为了利用外部内存以及片内内存控制器,工作量证明算法应访问一个大的内存缓冲区(称为“数据集”)。数据集必须满足:

  1. 大于可以存储在片上的容量(以需要外部内存)。
  2. 动态的(以需要可写内存)。

使用 16 纳米工艺,单个芯片上可放置的最大 SRAM 超过 512 MiB;使用 7 纳米工艺,则超过 2 GiB [18]。理想情况下,数据集的大小至少应为 4 GiB。然而,由于验证时间的限制(见下文),RandomX 选择使用的数据集大小为 2080 MiB。虽然使用当前技术(2019 年的 7 纳米)理论上可以制造出具有此 SRAM 容量的单芯片,但至少在不久的将来,这种解决方案的可行性是值得怀疑的。

1.4.1 轻客户端验证

虽然要求专用挖矿系统拥有 >2 GiB 的内存来解决工作量证明是合理的,但必须为轻客户端提供使用更少内存量来验证证明的选项。

“快速”模式和“轻量”模式所需内存的比率必须谨慎选择,以免轻量模式适合挖矿。特别是,轻量模式的空间-时间(AT)乘积不应小于快速模式的 AT 乘积。AT 乘积的减少是衡量折中攻击 [19] 的常用方法。

鉴于前面章节描述的约束,快速验证模式和轻量验证模式之间最大可能的性能比经验证确定为 8。这是因为:

  1. 进一步增加轻量验证时间将违反第 1.3 章规定的约束。
  2. 进一步减少快速模式的运行时间将违反第 1.1 章规定的约束,特别是程序生成和结果计算的开销时间会变得过高。

此外,选择 256 MiB 作为轻客户端模式所需的最大内存量。这个量对于 Raspberry Pi 等小型单板计算机来说也是可以接受的。

为了保持恒定的内存-时间乘积,快速模式的最大内存需求为:

8 * 256 MiB = 2048 MiB

这可以进一步增加,因为轻量模式需要额外的芯片面积用于 SuperscalarHash 函数(参见规范第 3.4 章和第 6 章)。假设每个 SuperscalarHash 核心的保守估计面积为 0.2 mm²,DRAM 密度为 0.149 Gb/mm² [20],则额外内存为:

8 * 0.2 * 0.149 * 1024 / 8 = 30.5 MiB

或四舍五入到最接近的 2 次幂为 32 MiB。快速模式的总内存需求可以是 2080 MiB,同时保持大致恒定的 AT 乘积。

2. 虚拟机架构

本节描述 RandomX 虚拟机 (VM) 的设计。

2.1 指令集

RandomX 使用固定长度的指令编码,每条指令 8 字节。这允许在指令字中包含一个 32 位的立即数值。指令字位的解释经过选择,使得任何 8 字节字都是有效指令。这允许非常高效的随机程序生成(参见第 1.1.1 章)。

2.1.1 指令复杂度

该 VM 是一个复杂指令集 (CISC) 机器,允许寄存器和内存寻址操作数。然而,每条 RandomX 指令仅转换为 1-7 条 x86 指令(平均 1.8 条)。保持指令复杂度相对较低以最小化具有定制指令集的专用硬件的效率优势非常重要。

2.2 程序

VM 执行的程序采用由 256 条随机指令组成的循环形式。

  • 256 条指令足够长,可以提供大量可能的程序,并为分支提供足够的空间。可以生成的不同程序数量限制为 2512 = 1.3e+154,这是随机生成器可能种子值的数量。
  • 256 条指令足够短,使得高性能 CPU 执行一次迭代所需的时间与从 DRAM 获取数据所需的时间相似。这是有利的,因为它允许数据集访问同步并可完全预取(参见第 2.9 章)。
  • 由于程序是一个循环,它可以利用某些 x86 CPU 中存在的 μop 缓存 [6_cache)]。从 μop 缓存运行循环允许 CPU 关闭 x86 指令解码器,这应有助于平衡 x86 架构与具有简单指令解码的架构之间的能效。

2.3 寄存器

VM 使用 8 个整数寄存器和 12 个浮点寄存器。这是在常见 64 位 CPU 架构中(x86-64 的架构寄存器数量最少)可以分配为物理寄存器的最大值。使用更多的寄存器会使 x86 CPU 处于劣势,因为它们将不得不使用内存来存储 VM 寄存器内容。

2.4 整数运算

RandomX 使用所有具有高输出熵的基本整数运算:加法 (IADD_RS, IADD_M)、减法 (ISUB_R, ISUB_M, INEG_R)、乘法 (IMUL_R, IMUL_M, IMULH_R, IMULH_M, ISMULH_R, ISMULH_M, IMUL_RCP)、异或 (IXOR_R, IXOR_M) 和循环移位 (IROR_R, IROL_R)。

2.4.1 IADD_RS

IADD_RS 指令利用了 CPU 的地址计算逻辑,并且可以被大多数 CPU(x86 lea,arm add)在单个硬件指令中执行。

2.4.2 IMUL_RCP

由于整数除法在 CPU 中没有完全流水线化,并且可以在 ASIC 中做得更快,因此 IMUL_RCP 指令要求每个程序只进行一次除法来计算倒数。这迫使 ASIC 包含一个硬件除法器,同时不会在程序执行期间给它们带来性能优势。

2.4.3 IROR_R/IROL_R

循环移位指令在右移和左移之间分配,比例为 4:1。右移频率更高,因为某些架构(如 ARM)本身不支持左移(必须使用右移来模拟)。

2.4.4 ISWAP_R

该指令可以被支持寄存器重命名/移动消除的 CPU 高效执行。

2.5 浮点运算

RandomX 使用双精度浮点运算,大多数 CPU 都支持双精度,并且比单精度需要更复杂的硬件。所有操作都作为 128 位向量运算执行,这也得到所有主要 CPU 架构的支持。

RandomX 使用 IEEE 754 标准保证给出正确舍入结果的五种运算:加法、减法、乘法、除法和平方根。使用了标准定义的所有 4 种舍入模式。

2.5.1 浮点寄存器组

浮点运算的域被分成“加法”运算(使用寄存器组 F)和“乘法”运算(使用寄存器组 E)。这样做是为了防止在将小数加到大数时加法/减法变成空操作。由于 F 组寄存器的范围限制在 ±3.0e+14 左右,因此加或减绝对值大于 1 的浮点数总是会改变至少 5 个小数位。

因为 F 组寄存器的有限范围允许使用更高效的定点表示(使用 80 位数字),所以 FSCAL 指令操作浮点格式的二进制表示以使这种优化更加困难。

E 组寄存器被限制为正值,这避免了 NaN 结果(例如负数的平方根或 0 * ∞)。除法仅使用内存源操作数,以避免被优化为乘以常数倒数。E 组内存操作数的指数设置为 -255 到 0 之间的值,以避免除以 0 和乘以 0,并增加可获得的数字范围。E 组寄存器值的近似范围是 1.7E-77无穷大 (infinity)

每个程序循环结束时浮点寄存器值的近似分布如下图所示(左图 - F 组,右图 - E 组):

Imgur

(注:区间由左侧值标记,例如标记为 1e-40 的区间包含从 1e-401e-20 的值。)

F 组寄存器值在 1e+14 处的小数目是由 FSCAL 指令引起的,该指令显著增加了寄存器值的范围。

E 组寄存器覆盖了非常大的数值范围。大约 2% 的程序至少产生一个 无穷大 (infinity) 值。

为了最大化熵,同时也为了适应一个 64 字节的缓存行,浮点寄存器在每个迭代结束时使用异或 (XOR) 操作组合后存储到暂存器 (Scratchpad) 中。

2.6 分支

现代 CPU 投入了大量的芯片面积和能量来处理分支。这包括:

  • 分支预测单元 [21]
  • 检查点/回滚状态,允许 CPU 在分支预测错误时恢复。

为了利用推测设计,随机程序应包含分支。然而,如果分支预测失败,推测执行的指令将被丢弃,这会导致每次预测错误都会浪费一定量的能量。因此,我们应致力于最小化预测错误的分支数量。

此外,代码中的分支至关重要,因为它们显著减少了可以进行的静态优化的数量。例如,考虑以下 x86 指令序列:

    ...
branch_target_00:
    ...
    xor r8, r9
    test r10, 2088960
    je branch_target_00
    xor r8, r9
    ...

XOR 操作通常会相互抵消,但由于分支的存在,不能进行优化,因为如果分支被采用,结果将会不同。同样,如果不是因为分支,ISWAP_R 指令总是可以被静态优化掉。

通常,随机分支的设计必须满足:

  1. 不可能出现无限循环。
  2. 预测错误的分支数量很少。
  3. 分支条件依赖于运行时值,以禁用静态分支优化。

2.6.1 分支预测

不幸的是,我们尚未找到在 RandomX 中利用分支预测的方法。因为 RandomX 是一个共识协议,所有规则必须预先设定,这包括分支规则。完全可预测的分支不能依赖于任何 VM 寄存器的运行时值(因为寄存器值是伪随机且不可预测的),因此它们必须是静态的,从而容易被专用硬件优化。

2.6.2 CBRANCH 指令

因此,RandomX 使用随机分支,其跳转概率为 1/256,并且分支条件依赖于一个整数寄存器值。这些分支将被 CPU 预测为“不跳转 (not taken)”。在大多数 CPU 设计中,除非发生跳转,否则这类分支是“免费的”。虽然这没有利用分支预测器,但与非推测性分支处理相比,推测性设计将看到显著的性能提升——更多信息参见附录 B。

分支条件和跳转目标的选择方式确保了在 RandomX 代码中不可能出现无限循环,因为控制分支的寄存器在重复的代码块中永远不会被修改。每条 CBRANCH 指令最多可以连续跳转两次。使用谓词执行 [22)] 来处理 CBRANCH 是不切实际的,因为大多数情况下分支不被采用。

2.7 指令级并行

CPU 利用多种利用代码指令级并行性的技术来提高性能。这些技术包括:

  • 拥有多个可以并行执行操作的执行单元(超标量执行)。
  • 不按程序顺序执行指令,而是按操作数可用的顺序执行(乱序执行)。
  • 预测分支的走向,以增强超标量和乱序执行的益处。

RandomX 受益于所有这些优化。详细分析见附录 B。

2.8 暂存器 (Scratchpad)

暂存器用作读写内存。其大小被选择为完全适合 CPU 缓存。

2.8.1 暂存器层级

暂存器分为 3 级,以模拟典型的 CPU 缓存层次结构 [23]。大多数 VM 指令访问“L1”和“L2”暂存器,因为 L1 和 L2 CPU 缓存靠近 CPU 执行单元,并提供最佳随机访问延迟。从 L1 和 L2 读取的比例为 3:1,这与典型延迟的反比相匹配(见下表)。

CPU μ架构L1 延迟L2 延迟L3 延迟来源
ARM Cortex A5526-[24]
AMD Zen+41240[25]
Intel Skylake41242[26#Memory_Hierarchy)]

L3 缓存要大得多,并且距离 CPU 核心更远。因此,其访问延迟要高得多,并可能导致程序执行停滞。

因此,RandomX 每次程序迭代仅执行 2 次对“L3”暂存器的随机访问(规范第 4.6.2 章中的步骤 2 和 3)。来自给定迭代的寄存器值被写入它们加载的相同位置,这保证了所需的缓存行已被移动到更快的 L1 或 L2 缓存中。

此外,从固定地址读取的整数指令也使用整个“L3”暂存器(规范表 5.1.4),因为重复访问将确保缓存行被放置在 CPU 的 L1 缓存中。这表明暂存器级别并不总是直接对应于相同的 CPU 缓存级别。

2.8.2 暂存器写入

在 VM 执行期间,暂存器以两种方式被修改:

  1. 在每次程序迭代结束时,所有寄存器值被写入“L3”暂存器(参见规范第 4.6.2 章,步骤 9 和 11)。这每次迭代总共写入 128 字节,分为两个 64 字节块。
  2. ISTORE 指令执行显式存储。平均每个程序有 16 次存储,其中 2 次存储进入“L3”级别。每条 ISTORE 指令写入 8 字节。

下图显示了在哈希计算期间对暂存器写入分布的示例。图像中的每个像素代表暂存器的 8 字节。红色像素代表在哈希计算期间至少被覆盖一次的暂存器部分。“L1”和“L2”级别在左侧(几乎完全被覆盖)。暂存器的右侧代表底部的 1792 KiB。其中只有大约 66% 被覆盖,但写入是均匀且随机分布的。

Imgur

暂存器熵分析见附录 D。

2.8.3 读写比率

程序每次迭代平均进行 39 次读取(指令 IADD_M, ISUB_M, IMUL_M, IMULH_M, ISMULH_M, IXOR_M, FADD_M, FSUB_M, FDIV_M)和 16 次写入(指令 ISTORE)到暂存器。每次迭代额外隐式读写 128 字节以初始化并存储寄存器值。每次迭代从数据集 (Dataset) 读取 64 字节数据。总计:

  • 每次程序迭代从内存读取的平均数据量为:39 * 8 + 128 + 64 = 504 字节
  • 每次程序迭代写入内存的平均数据量为:16 * 8 + 128 = 256 字节

这接近 2:1 的读/写比率,这是 CPU 优化的目标。

2.9 数据集 (Dataset)

由于暂存器通常存储在 CPU 缓存中,因此只有数据集访问会利用内存控制器。

RandomX 每次程序迭代从数据集随机读取一次(每个哈希结果 16384 次)。由于数据集必须存储在 DRAM 中,它提供了一个自然的并行化限制,因为 DRAM 每个存储体组 (bank group) 每秒最多只能进行约 2500 万次随机访问。每个独立寻址的存储体组允许约 1500 H/s 的吞吐量。

所有数据集访问读取一个 CPU 缓存行(64 字节)并且是完全预取的。执行规范第 4.6.2 章中描述的一次程序迭代所需的时间大约等于典型的 DRAM 访问延迟(50-100 ns)。

2.9.1 缓存 (Cache)

用于轻量验证和数据集构建的缓存 (Cache) 比数据集小约 8 倍。为了保持恒定的空间-时间乘积 (AT),每个数据集项由 8 次随机缓存访问构造而成。

因为 256 MiB 足够小,可以包含在片内,所以 RandomX 使用自定义的高延迟、高功耗混合函数(“SuperscalarHash”),它抵消了使用低延迟内存的优势,并且计算 SuperscalarHash 所需的能量使得轻量模式用于挖矿效率非常低下(参见第 3.4 章)。

使用少于 256 MiB 内存是不可能的,因为使用了具有 3 次迭代的抗折衷攻击 (tradeoff-resistant) 的 Argon2d。当使用 3 次迭代(遍数)时,将内存使用减半会使最佳折衷攻击 [27] 的计算成本增加 3423 倍。

3. 自定义函数

3.1 AesGenerator1R

AesGenerator1R 设计用于尽可能快地生成伪随机数据以填充暂存器。它利用了现代 CPU 中硬件加速的 AES。每输出 16 字节仅执行一轮 AES,这导致在大多数现代 CPU 中吞吐量超过 20 GB/s。

AesGenerator1R 在初始化状态足够“随机”时能提供良好的输出分布(参见附录 F)。

3.2 AesGenerator4R

AesGenerator4R 使用 4 轮 AES 为程序缓冲区初始化生成伪随机数据。由于 2 轮 AES 足以实现所有输入位的完全雪崩效应 [28],AesGenerator4R 具有优异的统计特性(参见附录 F),同时保持非常好的性能。

该生成器的可逆特性不是问题,因为生成器状态总是使用不可逆哈希函数 (Blake2b) 的输出进行初始化。

3.3 AesHash1R

AesHash 设计用于尽可能快地计算暂存器指纹。它将暂存器解释为一组 AES 轮密钥,因此相当于进行了 32768 轮的 AES 加密。最后执行额外的两轮以确保每个通道 (lane) 中的所有暂存器位都发生雪崩效应。

AesHash1R 的可逆特性不是问题,主要有两个原因:

  • 无法直接控制 AesHash1R 的输入。
  • AesHash1R 的输出被传递到不可逆的 Blake2b 哈希函数中。

3.4 SuperscalarHash

SuperscalarHash 设计用于在 CPU 等待从 DRAM 加载数据时消耗尽可能多的功耗。170 个周期的目标延迟对应于通常 40-80 ns 的 DRAM 延迟和 2-4 GHz 的时钟频率。为具有低延迟内存的轻量模式挖矿设计的 ASIC 设备在计算数据集项时将被 SuperscalarHash 所瓶颈,并且其效率将被 SuperscalarHash 的高功耗所破坏。

平均 SuperscalarHash 函数总共包含 450 条指令,其中 155 条是 64 位乘法。平均而言,最长的依赖链是 95 条指令长。一个用于轻量模式挖矿的 ASIC 设计,具有 256 MiB 片内内存和所有操作 1 周期延迟,在假设无限并行化的情况下,平均需要 95 8 = 760 个周期来构建一个数据集项。它必须为每个项执行 155 8 = 1240 次 64 位乘法,这将消耗与从 DRAM 加载 64 字节相当的能量。

附录

A. 链接 VM 执行的效果

第 1.2 章描述了为什么链接 N 个随机程序以防止寻找“简单”程序的挖矿策略。RandomX 使用 N = 8

让我们将 Q 定义为使用过滤策略时可接受程序的比率。例如 Q = 0.75 表示 25% 的程序被拒绝。

对于 N = 1,没有浪费的程序执行,唯一的成本是程序生成和过滤本身。下面的计算假设这些成本为零,唯一的实际成本是程序执行。然而,这是一种简化,因为在 RandomX 中程序生成并非免费(第一个程序生成需要完整的暂存器初始化),但它描述了攻击者的最佳情况。

对于 N > 1,第一个程序可以像往常一样被过滤,但在程序执行后,有 1-Q 的几率下一个程序应该被拒绝,这样我们就浪费了一次程序执行。

对于 N 次链接执行,只有 QN 的几率链中的所有程序都是可接受的。然而,在每次尝试找到这样一条链的过程中,我们将浪费执行一些程序。对于 N = 8,每次尝试浪费的程序数量等于 (1-Q)*(1+2*Q+3*Q2+4*Q3+5*Q4+6*Q5+7*Q6)(当 Q = 0.75 时约为 2.5)。

让我们考虑 3 种挖矿策略:

策略 I

诚实的矿工,不拒绝任何程序 (Q = 1)。

策略 II

使用优化的定制硬件的矿工,无法执行 25% 的程序 (Q = 0.75),但受支持的程序可以快 50% 执行。

策略 III

可以执行所有程序的矿工,但拒绝链中第一个程序运行时间最慢的 25%。这为链中第一个程序提供了 5% 的性能提升(这与附录 C 中的运行时间分布相匹配)。

结果

下表列出了上述 3 种策略针对不同 N 值的结果。列 N(I), N(II)N(III) 列出了每种策略平均需要执行的程序数量以获得一个有效的哈希结果(这包括在拒绝链中浪费的程序)。列 Speed(I), Speed(II)Speed(III) 列出了相对于策略 I 的平均挖矿性能。

NN(I)N(II)N(III)Speed(I)Speed(II)Speed(III)
11111.001.501.05
222.321.001.281.02
446.541.000.921.01
8827.081.000.441.00

对于 N = 8,策略 II 的执行速度不到诚实矿工的一半,尽管对选定程序具有 50% 的性能优势。策略 III 的微小统计优势在 N = 8 的情况下可以忽略不计。

B. 性能模拟

如第 2.7 章所讨论的,RandomX 旨在利用现代高性能 CPU 的复杂设计。为了评估超标量、乱序和推测执行的影响,我们进行了简化的 CPU 模拟。源代码可在 perf-simulation.cpp 中找到。

CPU 模型

模型 CPU 使用 3 级流水线来实现每周期 1 条指令的理想吞吐量:

        (1)                        (2)                     (3)
+------------------+       +----------------+      +----------------+
|   指令取指       |       |                |      |                |
|     + 解码       | --->  | 内存访问       | ---> |    执行        |
|                  |       |                |      |                |
+------------------+       +----------------+      +----------------+

3 个阶段是:

  1. 指令取指和解码。此阶段从程序缓冲区加载指令并解码指令操作和操作数。
  2. 内存访问。如果该指令使用内存操作数,则在此阶段从暂存器加载。这包括内存地址的计算。存储也在此阶段执行。地址寄存器的值必须在此阶段可用。
  3. 执行。此阶段使用前几个阶段检索到的操作数执行指令,并将结果写入寄存器文件。

请注意,这是一个乐观的短流水线,不允许非常高的时钟速度。使用更长流水线的设计将显著增加推测执行的好处。

超标量执行

我们的模型 CPU 包含两种组件:

  • 执行单元 (EXU) - 用于执行实际的整数或浮点操作。除 ISTORE 外的所有 RandomX 指令都必须在第 3 流水线阶段使用执行单元。所有操作都被认为仅需 1 个时钟周期。
  • 内存单元 (MEM) - 用于加载和存储到暂存器。所有内存指令(包括 ISTORE)在第 2 流水线阶段使用内存单元。

超标量设计将包含多个执行或内存单元以提高性能。

乱序执行

模拟模型支持两种设计:

  1. 按序 - 所有指令按照它们在程序缓冲区中出现的顺序执行。如果遇到依赖关系或所需的 EXU/MEM 单元不可用,此设计将暂停。
  2. 乱序 - 不按程序顺序执行指令,而是当指令的操作数就绪且所需的 EXU/MEM 单元可用时执行该指令。

分支处理

模拟模型支持两种分支处理类型:

  1. 非推测性 - 遇到分支时,流水线暂停。这通常为每个分支增加 3 个周期的惩罚。
  2. 推测性 - 所有分支被预测为“不跳转”,如果发生预测错误,则刷新流水线(概率为 1/256)。

结果

模拟了以下 10 种设计,并测量了执行一个 RandomX 程序(256 条指令)的平均时钟周期数。

设计超标量配置重排序分支处理执行时间 [周期]IPC
#11 EXU + 1 MEM按序非推测性2930.87
#21 EXU + 1 MEM按序推测性2620.98
#32 EXU + 1 MEM按序非推测性1971.3
#42 EXU + 1 MEM按序推测性1611.6
#52 EXU + 1 MEM乱序非推测性1441.8
#62 EXU + 1 MEM乱序推测性1222.1
#74 EXU + 2 MEM按序非推测性1351.9
#84 EXU + 2 MEM按序推测性992.6
#94 EXU + 2 MEM乱序非推测性892.9
#104 EXU + 2 MEM乱序推测性644.0

超标量、乱序和推测设计的好处显而易见。

C. RandomX 运行时间分布

运行时间数据在运行于 3.0 GHz 的 AMD Ryzen 7 1700(使用 1 个核心)上测量。测量程序执行和验证时间的源代码可在 runtime-distr.cpp 中找到。测量 x86 JIT 编译器性能的源代码可在 jit-performance.cpp 中找到。

快速模式 - 程序执行

下图显示了单个 VM 程序(在快速模式下)运行时间的分布。这包括:程序生成、JIT 编译、VM 执行和寄存器文件的 Blake2b 哈希。测得程序生成和 JIT 编译每个程序耗时 3.6 μs。

Imgur

AMD Ryzen 7 1700 在快速模式下(使用 1 个线程)每秒可计算 625 个哈希,这意味着单个哈希结果耗时 1600 μs (1.6 ms)。这包括(大约):

  • 1480 μs 用于 VM 执行(8 个程序)
  • 45 μs 用于初始暂存器填充 (AesGenerator1R)。
  • 45 μs 用于最终暂存器哈希 (AesHash1R)。
  • 30 μs 用于程序生成和 JIT 编译(8 个程序)

这给出了 7.5% 的总开销(每个哈希花费在未执行 VM 上的时间)。

轻量模式 - 验证时间

下图显示了使用轻量模式计算 1 个哈希结果所需时间的分布。大部分时间用于执行 SuperscalarHash 来计算数据集项(14.8 ms 中的 13.2 ms)。平均验证时间与 CryptoNight 算法的性能完全匹配。

Imgur

D. 暂存器熵分析

执行 8 次程序后暂存器的平均熵是使用 LZMA 压缩算法近似的:

  1. 计算哈希结果,并将最终的暂存器写入扩展名为 '.spad' 的文件(源代码:scratchpad-entropy.cpp
  2. 使用 7-Zip [29] 在 Ultra 压缩模式下压缩文件:7z.exe a -t7z -m0=lzma2 -mx=9 scratchpads.7z *.spad

生成的存档大小大约是暂存器文件未压缩大小的 99.98%。这表明在 VM 执行期间,暂存器保持了高熵。

E. SuperscalarHash 分析

SuperscalarHash 是 RandomX 用于生成数据集项的自定义函数。它对 8 个整数寄存器进行操作,并使用随机指令序列。大约 1/3 的指令是乘法。

下图显示了 SuperscalarHash 对改变输入寄存器单个比特的敏感性:

Imgur

这表明 SuperscalarHash 对高位比特的敏感性相当低,对最低位比特的敏感性略有降低。对第 3-53 位(含)的敏感性最高。

在计算数据集项时,第一个 SuperscalarHash 的输入仅依赖于项编号。为了确保结果的良好分布,规范第 7.3 节中描述的常量经过选择,为范围 0-34078718(数据集包含 34078719 项)内的所有项编号提供唯一的第 3-53 位值。所有数据集项编号的所有初始寄存器值都经过检查,以确保每个寄存器的第 3-53 位是唯一的且没有冲突(源代码:superscalar-init.cpp)。虽然这对于从 SuperscalarHash 获得唯一输出并非严格必要,但这是一种安全预防措施,可以减轻随机生成的 SuperscalarHash 实例的非完美雪崩特性的影响。

F. RNG 的统计测试

AesGenerator1R 和 AesGenerator4R 都使用 TestU01 库 [30] 进行了测试,该库用于随机数生成器的经验测试。源代码可在 rng-tests.cpp 中找到。

测试对每个生成器采样约 200 MB(“SmallCrush”测试)、500 GB(“Crush”测试)或 4 TB(“BigCrush”测试)的输出。这远远超过 RandomX 中生成的量(AesGenerator4R 为 2176 字节,AesGenerator1R 为 2 MiB),因此测试失败并不一定意味着生成器不适合其用例。

AesGenerator4R

当使用 Blake2b 哈希函数初始化时,该生成器通过了“BigCrush”套件中的所有测试:

$ bin/rng-tests 1
state0 = 67e8bbe567a1c18c91a316faf19fab73
state1 = 39f7c0e0a8d96512c525852124fdc9fe
state2 = 7abb07b2c90e04f098261e323eee8159
state3 = 3df534c34cdfbb4e70f8c0e1826f4cf7

...

========= BigCrush 的摘要结果 =========

 版本:          TestU01 1.2.3
 生成器:        AesGenerator4R
 统计量数量:  160
 总 CPU 时间:   02:50:18.34

 所有测试均已通过

即使初始状态设置为全零,该生成器也通过了“Crush”套件中的所有测试。

$ bin/rng-tests 0
state0 = 00000000000000000000000000000000
state1 = 00000000000000000000000000000000
state2 = 00000000000000000000000000000000
state3 = 00000000000000000000000000000000

...

========= Crush 的摘要结果 =========

 版本:          TestU01 1.2.3
 生成器:        AesGenerator4R
 统计量数量:  144
 总 CPU 时间:   00:25:17.95

 所有测试均已通过

AesGenerator1R

当使用 Blake2b 哈希函数初始化时,该生成器通过了“Crush”套件中的所有测试。

$ bin/rng-tests 1
state0 = 67e8bbe567a1c18c91a316faf19fab73
state1 = 39f7c0e0a8d96512c525852124fdc9fe
state2 = 7abb07b2c90e04f098261e323eee8159
state3 = 3df534c34cdfbb4e70f8c0e1826f4cf7

...

========= Crush 的摘要结果 =========

 版本:          TestU01 1.2.3
 生成器:        AesGenerator1R
 统计量数量:  144
 总 CPU 时间:   00:25:06.07

 所有测试均已通过

当初始状态初始化为全零时,该生成器在“Crush”套件的 144 项测试中有 1 项失败:

$ bin/rng-tests 0
state0 = 00000000000000000000000000000000
state1 = 00000000000000000000000000000000
state2 = 00000000000000000000000000000000
state3 = 00000000000000000000000000000000

...

========= Crush 的摘要结果 =========

 版本:          TestU01 1.2.3
 生成器:        AesGenerator1R
 统计量数量:  144
 总 CPU 时间:   00:26:12.75
 以下测试给出的 p 值超出了 [0.001, 0.9990]:
 (eps  表示值 < 1.0e-300):
 (eps1 表示值 < 1.0e-15):

       测试                          p 值
 ----------------------------------------------
 12  BirthdaySpacings, t = 3        1 -  4.4e-5
 ----------------------------------------------
 所有其他测试均已通过

参考文献

[1] CryptoNote 白皮书 - https://cryptonote.org/whitepaper.pdf
[2] ProgPoW: 低效的整数乘法 - https://github.com/ifdefelse/ProgPOW/issues/16
[3] 加密哈希函数 - https://en.wikipedia.org/wiki/Cryptographic_hash_function
[4] randprog - https://github.com/hyc/randprog
[5] RandomJS - https://github.com/tevador/RandomJS
[6] μop 缓存 - https://en.wikipedia.org/wiki/CPU_cache#Micro-operation_(%CE%BCop_or_uop)_cache
[7] 指令级并行 - https://en.wikipedia.org/wiki/Instruction-level_parallelism
[8] 超标量处理器 - https://en.wikipedia.org/wiki/Superscalar_processor
[9] 乱序执行 - https://en.wikipedia.org/wiki/Out-of-order_execution
[10] 推测执行 - https://en.wikipedia.org/wiki/Speculative_execution
[11] 寄存器重命名 - https://en.wikipedia.org/wiki/Register_renaming
[12] Blake2 哈希函数 - https://blake2.net/
[13] 高级加密标准 - https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
[14] 对数正态分布 - https://en.wikipedia.org/wiki/Log-normal_distribution
[15] CryptoNight 哈希函数 - https://cryptonote.org/cns/cns008.txt
[16] 动态随机存取存储器 - https://en.wikipedia.org/wiki/Dynamic_random-access_memory
[17] 多通道内存架构 - https://en.wikipedia.org/wiki/Multi-channel_memory_architecture
[18] Obelisk GRN1 芯片细节 - https://www.grin-forum.org/t/obelisk-grn1-chip-details/4571
[19] Biryukov 等人:内存困难函数的折衷密码分析 - https://eprint.iacr.org/2015/227.pdf
[20] SK 海力士 20nm DRAM 密度 - http://en.thelec.kr/news/articleView.html?idxno=20
[21] 分支预测器 - https://en.wikipedia.org/wiki/Branch_predictor
[22] 谓词执行 - https://en.wikipedia.org/wiki/Predication_(computer_architecture)
[23] CPU 缓存 - https://en.wikipedia.org/wiki/CPU_cache
[24] Cortex-A55 微架构 - https://www.anandtech.com/show/11441/dynamiq-and-arms-new-cpus-cortex-a75-a55/4
[25] AMD Zen+ 微架构 - https://en.wikichip.org/wiki/amd/microarchitectures/zen%2B#Memory_Hierarchy
[26] Intel Skylake 微架构 - https://en.wikichip.org/wiki/intel/microarchitectures/skylake_(client)#Memory_Hierarchy
[27] Biryukov 等人:用于加密货币和密码哈希的快速且抗折衷的内存困难函数 - https://eprint.iacr.org/2015/430.pdf 表 2,第 8 页
[28] J. Daemen, V. Rijmen: AES 提案: Rijndael - https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/aes-development/rijndael-ammended.pdf 第 28 页
[29] 7-Zip 文件归档器 - https://www.7-zip.org/
[30] TestU01 库 - http://simul.iro.umontreal.ca/testu01/tu01.html