Arch Linux 星球

July 29, 2021

Alynx Zhou

NVIDIA 驱动和 GNOME 和 Wayland

由于各种各样的历史原因和近期的变化,我在最近的聊天里发现很多朋友对 NVIDIA 驱动对 Wayland 的支持情况不甚了解,正好我最近在折腾相关的东西,所以打算简单介绍一下我了解的。

常见问题

为什么 NVIDIA 的 Wayland 支持这么差?

长话短说,大部分驱动(AMD 的开源和闭源驱动,NVIDIA 的开源驱动 nouveau,Intel 的开源驱动)都同意采用 GBM 作为缓存 API,但是 NVIDIA 像个赌气的小孩一样表示“你们这个不好,我要做个更好的 EGLStreams”所以不支持 GBM,于是所有要兼容 NVIDIA 闭源驱动的混成器都要为它做单独的实现代码。诸如 GNOME 或者 KDE 这种大型项目有足够的人手,同时 NVIDIA 似乎也提供了一定的帮助所以已经有了混成器内部的 EGLStreams 实现,但也有些小型项目并不支持这个玩意。而且 NVIDIA 吹牛皮吹得很大,EGLStreams 哪里“更好”从他们的龟速进展上根本体现不出来。可能是因为挖矿赚得盆满钵满,忘记了显卡的本职工作是显示,NVIDIA 的闭源 Linux 驱动质量一直停留在能用的程度,同时又做各种各样限制导致开源的 nouveau 在较新的显卡上几乎不能用。

NVIDIA 闭源驱动之前一直是稀泥巴糊不上墙的垃圾,因为它在 Wayland 下对使用 XWayland 的程序没有硬件加速支持——也就是说如果你使用 Xorg 会话,程序是有硬件加速的,你使用 Wayland 会话,Wayland 程序也是有硬件加速的,但是在 Wayland 会话下面那些旧的 Xorg 程序就没有硬件加速(听起来就像捡了芝麻丢了西瓜一样)——而仍然有相当一部分旧程序在 Wayland 下依赖 XWayland 运行,所以 NVIDIA 闭源驱动的 Wayland 会话基本可以认为是不能日常使用的。

从版本 470 之后 NVIDIA 闭源驱动有所改善,首先是他们的员工提交的补丁在 XWayland 21.1.1 版本释出了,这个补丁添加了对 XWayland 硬件加速的支持(需要新版本驱动),然后新版本驱动在上周变为稳定版,用户可以安装,所以在拖了至少三年之后最后一块空缺终于被补上了。使用 KDE 和 GNOME 的 NVIDIA 显卡用户理论上就可以获得完整的 Wayland 体验了。

现在 NVIDIA 用户的 Wayland 会话可以日常使用了吗?

更新到 470 驱动之后我第一时间体验了一下,结果就是很抱歉,还是不行,不过我遇到的都是一些小问题:

  • Firefox 稳定版没法正常工作,但是 Nightly 版本是可以的,原因是 NVIDIA 和 Mesa 的 EGL 实现存在一点小区别,修复补丁已经在 Nightly 里面了,我们只要等稳定版版本升上去就可以了。
  • GTK3 程序都能工作,但是 GTK4 的程序(比如 GNOME Extensions)却全都挂了,原因应该和 Firefox 一致,修复补丁也已经合并了,只是还没有释出稳定版。
  • Google Chrome 不能工作,但是比较新的 Chromium 似乎可以,原因推测也是一样的,我懒得查了,同时所有 Electron 的程序(Atom,VSCode)应该都有同样的问题。
  • GNOME Shell 的缩略图会显示错误,看起来是个两年的老 bug,最近开发者似乎找到了原因,还没修复。

不过总而言之这些都是能很快修复(废话,大家动作都比 NVIDIA 快,建议老黄把自己的皮衣换成乌龟壳)的问题,不至于被卡很久,至于你的发行版是那种追求“稳定”选择了一个不支持的大版本然后不更新的?哈哈,管我 X 事。

另外我在 openSUSE Tumbleweed 上测试的时候还遇到一个问题啊,它默认安装的是 nouveau,需要手动在 YaST 里面安装 NVIDIA,装完之后似乎两个驱动冲突把我显示器搞黑屏了,于是我就没法直接重启。而且在那之后我显示器神奇的再也不亮了(PS4、Switch 全都点不亮它),拔掉电源放半分钟再插上才好,很头疼啊。

我是 GNOME 用户,为什么装了 470 版本 GDM 选单里的 GNOME 还是 Xorg 会话?

这个有几个原因,首先就是 Wayland 需要 Kernel Mode Setting 的支持(这也是个 NVIDIA 拖了好久才补上的特性),所以你的内核参数要有 nvidia-drm.modeset=1。其次要看 /etc/gdm/custom.conf 里面 WaylandEnable=false 是不是被注释掉的(默认应该是)。

最可能的是 udev 规则,GDM 带了一个 /usr/lib/udev/rules.d/61-gdm.rules 文件,里面有这么几行:

# disable Wayland on Hi1710 chipsets
ATTR{vendor}=="0x19e5", ATTR{device}=="0x1711", RUN+="/usr/lib/gdm-runtime-config set daemon WaylandEnable false"
# disable Wayland when using the proprietary nvidia driver
DRIVER=="nvidia", RUN+="/usr/lib/gdm-runtime-config set daemon WaylandEnable false"
# disable Wayland if modesetting is disabled
IMPORT{cmdline}="nomodeset", RUN+="/usr/lib/gdm-runtime-config set daemon WaylandEnable false"

第二行表示如果检测到 NVIDIA 闭源驱动就关闭 Wayland,第三行则是如果没开启 Kernel Mode Setting 也关掉 Wayland。

我猜大部分用户都没有第一行的设备,所以我建议直接 ln -s /dev/null /etc/udev/rules.d/61-gdm.rules 屏蔽掉这个文件。

别问我既然 470 驱动都出来了 GNOME 开发者为什么不删掉这个文件,NVIDIA 憋出来 470 驱动总共也就一周多点,还有一堆小问题呢,删了怕不是又有一堆人来骂“怎么又把不稳定的东西搞给我了”。

我是笔记本双显卡用户,主用的是核芯显卡,为什么我 GDM 选单里的 GNOME 也是 Xorg 会话?

检查一下你是不是用了 NVIDIA 之前提供的 Xorg 下面的双显卡管理功能(有群友说叫 prime,我不知道是不是叫 prime)(顺便一提这也是 NVIDIA 憋了好几年才憋出来的本来就该有的东西)?如果你使用那个,首先它是 Xorg 下面的支持,其次它会加载 nvidia 模块,自然就会落到上一个问题里面的 udev 规则上。

有群友在群里表示他删了那个规则然后进 Wayland,可以是可以,但我感到很迷惑。这个 prime 功能 NVIDIA 之前只是说了在 Xorg 下面可用,所以我不是很理解又要 prime 又要 Wayland 的可行性,我也不知道 NVIDIA 的按需渲染现在到 Wayland 里面还能不能像之前 Xorg 里面提供的功能一样正常工作。就理论上我给 NVIDIA 掏了钱,这是他的义务给我支持这个功能,但这可是垃圾 NVIDIA 啊!

你问我有没有什么建议?如果你需要这玩意,就按照 GNOME 的规则用 Xorg 就好了,没错虽然我是 Wayland 支持者,但我在这里建议你还是先用着 Xorg。如果你只是想用 Wayland 然后并不是很需要用 NVIDIA 显卡,那就关掉它不让它加载驱动,没错,我在自己的笔记本上用的是 Bumblebee。

我是 KDE 用户,你扯这么多 GNOME,跟我说说 KDE 现在有什么问题呗?

大哥,我是 GNOME 用户啊,既然你是 KDE 用户,建议你自己试试然后给我讲讲……

没用的观点

你看这个 GDM 就是逊啦,我这里卡死根本不能用,大家都推荐换 SDDM 巴拉巴拉……

强调一下我的目的是解决问题,比如群里有人问为什么我双显卡 GDM 里没 Wayland 可选你来这么一句我觉得很不礼貌,当别人就是需要用 GDM 的时候说“大家都推荐换 SDDM”我觉得不管是 GNOME 用户还是 KDE 用户都会觉得不是个好回答。其次只说自己 GDM 卡死完全没法判断问题所在,别人都没这个问题,或许问题就不在 GDM 而在你的配置呢?同理,这个“大家都推荐换 SDDM”里的“大家”是哪里来的呢?我觉得这样的回复对解决问题没什么帮助。

Xorg 好!Wayland 坏!

请列举出充分而恰当的理由支持你的观点,而不是人云亦云。比方说你可能觉得 Wayland 缺乏你需要的某些功能,这可能是设计理念上的不同,或者发展时间不够所导致的,毕竟 X 协议的历史比 Wayland 长得多,所以请多一点耐心。而至于不讲道理或者二极管或者言论不友善的行为在哪里都是不受欢迎的。

我也是 NVIDIA 用户,我玩 XX 游戏卡成幻灯片了!

……信息太少,无法给出合理推断,只能告诉你我游戏玩的可欢乐了!

你们 NVIDIA 用户真麻烦,谁叫你们自己掏钱买 N 卡受罪!

你又不知道老子都有什么需求,买 N 卡我是暂时没法用 Wayland,但是不买 N 卡我就要达芬奇受罪 Windows 下面打游戏受罪直播编码受罪跑 hashcat 受罪(存疑)。我做的选择是我权衡之后的结果。再说一遍,解决问题和“换 XX”是完全不一样的态度。

所以就是钱是掏了,骂还是照样骂,而且我都掏钱了,骂得然更有底气了!

Alynx Zhou

A Coder & Dreamer

by Alynx Zhou (alynx.zhou@gmail.com) at July 29, 2021 11:50 PM

July 25, 2021

frantic1048

PLUM - 香风智乃 夏日祭 Ver.

Chino

已经收到了一个多星期,最近终于找到空闲给这位智乃开箱拍照。虽说小伙伴有安利同系列的心爱一起订,但因为我的爱不够就只订智乃了,结果后面还阴差阳错在不同地方订出了两个智乃,这可真是……

普通路过包装盒。 Chino

一圈过去没有啥很需要后期修复的细节,很科学。发饰的搭配非常棒。 Chino Chino Chino Chino

到处都充满了兔子的装饰,以及虽迟但到的提比。 Chino Chino Chino Chino

Chino Chino Chino Chino

July 25, 2021 05:13 PM

July 23, 2021

Alynx Zhou

可能只适合我自己的 RIME 配置

为什么我要折腾这个

在第 n 次忍受不了 RIME 的奇怪操作逻辑之后,我终于决定彻底教育一下这个不听话的输入法,考虑到已经有 n - 1 次失败的前提,做这个决定并不容易。

首先说明我是 Linux 用户,使用 ibus-rime 做输入引擎,所以使用什么小狼毫鼠须管或者 fcitx-rime 的如果发现不好用最好别烦我,那些我都没用过。(不好意思因为配置这玩意有点暴躁。)

然后我要吐槽一下 RIME 的文档,完全没有一致的类似 API 手册的东西,而且全繁体加上佛振独特的说话风格读起来真的很累,然后这个项目习惯又是起一大堆奇奇怪怪的名字(虽然我有时候也这样)。假如你想修改一点配置,读了文档里的示例“我懂了,巴拉巴拉”,打开配置一看一堆乱七八糟的就懵了。

然后就是网上“致第一次用 RIME 的你”、“也致第一次用 RIME 的你”,我都看过了,首先他们不用 ibus-rime,其次文章内容毕竟有限,每个人需求不一样,有些我需要的地方他们一笔带过了。


更新(2021-07-23):另外我是被 felixonmars 惯坏了的 Arch Linux 用户,他已经把各种乱七八糟的 RIME 的配置打包到官方仓库了,所以我不需要和那个什么用来配置 RIME 配置的什么什么东风破打交道。如果你不是 Arch Linux 用户的话,我假设你有能力搞明白那个东风破,因为我搞不明白,没办法告诉你怎么用。


RIME 的拼音功能确实很好用(虽然有时候它和我对于词组的想法不太一样),我的需求其实只有几项:

  • 对于一些 RIME 默认的中国人几乎都用不到的中文标点直接改成英文标点,这个在书写 Markdown 的时候简直折磨死我了,有几个人会输入全角井号???打个标题真的很麻烦。
  • 有些符号键在其他输入法里约定俗成的就直接输出特定的中文符号,RIME 的默认操作是弹出选择菜单,完全没这个必要,弹出菜单还需要二次选择而且打断了我的按键操作,需要英文标点时候直接切为英文就好了。
  • 关掉 RIME 的中英混输功能,在候选框输入英文字母真的很打断思路,我要输入英文要么就是打代码要么就是打单词,反正都不需要输入法,RIME 来就是给我找麻烦。
  • 还有一些奇奇怪怪的 RIME 的键位设置,偏偏要和其他输入法不一样,我已经习惯了那些操作,RIME 的键位只会降低输入速度。

下面介绍一下我的配置。当然如果你嫌麻烦,最后我会加上我的配置的 GitHub Repo。

首先建立一个干净的 RIME 配置环境,直接移走 ~/.config/ibus/rime 然后执行 ibus-daemon -rdx 重新生成(就是它文档里扯的部署部署部署)一套配置,由于我用的都是内置输入法所以也不需要什么乱七八糟的东风破 RIME Kit 地球拼音之类的。

然后你进去 ~/.config/ibus/rime 新版大概有以下几个东西:

  • 目录 build里面放了各种 RIME 的默认配置,我们不需要动这个。 很好,我搞错了,原来它是从 /usr/share/rime-data/~/.config/ibus/rime/ 下面加载不带 custom 的文件,然后再读取 custom 文件给之前的文件打 patch,最后生成到 build 目录下面。
  • 目录 luna_pinyin.userdb:看起来像是朙月拼音的词库,当然也不用修改。
  • 文件 installation.yaml:我猜不用管。
  • 文件 user.yaml:我猜也不用管。

各种乱七八糟操作逻辑的配置

按照 RIME 打 patch 的配置方式我们需要在这个目录下创建一个叫 default.custom.yaml 的文件,这样就可以给 /usr/share/rime-data/default.yaml 这个文件 patch 辣,当然你得先会写 YAML。第一行首先写个叫 patch: 的 key,RIME 要求这样,所有的自定义配置都是在 patch 字段下面。

怎么确定要修改的 key 名字呢?我这里的都是在 build/default.yaml 下面找到的, 因为 patch 的是 /usr/share/rime-data/default.yaml 所以就去看这个辣,之前又写错了,那个其实是生成的文件。你也可以试试其他的 YAML 文件。

RIME 的文档说什么要用 / 把不同层次的 key 折叠成一个比如 ascii_composer/switch_key亲测无所谓,我就爱展开了写完整的 YAML,这样更规范。 我说佛老师对不起对不起,我不懂规矩。a/b/c 是只 patch c,展开了则变成了 patch a。

然后首先第一步我要修改输入法列表,我只用朙月拼音简化字模式就行了:

patch:
  schema_list:
    - schema: "luna_pinyin_simp"

然后就是改掉那个自作聪明的英文输入模式:

  # 按 CapsLock 输出大写英文字母。
  ascii_composer/good_old_caps_lock: true
  # `inline_ascii` 在输入框内插入英文。
  # `commit_text` 候选文字上屏并切换至英文。
  # `commit_code` 输入拼音上屏并切换至英文。
  # `clear` 清除拼音并切换至英文。
  # `noop` 屏蔽此按键。
  # 如果你设置 `Caps_Lock` 为 `noop`,
  # 一个奇怪的问题是退格键不能用了,除非取消大写锁定。
  # 所以我直接设置文字上屏了。
  ascii_composer/switch_key/Caps_Lock: "commit_text"
  ascii_composer/switch_key/Shift_L: "commit_code"
  ascii_composer/switch_key/Shift_R: "commit_code"

大部分坑我都写在注释里了可以自己看。

然后我看那个设置选单也不是很爽,我习惯简体字,这个也可以自己改:

  # 改掉原来的繁体字标题。
  switcher/caption: "【设置菜单】"
  # 用半角斜线而不是奇丑无比的全角斜线做分隔符。
  switcher/option_list_separator: "/"
  # 屏蔽 Ctrl-s 开启菜单,只允许 Ctrl-` 和 F4。
  switcher/hotkeys:
    # - "Control+s"
    - "Control+grave"
    - "F4"

然后就是改掉它奇怪的键位,Emacs 键位挺好的,但是有几个不知道为什么用不了,再者就是为什么按向左是跳一个字拼音向右是跳一个字母?

# 这里修改的是整个输入法全局的键位,某些输入方案有自己的键位可以单独覆盖。
# 但我暂时不需要。
# Emacs 键位,我喜欢。
# 可是谁给我解释一下为什么 Left 是按字拼音跳而 Right 是按字母跳?
key_binder/bindings:
  - accept: "Control+p"
    send: "Up"
    when: "composing"
  - accept: "Control+n"
    send: "Down"
    when: "composing"
  - accept: "Control+b"
    send: "Left"
    when: "composing"
  - accept: "Control+f"
    send: "Right"
    when: "composing"
  - accept: "Alt+b"
    send: "Shift+Left"
    when: "composing"
  - accept: "Alt+f"
    send: "Shift+Right"
    when: "composing"
  - accept: "Control+a"
    send: "Home"
    when: "composing"
  - accept: "Control+e"
    send: "End"
    when: "composing"
  - accept: "Control+d"
    send: "Delete"
    when: "composing"
  # 这个用不了,不过估计也用不到。
  # - accept: "Control+k"
  #   send: "Shift+Delete"
  #   when: "composing"
  - accept: "Control+h"
    send: "BackSpace"
    when: "composing"
  - accept: "Alt+h"
    send: "Shift+BackSpace"
    when: "composing"
  - accept: "Control+g"
    send: "Escape"
    when: "composing"
  - accept: "Control+bracketleft"
    send: "Escape"
    when: "composing"
  - accept: "Alt+v"
    send: "Page_Up"
    when: "composing"
  - accept: "Control+v"
    send: "Page_Down"
    when: "composing"

还没完,我觉得正常人不会用 Tab 在拼音之间切换,除非你一次输入一句话(那你不觉得候选框太小了看着累吗???),设置 Tab 为跳候选词更自然一点,但我也不知道为什么 Shift-Tab 用不了:

      # 正常人不会用 Tab 切换拼音光标的,相信我。用它切换选项更快。
      # - accept: "ISO_Left_Tab"
      #   send: "Shift+Left"
      #   when: "composing"
      # - accept: "Shift+Tab"
      #   send: "Shift+Left"
      #   when: "composing"
      # - accept: "Tab"
      #   send: "Shift+Right"
      #   when: "composing"
      - accept: "Tab"
        send: "Down"
        when: "has_menu"
      - accept: "ISO_Left_Tab"
        send: "Up"
        when: "has_menu"
      # 鬼知道为什么这个也用不了!
      - accept: "Shift+Tab"
        send: "Up"
        when: "has_menu"

以及我觉得正常人不用逗号和句号翻页,毕竟下面的默认设置是逗号句号直接上屏,你设置了翻页也没啥卵用,反正我用减号等号或者上下,不过方括号也不错就是了:

  # 这里修改的是整个输入法全局的键位,某些输入方案有自己的键位可以单独覆盖。
  # 但我暂时不需要。
  # Emacs 键位,我喜欢。
  # 可是谁给我解释一下为什么 Left 是按字拼音跳而 Right 是按字母跳?
  key_binder/bindings:
    - accept: "Control+p"
      send: "Up"
      when: "composing"
    - accept: "Control+n"
      send: "Down"
      when: "composing"
    - accept: "Control+b"
      send: "Left"
      when: "composing"
    - accept: "Control+f"
      send: "Right"
      when: "composing"
    - accept: "Alt+b"
      send: "Shift+Left"
      when: "composing"
    - accept: "Alt+f"
      send: "Shift+Right"
      when: "composing"
    - accept: "Control+a"
      send: "Home"
      when: "composing"
    - accept: "Control+e"
      send: "End"
      when: "composing"
    - accept: "Control+d"
      send: "Delete"
      when: "composing"
    # 这个用不了,不过估计也用不到。
    # - accept: "Control+k"
    #   send: "Shift+Delete"
    #   when: "composing"
    - accept: "Control+h"
      send: "BackSpace"
      when: "composing"
    - accept: "Alt+h"
      send: "Shift+BackSpace"
      when: "composing"
    - accept: "Control+g"
      send: "Escape"
      when: "composing"
    - accept: "Control+bracketleft"
      send: "Escape"
      when: "composing"
    - accept: "Alt+v"
      send: "Page_Up"
      when: "composing"
    - accept: "Control+v"
      send: "Page_Down"
      when: "composing"
    # 正常人不会用 Tab 切换拼音光标的,相信我。用它切换选项更快。
    # - accept: "ISO_Left_Tab"
    #   send: "Shift+Left"
    #   when: "composing"
    # - accept: "Shift+Tab"
    #   send: "Shift+Left"
    #   when: "composing"
    # - accept: "Tab"
    #   send: "Shift+Right"
    #   when: "composing"
    - accept: "Tab"
      send: "Down"
      when: "has_menu"
    - accept: "ISO_Left_Tab"
      send: "Up"
      when: "has_menu"
    # 鬼知道为什么这个也用不了!
    - accept: "Shift+Tab"
      send: "Up"
      when: "has_menu"
    - accept: "minus"
      send: "Page_Up"
      when: "has_menu"
    - accept: "equal"
      send: "Page_Down"
      when: "has_menu"
    - accept: "bracketleft"
      send: "Page_Up"
      when: "has_menu"
    - accept: "bracketright"
      send: "Page_Down"
      when: "has_menu"
    # 我觉得正常人不应该用逗号和句号翻页。
    # - accept: "comma"
    #   send: "Page_Up"
    #   when: "paging"
    # - accept: "period"
    #   send: "Page_Down"
    #   when: "has_menu"

最后就是那一堆乱七八糟的快捷键了,鬼才记得住,有那时间直接翻菜单就行了,那个 Shift+Space 就是我动不动就变成全角的罪魁祸首,全部不要:

    # 鬼才记得住这么多乱七八糟的快捷键,我翻菜单比背这玩意快多了。
    # - accept: "Control+Shift+1"
    #   select: ".next"
    #   when: "always"
    # - accept: "Control+Shift+2"
    #   toggle: "ascii_mode"
    #   when: "always"
    # - accept: "Control+Shift+3"
    #   toggle: "full_shape"
    #   when: "always"
    # - accept: "Control+Shift+4"
    #   toggle: simplification
    #   when: "always"
    # - accept: "Control+Shift+5"
    #   toggle: "extended_charset"
    #   when: "always"
    # - accept: "Control+Shift+exclam"
    #   select: "".next"
    #   when: "always"
    # - accept: "Control+Shift+at"
    #   toggle: "ascii_mode"
    #   when: "always"
    # - accept: "Control+Shift+numbersign"
    #   toggle: "full_shape"
    #   when: "always"
    # - accept: "Control+Shift+dollar"
    #   toggle: "simplification"
    #   when: "always"
    # - accept: "Control+Shift+percent"
    #   toggle: "extended_charset"
    #   when: "always"
    # 你就是那个经常害我变成全角的罪魁祸首!
    # - accept: "Shift+space"
    #   toggle: "full_shape"
    #   when: "always"
    # - accept: "Control+period"
    #   toggle: "ascii_punct"
    #   when: "always"

我自己是不习惯写 inline 的字典和列表,都写的展开的。

然后是符号设置了,一开始我以为改 default 里面的符号表就行了,但是最近(2021-03-01)发现不行了,翻了一下代码,发现朙月拼音现在不读 default 了,而是加载 symbols 里面的。然后文档建议的是在输入法方案里面添加自定义的符号表,所以需要建立 luna_pinyin_simp.custom.yaml 并修改。

因为我们上面取消了逗号句号翻页,所以这里也就不用显式写 commit 直接上屏了。然后我去掉了一大堆菜单,我输入井号星号波浪线百分号就是想要英文标点,你给我弹个菜单我还得多确认好麻烦的。以及我觉得真的没人用那个巨长的全角斜杠,输入斜杠就是为了斜杠,什么通过朙月拼音命令输入假名有意义吗?我为什么不直接切日语输入法呢?另一些标点在中文语境下直接输出中文标点就好了,需要英文标点时候我敲一下 Shift 比看菜单选容易多了!比如书名号,竖线输出人名中间的点,反斜杠输出顿号之类的。

由于我不是金融行业的,我就把一些英文标点常见但对应中文标点也可能会用到的都丢到了 $ 的菜单里面:

patch:
  # 现在朙月拼音加载标点候选是加载 symbols 里面的,根本不加载 default。
  # 然后文档建议的是自己修改的标点符号表放在输入法方案配置里面。
  # 为了方便编写 Markdown,把一些奇怪的写中文根本用不到的符号弹出菜单改成直接输出英文符号。
  # 另一些直接默认输出中文符号,需要英文符号可以切换英文输入。
  # 有关 `"!": {commit: "!"}` 的写法含义是你设置这个键为翻页按键了,
  # 但是你又想在输入拼音出现选单之后输入这个按键直接上屏(常见的逗号句号问号叹号),
  # 我觉得这是多此一举,为什么你非要拿这几个符号翻页?反正我不用。
  # 乱七八糟的符号都塞给 `$` 就好了反正我不是会计不用天天输入 `¥`。
  # 我不会使用全角英文的,我觉得其他程序员也不会。
  # 但是中文的标点又是全角的,所以我就只改半角。
  punctuator/half_shape:
    "!": "!"
    "\"":
      pair:
        - "“"
        - "”"
    "#": "#"
    "$":
      - "¥"
      - "$"
      - "€"
      - "~"
      - "×"
      - "÷"
      - "°"
      - "℃"
      - "‰"
      - "‱"
      - "℉"
      - "©"
      - "®"
    "%": "%"
    "&": "&"
    "'":
      pair:
        - "‘"
        - "’"
    "*": "*"
    "+": "+"
    ",": ","
    "-": "-"
    ".": "。"
    "/": "/"
    "\\": "、"
    ":": ":"
    ";": ";"
    "=": "="
    "?": "?"
    "@": "@"
    "(": "("
    ")": ")"
    "[": "【"
    "]": "】"
    "{": "「"
    "}": "」"
    "<": "《"
    ">": "》"
    "^": "……"
    "_": "——"
    "`": "`"
    "|": "·"
    "~": "~"

最近的朙月拼音添加了反查笔画的功能,按下反引号并输入候选字来启动,但是反引号对于写 Markdown 的人很常用,所以我要关掉这个恼人的功能:

  # 反查占据了宝贵的反引号,导致 Markdown 用户非常痛苦,所以关掉。
  recognizer/patterns/reverse_lookup:

总之写完这些配置 之后要手动移除 ~/.config/ibus/rime/build/ 这个生成目录 再执行 ibus-daemon -rdx 就可以应用了,现在 RIME 用起来就更让我愉快了,接下来就是慢慢养词库就行了。

有关为什么 ibus-rime 总是竖着的

ibus-rime 是读取 rime 配置而不是 ibus 配置来设置横竖这一点本身就很离谱了,然后由于 bug 啦其他奇奇怪怪的原因啦好像很难搞清楚,我最近终于搞清楚啦!其实也不是很麻烦。

ibus-rime 会读一个叫做 ibus_rime.yaml 的配置文件,有这么一个配置可以让他变成横着的:

style:
  horizontal: true

可能看了之前的你会和我一样想那就打个 patch 到 ibus_rime.custom.yaml 不就行了嘛!但是不行,为什么呢?因为不管是 rime 还是 librime 还是 ibus-rime 都没有提供 /usr/share/rime-data/ibus_rime.yaml 的文件,所以你的 patch 找不到被打的文件,那就不会被生成到 build 目录里。

不要忘了之前说 rime 会读取 ~/.config/ibus/rime/ 下面的 yaml,所以其实只要自己建立 ~/.config/ibus/rime/ibus_rime.yaml 写入那段配置就可以啦,因为本来也没有所以就不用打 patch 了,或者你在那两个位置建立一个空的 ibus_rime.yaml 然后再打 patch 也行……


更新(2021 年 1 月 26 日):Arch 的 librime 现在打包了一个 /usr/share/rime/ibus_rime.yaml 文件,所以上面手动创建一个 ibus_rime.yaml 的办法会被覆盖,所以现在建议创建 ~/.config/ibus/rime/ibus_rime.custom.yaml 然后对照着打patch,比如我写的是:

patch:
  # 舒服不如倒着。
  style/horizontal: true
  # 有些软件的行内预测支持有 bug,所以我一般不开。
  style/inline_preedit: false

不要忘了删掉 build 目录再 ibus-daemon -rdx


更新(2021-07-23):最近研究了一下如何扩展 RIME 的词库,发现还是稍微复杂的,我尝试导入了肥猫打包的 rime-pinyin-zhwiki。如果你要给某个输入法导入词库,首先你得自己创建一个扩展词库文件让他继承这个输入法本来的词库和你想要的词库,因为输入法配置里面只能指定一个词库配置文件。

所以对于朙月拼音简化字版本,先创建一个叫 luna_pinyin_simp.extended.dict.yaml.dict.yaml 之前的名字其实是随便取的,内容如下:

# 原来要结合默认词库和第三方词库,
# 需要自己编写一个词库让它 fallback 到朙月拼音和第三方词库。
# 我说佛老师对不起对不起,我不懂规矩。
---
name: luna_pinyin_simp.extended
version: "0.1"
# `by_weight`(按词频高低排序)或 `original`(保持原码表中的顺序)。
sort: by_weight
# 因为导入的朙月拼音词库是繁转简,所以这里不能导入简化字八股文。
# 导入简化字八股文。
# vocabulary: essay-zh-hans
# 选择是否导入预设词汇表【八股文】。
use_preset_vocabulary: true

import_tables:
  - luna_pinyin
  - zhwiki

应该很容易懂,我就不多唠叨了,记得里面名字和外面文件名要一致。

然后在 luna_pinyin_simp.custom.yaml 的 patch 里面加一行:

  translator/dictionary: luna_pinyin_simp.extended

我还研究了一下如何添加 emoji 功能,也是靠肥猫打的 rime-emoji 包,只要在 luna_pinyin_simp.custom.yaml 的 patch 里面加一行:

  __include: emoji_suggestion:/patch

如果你用的不是 Arch,可能需要自己复制 patch 文件内容而不是简单地使用 include,参见官方说明

当然不要忘了安装这两个依赖的包,我这个配置在 Arch Linux 下面一共需要下面几个包:

# pacman -S librime ibus-rime rime-luna-pinyin rime-emoji rime-pinyin-zhwiki

当然你要有配置好能显示的 emoji 字体。这也是个坑,等我有时间写一下我的 /etc/fonts/local.conf 吧。


下载

更新(2021-07-23):因为加了词库和 emoji 之后文件变多了,请直接去 GitHub Repo 获取配置。

Alynx Zhou

A Coder & Dreamer

by Alynx Zhou (alynx.zhou@gmail.com) at July 23, 2021 11:05 AM

July 19, 2021

frantic1048

在 Arch Linux 上使用 LilyPond 记录鼓谱

最近许久没打 OSU! 了,一方面是感觉手指手腕很累(单手 XZCV 默认键位太鼓模式受害者),再玩下去迟早要完,另一方面是摸了个太鼓控制器来玩也感觉不是很带感,又不想止于听音乐,不如尝试一下真的乐器吧,所幸遇到了很棒的老师,摸到了爵士鼓的大门。

作为听说读写学习的一部分,同时也是为了能理解和学感兴趣的歌,以及基本的交流需要,记谱是必要的一环。为了在电脑(Arch Linux)上记谱,需要能满足这些预期的软件:

  • 能输出常见的五线谱格式的鼓谱,pdf 或者任意矢量图片格式都行
  • 能打一些简单文字标注
  • 能听个响,生成任意音频文件

经过一波大浪淘沙,得到如下两个选择:

  1. MuseScore
  2. LilyPond

两者尝试一番下来,简单来说,MuseScore 和 LilyPond 两个方向的体验就像 Word 和 LaTeX 的区别,两方表达力都很强,功能也都很丰富,主要是操作上风格相差很大,个人口味原因偏好 LilyPond 一些。

混沌的鼓谱

这里有必要提一下前面预期里的「常见的鼓谱」到底是什么,因为直接影响到了后面两个方案的体验。

第一天学认谱认硬件的时候就了解到了爵士鼓是复合乐器(多种乐器的组合,所以英文直接就干脆叫 Drum Set 或者 Drum Kit 了),因为硬件组合本身不固定,可能会少,也可能有一大堆,可能是量产的乐器,也可能是鼓手 DIY 的神奇装备,甚至是鼓手自己的大腿,另一方面敲同一个东西用各种姿势也会出现不同声音,这些操作不是很好标准化,然后刚好有五线谱这个现成的通用的表达能力也很好的通行格式就用上了,然后把里面的音符重新定义到和爵士鼓相关的乐器上,其它的时值、重复、强弱之类的功能就直接复用了。

实践上来说,鼓谱的单个音符有时候要表示最普通地敲特定乐器(敲军鼓、敲底鼓、敲重击镲、敲大腿……),有时候要表示用特定手法敲特定乐器(敲节奏镲中间、敲节奏镲镲帽、敲张开的踩镲边缘、敲半开的踩镲边缘、用鼓槌槌尖敲军鼓、用鼓棒末尾敲军鼓的鼓框……),有时候要表示敲出什么感觉的声音(重音、鬼音、渐强、渐弱……)。这些东西记录下来会长什么样子没有很统一的标准,因为硬件组合的不确定性,击打方式的不确定性,加上不同的人不同的实际需求和习惯,记录方式都会有差别,只有比较常用的一部分在不同地方写法比较一致,比如第一个军鼓用五线谱第二格的黑圈圈表示。很难去讨论绝对的正确的谱是怎么长的,配合作者给的额外信息比如图例之类的,能解释出原来作者要表达的意思就行,实在读不懂?那就问!

总结自己学过的谱、各个地方看到的谱、以及随手找到的一些各方对鼓谱记法的描述:

符合我的预期的「常见的谱」是把用手和用脚(主要是底鼓和踩踩镲)的部分用两个声部(Voice)记录了,用符杆朝上的音符记录手打的部分,用符杆朝下的音符表示脚打的,这个记录方式在 MuseScore 和 LilyPond 的功能上来说,都是对应的记录多个声部的功能。

MuseScore

MuseScore 由于单一一个软件把记谱、排版、回放全都带了,还有各种预置的乐谱模板,就成了首先尝试的对象。

安装与起手

安装 MuseScore

Arch Linux 官方软件仓库(官方仓库)里就有打好的 musescore 软件包可以直接装。

安装 MuseScore Drumline extension

可以参见上游给的指南:MuseScore Drumline - Download & Installation,简单来说就是启动 MuseScore,打开菜单栏的 Help / Resource Manager,然后在 Extensions 里找到名为 MuseScore Drumline 的扩展,戳它旁边的安装按钮就行了。

起手

正确完成安装之后,在 MuseScore 里创建新乐谱,到选择模板的步骤的时候,可以看到单独的一个名为 MuseScore DrumLine 的分类,其中的 MDL Drumset 就是普通爵士鼓用的乐谱模板。更多的使用介绍可以去 MuseScore 网站上翻。

体验

比较常用的功能基本不看文档也能戳出来,预览功能很棒,排版很好看。

发现鼠标键盘点点点起来不是很直观,经常在同一时刻已经有音符的时候想再插一个音符变成了插进了下一个音符的位置,操作的问题感觉多看看文档应该能熟练。

然后是自动休止符的插入的特性有点让人头疼,我就输入个刚学的动词大词怎么多了那么多休止符呢,这个功能大体上是 MuseScore 会保证每个小节、每个声部的全部音符的时值加起来是刚好一个小节,如果用户输入的末尾还没满一个小结,那么就会有自动的合适的时值的休止符冒出来去「填满」那个小节的时间,如果继续在那个小节插入音符,那么后面的休止符会自动缩短/消失,这个功能本身挺好的,只是有时候不好看,又不能关掉,只能隐藏音符,感觉不是很优雅。

LilyPond

发现 MuseScore 没有完全让我满意的时候,又来尝试了一下另一个比较著名的记谱工具 LilyPond。起初弹琴动听,做饭又好吃的辣师傅有推荐我用这个来打谱,只是被我嫌弃起手麻烦一开始没尝试。实际体验一番下来,和 MuseScore 一样也有一些不满意的地方,但是因为灵活,又是基于纯文本的编辑方式,有变量之类的避免复制粘贴的功能,用着用着就真香了。

安装

Tl;dr

安装下列软件包:

要装什么

LilyPond 是一个排版乐谱的软件,有了它就可以按照 LilyPond 的语法编写乐谱内容,让它排版出乐谱,然后以 PDF、SVG、PNG 等可以查看的格式来输出,它可以通过官方仓库的 lilypond 包安装。

LilyPond 不能输出能听个响的音频文件,但是它能将乐谱输出成 MIDI 格式的音乐。MIDI 记录的是什么时间什么乐器响了这样的事情,没有记录声音,有点像乐谱,用了脑子才能理解,理解了也只能在脑子里响。虽说不能直接聆听,但是 MIDI 格式的音乐可以用 MIDI 合成器(可以是软件或者硬件,后面的提到的都是指软件形式的合成器)将其记录的内容「转化」成能听到的声音,这有点像是让程序来演奏一个乐谱,这里有个问题是程序并不知道什么乐器是怎样的声音,所以 MIDI 合成器在工作之前,还需要知道需要用到的乐器的声音,这就是与 MIDI 合成器一起工作的 SoundFont 提供的信息,它可以看成是程序要「演奏」 MIDI 时候用的乐器。乐谱(MIDI)只是指定了乐器种类,最后演奏出来的声音会随着演奏的人(MIDI 合成器)和乐器(SoundFont)的变化而出现差异,这里我只是想要预览一下 LilyPond 的乐谱,对音质和仿真程度都没有特别要求,搜寻一番 Arch Linux 官方仓库找到的一组选择是:

  • MIDI 合成器:Timidity++,对应仓库里的 timidity++
  • SoundFont:FluidR3,对应仓库里的 soundfont-fluid

由于 LilyPond 是基于纯文本的乐谱格式,和写程序一样,要是有个语法高亮是再好不过的,如果能再随时看到刚刚编辑的输出那就更好了,搜寻一番之后找到的现成的程序是 Frescobaldi,可以通过仓库里的 frescobaldi 包安装。它有语法高亮,有实时预览,有一键播放,很够用了。只是作为文本编辑器,我目前最顺手的是 VSCode,理论上这套功能也能在 VSCode 上糊出来,获得更好的文本编辑体验,就差一位勇士去填这个坑了:p

整体使用流程

比如说,要编写这么一小节动词大词的乐谱,有图片,有声音(后文音频下方的链接皆为对应的 LilyPond 原文件):

simple-rhythm

simple-rhythm.ly

对应的 LilyPond 文件可以这样写(个人总结的比较方便的写法):

\version "2.22.1"
 
main = {
  \drums {
    \tempo 4 = 100
    \numericTimeSignature
    \time 4/4
 
    <<
      {
        hh8 hh8 <<hh8 sn8>> hh8 hh8 hh8 <<hh8 sn8>> hh8
      }\\{
        bd4 r4 bd4 r4
      }
    >>
  }
}
 
\score {
  \main
  \layout {}
}
 
\score {
  % https://lilypond.org/doc/v2.22/Documentation/notation/using-repeats-with-midi
  \unfoldRepeats {
    \main
  }
  \midi {}
}

然后将上述内容存为名为 simple-rhythm.ly 的文件(以 .ly 结尾即可,这里名称只是示例)。

启动任意终端模拟器,将 shell 的工作目录切到 simple-rhythm.ly 所在的位置,执行以下可以得到排版好的 SVG 格式的乐谱和对应的 MIDI 文件:

lilypond -fsvg -dcrop simple-rhythm.ly

会得到:

接下来再使用 timidity 处理上一步得到的 MIDI 文件,以及通过 ffmpeg 转换音频格式,就可以得到方便传输的 m4a 格式的音频:

timidity simple-rhythm.midi  -Ow -o - | ffmpeg -i - simple-rhythm.m4a

会得到 simple-rhythm.m4a,即为乐谱对应的音频。

看起来有点长,不要慌,比真的去打动词大词简单多了。

记录音符

首先,通过对照乐谱的图片和上面的内容,应该可以脑补到其中这段是记录音符的部分:

<<
  {
    hh8 hh8 <<hh8 sn8>> hh8 hh8 hh8 <<hh8 sn8>> hh8
  }\\{
    bd4 r4 bd4 r4
  }
>>

这里的每一个单词+数字组成的内容就是一个确定时值的音符,比如 hh8,表示八分音符长度的一次(闭合状态的)踩镲的敲击,数字前面的单词表示音符,数字本身表示时值,比如 4 就表示四分音符,以及再打一个小数点的话变成 4. 的话,就是附点四分音符。然后 hh 也可以写成 hihat,前面的音符可以写成 hihat8,表示的是完全相同的意思,只是简写和全写的关系,完整的简写/全写对照、以及更多的音色对应的记法可以参照LilyPond — Notation Reference v2.22.1: A.15 Percussion notes。本文后面也会列出一些常用的音符。

上面用到的剩下的音符分别是:

  • bd/bassdrum:底鼓
  • sn/snare:军鼓
  • r:休止符

知道音符含义之后,这个时候我们来看一下 <<hh8 sn8>>,这里面有两个不同的八分音符,然后回头听上面的动词大词,第三个和第七个八分音符的位置同时有踩镲和军鼓的声音,这个尖括号括起来就是表示里面的音符是在同一个时刻上的。

再把视野放大一点,看到包裹所有音符的那一大块符号:

<<
  {
    % 手上的音符
  }\\{
    % 脚上的音符
  }
>>

这是一种记录两个声部内容的写法,两对 {} 里的内容分别对应乐谱的第一和第二声部,然后默认情况下两个声部的符杆方向是不一样的,把想要朝上的音符写在第一声部,朝下写第二声部,就达成了前面定义的鼓谱的音符的布局。这并不是唯一的控制音符朝向的方法,也不是唯一的描述多个声部的方法,只是写起来比较简单一点就这样了。另一方面,整个乐谱里面,可以有任意数量、先后出现的 <<{}>>\\<<{}>> 这样的记号,里面两个声部占的时值是相同的,实践上把一段节奏型的一个或者几个小节、一段 Fill in 的多个小节写成一块会比较方便,如果一块 <<{}>>\\<<{}>> 里的小节太多了的话,写到后面会比较难分辨两个声部分别写到哪儿了,怎么对应的。

音符时值与名字的简写

除了音符的名字有简写的别名之外,还有一些常用的简写重复内容的方式。

对于一串时值不同的同种音符,可以省掉第一个之后的音符的名字,只写表示时值的数字,比如:

shorthand-1

shorthand-1.ly

sn8 sn16 sn16 sn16 sn16 sn8
% 可以简写成
sn8 16 16 16 16 8

对于一串时值相同的不同音符,可以省掉第一个之后音符的音符的时值,只写表示音符的标识,比如:

shorthand-2

shorthand-2.ly

sn16 tomh16 toml16 sn16 toml16 sn16 tommh16 sn16
% 可以简写成
sn16 tomh toml sn toml sn tommh sn

简写虽然写起来少敲几下键盘,但是阅读起来不一定总是方便的,所以看情况用吧。

音符上的文字标注

比较常见的情景是标记左右手,在音符的末尾跟上 ^"TEXT HERE" 就可以在音符上方加文字,如果把 ^ 换成 _ 的话,文字就会出现在音符下方。

text

text.ly

sn4^"L" 4^"R" 4_"L" 4_"R"
16^"L" 16^"R" 8^"L" 8^"L" 16^"L" 16^"R"
16_"L" 8_"R" 16_"R" r16 sn16_"R" 16_"L" 16_"R"

更多形式的文字和样式设定参见 LilyPond — Notation Reference v2.22.1: 1.8.1 Writing text

连音(Tuplet)

连音可以用来表示一些不能用简单时值记号表示的伤害大脑的时值。比如说,把一个四分音符的时值分成三等分,重新分给一长一短的两个或者一样长度的三个音符这样的操作。

以一个四分音符分长度里演奏均匀的三个音符的,三连音八分音符来说,表示的方式是 \tuplet 3/2 {sn8 sn8 sn8}{} 内的音符是要塞到一组连音里面的音符,3/2 表示 {} 里的音符的时值变成了原来塞2个音符的时间长度里要塞3个同样的音符才能填满,或者说 {} 里一个音符的时值变成原来长度的 2/3,结果就是三连音八分音符看起来是 3 个八分音符,实际占的是 2 的正常的八分音符的时间长度。另外在表示时值变化的分数后面可以多写一个数字,表示连音要以几分音符的长度进行分组显示,在写一长串同类连音的时候比较方便。

triplets

triplets.ly

sn8 8 8 8 \tuplet 3/2 4 {8 8 8 8 8 8}

更多细节参见 LilyPond — Notation Reference v2.22.1: 1.2.1 Writing rhythms#Tuplets

连接线(Tie)

这里指连接两个同种音符的弧线,在两个要连线的音符的第一个音符末尾加一个 ~ 即可,比如 bd8~ bd8

tie

tie.ly

<<
  {
    hh8 8 <<hh8 sn8>> hh8 8 8 <<hh8 sn8>> cymc8~
    cymc8 hh8 <<hh8 sn8>> hh8 8 8<<hh8 sn8>> hh8
  }\\{
    bd4 r4 bd8 8 r8 bd8~
    bd8 bd8 r4 bd8 8 r4
  }
>>

更多细节参见LilyPond — Notation Reference v2.22.1: 1.2.1 Writing rhythms#Ties

即兴记号(Improvisation)

即兴记号用来表示一段主体节奏确定,具体怎么演奏随意发挥的内容。这个功能是由 \improvisationOn\improvisationOff 两个命令组成的,这两个命令之间的音符会变成即兴记号(一条斜线),音符的种类会影响斜线在五线谱的上下位置,以及输出音频的时候,即兴部分会用指定的音符的音色(虽然显示上是一条斜线),一般都用同一种音符就行了,毕竟只是为了表示节奏,用 sn 起始画出来差不多在中间的位置,应该有更细节的控制样式的操作,这里就不细说了。

improvisation

improvisation.ly

<<
  {
    hh 8 8 8 8
    \improvisationOn
    sn4. 8~
    4. 8~ 4. 8
    \improvisationOff
  }\\{}
>>

更多细节参见LilyPond — Notation Reference v2.22.1: 1.1.4 Note heads#Improvisation

重音/强调(Accent)

在普通音符后面写上->即为重音,比如普通的敲军鼓 sn8,对应重音的话写成 sn8->

更多细节参见LilyPond — Notation Reference v2.22.1: 1.3.1 Expressive marks attached to notes#Articulations and ornamentations

鬼音(Ghost note)

在音符前面加上 \parenthesize 就会把原来的音符变成鬼音,比如 sn8 对应的鬼音的话写成 \parenthesize sn8

更多细节参见LilyPond — Notation Reference v2.22.1: 2.5.1 Common notation for percussion#Ghost notes

常用的音符

乐器/击打方式/音色 LilyPond 的写法 说明
底鼓(Bsss drum) bd, bassdrum 普通一击
一嗵鼓 tomh, hightom 适用于三个嗵鼓的情况,其它数量的有别的嗵鼓音符可以用。
二嗵鼓 tommh, himidtom 适用于三个嗵鼓。
落地嗵鼓 toml, lowtom 适用于三个嗵鼓。
军鼓(Snare drum) sn, snare 普通一击
边击(Rimshot) 同时击中鼓面和鼓框的认真一击
没有找到直接的音符,但是可以手动用 LilyPond 指令画出来
退一步可以用写成军鼓的重音 sn->
制音边击(Cross Stick/Side Stick/Rim Click) ss, sidestick 槌头放鼓面上,用鼓槌尾部敲鼓框,类似敲木头的声音
鬼音(Ghost note) \parenthesize sn 非常轻的一击,几乎听不到,但是有一点感觉的声音
Stick Shot 一只鼓槌槌头放在鼓面,用另一只鼓槌敲鼓槌的中间部分
没有找到直接的音符,可能也是要手画。
重击镲 cymc, crashcymbal
节奏镲 ride, ridecymbal 普通敲击镲面
节奏镲(镲帽) rb, ridebell 敲节奏镲的镲帽
踩镲 hh, hihat 闭合状态踩镲的敲击
踩镲(闭合) hhc, closedhihat 闭合状态踩镲的敲击
一般用 hh 可以替代,这个主要是更明显地标示踩镲的闭合
踩镲(全开) hho, openhihat 张开状态踩镲的敲击
踩镲(半开) hhho, halfopenhihat 半开状态的踩镲的敲击
踩镲(脚踏) hhp, pedalhihat 脚踏控制的一次踩镲开-合,让两个镲片碰撞产生的声音
踩镲(Foot Splash/Hihat Splash) 脚踏控制的一次踩镲的开-合-开,让镲片碰撞产生声音并且延续
没有直接能用的音符,一些地方记录成 hhp 的符号再多加一个圆圈表示

更多参照LilyPond — Notation Reference v2.22.1: A.15 Percussion notes

自定义音符的样式和位置可以从这里开始:LilyPond — Notation Reference v2.22.1: 2.5.1 Common notation for percussion#Custom percussion staves

乐谱的总体设定

一个乐谱除了音符之外,还需要一些额外的信息才能变得完整,比如拍号、速度之类的,这就是把音符括起来的外面一圈内容表示的信息:

\drums {
  \tempo 4 = 100
  \numericTimeSignature
  \time 4/4
 
  % 乐谱的音符...
}

\time 4/4 比较直观,是指每小节四拍,四分音符一拍的拍号,它表示随后的音符都是按照这个拍号来的,如果乐谱到中间要改变拍号了,就再写一个 \time 指定新的拍号就好了。更多用法参见 LilyPond — Notation Reference v2.22.1: 1.2.3 Displaying rhythms#Time signature

\numericTimeSignature 会让其后出现的拍号显示成数字的形式,默认情况下 LilyPond 会将 4/4 拍显示成一个大 C 记号,这是另外一种流派的拍号表示,可以根据个人需要选择拍号的表示。

\tempo 4 = 100 表示了乐谱的速度,4 = 100 即为每分钟 100 个四分音符。除了用固定的数字表示之外,速度也可以定成一个范围,比如 \tempo 4 = 100 - 120,或者用文本的方式表示,例如 \tempo "Allegretto",或者你不想做选择题,数字文字全都要也是可以的,不给乐谱写 \tempo 标记速度也可以,都可以,参见 LilyPond — Notation Reference v2.22.1: 1.2.3 Displaying rhythms#Metronome marks

\drums {} 表示记录一段鼓谱(准确来说是打击乐谱),只是写鼓谱的话不用太关心它背后具体的功能,好奇的话可以参见 LilyPond — Notation Reference v2.22.1: 2.5.1 Common notation for percussion#Basic percussion notation

另外,以 % 开头的一行内容是注释,那一行的内容不会被 LilyPond 处理。

乐谱的输出设定

虽然已经能表示鼓谱,能写音符了,还需要一些操作让 LilyPond 知道总共有哪些乐谱,需要怎么被输出,所以有了剩下的一块内容:

\version "2.22.1"
 
main = {
  \drums {
    % 乐谱内容
  }
}
 
\score {
  \main
  \layout {}
}
 
\score {
  % https://lilypond.org/doc/v2.22/Documentation/notation/using-repeats-with-midi
  \unfoldRepeats {
    \main
  }
  \midi {}
}

变量的使用

首先是离乐谱最近的 main = {},这是 LilyPond 的定义变量的功能,随后的地方可以用 \main 这个写法来引用 main = {} 里面包含的乐谱,main 这个名字只是顺手取的,可以根据实际内容取合适的名字,可以是一整个乐谱(示例的使用场景),也可以是一小段音符,比如:

(注意:鼓谱的音符需要用 \drummode 包起来)

dadada = { \drummode { sn8 8 8 8 8 8 8 8 } }
zizizi = { \drummode { hh8 8 8 8 8 8 8 8 } }
 
main = {
  \drums {
    % 引用上面的两段内容
    \dadada \zizizi
    \zizizi \dadada
  }
}

更多关于变量的使用介绍参见 LilyPond — Learning Manual v2.22.1: 2.4.1 Organizing pieces with variables#Basic percussion notation

排版与 MIDI 输出

\score {} 是 LilyPond 文件的基本组成单位,所有的表示音乐的内容都需要放到某个 \score 里面。\score 内部的 \layout 表示为当前 \score {} 内的音乐排版一份可打印的输出,而 \midi 是表示为 \score {} 里的音乐创建一份 MIDI 输出。

理论上一个乐谱只需要一个 \score {},直接写成:

\score {
  {
    % 令人心动的音乐
  }
  \layout
  \midi
}

就可以表示同时输出可打印的输出和 MIDI 输出了。但是,由于 LilyPond 有表示乐谱的各种形式的重复的记号,它们不能被直接转换成 MIDI 信号,如果不加处理的话,重复记号部分的音符会在最后生成的音频里没有声音。可是重复记号是乐谱不可分割的一个基本功能,用还是得用的,于是 LilyPond 给出的方案是把乐谱分成两份,一份普通地用 \layout 产生一份可打印的输出,另一份,先把重复记号都展开成实际的音符(\unfoldRepeats {} 所做的事情),然后再产生 MIDI 输出,结果就变成了写两个 \score {} 的样子。

更多关于 MIDI 输出与重复记号问题的说明参见 LilyPond — Notation Reference v2.22.1: 3.5.6 Using repeats with MIDI

更多关于 LilyPond 的文件结构的说明参见 LilyPond — Learning Manual v2.22.1: 3.1.1 Introduction to the LilyPond file structure

另外,如果想给乐谱加上标题和署名之类的信息的话,参见:LilyPond — Notation Reference v2.22.1: 3.2.1 Creating titles headers and footers

最后,文件开头还有个 \version "2.22.1",这是标识这份 LilyPond 文件对应需要什么版本的 LilyPond 才能处理的,通常来说,在你遇到问题之前都不用关心它。如果使用 Frescobaldi 来编写 LilyPond 文件的话,它会自动为新文件创建一行合适的 \version

生成乐谱

乐谱都写好了,排版的事情就可以交给 LilyPond 了,在终端里使用 lilypond 命令就可以创建排版好的图片格式的乐谱,以及 MIDI:

lilypond -fsvg -dcrop "刚刚写好的 LilyPond 乐谱.ly"

关于参数上,常用的有 -f,后面跟上文件的格式,可以控制输出的格式,比如 pdfpng,打印的话当然是 pdf,网页上使用的话,比较适合用 SVG 格式,因为能获得不受缩放影响的清晰图像。(注:MIDI 格式是只要文件里写了 \midi 就会输出,不需要在这里额外加参数)。

-dcrop 并不是必须的,只有在需要输出一小段乐谱的图片的时候才会用到。因为 LilyPond 默认输出的是 A4 尺寸的内容,即使只有一个小节的内容也会用那么大的面积,像本文里用到的乐谱片段的图片的话,我只需要只包含音符一个小小的图片,就需要用这个参数了。

完整的参数说明参见 LilyPond — Usage v2.22.1: 1.2 Command-line usage

生成音频

前面提到了,timidity 可以用来从 LilyPond 产生的 MIDI 文件生成能听的音频,使用上像是这样:

timidity "刚刚写好的 LilyPond 乐谱.midi"  -Ow -o "刚刚写好的 LilyPond 乐谱.wav"

-Ow 参数会让 timidity 输出 WAV 格式的音频。-o 是指定输出要保存到的文件名字。

更多 timidity 的参数参见 timidity(1)

其实 WAV 格式已经是能够用常规的音频播放器播放的了,如果想得到别的格式的音频,可以用 ffmpeg 来转换一下,比如生成在这个页面里用的 m4a 格式的音频,就可以:

timidity "刚刚写好的 LilyPond 乐谱.midi"  -Ow -o - | ffmpeg -i - "刚刚写好的 LilyPond 乐谱.m4a"

July 19, 2021 12:00 AM

July 07, 2021

Phoenix Nemo

修复 LVM XFS 的 Input/output error

某服务挂了。

设备被强制重启之后发现 LVM 满了,但是文件无法访问,所有文件操作显示 Input/output error

查看 dmesg 发现大量文件系统错误,应该是磁盘写满后仍有进程不断读写的过程中被强制断电的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[ 1714.217864] XFS (dm-0): page discard on page 00000000161e11d5, inode 0xd861b703d, offset 937984.
[ 1714.219674] XFS (dm-0): page discard on page 000000001d433e5e, inode 0xd861b703d, offset 942080.
[ 1714.221132] XFS (dm-0): page discard on page 00000000820efe8d, inode 0xd861b703d, offset 946176.
[ 1714.222431] XFS (dm-0): page discard on page 00000000518c8216, inode 0xd861b703d, offset 950272.
[ 1714.223744] XFS (dm-0): page discard on page 00000000753db760, inode 0xd861b703d, offset 954368.
[ 1714.225041] XFS (dm-0): page discard on page 00000000da40787d, inode 0xd861b703d, offset 958464.
[ 1714.226341] XFS (dm-0): page discard on page 00000000ba8adb4b, inode 0xd861b703d, offset 962560.
[ 1714.227629] XFS (dm-0): page discard on page 00000000784c4724, inode 0xd861b703d, offset 966656.
[ 1714.228923] XFS (dm-0): page discard on page 0000000063b2c764, inode 0xd861b703d, offset 970752.
[ 1714.228990] XFS (dm-0): page discard on page 0000000046a36fd8, inode 0xd861b703d, offset 974848.
[ 1714.337426] dm-0: writeback error on inode 58084519997, offset 905216, sector 34365282240
[ 1716.586318] dm-0: writeback error on inode 58084519997, offset 905216, sector 34365309816
[ 1728.444718] xfs_discard_page: 9674 callbacks suppressed

...

[ 1763.990454] XFS (dm-0): xfs_do_force_shutdown(0x8) called from line 955 of file fs/xfs/xfs_trans.c. Return address = 00000000ea9478e4
[ 1763.990459] XFS (dm-0): Corruption of in-memory data detected. Shutting down filesystem
[ 1763.992696] XFS (dm-0): Please unmount the filesystem and rectify the problem(s)

日志写的很清楚了,那就来卸载修复吧

1
2
~> umount /data
~> xfs_repair /dev/mapper/data

显示

1
2
3
4
5
6
7
8
9
10
Phase 1 - find and verify superblock...
- reporting progress in intervals of 15 minutes
Phase 2 - using internal log
- zero log...
ERROR: The filesystem has valuable metadata changes in a log which needs to
be replayed. Mount the filesystem to replay the log, and unmount it before
re-running xfs_repair. If you are unable to mount the filesystem, then use
the -L option to destroy the log and attempt a repair.
Note that destroying the log may cause corruption -- please attempt a mount
of the filesystem before doing this.

咦,为什么还要我 remount。

1
2
3
~> mount -a
~> umount /data
~> xfs_repair /dev/mapper/data

然后就是漫长的等待…(因为是 HDD)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Phase 1 - find and verify superblock...
- reporting progress in intervals of 15 minutes
Phase 2 - using internal log
- zero log...
- 19:33:58: zeroing log - 521728 of 521728 blocks done
- scan filesystem freespace and inode maps...
- 19:34:11: scanning filesystem freespace - 33 of 33 allocation groups done
- found root inode chunk
Phase 3 - for each AG...
- scan and clear agi unlinked lists...
- 19:34:11: scanning agi unlinked lists - 33 of 33 allocation groups done
- process known inodes and perform inode discovery...
- agno = 0
- agno = 15
- agno = 30
...
- agno = 27
- agno = 28
- agno = 29
- 19:43:14: process known inodes and inode discovery - 16555072 of 16555072 inodes done
- process newly discovered inodes...
- 19:43:14: process newly discovered inodes - 33 of 33 allocation groups done
Phase 4 - check for duplicate blocks...
- setting up duplicate extent list...
- 19:43:15: setting up duplicate extent list - 33 of 33 allocation groups done
- check for inodes claiming duplicate blocks...
- agno = 7
- agno = 3
- agno = 8
...
- agno = 30
- agno = 31
- agno = 32
- 19:43:24: check for inodes claiming duplicate blocks - 16555072 of 16555072 inodes done
Phase 5 - rebuild AG headers and trees...
- 19:43:27: rebuild AG headers and trees - 33 of 33 allocation groups done
- reset superblock...
Phase 6 - check inode connectivity...
- resetting contents of realtime bitmap and summary inodes
- traversing filesystem ...
- 19:48:58: rebuild AG headers and trees - 33 of 33 allocation groups done
- traversal finished ...
- moving disconnected inodes to lost+found ...
Phase 7 - verify and correct link counts...
- 19:50:02: verify and correct link counts - 33 of 33 allocation groups done
done

完事后重启,就可以重新访问 LVM 里的文件啦。

后记:

这次所幸根分区是单独的盘,如果根分区和 LVM 在同一块物理盘上的话,需要重启系统进入救援模式,手动激活 LVM 再执行修复。

以及 XFS 还是靠谱呀(看向某每天摸鱼看番剧透的 btrfs 开发者

某服务挂了。

设备被强制重启之后发现 LVM 满了,但是文件无法访问,所有文件操作显示 Input/output error

July 07, 2021 03:05 PM

June 14, 2021

中文社区新闻

替换旧密码散列

libxcrypt 4.4.21 版本开始,新的密码不再接受由弱密码散列算法(比如 MD5 和 SHA1)产生的散列。还在用弱算法保存密码的用户会在下一次登入时收到提示更新他们的密码。
如果登入失败(比如通过显示管理器Display Manager时)请尝试切换到虚拟终端(Ctrl-Alt-F2)然后从那儿登入一次。

by farseerfc at June 14, 2021 01:19 AM

June 08, 2021

frantic1048

FREEing B-style - 本间芽衣子 兔耳 Ver.

Menma usamimi

苦苦等待一年终于收到了这款面码,当初完全没发现到这个手办的,要不是某天晚上一不小心点到了GSC 官网看到了照片导致瞬间心动……结果来说还是很值得的,甚至感觉比当初看到的照片还要更棒一些。只是这个面码是真的,收到快递箱子的时候惊呆了,整个高度比之前最大的大秋千智乃的秋千上的鸟窝还要高一个头。

Menma usamimi

兔耳的发卡是组装的,左右都是防呆的插口,包装的保护也比较完善,很科学。

Menma usamimi Menma usamimi

太可爱了,没时间打字了,全自动快门浪费机启动!(点击图片可查看大图)

Menma usamimi Menma usamimi Menma usamimi Menma usamimi Menma usamimi Menma usamimi Menma usamimi Menma usamimi

Menma usamimi Menma usamimi Menma usamimi

Menma usamimi Menma usamimi Menma usamimi Menma usamimi Menma usamimi

Menma usamimi Menma usamimi

尝试了一下新的光照角度,看起来对比好很多。

Menma usamimi Menma usamimi Menma usamimi Menma usamimi Menma usamimi Menma usamimi Menma usamimi Menma usamimi

June 08, 2021 12:00 AM

June 04, 2021

frantic1048

EXQ Figure - 忍野忍

Shinobu

今天刚到的小忍(中忍?),和之前的那位不带刀的小忍相比,画风从可爱转向了美型。虽说带着长长的妖刀,但是不会感到凶。

另外最近感觉手办有点记录不过来了,终于注册了很久以前被 Mike Lei 安利的有很多好看的亚丝娜的 MyFigureCollection:https://myfigurecollection.net/profile/frantic1048 ,看起来是个很科学的平台。

Shinobu Shinobu Shinobu

面具和绳子看起来以为是活动的,尝试取下之后才发现是完全固定的,并且绳子也是硬的。头发和躯干的接缝略有点明显,但是只要不拍到就好啦。腿部反光太强了,尝试了拉远灯的距离、调了很久的光的角度、加上轻微的修图依然能感受到……刀的上色很科学,很有金属感。

Shinobu Shinobu Shinobu

Shinobu Shinobu

June 04, 2021 12:00 AM

May 25, 2021

中文社区新闻

大部分官方 IRC 频道移至 libera.chat

你们中一些人可能已经听说了过去几日 freenode.net 的归属权纷争。Arch Linux 和很多别的项目在过去的几十年间一直使用这个 IRC 网络作为讨论和支持的平台。纷争导致很多前 freenode 成员逃离这个网络,成立了新的网络: libera.chat

今日起,Arch Linux 将携其姊妹项目 Arch Linux ARMArch Linux 32 一起将官方 IRC 频道从 freenode.net 迁移至 libera.chat 。请给予我们一些时间以待迁移工作尘埃落定。

我们感谢 freenode 社区多年以来的服务和合作。

by farseerfc at May 25, 2021 02:32 AM

April 25, 2021

Alynx Zhou

新项目和新相机和新住处

本来之前是想等新开的项目搞差不多了就来更新博客,结果没想到越写 TODO 越多一直搞到这个月才搞定,所以到现在才更新。

新项目

从家里回来北京之后第一件事情就是 SUSE 的 HackWeek,我之前大致想好了要做什么,我用过 screenkey 这个项目,但它使用了 X11 的 API,所以不支持 Wayland,我简单调查了一下,发现其实是可以绕过显示部分直接读取输入设备的事件的,于是就打算造一个替代品。

读取键盘事件的部分其实很容易,简单试了一下就完成了。但是反而是显示部分比较难搞。一开始我打算用刚发布稳定版的 GTK4,结果发现 GTK4 在 NVIDIA 驱动的 Wayland 会话下面反而是有问题的,只能回退到没问题的 GTK3。我原本以为一周的时间做出一个能用的程序还是挺充足的,但是后来发现中间有各种各样的问题和奇怪的 work around。比如涉及到 GObject 对象在什么时候释放,有些文档说的也不是很清楚。以及因为要用单独的子进程执行需要 root 权限的后端读取输入事件和用单独的子线程查询后端输出带来的进程/线程间通信问题。篇幅有限,打算后续再开一篇博客来介绍这里面的经验。在一周的时间里勉强做出了能用的 demo 参加了 HackWeek 的成果展示环节(给有始有终的参与者的小礼物大概还放在我公司的桌子上,我都不知道是什么,因为一直没去取),然后用了几周打游戏的时间整理代码里面的问题(主要是各种资源释放),以及做一下翻译工作和打包工作,终于在上周达到基本稳定了。如果有兴趣,可以访问 https://showmethekey.alynx.one/ 或者观看 我发在 B 站上的介绍视频

新相机

之前一直想提升视频的画质,但是还有一个想法是想玩玩摄影,因为平时周末经常是起得很晚然后在家里玩游戏,感觉也挺无聊的,所以打算买个相机,这样周末就有理由出去走走了。拍出来什么东西无所谓,主要是找点乐趣。然后朋友建议我买黑卡,虽然这个玩意确实很黑科技,但我并不是太感兴趣。很久以前我家里有一个数码相机,不是卡片机,是一个能变焦变很长的型号,但是后来智能手机出现之后就很快的不用它了,因为没有手机轻便的同时也没有足够的专业程度。所以我对相机的看法就是要么就搞高级一点的,肯定不会被手机取代,要么就用手机算了,而且说实话最高端的黑卡价格也都达到微单的价格区间了。考虑到要拿来录视频,很多录视频的 UP 主选择的都是微单而不是单反,再查找了一下发现很多人都用的索尼 A73,那选一个大家都在用的总是错不了的吧,于是初步选定了目标。然后又开始考虑是 A73 还是 A7C,虽然很多人对于 A7C 只是缩小体积然后价格却比 A73 高感到不满,但我阅读了一个对比表格之后,觉得还是 A7C 更适合我这种有录视频需求的人,于是就选择了 A7C。

我个人是对 A7C 相比 A73 砍掉的部分没什么需求的,只看参数很容易会变成那种“这个也想要那个也想要”的情况,虽然作为消费者而言,当然是希望厂家提供越多的功能越好。但是在只有这几个选项的情况下还是得考虑需求做一下取舍。比如去掉 MicroUSB 接口导致只能接一些 USB-C 接口的配件,看了看配件价格,短期内我大概是不会考虑这些配件的……去掉前拨轮和摇杆对我来说也不是不能接受,毕竟触摸对焦也挺好用的。虽然少了很多自定义按键,但是方向键现在都是可以自定义的,其实差别也不大。反而轻巧的体积和翻转屏,以及没有录制时间限制于我来说很重要。有人说没有双卡槽不够专业,万一给别人录东西丢了数据没有冗余,但可以预见的未来我不会靠这玩意获得收入,录我自己的话丢了也不算什么无法挽回的后果(反正自己选的)。

看点作品?虽然我就是随便拍拍,还是有几张觉得好看的。

之前去五棵松玩,想试试夜拍效果,结果套头的焦段显然不够拍月亮,不过回家看看感觉这张图还不错。

某天吃完饭散步,坐在长椅上拍了一张,莫名觉得很好看。

之前经常来我窗口咕咕咕叫吵我清梦的珠颈斑鸠,我管它叫大鸽,经常我拿着相机走到窗口它跑了,这张一开始其实拍到了部分室内,导致窗外的部分有点过曝,不过好在 RAW 可以调整,拉低了一些曝光然后裁掉窗户周围的部分,就变得很生动了。

最近大鸽不怎么来了,是不是因为楼下在锯树,大鸽的家没了?(虽然我对珠颈斑鸠再造一个窝需要花的时间表示怀疑。)

买完了 A7C 莫名开始种草 A7S3,4K 60 帧看起来真诱人啊……1080P 60 帧和 4K 30 帧只能二选一到底是谁想出来的牙膏啊!

说起来最近拍照片之后发现存储空间的需求还是很大啊,虽然我两块 2T 的机械应该还足以满足短期内的需求。最近挖矿导致大容量硬盘也涨价了,估计更新存储设备也不太现实,还是有空精简一下吧。NAS 的问题我也考虑过,但是后来想想还是放弃了把星际蜗牛重新搞起来的想法,因为看了很多视频/数码 UP 工作室的介绍之后意识到 NAS 比起存储的用途,更大的用途还是共享,比如多个剪辑师可以同时访问 NAS 上面的一份素材,不用在每台电脑上都复制一份。但我显然没这个需求也没这个网速,只是存储一些数据的话,还是直接插台式机里面比较方便吧!

顺便我朋友一直怂恿我买一个大光圈定焦镜头,我也确实心动了,比较了一下之后打算先入 FE 55 F1.8 ZA,然后再考虑 FE 24-105 G F4 代替套头,不过因为一些原因还是暂时搁置了,原因的话就是下一部分。

新住处

当初因为时间比较紧所以选的这个住处,有几个不太满意的地方:房间太小了,放下柜子床电脑桌就没什么地方了。为了节约空间我是把桌子沿着床边摆的,我自己坐在桌子和衣柜之间,所以如果我要打开衣柜找东西就要把椅子推到桌子下面。然后在我 B 站录的视频里也能看出来,有朋友不止一次吐槽过我录视频环境过于混乱了。房子太旧了,实在是让人连做饭的想法都没,又破又旧,同时上班的话也比较麻烦,不是说交通不便,而是这个房子在一个院子的深处,出门坐公交或者去超市都要走很长一段距离。再加上我的卧室是直接挨着楼道,隔音又比较差,有时候午睡就会被上下楼的脚步声打扰。以及说实话我想要个稍微便宜点的房子,谁不想呢?

所以今年快到期的时候我就在考虑搬家的问题。我们现在还是在家办公的状态,所以相对来说位置不太重要,虽然还是想尽可能找个上班方便的地方。但是很少有人和我一个方向上班,所以折腾了很久。本来是打算搬到更西南边的,甚至这一个月跑了好几个不同地方看房子,但是都因为各种原因没有选中,比如同学上班太远,周围没什么生活设施(虽然就在路边但是出门走好远好远才有超市到底是怎么搞的啊,为什么北京这边经常小区靠着路的一侧都是围墙,没有什么商业网点之类的,真的很没生活感),离地铁站太远,小区门禁是人脸识别(谁允许你们随便采集人脸信息的,而且约朋友来我家还要登记算几个意思啊)。而且想一起合租的同学以后可能考了教师资格证之后回老家工作,那样到时候就要再找人合租,我实在是不抱信心,所以还是决定再找别人合租。

最后和有猫的孙老师在交大附近找了另一个房子。看了一上午最后直接选中的原因只有一个:实在是太新了,刚装修完,非常干净,而且卫生间和厨房都有采光。让我这个饱受破旧和阴暗厨房之苦的人非常满意。同时面积分配也非常科学,孙老师希望要有阳台的卧室,养猫比较方便,我希望卧室面积别太小,这样我录视频的时候把电脑桌当作背景,可以有一定的景深,让桌子在焦外就不会显得太乱。很多房子都是有阳台的卧室大得不得了而另一个卧室只能放个床,这个房子的次卧则不一样,有充足的地方放桌子。所以我们就敲定了这个,兜兜转转还是没离开学校周边。

当然也没有完美的事情,满足了空间和干净和隔音(卧室并不是直接挨着楼道),价格和位置也就不能太强求了,交通的话看起来还好,离路边不会太远,大学周围也不至于太荒凉吧(街头生活感本来就和整齐不沾边,城市到底是让大人物拿来看的地方还是让小人物生活的地方呢?)。虽然考虑到更高的房租+中介费让我感到心疼肉疼(感觉镜头离自己远去了)……但是安慰自己多花几百块买来了刚装修完崭新的房子其实也很赚了对吧!

所以希望接下来能轻松地搬家,毕竟我东西还是挺多的,有了空间就可以仔细布置一下了,让视频看起来更精致一点。

总之还是要对生活有希望吧。

Alynx Zhou

A Coder & Dreamer

by Alynx Zhou (alynx.zhou@gmail.com) at April 25, 2021 06:50 AM

Leo Shen

Deploying this site on CloudFlare Pages

Historically I've been using my own server to host this site. This leads to very good versatility, but this also means CloudFlare will have to reach the original server every HTML request, and that significantly increases the first request latency. I tried to mitigate that via enabling cache on everything with CloudFlare, but the CDN still has to check periodically that if the cache has expired, which is not ideal.

April 25, 2021 01:00 AM

April 21, 2021

ヨイツの賢狼ホロ

Google 的 FLoC 是个糟糕透顶的主意

翻译自电子前哨基金会 (Electronic Frontier Foundation) DeepLink BLog 的 Google’s FLoC Is a Terrible Idea : https://www.eff.org/deeplinks/2021/03/googles-floc-terrible-idea , 原作者 Bennett Cyphers

EFF 网站的内容以知识共享 署名 4.0 协议授权,本译文也如此。

2021 年 4 月 9 日追记:我们开设了一个名为 "Am I FLoCed" 的网站,如果汝有在用 Google Chrome 浏览器的话,汝可以来看看汝有没有“幸运的”成为 Google 主导的新的“队列联合学习”(Federated Learning of Cohorts ,或者缩写成 FLoC)这一定向广告实验的小白鼠咯。


第三方 Cookie 正在消亡, Google 正尝试创造它的替代品。

没必要为这玩意表示哀悼。二十年来,第三方 Cookie 成了阴暗、肮脏的广告监控行业的关键部分,价值高达数十亿美元;咱们早该淘汰像跟踪 Cookies 这样的 持久的第三方标识符了。不过呢,广告业最大的“玩家”在基础发生变化的时候也不是毫无作为。

Google 正在用一套新技术引领取代第三方 Cookie 的变革。虽然它的提案中的某些部分表示他们并没有从对监控这一商业模型的持续的反对中吸取教训。 这篇文章关注的是提案之一的 “队列联合学习”,这也许是这其中最具野心,破坏性也最大的一部分。

FLoC 意为把原来那些第三方跟踪器做的那些事情转嫁给汝等的浏览器:在这种情况下,汝最近的浏览活动会被归纳为一个行为标签分享给广告商。 这种方法看起来解决了第三方 Cookies 曾经遇到的那些隐私问题,却又在这一过程中产生了新的问题。它也许还会加剧行为广告带来的许多严重的不和隐私相关发问题,像是歧视和劫掠目标。

作为“隐私倡导者”,Google 说 FLoC (和他们声称的“隐私沙盒”里的一 些其他元素)的表现会比现在任由“数据经纪人”和广告巨头肆无忌惮的跟踪和画像更好。 不过这个整个愿景的基础——我们只能从‘老式的跟踪方法’和‘新式的跟踪方法’之中选择一个——本身就是个伪命题。 这并不是个非此即彼的问题。与其重新发明跟踪轮子,咱们为啥不去想象一个没有目标投放广告(和它带来的无数问题)的更好的世界?

这是一条岔路,后方是第三方 Cookies,这也许是互联网发明以来最大的错误。咱们的前方则是两种选择。

一条是,用户可以决定与他们选择互动的每个网站分享什么信息。当用户下次打开一个标签页时,不必担心他们过去的浏览记录会反被用来操纵他们。

另一条则是,每个用户的行为被一目了然的从一个网站传送到另一个网站。他们最近的历史,被提炼成几个片段,被 "民主化",然后和参与每个网页服务的几十个 无名角色分享。用户在每次互动时,都会以“这是我这周的活动,请借此关照我”这种自招的告白开始。

用户和(真正的)隐私倡导者都要拒绝 FLoC 和其他的试图重新发明基于行为的定向广告的错误尝试。 希望 Google 放弃 FLoC 转而为构建真正的用户友好的互联而努力。

FLoC 是个啥?

2019 年,Google 提出了“隐私沙盒”,一种对网络隐私的未来愿景。 它的中心是一套满足现在第三方 Cookies 提供给广告商的诸多使用场景的协议,其中没有 Cookies。 在成立了一个主要由广告商组成的“ Web Advertising Business Group”(网络广告商务工作组?)以后,Google 又向 W3C 提交了这一系列的提案。其后的数个月,Google 和一众广告商又提交了一打以鸟类的名字命名的“技术标准”: PIGIN (这啥?), TURTLEDOVE (斑鸠?), SPARROW (麻雀?), SWAN (天鹅?), SPURFOWL(白鹭?), PELICAN (鹈鹕?, PARROT (鹦鹉?), 以及更多不胜枚举。 每一个“鸟”提案都在实现所谓的定向广告生态系统中一个目前由 Cookie 实现的功能。

FLoC 设计上用来帮助广告商不用第三方 Cookies 进行行为定位。启用 FLoC 的浏览器会收集用户浏览偏 具有相似偏好的用户(这里的“相似”要怎么定义?)会分组到同一队列中。每个用户的浏览器会和网站和广告商分享一份自己属于哪些群组的队列列表。根据他们的提案,每个队列中至少会有几千个用户(虽然没有保证)。

话太长不想看?那汝大可以这么想:汝的 FLoC ID 就是汝最近活动的简单摘要。

Google 的概念验证文档(Proof of Concept,PoC)用每个用户访问的网站的域名作为分组的依据。然后利用名为 SimHash 的算法来生成群组。 这个算法进行的计算可以在用户的设备上完成,因此不需要中心服务器去收集那些行为数据。但是“还是需要一个中心管理员来执行隐私保证”。为了防止队列为了识别用户规划的太小, Google 提议由一个中心统计分配给每个群组的用户数量。如果有的群组用户太少,可以将其与其他类似的群组合并,直到每个群组有足够的用户。

FLoC 对广告商有用的原因是,用户的队列群组会透露关于他们的行为的必要信息。

虽然大部分的细节在提案中还没有定论。这份提案的草稿表示用户的队列 ID 可以通过 JavaScript 访问,但是并没有清楚的指出会不会对可以访问的对象有所限制,以及队列 ID 有无其它的共享方法。 FLoC 可以基于 URL 或页面的内容(而不是之前提起的域名)来集群,也可以使用基于联合学习的系统(它的名字不就是这么暗示的?)代替 SimHash 来生成群组。 它也没有明确队列群组的数量,Google 在实验中使用了 8 位长的队列标识符(也就是队列有 256 个),实际情况下的数量应该会更大;文档中建议的长度是 16 位, 也就是四个十六进制数。队列越多,它们就越具体。更长的队列 ID 意味着广告商了解了更多用户的兴趣,也能更容易的描绘他们的指纹。

不过期限这件要素 ''是'' 约定好的,FLoC 队列会以每周为一个周期利用上周的浏览数据重新计算。这使得 FLoC (看起来)有些难以用于长期识别,但这也使它们更有力地计量了用户在一段时间内的行为。

新的隐私问题

FLoC 意在将定向广告带入保护隐私的未来。不过它的核心设计涉及到了会和广告伤分享新的信息。于是就不出所料的引入了新的隐私风险。

指纹

首先令人担忧的就是指纹。老生常谈的浏览器指纹就是通过收集用户浏览器中离散的信息为这个浏览器创建一个独特和稳定的标记的过程。 EFF 的 “Cover Your Tracks” 演示了这个过程是如何运作的。 一言以蔽之:汝的浏览器看起来越和其他人的不一样,就更容易制作一个独特的指纹。

Google “保证”说绝大多数的 FLoC 队列都会包含几千名用户,因此一个队列 ID 应该不能把汝从数千个同一队列中的其他用户区分出来。然而,这还是给指纹识别者带来了巨大的领先优势。 要是跟踪者从汝的队列 ID 出发的话,它就只需要从几千个(而不是上百万)浏览器中把汝挑出来了。 用信息论的术语来说的话,FLoC 队列包含几比特的,在 Google 的实验中最大是 8 bit。 鉴于这些信息不太可能与浏览器暴露的其他信息相关联,所有它们说不定更加有力。 跟踪者利用现有的独特指纹跟踪一个 FLoC 用户说不定更容易了呢。

Google 也明白这会是个挑战,不过他们却是承诺会在更长远的“隐私预算”计划中来解决指纹的问题。 解决指纹问题是一个令人钦佩的目标,它的提案提供的捷径看起来也很有希望。 不过 FAQ 中又说这“是个早期阶段的建议,目前还没有浏览器实现”, 于此同时,Google 在这个月开始测试 FLoC。

指纹问题已经众所周知的难以阻止,像 Safari 和 Tor Browser 这样的浏览器为了减少指纹攻击面牺牲了大量功能,这个和追踪者进行的消耗战已经持续了数年之久。 缓解指纹追踪的方法一般都会是去除或者限制不必要的熵来源——然后 FLoC 成为了一种新的熵来源。在解决现有的指纹问题之前,Google 还要再制造一个新的问题吗?

跨越内容的暴露

第二个问题不太容易解释:这技术和已经在识别用户的追踪者分享了新的个人数据。FLoC 对广告商有用的原因是,用户的队列群组会透露关于他们的行为的必要信息。

这个项目的 GitHub 页面是这么描述的:

这个 API 将个人的一般浏览历史(以及一般兴趣)的一些信息“民主化”,让任何选择加入的网站都能获得...... 已经知晓某个人的网站(例如他/她/它通过电子邮件地址在这个网站注册)可以记录和展示他们的队列。这意味着有关个人兴趣的信息最终可能会被公开。

就像上面描述的那样, FLoC 队列自己并不能作为标识符。不过能以其它方式标记用户的网站(例如“通过 Google 登录”)就能把通过 FLoC 了解到的信息和个人资料关联。

两类信息可能会因为这种方式暴露:

  1. 关于浏览历史的具体信息。追踪者可能会通过逆向工程队列分配算法来确定任何属于特定群体的用户可能(或者一定?)会访问特定的网站。
  2. 观察者可能会了解到关于人口统计学或兴趣的一般信息。一般来说某一特定群体的成员很可能是某一特定类型的人。例如,某一特定群组可能会有很多年轻的黑人女性用户,或者中年共和党选民;或者 LGBTQ+ 青年。

这就意味着汝访问的网站不需要提前在整个互联网中调查汝就能在第一次接触中知道汝是个什么样的家伙。除此以外,汝的 FLoC 队列会定期更新,能以其它方式标记汝的网站也能借此跟踪汝浏览行为的变化。 别忘了,一个 FLoC 群组就是对汝最近浏览活动的摘要,仅此而已。

汝应该有权在不同的语境中展示汝身份中不同的部分,例如汝在访问一个医疗信息网站的时候会允许它获得汝的健康状况,但是多半不想让它知道汝的政治倾向。 类似的,汝在逛零售网站的时候也多半不想让它知道汝最近有没有阅读关于治疗抑郁症的资料。 FLoC 就这样腐蚀了汝划分的各种语境,取而代之的是把同样的摘要分享给了和汝互动的每一位。

隐私之外

FLoC 被设计来防止通过跨越环境的标识实现个性化的特征分析这一具体问题。 它们的目标都是避免让追踪者获取可以与特定人联系起来的特定信息。 但就像咱们之前展示的那样,FLoC 也能在大量的环境中帮到追踪者。即使 Google 能够通过迭代设计防范这一风险,定向广告的危害并不限于侵犯隐私。 FLoC 的核心目标就和公民自由的其他部分相悖。

定向的力量也是歧视。从定义来看,有针对性的广告允许广告商达到某些种类的人,而排除掉其它的。定向系统可以像为鞋子做广告一样容易的决定谁能看到招聘信息或贷款提议。

这么多年以来,定向广告利用的最多的地方就是剥削、歧视和伤害。关于工作、住房和信贷的广告可以根据目标人群的种族、宗教、性别、年龄或能力等因素不同进行程度的辨别。 基于信用记录或相关历史性特征的定位让高息贷款广告铺天盖地。 基于人口统计学、地点和政治派别的定向广告帮助了一大批出于各种政治动机传播虚假信息和压制选民的传播者。所有类型的行为标的都在增加“令人信服的诈骗”的风险、

与其重新发明跟踪轮子,咱们为啥不去想象一个没有目标投放广告(和它带来的无数问题)的更好的世界?

Google、Facebook 和其它不少的大广告平台已经在设法控制对它们平台的某些用途。例如 Google 限制广告商收集“敏感兴趣类别有关的信息”。 但是没有什么作用,铁了心要这么做的人总能找到解决整个平台对某些类型的定向某些类型的广告的限制的变通方法。

即使平台有绝对的权力知晓哪些信息能用来针对谁,也往往无法阻止对技术的滥用。 FLoC 用一种无监督学习算法来构建它的集群,所以没人能直接控制哪些人会被划分到一起。 (对于广告商的)理想状况来说,FLoC 会把有共同的行为和兴趣爱好的人划分到一起。但网络行为还会和各种敏感特征有关—— 像是性别、种族、年龄和收入这样的人口统计学数据 和“五大”人格特征,甚至心理健康状况。 很有可能 FLoC 也会把具有相同的此类特征的人划分到一起,FLoC 的分组也可能直接反映到与药物滥用、经济困难或支持创伤幸存者有关的网站的访问状况。

Google 提议称它可以通过监控系统的输出来检查和敏感类别的相关性。如果它发现某个队列与某个受保护群体的关系过于密切,管理服务器可以为算法选择新的参数,并告诉用户的浏览器重新分组。

这个解决方案听起来既奥威尔又西西弗,为了监控 FLoC 的分组和敏感分类有多么相关,Google 要用关于用户种族、性别、宗教、年龄、健康和财务状况的数据进行大量审计。每次发现关联太强的队列的时候,就不得不重新配置整个算法并再次尝试,希望新版本中没有其他 "敏感类别 "受到牵连。这个问题比之前在尝试解决的问题更加困难,而且每次解决的尝试都以失败告终。

如果 FLoC 大规模的应用,利用年龄、性别、收入等因素直接区分用户也许会更难,但还不无可能。 坐拥辅助信息的跟踪者可以通过观察和实验学习 FLoC 分组的“意义”——这队列中包含哪些人。 那些铁了心要这么做的人就饿能继续如此辨别用户。 此外,这种行为对于平台来说,将比现在更难监管。有不良意图的广告商必然会以合理的方式进行否认——毕竟它们只是根据行为(而不是那些“敏感的类别”)在接触人们。 整个系统对于用户和监管者来说也会愈发不透明。

Google,别这么做

我们在 Google 第一次提出这些提案的时候曾经把 FLoC 叫做 “隐私保护技术的反面教材”。 我们希望标准流程能阐明 FLoC 的根本性缺陷,让 Google 能重新考虑推进。 事实上,Github 上的几个 Issue 也提出了我们在这里强调的同样的问题。 (例如这个,这个这个。 不过 Google 还是在基础几乎不变的情况下继续开发这个系统。以“95% 有效的” Cookies 取代手段 向广告商夸口。发布于 3 月 2 日的 Chrome 89已经开始测试这一技术。 有一小批 Chrome 用户(大概有上百万)将会(或已经)参加这一实验。

别搞错了,如果 Google 是在按计划在 Chrome 上实现 FLoC 的话,那应该会给大家“选项”。这个系统可能会让从中受益的广告商选择加入,而让觉得会受到损害的用户选择退出。 然后 Google 会在明知绝大多数用户完全不知道 FLoC 是啥,没啥人会选择关闭的的情况下吹捧说这是这是 "透明度和用户控制 "的一步。 自夸说自己终结了在它半生中帮自己赚到上十亿美元的 邪恶的第三方 Cookie,在互联网上开创了一个“新的私密时代”。

不一定非要这样。隐私沙盒中最重要的部分,譬如放弃第三方标识符和对抗指纹识别,将真正更好地改变网络的现状。Google 本可以把那些旧的监视脚手架拆掉的时候不再换上新的既独特又有害的家伙们的。

我们坚决反对 FLoC。那不是我们想要的,也不是用户应有的世界。 Google 该从第三方追踪的经历中学到正确的教训,为用户(而不是为了广告商)设计浏览器。

by Horo at April 21, 2021 02:34 PM

April 12, 2021

Leo Shen

Redesigning this blog

On September 2019, I wrote the first version of this blog. It has been a pretty successful run, but it's time to improve it. To see some context, view release notes for the previous version. What didn't change Many things stays the same. For example, this version of the site is still written with Hugo. I have the idea to switch to Zola, which is written in Rust and has cleaner template language, but Hugo still has the best support for Org-mode.

April 12, 2021 11:00 AM

Phoenix Nemo

FAT32 与 FAT32 的不同

缘由:游戏用的台式机坏掉了。

去年底配了一台 ITX,GIGABYTE AORUS X570 I PRO WIFI 大概是这个型号的主板,在一次 BIOS 升级之后无法进入 POST,配件 LED 都显示正常,风扇正常。于是按照官方说明,准备重刷 BIOS:

  • 一张闪存盘格式化为 FAT32
  • 固件改名为 GIGABYTE.BIN 放在根目录下
  • 插在 BIOS USB 接口,接通电源但不开机的情况下按 Q Flash Plus 开始刷机

但是按下之后并没有描述的橙色灯亮,只看到闪存盘的存取 LED 快速闪了几下就没了动静。

一度以为文件有问题,或者闪存盘有问题,或者格式化有问题。于是拆了一个新的闪迪的闪存盘,重新下载保存固件,依然没有效果。

对着文档回顾一次操作顺序以免有疏漏,然后发现了蹊跷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~> sudo fdisk /dev/sdb
Welcome to fdisk (util-linux 2.36.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): o
Created a new DOS disklabel with disk identifier 0x5f576f17.

Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1):
First sector (2048-15137279, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-15137279, default 15137279):

Created a new partition 1 of type 'Linux' and of size 7.2 GiB.

Created a new partition 1 of type ‘Linux’ and of size 7.2 GiB.

嗯?

Partition Type 是 Linux。这会是读取失败的原因么?于是列出所有的文件系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Command (m for help): l

00 Empty 24 NEC DOS 81 Minix / old Lin bf Solaris
01 FAT12 27 Hidden NTFS Win 82 Linux swap / So c1 DRDOS/sec (FAT-
02 XENIX root 39 Plan 9 83 Linux c4 DRDOS/sec (FAT-
03 XENIX usr 3c PartitionMagic 84 OS/2 hidden or c6 DRDOS/sec (FAT-
04 FAT16 <32M 40 Venix 80286 85 Linux extended c7 Syrinx
05 Extended 41 PPC PReP Boot 86 NTFS volume set da Non-FS data
06 FAT16 42 SFS 87 NTFS volume set db CP/M / CTOS / .
07 HPFS/NTFS/exFAT 4d QNX4.x 88 Linux plaintext de Dell Utility
08 AIX 4e QNX4.x 2nd part 8e Linux LVM df BootIt
09 AIX bootable 4f QNX4.x 3rd part 93 Amoeba e1 DOS access
0a OS/2 Boot Manag 50 OnTrack DM 94 Amoeba BBT e3 DOS R/O
0b W95 FAT32 51 OnTrack DM6 Aux 9f BSD/OS e4 SpeedStor
0c W95 FAT32 (LBA) 52 CP/M a0 IBM Thinkpad hi ea Linux extended
0e W95 FAT16 (LBA) 53 OnTrack DM6 Aux a5 FreeBSD eb BeOS fs
0f W95 Ext'd (LBA) 54 OnTrackDM6 a6 OpenBSD ee GPT
10 OPUS 55 EZ-Drive a7 NeXTSTEP ef EFI (FAT-12/16/
11 Hidden FAT12 56 Golden Bow a8 Darwin UFS f0 Linux/PA-RISC b
12 Compaq diagnost 5c Priam Edisk a9 NetBSD f1 SpeedStor
14 Hidden FAT16 <3 61 SpeedStor ab Darwin boot f4 SpeedStor
16 Hidden FAT16 63 GNU HURD or Sys af HFS / HFS+ f2 DOS secondary
17 Hidden HPFS/NTF 64 Novell Netware b7 BSDI fs fb VMware VMFS
18 AST SmartSleep 65 Novell Netware b8 BSDI swap fc VMware VMKCORE
1b Hidden W95 FAT3 70 DiskSecure Mult bb Boot Wizard hid fd Linux raid auto
1c Hidden W95 FAT3 75 PC/IX bc Acronis FAT32 L fe LANstep
1e Hidden W95 FAT1 80 Old Minix be Solaris boot ff BBT

Aliases:
linux - 83
swap - 82
extended - 05
uefi - EF
raid - FD
lvm - 8E
linuxex - 85

好家伙,看到一个 0b W95 FAT32。直接把分区类型改为 0b 再重新格式化,放入文件,插回 BIOS USB 接口,按下 Q Flash Plus,瞬间电源启动,橙色 LED 开始闪烁,说明已经开始刷写。

查了 Wikipedia,看到这么一段话:

Lists of assigned partition types to be used in the partition table in the MBR were originally maintained by IBM and Microsoft internally. When the market of PC operating systems and disk tools grew and liberated, other vendors had a need to assign special partition types to their products as well. As Microsoft neither documented all partition types already assigned by them nor wanted to maintain foreign assignments, third parties started to simply assign partition types on their own behalf in a mostly uncoordinated trial-and-error manner. This led to various conflictive double-assignments sometimes causing severe compatibility problems between certain products.([1])[http://msdn.microsoft.com/en-us/windows/hardware/gg463525.aspx]

所以也就有了 Partition Type 这样一个细节导致刷写任务失败,原因是 GIGABYTE 在开发 Q Flash Plus 的时候没有考虑过不同厂家、设备、操作系统所建立的 FAT32 分区类型的不同,文档中也没有提及这个问题。顺带又看到了同样有很多 GIGABYTE 主板的用户在网络上表示他们没有办法通过这种方式刷写 BIOS,也难怪会让人困惑了。

再顺便,使用闪迪出厂自带的 FAT32 文件系统无法正常被 Q Flash Plus 识别,是不是也说明了闪迪的闪存盘在出厂时建立的文件系统类型并不是 0b W95 FAT32

总之:

  1. 我的主板终于修好了。
  2. 开发任何功能时要考虑好对不同硬件、系统、环境的兼容性。
  3. 下次买主板,还是乖乖选败家之眼 ASUS ROG 吧(笑

缘由:游戏用的台式机坏掉了。

April 12, 2021 07:19 AM

April 11, 2021

berberman

Subtle cases in GHCi

Posted on April 11, 2021 by berberman

我写这篇文章的原因是无意中发现 head [] 在关闭 ExtendedDefaultRules 的 GHCi 中可以通过类型检查,让我感到很意外;经过一番摸索之后——

前言

GHCi,即 GHC 的交互式(interactive)环境,可以用来求值 Haskell 表达式、加载编译好的 Haskell 程序、交互式推断表达式的类型等等。想必使用 GHCi 是每一位 Haskell 程序员的必备技能。然而,在 GHCi 中类型检查规则与默认情况下的 GHC 略有不同,这可能在某种程度上会带来一定困惑。在这篇文章中,我们将一起认识带来这个不同的语言扩展,并通过 GHC 以及 GHCi 的源码了解语句在 GHCi 中是怎样被执行、并打印的。

GHCi 中默认启用的语言扩展

一些在 GHC 中无法通过类型检查的表达式在 GHCi 环境中却可以通过,我们来看一个最简单的例子:

-- 在 ghci 中
λ> show $ reverse []
"[]"

-- 在普通的 ghc 中
foo = show $ reverse []
-- • Ambiguous type variable ‘a0’ arising from a use of ‘show’
--   prevents the constraint ‘(Show a0)’ from being solved.

在 GHCi 中,show $ reverse [] 返回了一个字符串 "[]",但在 GHC 中这个表达式不能过编译——正如错误信息所说,Show a 约束中的 a不确定 的,编译器无法从 [] 中推断出来,于是就不知道该选择哪个实例的 show 函数了。听起来很有说服力,因为 [a]a 的选择可能会改变 [a] 的显示方法:

data X = X
  deriving Show

instance {-# OVERLAPS #-} Show [X] where
  show [] = "??"
  show [x] = show x
  show (x:xs) = show x ++ ", " ++ show xs

-- >>> show ([] :: [X])
-- "??"

-- >>> show [X, X, X]
-- "X, X, X"

-- >>> show ([] :: String)
-- "\"\""

看来在 GHCi 中有特殊操作,帮助我们选择了一个 a 来显示空列表。让我们看看 GHCi 默认启用了哪些扩展:

λ> :showi language
with the following modifiers:
  -XNoDatatypeContexts
  -XExtendedDefaultRules
  -XNoMonomorphismRestriction
  -XNondecreasingIndentation

DatatypeContexts

NoDatatypeContexts…… 跑一下题,不得不说 DatatypeContexts 是个彻底失败的特性,来看个例子:

newtype Eq a => B a = B a

我们想让用来构造 Ba 必须满足 Eq 约束,好,咱来写个函数体验一下:

bEq :: B a -> B a -> Bool
bEq (B x) (B y) = x == y

-- • No instance for (Eq a) arising from a use of ‘B’
--   Possible fix:
--     add (Eq a) to the context of
--       the type signature for:
--         bEq :: forall a. B a -> B a -> Bool
-- • In the pattern: B x
--   In an equation for ‘bEq’: bEq (B x) (B y) = x == y

非常不幸,这是个伤星的故事——在模式匹配解构 B 时,它不光没有附带 a 满足 Eq 的证据,反倒向我们索要这个证据。在类型构造器前加的 Eq a => 只确保了在 构造 Ba 必须满足 Eq。于是我们需要在所有用到 B 的函数的签名上都加上 Eq a =>(如果不这样我们是没有办法解构 B 的)显然非常离谱。当然这个语言扩展已经将近废弃十年了,新的入门教程中几乎不会再出现它的身影,一些稍老的材料中也会提醒读者不要使用它。

ExtendedDefaultRules

能让开头 show $ reverse [] 在 GHCi 工作的正是这个语言扩展。这个扩展意为“扩展默认的规则”,那我们就得先搞明白“默认的规则”是啥。首先我们要知道,Haskell 中整数字面值的类型是 Num a => a,浮点数字面值的类型是 Fractional a => a。那么问题来了:233 == 233 中,233 的类型是啥?(==) :: Eq a => a -> a -> Bool,这和前面例子中的 Show 很相似——光有 Num a 约束我们是无法进行比较的,除非 Num a => a 被实例化成某个 具体 的类型。这里则有 233 :: Integer。为什么呢?在 Haskell 2010 Report 中规定:

  • 表达式 e 若有类型 forall U. cx => t,该类型的全称量化 U 中含有类型变量 uucx 中出现了但却没在 t 中出现,我们就说这个类型是 非法 的,而表达式 e 的类型是 不确定
  • 在模块顶层可以使用 default (t1 , … , tn) (n >= 0) 为该模块声明一个 默认规则
  • 当遇到 不确定 类型时,假设类型变量 v不确定 的,在以下条件满足时我们说 v可默认的
    • v 仅出现在一个约束 C v 中(C 是一个类型类)
    • 约束的类型类必须是 NumNum 的子类
    • 这些类型类必须定义在 Prelude 中
  • 可默认的 类型变量会被 默认规则 列表中第一个所满足约束的类型替代,如果没有这样的类型则产生错误
  • 每个模块仅能有一个 默认规则 声明,它的作用域是该模块;如果一个模块没有 默认规则 声明,那么使用 default (Integer, Double)
  • 为模块声明 default () 会禁用掉这个功能

不难理解 print 233233 == 233 这些时候都有 233 :: Integer。那么开启 ExtendedDefaultRules 之后会发生甚么事呢?根据 GHCi 文档,当遇到约束 (C1 a, C2 a, …, Cn a) 时:

  1. 找到所有未解析的约束
  2. C a 这样的形式,把未解析的约束进行分组,使得一个组内不同的 C 共享相同的 a
  3. 仅保留包含至少一个 C交互式类型类 的组
  4. 对于每个留下的组 G,尝试将定义在 默认规则 中的 ty 依次代入 a;如果这让 G 中所有的约束都能被解析了,那么 a可默认类型 就是 ty

此外,ExtendedDefaultRules 还会:

  • 定义 ShowEqOrdFoldableTraversable交互式类型类
  • 放宽限制——默认规则 中的类型必须要实现 Num 改为必须是 交互式类型类 的实例
  • 将标准情况下的 default (Integer, Double) 改为 default ((), [], Integer, Double)

按照这样的处理方式,只要 默认规则 中含有任意满足 Show 约束的类型,咱叫它 Fooshow $ reverse [] 就可以通过类型检查——列表的类型会被推断成 [Foo]

MonomorphismRestriction

这是个比较常见的坑,而且把每种情况解释一遍会很占篇幅,因此在这儿就说个大概。

上节我们提到过,print 233 中受到 默认规则 的影响 233 的类型是 Integer。那么在一个整数字面值的绑定中呢?

qwq = 233
-- Top-level binding with no type signature: qwq :: Integer

GHC 告诉我们 qwq 的类型是 Integer。可这里并没有涉及到使用约束,为什么字面值还是被实例化成了一个 具体 的类型呢?这就是 MonomorphismRestriction 的作用,没有类型签名的绑定会可能被应用 默认规则。这会让我们创建的绑定没有那么地“多态”。让我们一起看几个来自 Wiki 的例子:

f1 x = show x

f2 = \x -> show x

f3 :: (Show a) => a -> String
f3 = \x -> show x

f4 = show

f5 :: (Show a) => a -> String
f5 = show

默认不动任何扩展的情况下,f1f3 以及 f5 都是我们想要的,它们具有类型 Show a => a -> String;而 f2f4 无法通过类型检查,Show a 中的 a不确定 的。更有意思的是,这时候要是启用 ExtendedDefaultRulesf2f4 也是良型的了,但它们的类型是 () -> String。原因很简单,上节中我们提到过,() 会被插入到 默认规则 的头部,所以 GHC 选择了第一个能解析约束的 () 来实例化 a。当然,这有些反直觉——eta-reduce 一下居然改变了语义。根据 Haskell 2010 Report,这种限制是为了解决两个问题:无法通过标注类型签名解决的不确定性,以及不必要的重复求值。在 GHCi 中,我们通常不希望这种单态化发生:

λ> :set -XMonomorphismRestriction
λ> plus = (+)
λ> plus 233.3 3

<interactive>:28:6-10: error:
No instance for (Fractional Integer)
        arising from the literal ‘233.3
In the first argument of ‘plus’, namely ‘233.3
      In the expression: plus 233.3 3
      In an equation for ‘it’: it = plus 233.3 3

显然,这里 plus 的类型是 Integer -> Integer -> Integer。但在文件中用 GHC 编译是不会有问题的:

plus = (+)

qwq = plus 233.3 3

因为 GHC 会在推断出 plus 的类型前先看一看它被调用的地方,这时就有 plus :: Double -> Double -> Double;但在 GHCi 中语句是一步一步执行的,声明完 plus 直接就定下来了。

NondecreasingIndentation

咱也不知道这是干啥的,查了下好像在某些情况下可以让缩进往前越一级(?)不懂在 GHCi 里有啥用(x

GHCi 中的语句执行

在 GHCi session 中可以:

λ> let x = 1          -- 绑定纯的变量
λ> x' = 1             -- 绑定纯的变量,let 可以省略
λ> y <- pure 2        -- 绑定 IO 结果到变量
λ> 3 + 3              -- 求值表达式并打印
6
λ> print 4            -- 执行 IO 操作
4
λ> it                 -- 获得上次的求值结果
()

是不是感觉有点像在一个 IO () do notation 中,但可以省略 let、还多了个 it。这个 it 比较有意思,咱后面细说。咱可以直接看 GHCi 中的某个入口函数,它直接接收输入的字符串:

-- GHCi/UI.hs

runStmt :: GhciMonad m => String -> SingleStep -> m (Maybe GHC.ExecResult)
runStmt input step = do
  pflags <- initParserOpts <$> GHC.getInteractiveDynFlags
  st <- getGHCiState
  let source = progname st
  let line = line_number st

  -- 如果输入是一个 statement
  if | GHC.isStmt pflags input -> do
         hsc_env <- GHC.getSession
         -- 尝试 parse 它
         mb_stmt <- liftIO (runInteractiveHsc hsc_env (hscParseStmtWithLocation source line input))
         case mb_stmt of
           Nothing ->
             -- parse 失败什么也不做
             return (Just exec_complete)
           Just stmt ->
             -- 调用 run_stmt 执行
             run_stmt stmt
     -- 如果输入是一个 import
     | GHC.isImport pflags input -> do
         -- 添加 import
         addImportToContext input
         return (Just exec_complete)

     -- 其他情况我们把输入当作 declaration
     | otherwise -> do
         hsc_env <- GHC.getSession
         let !ic = hsc_IC hsc_env
         -- 尝试 parse 成 declaration
         decls <- liftIO (hscParseDeclsWithLocation hsc_env source line input)
         -- 看下个代码块
         run_decls decls

x = y 是一个声明(declaration),但 let x = y 是一个语句(statement)。GHCi 的处理非常直接,把所有这种声明 parse 完之后将 AST 改写成 let 语句并执行:

-- GHCi/UI.hs

run_decls :: GhciMonad m => [LHsDecl GhcPs] -> m (Maybe GHC.ExecResult)
run_decls [L l (ValD _ bind@FunBind{})] =
  run_stmt (mk_stmt (locA l) bind) -- 调用 run_stmt 执行
run_decls [L l (ValD _ bind@VarBind{})] =
  run_stmt (mk_stmt (locA l) bind) -- 调用 run_stmt 执行
run_decls decls = do -- 如果不是 FunBind 或者 VarBind,还得按声明来处理
  m_result <- GhciMonad.runDecls' decls
  forM m_result $ \result ->
    afterRunStmt (const True) (GHC.ExecComplete (Right result) 0)

-- 把 Bind 变成 LetStmt
mk_stmt :: SrcSpan -> HsBind GhcPs -> GhciLStmt GhcPs
mk_stmt loc bind = let la = ... in la (LetStmt noAnn (HsValBinds noAnn (ValBinds NoAnnSortKey (unitBag (la' bind)) [])))

可以看到“省略 let 的绑定”是在 GHCi 入口处实现的。那么 run_stmt 是啥?GHCi 最终调用的函数应该是 execStmt' ——这是 GHC 暴露 API 的一部分,所以有 Haddock 文档。这个函数干的最重要的一件事就是调用了 hscParsedStmt:而语句在这个函数中走过了完整的编译过程:tcRnStmt 完成 rename & typecheck、deSugarExpr 完成 desugar & generate core、hscCompileCoreExpr 完成 codegen & link。我们只关心类型检查部分。从注释我们可以清晰地知道 GHC 在类型检查时加 buff,实现创建交互式绑定以及 it 变量的策略:

                Typechecking Stmts in GHCi

Here is the grand plan, implemented in tcUserStmt

        What you type                   The IO [HValue] that hscStmt returns
        -------------                   ------------------------------------
        let pat = expr          ==>     let pat = expr in return [coerce HVal x, coerce HVal y, ...]
                                        bindings: [x,y,...]

        pat <- expr             ==>     expr >>= \ pat -> return [coerce HVal x, coerce HVal y, ...]
                                        bindings: [x,y,...]

        expr (of IO type)       ==>     expr >>= \ it -> return [coerce HVal it]
          [NB: result not printed]      bindings: [it]

        expr (of non-IO type,   ==>     let it = expr in print it >> return [coerce HVal it]
          result showable)              bindings: [it]

        expr (of non-IO type,
          result not showable)  ==>     error

HValue 相当于一个装着 Any 的容器——编译、求值一个表达式的结果可能有任意类型。GHC 将这几种情况分成三类,名曰 plans:

  • Plan A. [it <- e; print e]it 不能是 ()
  • Plan B. [it <- e]
  • Plan C. [let it = e; print it]

可在 tcUserStmt 中找到相应的代码:

-- TcRnDriver.hs

tcUserStmt :: GhciLStmt GhcPs -> TcM (PlanResult, FixityEnv)
tcUserStmt (dL->L loc (BodyStmt _ expr _ _))
  = do { (rn_expr, fvs) <- checkNoErrs (rnLExpr expr)
-- ...省略
          let
              -- [it = expr]
              the_bind  = cL loc $ (mkTopFunBind FromSource
                                     (cL loc fresh_it) matches)
                                         { fun_ext = fvs }

              -- [let it = expr]
              let_stmt  = cL loc $ LetStmt noExtField $ noLoc $ HsValBinds noExtField
                           $ XValBindsLR
                               (NValBinds [(NonRecursive,unitBag the_bind)] [])

              -- [it <- e]
              bind_stmt = cL loc $ BindStmt noExtField
                                       (cL loc (VarPat noExtField (cL loc fresh_it)))
                                       (nlHsApp ghciStep rn_expr)
                                       (mkRnSyntaxExpr bindIOName)
                                       noSyntaxExpr

              -- [; print it]
              print_it  = cL loc $ BodyStmt noExtField
                                           (nlHsApp (nlHsVar interPrintName)
                                           (nlHsVar fresh_it))
                                           (mkRnSyntaxExpr thenIOName)
                                                  noSyntaxExpr

              it_plans = [
                    -- Plan A
                    do { stuff@([it_id], _) <- tcGhciStmts [bind_stmt, print_it]
                       ; it_ty <- zonkTcType (idType it_id)
                       ; when (isUnitTy $ it_ty) failM -- it 不能是 ()
                       ; return stuff },

                        -- Plan B
                    tcGhciStmts [bind_stmt],

                        -- Plan C
                        -- 先看一看 let 绑定是不是良型的
                        -- 否则会得到两个错误信息,一个在 let 绑定上,一个在打印上
                    do { _ <- checkNoErrs (tcGhciStmts [let_stmt])
                       ; tcGhciStmts [let_stmt, print_it] } ]

-- ...省略

GHC 在这里加的特效大概就是拼接 renamed AST 来进行下一步的 tcGhciStmts

  • Plan A:e 是一个 IO 操作,并且这个操作的返回值可以打印并且不是 ()。将 e 的执行结果绑定到 it 上,并打印 it
  • Plan B:e 是一个 IO 操作,并且这个操作的返回值无法打印。将 e 的执行结果绑定到 it 上,不打印
  • Plan C:e 是一个可以打印的表达式。将 e 绑定到 it 上,并打印 it

从 Plan A 开始依次尝试,若全部失败就打印错误信息。这里的“打印”并不是使用 print,而是 ic_int_print。后者是一个任意具有类型 a -> IO () 的函数,可以通过 -interactive-print=<FUNCTION_NAME> 选项在 GHCi 启动时指定。

总结

到目前为止,相信读者已经对 GHCi 这套操作很熟悉了。那么回过头来看我在文章开头提出的困惑:

λ> :set -XNoExtendedDefaultRules
λ> head []
*** Exception: Prelude.head: empty list

为什么关闭了 ExtendedDefaultRules 之后 head [] 依然能在 GHCi 中预期执行,没有产生 a 不确定的错误呢?再看一眼上面的三个 plans —— Plan A 和 Plan C 都需要 Show 约束,只有 Plan B 不需要。答案呼之欲出:我们在尝试获取并执行(并非打印)一个 [IO a] 列表的头部,但这个列表是空的,所以扔出了错误,给我们造成了“正在尝试打印空列表头部”的假象。读完这篇文章可能会获得一些没什么用的知识:

  • headlast 这种 [a] -> a 的函数在 GHCi 中应用到空列表时没有用到 Show 约束
  • pure () 在 GHCi 中不会打印 ()pure 233 会打印 233
  • ……

April 11, 2021 11:58 PM

April 08, 2021

中文社区新闻

附带安装器的安装媒介

最新的安装媒介开始附带一个有指引的安装器
这是作为默认安装方式(跟随安装向导)的补充,比较像其余安装方式
如果你使用了这个安装器,不要忘记在寻求技术支持的时候提及这一点,以及如果被问到的话提供 archinstall 的日志。

by farseerfc at April 08, 2021 10:55 AM

March 30, 2021

frantic1048

春游武汉

最近被小伙伴安利到武汉的樱花,才知道原来还有这么厉害的地方,正好花季,找了个就近的周末,3 月 13 日就上路了。

spring in wuhan

上次去武汉还是 2016 年 8 月到 Deepin 实习的一阵子,时逢雨季涨水,去的又是被水淹特别厉害的金融港。刚到武汉的夜晚,司机开了大半程路说小伙子只能送你到这里了,前方在月光下荡漾的大池塘是地图上的公路,这还真是港口啊!每天上班十分钟步行路上很可能就突然来一波大风大雨,就算提前打伞也防不住一身的水,公司门口旁边就是个小浴室,不知道是不是也是为了应对这样的天气;一波下来的感受就是和传说中的「火炉」的外号一样,是个天气很猛的地方。

这次去天气就很舒服了,中午到武汉,吃了饭就立刻打车去东湖,结果先是一个司机各种不愿意去,第二个司机说你们这一趟少说两个小时,当时还疑惑这就几公里是要怎么走这么久的,就还是上车了。因为出发起得很早,车上困了一觉,醒来之后一看窗外,被车包围,再看地图,只走了一半多点的路程,卡在东湖隧道,时间已经下午三点多了,按照这速度四点能到都悬,结果出了隧道之后和小伙伴商量决定下车走过去了,还真比车走得快不少,到下午四点半终于到了东湖樱园门口,被人群吓到,差点没找到入口。进去之后还是人海,只是多了很多很多各种各样的大和更大的樱花树,非常密集,很棒。唯一的问题是,人太多了,和老家景区的黄金周完全一致,车行道堵车,人行道堵人,只要是能塞下人的地方都会有人,这方面体验很糟糕。可是的确又是得这个季节来才能看到花,体会了一番过去自己眼中的不嫌人多凑热闹傻子游客的心境。

来都来了,还是暂时忘掉人群,多关注一下花吧!

spring in wuhan spring in wuhan spring in wuhan spring in wuhan spring in wuhan

作为樱园,草坪、步行道、大灯我都懂,作为景区,各种小吃饮料和歇脚处也没毛病,到处汉服出租点也行吧有人喜欢,突然出现的小猪佩奇一家子还是超出了我的预想。

spring in wuhan

因为去的挺晚,很快就晚上了,顺便也感受了一下夜樱,各种 RGB 打上去还是蛮不错的。

spring in wuhan spring in wuhan

大门后面的樱园名字的石雕:

spring in wuhan

第一天路上想着有点晚要不要第二天早上再去一次东湖,路上堵了一波之后决定第二天还是去另一个也有很多樱花的解放公园逛逛。早上起来,一看地图东湖隧道就又堵上了。

spring in wuhan

出门沿着江边一路朝解放公园步行,路过一家星巴克,然后看到地图旁边是一家叫做 1.Z Coffee(江景店)的评价不错的店,想着来都来了,就上楼去感受了一下江景。

休息中的小伙伴: spring in wuhan

肥宅快乐三角: spring in wuhan

路上见到的大号加湿器: spring in wuhan

沿江一路步行,经过江滩公园,就接着逛了。

spring in wuhan spring in wuhan spring in wuhan spring in wuhan

一眼感觉是普通树林,仔细一看是光效打满的赛博树: spring in wuhan

神奇小店: spring in wuhan

终于到了解放公园,人也挺多,只是没东湖那么可怕了。

在公园里面又遇到了中华名塔园,进去康康。这里是个包含各地著名塔的缩小版石雕的园子,塔很多,细节也很不错,要是哪天能建一个 1:1 手办园就好了。

spring in wuhan spring in wuhan spring in wuhan spring in wuhan spring in wuhan

公园里另一处有个聚集非常多人的地方,是个有很多鸽子的木屋,看了一眼地图,这个地方叫做「鸽子广场」,我悟了! spring in wuhan

解放公园里各种花也蛮多的。

spring in wuhan spring in wuhan spring in wuhan

March 30, 2021 12:00 AM

March 25, 2021

ヨイツの賢狼ホロ

用 Hyper-V 玩些小花样

从前面那篇 Hyper-V 和 OpenWrt 搭建软路由 开始咱就开始在 Windows 上用 Hyper-V 了。 然后场面逐渐失控……

这篇文章就是把咱这几天遇到的各种问题和可能的解决方案记下来这个样子。

汝应该尝试 Hyper-V 吗?

如果汝有像咱一样的某些需求的话,那 Hyper-V 可能也很适合汝。

  • 想在 Windows 开机时自动启动某个虚拟机。

    VMware Workstation 15 还可以用内置的共享虚拟机设置开机启动的,不过 16 开始弃用了。 VirtualBox 文档里有提用服务设置开机启动虚拟机。 虽然咱一次都没成功过……

  • 有用另一台电脑访问这台电脑上虚拟机的需求。

  • 在用 WSL 2,那其实汝已经算是启用了 Hyper-V 了嘛。

  • (或者还有别的?)

不过汝也许会因为这些原因不想尝试。

  • 旧版 VMware Workstation 或 VirtualBox 用户。

    启用 Hyper-V 以后会导致旧版 VMware Workstation 或 VirtualBox 无法启动虚拟机。

    也有人觉得在开启 Hyper-V 的主机上开别的虚拟化软件时性能会下降什么的。

  • 想在 Windows 上试用 GNU/Linux 桌面。

    Hyper-V 的 framebuffer 缓存(是这个意思吧?)只有 8M,于是开 GNU/Linux 桌面可能会很吃力。 (增强会话是基于 RDP 的,嗯……)

  • Windows 10 家庭版用户 (也没法开)

以及咱这几天下来对 Hyper-V 的槽点,大概就只有那个检查点了吧, 虽然有类似快照的“检查点”,但是不能让某个虚拟磁盘不在检查点里。(VMware 的独立虚拟磁盘或者 VirtualBox 的完全写入)

环境准备

如果是 Windows 10 专业版(或者专业工作站版/企业版/教育版啥的),就参考前面那篇文章了。

不过咱这回换成 Windows Server 2019 了,所以从服务器管理器安装 Hyper-V 角色就可以了。

以及如果汝只需要 Hyper-V 这一个功能的话,还可以选择用 Hyper-V Server , 虽然这个没有图形界面,不过可以通过 Windows Admin Center 或者 Windows 远程管理来管理。

安装和使用 Windows Admin Center

Windows Admin Center 是本地部署的基于浏览器的应用,用于管理 Windows 服务器、 群集、超融合基础设施和 Windows 10 电脑。 它是免费产品,可供在生产中使用。

Windows Admin Center 概述 | Microsoft Docs

Windows Admin Center 可以装在 Windows 10 上作为客户端,也可以装在 Windows Server 上作为网络服务, 咱这次就装在 Server 上了。有桌面体验的话直接从 Microsoft Evaluation Center 下载和安装就好,如果是服务器核心模式或者 Hyper-V Server 的话,用 PowerShell 来下载和安装也是可以的。

# PowerShell 里也是有 wget 的,虽然用法和 GNU/Linux 里面的不太一样。
wget -Uri https://aka.ms/WACDownload -UseBasicParsing -OutFile c:\WindowsAdminCenter.msi
# 用 msiexec 安装 Windows Admin Center,这里选择用自生成的证书。
# SME_PORT 是 Windows Admin Center 的端口。
## 不要使用点斜杠相对路径表示法(例如 .\<WindowsAdminCenterInstallerName>.msi)
## 从 PowerShell 调用 msiexec。 该表示法不受支持,会导致安装失败。 请删除 .\ 前缀或指定 MSI 的完整路径。
msiexec /i c:\WindowsAdminCenter.msii /qn /L*v log.txt SME_PORT=443 SSL_CERTIFICATE_OPTION=generate

安装好后 Windows Admin Center 就会从 Windows Update 接收更新了。 然后从浏览器打开 Server 的 IP 地址:

Microsoft 推荐是用 Microsoft Edge 或者 Google Chrome 来访问 Windows Admin Center 啦。

在输入服务器上管理员的用户名和密码登录以后,就能见到主界面啦。

Windows Admin Center 的主界面

选择要连接的服务器,就能看到服务器的各种状态。

主机状态

如果已经安装好 Hyper-V 的话,就能在这里管理虚拟机。

虚拟机管理器

也可以管理设置和远程连接虚拟机。

虚拟机连接。

不过用 Windows Admin Center 下载来的远程桌面连接的时候,要输入的是服务器的用户名和密码的样子。

使用 Hyper-V 管理器和 Windows 远程管理(WinRM)管理虚拟机

效果就是可以用 Windows 上的 Hyper-V 管理器连接到服务器,然后就像 Hyper-V 运行在本地一样。 不过也挺麻烦的就是了。

首先,在服务器上启动 Windows 远程管理服务,以及适当的前期配置。 下面的命令都是在以管理员身份运行的 PowerShell 里完成的。

# 启动 WinRM 快速配置。
# 这三步中间的询问按提示来就可以了。
# 如果遇到因为公用网络无法设置防火墙规则的话,解决方法在下面。
winrm quickconfig
# 启用 PowerShell 远程访问。
Enable-PSRemoting
# 为凭据委派(咱也不知道这是啥)启用 CredSSP。
Enable-WSManCredSSP -Role server

如果汝遇上因为公用网络无法设置防火墙规则的话,可以用 PowerShell 更改网络类型。

# 用 Get-NetConnectionProfile 获得现在连接的网络适配器的信息
Get-NetConnectionProfile

    Name             : 网络 2
    InterfaceAlias   : vEthernet (Internal)
    InterfaceIndex   : 8
    NetworkCategory  : Private
    IPv4Connectivity : Internet
    IPv6Connectivity : LocalNetwork

    Name             : 网络
    InterfaceAlias   : vEthernet (External)
    InterfaceIndex   : 18
    NetworkCategory  : Public
    IPv4Connectivity : Internet
    IPv6Connectivity : Internet

# 用 Set-NetConnectionProfile 设置网络的类别,记得把 InterfaceIndex 换成汝需要更改的那个网络。
Set-NetConnectionProfile -InterfaceIndex 18 -NetworkCategory Private

接下来这些操作在客户端上完成。

# 首先就是把 Hyper-V 管理工具装上啦。
# 用 “启用或关闭 Windows 功能” 也是可以的。
add-windowsfeature rsat-hyper-v-tools
# 然后在客户端启动 WinRM 快速配置。
winrm quickconfig
# 设置客户端 WinRM 的信任的主机。
# 记得把 fqdn-of-hyper-v-host 换成服务器的完全限定域名(FQDN Hostname)
# 在 Windows 上的话, FQDN Hostname 就是服务器的计算机名加上连接特定的 DNS 后缀。
# 这个后缀能用 ipconfig 命令看到。例如咱这里的 FQDN Hostname 就是 "WIN-0JL0BB72KFC.lan"。
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "fqdn-of-hyper-v-host"
# 启用客户端 CredSSP 认证。
Enable-WSManCredSSP -Role client -DelegateComputer "fqdn-of-hyper-v-host"

接着打开组策略对象编辑器(gpedit.msc),把 "计算机配置 >管理模板 >系统 >凭据委派 >允许向仅 NTLM 服务器身份验证分配全新凭据" 设置为启用,并在列表里添加 “wsman/汝服务器的 FQDN Hostname” 。像这个样子。

设定必要的策略

打开 Hyper-V 管理器,选择“连接到服务器”,输入服务器的计算机名。以及把下面“以另一个用户身份登录”那项 选中。

设定服务器

不过设置用户的时候,这里的用户名是“服务器的计算机名\用户名”的形式。

设定服务器的用户

如果一切顺利的话,Hyper-V 管理器应该就能连接到服务器了。

连接成功的效果

通过 RemoteApp 运行 Windows 虚拟机上的应用

RemoteApp 就像 VMware 的 Unity 模式或者 VirtualBox 的无缝模式, 不过虚拟机不在运行 RemoteApp 的电脑上而是在远程连接上啦。

不过以官方的方法配置 RemoteApp 的话,汝需要设置远程桌面服务,购买相应的许可证,设置 Active Directory 等等操作。 咱这里就用一个第三方的开源小工具 RemoteApp Tool 好了。

可以在 https://github.com/kimmknight/remoteapptool/wiki/Windows-Compatibility 上看到兼容性列表, 基本就只有 Windows 的企业版和 Windows Server 支持了。(好像最新的 Windows 10 专业版也可以?)

安装好 RemoteAppTool 然后打开,界面大概就是这个样子。

RemoteApp Tool 的界面

点击下面的绿色加号按钮添加要供远程访问的应用。

RemoteApp Tool 添加应用

然后点击右下角的 “Create Client Connection” 生成远程桌面连接文件。基本上只要修改远程 桌面连接的地址就好。

RemoteApp Tool 生成 RDP 文件

然后就可以用远程桌面连接客户端连接啦,虽然可能没有本机上的无缝模式流畅。

RemoteApp 效果演示

以及 Windows 对远程会话的数量是有限制的(Windows 1 个,Windows Server 没有 RDS 许可时 2 个), 不过应该够用吧……


以上。

by ホロ at March 25, 2021 04:00 PM

March 16, 2021

frantic1048

FuRyu - 宇治松千夜 啦啦队

最近各种奇奇怪怪的事情忙,总算是有机会整理出这套千夜了,放在桌上每天都充满了精神!

顺便给博客糊了个勉强能点的分页,先将就着吧(

Chiya

Chiya Chiya

Chiya

Chiya Chiya

March 16, 2021 12:00 AM

March 14, 2021

ヨイツの賢狼ホロ

Hyper-V 和 OpenWrt 搭建软路由

笔记本上虚拟机越开越多,于是怎么给它们划分网络就是一个问题,例如代理软件什么的。

因为咱不想每一个虚拟机上就单独设置一遍,就想起来了咱给咱手上不支持代理设置的设备 (例如游戏主机什么的)连的那个运行有代理软件的路由器了。然后发现已经有人尝试过了,那就开工咯。

介于随便汝等说咱是更想授之以渔还是就是在偷懒都好的想法,咱不会给出事无巨细的操作方法。 当然如果汝也想跟着做的话,遇到问题时也可以来问咱(或者上网搜索一番)。

以及基于 Hyper-V 的特殊性,启用完以后可能会导致老的虚拟机软件无法启动。 嘛最新的 VMware Workstation 和 VirtualBox 应该是已经支持了的。

准备工作 - Hyper-V 管理器、映像和创建虚拟机

要使用 Hyper-V 的话,汝得有一个 Windows 8.1/10 专业版或者企业版, 或者 Windows Server 2012 以后的 Windows Server 才行。同时也要求汝的 CPU 支持必要的虚拟化技术。 (Intel VT-x 或者 AMD-V 什么的)可以通过 PowerShell 里的 systeminfo 命令确定是否可以开启 Hyper-V 。

在满足了前置要求之后,可以这样启用 Hyper-V:

# 如果提示找不到命令的话,换以管理员身份运行的 PowerShell 窗口。
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All

或者用“启用或关闭 Windows 功能”也是可以的。

下载和转换 OpenWrt 映像

接下来到 OpenWrt 的网站上下载映像 (这时候最新的版本是 19.07,于是链接就是 https://downloads.openwrt.org/releases/19.07.7/targets/x86/64/

OpenWrt 下载页面的样子,这里咱们选择包含有内核和 rootfs 的 combined 映像。

这里咱们选择包含有内核和 rootfs 的 combined 那俩, 区别在于 squashfs 那个安装好以后系统分区像在普通路由器一样是只读的,可以实现像是升级或者复位等功能。 不过咱大概会用检查点来实现这些,那就直接用 ext4 的好了。下载和解压以后汝大概就可以得到一个像是 openwrt-19.07.7-x86-64-combined-ext4.img 这样的文件了。

但是 Hyper-V 只能用微软自己的 VHD 或者 VHDX 格式,所以还需要转换一下。 转换的方法有很多,咱这里给出咱的一个例子。

创建一个虚拟磁盘。这里用了 diskpart 命令。如果汝更偏好用磁盘管理操作的话, 可以参考 这篇文章

Microsoft DiskPart version 10.0.21332.1000
Copyright (C) Microsoft Corporation.
On computer: DESKTOP-H6MANBV

# 创建一个虚拟磁盘文件
# create vdisk file=<汝要存放虚拟磁盘文件的位置> maximum=<它的最大大小,以 MiB 为单位> type=expandable
# 假如汝有充足的硬盘空间的话,可以把上面的 type=expandable 改为 type=fixed 创建一个固定大小的虚拟磁盘。
# 可以提高一些虚拟磁盘的性能。当然动态扩展对 OpenWrt 来说也够用就是了……
DISKPART> create vdisk file=c:\test.vhd maximum=8192 type=expandable
    100 percent completed
DiskPart successfully created the virtual disk file.

# 选择刚才创建的虚拟磁盘
# select vdisk file=<汝要存放虚拟磁盘文件的位置>
DISKPART> select vdisk file=c:\test.vhd
DiskPart successfully selected the virtual disk file.

# 挂载刚才选择的虚拟磁盘。
DISKPART> attach vdisk
    100 percent completed
DiskPart successfully attached the virtual disk file.

# 初始化虚拟磁盘为 MBR 分区表。
# OpenWrt 官方编译的版本不支持 UEFI 启动,所以就用 MBR 了。
DISKPART> convert mbr
DiskPart successfully converted the selected disk to MBR format.

# 在下一步操作完成之后卸载虚拟磁盘
DISKPART> detach vdisk
DiskPart successfully detached the virtual disk file.

接下来把这个映像写入虚拟磁盘就 OK 啦。(以及咱发现 Rufus 能把虚拟磁盘识别出来)

Rufus 能给虚拟磁盘写入 RAW 映像。

创建虚拟交换机和虚拟机

有点像其它 Hypervisor 里的虚拟网络啦,虚拟交换机类型里的外部网络就相当于桥接, 内部就相当于仅主机网络。于是这里创建一个外部和一个内部交换机。

Hyper-V 里的虚拟交换机

留意外部交换机这里的“允许管理操作系统共享此网络适配器”这个选项,把这个选项取消选择以后, 这块网卡就只能由虚拟机访问了。等全部设置妥当以后,汝就可以把这个选项关掉让主机从虚拟机联网了。

Hyper-V 里的虚拟机代数,第一代是传统的 BIOS 引导

创建虚拟机的时候,因为刚才提起过的 OpenWrt 官方编译的版本不支持 UEFI 启动的问题,所以就用第一代了。

为虚拟机连接新的虚拟交换机

以及新建虚拟机的时候只能添加一个网络适配器,那这里就先把上面新建的内部交换机加上好了。

在虚拟机设置为虚拟机连接新的虚拟交换机

创建完成以后可以在虚拟机设置里添加新的网络适配器,虚拟硬盘就是刚刚转换好的磁盘映像(所以汝刚才卸载了没有?)。

在连接和启动虚拟机之后,汝大概就可以看到这样的界面:

OpenWrt x86 首次启动的界面

所以当然是先用 passwd 命令设置 root 用户的密码啦。

查看和调整网卡设定

如果汝像咱刚才一样先添加的内部交换机, ip link 命令应该可以看到虚拟机从上游获得了 IP 地址,也可以正常的联网。

(为了节省空间,这里省略了获得的和本地 IPv6 地址)

root@OpenWrt:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    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 mq master br-lan state UP qlen 1000
    link/ether 00:15:5d:0a:a9:01 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
    link/ether 00:15:5d:0a:a9:02 brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.147/24 brd 192.168.10.255 scope global eth1
        valid_lft forever preferred_lft forever
4: br-lan: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 00:15:5d:0a:a9:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.1/24 brd 192.168.10.255 scope global br-lan
        valid_lft forever preferred_lft forever

当然如果没有成功也别担心,记下界面名称(eth0 这样的)下面 link/ether 后面的文字, 这就是汝这个虚拟网卡的 MAC 地址啦。

在网络适配器的高级功能选项里查看虚拟网卡的 MAC 地址

然后在虚拟机设置里找到对应的适配器,高级设置里可以看到这个适配器的 MAC 地址。

于是在咱这里,要连到上游的接口就是 eth1 了。接下来用 vim 打开 /etc/config/network 文件。

它默认的里面只有 vim 让咱也很难办啊,如果汝对 VIM 的操作不是很熟练的话,记得准备一个操作指南在手边。
config interface 'loopback'
        option ifname 'lo'
        option proto 'static'
        option ipaddr '127.0.0.1'
        option netmask '255.0.0.0'

config globals 'globals'
        option ula_prefix 'fddb:38c5:4b0a::/48'

config interface 'lan'
        option type 'bridge'
        # 把这里的 eth0 换成汝内部交换价连接的适配器上的接口
        option ifname 'eth0'
        option proto 'static'
        # 默认是 192.168.1.1,如果担心和自己网络上已有的端冲突的话也可以修改一下。
        option ipaddr '192.168.50.1'
        option netmask '255.255.255.0'
        option ip6assign '60'

config interface 'wan'
        # 把这里的 eth1 换成汝外部交换价连接的适配器上的接口
        option ifname 'eth1'
        option proto 'dhcp'

# 如果汝的上游网络支持 IPv6,那可以添上这一段试试?
config interface 'wan6'
        option ifname 'eth1'
        option proto 'dhcpv6'
        option reqaddress 'try'
        option reqprefix 'auto'

保存完成以后可以重启网络服务,或者直接重启系统也行。一切顺利的话, 主机上的虚拟交换机应该能正确的获得 IP 地址:

Ethernet adapter vEthernet (Internal):

    Connection-specific DNS Suffix  . : lan
    IPv6 Address. . . . . . . . . . . : fd51:c6aa:1df:0:99e6:8496:55c7:733e
    IPv6 Address. . . . . . . . . . . : fddb:38c5:4b0a::a32
    IPv6 Address. . . . . . . . . . . : fddb:38c5:4b0a:0:99e6:8496:55c7:733e
    Temporary IPv6 Address. . . . . . : fd51:c6aa:1df:0:b04a:f9d5:61bd:63e4
    Temporary IPv6 Address. . . . . . : fddb:38c5:4b0a:0:8d12:c734:e2f0:8ec8
    Link-local IPv6 Address . . . . . : fe80::99e6:8496:55c7:733e%26
    IPv4 Address. . . . . . . . . . . : 192.168.50.145
    Subnet Mask . . . . . . . . . . . : 255.255.255.0
    Default Gateway . . . . . . . . . : fe80::215:5dff:fe0a:a901%26
                                        fe80::215:5dff:fe0a:a907%26
                                        192.168.50.1

接下来就像使用一个普通的有 OpenWrt 的路由器一样设置它吧,像是装上 luci 和代理软件什么的。

在所有的设置都妥当以后,除了上面提到的允许管理操作系统共享此网络适配器那个选项以外, 汝还可以考虑设置虚拟机开机启动。

(在“管理-自动启动操作”那里,可以设置成“总是”来让虚拟机在主机启动时自动启动。)

by ホロ at March 14, 2021 04:00 PM

March 10, 2021

ヨイツの賢狼ホロ

咱和()的 2020 年?

就不要 不合时宜的 问为啥隔了接近三个月才写了…… (?)

以及第四年的总结 在咱的 Matters 上。

为啥这一年只写了八篇文章?

啊这……咱写字的地方越来越分散了啊……

Google Search Console

Google Search Console 的表现,是怎么样呢?

这一年……?

因为众所周知的原因(COVID-19 啦……),于是几乎都是呆在家里, 然后被批判一番不务正业…… 那就记一下这一年玩过的游戏好了。

  • DJMax Respect V ,除了正式发售时突然降价引起抢先体验玩家不满和全程联网外加笨蛋反作弊插件以外, 只是 DJMax 这个老字号就能让咱接受前面的大部分缺点了。
  • 女神异闻录5 皇家版,给女神异闻录系列出加强版是 ATLUS 老传统了。
  • 集合啦!动物森友会,成功登上咱 Switch 游戏时间排行第一。
  • Project DIVA MEGA39s,咱为什么要买它…… FTDX 加上 DLC 它不香么……
  • Fluffy Store 和 ATRI -My Dear Moments- ,两部被推荐来看后觉得不错的视觉小说。(所以 fault - SILENCE THE PEDANT 还是没有出)
  • Untitled Goose Game,其实 2019 年在 PS4 上玩过了,今年借着在 Steam 上发售又玩了一遍。 (然而并没有人一起玩更新的双人模式……)
  • 妄想破绽和原神……一言难尽*2。
  • Halo: The Master Chief Collection,用手柄坑完了除了 ODST 以外的正常难度剧情,嗯…… 至少咱的老笔记本还跑得动。
  • 女神异闻录5 乱战 魅影攻手,第一次录下了全程 然后给友人做素材工具人(?)
  • Cyberpunk 2077,除了分不清是 Bug 还是剧情演出以外还好啦。 这话到底是赞美还是批评……
  • Spice & Wolf VR2, “什麼?你說今天12/10還有2077? 2077能有赫蘿嗎?” 新的动画鉴赏模式虽然短了点……
  • Stellaris, P社玩家没有一个无辜的……

当然除了这些以外还有些一直在玩的老游戏,也就是那些音游什么的。PSN 给咱发年度报告的时候去年游玩时间最长的 游戏还是 FTDX ……


剩下的一时想不起来了,就先这样吧。

by ホロ at March 10, 2021 04:00 PM

February 28, 2021

ヨイツの賢狼ホロ

Arch Linux 上的 GLNMP

前言

请允许咱引用一下 lilydjwg 的一段话:

我之所以现在记录这事儿,「现在」的原因是,我要在另一个系统上再测试一遍再发布出来, 「记录」的原因是,下一次我就不用想要执行哪些命令了。

所以这篇文章更多是留给咱自己看的,绝对不可能适合所有人(例如万一有人是为了塞给 V2Ray 一类的代理软件用什么的)。

记得 Read the Fine Manual 是汝等的好伙伴就好啦。

那为啥要叫 GLNMP ?

如果汝会这么问的话,那应该是有听说过 LAMP/LNMP 系统吧,大概就是 Linux,Apache(或者 Nginx),MySQL(现在可能更多在用 MariaDB) 和 PHP(还有可能是 Python 甚至 Perl?)的组合。因为除了此上下文环境中的“Linux”实际上指的是 GNU/Linux 操作系统。 所以咱就这么叫了。


接下来就从一个刚装好的 Arch Linux 开始了。

Nginx (先运行起来)

https://wiki.archlinux.org/index.php/Nginx

Nginx 官方有两个分支, mainline 和 stable。 看名字应该就能知道哪个更新哪个更稳定了。

安装好和启动服务之后,就可以打开 http://localhost 看看默认的主页了(就那个 Welcome to Nginx 啥的), 当然现在这些肯定不够,所以先把剩下的装上再回来设置了。

MariaDB

https://wiki.archlinux.org/index.php/MariaDB

在 MySQL 被 Orcale 收购之后,一些对 MySQL 的未来表示担心的人发起了 MariaDB 这个项目。到现在越来越多的 WebApp 和 GNU/Linux 发行版都开始(或已经)用 MariaDB 代替 MySQL 了。

在安装完 mariadb 以后记得初始化一下:

## 安装 MariaDB 的基础数据
# mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql
## 启动服务,进行第一次启动的设置,除了设置密码那一步的话基本上都能保持默认。
## 像是禁用 root 用户的远程登录,移除默认的 annoymous 用户和移除测试用的数据库和权限啥的。
# systemctl start mariadb
# mysql_secure_installation
# systemctl restart mariadb

如果有需要的话,可以在 /etc/my.cnf.d/ 里修改适合自己的配置,例如:

# 让 mysqld 只使用本地 Unix Socket 连接。
[mysqld]
skip-networking

# 默认使用 utf8mb4 编码。
[client]
default-character-set = utf8mb4

[mysqld]
collation_server = utf8mb4_unicode_ci
character_set_server = utf8mb4

[mysql]
default-character-set = utf8mb4

调整好设置以后,可以用 mysql 命令来测试连接:

# mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.5.9-MariaDB Arch Linux

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]>

例如创建一个数据库和用户,然后为刚创建的用户分配刚创建的数据库的所有权限:

MariaDB> CREATE DATABASE mydb;
MariaDB> CREATE USER 'user'@'localhost' IDENTIFIED BY 'some_pass';
MariaDB> GRANT ALL PRIVILEGES ON mydb.* TO 'user'@'localhost';
MariaDB> FLUSH PRIVILEGES;
MariaDB> quit

PHP

https://wiki.archlinux.org/index.php/PHP

Arch 习惯是只留最新的版本的,除非官方仓库中别的包在过渡的时候。虽然其它版本也能从 AUR 里装, 但是没有保证就是了……

以及有一些扩展也在仓库里,看汝使用的应用有什么要求就装上吧。

然后就是修改 PHP 的配置文件 (/etc/php/php.ini) 和 PHP-FPM 的配置文件 (/etc/php-fpm.d/) 了,基本上就是修改下上传文件的大小,启用扩展和其它的别的。

Let's Encrypt

这都 1021 年了还需要人提醒给自己的网站上 TLS ?

咱是参考 通过 Cloudflare DNS 验证来申请 Let's Encrypt 证书 这篇文章用 Certbot 和 Cloudflare 来验证域名的,汝可能喜欢用像是 acme.sh 之类的别的方法的话,那照着 自己的喜好来的话也没有关系。

组合配置

DigitalOcean 有个 Nginx 配置文件生成器说不定有用: https://nginxconfig.io/

比较常见的组织 Nginx 配置文件的方法就是用不同的配置文件区分不同的网站,像 /etc/nginx/sites-available 和 /etc/nginx/sites-enabled 这样的。

咱的话,习惯把配置文件中 PHP 和 TLS 的配置再拆出来共用的样子。

PHP 的配置大概像这样:

# /etc/nginx/include/php.conf
location ~ \.php$ {
    # 404
    try_files $fastcgi_script_name =404;

    # default fastcgi_params
    include fastcgi_params;

    # fastcgi settings
    fastcgi_pass                    unix:/run/php-fpm/php-fpm.sock;
    fastcgi_index                   index.php;
    fastcgi_buffers                 8 16k;
    fastcgi_buffer_size             32k;

    # fastcgi params
    fastcgi_param DOCUMENT_ROOT     $realpath_root;
    fastcgi_param SCRIPT_FILENAME   $realpath_root$fastcgi_script_name;
    #fastcgi_param PHP_ADMIN_VALUE  "open_basedir=$base/:/usr/lib/php/:/tmp/";
}

TLS 的配置大概像这样,推荐参考 MoillaWiki:Server Side TLS ,Mozilla 也有提供一个配置文件生成器的样子,咱目前使用的配置有些差别。

# /etc/nginx/include/ssl.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:TLS-AES-128-GCM-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers on;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_timeout  10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options sameorigin;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "no-referrer";
server_tokens off;
#add_header Content-Security-Policy "default-src https: ; object-src 'none'";
#add_header X-Robots-Tag "none" ;
#add_header X-Download-Options "noopen";
#add_header X-Permitted-Cross-Domain-Policies "none";

组合在一起的其中一个例子:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name $_server;
    include /etc/nginx/include/ssl.conf;
    ssl_certificate /etc/letsencrypt/live/$_server/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/$_server/privkey.pem;

    root /home/horo/www/$_server;
}

在大部分都安排妥当以后,就可以设置让必要的服务在开机时自动启动啦。


差点就忘了 reStructuredText 怎么写…… ,虽然 Pelican 是有 markdown 支持的。

by ホロ at February 28, 2021 04:00 PM

February 22, 2021

中文社区新闻

mkinitcpio 将迁移到默认使用 Zstandard 压缩镜像

随着 linux-lts 升级到了 5.10 版本,现在 Arch Linux 的所有官方内核都支持了 zstd 压缩的 initramfs 镜像,所以 mkinitcpio 从版本 30 起将默认使用 zstd 压缩镜像,现在这个版本已经在 [testing] 仓库中。

如果,因为任何理由,你仍在使用 5.9 之前的内核版本,请确保修改 mkinitcpio.conf 中的 COMPRESSION 使用某个受内核支持的压缩方式,比如 gzip ,否则将 不能 引导进 mkinitcpio 创建的新版镜像中。

by farseerfc at February 22, 2021 06:03 AM

February 19, 2021

Justin Wong

Fit an Overfit with MegEngine

本文为MegEngine系列开篇,难得有机会写一些能公开发出来,又和自己平时工作高度相关的内容,期望下次更新不要再鸽一年了。

作为开篇,我会写一点点深度学习炼丹的基本概念,然后讲一个有意思的小实验: 如何用 MegEngine 拟合一个 “OVERFIT” 出来。

February 19, 2021 12:00 AM

February 18, 2021

Alynx Zhou

运行在 JACK 上层的 PulseAudio

很多朋友都知道我除了是个程序员以外还是个乐器玩家,很久之前因为想要录音上网了解了一下需要购买专门的麦克风声卡从此掉进深坑一发不可收拾。当然 Linux 用户在购买硬件之前需要做好功课,大概 16 年左右我还在上高中的时候用我还凑合的英语水平翻了几个 Linux 音乐论坛最后决定买一台 Focusrite Scarlett 2i4(不过它后来似乎更新了几个版本所以我购买的变成了 1st Gen),我不太清楚除了更换了接口之后还有什么奇怪的改动没有,所以这里就不盲目推荐大家买更新的型号了,反正声卡这玩意够用的话也不太需要追新。

做功课的时候还了解到常见的 Linux 桌面采用的都是 PulseAudio,但专业录音为了追求更低的延迟所以都使用 JACK,于是简单学习了一下,发现只要打开 Qjackctl 选择设备然后启动,Audacity 就可以选择使用 JACK 设备了。系统的其它软件仍然通过 PulseAudio 输出到板载声卡,不会冲突,基本满足我的需求也就没研究过其它的。

但是最近直播的时候总有人说我的耳机麦克风不太灵敏,我想了想不如干脆把录音用的声卡和话筒利用上,整个系统直接采用 2i4 作为默认声卡?但是虽然 PulseAudio 可以直接控制 2i4,软件的兼容性却不太好,比如 Audacity 启动的时候即使没有运行 JACK 它似乎也会尝试通过 JACK 连接 2i4 于是导致缓慢的启动和几下破音。于是只能采用网上常见的方案也就是把 PulseAudio 的音频输出到 JACK,但我觉得其实这样不太准确因为音频是既有输出又有输入的,所以我的标题是把 PulseAudio 运行在 JACK 上层。当然还有一种方案是采用 PipeWire,打算自己替代 PulseAudio 和 JACK 一桶浆糊的新项目,它欺骗程序让它们以为它就是 JACK 和 PulseAudio,但虽然我是个 GNOME Dev,我对这个不太感冒。有个常见的笑话是“现在有 N 个不那么好用的系统了,我们写个新的把它们全部替代掉!然后现在有 N + 1 个不那么好用的系统了!”。JACK 对于专业用户来说很好用,而专业用户通常是不太喜欢变化的,所以我不太期待 PipeWire 替代 JACK。

有人说 Linux 的音频系统比意大利面条还复杂,这倒不能说错,因为假如你看维基百科上那张巨恐怖的图的话确实是这样。但本质上来说音频不过是把信号丢给声卡,所以只要是个能写声卡的软件都可以叫音频系统嘛,也就不奇怪那张图那么复杂,实际上在现代的 Linux 桌面通常都集成 PulseAudio,我们也没必要去研究那些边边角角的奇怪方案,于是整个结构其实很清晰,一般的用户看到的都是这样的:

typical-desktop

然后作为对比,我之前的方式是这样的:

mine-old

而这篇文章要达到的目的则是下图这样:

mine-new

当然实际上假如你理解了这个结构的话,其实也没必要只用一个声卡,完全可以用独立声卡录音用板载声卡输出,只是 JACK 是绑定独立声卡的,于是就像下图这样:

some-interesting-things

总之这样做的依据在于 JACK 被我们绑定了独立声卡,然后利用 PulseAudio 设置不同的输入输出设备控制基于 PulseAudio 的桌面程序的输入输出,同时它还可以把自己作为 JACK 的客户端。而具体到与声卡交互,则全部都是内核里面的 ALSA 组件控制的。这里没有涉及到使用 ALSA 用户态组件的客户端程序,因为 PulseAudio 会把自己伪装成 ALSA 的客户态组件,于是这些老旧的程序就被连接到了 PulseAudio 上面从而无法直接占据声卡了。

了解原理之后就可以具体操作,首先需要安装 jack2,这个软件包包含的是 JACK 的组件,必须要装 jack2 因为 jack1 不支持 DBus 所以也就没办法和桌面交互了。安装 qjackctl 来控制 JACK,然后安装 pulseaudio-jack,这是让 PulseAudio 作为 JACK 客户端的兼容层。

然后需要启动 Qjackctl,在 Setup 的 Settings 选项卡里面选择 Advanced,将 Input Device 和 Output Device 全都设置为 2i4(应该就是 hw:USB 那一项),然后切换到 Misc 选项卡,像下图那样设置 Others 部分(基本就是除了 Keep child windows always on top 的全都勾上),这样就可以做到毫无感觉的启动 JACK(只要打开 Qjackctl 它就在后台启动 JACK,退出 Qjackctl 也只是退到后台)。

misc-tab

这样再次启动 Qjackctl,应该 JACK 就已经在控制 2i4 了,PulseAudio 应该会自动把 2i4 的控制权交给 JACK,省了不少事情。对于一些原生支持 JACK 的客户端程序来说这已经足够了,比如 Ardour 或者 Audacity,它们不经过 PulseAudio,直接连接到 JACK。接下来需要调整的是那些基于 PulseAudio 的客户端程序。

然后打开你桌面环境的音频控制器,比如我是 GNOME 就是 GNOME Control Center 里面的 Sound 选项卡,这里基本都是集成的 PulseAudio 控制,把 Input Device 设置成 Jack Source,于是 2i4 上的麦克风的录音便通过 JACK 传到 PulseAudio 再传到 PulseAudio 的客户端程序比如 OBS Studio 和 Firefox。然后如果你想让 PulseAudio 的客户端程序把音频输出到 2i4 上的耳机里面,那就将 Output Device 设置为 Jack sink,这样其实 PulseAudio 就是运行在 JACK 上层。

最后你需要设置 JACK 在登录时启动,这样 PulseAudio 才能找到 JACK,这个很简单,因为我们已经设置 Qjackctl 无感启动 JACK 了,那只要将 Qjackctl 设置为自动启动即可,比如 GNOME 用户可以在 GNOME Tweaks 里面设置。

当然,一般的家庭录音用户都是单声道麦克风,某些客户端程序需要自己手动设置,比如 OBS Studio 需要在 Advanced Sound Properties 里面勾选 Mono。

如果你遇到了一些奇怪的明明已经设置 PulseAudio 重定向到 JACK 却没有声音的情况,可能是因为你某些软件或者插件带了奇怪的设置,建议先重置它们试试。

Alynx Zhou

A Coder & Dreamer

by Alynx Zhou (alynx.zhou@gmail.com) at February 18, 2021 02:51 AM

February 05, 2021

中文社区新闻

从三月初起 Chromium 将失去同步支持

Google 已经发出通知说他们将从3月15日起禁止除了 Chrome 以外的所有浏览器访问 Google 的一些功能(比如 Google sync)。这一来自 Google 的变化将较早影响 Arch 的 chromium 包,从3月2日起,预计会在 Chromium 89 发布之后。

我们已经确定 数据同步(data sync) 会停止工作(密码、书签、等)。其它特性比如定位(geolocation)或者增强的拼写检查(enhanced spell check)可能可以继续工作一段时间。访问 Google Drive 的浏览器扩展也可能受影响,以及 LibreOffice 可能将失去存储文档到那儿(Google Drive)的能力。

其他发行版比如 openSUSE 和 Fedora 已经在他们的 Chromium 88 软件包中移除了将要被限制功能的 API key 。Fedora 的升级建议中详细描述了关于这个变化的观点,我还发现 Hackaday 的 这篇文章也可供参考。

by farseerfc at February 05, 2021 12:29 AM

February 03, 2021

Phoenix Nemo

在 Linux 系统中升级超微 BIOS 固件

最近突然有客户找来说 BIOS 进不去了呀…看截图 stuck 在 POST Code AB 大概就知道什么情况了。

这不就是经典的 y2k bug 再现嘛…

一般情况下,升级超微 BIOS 固件的推荐方式是制作 DOS 启动盘引导系统升级,不过现在 BIOS 进不去也没有物理 access 就只好在 Linux 里操作啦。

获取设备信息

简单命令 lshw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
~> lshw | head -n 25
localhost
description: System
product: X9SCL/X9SCM (To be filled by O.E.M.)
vendor: Supermicro
version: 0123456789
serial: 0123456789
width: 64 bits
capabilities: smbios-2.7 dmi-2.7 smp vsyscall32
configuration: boot=normal chassis=desktop family=To be filled by O.E.M. sku=To be filled by O.E.M. uuid=[redacted]
*-core
description: Motherboard
product: X9SCL/X9SCM
vendor: Supermicro
physical id: 0
version: 1.11A
serial: [redacted]
slot: To be filled by O.E.M.
*-firmware
description: BIOS
vendor: American Megatrends Inc.
physical id: 0
version: 1.1a
date: 09/28/2011
size: 64KiB
capacity: 8128KiB

X9 主板不是 2015 年就 EOL 了吗这可真是够老了…

总之主板型号是 X9SCM 于是在超微找到了新版的 BIOS 固件。下载到系统中解压得到一堆文件,其中 X9SCM1.106 这个文件就是需要的 BIOS 固件本体。

编译 SUM 内核模块

Supermicro Update Manager (SUM) 是用于在系统中控制 BIOS/BMC 的程序。首先下载并解压,得到 sum 二进制文件和一堆其他东西。在 driver 目录中发现了对应发行版预编译的内核模块,但是直接 insmod sum_bios.ko 出错,好在它也提供了源码,那么就直接编译吧。

首先安装对应内核的 Linux 头文件,搜索 linux-headers 一般都可以找到。

1
2
~> uname -a
Linux localhost 4.19.0-13-amd64 #1 SMP Debian 4.19.160-2 (2020-11-28) x86_64 GNU/Linux

然后

1
~> apt install linux-headers-4.19.0-13-amd64 build-essential # 还需要 make 和 gcc

进入 driver/Source/Linux 执行 make,如果成功编译,则 insmod ./sum_bios.ko

嗯,这次没有报错了。

升级 BIOS

升级命令和其他主板商以及 RH 系的包命名风格一样喜欢大小写混合 = =…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~> ./sum -c UpdateBios --file ../X9SCM1.106/X9SCM1.106
Supermicro Update Manager (for UEFI BIOS) 2.5.1 (2020/11/12) (x86_64)
Copyright(C) 2013-2020 Super Micro Computer, Inc. All rights reserved.

WARNING: BIOS setting will be reset without option --preserve_setting
Reading BIOS flash ..................... (100%)
Writing BIOS flash ..................... (100%)
Verifying BIOS flash ................... (100%)
Checking ME Firmware ...
Putting ME data to BIOS ................ (100%)
Writing ME region in BIOS flash ...
- Update success for /FDT!!
- Updated Recovery Loader to OPRx
- Updated FPT, MFSB, FTPR and MFS
- ME Entire Image done
WARNING:Must power cycle or restart the system for the changes to take effect!

至此就基本完成啦。然后直接重启即可生效。

不过还是需要注意一下,升级固件都是有变砖风险的,某些情况下需要先联系硬件厂商支持确定升级路线。

以及新版 X10 开始升级 BIOS 需要激活 license 了… 嘛。听说超微的密钥早就被提取出来写了算号器了(什

最后的最后,提醒各位到 2038 年只有 17 年了哦(笑

最近突然有客户找来说 BIOS 进不去了呀…看截图 stuck 在 POST Code AB 大概就知道什么情况了。

这不就是经典的 y2k bug 再现嘛…

February 03, 2021 03:05 AM

January 29, 2021

中文社区新闻

开始提供 PHP 8.0 和 PHP7 旧包

php 包已经升级到8.0 版本。请参考上游提供的迁移指引。由于许多程序还尚未兼容 PHP 8 ,我们同时也提供了php7包,可以和第8版同时安装。依赖 PHP 的包已经根据这个更新做了相应调整,如果需要的话会依赖 php7 。您可能需要手动更新您的配置文件。 PHP7 的二进制和配置文件会有“7”的后缀:

  • /usr/bin/php -> /usr/bin/php7
  • /etc/php -> /etc/php7
  • /usr/bin/php-fpm -> /usr/bin/php-fpm7
  • /usr/lib/systemd/system/php-fpm.service -> /usr/lib/systemd/system/php-fpm7.service
  • /run/php-fpm -> /run/php-fpm7

我们也提供了一些为 PHP 7 打包的第三方模块:

注意对 php7 的支持非常有限,并且很可能在一年左右之后放弃对 php7 的支持,具体取决于大部分软件对 8 的兼容性。

by farseerfc at January 29, 2021 01:01 AM

January 15, 2021

Alynx Zhou

装机小记

由于笔记本散热和性能实在是不适合打游戏(有一说一,Optimus 双显卡还是没有直接单卡来得爽),所以很早就想组装一台台式机。特别是最近一直和高中同桌玩 Dota2,我的笔记本如果直播 Dota2,直播推流就会十分卡顿,而我又不想像 CSGO 一样降低画质玩。

作为计算机专业的学生,当然不打算直接淘宝整机啦。经过一番挑选和参考别人意见,最后成套配置是下面这样:

  • CPU:AMD Ryzen 7 5800X 3199(之前是 AMD Ryzen 5 3600X)
  • 主板:ROG STRIX X570-E GAMING 1999(特价)(之前是 微星 X570-A PRO 套装 2349)
  • 显卡:NVIDIA GeForce RTX 2070 SUPER 4300(之前是 七彩虹 RTX 2060 战斧 6G 2199)
  • 显卡支架:酷冷至尊 显卡支撑架 70
  • 内存:铂胜 C9BJZ 颗粒 白马甲 DDR4 3000 8G x4 1000
  • 网卡:Intel AX200(主板内置,之前是单独购买的花了 150)
  • 固态硬盘:三星 970 EVO Plus 512G 769
  • 固态硬盘:英睿达 MX500 2TB 1299
  • 机械硬盘:东芝 P300 2TB 400
  • 机械硬盘:西数 紫盘 2TB 400
  • 电源:海韵 CORE GX650 650W 金牌 549(特价)
  • CPU 散热:利民 AK120 145(之前是 超频三 东海x5 89)
  • M.2 散热:主板自带(之前是利民 M.2 硬盘散热片 39)
  • 机箱:Corsair 4000D 白色 629 (之前是 先马 鲁班1 209)
  • 机箱风扇:利民 TL-C12R-S x2 + 利民 TL-C12S 418(TL-C12R-S 只有 299 的三联包)(之前是 Arctic F12 PWM 温控 x3 120)

总计 15177。(有些具体价格我记不清了,而且京东价格总是波动。)性价比一般,因为我在其它人觉得没必要的地方分了很多预算比如主板风扇和硬盘,选 B450M 和西数的 NVMe 以及随便买个不带温控但是带灯的风扇的大有人在。

选这样一套配置其实有原因,可能很多人觉得为什么要给 R5 配 X570 这种高端主板,不如换成 B450 然后把预算加到别的上面。但按照我朋友的使用经验,4 代和以前的 AMD 芯片组存在 USB 兼容性问题,而且我装好机器后就在 Arch Linux CN 群里看到有人遇到这种问题,5 代似乎重新设计了 USB 控制器,目前一切正常。虽然这大概是最丐的 X570,连前面板 USB C 的接口都不支持,但是带 3900X 以下的 CPU 还是没什么压力的,而且我的机箱也没前面板 USB C。唯一的问题是微星不太厚道,今年 B550 出来之后又搞了个 X570 Tomahawk,大概只贵了几百块但是却有比肩旗舰的供电能力和前面板 USB C 支持,所以不再推荐购买 X570-A PRO 和 X570 Gaming Edge 这两个低端款了。有这种好东西为什么不一开始就吐出来???


更新(2020 年 11 月 16 日):AMD 发布了 Zen 3 系列的 CPU,游戏性能全面反超 Intel,对于我这种玩的都是吃 CPU 的游戏的用户来说实在是太香了,而且我一直想要 8 核心的 R7,于是买了首发的 R7-5800X,果然性能提升明显。然后正好有朋友的朋友打算装机买我换下来的 CPU 和主板,于是双十一为了 更好的 RGB 效果 更好的供电支持换了 ROG 的 X570-E(东哥疯狂耍猴,本来我是想 Tomahawk 的,但是微星的几款真香主板都无货,而且我对微星土里土气的设计也审美疲劳了,1999 的打人国度带眼睛它不香吗)。


如果仅仅是打游戏也可以把 R5-3600X + X570 换成 i5-9600KF + Z390,大概可以便宜一点然后升级一下显卡?我觉得差价是不够 2060 换 2060S 的。或者如果要求不高换成 R5-3600 也行。但 R5-3600(X) 是 12 线程而 i5-9600KF 只有 6 线程,所以我还是选择了 Ryzen,反正 AMD CPU 最近表现都很不错,没必要给 Intel 掏更多的钱。如果你在京东买,建议自己翻翻店铺,一般买主板 CPU 套装更便宜,但是它不会摆出来……

显卡一开始我是打算买个 1999 的 GTX 1660Ti 的,反正我玩的游戏也不需要光线追踪(难道真相不是本来就没几个游戏支持光线追踪嘛?),然而看到七彩虹最便宜的 2060 正在特价 2199,果断少买一条内存,加钱上这个。反正 1999 的 1660Ti 也是便宜货,而且看了一下视频似乎这块卡也不是丐中丐中丐,质量还可以(同价位索泰铭瑄也不能说是什么高端货吧?),反正内存可以再插新的,而显卡二手不值钱。什么你说 A 卡?虽然 A 卡对于 Linux 的驱动支持更好一点,但是 N 卡也不是不能用,而且我要开直播的话 NVENC 编码挺香的,而且对我的 Steam 库存来说 N 卡也更友好一点(但是骂老黄还是要骂的)。另外虽然我不做机器学习,但万一室友毕设需要的话,反正我上班也不用台式机,装个 CUDA 让他 SSH 上去用岂不是你好我好大家好。

内存其实没什么特别要求,不过由于 Ryzen 的设计,频率越高越好,但按照这一代的设计,最佳频率是 3600 左右,再高反而会下降,我是买不起那么奢侈的内存,甚至 3200 的都买不起,但这款 3000 的也不错,据说颗粒是镁光创下超频记录的那一批,所以就买了四条。我也不需要 RGB,这款的马甲还挺好看的。当然 3000 是 XMP 频率,需要在 BIOS 里打开 XMP,不然默认只有 2400。最近抄了个 3600 16-19-19-36 的作业,结果四条内存轻轻松松就上去了,2K 下面 PUBG 大概提升了 20 FPS,还是挺爽的。

硬盘不多说了,买 MX500 还是看中一个稳,我还是不放心买同价位国产白片……而且作为一个 2016 年就在笔记本上使用 NVMe SSD 的人,表示并没感觉出 NVME 和 SATA 有什么使用差别……我也不渲染视频,写写代码打打游戏都不卡。但是后来打脸了,公司发了新年福利于是还是上了个 NVMe 硬盘。顺便还配了个无线网卡用来接蓝牙和 WiFi,似乎 Intel AX200 是对 Linux 支持比较好的,就随便买了个 PCI-E 的插上了(御三家集齐了!)(ATX 的好处终于用上了,多出来的 PCI-E 随便插)。

电源有点买亏了,买完了发现长城同系列 550W 的电源当时也是 329,都怪京东迷一样的定价策略,好在我这一套功耗并不是很高,当然最后趁着福利 + 打折还是换成了海韵一元一瓦的金牌全模组,带我这套绰绰有余。开始我听说原装散热器也能压住 3600X,所以就没打算换,但后来发现刀法还是精准啊,3600X 带的散热器竟然不是铜芯的,再加上 Ryzen 三代的电压控制比较激进,待机温度有时候超过 50,打游戏时候机箱上方有点烤腿,所以还是换了个塔式散热器,风道科学了不少,温度控制也更好了。而且 AMD 原装风扇转速太高,3000rpm 的时候机箱都在震,换了之后安静了很多。顺便安原装散热器时候拆下来的螺丝卡扣不要扔,万一换塔式散热器,好多都是需要装在这个卡扣上的。

说到机箱和风扇我就一肚子气!本着对京东自营品质和速度的信赖,我全套都是在京东买的,结果拿到手全部安装上之后发现机箱开机跳线是短路的,插上电源就开机,开一会因为短路主板以为你在长按电源又关了!开始我还以为是主板坏了,结果发现螺丝刀手动碰一下开机跳线开关是可以正常开机的!于是又费了九牛二虎之力拆下来退货,主板散热器显卡都在盒子里椅子上放了两天,还好新机箱没问题,但是我买风扇的时候又给我发了个断轴的!东哥呀东哥,我拿你当兄弟,你拿我当代价?你是盯上我了?虽然我平时说你两句坏话但是对京东的服务还是好评的,但是经过这次之后我还是得重新考虑考虑了……顺便据说启航者 S5 这个机箱前面板音频口有的是 HD Audio 有的是老版 AC97,涉嫌虚假宣传,如果你遇到 AC97 的大概可以换货,麻烦一点,不过反正便宜货就这样子,我的抽奖抽到 HD Audio 了。但是这个机箱设计的很抠门,比如主板装上了就没法在上面走 CPU 电源线了,有两个走线口直接被 ATX 主板盖住,所以主板和显卡的电源线只能从一个口里挤出去,然后固态硬盘就在这个口下面所以也很难接线……而且机箱侧板是个黑色半透明的亚克力(那就别宣传透明啊!),金属外壳感觉也不是很厚实。所以最后还是换了先马的鲁班 1,各种设计都宽松了许多,装起来也很好看。

其他的外设我自己都有,显示器 就是之前买的优派 XG2402,1080p@144Hz 并且自带扬声器 换成了 DELL S2721DGF,27 寸的 2K@165Hz 屏幕,除了通病漏光以外都还不错,音箱外接了一个 JBL 的蜗牛一代(因为我觉得二代没有一代好看),鼠标就卓威 EC1-B CSGO 特别版(这个版本已经停产了,而且有偶尔指天/指地的 bug,建议买新的 EC1/2 或者 DIVINA 版本),键盘则是前段时间买的 ikbc C87 红轴,便宜还好用。

顺便由于我手残以及力气小和室友跃跃欲试,很多东西都是他装的,非常感谢。话说回来装这东西还真是个力气活,毕竟接口都有防呆设计仔细看看不会装错,但是真的很紧很难拔……非常担心把主板搞坏了。


更新:

装好的完全体照片


更新:避免你们说我灵魂走线,重新整理了一下,线太硬了。

重新走线正面

重新走线背面


更新:内存插满。

内存插满


更新:NVMe + 蓝牙无线网卡的完全体。

完全体

这个机箱 CPU 线走上面是要把主板拿下来才能穿过去的,而且右边两个有硅胶垫的孔 ATX 完全不能用,只能用一个孔,而且不能把线固定在机箱中间,很难盖上。


再更新:新机箱比原来的好看多了也宽敞多了。

正面

背面

公司的蜥蜴(明明是变色龙!)玩偶太高了,显卡下面放不开,挂着我又不放心,盖盖子之前还是拿出来了。


更新:换上了公版 2070 SUPER 和利民 AK120。

显卡

全景


更新(2020 年 10 月 12 日):冬天到了,还是换了几个支持 ARGB 的风扇,我个人不喜欢蓝光紫光夜店土嗨风,所以就弄了点温暖的颜色假装是个电暖气。前面是利民 TL-C12R-S,后面是利民 TL-C12S,虽然是三联包,但是似乎螺丝有问题,有一个风扇螺纹被拧花了,而且后面的风扇竟然少一个角上的橡胶减震垫,于是我就没装拧花的那个,并且把它的减震垫安装到后面了。反正考虑到我最下面是机械硬盘,装上这个风扇风道也不畅通,而且那里并没有什么需要散热的设备,电源风道是独立的。每把风扇两根线,ARGB 线要串联,PWM 线要一分三,而且有前有后,风扇线还有编织保护套,还要防止线材打到风扇扇叶。理线花了好久,最后 把他们用扎带固定到下面和前面空的风扇挂架上 还是走背线了,刚好够长。而且我还有三个 SATA 硬盘打算换一下 SATA 线……我真的想不出来那些水冷排且上下左右全都是 ARGB 风扇的人怎么理线的。

正面看灯光

侧面看灯光

风扇线不在下面啦

最终的背线效果

我不会告诉你其实我是想调出我 TB 至宝的颜色:

TB 至宝

一开始我以为需要用 OpenRGB 这个项目才能在 Linux 下控制颜色,但是这种 ARGB 风扇好像有存储机制,会自动记住上次的设置。于是就在 Windows 下面安装了一个有一大堆乱七八糟组件和功能的 MSI Dragon Center,其实我只需要调成长亮,然后重启进 Linux 颜色就一直是我设置的,然后我想关掉 Dragon Center 的自启动因为反正也用不到,但是微星的软件自己拉跨,重启进 Linux 灯光不变,重启进 Windows 又变呼吸彩虹灯光了。最后发现 Dragon Center 里面有一个类似“覆盖第三方RGB软件”的选项……好像他把他自己上次存储的结果也当第三方软件了,关掉就好了。原理我猜因为 Dragon Center 只是个 Client,真正控制颜色的是他某个 SDK 里面的 Daemon,这个选项的意思其实就是 Daemon 每次启动都按照 Dragon Center 设置的颜色重新设置风扇就实现覆盖功能了……但是 Dragon Center 的启动被我关了所以就默认了,不管了,统统关掉就 OK。


更新(2020 年 11 月 16 日):有了打人国度的眼睛感觉瞬间高贵了起来(大误)。

老黄卡只有绿色一种颜色,于是只能整个机箱调个老黄绿来配合。

老黄绿了

但是我自己平时还是喜欢红色的。

平时灯光

我发现主板的装饰灯正好在公版 SUPER 卡上产生倒影,很有意思。

倒影

这个主板可以完美使用 OpenRGB,在 AUR 里安装之后把 /usr/lib/udev/rules.d/60-openrgb.rules 复制到 /etc/udev/rules.d 重启即可支持,不过对于 Addressable Header 上的设备也就是风扇或者灯条需要手动 resize zone 也就是填写灯珠个数,我用的利民 TL-C12S 系列经过尝试是 8 个灯珠。


更新(2020 年 12 月 1 日):我之前有考虑过换个机箱,倒不是先马鲁班不符合我的需求了,而是一些细节问题,比如我这个玻璃侧板不那么平,有一个角翘起来一点。然后我的前面板角落里虽然我十分注意还是被我磕了一下。以及当初从宿舍搬出来的时候没有把机箱包装拿出来,以后搬家可能不方便……我一开始本来打算再买一个同款然后把侧板和前面板换了得了,然后那天发现前面 USB 2.0 接口接声卡会破音……于是打算换一个别的机箱。

我个人其实比较喜欢白色的机箱,于是初步考察了一下,我很喜欢NZXT H1,但我又不可能装 ITX,然后 NZXT H710 看起来不错,我觉得很漂亮,而且我在 NGA 上看到过有人给它纯白色的前面板贴了个黑色的两仪式剪影贴纸很帅气,但是太大太贵了。为什么没选择 H510 呢,因为我有几个硬性需求,我日常使用总结出来的,是一般机箱评测和用户不会注意到的,比如最好不要显卡竖装(我个人肯定是不会竖装的),PCIE的螺丝锁孔也不要做成从机箱尾部凸出来的,其实这里的实际意思是机箱不要有太大的空洞,很多机箱的显卡竖装PCIE螺丝孔上面就那么开着口,凸出来的横着的PCIE螺丝孔虽然有一个覆盖物,但它总是不那么好用,于是这样排除掉了 NZXT H510, NZXT H510i 和 NZXT H710i。

然后我也不想要钢化玻璃打孔固定的,这个大部分人应该也都不想要。我个人而言还不想要附带一个只有某些软件才能控制的控制器的机箱(点名 NZXT CAM),虽然 NZXT 的水冷确实非常漂亮,但我不放心把电脑这些基础的硬件交给一个需要联网还经常不一定能不能连得上而且只有进了 Windows 才能启动的废物控制软件——我是 Linux 用户。同理我也讨厌雷蛇和罗技难用的鼠标驱动程序。明明我可以用 BIOS 控制风扇/水泵转速,用主板的 ARGB 插针控制风扇,为什么非要用那么难用的软件?所以肯定只考虑 H710 不考虑 H710i。

如果光是这些可能我就下单 H710 了,虽然贵一点,但是能满足需求也就不在乎了。可是我翻了翻各种帖子都提到了这个机箱的一个缺点:它的硬盘架需要用螺丝刀拧螺丝才能拆除。虽然现在两百块钱的机箱都能免工具调整硬盘仓了但我觉得理完线也没什么人总动机械硬盘所以这不是个问题,但是更严重的是许多人都说固定硬盘仓的螺丝非常难拧,甚至螺丝都花了也拧不下来。作为之前被笔记本内部拧花了的螺丝支配的恐惧的人,我可不要买个这样的样子货,于是只好放弃 NZXT。可能它的设计师平时是不用机械硬盘没有软件更新服务器连不上的问题也不在乎机箱开一大堆没有保护网的口的人吧!

然后我又开始研究买什么,看了一下联力的鬼斧似乎很不错,虽然造型上比起 NZXT 要难看很多,但是和其它的机箱比起来也算是鹤立鸡群了。而且相比于 NZXT 只是好看来说,它在设计上就考虑到了更多的功能性的细节。比如有单独的重启键(可怜的硬盘灯已经被时代抛弃了)和 LED 控制键而且 LED 控制器可以设置为转发主板的 ARGB 信号(看看人家!),还有磁吸的合页式的玻璃侧板方便随时打开(对我这种强迫症太友好了,拧螺丝搬侧板好累),而且它的电源仓设置了单独的合页门!你可以从前面打开,然后硬盘仓设计成了 NAS 那种可以从前面拿出来的样式(甚至官方还有热插拔配件,彻底变身 NAS)(看看人家!),对折腾型用户不要太友好。但我最后还是没选它,因为我发现这个机箱第一个 PCIE 挡板和它上面的边框之间的缝隙太太太太太大了——你们厂家在这里多加两毫米宽度会赔本吗?而且后来我又想了想,合页式多半不能完全贴合(要给合页留出空间,所以还是算了)。

然后我看到了海盗船新出的 4000D,纯白色的前面板 + 非外凸的 PCIE 螺丝孔 + 独立的重启键 + 滑动抽拉的硬盘仓 + 有卡口的侧板,虽然它只有一个前置 USB 3.0 和一个 Type-C,不过也不是不能接受,而且有独立的重启键(硬盘灯:四个人的接头我却不能拥有姓名)。虽然它也支持显卡竖装,但它显卡竖装的上面有一个封得比较严实的挡板,它的 PCIE 槽相对也没有太大缝隙(相对)。颜值虽然没有 NZXT H710 好看,但在简洁上也是吊打鬼斧了,于是决定入手这个。

到手了也发现这个箱子还不算完美,主要的问题在于以下几个:电源仓上边的挡板强度不够,虽然没什么人会拿这个地方承重,但是比我之前的先马鲁班软得多也太差劲了吧!好在机箱其它承重的组件都过得去。然后就是 PCIE 的螺丝孔和显卡的孔有错位!需要很大力气按着才能勉强拧上螺丝,我用的可是公版显卡,你不能说公版卡孔位不标准吧!最后最后,这个机箱底板并没有多延伸出一块盖住侧板的底部!虽然侧板不需要这个位置辅助固定(它有很多卡口固定住),但是明明你顶板是有延伸的,为什么底板要在这里留一条缝隙???总而言之就是搬机箱时候要注意手不要抠到侧板和机箱这里的小缝隙(挺窄的,手多往中间伸一点就好了),但是还是会让人担心抠这里把侧板抠变形了。不过总之看下去也没有更合适的选择了,所以就将就吧。送的两个风扇是 3 pin 的,不能 PWM 调速,真是抠门啊海盗船。

顺便还有个对我不是问题但对大部分人可能是问题的地方,这个机器的前面板里面有一大块金属防尘网,但是根据一些国外的评测,假如你在前面板装上风扇,可能会因为风扇吸气导致这个防尘网向里面贴而蹭到风扇扇叶。但是我装了三个反向进风的风扇在前边,风扇框架在防尘网一侧所以并不担心这个问题。我也推荐所有前面板不是透明玻璃的人在前面使用反向风扇,毕竟正常来说通过侧板看到机箱内部反向风扇的灯光才是最合适的(风扇框架不会出现在机箱内部)。并且说实话,前面板通风比美观更重要,真的没必要买玻璃前面板。

于是装好了以后正面和背面就是这样的,不要和我说机箱不是留了理线槽了吗为什么不把主板电源线理进去,海韵的主板电源线又粗又硬,连用这块机箱的盖线板盖住都很勉强了,扭来扭去塞到那个理线槽里根本不可能,我也不想让它接头的部分受太大的力。这块主板 Type-C 的接头在内存附近,虽然这是主流位置,但对于直插的 Type-C 线缆来说弯线同样也很困难,别的理线倒没遇到什么大问题。

正面

背面

白色机箱里面反光要亮一些,但是有灰尘也更明显了……

亮起来

前面侧板的灯光效果很好看,没买 Airflow 版一个是因为它挖孔太多太乱,另一个原因是以后我也打算贴上贴纸。

前面

今天看到 NGA 上有人是风扇白光配红色 ROG LOGO,我也试了一下蛮好看的,就是感觉屋子里又冷了 10 度……

白色

全景


接下来是喜闻乐见的 debug 时间,首先是 Linux 下的,相对比较好调:

三代 Ryzen 有一个 每次都返回 0xFFFFFFFF 作为随机数的 BUG,在我这主要影响 wireguard,巧的是购买前几天我刚读过这篇文章,AMD 已经发了新的固件修正错误,建议更新到主板厂家提供的最新 BIOS 版本一般即可解决,如果主板厂家最新的 BIOS 还没更新固件建议联系售后催一下。

在我这不知道为什么 GDM 有时候没法自动启动,但是手动切 tty start 又可以显示,查了一下 ArchWiki 的 GDM 页面 发现有解决方案,但是并没有原因,搜索了一下也没发现原因是什么。

我还发现有时候刚开机没多久很快就关机会卡在什么 systemd-udevd 进程没结束,最后 event loop failed + timed out,大概要卡好几分钟才关机,但如果你用一会再关机就没问题。检查好几次关机日志没发现问题,后来群友火眼金睛对比了完整日志发现有个叫 ucsi_ccg 的模块开机加载了两分钟,猜测是这个的问题,搜索一下发现是 5.3 内核里 NVIDIA 添加的相关代码,用来控制 NVIDIA 显卡上的 Type-C 接口的,可是我这块显卡根本就没提供 Type-C 接口!(以及很多笔记本内置的有输出的 N 卡也有这个问题。)临时在 /etc/modprobe.d/ 里加了个 blacklist ucsi_ccg 的 conf 屏蔽了这个,好像没什么不良影响……

然后是奇怪的 Windows 的问题,我一开始装的 LTSC,不知道怎么回事输入法没了……折腾无果只能重装。以及现在除了不要联网装 Windows 之外(否则会强制你登录微软账户然后用你名字拼音前五位做用户名),还得不要联网装 NVIDIA 驱动,否则 Win10 自动更新驱动会给你安装 DCH 版的,虽然没什么影响(只是在 NVIDIA 官网升级驱动时候不能选标准选 DCH),但是就是让人很不爽。声卡驱动要装主板厂商的,Win10 自带的只能输出,不能接麦克风录音,然后如果麦克风声音很小就打开 Realtek 的声音控制程序,在右上角齿轮里取消掉什么把所有输入结合到一起的设定(什么乱七八糟的玩意!)。还有要关掉快速启动,不然直接开机会卡在黑屏一个鼠标光标……反正就很烦人。

更新:还有一个奇怪的问题是 Win10 关机重启也要卡很久,上网搜了一下全是一些忽悠小白的办法,经过我不懈搜索发现了一个熟悉的名字 UCSI!微软承认存在 UCSI 问题,既然又是这个 UCSI,多半还是 NVIDIA Type-C 的问题!反正我没这个接口,果断重新安装 N 卡驱动,选择清洁安装(删除旧驱动)并不勾选 Type-C Driver,问题解决……

最后既然设备到位了,大概就每周一三四五晚上八点半在 Bilibili 4312991 直播间 播一个半小时游戏,反正我玩什么播什么,大概就 CSGO Dota2 PUBG 什么的吧,尼尔也有可能,反正不是恶心反胃的就可以。周末随缘直播,如果没什么事情白天就播一会,周二可能晚一点开播因为有课。

Alynx Zhou

A Coder & Dreamer

by Alynx Zhou (alynx.zhou@gmail.com) at January 15, 2021 11:46 AM

January 14, 2021

中文社区新闻

手册页索引服务

我们非常高兴地宣布我们最新的公共服务: 在 man.archlinux.org 公开的手册页索引站,用来发布我们所有软件包中所包含的手册页(man pages)并提供 搜索在线浏览功能。比如可以试一试 tar 的手册页

也可以在每个软件包的详情页面的侧边栏中看到新增的手册页链接。感谢我们的 Wiki 管理员 lahwaacz 开发了 archmanweb 实现这一功能。

尽管网上已经很多其它的手册页索引网站,通过发布与我们提供的软件包相符版本的手册页,我们希望能进一步改进 Arch 的可用性和文档。

by farseerfc at January 14, 2021 06:52 AM

January 07, 2021

Leo Shen

Homelab Project: 6 months in

At the summer of 2020, I started the Homelab project. Now, 6 months later, it's time to evaluate how the thing goes. Hardware configuration Previously, I've been using an AMD Ryzen 2200g with a garden-variety A320 motherboard. However, there had been two incidents, of different reasons, which prevented the OS from booting, and I had to remotely instruct my parents to fix it using TTY. Needless to say, these didn't go well.

January 07, 2021 07:01 AM

January 04, 2021

Alynx Zhou

StackHarbor 的 2020 尾记

我最近思考了一下,总是记不起来去年的总结写了什么,结果翻了一下博客发现我的记忆力是对的——我去年还真的就忘了写总结。

今年的总结因为各种原因写的稍微晚了一点,不过总之还算是写了,比忘记写要好得多吧!

以前小时候总是觉得一年过得很慢,要过很久才到新一年放烟花吃饺子,但是现在觉得一年过得很快,可能要忙的事情多了就会觉得时间不够用。但我一般来说又不觉得自己做了什么值得记录的事情,看到别人的博客年终总结写的特别充实,又是自己出国求学又是自己找实习转正的,但是到了我自己总觉得这些也没什么好写的。再加上我是个相当讨厌计划的人,所以也没什么“检查自己一年的完成度”的机会。

不过我在那些不错的年终总结里面还是学到了一些东西,所以打算也写点类似的。不过我虽然是程序员,写文章还是习惯从头到尾写,不擅长做那些分类加标题的事情,所以就想到哪写到哪。

2020 年感觉最不错的事情大概是加入某绿色蜥蜴工作,虽然这个严格来说从 2019 年就开始了,但是我去年忘记写总结了……多亏了同学的推荐得到了一份实习,面试感觉很好,没有考什么我特别不擅长的算法题而是一些实践性的知识,这个我还是挺擅长的。然后同事也都相当好相处,一开始是测试相关的工作,也了解了很多测试方面的知识,甚至还写了点 perl(虽然只了解皮毛),总之是很有意思的经历,然后更意外的是领导居然主动问我有没有什么别的感兴趣的领域,因为我一直是 GNOME 用户所以对 GNOME 维护挺感兴趣的,结果后面就转到 GNOME 组去实习了(这也太好了吧天哪)。然后就是快毕业了需要准备正式工作,一般来说这边没有类似国内互联网企业那样招一大堆实习生然后给几个转正名额竞(yang)争(gu)的途径,并且他们也几乎不进行校招,对毕业生和社招一视同仁,虽然我个人很想在这工作,但是如果想留下来的话还挺看运气的。这时候领导又和我说正好组里有空缺职位,可以安排面试,只要能保证一直实习到正式入职就可以了,于是又十分幸运的毕业之后正式入职。总之能在自己感兴趣的领域做工作已经是十分幸运了,然后待遇相对来说也不错,特别是看多了加班猝死的新闻,心里更加满足了……同事也都很友善,而且都是技术类型的,做的又都是开源相关,平时也很聊得来。今年整体来说不是那么容易找工作的,我都要反思我为什么那么幸运了……顺便由于疫情原因,我司今年一直是在家办公状态,也节省了好多通勤的时间金钱……

在找工作这方面我实在是没什么经验可谈,我太靠运气了……如果非要说的话,就是平时自己多学习多写写程序吧……

因为工作的原因今年一直是自己在外面住的,房租好贵啊……至于自己住虽然挺安静的但也挺麻烦的……把东西从学校搬出来也花了不少麻烦。

今年个人项目方面没做什么新东西,去年把 Hikaru 从 CoffeeScript 换成 JavaScript 之后基本就只是写文档、加测试、改功能,大改是去掉了 cheerio 改成自己实现了一部分功能,谁叫他们一年不……主题方面给 ARIA 做了个大改是去掉了 jQuery 加上了暗色模式,当然我自己看起来界面并没有什么变化。大一时候写的 FlipClock 努努力改成了 CMake,然后这样就可以跨平台做成 Windows 屏保了。顺便了解了一下怎么在 Android 上面运行 SDL,做了个 Android 的 FlipClock。而至于我的弹钢琴页面和 Telegram Bot,我已经忘记是今年还是去年写的了……

今年折腾了一遍我的电脑,因为终于有时间和钱玩自己的台式机了,仔细想想好像把之前的能换的都换了,27 寸的显示器对没有双屏空间的人来说提升了不少工作效率,5800X 打游戏也很爽,就是钱包不太舒服……

口琴方面今年年末又高产起来了,而且开始剪视频,发现达芬奇可以在 Linux 下面用(虽然有些限制)(Adobe 看看人家!),而且还挺流畅的,于是看了影视飓风的达芬奇教程学了一些基本的剪辑知识,为了用的更舒服还闲鱼买了个加密狗(假货很多,安全下车),今后可能剪视频的频率会逐渐增多,就当练习新技能了。

手机打算再用一年,今年手机厂商出的都是什么垃圾?我现在也想清楚了反正手机又不能给我带来收入,有这个钱还不如投资到台式机上,希望各种换手机患者也考虑一下,我现在是能用就行了。除非哪个厂家出一个摄像头不丑还有耳机孔最好还是直屏系统不要乱删乱改的旗舰机。

动漫除了看电磁炮 T 以外就是看了紫罗兰永恒花园,一开始很多人吹导致我对这个比较反感,实际看了以后觉得还是很不错的,所以吹得太过果然会招黑吗……电磁炮 T 总之中规中矩,能有第三部已经很不错了,我还想看第四部……、

认识了一些新朋友,同时很多老朋友也都有各自要忙的事情,总之几乎没什么人一起打游戏了……不过经常能和蓝猫她们一起出去玩还是避免了成为死宅的命运,本来我都打算在家打游戏跨年了,最后和蓝猫狐狸一起吃了海底捞,虽然三点才回家导致第二天犯了鼻炎,不过还是非常开心。

年末通关了 Titanfall 2,剧情很短,中规中矩,但是就已经是非常不错了,除了操作不适合我这个手残以外都很适合我。今年几乎没怎么玩 CSGO,但是下班之后有很多空余时间基本都投入在 Dota 2 上面了,虽然我也看很久 Dota 2 了,但是玩起来确实很难……不过我这一年一直都沉迷在中单光一直播里面,已经成了我玩 Dota 2 的动力了……现在多少也算入门了,虽然偶尔还是操作不过来,但至少明白是个怎么回事了。中单光一的直播真的很好看!正人君子,皮又好看,说话又好听,打游戏厉害,又很温柔。一开始我只是看他打 Dota,反正讲围棋我又看不懂,但我发现他读围棋棋手传记有意思多了,已经进入追小说模式了……拖到现在才写年终总结也是因为坐了 16 个小时的火车跑到上海去看 VirtuaReal 的第一次线下 Live,不过互动环节没抽到我实在是令人沮丧,我太非了,那么多人根本没我的机会呜呜呜呜呜……

就写这些吧,希望 2021 年大家的生活都能变得顺利!

Alynx Zhou

A Coder & Dreamer

by Alynx Zhou (alynx.zhou@gmail.com) at January 04, 2021 09:21 AM

中文社区新闻

Arch Linux 邮件列表 id 变更

由于我们的垃圾邮件应对机制,我们不得不迁移邮件列表,原本从 @archlinux.org 发出的邮件变更为从 @lists.archlinux.org 域名发出。

发送邮件到邮件列表不受影响,发往 @archlinux.org 还能继续使用,邮件会被自动转发。

需要用户操作的唯一变化在于匹配 From 和 List-id 字段的过滤器和规则需要相应更改。

by farseerfc at January 04, 2021 12:16 AM

December 29, 2020

Leo Shen

Fix incompatible bytes library for actix-web and tokio

When attemping to build some web app with actix-web, I ran into this issue: 1 2 3 4 5 6 7 8 error[E0271]: type mismatch resolving `<fn(bytes::BytesMut) -> bytes::Bytes {bytes::BytesMut::freeze} as FnOnce<(bytes::BytesMut,)>>::Output == actix_web::web::Bytes` --> src/api/get.rs:35:47 | 35 | return HttpResponse::Ok().streaming(s); | ^^^^^^^^^ expected struct `bytes::Bytes`, found struct `actix_web::web::Bytes` | = note: perhaps two different versions of crate `bytes` are being used? = note: required because of the requirements on the impl of `futures_util::fns::FnOnce1<bytes::BytesMut>` for `fn(bytes::BytesMut) -> bytes::Bytes {bytes::BytesMut::freeze}` This happens when I'm trying to utilize FramedRead in Tokio in order to stream the content while sending it in acitx-web (instead of reading them all into memory before sending it out).

December 29, 2020 12:05 PM

December 12, 2020

百合仙子

一次失败的 KDE 尝试

本文来自依云's Blog,转载请注明。

前些天尝试了一下 KDE 桌面环境,不过实在是没能用下去。

首先要说的是,KDE 桌面确实漂亮,非常养眼。设置项也挺多,可定制性还是挺不错的。只可惜问题同样很多。

首先是显示器缩放的问题。受限于 X11,KDE 只能设置一下全局的缩放比例。所以我们只好缩放显示器显示的画面。不幸的是,一向相当体贴的 KDE 此时却笨笨的,在使用 xrandr 设置好之后需要重启 plasmashell 来使其获取 xrandr 的设置更新

kquitapp5 plasmashell && kstart5 plasmashell

KDE 的设置项很多,分门别类地在「设置」应用程序中集中列出来,然而问题也由此产生:同时只会显示一个「设置」窗口。也就是说,我配置快捷键时,想去窗口管理器那边看一看,调整一点选项,就必须放弃我当前打开的快捷键视图,放弃我键入的搜索词,并且选择「应用」或者「放弃」更改,才能切换到另一个「设置」组件中去。即使从 krunner 里打开某个组件的设置,它也会找到并更新已有的窗口。

我知道 Windows 10 也是这么个「单任务」设置的风格。可 Windows 10 也没有这么多可以设置的地方呀。后来获知有个命令可以打开单独组件的设置窗口。很不方便。它被隐藏起来的原因是这种窗口不能返回到组件列表界面,会让用户困惑。可是,为什么我不能同时打开「设置」的不同组件的多个窗口?单独组件的窗口会让用户困惑,那就不要用单独组件的窗口就好了嘛。

KDE 桌面还有个问题:启动特别慢。登录进入界面要好久,启动一个程序,它的图标也要跳好久窗口才会出现。不知道它在干什么。我甚至怀疑它是为了展示启动动画而故意推迟界面的显示。

KDE 有提供丰富的桌面部件。我往副显示器上放了一些系统状态的监视器——CPU、磁盘、网络啥的。然后问题来了:我凑齐了四个部件刚好形成2x2的网格,可是我要怎么对齐它们呢?并没有对齐的选项,也没有吸附的功能。在我找到它使用的配置文件并手动修改之前,我只能用肉眼瞅。可计算机不就是用来做这种人不擅长而机器擅长的事情的吗?

终端我还是用 GNOME Terminal,因为有些特性(比如超链接)只有它支持。但又出现问题了:它启动之后,pin 它的任务图标,或者通过任务栏图标创建新实例均会失败。把它 pin 到任务栏上,需要从主菜单的右键菜单里操作。即使这样,启动之后终端窗口还是会位于新的图标,旧图标还是不对应任何窗口。后来查了一下,GNOME 的东西都没有主动支持启动通知,导致 KDE 很多时候只能猜测,而这次它猜错了。解决的办法是给 GNOME Terminal 的 .desktop 文件加上正确的 StartupWMClass 项。这其实不是 KDE 的问题,但也没办法。KDE 不想为别人擦屁股,GNOME 不在意自己的软件在别的桌面上的可用性。

不过 Qt 写的 flameshot 我就不知道是怎么回事了。具体情况不记得了,反正就是显示异常。好像是全黑吧。我没来得及 debug 这个。

最后,让我决定放弃 KDE 的点来了:我设置不了我需要的窗口管理快捷键

切换窗口,默认是 Alt-tab 的那个,我好不容易在「快捷键」设置里找到了添加更多快捷键的方式,但我发现除了 Alt-tab,我自定义的都不能连续切换窗口。按一下,切换一下,然后就切不动了,只能放开快捷键。后来了解到这是设置更新方面的问题,kwin_x11 --replace一下就有效了。

切窗口其实问题不大。问题大的是切显示器屏幕。两个功能:一、把焦点切到另一个屏幕;二、把当前窗口移到另一个屏幕上。

前者可以勾选「分隔屏幕焦点」选项,然后调整一下「阻止盗取焦点」的级别。我也不知道这个级别都是啥意思。「无」我能理解,「低」「中」「高」「终极」都是些啥?反正调整一下,确实可以把窗口焦点切换到另一个屏幕去了,除了鼠标不会跟着过去!另外测试过程中,有时候焦点会丢失——我不知道当前什么窗口获得了焦点,也不知道接下来谁会获得焦点。比 Mac OS X 里焦点跑到一个窗口也没有的 Finder 上还要神秘。

不过这个倒是可以自己写个脚本解决:使用 xrandr 获取屏幕的大小和位置,通过 X 的接口获取鼠标的位置并通过 Xtest 扩展来移动它,然后再用某个 X 的接口去设置窗口焦点——完全绕过 KDE 的功能。

然后我被另一个问题难住了——我怎么把窗口移到另一个屏幕上并且把焦点也移过去呢?使用文档匮乏的 kwin script 是可以把窗口移过去,然后我没能找到移动鼠标光标的 API。通过 X 是可以移窗口的同时移鼠标,但是我拿不到带窗口装饰的窗口位置信息。kwin 有一个 getWindowInfo 的 D-Bus 接口,但是它接收的那个 UUID 参数,我没找到获取的方法。

总结一下,KDE 对快捷键的支持并没有想像的那么好,尤其是多显示器的支持。快捷键的设置是通过图形界面来操作的,虽然直观但是对于大量快捷键的管理来说非常困难。而对于大显示器来说,通过快捷键来管理窗口是十分必要的——因为我更难肉眼找到我的鼠标光标去了哪里。

接下来,我打算一边忍受着 Awesome 3.5.9 的旧与 bug,一边尝试将 i3 改造成我需要的样子。

by 依云 at December 12, 2020 02:55 PM

December 06, 2020

百合仙子

i3 的 scratchpad 处理逻辑

本文来自依云's Blog,转载请注明。

i3 有个东西叫「scratchpad」,和我在 Awesome 里用的 run_or_raise 功能有些类似。

我的需求是某些浮动窗口可以「招之即来,挥之即去」。上次尝试切换 i3 遇到的一大麻烦就是,我经常从终端里启动图形界面的程序,而启动完之后我得手动给我的终端找个地方放着。i3 不支持最小化,也只有十个带数字快捷键、可以快速访问的工作区,所以 scratchpad 很重要,但是它的行为我有些捉摸不定。

首先是 move scratchpad 这个命令。它会把当前 con(窗口或者容器)浮动、取消全屏,然后移到一个叫 __i3_scratch 的不显示的工作区。

然后是 scratchpad show 命令(动词放后边了)。如果没有指定条件,它有如下复杂的处理逻辑:

  • 检查当前窗口是不是去过 scratchpad。如果是,就把它丢回去。
  • 否则检查当前工作区是否有另外的 scratchpad 窗口。如果有,就给它焦点。
  • 否则检查其它工作区是否有另外的 scratchpad 窗口。如果有,就把它移过来。
  • 否则把 __i3_scratch 里最久没有「见到光」的窗口移过来。

如果指定了条件,那么这样检查匹配的窗口:

  • 如果窗口不曾去过 scratchpad,什么也不做。
  • 否则如果窗口去过 scratchpad 并且在当前工作区,就隐藏它。
  • 否则就把它移过去。

总结一下,就是「回去,或者回来」。虽然动作的名字叫「show」,但其实是一个类似于 toggle 的功能。它的麻烦之处在于:如果你有多个去过 scratchpad 的窗口,你很难控制出现的是哪个窗口。一个绕过这个问题的办法是,总是带条件地使用 scratchpad。另一个小麻烦是:没有办法在匹配的窗口已经显示的时候,不要把它隐藏掉——有时候我只是习惯性地呼叫我的终端,而不看它是不是已经在我面前了。

对于浮动窗口,i3 有很多奇怪的限制,或者说是未实现:

  • 不支持最小化
  • 浮动窗口也不能显示在平铺窗口之下(加上上一条,就是没办法暂时藏起来)
  • 不支持最大化(手动调整窗口大小无法自动适配显示器大小,也没有「恢复」一说)
  • 不支持显示在最上层(当你在 GIMP 里开了一堆图片需要局部对比时)
  • 有全屏窗口时不能显示浮动窗口(看视频无法临时使用浮动窗口查个单词啥的)
  • 切换窗口时,平铺窗口和浮动窗口是隔绝的(需要单独的快捷键来切换)

by 依云 at December 06, 2020 06:05 AM

November 26, 2020

Alynx Zhou

解决 Spleeter 愚蠢的依赖问题

我倒不是对机器学习有什么莫名的偏见。事实上有些只能用机器学习搞定的东西我也很支持用机器学习解决,比如 waifu2x 这种增加图片分辨率的或者 Spleeter 这种分离人声和伴奏的,用传统的分离人声的方法就是不能完美解决这类问题,而机器学习模糊分类则可以无限接近完美解决。我讨厌的有两个,一个是传统方法很好解决的东西非要用机器学习解决,另一个是混乱的机器学习项目,后者更严重。

不知道是因为什么原因,许多机器学习从业者似乎都缺乏整理代码和依赖的能力——能轻松地把一个这类开源项目打包简直是奇迹,更多时候别提打包了,你想自己安装然后跑起来都不太现实,哪怕是同样做机器学习的其他人也有这样的苦恼:实现者似乎把论文写好然后在自己电脑上能跑起来就心满意足了,丢出来一份没什么文档的代码,不折腾几天根本不知道这坨代码需要装些什么才能跑起来。对我这种洁癖用户就更严重了,我可不希望 pip 在我的系统里塞一堆乱七八糟的东西,这个需要其他项目才能避免全局安装的程序比起 npm 真是差远了,解决依赖的能力也远不如 pacman 和 npm。

然后有人发明了 conda 和 docker 这样的 辣鸡 项目来拯救其他的辣鸡,但事实上不过是变得更糟而已——它们给了一些懒人名正言顺不维护项目的借口——反正我丢一个 conda 配置或者 docker 文件上去,你们拉上一大堆和我一样的过期依赖就能跑了,至于占了你多少硬盘,干不干净,关我什么事?如果不是物理限制,恐怕这些懒人会把整个宇宙都塞进去。而且他们再也不会管版本更新,什么 tensorflow 2 与我何干?用了我的 docker 你的系统里就会有几百个不同的过期的 tensorflow 1,买硬盘就完了呗?这和买显卡就完了呗还真是一路货色,这样依赖商业公司的产品,总有一天感觉会被割韭菜。

今天我用到的这个 Spleeter 某种意义上也是这样的一坨辣鸡,它的仓库里面的 README 是完全过时的,但这起码是件好事:代码还在更新。只是你按照文档是没办法顺利的搞定它的,我总结了一下我的解决方案,可能不适合其他人,但我大概知道怎么解决了。

这个文档有多离谱呢?它说你如果使用 GPU 加速的版本,必须从 conda-forge 安装,pip 和 GitHub 的版本都只有 CPU——那你这也是按揭开源?然后整个文档没有一个地方告诉我我需要单独下载训练好的模型才能跑起来,直接运行程序则丢一个段错误——可能他们搞机器学习的人觉得下模型是常识不需要说吧。

如果直接按它的方法运行 conda install -c conda-forge spleeter-gpu,你会得到一个不能用 GPU 的 GPU 版,这实在是太搞笑了。我捏着鼻子用 conda 就是为了能让你一步把这一坨东西给我弄好,现在你说你弄不好?

但是有一点好处就是实际上他们的代码已经更新到支持 tensorflow 2 了,所以其实完全不需要用那个过期的 conda-forge 的版本,你完全可以直接在 Python 3.8 里面 pip install spleeter-gpu 安装最新的版本——然后我又遇到了依赖问题,我的一些系统软件包依赖 numpy,然后 Arch 官方源里的 numpy 版本还是比 spleeter-gpu 依赖的 tensorflow 依赖的 numpy 版本新——你们写 Python 的人真麻烦,版本号兼容性是可以随便 break 的吗?

所以这时候我还是用 conda 解决,总之就是搞一个和系统独立的虚拟环境(npm:这不应该是内置功能吗?还需要用商业软件?)。总之去 TUNA 搞一个 Miniconda 3 最新的安装包来,然后直接安装,看着它往你的 shell 配置里塞一坨辣鸡(我把实际用到的命令拿出来做了个 alias,这样就可以只在我需要的时候打开 conda 了)。

为什么使用 miniconda 而不是 anaconda?因为 anaconda 带的那个图形界面根本用不了,miniconda 够用了。

然后创建一个 Python 3.8 的环境,高了低了都不行,真是难伺候,同时别忘了带上 cuda 和 cudnn,不然它一声不吭的就会只能用 CPU:

$ conda create -n spleeter python=3.8 cudatoolkit cudnn

cuda 不叫 cuda,叫 cudatoolkit 就离谱。

然后切进去:

$ conda activate spleeter

然后装 spleeter-gpu 到 conda 新创建的这个 Python 环境:

$ pip install spleeter-gpu

所有的依赖应该 pip 都会解决,但这个弱智有时候还会说我本机已经装了 numpy 1.19.4,比 tensorflow 需要的版本高,但我都创建虚拟环境了你还读取我系统的干嘛?不过其实好像也不影响使用,或者此时可以 pip install numpy==1.18.5

然后到 https://github.com/deezer/spleeter/releases 去下载训练好的模型,这也是个弱智的地方:哪有 tar 打包不把目录本身打进去的?然后还得给这个程序创建一个工作目录,因为它是写死的到当前目录下面的 pretrained_models 下面去找模型。

$ mkdir -p spleeter/pretrained_models
$ cd spleeter/pretrained_models

我这里用 2stems 的模型示范,因为我只需要分离人声和伴奏:

$ wget -c 'https://github.com/deezer/spleeter/releases/download/v1.4.0/2stems.tar.gz'
$ mkdir 2stems
$ cd 2stems
$ tar -xpvzf ../2stems.tar.gz

因为他们打 tar 包时候没把目录打进去,所以别忘了自己创建目录!

然后回到你的工作目录就可以用了,我这里结构是 spleeter/pretrained_models/2stems 所以工作目录就是 spleeter

$ cd ../../
$ spleeter separate -i 你要处理的歌曲 -p spleeter:2stems -o 输出目录 -B tensorflow

如果你想使用其他的几个模型,那就把 2stems 改成其他模型的名字,但是这里还有个开发者脑子抽了的地方:带有 -finetune 的是高品质模型,它们的模型目录名字应该是比如 2stems-finetune,但是参数名字却不是这个而是 -p spleeter:2stems-16kHz,文档里当然是没找到的,我觉得应该揍开发者一顿让他老实写文档(不过我又看了一下,这个 finetune 对于分离音轨没什么用好像)。

我这里必须使用 -B tensorflow 才会走显卡加速。

用完了就可以 conda deactivate 退出虚拟环境,要用的时候别忘了 conda activate spleeter 切换进来,

遇到类似问题的同学可以参考我的文章,但是因为这个处理过程影响因素太多了,如果你的不能用我也没什么办法。

回头一看,这个项目犯了一大堆禁忌:难以解决的依赖,写死的模型路径还有匮乏的文档,导致配环境就要配一大堆。当然,好在他们还是在努力更新跟上依赖而不是撒手不管让它慢慢死去,并且功能非常好。真正对开发者友好应该是不需要配环境的,比如在 Linux 下面开发软件,包管理已经帮你考虑好各种依赖了。只有像 Windows 或者 Android 这种不以开发者为中心或者是许多机器学习项目这种“数据好看就行”的地方才会有这么多麻烦。真的很希望这些人能补一点务实的基础,不要让他们的软件这么难用。

Alynx Zhou

A Coder & Dreamer

by Alynx Zhou (alynx.zhou@gmail.com) at November 26, 2020 08:43 AM

November 23, 2020

frantic1048

Espresto - 忍野忍 Clear materials

Shinobu

到了一两周的小忍的照片,总算是搞出来了。当时看到介绍的照片还不错,而且还是景品,就果断入手了,到手一看,非常良心!

也是这个月,升级了一下之前的小相机 Canon G7X Mark II (G7X2)到新出的 Sony Alpha 7C(A7C)。新机器到手后兴致勃勃地搭起环境拍了一波,传到到电脑之后——Darktable (脑补的)一副阿库娅脸提示读到的是不认识的 RAW 格式,拜拜。

作为多年 Arch 用户对设备过新造成的问题早有体会,比如 18 年底装机,入手首发 RX590,旧系统直接黑屏,最新的 live iso 上来也是黑屏,折腾半天 GPU 的驱动问题解决之后接着主板也来一脚,让我新装机器的激情全撒在想方设法让系统启动上了,到最后终于成功启动,已经内心毫无波动 ヾ(°ω。ヽ=ノ °ω。)ノ

简单去 Darktable 的 issue 列表翻了一下,很巧数天前就有人报告关于 A7C 支持的问题,对照了一下已有的别的相机支持的 issue 的创建时间之后,这样等下去怕是半年都不定会有支持,我不能接受!于是去 Darktable 历史翻了一通的新相机支持的 PR,发现并不是那么深奥的操作,糊了俩看起来能用的 PR,今天(2020-11-23)总算是过了上游 Review 合进去了,终于确定之前自己 patch 的版本是 ok 的了!

接下来是小忍的时间。

右边的头发尖有一点点歪,不知道是不是故意的,还是只是运输的时候给搞弯了,尝试一番也没能扭直就这样吧,不要盯着看也不会察觉。和四糸乃作为同样暗色服装的手办来说,服装上的(只有拍照时候比较容易凸显出来的)细微瑕疵相对少很多,修图工作量大大减小,拯救了我的眼睛。

这个 JPEG 输出 banding 都压出来了有点难受(没看到?那就是没问题!),等工具链的 AVIF 站起来了再改善一下画质。

Shinobu Shinobu Shinobu Shinobu

Shinobu Shinobu Shinobu Shinobu

半透明的材质效果很棒,群摆上的细节也比较稳定。

Shinobu

我好了!

Shinobu

November 23, 2020 12:00 AM

November 21, 2020

百合仙子

HiDPI 配置记录

本文来自依云's Blog,转载请注明。

首先,我是用 X11 窗口系统的,不同屏幕分别设置肯定没戏。所以只好让笔记本电脑的屏幕迁就一下我的4K主屏啦,把笔记本屏幕缩放一下。算一下 scale 值:192 / 120 = 1.6。不是整数,会糊,可总比放大两倍的巨大界面要好。

xrandr --output eDP-1 --scale 1.6 --auto --output DP-2 --auto --pos 3072x0 --primary --fb 6912x2160

这里要注意的是,要指定--pos(或者--panning),不然会重叠;要指定--fb,不然鼠标可能会有部分区域去不了。

然后开始设置。本来我是尝试了一下 KDE 的,但因为我将在下一篇文章中写的原因而放弃,回到了 Awesome。不过也不是全无收获。我把 KDE 的配置方案拿过来用了。你想问怎么拿的?我 btrfs 的文件系统,做好快照再 rsync -n 对比一下它动了哪些文件就有了。

首先是 X11 的资源。在~/.Xresources里写上Xft.dpi: 192,然后xrdb -merge ~/.Xresources一下就好了。顺便再xrandr --dpi 192一下,听说有些程序会读这个。

然后是 GTK。GTK 2 就放弃吧,没办法。文字会按设置的 Xft.dpi 放大,图标啥的不会。GTk 3,要设置两个环境变量:

export GDK_SCALE=2 GDK_DPI_SCALE=0.5

前一个是把界面放大,后一个是把文字缩回去,因为文字已经按 Xft.dpi 放大过,不能再放大一次了。

再然后是 Qt。Qt4 早卸载干净了不用管。Qt5 嘛,也不用管。它自己会处理好。有个按不同屏幕缩放的环境变量QT_SCREEN_SCALE_FACTORS,效果跟 Windows 10 差不多的。但是我为了照顾其它程序已经把屏幕给 scale 过了,就不需要设置这个了。你要设置个QT_AUTO_SCREEN_SCALE_FACTOR=0也行,但这个是默认行为。

最后是个别的程序。

Telegram 直接在设置里关掉「默认界面缩放比例」并且设置缩放比例为 300% 就好了。我也不知道为什么,Telegram 默认的字总是很小。之前 120dpi 的时候我要 200% 缩放,现在 192dpi 需要 300% 缩放了。

YouTube,就是那个网站啦。它其实没什么显示上的问题,只是死活不会给我自动选择 1080p 以上的分辨率。经过仔细二分测试之后发现,把火狐的配置文件夹下的storage/default/https+++www.youtube.com目录删掉之后就好了。没发现删掉这个会有其它影响。

mpv 要修改配置文件,加上no-hidpi-window-scale参数,不然会把视频自动放大,4K视频一打开会只能看到四分之一的画面。加上这个参数,默认窗口大小时,一个视频里的像素会对应一个显示器上的像素,不大不小刚刚好。mpv 文档上说这是 OS X 系统上的默认行为,可我这是 Linux 桌面啊,你把别的平台上的习惯搬过来是几个意思?另外我加了个demuxer-readahead-secs = 20选项。我的大文件都在机械硬盘上,4K 码率又比较高,不多预读一点容易卡。

我的 qemu 之前使用的是-display gtk,也坏掉了。窗口那么大,虚拟机只用左下角那里四分之一的空间。spicy 也有问题,会告诉虚拟机只有 1080p。解决方法是 unset GDK_SCALE GDK_DPI_SCALE。它们在放大了自己的界面的同时,把显示的虚拟机的内容也给放大了,所以干脆叫它们别动。也没什么别的影响。

哦还有 Zoom。设置个QT_AUTO_SCREEN_SCALE_FACTOR=1似乎就好了?我试了一下QT_SCREEN_SCALE_FACTORS,会导致很怪异的行为。

以上解决了显示大小的问题,但我发现还有个问题:我的鼠标光标时大时小的……从 KDE 那边弄来几个设置之后就好了,而且主题也更加一致了呢。

首先是设置 xcursor 环境变量:

export XCURSOR_THEME=Vanilla-DMZ XCURSOR_SIZE=36

听说对应的 X 资源大家都不理睬,那我也就不设好了。

然后是 GTK 2 的~/.gtkrc-2.0文件里写上:

gtk-cursor-theme-name = "Vanilla-DMZ"
gtk-cursor-theme-size = 36

再接下来是 GTK 3 的~/.config/gtk-3.0/settings.ini

[Settings]
gtk-cursor-theme-name = Vanilla-DMZ
gtk-cursor-theme-size = 36

然后又没了。天知道为什么 Qt 那边啥都不干就好好的,GTK 却这么麻烦。

啊,你问这些环境变量在哪里设?我给写~/.xprofile里了。不过这还不够。有些 GUI 程序会由用户的 systemd启动(比如我的 Telegram 是由 systemd 启动的,为了在内存用得太多的时候自动重启),有些 GUI 程序会由 D-Bus 激活(比如 gnome-terminal)。这些是和登录会话分开的,所以要手动导入一下。以下是我的 .xprofile 中导入图形界面相关环境变量的部分:

_envs=(
  GDK_SCALE GDK_DPI_SCALE
  XCURSOR_THEME XCURSOR_SIZE
  XMODIFIERS QT_IM_MODULE GTK_IM_MODULE
  LIBVA_DRIVER_NAME GST_VAAPI_ALL_DRIVERS
)
dbus-update-activation-environment "${_envs[@]}"
systemctl --user import-environment "${_envs[@]}"

至于登录界面怎么办,我是在 lightdm 的 display-setup-script 里,跑了跑 xrandr,设置了一下 Xft.dpi 资源。环境变量啥的没动,反正用不上。当然你也可以去改~lightdm/.pam_environment来设环境变量,反正现在 Arch Linux 还是读它的。别的 dm 同理。

by 依云 at November 21, 2020 10:12 AM

November 20, 2020

百合仙子

让 QEMU 使用 SPICE 协议

本文来自依云's Blog,转载请注明。

缘起

我就是买了个4K显示器,咋这么多事呢……(有两篇文章还在路上)

第一个问题是,我的显卡 vGPU 最高只支持 1920x1200 的分辨率。行吧,我缩放成了吧?嘿嘿,-display gtk 的缩放不会保持比例,我只好算了算最大保持比例的大小,然后窗口切成浮动,再调用命令调整到指定的大小:

sleep 1 && xdotool getactivewindow windowsize 3280 2122

然后还要居中放置一下。多麻烦!

第二个问题是,我想在虚拟机里试试 i3,但是我的按键总是会被外边捕获。Super 键基本上是 Awesome 在用,而 Alt 键会撞上这个 GTK 窗口菜单栏的快捷键。

配置

经过多番尝试和摸索之后,确定了如下的参数:

  -display egl-headless,gl=on,rendernode=/dev/dri/renderD128
  -spice unix,addr=/run/user/1000/qemu/ArchKDE/spice.sock,disable-ticketing
  -device virtio-serial-pci
  -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0
  -chardev spicevmc,id=spicechannel0,name=vdagent

当然要gl=on啦,不然我怎么玩特效,我还不如回到 vbox 去呢(啊不,我的 vbox 不喜欢 btrfs,经常出错然后只读挂载,所以已经被删掉了)。要指定rendernode,不然它会 fallback 到 llvmpipe,然后还段错误崩掉……这也是我不-display spice-app的原因。

后边三行是从这个 QEMU + Spice with Copy & Paste 抄来的。搜索问题时不小心遇见,然后解决了这个我一直没有处理的问题。SPICE 不但能共享剪贴板,而且还支持 PRIMARY 选择区呢~虚拟机里要安装 spice-vdagent 并启动相应的服务。

然后是客户端的选择。一个很神奇的地方是,virt-viewer 看上去轻量,实际上只是选项少而已。它不光拖进来个 gtk-vnc 依赖,还把 libvirt 都给我带上了……然后 GNOME 的 vinagre,我不知道为啥,它就是连不上我的 spice+unix 地址。哦对了,virt-viewer 直接敲命令调用也是连不上,只能用 xdg-open 才能正常打开。

virt-viewer 有个依赖叫 spice-gtk。我试着 pacman -Ql 了一下,还真找到个 spicy 工具。比 virt-viewer 轻量多了,选项也更为丰富,比如可以选择不 grab 键盘。

一点额外的东西

在群里听说了 virtio-fs 共享方案,听说比 virtio + 9p 更高效。然后我用它成功取代了之前用的 NFS 方案(反正我的 vbox 虚拟机已经被删掉啦)。NFS 的服务也可以卸载啦(开放一堆端口到公网,看着有点怕怕的,虽然是 IPv6 地址不太会被扫到,但知道我的地址的咋办呢)。

virtio-fs 相比 virtio + 9p 的另一个优点是,和 NFS 一样,virtiofsd 是以 root 权限运行的,所以可以写入我的 pacman 缓存。qemu 那个 9p 似乎没有办法。至于启动嘛,用 systemd abstract socket 触发一下就好了。

另外,我使用 GVT-g 和 virtio 输出视频信号时,均遇到了声音在视频画面变化时声音卡顿的情况。一个绕过的办法是,通过设置 PULSE_SERVER 环境变量以及加载 module-native-protocol-tcp 模块,将音频信号直接通过网络发送到宿主机上,一点也不卡!

by 依云 at November 20, 2020 09:04 AM

November 16, 2020

百合仙子

Python 小版本升级是怎么 break 已有项目的

本文来自依云's Blog,转载请注明。

近日,Arch Linux 终于开始升级到 Python 3.9 了。很多人认为 Python 小版本升级容易搞坏兼容性,导致项目无法在新的版本上运行。事实是这样的吗?我正好借着 Arch Linux 升级 3.9 的机会,分析一下打包过程中失败的项目到底是出了什么事。

需要说明的是,我仅大致地分析了打包的报错信息,不排除分析出错,或者有额外的问题没有被看见的情况。另外我是在打包过程中随机(arbitrarily)取样,并且排除了我不能确定问题所在的案例。

以下项目测试失败是和 Python 3.9 相关的。排序是按照项目开发者的无辜程度排序的。也就是说,排序越靠前的,我越是认为项目开发者是无辜的;而像「硬编码 Python 3.9 为未发布的版本」这种完全不 future-proof 的做法,现在坏掉了真是自找的。

其中,使用的公开特性变化导致问题的有 3 个,调用私有属性或者方法、依赖非正式的文本信息的有 11 个,使用已废弃的特性的有 8 个,使用已被修复的 bug 的有 2 个,使用未来注定会出问题的信息的有 3 个。总共 27 个。

  • freecad: PyTypeObject.tp_print 没了
  • python-llfuse: PyTypeObject.tp_print 没了
  • linux-tools: PyMODINIT_FUNC 的变化导致了警告,然后被转为错误
  • python-blist: _PyObject_GC_IS_TRACKED不再在第三方库中可用(被公开 API 取代)
  • python-pyflakes: Python 语法解析报告的列位置似乎不太对,应该是受新的语法解析器的影响
  • python-pylint: Python 语法解析报告的列位置似乎不太对,应该是受新的语法解析器的影响
  • python-typing_inspect: 使用私有名称 typing._GenericAlias,结果新版本变成了 typing._SpecialGenericAlias
  • python-sphinx-autodoc-typehints: 看上去是类型标注相关的内部更改移除了 typing.Dict.__parameters__ 属性造成的
  • python-fastnumbers: 看上去是内部函数 _Py_dg_stdnan 不再被默认包含导致的问题
  • python-libcst: 类型标注相关的内部更改移除了 typing.Dict.__args__ 属性造成的
  • monkeytype: typing.Dict 的类型从 type 变成了 typing._SpecialGenericAlias
  • scrapy: 由于 typing.Optional[str] 的字符串表示由 typing.Union[str, NoneType] 变成了 typing.Optional[str] 导致 mitmproxy 运行出错,进而使得 scrapy 的测试失败
  • python-billiard: 调用的私有方法 _posixsubprocess.fork_exec 参数发生了变化
  • python-pytest-benchmark: argparse 的帮助信息格式有优化
  • python-opentracing: 自 3.7 起废弃的 asyncio.Task.current_task 被移除
  • python-engineio: 自 3.7 起废弃的 asyncio.Task.all_tasks 被移除
  • impacket: 自 3.2 起废弃的 array.array.tostring() 被移除
  • python-pybtex: 自 3.2 起废弃的 xml.etree.ElementTree.Element.getchildren 被移除
  • python-jsonpickle: 自 3.1 起废弃的 base64.decodestring 被移除
  • python-ioflo: 自 3.1 起废弃的 json.loads() 参数 encoding 被移除
  • routersploit: 自 Python 3 起废弃的 threading.Thread.isAlive 终于被移除了
  • python-socketpool: 自 Python 3 起废弃的 threading.Thread.isAlive 终于被移除了
  • python-furl: Python 3.9 修正了一处 URL 解析 bug
  • python-stem: Python 3.9 移除了错误的 unittest.mock.__version__
  • python-natsort: Python 的 Unicode 支持更新到了 13.0.0 版本,CHORASMIAN NUMBER ONE 字符被判定为数字,但是测试代码不认识,认为程序出错
  • python-pony: 对新版本的 Python 报不支持的错误
  • python-dephell-pythons: 硬编码 Python 3.9 为未发布的版本,但现在 3.9 已经发布了

而以下项目的测试失败与 Python 3.9 没有直接关系,共 26 个。其中与 Python 生态有关的有 18 个,与其它项目有关的有 4 个,依赖外部信息的有 3 个,包括一个特别搞笑的依赖夏令时是否生效的。

  • python-eventlet: 调用的 dnspython 私有方法已不存在;DNS 解析超时
  • python-markdown2: 语法高亮的结果有少许变化,不符合预期。推测是 pygments 新版本的变化
  • python-flake8-typing-imports: 似乎是 flake8 能够检测到更多的问题了
  • python-babel: 使用了已废弃的特性,测试被 pytest 拒绝
  • python-pygal: pytest 6.1.0 移除了 Metafunc 的 funcargnames 属性
  • python-flask-gravatar: 使用了已废弃的特性,测试被 pytest 拒绝
  • python-pytest-relaxed: 使用了已废弃的特性,测试被 pytest 拒绝
  • python-pytest-randomly 使用了已废弃的特性,测试被 pytest 拒绝
  • python-deprecated: 测试所预期的警告文本信息已经发生变化
  • python-dbus-signature-pyparsing: 执行时间超过了测试设定的 200ms 时限
  • python-tinycss2: flake8 风格检查未通过
  • python-pytest-runner: black 风格检查未通过
  • python-portend: black 风格检查未通过
  • python-aiohttp: @coroutine 的 DeprecationWarning 被视作错误
  • python-poetry: poetry-core 的一项数据由 dict 改为 OrderedDict,使得输出顺序与测试预期的不一致
  • python-isort: 将使用旧版本 isort 的外部项目的 import 排序视为正确,然后它还真出错了
  • python-cachecontrol: Python 2.7 相关
  • python-zc.lockfile: 测试代码把 Python 3 代码喂给了 Python 2.7。可能是该库已经不支持 2.7 了
  • python-occ-core: 依赖 OpenCASCADE 的版本更新,不被支持
  • protobuf: C 整型比较因表示范围问题而恒为假,警告转错误。是因为新版本的 gcc 比较聪明么?
  • gnome-passwordsafe: 构建系统发现有依赖缺失
  • io: C 代码引用了不存在的系统头文件
  • ceph: C++ 相关问题
  • python-distlib: 调用远程 XML-RPC 太多被限制导致预期的数据与实际错位
  • python-requests-toolbelt: 测试所需要的 HTTP 资源 404 了
  • postgresql: 夏令时结束,导致实际时区与预期对不上。「所以冬天就不要滚包啦,冬天要冬眠!」

所以在这些升级 Python 3.9 的项目中,不兼容 Python 3.9 仅仅只占一半,其中又有一半多属于「总有一天会坏掉」的类型(一大半属于「不听话」,使用没有明确文档、预期为私有的特性,少数尝试当预言家但是失败了)。最后剩下的,再一大半是使用了至少两个版本前已经说了要废弃的特性,只有三个莫名地发现自己真的被 Python 坑了,还都是 C API 部分的。

所以我对我自己的脚本顺利升级到 Python 3.9 非常有信心呢。可能有些老代码使用了已经废弃的特性,所以我也设置了环境变量 PYTHONWARNINGS=default,ignore::ResourceWarning 以便及时得到提示。

哦对了,Arch Linux 中受 Python 3.9 升级影响需要更新的软件包共有2077个,绝大部分我都没见着失败的。目前从开始升级到现在已经过去六天,还剩最后40个失败了的包。

by 依云 at November 16, 2020 01:54 PM

November 02, 2020

中文社区新闻

无障碍(accessible)安装媒介

我们高兴地宣布从 archiso v49 开始我们的安装媒介集成了无障碍功能(accessibility)支持。从 2020.11.01 起,可以从发布的安装媒介中的第二项启动项开启这个特性。在 wiki 上有关于这个的特殊安装指引页

非常感谢 Alexander Epaneshnikov 从 TalkingArch 项目中将相关特性集成到 archiso 的 releng 设置中,我们用它来创建安装媒介。

注意:引导器的超时设置改到了15秒,方便盲人用户选择引导项,因为引导器本身没有提供可用性的相关支持。

by farseerfc at November 02, 2020 12:44 AM

October 29, 2020

百合仙子

让 Arch Linux 系统和最新的镜像同步,从最快的镜像下载

本文来自依云's Blog,转载请注明。

Arch Linux 就是要追新!要追新自然要选择一个更新及时的软件仓库镜像啦,比如国内的 TUNA、USTC 同步都很及时。但是呢,这俩难兄难弟最近一段时间有些吃不消了,导致下载包的时候很慢,甚至超时失败,使用体验真糟糕。如果直接用上游镜像,比如 pkgbuild.com,漂洋过海的,也挺慢的。

而国内另一些镜像,比如网易腾讯云阿里云华为云,他们要么有 CDN,要么线路很好,下载速度飞快。但是呢,他们基本上每天才同步一次,阿里云还时不时连续数天都没能同步成功,这让喜欢追新的 Arch Linux 用户多不舒服呀。当群里的小伙伴们都用上了最新版本的软件,体会到了让人心痒痒的新特性和 bug 时,你 -Syu 却是「今日无事可做」,真是扫兴呢。

和最新的镜像同步,从最快的镜像下载,真的不可兼得吗?

非也。只需要稍微配置一下,用上我的 pacsync 脚本,就可以啦~

配置方式是,为 /etc/pacman.d 下的镜像列表文件创建一个.sync后缀的同名文件,里边指定用于同步的镜像,而不带.sync后缀的文件里按优先级列出多个镜像。pacman 在下载文件时,会按顺序依次尝试列出的镜像,如果遇到更新不及时 404 的时候,就会尝试另一个。这样,可以仅在下载快的镜像里还没有需要的包文件时,才转而从比较慢的镜像下载。

而需要同步 pacman 数据库的时候,使用pacsync脚本取代pacman -Sy。脚本会使用 bind mount 用.sync文件取代不.sync的版本,就能同步到最新的数据库了。原来的pacman -Syu命令要拆开来用,先pacsyncpacman -Su了。

脚本里使用了单独的挂载空间并且将挂载改为了私有,所以并不会影响到外边。

by 依云 at October 29, 2020 02:31 PM

tar 归档的权限问题

本文来自依云's Blog,转载请注明。

一次系统升级之后,我的许多 Python 程序突然开始报错:

[...]
  File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2762, in _get_metadata
    for line in self.get_metadata_lines(name):
  File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 1415, in get_metadata_lines
    return yield_lines(self.get_metadata(name))
  File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 1405, in get_metadata
    value = self._get(path)
  File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 1609, in _get
    with open(path, 'rb') as stream:
PermissionError: [Errno 13] Permission denied: '/usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/PKG-INFO'

WTF 滚坏了!立即回滚!

回滚完之后,我开始调查这个事件——因为 [archlinuxcn] 的这个包是我管的呀。而且我记得之前也遇到过一次类似的情况,当时没有深究。

检查一下软件包里的文件的权限:

>>> tar tvf python-telethon-1.17.4-1-any.pkg.tar.zst | grep PKG-INFO
-rw------- root/root      3659 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/PKG-INFO
>>> tar tvf python-telethon-1.17.4-1-any.pkg.tar.zst | grep -- ----
-rw------- root/root      3659 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/PKG-INFO
-rw------- root/root     12078 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/SOURCES.txt
-rw------- root/root         1 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/dependency_links.txt
-rw------- root/root        27 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/requires.txt
-rw------- root/root        15 2020-10-24 10:05 usr/lib/python3.8/site-packages/Telethon-1.17.4-py3.8.egg-info/top_level.txt

好奇怪,Telethon 这些包信息文件怎么只让 root 读了呢?

从 PyPI 上下载 Telethon 的原始 tar 归档回来看看,发现最近几个版本里,文件权限全部只有自己可以读(-rw-------),而所有者是 u0_a167/10167。开发者突然在 Android 系统上打包了呢……安装的时候,部分文件的权限被保留了下来(Arch Linux 打包时强烈反对使用 root 权限执行,因此我用 devtools 打包,解包部分自然是普通用户操作的,所有者无法被保留)。

然后我又看了一下之前的版本,哦豁,所有者成开发者的 id 了,但是有三个版本的 pyc 文件,还有好几个 pyc 文件都是 -rwxrwxrwx。大概系统上的低权限用户可以去改改,然后看谁跑 Telegram 机器人就拿谁的权限?

经过跟开发者的讨论,最终干掉了 pyc 文件,也不在 Android 上打包了。777 权限问题还待解决。不过我更在意的是,为什么会发生这种状况呢?setuptools 干嘛不修一修呢?别的工具创建的用于发布的 tar 归档会不会有类似的问题呢?

结果找了找,发现 setuptools 前年就有人报告这个问题,但是并没有解决。行吧,我打包时统一修正一下权限好了……

下一个 GitHub 生成的 tar 归档看看?咦,-rw-rw-r-- root/root,是处理过了么?啊对,git archive 生成的包是怎么样的?去试了试,原来一样的啊。看来 git 想到了这个问题并且处理了,只是 002 的 umask 有点意外。

Arch Linux 为了普通用户打出文件为 root 所有的 tar 归档使用了 fakeroot,那么 git 是怎么实现的呢?翻了翻代码,git 是自己生成 tar 文件的,写死了所有者是 root/root,但是权限位还是有专门的 umask,默认是 002。可以配置,比如git config --global tar.umask user一下,就会取当前 umask 作为 tar 归档里文件的 umask 了。

至于传统的 GNU autotools 构建系统创建的 tar 归档,我也创建了一个看了一下,并没有特殊处理,跟手动跑 tar 一样。

by 依云 at October 29, 2020 02:01 PM

October 27, 2020

berberman

Setting up a Haskell development environment on Arch Linux

Posted on October 27, 2020 by berberman

Once you accept the principles of Arch Linux – being simplicity and modernity – everything goes easier. In this article, we will use up-to-date Haskell ecosystem by using system provided Haskell packages, getting rid of awkward stack which could eat huge amount of your disk space. We won’t going to nix or ghcup, since they are both general Haskell toolchain solutions, not specific to Arch Linux.

Preface

If you get pandoc, shellcheck, or other Haskell programs installed on your system, you will find that a bunch of packages with haskell- prefix emerge frequently when rolling the system, which is pretty verbose and noisy. Thus, many of general users, i.e. not Haskell developers, always complain that “why every time I tries to roll my system, there are so many Haskell packages to be updated, and wait… What Is Haskell?” Remember that Arch Linux official repositories are not built for Haskell developers, haskell- packages distributed there are only for programs written in Haskell. To save disk space, Haskell executables do not bundle their dependencies, so libraries are stripped into independent packages, consistent with Haskell package management. Other languages’ distributions follow the similar strategy, whereas a vexing problem arise particularly in Haskell packaging. Haskell packages are packaged and linked dynamically, but GHC does not provide a stable ABI, since its specific hash method acting on circular dependences, which lie ubiquitously involving tests, will cause the soname of shared libraries interdependent. Consequently, you may notice that the entire Haskell packages in [community] are not reproducible. If a Haskell library changes, all dependent packages are required to be rebuilt. For us, it is inevitable to rebuild and reinstall all tools which depend on those shared libraries after updating haskell- packages. Some users choose to avoid getting involved this sort of cheating, using static version Haskell programs as alternative. However, as a Haskell developer, we can make full use of these shared libraries.

Configure Cabal

We will use Cabal without sandboxes as our build tool, and system level GHC as our compiler. Let’s install them via system package management tool:

# pacman -S ghc cabal-install

Generate the configuration and update hackage index as normal:

$ cabal update

Cabal are able to find system Haskell packages installed by pacman. If you try to use Cabal directly to compiling your project now, you will get:

Could not find module ‘Prelude’…

indicating that many packages which would come with GHC are missing. This is because currently system Haskell packages provide only dynamic linked shared libraries, which can be used only when GHC is running in dynamic. So we have to configure ~/.cabal/config as following:

library-vanilla: False
shared: True
executable-dynamic: True
program-default-options
  ghc-options: -dynamic

And if we want to install a Haskell program from cabal, we have to run:

cabal install --ghc-options=-dynamic [package to install]

to let Cabal call GHC enabled dynamic linking.

Install the language server

Personally, I would recommend haskell-language-server, which is active in developing and provides unprecedented coding experience. Because we choose using dynamic GHC, we have to compile HLS by ourselves with dynamic option. Clone the source code:

$ git clone https://github.com/haskell/haskell-language-server --recurse-submodules
$ cd haskell-language-server

IMPORTANT: Configure the HLS project locally:

$ cabal configure --disable-library-vanilla --enable-shared --enable-executable-dynamic --ghc-options=-dynamic

Finally install it:

$ ./cabal-hls-install latest

Next step is choosing your favorite editor, and installing following the instruction in HLS. That’s it, happy coding!

Conclusion

Indeed, we have encountered the first impediment in installing HLS. Using dynamic GHC with system Haskell packages is double-edged, suggesting that we have to face various lurking issues.

Pros:

  • it’s impossible to get stuck into dependency hell (we always use the latest Haskell packages)
  • far less disk usage is required

Cons:

  • out-of-date packages are not available (so there will be slightly fewer libraries we can use)
  • programs involving GHC should be given special treatment (make sure GHC is called with dynamic flag)

The last is kind of troublesome, because maintainers should patch the source of those programs to let them call GHC properly. Here are some examples:

Overall, it seems that we’d better don’t touch these dynamic things in Haskell developing… Anyway, I’m posting this to illustrate that working with haskell- packages is possible. It is worth mentioning that all Haskell packages, and even three tenths of entire Arch Linux packages are maintaining by felixonmars individually, who is dedicated to these unpaid contributions. As an Arch Linux user, I would like to express my high respects to his greatest professionalism and responsibility. Next time, I will introduce my Haskell packaging tool arch-hs, which can be used by both Arch Linux Haskell packagers and Haskell developers.

October 27, 2020 12:00 AM

Setting up a Haskell development environment on Arch Linux

Posted on October 27, 2020 by berberman

Once you accept the principles of Arch Linux – being simplicity and modernity – everything goes easier. In this article, we will use up-to-date Haskell ecosystem by using system provided Haskell packages, getting rid of awkward stack which could eat huge amount of your disk space. We won’t going to nix or ghcup, since they are both general Haskell toolchain solutions, not specific to Arch Linux.

Preface

If you get pandoc, shellcheck, or other Haskell programs installed on your system, you will find that a bunch of packages with haskell- prefix emerge frequently when rolling the system, which is pretty verbose and noisy. Thus, many of general users, i.e. not Haskell developers, always complain that “why every time I tries to roll my system, there are so many Haskell packages to be updated, and wait… What Is Haskell?” Remember that Arch Linux official repositories are not built for Haskell developers, haskell- packages distributed there are only for programs written in Haskell. To save disk space, Haskell executables do not bundle their dependencies, so libraries are stripped into independent packages, consistent with Haskell package management. Other languages’ distributions follow the similar strategy, whereas a vexing problem arise particularly in Haskell packaging. Haskell packages are packaged and linked dynamically, but GHC does not provide a stable ABI, since its specific hash method acting on circular dependences, which lie ubiquitously involving tests, will cause the soname of shared libraries interdependent. Consequently, you may notice that the entire Haskell packages in [community] are not reproducible. If a Haskell library changes, all dependent packages are required to be rebuilt. For us, it is inevitable to rebuild and reinstall all tools which depend on those shared libraries after updating haskell- packages. Some users choose to avoid getting involved this sort of cheating, using static version Haskell programs as alternative. However, as a Haskell developer, we can make full use of these shared libraries.

Configure Cabal

We will use Cabal without sandboxes as our build tool, and system level GHC as our compiler. Let’s install them via system package management tool:

# pacman -S ghc cabal-install

Generate the configuration and update hackage index as normal:

$ cabal update

Cabal are able to find system Haskell packages installed by pacman. If you try to use Cabal directly to compiling your project now, you will get:

Could not find module ‘Prelude’…

indicating that many packages which would come with GHC are missing. This is because currently system Haskell packages provide only dynamic linked shared libraries, which can be used only when GHC is running in dynamic. So we have to configure ~/.cabal/config as following:

library-vanilla: False
shared: True
executable-dynamic: True
program-default-options
  ghc-options: -dynamic

And if we want to install a Haskell program from cabal, we have to run:

cabal install --ghc-options=-dynamic [package to install]

to let Cabal call GHC enabled dynamic linking.

Install the language server

Personally, I would recommend haskell-language-server, which is active in developing and provide unprecedented coding experience. Because we choose using dynamic GHC, we have to compile HLS by ourselves with dynamic option. Clone the source code:

$ git clone https://github.com/haskell/haskell-language-server --recurse-submodules
$ cd haskell-language-server

IMPORTANT: Configure the HLS project locally:

$ cabal configure --disable-library-vanilla --enable-shared --enable-executable-dynamic --ghc-options=-dynamic

Finally install it:

$ ./cabal-hls-install latest

Next step is choosing your favorite editor, and installing following the instruction in HLS. That’s it, happy coding!

Conclusion

Indeed, we have encountered the first impediment in installing HLS. Using dynamic GHC with system Haskell packages is double-edged, suggesting that we have to face various lurking issues.

Pros:

  • it’s impossible to get stuck into dependency hell (we always use the latest Haskell packages)
  • far less disk usage is required

Cons:

  • out-of-date packages are not available (so there will be slightly fewer libraries we can use)
  • programs involving GHC should be given special treatment (make sure GHC is called with dynamic flag)

The last is kind of troublesome, because maintainers should patch the source of those programs to let them call GHC properly. Here are some examples:

Overall, it seems that we’d better don’t touch these dynamic things in Haskell developing… Anyway, I’m posting this to illustrate that working with haskell- packages is possible. It is worth mentioning that all Haskell packages, and even a half of entire Arch Linux packages are maintaining by felixonmars individually, who is dedicated to these unpaid contributions. As an Arch Linux user, I would like to express my high respects to his greatest professionalism and responsibility. Next time, I will introduce my Haskell packaging tool arch-hs, which can be used by both Arch Linux Haskell packagers and Haskell developers.

October 27, 2020 12:00 AM

October 24, 2020

中文社区新闻

libtraceevent>=5.9-1 升级需要手动干预

libtraceevent 包在版本 5.9-1 之前缺失了一个动态库链接。这个问题已经在 5.9-1 中修复,所以更新时需要覆盖 ldconfig 创建出的未被跟踪到的文件。如果你在升级时遇到如下报错:

libtraceevent: /usr/lib/libtraceevent.so.1 exists in filesystem

那么请使用命令:

pacman -Syu --overwrite /usr/lib/libtraceevent.so.1

完成更新。

by farseerfc at October 24, 2020 03:21 AM

October 21, 2020

中文社区新闻

nvidia 455.28 与 linux >= 5.9 不兼容

当前的 nvidia 与 linux >= 5.9 内核部分程度不兼容 [1] [2] 。虽然图形显示应该还能工作,但是搞坏了 CUDA 、 OpenCL 以及可能还有别的一些特性。建议需要这些特性而已经升级了内核的用户切换到 linux-lts 内核,直到 nvidia 提供修复。

by farseerfc at October 21, 2020 04:46 AM

October 06, 2020

Alynx Zhou

奇怪的书名和我和吐槽

好久没更新非技术类博文了。可能很多人不但丢掉了阅读的习惯,连书店都不去了,书店都沦为练习册店,学生们更加对书店敬而远之,这种恶性循环实在是可悲又无趣的事情。

今天和同学一起去了书店,在常规意义的书店而非练习册店的部分发现了一些有趣或奇怪的书名,很有槽点,让人想要吐槽。于是拍了点照片发到这里。

套娃型

此类书名非常适合套娃,比如这本《如何阅读一本书》,且不说阅读方法因人而异且随经验变化,单就这个名字还可以有《如何阅读如何阅读一本书》、《如何阅读如何阅读如何阅读一本书》。

如何阅读一本书

我以为只有当代为搏销量疯狂吸引眼球的作者会起这种名字,然而接下来我看到了一本《木心谈木心》。虽然我不太清楚木心是谁,但似乎是个有名的作者。这书名总是让我想后续会不会有《木心谈木心谈木心》、《木心谈木心谈木心谈木心》。

木心谈木心

性转型

这本书的名字非常奇特,她叫做《少女福尔摩斯》,作为半资深福尔摩斯迷和半资深月球氪金母X,我只能说型月都没敢做的这作者做了。由于原作实在是珠玉在前,写老福的故事已经很难了,性转老福怕是难上加难,建议寄一本给蘑菇看看。

少女福尔摩斯

蹭热度型

这位叫蒋勋的作者在书架上占据了一片,又是说唐诗又是说宋词,一会谈莫奈一会谈高达哦不是梵高和达芬奇,想必上知天文下知地理。不过这书脊把这些名人加粗放在下面,让我总是有一种这是个达芬奇/梵高/莫奈的著作、作品标题叫做蒋勋的感觉。 不知道这个叫蒋勋的作品是不是样样通样样松呢?

蒋勋

还有这本《逝水年华》,连我的输入法都怀疑我是不是想输入《追忆似水年华》,敢问作者是不是有点大舌头,让出版社的编辑听错了标题?

逝水年华

连锁反应型

《知更鸟》吗?大家都说有本书想杀你,不知道与你是什么关系?

知更鸟

虽然《重返美丽新世界》和《美丽新世界》是一个作者,但总让我想起狗尾续貂,不知道是不是我错了。

美丽新世界

好家伙,你把《24个比利》放在这,那他们肯定会打架的,不过叫《比利打群架》比《比利战争》更好吧,无谓夸大不可取,要实事求是。

比利

《面包树上的女人》怎么啦?她没怎么,《面包树出走了》,那可真是个离奇的故事,但是你这内容就不知道是不是好故事了。

面包树

我以为文学大家不屑于做这种在标题上吸引人的无聊事情,毕竟内容才是王道。我也不是有意冒犯杨绛先生,但是《洗澡》做书名已经是太随意了,再整一本《洗澡之后》是不是过分了?还是说这是出书的不负责任的编辑洗澡前洗澡后决定的两本文集的名字?那这就说得通了。但是说实话《洗澡》、《洗澡之后》接下来摆一本《暗示》是不是店员的恶趣味?

洗澡

希望看了我文章的朋友不要给我评论“一看到白胳膊……”,我老实承认我不是一个完全脱离了低级趣味的人,写这文章也是让大家开心一下,毕竟实事求是更重要。我也希望写书的作者们都实事求是一点,内容不行,靠标题党吸引人肯定是经不起考验的。

Alynx Zhou

A Coder & Dreamer

by Alynx Zhou (alynx.zhou@gmail.com) at October 06, 2020 09:48 AM

farseerfc

关于 swap 的一些补充

上周翻译完 【译】替 swap 辩护:常见的误解 之后很多朋友们似乎还有些疑问和误解,于是写篇后续澄清一下。事先声明我不是内核开发者, 这里说的只是我的理解, 基于内核文档中关于物理内存的描述 ,新的内核代码的具体行为可能和我的理解有所出入,欢迎踊跃讨论。

误解1: swap 是虚拟内存,虚拟内存肯定比物理内存慢嘛

这种误解进一步的结论通常是:「使用虚拟内存肯定会减慢系统运行时性能,如果物理内存足够为什么还要用虚拟的?」 这种误解是把虚拟内存和交换区的实现方式类比于「虚拟磁盘」或者「虚拟机」等同的方式, 也隐含「先用物理内存,用完了之后用虚拟内存」也即下面的「误解3」的理解。

首先,交换区(swap) 不是 虚拟内存。操作系统中说「物理内存」还是「虚拟内存」的时候在指程序代码 寻址时使用的内存地址方式,使用物理地址空间时是在访问物理内存,使用虚拟地址空间时是在访问虚拟内存。 现代操作系统在大部分情况下都在使用虚拟地址空间寻址, 包括 在执行内核代码的时候。

并且,交换区 不是 实现虚拟内存的方式。操作系统使用内存管理单元(MMU,Memory Management Unit)做虚拟内存地址到物理内存地址的地址翻译,现代架构下 MMU 通常是 CPU 的一部分,配有它专用的一小块存储区叫做地址转换旁路缓存(TLB,Translation Lookaside Buffer), 只有在 TLB 中没有相关地址翻译信息的时候 MMU 才会以缺页中断的形式调用操作系统内核帮忙。 除了 TLB 信息不足的时候,大部分情况下使用虚拟内存都是硬件直接实现的地址翻译,没有软件模拟开销。 实现虚拟内存不需要用到交换区,交换区只是操作系统实现虚拟内存后能提供的一个附加功能, 即便没有交换区,操作系统大部分时候也在用虚拟内存,包括在大部分内核代码中。

误解2: 但是没有交换区的话,虚拟内存地址都有物理内存对应嘛

很多朋友也理解上述操作系统实现虚拟内存的方式,但是仍然会有疑问:「我知道虚拟内存和交换区的区别, 但是没有交换区的话,虚拟内存地址都有物理内存对应,不用交换区的话就不会遇到读虚拟内存需要读写磁盘 导致的卡顿了嘛」。

这种理解也是错的,禁用交换区的时候,也会有一部分分配给程序的虚拟内存不对应物理内存, 比如使用 mmap 调用实现内存映射文件的时候。实际上即便是使用 read/​write 读写文件, Linux 内核中(可能现代操作系统内核都)在底下是用和 mmap 相同的机制建立文件 到虚拟地址空间的地址映射,然后实际读写到虚拟地址时靠缺页中断把文件内容载入页面缓存(page cache )。内核加载可执行程序和动态链接库的方式也是通过内存映射文件。甚至可以进一步说, 用户空间的虚拟内存地址范围内,除了匿名页之外,其它虚拟地址都是文件后备(backed by file ),而匿名页通过交换区作为文件后备。上篇文章中提到的别的类型的内存,比如共享内存页面(shm )是被一个内存中的虚拟文件系统后备的,这一点有些套娃先暂且不提。于是事实是无论有没有交换区, 缺页的时候总会有磁盘读写从慢速存储加载到物理内存,这进一步引出上篇文章中对于交换区和页面缓存这两者的讨论。

误解3: 不是内存快用完的时候才会交换的么?

简短的答案可以说「是」,但是内核理解的「内存快用完」和你理解的很可能不同。 也可以说「不是」,就算按照内核理解的「内存快用完」的定义,内存快用完的时候内核的行为是去回收内存, 至于回收内存的时候内核会做什么有个复杂的启发式经验算法,实际上真的内存快满的时候根本来不及做 swap ,内核可能会尝试丢弃 page cache 甚至丢弃 vfs cache (dentry cache / inode cache) 这些不需要磁盘I/O就能更快获取可用内存的动作。

深究这些内核机制之前,我在思考为什么很多朋友会问出这样的问题。可能大部分这么问的人,学过编程, 稍微学过基本的操作系统原理,在脑海里对内核分配页面留着这样一种印象(C伪代码):

////////////////////  userspace space  ////////////////
void* malloc(int size){
    void* pages = mmap(...);                                    // 从内核分配内存页
    return alloc_from_page(pages, size);                        // 从拿到的内存页细分
}

////////////////////  kernel space  //////////////////
void * SYSCALL do_mmap(...){
   //...
   return kmalloc_pages(nr_page);
}

void* kmalloc_pages(int size){
  while ( available_mem < size ) {
    // 可用内存不够了!尝试搞点内存
    page_frame_info* least_accessed = lru_pop_page_frame();     // 找出最少访问的页面
    switch ( least_accessed -> pf_type ){
      case PAGE_CACHE: drop_page_cache(least_accessed); break;  // 丢弃文件缓存
      case SWAP:       swap_out(least_accessed);        break;  // <- 写磁盘,所以系统卡了!
      // ... 别的方式回收 least_accessed
    }
    append_free_page(free_page_list, least_accessed);           // 回收到的页面加入可用列表
    available_mem += least_accessed -> size;
  }
  // 搞到内存了!返回给程序
  available_mem -= size;
  void * phy_addr = take_from_free_list(free_page_list, size);
  return assign_virtual_addr(phy_addr);
}

这种逻辑隐含三层 错误的 假设:

  1. 分配物理内存是发生在从内核分配内存的时候的,比如 malloc/​mmap 的时候。
  2. 内存回收是发生在进程请求内存分配的上下文里的,换句话说进程在等内核的内存回收返回内存, 不回收到内存,进程就得不到内存。
  3. 交换出内存到 swap 是发生在内存回收的时候的,会阻塞内核的内存回收,进而阻塞程序的内存分配。

这种把内核代码当作「具有特权的库函数调用」的看法,可能很易于理解, 甚至早期可能的确有操作系统的内核是这么实现的,但是很可惜现代操作系统都不是这么做的。 上面三层假设的错误之处在于:

  1. 在程序请求内存的时候,比如 malloc/​mmap 的时候,内核只做虚拟地址分配, 记录下某段虚拟地址空间对这个程序是可以合法访问的,但是不实际分配物理内存给程序。 在程序第一次访问到虚拟地址的时候,才会实际分配物理内存。这种叫 惰性分配(lazy allocation)
  2. 在内核感受到内存分配压力之后,早在内核内存用尽之前,内核就会在后台慢慢扫描并回收内存页。 内存回收通常不发生在内存分配的时候,除非在内存非常短缺的情况下,后台内存回收来不及满足当前分配请求, 才会发生 直接回收(direct reclamation)
  3. 同样除了直接回收的情况,大部分正常情况下换出页面是内存管理子系统调用 DMA 在后台慢慢做的, 交换页面出去不会阻塞内核的内存回收,更不会阻塞程序做内存分配(malloc )和使用内存(实际访问惰性分配的内存页)。

也就是说,现代操作系统内核是高度并行化的设计,内存分配方方面面需要消耗计算资源或者 I/O 带宽的场景,都会尽量并行化,最大程度利用好计算机所有组件(CPU/MMU/DMA/IO)的吞吐率, 不到紧要关头需要直接回收的场合,就不会阻塞程序的正常执行流程。

惰性分配有什么好处?

或许会有人问:「我让你分配内存,你给我分配了个虚拟的,到用的时候还要做很多事情才能给我,这不是骗人嘛」, 或者会有人担心惰性分配会对性能造成负面影响。

这里实际情况是程序从分配虚拟内存的时候,「到用的时候」,这之间有段时间间隔,可以留给内核做准备 。程序可能一下子分配一大片内存地址,然后再在执行过程中解析数据慢慢往地址范围内写东西。 程序分配虚拟内存的速率可以是「突发」的,比如一个系统调用中分配 1GiB 大小,而实际写入数据的速率会被 CPU 执行速度等因素限制,不会短期内突然写入很多页面。 这个分配速率导致的时间差内内核可以完成很多后台工作,比如回收内存, 比如把回收到的别的进程用过的内存页面初始化为全0,这部分后台工作可以和程序的执行过程并行, 从而当程序实际用到内存的时候,需要的准备工作已经做完了,大部分场景下可以直接分配物理内存出来。

如果程序要做实时响应,想避免因为惰性分配造成的性能不稳定,可以使用 mlock/​mlockall 将得到的虚拟内存锁定在物理内存中,锁的过程中内核会做物理内存分配。不过要区分「性能不稳定」和「低性能」, 预先分配内存可以避免实际使用内存时分配物理页面的额外开销,但是会拖慢整体吞吐率,所以要谨慎使用。

很多程序分配了很大一片地址空间,但是实际并不会用完这些地址,直到程序执行结束这些虚拟地址也一直 处于没有对应物理地址的情况。惰性分配可以避免为这些情况浪费物理内存页面,使得很多程序可以无忧无虑地 随意分配内存地址而不用担心性能损失。这种分配方式也叫「超额分配(overcommit)」。飞机票有超售, VPS 提供商划分虚拟机有超售,操作系统管理内存也同样有这种现象,合理使用超额分配能改善整体系统效率。

内核要高效地做到惰性分配而不影响程序执行效率的前提之一,在于程序真的用到内存的时候, 内核能不做太多操作就立刻分配出来,也就是说内核需要时时刻刻在手上留有一部分空页, 满足程序执行时内存分配的需要。换句话说,内核需要早在物理内存用尽之前,就开始回收内存。

那么内核什么时候会开始回收内存?

首先一些背景知识:物理内存地址空间并不是都平等,因为一些地址范围可以做 DMA 而另一些不能,以及 NUMA 等硬件环境倾向于让 CPU 访问其所在 NUMA 节点内存范围。在 32bit 系统上内核的虚拟地址空间还有低端内存和高端内存的区分,他们会倾向于使用不同属性的物理内存,到 64bit 系统上已经没有了这种限制。

硬件限制了内存分配的自由度,于是内核把物理内存空间分成多个 Zone ,每个 Zone 内各自管理可用内存, Zone 内的内存页之间是相互平等的。

zone 内水位线
ditaa diagram

一个 Zone 内的页面分配情况可以右图描绘。 除了已用内存页,剩下的就是空闲页(free pages),空闲页范围中有三个水位线(watermark )评估当前内存压力情况,分别是高位(high)、低位(low)、最小位(min)。

当内存分配使得空闲页水位低于低位线,内核会唤醒 kswapd 后台线程, kswapd 负责扫描物理页面的使用情况并挑选一部分页面做回收,直到可用页面数量恢复到水位线高位(high)以上。 如果 kswapd 回收内存的速度慢于程序执行实际分配内存的速度, 可用空闲页数量可能进一步下降,降至低于最小水位(min)之后,内核会让内存分配进入 直接回收(direct reclamation) 模式,在直接回收模式下,程序分配某个物理页的请求( 第一次访问某个已分配虚拟页面的时候)会导致在进程上下文中阻塞式地调用内存回收代码。

除了内核在后台回收内存,进程也可以主动释放内存,比如有程序退出的时候就会释放一大片内存页, 所以可用页面数量可能会升至水位线高位以上。有太多可用页面浪费资源对整体系统运行效率也不是好事, 所以系统会积极缓存文件读写,所有 page cache 都留在内存中,直到可用页面降至低水位以下触发 kswapd 开始工作。

设置最小水位线(min)的原因在于,内核中有些硬件也会突然请求大量内存,比如来自网卡接收到的数据包, 预留出最小水位线以下的内存给内核内部和硬件使用。

设置高低两个控制 kswapd 开关的水位线是基于控制理论。唤醒 kswapd 扫描内存页面本身有一定计算开销,于是每次唤醒它干活的话就让它多做一些活( high - low ),避免频繁多次唤醒。

因为有这些水位线,系统中根据程序请求内存的「速率」,整个系统的内存分配在宏观的一段时间内可能处于以下几种状态:

  1. 不回收: 系统中的程序申请内存速度很慢,或者程序主动释放内存的速度很快, (比如程序执行时间很短,不怎么进行文件读写就马上退出,)此时可用页面数量可能一直处于低水位线以上, 内核不会主动回收内存,所有文件读写都会以页面缓存的形式留在物理内存中。
  2. 后台回收: 系统中的程序在缓慢申请内存,比如做文件读写, 比如分配并使用匿名页面。系统会时不时地唤醒 kswapd 在后台做内存回收, 不会干扰到程序的执行效率。
  3. 直接回收: 如果程序申请内存的速度快于 kswapd 后台回收内存的速度, 空闲内存最终会跌破最小水位线,随后的内存申请会进入直接回收的代码路径,从而极大限制内存分配速度。 在直接分配和后台回收的同时作用下,空闲内存可能会时不时回到最小水位线以上, 但是如果程序继续申请内存,空闲内存量就会在最小水位线附近上下徘徊。
  4. 杀进程回收: 甚至直接分配和后台回收的同时作用也不足以拖慢程序分配内存的速度的时候, 最终空闲内存会完全用完,此时触发 OOM 杀手干活杀进程。

系统状态处于 1. 不回收 的时候表明分配给系统的内存量过多,比如系统刚刚启动之类的时候。 理想上应该让系统长期处于 2. 后台回收 的状态,此时最大化利用缓存的效率而又不会因为内存回收 减缓程序执行速度。如果系统引导后长期处于 1. 不回收 的状态下,那么说明没有充分利用空闲内存做 文件缓存,有些 unix 服务比如 preload 可用来提前填充文件缓存。

如果系统频繁进入 3. 直接回收 的状态,表明在这种工作负载下系统需要减慢一些内存分配速度, 让 kswapd 有足够时间回收内存。就如前一篇翻译中 Chris 所述,频繁进入这种状态也不一定代表「内存不足」,可能表示内存分配处于非常高效的利用状态下, 系统充分利用慢速的磁盘带宽,为快速的内存缓存提供足够的可用空间。 直接回收 是否对进程负载有负面影响要看具体负载的特性。 此时选择禁用 swap 并不能降低磁盘I/O,反而可能缩短 2. 后台回收 状态能持续的时间, 导致更快进入 4. 杀进程回收 的极端状态。

当然如果系统长期处于 直接回收 的状态的话,则说明内存总量不足,需要考虑增加物理内存, 或者减少系统负载了。如果系统进入 4. 杀进程回收 的状态,不光用空间的进程会受影响, 并且还可能导致内核态的内存分配受影响,产生网络丢包之类的结果。

微调内存管理水位线

可以看一下运行中的系统中每个 Zone 的水位线在哪儿。比如我手上这个 16GiB 的系统中:

$ cat /proc/zoneinfo
Node 0, zone      DMA
   pages free     3459
         min      16
         low      20
         high     24
         spanned  4095
         present  3997
         managed  3975
Node 0, zone    DMA32
   pages free     225265
         min      3140
         low      3925
         high     4710
         spanned  1044480
         present  780044
         managed  763629
Node 0, zone   Normal
   pages free     300413
         min      13739
         low      17173
         high     20607
         spanned  3407872
         present  3407872
         managed  3328410

因为不是 NUMA 系统,所以只有一个 NUMA node,其中根据 DMA 类型共有 3 个 Zone 分别叫 DMA, DMA32, Normal 。三个 Zone 的物理地址范围(spanned)加起来大概有 \(4095+1044480+3407872\) 大约 17GiB 的地址空间,而实际可访问的地址范围(present )加起来有 \(3997+780044+3407872\) 大约 16GiB 的可访问物理内存。

其中空闲页面有 \(3459+762569+1460218\) 大约 8.5GiB ,三条水位线分别在: \(\texttt{high} = 24+4710+20607 = 98\texttt{MiB}\)\(\texttt{low} = 20+3925+17173 = 82\texttt{MiB}\)\(\texttt{min} = 16+3140+13739 = 65\texttt{MiB}\) 的位置。

具体这些水位线的确定方式基于几个 sysctl 。首先 min 基于 vm.min_free_kbytes 默认是基于内核低端内存量的平方根算的值,并限制到最大 64MiB 再加点余量,比如我这台机器上 vm.min_free_kbytes = 67584 ,于是 min 水位线在这个位置。 其它两个水位线基于这个计算,在 min 基础上增加总内存量的 vm.watermark_scale_factor /​ 10000 比例(在小内存的系统上还有额外考虑),默认 vm.watermark_scale_factor = 10 在大内存系统上意味着 low 比 min 高 0.1% , high 比 low 高 0.1% 。

可以手动设置这些值,以更早触发内存回收,比如将 vm.watermark_scale_factor 设为 100:

$ echo 100 | sudo tee /proc/sys/vm/watermark_scale_factor
$ cat /proc/zoneinfo
Node 0, zone      DMA
   pages free     3459
         min      16
         low      55
         high     94
         spanned  4095
         present  3997
         managed  3975
   Node 0, zone    DMA32
   pages free     101987
         min      3149
         low      10785
         high     18421
         spanned  1044480
         present  780044
         managed  763629
   Node 0, zone   Normal
   pages free     61987
         min      13729
         low      47013
         high     80297
         spanned  3407872
         present  3407872
         managed  3328410

得到的三条水位线分别在 \(\texttt{min} = 16+3149+13729 = 66\texttt{MiB}\)\(\texttt{low} = 55+10785+47013 = 226\texttt{MiB}\)\(\texttt{high} = 94+18421+80297 = 386\texttt{MiB}\) , 从而 low 和 high 分别比 min 提高 160MiB 也就是内存总量的 1% 左右。

在 swap 放在 HDD 的系统中,因为换页出去的速度较慢,除了上篇文章说的降低 vm.swappiness 之外,还可以适当提高 vm.watermark_scale_factor 让内核更早开始回收内存,这虽然会稍微降低缓存命中率,但是另一方面可以在进入直接回收模式之前 有更多时间做后台换页,也将有助于改善系统整体流畅度。

只有 0.1% ,这不就是说内存快用完的时候么?

所以之前的「误解3」我说答案可以说「是」或者「不是」,但是无论回答是或不是,都代表了认为「swap 就是额外的慢速内存」的错误看法。当有人在强调「swap 是内存快用完的时候才交换」的时候, 隐含地,是在把系统总体的内存分配看作是一个静态的划分过程:打个比方这就像在说,我的系统里存储空间有快速 128GiB SSD 和慢速 HDD 的 1TiB ,同样内存有快速的 16GiB RAM 和慢速 16GiB 的 swap 。 这种静态划分的类比是错误的看待方式,因为系统回收内存进而做页面交换的方式是动态平衡的过程, 需要考虑到「时间」和「速率」而非单纯看「容量」。

假设 swap 所在的存储设备可以支持 5MiB/s 的吞吐率( HDD 上可能更慢, SSD 上可能更快,这里需要关注数量级),相比之下 DDR3 大概有 10GiB/s 的吞吐率,DDR4 大概有 20GiB/s ,无论多快的 SSD 也远达不到这样的吞吐(可能 Intel Optane 这样的 DAX 设备会改变这里的状况)。从而把 swap 当作慢速内存的视角来看的话,加权平均的速率是非常悲观的,「 16G 的 DDR3 + 16G 的 swap 会有 \(\frac{16 \times 10 \times 1024 + 16 \times 5}{16+16} = 5 \texttt{GiB/s}\) 的吞吐?所以开 swap 导致系统速度降了一半?」显然不能这样看待。

动态的看待方式是, swap 设备能提供 5MiB/s 的吞吐,这意味着:如果我们能把未来 10 分钟内不会访问到的页面换出到 swap ,那么就相当于有 \(10 \times 60 \texttt{s} \times 5 \texttt{MiB/s} = 3000 \texttt{MiB}\) 的额外内存,用来放那 10 分钟内可能会访问到的页面缓存。 10 分钟只是随口说的一段时间,可以换成 10 秒或者 10 小时,重要的是只要页面交换发生在后台, 不阻塞前台程序的执行,那么 swap 设备提供的额外吞吐率相当于一段时间内提供了更大的物理内存, 总是能提升页面缓存的命中,从而改善系统性能。

当然系统内核不能预知「未来 10 分钟内需要的页面」,只能根据历史上访问内存的情况预估之后可能会访问的情况, 估算不准的情况下,比如最近10分钟内用过的页面缓存在之后10分钟内不再被使用的时候, 为了把最近这10分钟内访问过的页面留在物理内存中,可能会把之后10分钟内要用到的匿名页面换出到了交换设备上。 于是会有下面的情况:

但是我开了 swap 之后,一旦复制大文件,系统就变卡,不开 swap 不会这样的

大概电脑用户都经历过这种现象,不限于 Linux 用户,包括 macOS 和 Windows 上也是。 在文件管理器中复制了几个大文件之后,切换到别的程序系统就极其卡顿,复制已经结束之后的一段时间也会如此。 复制的过程中系统交换区的使用率在上涨,复制结束后下降,显然 swap 在其中有重要因素,并且禁用 swap 或者调低 swappiness 之后就不会这样了。于是网上大量流传着解释这一现象,并进一步建议禁用 swap 或者调低 swappiness 的文章。我相信不少关心系统性能调优的人看过这篇「 Tales from responsivenessland: why Linux feels slow, and how to fix that 」或是它的转载、翻译,用中文搜索的话还能找到更多错误解释 swappiness 目的的文章,比如 这篇将 swappiness 解释成是控制内存和交换区比例的参数

除去那些有技术上谬误的文章,这些网文中描述的现象是有道理的,不单纯是以讹传讹。 桌面环境中内存分配策略的不确定性和服务器环境中很不一样,复制、下载、解压大文件等导致一段时间内 大量占用页面缓存,以至于把操作结束后需要的页面撵出物理内存,无论是交换出去的方式还是以丢弃页面缓存的方式, 都会导致桌面响应性降低。

不过就像前文 Chris 所述,这种现象其实并不能通过禁止 swap 的方式缓解:禁止 swap 或者调整 swappiness 让系统尽量避免 swap 只影响回收匿名页面的策略,不影响系统回收页面的时机, 也不能避免系统丢弃将要使用的页面缓存而导致的卡顿。

以前在 Linux 上也没有什么好方法能避免这种现象。 macOS 转用 APFS 作为默认文件系统之后, 从文件管理器(Finder)复制文件默认启用 file clone 快速完成,这操作不实际复制文件数据, 一个隐含优势在不需要读入文件内容,从而不会导致大量页面缓存失效。 Linux 上同样可以用支持 reflink 的文件系统比如 btrfs 或者开了 reflink=1 的 xfs 达到类似的效果。 不过 reflink 也只能拯救复制文件的情况,不能改善解压文件、下载文件、计算文件校验等情况下, 一次性处理大文件对内存产生的压力。

好在最近几年 Linux 有了 cgroup ,允许更细粒度地调整系统资源分配。进一步现在我们有了 cgroup v2 ,前面 Chris 的文章也有提到 cgroup v2 的 memory.low 可以某种程度上建议内存子系统 尽量避免回收某些 cgroup 进程的内存。

于是有了 cgroup 之后,另一种思路是把复制文件等大量使用内存而之后又不需要保留页面缓存的程序单独放入 cgroup 内限制它的内存用量,用一点点复制文件时的性能损失换来整体系统的响应流畅度。

关于 cgroup v1 和 v2

稍微跑题说一下 cgroup v2 相对于 v1 带来的优势。这方面优势在 Chris Down 另一个关于 cgroup v2 演讲 中有提到。老 cgroup v1 按控制器区分 cgroup 层级,从而内存控制器所限制的东西和 IO 控制器所限制的东西是独立的。在内核角度来看,页面写回(page writeback)和交换(swap)正是 夹在内存控制器和IO控制器管理的边界上,从而用 v1 的 cgroup 难以同时管理。 v2 通过统一控制器层级解决了这方面限制。具体见下面 Chris Down 的演讲。

用 cgroup v2 限制进程的内存分配

实际上有了 cgroup v2 之后,还有更多控制内存分配的方案。 cgroup v2 的内存控制器 可以对某个 cgroup 设置这些阈值:

  • memory.min : 最小内存限制。内存用量低于此值后系统不会回收内存。
  • memory.low : 低内存水位。内存用量低于此值后系统会尽量避免回收内存。
  • memory.high : 高内存水位。内存用量高于此值后系统会积极回收内存,并且会对内存分配节流(throttle)。
  • memory.max : 最大内存限制。内存用量高于此值后系统会对内存分配请求返回 ENOMEM,或者在 cgroup 内触发 OOM 。

可见这些设定值可以当作 per-cgroup 的内存分配水位线,作用于某一部分进程而非整个系统。 针对交换区使用情况也可设置这些阈值:

  • memory.swap.high : 高交换区水位,交换区用量高于此值后会对交换区分配节流。
  • memory.swap.max : 最大交换区限制,交换区用量高于此值后不再会发生匿名页交换。

到达这些 cgroup 设定阈值的时候,还可以设置内核回调的处理程序,从用户空间做一些程序相关的操作。

Linux 有了 cgroup v2 之后,就可以通过对某些程序设置内存用量限制,避免他们产生的页面请求把别的 程序所需的页面挤出物理内存。使用 systemd 的系统中,首先需要 启用 cgroup v2 ,在内核引导参数中加上 systemd.unified_cgroup_hierarchy=1 。然后开启用户权限代理:

# systemctl edit user@1000.service
[Service]
Delegate=yes

然后可以定义用户会话的 slice (slice 是 systemd 术语,用来映射 cgroup ),比如创建一个叫 limit-mem 的 slice :

$ cat ~/.config/systemd/user/limit-mem.slice
[Slice]
MemoryHigh=3G
MemoryMax=4G
MemorySwapMax=2G

然后可以用 systemd-run 限制在某个 slice 中打开一个 shell:

$ systemd-run --user --slice=limit-mem.slice --shell

或者定义一个 shell alias 用来限制任意命令:

$ type limit-mem
limit-mem is an alias for /usr/bin/time systemd-run --user --pty --same-dir --wait --collect --slice=limit-mem.slice
$ limit-mem cp some-large-file dest/

实际用法有很多,可以参考 systemd 文档 man systemd.resource-controlxuanwo有篇博客介绍过 systemd 下资源限制lilydjwg写过用 cgroup 限制进程内存的用法用 cgroup 之后对 CPU 调度的影响

未来展望

最近新版的 gnome 和 KDE 已经开始为桌面环境下用户程序的进程创建 systemd scope 了, 可以通过 systemd-cgls 观察到,每个通过桌面文件(.desktop)开启的用户空间程序 都有个独立的名字叫 app-APPNAME-HASH.scope 之类的 systemd scope 。 有了这些 scope 之后,事实上用户程序的资源分配某种程度上已经相互独立, 不过默认的用户程序没有施加多少限制。

今后可以展望,桌面环境可以提供用户友好的方式对这些桌面程序施加公平性的限制。 不光是内存分配的大小限制,包括 CPU 和 IO 占用方面也会更公平。 值得一提的是传统的 ext4/xfs/f2fs 之类的文件系统虽然支持 cgroup writeback 节流 但是因为他们有额外的 journaling 写入,难以单独针对某些 cgroup 限制 IO 写入带宽(对文件系统元数据的写入难以统计到具体某组进程)。 而 btrfs 通过 CoW 避免了 journaling , 在这方面有更好的支持 。相信不远的将来,复制大文件之类常见普通操作不再需要手动调用加以限制, 就能避免单个程序占用太多资源影响别的程序。

by farseerfc at October 06, 2020 04:45 AM

October 03, 2020

中文社区新闻

ghostpcl>=9.53.2-2 和 ghostxps>=9.53.2-2 升级需要手动干预

ghostpcl 和 ghostxps 包在版本 9.53.2-2 之前各缺失了一个动态库链接。这个问题已经在 9.53.2-2 中修复,所以更新时需要覆盖 ldconfig 创建出的未被跟踪到的文件。如果你在升级时遇到如下报错:

ghostpcl: /usr/lib/libgpcl6.so.9 exists in filesystem
ghostxps: /usr/lib/libgxps.so.9 exists in filesystem

那么请使用命令:

pacman -Syu --overwrite /usr/lib/libgpcl6.so.9,/usr/lib/libgxps.so.9

完成更新。

by farseerfc at October 03, 2020 03:25 PM

September 30, 2020

farseerfc

【译】替 swap 辩护:常见的误解

这篇翻译自 Chris Down 的博文 In defence of swap: common misconceptions原文的协议CC BY-SA 4.0 ,本文翻译同样也使用 CC BY-SA 4.0 。其中加入了一些我自己的理解作为旁注,所有译注都在侧边栏中。

翻译这篇文章是因为经常看到朋友们(包括有经验的程序员和 Linux 管理员)对 swap 和 swappiness 有诸多误解,而这篇文章正好澄清了这些误解,也讲清楚了 Linux 中这两者的本质。值得一提的是本文讨论的 swap 针对 Linux 内核,在别的系统包括 macOS/WinNT 或者 Unix 系统中的交换文件可能有不同一样的行为, 需要不同的调优方式。比如在 FreeBSD handbook 中明确建议了 swap 分区通常应该是两倍物理内存大小,这一点建议对 FreeBSD 系内核的内存管理可能非常合理, 而不一定适合 Linux 内核,FreeBSD 和 Linux 有不同的内存管理方式尤其是 swap 和 page cache 和 buffer cache 的处理方式有诸多不同。

经常有朋友看到系统卡顿之后看系统内存使用状况观察到大量 swap 占用,于是觉得卡顿是来源于 swap 。就像文中所述,相关不蕴含因果,产生内存颠簸之后的确会造成大量 swap 占用,也会造成系统卡顿, 但是 swap 不是导致卡顿的原因,关掉 swap 或者调低 swappiness 并不能阻止卡顿,只会将 swap 造成的 I/O 转化为加载文件缓存造成的 I/O 。

以下是原文翻译:


这篇文章也有 日文俄文 翻译。

tl;dr:

  1. Having swap is a reasonably important part of a well functioning system. Without it, sane memory management becomes harder to achieve.
  2. Swap is not generally about getting emergency memory, it's about making memory reclamation egalitarian and efficient. In fact, using it as "emergency memory" is generally actively harmful.
  3. Disabling swap does not prevent disk I/O from becoming a problem under memory contention, it simply shifts the disk I/O thrashing from anonymous pages to file pages. Not only may this be less efficient, as we have a smaller pool of pages to select from for reclaim, but it may also contribute to getting into this high contention state in the first place.
  4. The swapper on kernels before 4.0 has a lot of pitfalls, and has contributed to a lot of people's negative perceptions about swap due to its overeagerness to swap out pages. On kernels >4.0, the situation is significantly better.
  5. On SSDs, swapping out anonymous pages and reclaiming file pages are essentially equivalent in terms of performance/latency. On older spinning disks, swap reads are slower due to random reads, so a lower vm.swappiness setting makes sense there (read on for more about vm.swappiness ).
  6. Disabling swap doesn't prevent pathological behaviour at near-OOM, although it's true that having swap may prolong it. Whether the system global OOM killer is invoked with or without swap, or was invoked sooner or later, the result is the same: you are left with a system in an unpredictable state. Having no swap doesn't avoid this.
  7. You can achieve better swap behaviour under memory pressure and prevent thrashing using memory.low and friends in cgroup v2.

太长不看:

  1. 对维持系统的正常功能而言,有 swap 是相对挺重要的一部分。没有它的话会更难做到合理的内存管理。
  2. swap 的目的通常并不是用作紧急内存,它的目的在于让内存回收能更平等和高效。 事实上把它当作「紧急内存」来用的想法通常是有害的。
  3. 禁用 swap 在内存压力下并不能避免磁盘I/O造成的性能问题,这么做只是让磁盘I/O颠簸的范围从 匿名页面转化到文件页面。这不仅更低效,因为系统能回收的页面的选择范围更有限了, 而且这种做法还可能是加重了内存压力的原因之一。
  4. 内核 4.0 版本之前的交换进程(swapper)有一些问题,导致很多人对 swap 有负面印象, 因为它太急于(overeagerness)把页面交换出去。在 4.0 之后的内核上这种情况已经改善了很多。
  5. 在 SSD 上,交换出匿名页面的开销和回收文件页面的开销基本上在性能/延迟方面没有区别。 在老式的磁盘上,读取交换文件因为属于随机访问读取所以会更慢,于是设置较低的 vm.swappiness 可能比较合理(继续读下面关于 vm.swappiness 的描述)。
  6. 禁用 swap 并不能避免在接近 OOM 状态下最终表现出的症状,尽管的确有 swap 的情况下这种症状持续的时间可能会延长。在系统调用 OOM 杀手的时候无论有没有启用 swap ,或者更早/更晚开始调用 OOM 杀手,结果都是一样的:整个系统留在了一种不可预知的状态下。 有 swap 也不能避免这一点。
  7. 可以用 cgroup v2 的 memory.low 相关机制来改善内存压力下 swap 的行为并且 避免发生颠簸。

As part of my work improving kernel memory management and cgroup v2, I've been talking to a lot of engineers about attitudes towards memory management, especially around application behaviour under pressure and operating system heuristics used under the hood for memory management.

我的工作的一部分是改善内核中内存管理和 cgroup v2 相关,所以我和很多工程师讨论过看待内存管理的态度, 尤其是在压力下应用程序的行为和操作系统在底层内存管理中用的基于经验的启发式决策逻辑。

A repeated topic in these discussions has been swap. Swap is a hotly contested and poorly understood topic, even by those who have been working with Linux for many years. Many see it as useless or actively harmful: a relic of a time where memory was scarce, and disks were a necessary evil to provide much-needed space for paging. This is a statement that I still see being batted around with relative frequency in recent years, and I've had many discussions with colleagues, friends, and industry peers to help them understand why swap is still a useful concept on modern computers with significantly more physical memory available than in the past.

在这种讨论中经常重复的话题是交换区(swap)。交换区的话题是非常有争议而且很少被理解的话题,甚至包括那些在 Linux 上工作过多年的人也是如此。很多人觉得它没什么用甚至是有害的:它是历史遗迹,从内存紧缺而 磁盘读写是必要之恶的时代遗留到现在,为计算机提供在当年很必要的页面交换功能作为内存空间。 最近几年我还经常能以一定频度看到这种论调,然后我和很多同事、朋友、业界同行们讨论过很多次, 帮他们理解为什么在现代计算机系统中交换区仍是有用的概念,即便现在的电脑中物理内存已经远多于过去。

There's also a lot of misunderstanding about the purpose of swap – many people just see it as a kind of "slow extra memory" for use in emergencies, but don't understand how it can contribute during normal load to the healthy operation of an operating system as a whole.

围绕交换区的目的还有很多误解——很多人觉得它只是某种为了应对紧急情况的「慢速额外内存」, 但是没能理解在整个操作系统健康运作的时候它也能改善普通负载的性能。

Many of us have heard most of the usual tropes about memory: " Linux uses too much memory ", " swap should be double your physical memory size ", and the like. While these are either trivial to dispel, or discussion around them has become more nuanced in recent years, the myth of "useless" swap is much more grounded in heuristics and arcana rather than something that can be explained by simple analogy, and requires somewhat more understanding of memory management to reason about.

我们很多人也听说过描述内存时所用的常见说法: 「 Linux 用了太多内存 」,「 swap 应该设为物理内存的两倍大小 」,或者类似的说法。 虽然这些误解要么很容易化解,或者关于他们的讨论在最近几年已经逐渐变得琐碎,但是关于「无用」交换区 的传言有更深的经验传承的根基,而不是一两个类比就能解释清楚的,并且要探讨这个先得对内存管理有 一些基础认知。

This post is mostly aimed at those who administrate Linux systems and are interested in hearing the counterpoints to running with undersized/no swap or running with vm.swappiness set to 0.

本文主要目标是针对那些管理 Linux 系统并且有兴趣理解「让系统运行于低/无交换区状态」或者「把 vm.swappiness 设为 0 」这些做法的反论。

背景

It's hard to talk about why having swap and swapping out pages are good things in normal operation without a shared understanding of some of the basic underlying mechanisms at play in Linux memory management, so let's make sure we're on the same page.

如果没有基本理解 Linux 内存管理的底层机制是如何运作的,就很难讨论为什么需要交换区以及交换出页面 对正常运行的系统为什么是件好事,所以我们先确保大家有讨论的基础。

内存的类型

There are many different types of memory in Linux, and each type has its own properties. Understanding the nuances of these is key to understanding why swap is important.

Linux 中内存分为好几种类型,每种都有各自的属性。想理解为什么交换区很重要的关键一点在于理解这些的细微区别。

For example, there are pages ("blocks" of memory, typically 4k) responsible for holding the code for each process being run on your computer. There are also pages responsible for caching data and metadata related to files accessed by those programs in order to speed up future access. These are part of the page cache , and I will refer to them as file memory.

比如说,有种 页面(「整块」的内存,通常 4K) 是用来存放电脑里每个程序运行时各自的代码的。 也有页面用来保存这些程序所需要读取的文件数据和元数据的缓存,以便加速随后的文件读写。 这些内存页面构成 页面缓存(page cache),后文中我称他们为文件内存。

There are also pages which are responsible for the memory allocations made inside that code, for example, when new memory that has been allocated with malloc is written to, or when using mmap 's MAP_ANONYMOUS flag. These are "anonymous" pages – so called because they are not backed by anything – and I will refer to them as anon memory.

还有一些页面是在代码执行过程中做的内存分配得到的,比如说,当代码调用 malloc 能分配到新内存区,或者使用 mmap MAP_ANONYMOUS 标志分配的内存。 这些是「匿名(anonymous)」页面——之所以这么称呼它们是因为他们没有任何东西作后备—— 后文中我称他们为匿名内存。

There are other types of memory too – shared memory, slab memory, kernel stack memory, buffers, and the like – but anonymous memory and file memory are the most well known and easy to understand ones, so I will use these in my examples, although they apply equally to these types too.

还有其它类型的内存——共享内存、slab内存、内核栈内存、文件缓冲区(buffers),这种的—— 但是匿名内存和文件内存是最知名也最好理解的,所以后面的例子里我会用这两个说明, 虽然后面的说明也同样适用于别的这些内存类型。

可回收/不可回收内存

One of the most fundamental questions when thinking about a particular type of memory is whether it is able to be reclaimed or not. "Reclaim" here means that the system can, without losing data, purge pages of that type from physical memory.

考虑某种内存的类型时,一个非常基础的问题是这种内存是否能被回收。 「回收(Reclaim)」在这里是指系统可以,在不丢失数据的前提下,从物理内存中释放这种内存的页面。

For some page types, this is typically fairly trivial. For example, in the case of clean (unmodified) page cache memory, we're simply caching something that we have on disk for performance, so we can drop the page without having to do any special operations.

对一些内存类型而言,是否可回收通常可以直接判断。比如对于那些干净(未修改)的页面缓存内存, 我们只是为了性能在用它们缓存一些磁盘上现有的数据,所以我们可以直接扔掉这些页面, 不需要做什么特殊的操作。

For some page types, this is possible, but not trivial. For example, in the case of dirty (modified) page cache memory, we can't just drop the page, because the disk doesn't have our modifications yet. As such we either need to deny reclamation or first get our changes back to disk before we can drop this memory.

对有些内存类型而言,回收是可能的,但是不是那么直接。比如对脏(修改过)的页面缓存内存, 我们不能直接扔掉这些页面,因为磁盘上还没有写入我们所做的修改。这种情况下,我们可以选择拒绝回收, 或者选择先等待我们的变更写入磁盘之后才能扔掉这些内存。

For some page types, this is not possible. For example, in the case of the anonymous pages mentioned previously, they only exist in memory and in no other backing store, so they have to be kept there.

对还有些内存类型而言,是不能回收的。比如前面提到的匿名页面,它们只存在于内存中,没有任何后备存储, 所以它们必须留在内存里。

说到交换区的本质

If you look for descriptions of the purpose of swap on Linux, you'll inevitably find many people talking about it as if it is merely an extension of the physical RAM for use in emergencies. For example, here is a random post I got as one of the top results from typing "what is swap" in Google:

Swap is essentially emergency memory; a space set aside for times when your system temporarily needs more physical memory than you have available in RAM. It's considered "bad" in the sense that it's slow and inefficient, and if your system constantly needs to use swap then it obviously doesn't have enough memory. […] If you have enough RAM to handle all of your needs, and don't expect to ever max it out, then you should be perfectly safe running without a swap space.

如果你去搜 Linux 上交换区的目的的描述,肯定会找到很多人说交换区只是在紧急时用来扩展物理内存的机制。 比如下面这段是我在 google 中输入「什么是 swap」 从前排结果中随机找到的一篇:

交换区本质上是紧急内存;是为了应对你的系统临时所需内存多余你现有物理内存时,专门分出一块额外空间。 大家觉得交换区「不好」是因为它又慢又低效,并且如果你的系统一直需要使用交换区那说明它明显没有足够的内存。 [……]如果你有足够内存覆盖所有你需要的情况,而且你觉得肯定不会用满内存,那么完全可以不用交换区 安全地运行系统。
To be clear, I don't blame the poster of this comment at all for the content of their post – this is accepted as "common knowledge" by a lot of Linux sysadmins and is probably one of the most likely things that you will hear from one if you ask them to talk about swap. It is unfortunately also, however, a misunderstanding of the purpose and use of swap, especially on modern systems.

事先说明,我不想因为这些文章的内容责怪这些文章的作者——这些内容被很多 Linux 系统管理员认为是「常识」, 并且很可能你问他们什么是交换区的时候他们会给你这样的回答。但是也很不幸的是, 这种认识是使用交换区的目的的一种普遍误解,尤其在现代系统上。

Above, I talked about reclamation for anonymous pages being "not possible", as anonymous pages by their nature have no backing store to fall back to when being purged from memory – as such, their reclamation would result in complete data loss for those pages. What if we could create such a store for these pages, though?

前文中我说过回收匿名页面的内存是「不可能的」,因为匿名内存的特点,把它们从内存中清除掉之后, 没有别的存储区域能作为他们的备份——因此,要回收它们会造成数据丢失。但是,如果我们为这种内存页面创建 一种后备存储呢?

Well, this is precisely what swap is for. Swap is a storage area for these seemingly "unreclaimable" pages that allows us to page them out to a storage device on demand. This means that they can now be considered as equally eligible for reclaim as their more trivially reclaimable friends, like clean file pages, allowing more efficient use of available physical memory.

嗯,这正是交换区存在的意义。交换区是一块存储空间,用来让这些看起来「不可回收」的内存页面在需要的时候 可以交换到存储设备上。这意味着有了交换区之后,这些匿名页面也和别的那些可回收内存一样, 可以作为内存回收的候选,就像干净文件页面,从而允许更有效地使用物理内存。

Swap is primarily a mechanism for equality of reclamation, not for emergency "extra memory". Swap is not what makes your application slow – entering overall memory contention is what makes your application slow.

交换区主要是为了平等的回收机制,而不是为了紧急情况的「额外内存」。使用交换区不会让你的程序变慢—— 进入内存竞争的状态才是让程序变慢的元凶。

So in what situations under this "equality of reclamation" scenario would we legitimately choose to reclaim anonymous pages? Here are, abstractly, some not uncommon scenarios:

那么在这种「平等的可回收机遇」的情况下,让我们选择回收匿名页面的行为在何种场景中更合理呢? 抽象地说,比如在下述不算罕见的场景中:

  1. During initialisation, a long-running program may allocate and use many pages. These pages may also be used as part of shutdown/cleanup, but are not needed once the program is "started" (in an application-specific sense). This is fairly common for daemons which have significant dependencies to initialise.
  2. During the program's normal operation, we may allocate memory which is only used rarely. It may make more sense for overall system performance to require a major fault to page these in from disk on demand, instead using the memory for something else that's more important.
  1. 程序初始化的时候,那些长期运行的程序可能要分配和使用很多页面。这些页面可能在最后的关闭/清理的 时候还需要使用,但是在程序「启动」之后(以具体的程序相关的方式)持续运行的时候不需要访问。 对后台服务程序来说,很多后台程序要初始化不少依赖库,这种情况很常见。
  2. 在程序的正常运行过程中,我们可能分配一些很少使用的内存。对整体系统性能而言可能比起让这些内存页 一直留在内存中,只有在用到的时候才按需把它们用 缺页异常(major fault) 换入内存, 可以空出更多内存留给更重要的东西。

考察有无交换区时会发生什么

Let's look at typical situations, and how they perform with and without swap present. I talk about metrics around "memory contention" in my talk on cgroup v2 .

我们来看一些在常见场景中,有无交换区时分别会如何运行。 在我的 关于 cgroup v2 的演讲 中探讨过「内存竞争」的指标。

在无/低内存竞争的状态下

  • With swap: We can choose to swap out rarely-used anonymous memory that may only be used during a small part of the process lifecycle, allowing us to use this memory to improve cache hit rate, or do other optimisations.
  • Without swap We cannot swap out rarely-used anonymous memory, as it's locked in memory. While this may not immediately present as a problem, on some workloads this may represent a non-trivial drop in performance due to stale, anonymous pages taking space away from more important use.
  • 有交换区: 我们可以选择换出那些只有在进程生存期内很小一部分时间会访问的匿名内存, 这允许我们空出更多内存空间用来提升缓存命中率,或者做别的优化。
  • 无交换区: 我们不能换出那些很少使用的匿名内存,因为它们被锁在了内存中。虽然这通常不会直接表现出问题, 但是在一些工作条件下这可能造成卡顿导致不平凡的性能下降,因为匿名内存占着空间不能给 更重要的需求使用。
译注:关于 内存热度内存颠簸(thrash)

讨论内核中内存管理的时候经常会说到内存页的 冷热 程度。这里冷热是指历史上内存页被访问到的频度, 内存管理的经验在说,历史上在近期频繁访问的 内存,在未来也可能被频繁访问, 从而应该留在物理内存里;反之历史上不那么频繁访问的 内存,在未来也可能很少被用到, 从而可以考虑交换到磁盘或者丢弃文件缓存。

颠簸(thrash) 这个词在文中出现多次但是似乎没有详细介绍,实际计算机科学专业的课程中应该有讲过。 一段时间内,让程序继续运行所需的热内存总量被称作程序的工作集(workset),估算工作集大小, 换句话说判断进程分配的内存页中哪些属于 内存哪些属于 内存,是内核中 内存管理的最重要的工作。当分配给程序的内存大于工作集的时候,程序可以不需要等待I/O全速运行; 而当分配给程序的内存不足以放下整个工作集的时候,意味着程序每执行一小段就需要等待换页或者等待 磁盘缓存读入所需内存页,产生这种情况的时候,从用户角度来看可以观察到程序肉眼可见的「卡顿」。 当系统中所有程序都内存不足的时候,整个系统都处于颠簸的状态下,响应速度直接降至磁盘I/O的带宽。 如本文所说,禁用交换区并不能防止颠簸,只是从等待换页变成了等待文件缓存, 给程序分配超过工作集大小的内存才能防止颠簸。

在中/高内存竞争的状态下

  • With swap: All memory types have an equal possibility of being reclaimed. This means we have more chance of being able to reclaim pages successfully – that is, we can reclaim pages that are not quickly faulted back in again (thrashing).
  • Without swap Anonymous pages are locked into memory as they have nowhere to go. The chance of successful long-term page reclamation is lower, as we have only some types of memory eligible to be reclaimed at all. The risk of page thrashing is higher. The casual reader might think that this would still be better as it might avoid having to do disk I/O, but this isn't true – we simply transfer the disk I/O of swapping to dropping hot page caches and dropping code segments we need soon.
  • 有交换区: 所有内存类型都有平等的被回收的可能性。这意味着我们回收页面有更高的成功率—— 成功回收的意思是说被回收的那些页面不会在近期内被缺页异常换回内存中(颠簸)。
  • 无交换区: 匿名内存因为无处可去所以被锁在内存中。长期内存回收的成功率变低了,因为我们成体上 能回收的页面总量少了。发生缺页颠簸的危险性更高了。缺乏经验的读者可能觉得这某时也是好事, 因为这能避免进行磁盘I/O,但是实际上不是如此——我们只是把交换页面造成的磁盘I/O变成了扔掉热缓存页 和扔掉代码段,这些页面很可能马上又要从文件中读回来。

在临时内存占用高峰时

  • With swap: We're more resilient to temporary spikes, but in cases of severe memory starvation, the period from memory thrashing beginning to the OOM killer may be prolonged. We have more visibility into the instigators of memory pressure and can act on them more reasonably, and can perform a controlled intervention.
  • Without swap The OOM killer is triggered more quickly as anonymous pages are locked into memory and cannot be reclaimed. We're more likely to thrash on memory, but the time between thrashing and OOMing is reduced. Depending on your application, this may be better or worse. For example, a queue-based application may desire this quick transfer from thrashing to killing. That said, this is still too late to be really useful – the OOM killer is only invoked at moments of severe starvation, and relying on this method for such behaviour would be better replaced with more opportunistic killing of processes as memory contention is reached in the first place.
  • 有交换区: 我们对内存使用激增的情况更有抵抗力,但是在严重的内存不足的情况下, 从开始发生内存颠簸到 OOM 杀手开始工作的时间会被延长。内存压力造成的问题更容易观察到, 从而可能更有效地应对,或者更有机会可控地干预。
  • 无交换区: 因为匿名内存被锁在内存中了不能被回收,所以 OOM 杀手会被更早触发。 发生内存颠簸的可能性更大,但是发生颠簸之后到 OOM 解决问题的时间间隔被缩短了。 基于你的程序,这可能更好或是更糟。比如说,基于队列的程序可能更希望这种从颠簸到杀进程的转换更快发生。 即便如此,发生 OOM 的时机通常还是太迟于是没什么帮助——只有在系统极度内存紧缺的情况下才会请出 OOM 杀手,如果想依赖这种行为模式,不如换成更早杀进程的方案,因为在这之前已经发生内存竞争了。

好吧,所以我需要系统交换区,但是我该怎么为每个程序微调它的行为?

You didn't think you'd get through this entire post without me plugging cgroup v2, did you? ;-)

你肯定想到了我写这篇文章一定会在哪儿插点 cgroup v2 的安利吧? ;-)

Obviously, it's hard for a generic heuristic algorithm to be right all the time, so it's important for you to be able to give guidance to the kernel. Historically the only tuning you could do was at the system level, using vm.swappiness . This has two problems: vm.swappiness is incredibly hard to reason about because it only feeds in as a small part of a larger heuristic system, and it also is system-wide instead of being granular to a smaller set of processes.

显然,要设计一种对所有情况都有效的启发算法会非常难,所以给内核提一些指引就很重要。 历史上我们只能在整个系统层面做这方面微调,通过 vm.swappiness 。这有两方面问题: vm.swappiness 的行为很难准确控制,因为它只是传递给一个更大的启发式算法中的一个小参数; 并且它是一个系统级别的设置,没法针对一小部分进程微调。

You can also use mlock to lock pages into memory, but this requires either modifying program code, fun with LD_PRELOAD , or doing horrible things with a debugger at runtime. In VM-based languages this also doesn't work very well, since you generally have no control over allocation and end up having to mlockall , which has no precision towards the pages you actually care about.

你可以用 mlock 把页面锁在内存里,但是这要么必须改程序代码,或者折腾 LD_PRELOAD ,或者在运行期用调试器做一些魔法操作。对基于虚拟机的语言来说这种方案也不能 很好工作,因为通常你没法控制内存分配,最后得用上 mlockall ,而这个没有办法精确指定你实际上想锁住的页面。

cgroup v2 has a tunable per-cgroup in the form of memory.low , which allows us to tell the kernel to prefer other applications for reclaim below a certain threshold of memory used. This allows us to not prevent the kernel from swapping out parts of our application, but prefer to reclaim from other applications under memory contention. Under normal conditions, the kernel's swap logic is generally pretty good, and allowing it to swap out pages opportunistically generally increases system performance. Swap thrash under heavy memory contention is not ideal, but it's more a property of simply running out of memory entirely than a problem with the swapper. In these situations, you typically want to fail fast by self-killing non-critical processes when memory pressure starts to build up.

cgroup v2 提供了一套可以每个 cgroup 微调的 memory.low ,允许我们告诉内核说当使用的内存低于一定阈值之后优先回收别的程序的内存。这可以让我们不强硬禁止内核 换出程序的一部分内存,但是当发生内存竞争的时候让内核优先回收别的程序的内存。在正常条件下, 内核的交换逻辑通常还是不错的,允许它有条件地换出一部分页面通常可以改善系统性能。在内存竞争的时候 发生交换颠簸虽然不理想,但是这更多地是单纯因为整体内存不够了,而不是因为交换进程(swapper)导致的问题。 在这种场景下,你通常希望在内存压力开始积攒的时候通过自杀一些非关键的进程的方式来快速退出(fail fast)。

You can not simply rely on the OOM killer for this. The OOM killer is only invoked in situations of dire failure when we've already entered a state where the system is severely unhealthy and may well have been so for a while. You need to opportunistically handle the situation yourself before ever thinking about the OOM killer.

你不能依赖 OOM 杀手达成这个。 OOM 杀手只有在非常急迫的情况下才会出动,那时系统已经处于极度不健康的 状态了,而且很可能在这种状态下保持了一阵子了。需要在开始考虑 OOM 杀手之前,积极地自己处理这种情况。

Determination of memory pressure is somewhat difficult using traditional Linux memory counters, though. We have some things which seem somewhat related, but are merely tangential – memory usage, page scans, etc – and from these metrics alone it's very hard to tell an efficient memory configuration from one that's trending towards memory contention. There is a group of us at Facebook, spearheaded by Johannes , working on developing new metrics that expose memory pressure more easily that should help with this in future. If you're interested in hearing more about this, I go into detail about one metric being considered in my talk on cgroup v2.

不过,用传统的 Linux 内存统计数据还是挺难判断内存压力的。我们有一些看起来相关的系统指标,但是都 只是支离破碎的——内存用量、页面扫描,这些——单纯从这些指标很难判断系统是处于高效的内存利用率还是 在滑向内存竞争状态。我们在 Facebook 有个团队,由 Johannes 牵头,努力开发一些能评价内存压力的新指标,希望能在今后改善目前的现状。 如果你对这方面感兴趣, 在我的 cgroup v2 的演讲中介绍到一个被提议的指标

调优

那么,我需要多少交换空间?

In general, the minimum amount of swap space required for optimal memory management depends on the number of anonymous pages pinned into memory that are rarely reaccessed by an application, and the value of reclaiming those anonymous pages. The latter is mostly a question of which pages are no longer purged to make way for these infrequently accessed anonymous pages.

通常而言,最优内存管理所需的最小交换空间取决于程序固定在内存中而又很少访问到的匿名页面的数量, 以及回收这些匿名页面换来的价值。后者大体上来说是问哪些页面不再会因为要保留这些很少访问的匿名页面而 被回收掉腾出空间。

If you have a bunch of disk space and a recent (4.0+) kernel, more swap is almost always better than less. In older kernels kswapd , one of the kernel processes responsible for managing swap, was historically very overeager to swap out memory aggressively the more swap you had. In recent times, swapping behaviour when a large amount of swap space is available has been significantly improved. If you're running kernel 4.0+, having a larger swap on a modern kernel should not result in overzealous swapping. As such, if you have the space, having a swap size of a few GB keeps your options open on modern kernels.

如果你有足够大的磁盘空间和比较新的内核版本(4.0+),越大的交换空间基本上总是越好的。 更老的内核上 kswapd , 内核中负责管理交换区的内核线程,在历史上倾向于有越多交换空间就 急于交换越多内存出去。在最近一段时间,可用交换空间很大的时候的交换行为已经改善了很多。 如果在运行 4.0+ 以后的内核,即便有很大的交换区在现代内核上也不会很激进地做交换。因此, 如果你有足够的容量,现代内核上有个几个 GB 的交换空间大小能让你有更多选择。

If you're more constrained with disk space, then the answer really depends on the tradeoffs you have to make, and the nature of the environment. Ideally you should have enough swap to make your system operate optimally at normal and peak (memory) load. What I'd recommend is setting up a few testing systems with 2-3GB of swap or more, and monitoring what happens over the course of a week or so under varying (memory) load conditions. As long as you haven't encountered severe memory starvation during that week – in which case the test will not have been very useful – you will probably end up with some number of MB of swap occupied. As such, it's probably worth having at least that much swap available, in addition to a little buffer for changing workloads. atop in logging mode can also show you which applications are having their pages swapped out in the SWAPSZ column, so if you don't already use it on your servers to log historic server state you probably want to set it up on these test machines with logging mode as part of this experiment. This also tells you when your application started swapping out pages, which you can tie to log events or other key data.

如果你的磁盘空间有限,那么答案更多取决于你愿意做的取舍,以及运行的环境。理想上应该有足够的交换空间 能高效应对正常负载和高峰(内存)负载。我建议先用 2-3GB 或者更多的交换空间搭个测试环境, 然后监视在不同(内存)负载条件下持续一周左右的情况。只要在那一周里没有发生过严重的内存不足—— 发生了的话说明测试结果没什么用——在测试结束的时候大概会留有多少 MB 交换区占用。 作为结果说明你至少应该有那么多可用的交换空间,再多加一些以应对负载变化。用日志模式跑 atop 可以在 SWAPSZ 栏显示程序的页面被交换出去的情况,所以如果你还没用它记录服务器历史日志的话 ,这次测试中可以试试在测试机上用它记录日志。这也会告诉你什么时候你的程序开始换出页面,你可以用这个 对照事件日志或者别的关键数据。

Another thing worth considering is the nature of the swap medium. Swap reads tend to be highly random, since we can't reliably predict which pages will be refaulted and when. On an SSD this doesn't matter much, but on spinning disks, random I/O is extremely expensive since it requires physical movement to achieve. On the other hand, refaulting of file pages is likely less random, since files related to the operation of a single application at runtime tend to be less fragmented. This might mean that on a spinning disk you may want to bias more towards reclaiming file pages instead of swapping out anonymous pages, but again, you need to test and evaluate how this balances out for your workload.

另一点值得考虑的是交换空间所在存储设备的媒介。读取交换区倾向于很随机,因为我们不能可靠预测什么时候 什么页面会被再次访问。在 SSD 上这不是什么问题,但是在传统磁盘上,随机 I/O 操作会很昂贵, 因为需要物理动作寻道。另一方面,重新加载文件缓存可能不那么随机,因为单一程序在运行期的文件读操作 一般不会太碎片化。这可能意味着在传统磁盘上你想更多地回收文件页面而不是换出匿名页面,但仍旧, 你需要做测试评估在你的工作负载下如何取得平衡。

译注:关于休眠到磁盘时的交换空间大小
原文这里建议交换空间至少是物理内存大小,我觉得实际上不需要。休眠到磁盘的时候内核会写回并丢弃 所有有文件作后备的可回收页面,交换区只需要能放下那些没有文件后备的页面就可以了。 如果去掉文件缓存页面之后剩下的已用物理内存总量能完整放入交换区中,就可以正常休眠。 对于桌面浏览器这种内存大户,通常有很多缓存页可以在休眠的时候丢弃。
For laptop/desktop users who want to hibernate to swap, this also needs to be taken into account – in this case your swap file should be at least your physical RAM size.

对笔记本/桌面用户如果想要休眠到交换区,这也需要考虑——这种情况下你的交换文件应该至少是物理内存大小。

我的 swappiness 应该如何设置?

First, it's important to understand what vm.swappiness does. vm.swappiness is a sysctl that biases memory reclaim either towards reclamation of anonymous pages, or towards file pages. It does this using two different attributes: file_prio (our willingness to reclaim file pages) and anon_prio (our willingness to reclaim anonymous pages). vm.swappiness`plays into this, as it becomes the default value for :code:`anon_prio , and it also is subtracted from the default value of 200 for file_prio , which means for a value of vm.swappiness = 50 , the outcome is that anon_prio is 50, and file_prio is 150 (the exact numbers don't matter as much as their relative weight compared to the other).

首先很重要的一点是,要理解 vm.swappiness 是做什么的。 vm.swappiness 是一个 sysctl 用来控制在内存回收的时候,是优先回收匿名页面, 还是优先回收文件页面。内存回收的时候用两个属性: file_prio (回收文件页面的倾向) 和 anon_prio (回收匿名页面的倾向)。 vm.swappiness 控制这两个值, 因为它是 anon_prio 的默认值,然后也是默认 200 减去它之后 file_prio 的默认值。 意味着如果我们设置 vm.swappiness = 50 那么结果是 anon_prio 是 50, file_prio 是 150 (这里数值本身不是很重要,重要的是两者之间的权重比)。

译注:关于 SSD 上的 swappiness

原文这里说 SSD 上 swap 和 drop page cache 差不多开销所以 vm.swappiness = 100 。我觉得实际上要考虑 swap out 的时候会产生写入操作,而 drop page cache 可能不需要写入( 要看页面是否是脏页)。如果负载本身对I/O带宽比较敏感,稍微调低 swappiness 可能对性能更好, 内核的默认值 60 是个不错的默认值。以及桌面用户可能对性能不那么关心,反而更关心 SSD 的写入寿命,虽然说 SSD 写入寿命一般也足够桌面用户,不过调低 swappiness 可能也能减少一部分不必要的写入(因为写回脏页是必然会发生的,而写 swap 可以避免)。 当然太低的 swappiness 会对性能有负面影响(因为太多匿名页面留在物理内存里而降低了缓存命中率) ,这里的权衡也需要根据具体负载做测试。

另外澄清一点误解, swap 分区还是 swap 文件对系统运行时的性能而言没有差别。或许有人会觉得 swap 文件要经过文件系统所以会有性能损失,在译文之前译者说过 Linux 的内存管理子系统基本上独立于文件系统。 实际上 Linux 上的 swapon 在设置 swap 文件作为交换空间的时候会读取一次文件系统元数据, 确定 swap 文件在磁盘上的地址范围,随后运行的过程中做交换就和文件系统无关了。关于 swap 空间是否连续的影响,因为 swap 读写基本是页面单位的随机读写,所以即便连续的 swap 空间(swap 分区)也并不能改善 swap 的性能。希疏文件的地址范围本身不连续,写入希疏文件的空洞需要 文件系统分配磁盘空间,所以在 Linux 上交换文件不能是希疏文件。只要不是希疏文件, 连续的文件内地址范围在磁盘上是否连续(是否有文件碎片)基本不影响能否 swapon 或者使用 swap 时的性能。

This means that, in general, vm.swappiness is simply a ratio of how costly reclaiming and refaulting anonymous memory is compared to file memory for your hardware and workload. The lower the value, the more you tell the kernel that infrequently accessed anonymous pages are expensive to swap out and in on your hardware. The higher the value, the more you tell the kernel that the cost of swapping anonymous pages and file pages is similar on your hardware. The memory management subsystem will still try to mostly decide whether it swaps file or anonymous pages based on how hot the memory is, but swappiness tips the cost calculation either more towards swapping or more towards dropping filesystem caches when it could go either way. On SSDs these are basically as expensive as each other, so setting vm.swappiness = 100 (full equality) may work well. On spinning disks, swapping may be significantly more expensive since swapping in generally requires random reads, so you may want to bias more towards a lower value.

这意味着,通常来说 vm.swappiness 只是一个比例,用来衡量在你的硬件和工作负载下, 回收和换回匿名内存还是文件内存哪种更昂贵 。设定的值越低,你就是在告诉内核说换出那些不常访问的 匿名页面在你的硬件上开销越昂贵;设定的值越高,你就是在告诉内核说在你的硬件上交换匿名页和 文件缓存的开销越接近。内存管理子系统仍然还是会根据实际想要回收的内存的访问热度尝试自己决定具体是 交换出文件还是匿名页面,只不过 swappiness 会在两种回收方式皆可的时候,在计算开销权重的过程中左右 是该更多地做交换还是丢弃缓存。在 SSD 上这两种方式基本上是同等开销,所以设成 vm.swappiness = 100 (同等比重)可能工作得不错。在传统磁盘上,交换页面可能会更昂贵, 因为通常需要随机读取,所以你可能想要设低一些。

The reality is that most people don't really have a feeling about which their hardware demands, so it's non-trivial to tune this value based on instinct alone – this is something that you need to test using different values. You can also spend time evaluating the memory composition of your system and core applications and their behaviour under mild memory reclamation.

现实是大部分人对他们的硬件需求没有什么感受,所以根据直觉调整这个值可能挺困难的 —— 你需要用不同的值做测试。你也可以花时间评估一下你的系统的内存分配情况和核心应用在大量回收内存的时候的行为表现。

When talking about vm.swappiness , an extremely important change to consider from recent(ish) times is this change to vmscan by Satoru Moriya in 2012 , which changes the way that vm.swappiness = 0 is handled quite significantly.

讨论 vm.swappiness 的时候,一个极为重要需要考虑的修改是(相对)近期在 2012 年左右 Satoru Moriya 对 vmscan 行为的修改 ,它显著改变了代码对 vm.swappiness = 0 这个值的处理方式。

Essentially, the patch makes it so that we are extremely biased against scanning (and thus reclaiming) any anonymous pages at all with vm.swappiness = 0 , unless we are already encountering severe memory contention. As mentioned previously in this post, that's generally not what you want, since this prevents equality of reclamation prior to extreme memory pressure occurring, which may actually lead to this extreme memory pressure in the first place. vm.swappiness = 1 is the lowest you can go without invoking the special casing for anonymous page scanning implemented in that patch.

基本上来说这个补丁让我们在 vm.swappiness = 0 的时候会极度避免扫描(进而回收)匿名页面, 除非我们已经在经历严重的内存抢占。就如本文前面所属,这种行为基本上不会是你想要的, 因为这种行为会导致在发生内存抢占之前无法保证内存回收的公平性,这甚至可能是最初导致发生内存抢占的原因。 想要避免这个补丁中对扫描匿名页面的特殊行为的话, vm.swappiness = 1 是你能设置的最低值。

The kernel default here is vm.swappiness = 60 . This value is generally not too bad for most workloads, but it's hard to have a general default that suits all workloads. As such, a valuable extension to the tuning mentioned in the "how much swap do I need" section above would be to test these systems with differing values for vm.swappiness , and monitor your application and system metrics under heavy (memory) load. Some time in the near future, once we have a decent implementation of refault detection in the kernel, you'll also be able to determine this somewhat workload-agnostically by looking at cgroup v2's page refaulting metrics.

内核在这里设置的默认值是 vm.swappiness = 60 。这个值对大部分工作负载来说都不会太坏, 但是很难有一个默认值能符合所有种类的工作负载。因此,对上面「 那么,我需要多少交换空间? 」那段讨论的一点重要扩展可以说,在测试系统中可以尝试使用不同的 vm.swappiness ,然后监视你的程序和系统在重(内存)负载下的性能指标。在未来某天,如果我们在内核中有了合理的 缺页检测 ,你也将能通过 cgroup v2 的页面缺页 指标来以负载无关的方式决定这个。

2019年07月更新:内核 4.20+ 中的内存压力指标

The refault metrics mentioned as in development earlier are now in the kernel from 4.20 onwards and can be enabled with CONFIG_PSI=y . See my talk at SREcon at around the 25:05 mark:

前文中提到的开发中的内存缺页检测指标已经进入 4.20+ 以上版本的内核,可以通过 CONFIG_PSI=y 开启。详情参见我在 SREcon 大约 25:05 左右的讨论。

结论

  • Swap is a useful tool to allow equality of reclamation of memory pages, but its purpose is frequently misunderstood, leading to its negative perception across the industry. If you use swap in the spirit intended, though – as a method of increasing equality of reclamation – you'll find that it's a useful tool instead of a hindrance.
  • Disabling swap does not prevent disk I/O from becoming a problem under memory contention, it simply shifts the disk I/O thrashing from anonymous pages to file pages. Not only may this be less efficient, as we have a smaller pool of pages to select from for reclaim, but it may also contribute to getting into this high contention state in the first place.
  • Swap can make a system slower to OOM kill, since it provides another, slower source of memory to thrash on in out of memory situations – the OOM killer is only used by the kernel as a last resort, after things have already become monumentally screwed. The solutions here depend on your system:
    • You can opportunistically change the system workload depending on cgroup-local or global memory pressure. This prevents getting into these situations in the first place, but solid memory pressure metrics are lacking throughout the history of Unix. Hopefully this should be better soon with the addition of refault detection.
    • You can bias reclaiming (and thus swapping) away from certain processes per-cgroup using memory.low, allowing you to protect critical daemons without disabling swap entirely.
  • 交换区是允许公平地回收内存的有用工具,但是它的目的经常被人误解,导致它在业内这种负面声誉。如果 你是按照原本的目的使用交换区的话——作为增加内存回收公平性的方式——你会发现它是很有效的工具而不是阻碍。
  • 禁用交换区并不能在内存竞争的时候防止磁盘I/O的问题,它只不过把匿名页面的磁盘I/O变成了文件页面的 磁盘I/O。这不仅更低效,因为我们回收内存的时候能选择的页面范围更小了,而且它可能是导致高度内存竞争 状态的元凶。
  • 有交换区会导致系统更慢地使用 OOM 杀手,因为在缺少内存的情况下它提供了另一种更慢的内存, 会持续地内存颠簸——内核调用 OOM 杀手只是最后手段,会晚于所有事情已经被搞得一团糟之后。 解决方案取决于你的系统:
    • 你可以预先更具每个 cgroup 的或者系统全局的内存压力改变系统负载。这能防止我们最初进入内存竞争 的状态,但是 Unix 的历史中一直缺乏可靠的内存压力检测方式。希望不久之后在有了 缺页检测 这样的性能指标之后能改善这一点。
    • 你可以使用 memory.low 让内核不倾向于回收(进而交换)特定一些 cgroup 中的进程, 允许你在不禁用交换区的前提下保护关键后台服务。

感谢在撰写本文时 RahulTejunJohannes 提供的诸多建议和反馈。

by farseerfc at September 30, 2020 04:45 AM

September 26, 2020

Alynx Zhou

固定 GNOME Shell 的输入法列表

GNOME Shell 有个令人很不爽的“特性”,它的输入法列表使用的是最近使用优先排列。也就是说当你有三个或以上输入法的时候,比如我,我有英文简体中文和日语输入法,我经常在中英之间切换,这没什么,前两个总是中英所以按一次就可以在这两个之间切换,但假如我偶尔用了一次日语输入法,我的列表就被打乱了,我不清楚按几下才能切回中文,并且再切到英文也得看一眼才能知道。

我不是很理解这个特性存在的意义,设置里面是可以手动调节输入法的顺序的,我明明调成了我想要的顺序,你就给我这个顺序好了,这样我闭着眼睛不用动脑子都能猜出来要按几下,比如从英文到日语按两下,中文到日语按一下等等。可能有些人的脑子长得比较擅长模拟最近使用优先排列?反正我不行。

既然感觉不爽那就动手处理一下好了,最近看了一些有关写 GNOME Shell 扩展的文档,所以写个扩展解决一下就可以了。为什么不直接提交给上游?因为上游一开始是固定顺序的,但是很久以前某个人加了这个“特性”,现在如果提个请求说删掉这个特性,势必会陷入一场“用户到底是喜欢最近使用优先排列还是固定排列”的争论,这肯定很难得出结论(毕竟大部分的人实际上是不需要使用输入法的英语用户以及只有两种输入法的用户!),并且按照 GNOME 上游的习惯他们也是不愿意为了这个多添加一个开关的。所以比起在拉锯战上浪费时间,先搞一个能用的才是我的风格。至于升级之后扩展挂掉……不就是在上游里和其他代码一起被重构和我自己单独重构的区别吗?只要我还在用应该就会持续更新了。

具体的解决方法比较 dirty,是我从别的扩展里学来的:把 GNOME Shell 里面的类的原型上的方法替换成自己的,就可以修改实例调用时的函数了(也算 JS 特性之一),不过不要用箭头函数,因为显然我们希望 this 是调用时的上下文也就是实例,而不是绑定到当前上下文。

因为这算是我第一个扩展所以也多少记录一下踩的坑。

首先 Gjs 的导入和 Node.js 的导入是不一样的,它通过一个 imports 对象引入其他库,比如通过 GI 导入的就在 gi 下面,因为是 GNOME Shell 扩展所以可以访问 GNOME Shell 的 JS 库,就是简单地把 JS 路径换成对象的 key 然后 JS 文件里所有的 varfunction 都会被导出。比如导入 Main 就是 imports.ui.main.Main

然后就是怎么知道要修改什么以及如何获取到相关对象,不过因为 GNOME Shell JS 部分经常重构,也没什么完整的文档,反正只能多花时间看代码吧,而且它的结构其实比看起来的要复杂,所以经常需要仔细翻来翻去的。比如 GNOME Shell 的输入法部分很多人认为是需要修改 iBus,实际上 GNOME Shell 只是调用 iBus 作为后端,自己处理状态和界面,这部分的代码都在 js/ui/status/keyboard.js 里面。

扩展主要有 init()enable()disable() 三个函数,init() 在 GNOME Shell 加载扩展时候调用,我这个显然不需要。enable() 是你在 Extensions app 里面打开开关时候调用的,disable() 是关掉开关时候调用的。

enable() 里面有几个需要我修改的地方,一个是阻止 InputSourceManager 在输入法切换之后的最近使用优先排列,解决方法很简单,需要自己替换掉 _currentInputSourceChanged 函数,注释掉 https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/gnome-3-36/js/ui/status/keyboard.js#L447-453 这一段更新代码。

当然光有这个还是不行的,这样假如你是先切换过再打开扩展,实际上列表是你开启扩展之前的状态而不是用户设置的顺序,所以我们还需要在打开扩展之后更新它的列表,让它直接读取用户设置。更新列表的函数是 _updateMruSources,假如检测到当前列表为空,会先从一个缓存的 gsettings 里读取之前存储的最近使用优先排列列表,这显然是很恶心的所以要注释掉 https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/gnome-3-36/js/ui/status/keyboard.js#L504-522 这一段。之后它会先加载当前列表里的,然后再把用户列表里增加的当前列表里没有的加到后面,因为我们已经决定要清空当前列表并且不加载 gsettings 里面的缓存,所以这个当前列表肯定是空,那直接加用户列表就行了,所以注释掉 https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/gnome-3-36/js/ui/status/keyboard.js#L525-533。这样我们后续只要清空 _mruSources 设置 _mruSourcesBackupnull 然后调用 _updateMruSources 就可以了。

然后我们需要获取运行时的这个 InputSourceManager 实例,这个实例没有被绑定到 Main 对象上,不过我阅读代码发现它是个单例模式,就是说在 js/ui/status/keyboard.js 有一个 _inputSourceManager 变量,然后有个 getInputSourceManager() 的函数,被调用时候如果有就返回 _inputSourceManager 否则创建一个赋值返回,其他代码都用的这个,所以我们也导入这个就行了。

然后你会发现另一个弱智的地方,怎么每次按下切换键,切换框都是从第一个切换到第二个?不是应该从我当前的切换到下一个吗?这个对于当前输入法总在第一个的最近使用优先排列是可以的,但在我们这个场景选中的并不总是第一个,所以需要修改。这部分函数是 _switchInputSource,可以看到它只是展示了一个 InputSourcePopup,而 InputSourcePopup 继承的是 imports.ui.switcherPopup.SwitcherPopup,这个类有一个叫做 _selectedIndex 的变量用于选择下一个上一个时候的计算,而且它默认是 0!不能通过参数初始化!真是头秃,不过我们可以在创建完切换框但展示之前单独设置这个值就行了,所以我在 https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/gnome-3-36/js/ui/status/keyboard.js#L412 的下一行插入如下代码:

if (this._currentSource != null) {
  popup._selectedIndex = this._mruSources.indexOf(this._currentSource);
}

因为我们不一定总有 _currentSource 所以还是要检查一下,如果没有的话让它从 0 开始也无所谓。

然后还有一个比较头痛的是快捷键是绑定的回调函数,回调函数又绑定了 this,所以我们光修改原型是改不了被回调的函数的,这个也简单,我们需要读一下 InputSourceManagerconstructor 的代码,然后删掉它在 Main.wm 里面绑定的组合键重新绑定成我们的,就是这样:

Main.wm.removeKeybinding("switch-input-source");
_inputSourceManager._keybindingAction =
  Main.wm.addKeybinding(
    "switch-input-source",
    new Gio.Settings({"schema_id": "org.gnome.desktop.wm.keybindings"}),
    Meta.KeyBindingFlags.NONE,
    Shell.ActionMode.ALL,
    InputSourceManager.prototype._switchInputSource.bind(_inputSourceManager)
  );
Main.wm.removeKeybinding("switch-input-source-backward");
_inputSourceManager._keybindingActionBackward =
  Main.wm.addKeybinding(
    "switch-input-source-backward",
    new Gio.Settings({"schema_id": "org.gnome.desktop.wm.keybindings"}),
    Meta.KeyBindingFlags.IS_REVERSED,
    Shell.ActionMode.ALL,
    InputSourceManager.prototype._switchInputSource.bind(_inputSourceManager)
  );

同样我们也不要忘记绑定 this,实际上我们希望调用的时候绑定的 this 是那个单例,那直接 bind 它就好了。

但是你会发现这个弱智的家伙没有按我们想象的工作!这是什么意思!仔细阅读代码之后我发现有如下逻辑:你按下的第一次组合键其实并不是算在那个弹框的按键回调里面,而是我们通过构造函数传递进去的,然后它分析这个传进去的按键是哪一种,调用 _initialSelection 执行第一次切换,而这个家伙更弱智了!明明有 _selectedIndex 它不用,竟然用硬编码的倒数第一个和第一个!真有你的啊!我不太敢修改 SwitcherPopup 因为还有别的东西使用它,那就修改 InputSourcePopup 这个子类吧,其实就是把 InputSourcePopup.prototype._initialSelection 这个函数原来的的 this._select(this._items.length - 1); 换成 this._select(this._previous());this._select(1) 换成 this._select(this._next())(1 其实是 0 + 1 的意思),不但功能增加了,可读性也提升了!

现在搭配起来应该和我们的需求一致了!但假如我关掉扩展之后希望列表是打开之前的状态怎么办!还记得之前说的那个 _updateMruSources 会读取 gsettings 吗?这个 gsettings 实际上在每次切换输入法的时候都会写入当前状态,那我们只要让它开启扩展时候不要写入,关掉扩展恢复的时候再更新不就读取了之前的状态吗。所以需要修改 InputSourceManager.prototype._updateMruSettings,注释掉 https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/gnome-3-36/js/ui/status/keyboard.js#L432-438

总结一下其实就是在 enable 的时候修改这些函数,然后获取单例,重新绑定快捷键,然后清空当前的列表重新更新列表,然后为了避免 bug,我们总是激活列表里的第一个输入法:

if (_inputSourceManager._mruSources.length > 0) {
  _inputSourceManager._mruSources[0].activate(true);
}

disable 的时候同样是把函数修改回去,然后获取单例,重新绑定快捷键这样它又绑成了原来的函数,然后清空当前的列表重新更新列表这样它就恢复到开启之前的顺序了,接下来同样地,因为最近使用优先列表的第一个元素肯定是正在用的,所以我们也还是激活第一个输入法就可以了。

完整的项目参见 GitHub,Arch Linux 用户也可以从 AUR 或者 Arch Linux CN 源安装 gnome-shell-extension-fixed-ime-list

Alynx Zhou

A Coder & Dreamer

by Alynx Zhou (alynx.zhou@gmail.com) at September 26, 2020 02:40 AM

September 24, 2020

中文社区新闻

Arch Conf 2020 日程

我们将在10月10日和11日举办在线 Arch Conf。会议中将会有来自 Arch 团队的讲演和从社区提交的讲演和闪电讨论。

我们非常高兴能发布日程安排的初版计划!

https://pretalx.com/arch-conf-online-2020/talk/

会议所在时区是 CEST/UTC+2:
https://everytimezone.com/s/40cc4784

更新和附加消息将发表在会议主页上: https://conf.archlinux.org

期待您的参与!

来自会议主办团队的祝贺

by farseerfc at September 24, 2020 12:34 AM

September 23, 2020

frantic1048

Amakuni - 梅瑟·恩达斯特 (Endro~!)

Mei

期待已久的梅依这周平安无事到了,带着前些天到的大号背景纸一起开个箱。另外看到最近 Darktable 3.21 更新了 filmic rgb v42 模块,这次也尝试了一下它带来的新的工作流,总体体验还是令人满意的。

这次手办细节上感觉很稳,各个角度过去没有一眼就让人想开 retouch 模块开始划拉的地方,而且服装细节还原得非常棒。与之对比的是 之前的四糸乃,衣服的蓝色区域有很多并不是尘土的细小白点,虽然普通眼睛观察不怎么看不出来,但是到了照片里,就像平时看不到的尘土一样全都冒出来了,结果就是令人头秃的修图,毕竟都断过一次手了,这点不算什么

Yoshino with many retouches

好了,上图!

Mei Mei

Mei Mei Mei Mei

Mei Mei Mei

Mei


  1. darktable 3.2: containment effect! https://www.darktable.org/2020/08/darktable-3-2/
  2. darktable ep 067 - Filmic v4 https://www.youtube.com/watch?v=qVnuqbR7Z-M

September 23, 2020 12:00 AM

September 14, 2020

ヨイツの賢狼ホロ

如何可能正确的以大部分人可能会接受的方式提出电脑相关的问题

汝问咱为啥标题起得这么长,那是因为咱不敢保证所有的人都乐于接受这种提问方法啊……

以及似乎能衍生到其它领域?那就靠汝自己斟酌了。

本文参考了下面几篇文章的观点,某些文章可能正在经常的被提起。

  • 提问的智慧,原文作者 Eric S. Raymond,以开放源代码运动(不是自由软件运动,这个的领导者是 Richard Stallman)的提出者和主要领导者为人所知的黑客。本指南将教你如何正确的提问以获得你满意的答案。
  • 如何有效的报告 Bug,以程序员的视角阐述如何提交一份足够准确的 Bug 报告
  • X-Y 问题,一种常见的令人疑惑的提问方式,至于为啥令人疑惑嘛……
  • 真的,再这样提问就没人理你了,以提问的智慧的方法提出不一定是电脑相关的问题的方法,大概吧。

提问真的有那么多讲究的地方嘛?

大多数时候如此,因为有一个大前提:

“我们(在很大程度上)是自愿的,从繁忙的生活中抽出时间来解答疑惑,而且时常被提问淹没。所以我们无情的滤掉一些话题,特别是拋弃那些看起来像失败者的家伙,以便更高效的利用时间来回答赢家(winner)的问题。”

大多数人其实都是出于各种志愿目的回答来自不知道何处的提问的,除非……(“我本来想这样拒绝他的,但是他给的钱实在是太多了……”)

所以,你不必在技术上很在行才能吸引我们的注意,但你必须表现出能引导你变得在行的特质 -- 机敏、有想法、善于观察、乐于主动参与解决问题。如果你做不到这些使你与众不同的事情,我们建议你花点钱找家商业公司签个技术支持服务合同,而不是要求黑客个人无偿地帮助你。

于是换做汝自己来回答的话,下面两种问题汝更倾向于回答哪一种?(假设汝有回答这个问题所需的能力的话):

  • 我从 foo 项目找来的源码没法编译。它怎么这么烂?
  • foo 项目代码在 Nulix 6.2 版下无法编译通过。我读过了 FAQ,但里面没有提到跟 Nulix 有关的问题。这是我编译过程的记录,我有什么做的不对的地方吗?

可能不那么显然的,后面的提问者已经指明了环境,也读过了 FAQ,还列出了错误,并且他没有把问题的责任推到别人头上,他的问题值得被关注。

除此之外,对于黑客来说,如果能回答一个有挑战性的问题,或者能激发他们思维的好问题。对他们来说可能就不再是负担,而成了他们的一种乐趣。对黑客而言,"好问题!"是诚挚的大力称赞。(当然放在其它领域里也差不多啦。)

因为时间有限,所以不要活在别人的生活里。也是因为时间有限,于是大家都习惯的去忽略那些不愿思考、或者在发问前不做他们该做的事的人。汝在提问的时候一定不想被这样忽略,对吧?

如果你决定向我们求助,当然你也不希望被视为失败者,更不愿成为失败者中的一员。能立刻得到快速并有效答案的最好方法,就是像赢家那样提问 -- 聪明、自信、有解决问题的思路,只是偶尔在特定的问题上需要获得一点帮助。

在汝遇到问题并决定提出问题之前

于是首先汝在什么地方有了麻烦……是呗?

据完全不可靠的实践发现,大多数的问题都能在这几步方法全运用完之前得到解决……

尝试阅读硬件或软件的说明文档找到答案

硬件设计者和开发者遇到的问题大多数时候不会比汝少,有一部分他们曾经遇到的问题可能的解决方法都被他们写进了自己硬件或软件的说明书或其它地方里。

对于硬件的话,最常见的是说明书或者网站上的教学手册以及常见问题解答一类的。

对于常见的的桌面软件的话,可以在菜单中寻找“帮助”菜单,或者去软件的网站查找说明。

例如大多数 macOS 的软件的“帮助”菜单里可以找到部分说明。

如果是有点那么不常见的命令行软件的话,比较常用的获得说明的方法有两种:

一种是在后面加上 --help 再运行,会列出像是可以添加的参数一类的信息。

部分程序也有可能是 -h 之类的短参数,或者其它别的(例如 Windows 里的 /?

$ python3 --help
usage: /usr/local/bin/python3 [option] ... [-c cmd | -m mod | file | -] [arg] ...
Options and arguments (and corresponding environment variables):
-b     : issue warnings about str(bytes_instance), str(bytearray_instance)
         and comparing bytes/bytearray with str. (-bb: issue errors)
-B     : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x
-c cmd : program passed in as string (terminates option list)
-d     : debug output from parser; also PYTHONDEBUG=x
-E     : ignore PYTHON* environment variables (such as PYTHONPATH)
-h     : print this help message and exit (also --help)

...

另一种做法是查阅相应的手册页,这里需要用到 man 命令。最简单的用法像这样:

$ man 要查询的命令的名称

这个 man 是男人还是 manual 的缩写呢……

典型的手册页大概像这个样子(还是拿刚才的 python 来举例),这里再加上一些解释?

实际上 man 会调用系统设置的分页程序来打开手册页(比较常见的是 less),至于 less 的用法嘛……大家可以去查阅 less 的手册页,溜了溜了……

PYTHON(1)                                                                        PYTHON(1)


# 程序的名字和一行简介
NAME
       python - an interpreted, interactive, object-oriented programming language

# 这里的 python 是一个命令,于是描述它如何运行,以及需要什么样的命令行参数。
# 如果查阅的是函数的手册页,可以看到函数所需的参数,以及哪个头文件包含该函数的定义。
SYNOPSIS
       python [ -B ] [ -b ] [ -d ] [ -E ] [ -h ] [ -i ] [ -I ]
              [ -m module-name ] [ -q ] [ -O ] [ -OO ] [ -s ] [ -S ] [ -u ]
              [ -v ] [ -V ] [ -W argument ] [ -x ] [ [ -X option ] -?  ]
              [ --check-hash-based-pycs default | always | never ]
              [ -c command | script | - ] [ arguments ]

# 更长的描述
DESCRIPTION
       Python  is  an  interpreted, interactive, object-oriented programming language that
       combines remarkable power with very clear syntax.  For an introduction to  program-
       ming  in  Python,  see the Python Tutorial.  The Python Library Reference documents
       built-in and standard types, constants, functions and modules.  Finally, the Python
       Reference  Manual  describes the syntax and semantics of the core language in (per-
       haps too) much detail.  (These documents may be located via the INTERNET  RESOURCES
       below; they may be installed on your system as well.)

# 可以在命令行下使用的参数以及参数
COMMAND LINE OPTIONS
       -B     Don't write .pyc files on import. See also PYTHONDONTWRITEBYTECODE.

       -b     Issue  warnings  about str(bytes_instance), str(bytearray_instance) and com-
              paring bytes/bytearray with str. (-bb: issue errors)

       -c command
              Specify the command to execute (see  next  section).   This  terminates  the
              option list (following options are passed as arguments to the command).

(下面其实还有很多的为了节省空间就省掉了……)

# 这一部分是 Python 的手册页特别的,介绍了解释器的接口
INTERPRETER INTERFACE
       The  interpreter interface resembles that of the UNIX shell: when called with stan-
       dard input connected to a tty device, it prompts for  commands  and  executes  them
       until an EOF is read; when called with a file name argument or with a file as stan-
       dard input, it reads and executes a script from that file; when called with -c com-
       mand,  it executes the Python statement(s) given as command.  Here command may con-
       tain multiple statements separated by newlines.  Leading whitespace is  significant
       in  Python  statements!  In non-interactive mode, the entire input is parsed before
       it is executed.

# 安装的文件和目录
FILES AND DIRECTORIES
       These are subject to difference depending on local installation conventions; ${pre-
       fix} and ${exec_prefix} are installation-dependent and should be interpreted as for
       GNU software; they may be the same.  The default for both is /usr/local.

# 可以设置的环境变量
ENVIRONMENT VARIABLES
       PYTHONHOME
              Change the location of the  standard  Python  libraries.   By  default,  the
              libraries  are  searched  in  ${prefix}/lib/python<version>  and ${exec_pre-
              fix}/lib/python<version>, where ${prefix} and ${exec_prefix}  are  installa-
              tion-dependent directories, both defaulting to /usr/local.  When $PYTHONHOME
              is set to  a  single  directory,  its  value  replaces  both  ${prefix}  and
              ${exec_prefix}.   To  specify different values for these, set $PYTHONHOME to
              ${prefix}:${exec_prefix}.

# 手册页的作者
AUTHOR
       The Python Software Foundation: https://www.python.org/psf/

# 一些在网上的资源的链接
INTERNET RESOURCES
       Main website:  https://www.python.org/
       Documentation:  https://docs.python.org/
       Developer resources:  https://devguide.python.org/
       Downloads:  https://www.python.org/downloads/
       Module repository:  https://pypi.org/
       Newsgroups:  comp.lang.python, comp.lang.python.announce

# 许可协议信息
LICENSING
       Python is distributed under an Open Source license.  See the file "LICENSE" in  the
       Python  source distribution for information on terms & conditions for accessing and
       otherwise using Python and for a DISCLAIMER OF ALL WARRANTIES.



                                                                                 PYTHON(1)

偶尔汝会在手册页见到 malloc(3) 这样的描述,这表示了特定区块的手册页。在 BSD 和 GNU/Linux 中,手册页通常被分作八个区块:

1 一般命令 2 系统调用 3 库函数,涵盖C标准函数库 4 特殊文件(通常是/dev中的设备)和驱动程序 5 文件格式和约定 6 游戏和屏保 7 杂项 8 系统管理命令和守护进程

于是要查阅特定区块的手册页的话,大概是这个样子(例如刚才的 malloc(3) ):

$ man 3 malloc

尝试上网搜索找到答案

有很多的问题也许不是第一次发生,那大抵汝也不会是第一次遇到的家伙。也许有人留下过类似的解决过程,如果汝找到了而且能成功地解决汝目前的问题,那这世界上就可能少了一个蠢问题(?)

可以搜索的地方也有很多,例如汝正在使用的软件或操作系统发行版的网站(如果汝没在上一步试图寻找的话)、论坛(有可能是官方名义的,或者是社区建立的)和邮件列表的存档,或者某个曾经解决过类似问题的家伙留下的文章等等。

至于搜索引擎要咋用,这个实在是太复杂了,咱也只能给出一些建议:

  • 选对一个好的搜索引擎就差不多成功了一半,嗯。
  • 把程序提示汝的消息作为搜索关键词可能有奇效,例如 Permission denied (publickey). (当然太长的话可能会被搜索引擎剪掉,于是尝试一下找出关键的部分吧。
  • 如果要自己决定关键词的话,首先不要用提问的方式,比如「我的电脑上不了网怎么办」,要寻找问题的线索,将线索变成关键词去搜索,一个关键词找不到就换另一个。啥?汝连这一步都懒得做?那么汝大抵有足够的资金找一个或者一群人帮汝解决汝现在遇到的问题,这就不在讨论范围内了……
  • 介于现在的硬件和软件都大量使用英语,有时也可以试试用英文搜索。
  • ……

尝试问一下汝身边熟悉的朋友?

这个有很大一部分是感性原因,因为某个完全不可靠的证据表明,汝身边最亲近的朋友大抵是最能忍受来自汝自己的看着很蠢的问题的。至少会比一无所知的陌生人容忍度大一点点。

不过朋友毕竟也是人,耐性也是有限的。(汝要是来问咱的话大概耐性会更低)于是为了避免发生像是喋血街头一类的惨剧,还是不负责的建议先把功课做一做了再提问。

天有不测风云?

不过要是汝已经做过了之前的尝试但是问题还是没解决的话……

  • 坐下来放松,然后再来一次(?)。

不要指望几秒钟的 Google 搜索就能解决一个复杂的问题。在向专家求助之前,再阅读一下常见问题文件(FAQ)、放轻松、坐舒服一些,再花点时间思考一下这个问题。相信我们,他们能从你的提问看出你做了多少阅读与思考,如果你是有备而来,将更有可能得到解答。不要将所有问题一股脑拋出,只因你的第一次搜索没有找到答案(或者找到太多答案)

  • 再次思考汝将要提出的问题。有不可靠的统计显示出寻求帮助前汝为解决问题所付出的努力程度和汝获得实质性的帮助的机率很大概率成正相关。

另一方面,表明你愿意在找答案的过程中做点什么是一个非常好的开端。谁能给点提示?我的这个例子里缺了什么?以及我应该检查什么地方请把我需要的确切的过程贴出来更容易得到答复。因为你表现出只要有人能指个正确方向,你就有完成它的能力和决心。

  • 再收集一些更详细的信息。例如 POST 画面的提示,主板七划显示器上的字形,软件弹出的提示,汝在之前做了些什么等等。

https://wiki.archlinux.org/index.php/Bug_reporting_guidelines#Gather_useful_information 上也列出了哪些信息可能是有用的。

  • 然后多想一下汝要在哪里提问。

我要在哪里提问?

在网上比较常见的发问场所,大抵有聊天群组,论坛和邮件列表。不过无论在哪里,下面的建议似乎都非常实用。(但未经过详细的检验)

  • 不要在与主题不合的地方贴出汝的问题。例如在绘画群组里提问游戏技巧,多半没人会搭理。
  • 不要在探讨进阶技术问题的论坛张贴非常初级的问题;反之亦然。至于怎么样算进阶怎么样算初级嘛,这个要自己掂量一下了。
  • 不要在太多的不同地方上重复转贴同样的问题。有很大一部分原因是不同的地方有可能都是同一群人(大雾)。
  • 别像机关枪似的一次"扫射"所有的帮助渠道,这就像大喊大叫一样会使人不快。要一个一个地来。
  • 先特定后通用,例如先去特定发行版的论坛或邮件列表中提问,再到程序本身的论坛或邮件列表提问。
  • 有些软件的网站上可能记载有官方支持和提交 Bug 报告的规则,如果有看到的话,照做就是了。
  • 通过论坛或聊天群组来提供使用者支持服务有增长的趋势,电子邮件则大多为项目开发者间的交流而保留。所以最好先在论坛或聊天群组中寻求与该项目相关的协助。

别问蠢问题!以及……

最常见的蠢问题大概有这么几种,至于为什么这些问题被觉得蠢……

  • 我能在哪找到 X 程序或 X 资源?

就在咱找到它的地方啊,大笨驴 —— 搜索引擎的那一头。天哪!难道还有人不会用 Google 吗?

  • 我怎样用 X 做 Y ?

如果汝想解决的是 Y ,提问时别给出可能并不恰当的方法。这种问题说明提问者不但对 X 完全无知,也对 Y 要解决的问题糊涂,还被特定形势禁锢了思维。最好忽略这种人,等他们把问题搞清楚了再说。

  • 如何设定我的 shell 提示??

如果汝有足够的智慧提这个问题,汝也该有足够的智慧去 RTFM,然后自己去找出来。

  • 我可以用 Bass-o-matic 文件转换工具将 AcmeCorp 档案转换为 TeX 格式吗?

试试看就知道了。如果汝有试过的话,汝既知道了答案,也不用浪费我的时间了。

  • 我的{程序/设定/SQL 语句}不工作

这不算是问题吧,咱有更有意思的事要做呢,对汝这还要再问二十句才能知道问题在哪的问题完全提不起劲。

在看到这类问题的时候,大多数人的反应通常不外如下三种:

你还有什么要补充的吗?| 真糟糕,希望你能搞定。| 这关我屁事?

  • 我的 Windows 电脑有问题,你能帮我吗?

能啊,扔掉微软的辣鸡,换个像 GNU/Linux 或 BSD 的开放源代码的操作系统吧。

注意:如果程序有官方 Windows 版本或者与 Windows 有互动(如 Samba),你可以问与 Windows 相关的问题, 只是别对问题是由 Windows 操作系统而不是程序本身造成的回复感到惊讶, 因为 Windows 对一般开发者来说实在太烂,这种说法通常都是对的。

  • 我的程序不会动了,我认为系统工具 X 有问题

汝完全有可能是第一个注意到被成千上万用户反复使用的系统调用与函数库档案有明显缺陷的家伙,或者汝完全没有根据。

不同凡响的说法需要不同凡响的证据,当汝这样声称的时候,汝大概已经有了清楚而详尽的报告作后盾了吧?

  • 我在安装 Linux(或者 X )时有问题,你能帮我吗?

不能。

咱只有亲自在汝自己的电脑上动手才能找到毛病。要么试试寻求汝附近的 GNU/Linux 使用者群组的实际指导?

注意:如果安装问题与某 Linux 的发行版有关,在它的邮件列表、论坛或本地使用者群组中提问也许是恰当的。此时,应描述问题的准确细节。在此之前,先用 Linux所有被怀疑的硬件作关键词仔细搜索。

  • 问题:我怎么才能破解 root 帐号/窃取 OP 特权/读别人的邮件呢?

想要这样做,说明了汝是个卑鄙小人;想找个黑客帮忙,说明汝是个不折不扣的大笨驴!

以及不要问那种看着就很像家庭作业的问题,这些问题很明显要汝自己找到答案。尽管汝也许需要点提示,但不要指望能从别人那里得到完整答案。

保持一个平和的正确态度

  • 虽然骄傲是不行的,但是也不要走向另一个极端。尽可能清楚地描述背景条件和问题情况,这比低声下气更好地定位了汝自己的位置。
  • 彬彬有礼,多用谢谢您的关注,或谢谢你的关照。让大家都知道汝对他们花时间免费提供帮助心存感激。当然这不是汝可以把报告写的粗心又含糊的借口。

使用有意义且描述明确的标题

在这个领域,卖惨大概是最没有作用的行为之一,宣称紧急极有可能事与愿违:大多数黑客会直接删除无礼和自私地企图即时引起关注的问题。更严重的是,紧急这个字(或是其他企图引起关注的标题)通常会被垃圾信过滤器过滤掉 —— 这样汝所希望能看到汝的问题的人可能永远都看不到汝那看似紧急的问题。

于是来看一下下面的例子:

救命啊!我的笔记本电脑不能正常显示了!

X.org 6.8.1 的鼠标光标会变形,某牌显卡 MV1005 芯片组。

现在用上面的想法思考一下哪一个问题更蠢?再小声的说一句,大家都觉得“目标-差异”格式的标题往往最能吸引黑客,因为这样他们马上就能知道汝的环境和遇到的问题。试试看用更聪明的方法改写上面提问的标题?

用清晰、正确、精准且语法正确的语句仔细组织汝的提问

从经验中大家得出了一个结论,粗心的提问者通常也会粗心的写程序与思考。(真的如此吗,欢迎来证实或证伪)

  • 正确的拼写、标点符号和大小写是很重要的。汝说关注这个很麻烦?那好咱们也觉得思考汝那错词百出的问题很麻烦,于是就不回答了。
  • 如果在非母语的论坛发问,尽管拼写错误之类的会宽容些,但还是不能怠于思考。以及拿不懂回复者使用的语言的话就用英文撰写问题。
  • 清楚的表达汝的问题以及需求。繁忙的人往往厌恶那种漫无边际的空泛提问。

要理解专家们所处的世界,请把专业技能想像为充裕的资源,而回复的时间则是稀缺的资源。你要求他们奉献的时间越少,你越有可能从真正专业而且很忙的专家那里得到解答。

  • 仔细、清楚地描述你的问题或 Bug 的症状。

来看看这句话:“我运行了FooApp,它弹出一个警告窗口,我试着关掉它,它就崩溃了。”这种表述并不清晰,用户究竟关掉了哪个窗口?是警告窗口还是整个FooApp程序?如果这样说,“我运行FooApp程序时弹出一个警告窗口,我试着关闭警告窗口,FooApp崩溃了。”这样虽然罗嗦点,但是很清晰不容易产生误解。

  • 描述问题发生的环境(机器配置、操作系统、应用程序、以及相关的信息),提供发行版和版本号。
  • 描述在提问前汝是怎样去研究和理解这个问题的。以及确定问题而采取的诊断步骤。

简单介绍一下汝来提问之前都做了些什么,我在 Google 中搜过下列句子但没有找到什么有用的东西 之类的反馈也是有用的参考意见。

  • 描述最近做过什么可能相关的硬件或软件变更。

问题发生前的一系列操作,往往就是对找出问题最有帮助的线索。因此,你的说明里应该包含你的操作步骤,以及机器和软件的反应,直到问题发生。在命令行处理的情况下,提供一段操作记录(例如运行脚本工具所生成的),并引用相关的若干行(如 20 行)记录会非常有帮助。

  • 尽可能的提供一个可以重现这个问题的可控环境的方法。

报告bug的最好的方法之一是“演示”给程序员看。让程序员站在电脑前,运行他们的程序,指出程序的错误。让他们看着您启动电脑、运行程序、如何进行操作以及程序对您的输入有何反应。

如果您必须报告bug,而此时程序员又不在您身边,那么您就要想办法让bug重现在他们面前。当他们亲眼看到错误时,就能够进行处理了。

确切地告诉程序员您做了些什么。如果是一个图形界面程序,告诉他们您按了哪个按钮,依照什么顺序按的。如果是一个命令行程序,精确的告诉他们您键入了什么命令。您应该尽可能详细地提供您所键入的命令和程序的反应。

  • 如果汝做完前一步后发现整个问题显得太长(特别是复现的方法和样例太过复杂时),尽量将它剪裁得越小越好。

这样做的用处至少有三点。 第一,表现出你为简化问题付出了努力,这可以使你得到回答的机会增加; 第二,简化问题使你更有可能得到有用的答案; 第三,在精炼你的 bug 报告的过程中,你很可能就自己找到了解决方法或权宜之计。

  • 除非汝非常、非常的有根据(例如有可以证明的测试或者修复的补丁),不要动辄声称找到了 Bug。最好写得像是自己做错了什么。

如果真的有 Bug,你会在回复中看到这点。这样做的话,如果真有 Bug,维护者就会向你道歉,这总比你惹恼别人然后欠别人一个道歉要好一点。

  • 描述目标而不是过程。在一开始就清楚的表示出来汝想做什么,然后再描述问题在哪里。

经常寻求技术帮助的人在心中有个更高层次的目标,而他们在自以为能达到目标的特定道路上被卡住了,然后跑来问该怎么走,但没有意识到这条路本身就有问题。结果要费很大的劲才能搞定。

所谓的 X-Y Problem 大抵如此,提出这种问题就是在一个根本错误的方向上浪费他人大量的时间和精力。这里有一个来自 CoolShell 的例子:

Q)问一下大家,我如何得到一个文件的大小 A1) size = ls -l $file | awk ‘{print $5}’ Q) 哦,要是这个文件名是个目录呢? A2) 用du吧 A3) 不好意思,你到底是要文件的大小还是目录的大小?你到底要干什么? Q) 我想把一个目录下的每个文件的每个块(第一个块有512个字节)拿出来做md5,并且计算他们的大小 …… A1) 哦,你可以使用dd吧。 A2) dd不行吧。 A3) 你用md5来计算这些块的目的是什么?你究竟想干什么啊? Q) 其实,我想写一个网盘,对于小文件就直接传输了,对于大文件我想分块做增量同步。 A2) 用rsync啊,你妹!*

  • 描述症状,而不是汝的猜测。(如果汝的推断如此有效的话,那汝还用向别人求助吗?)

因此要确信你原原本本告诉了他们问题的症状,而不是你的解释和理论;让黑客们来推测和诊断。如果你认为陈述自己的猜测很重要,清楚地说明这只是你的猜测,并描述为什么它们不起作用。

针对诊断者而言,这并不是一种怀疑,而只是一种真实而有用的需求,以便让他们看到的是与你看到的原始证据尽可能一致的东西,而不是你的猜测与归纳的结论。所以,大方的展示给我们看吧!

  • 然后去掉无意义的提问句,例如有人能帮我吗?或者这有答案吗?。除非汝想得到这样的回答:没错,有人能帮你或者不,没答案

使用易于读取且标准的文件格式发送易于回复的问题

没有人喜欢自找麻烦,难以阅读的问题让人没有阅读的欲望,难于回复的问题让人没有回答的热情。

首先不管在哪里提问,绝对,永远不要指望黑客们阅读使用封闭格式编写的文档,像微软公司的 Word 或 Excel 文件等。即便他们能够处理,他们也很厌恶这么做。

如果汝在邮件中提问,下面的建议可以参考:

  • 使用纯文字而不是 HTML (关闭 HTML 并不难)。
  • 使用 MIME 附件通常是可以的,前提是真正有内容(譬如附带的源代码或 patch),而不仅仅是邮件程序生成的模板(譬如只是信件内容的拷贝)。
  • 不要发送一段文字只是一行句子但自动换行后会变成多行的邮件(这使得回复部分内容非常困难)。设想你的读者是在 80 个字符宽的终端机上阅读邮件,最好设置你的换行分割点小于 80 字。
  • 但是,对一些特殊的文件不要设置固定宽度(譬如日志档案拷贝或会话记录)。数据应该原样包含,让回复者有信心他们看到的是和你看到的一样的东西。

如果汝在论坛中提问,下面的建议可以参考:

  • 一两个表情符号和彩色文本通常没有问题,但是不要滥用

花哨的彩色文本倾向于使人认为你是个无能之辈。过滥地使用表情符号、色彩和字体会使你看来像个傻笑的小姑娘。这通常不是个好主意,除非你只是对性而不是对答案感兴趣。

  • 别要求通过电子邮件回复。

    要求通过电子邮件回复是非常无礼的,除非你认为回复的信息可能比较敏感(有人会为了某些未知的原因,只让你而不是整个论坛知道答案)。如果你只是想在有人回复讨论串时得到电子邮件提醒,可以要求网页论坛发送给你。几乎所有论坛都支持诸如追踪此讨论串有回复时发送邮件提醒等功能。

古老和神圣的传统和它的亲戚 - RTFM 和 STFW

有一个古老而神圣的传统:如果你收到RTFM (Read The Fucking Manual)的回应,回答者认为你应该去读他妈的手册。当然,基本上他是对的,你应该去读一读。

RTFM 有一个年轻的亲戚。如果你收到STFW(Search The Fucking Web)的回应,回答者认为你应该到他妈的网上搜索。那人多半也是对的,去搜索一下吧。(更温和一点的说法是 Google 是你的朋友!有时也许是别的样式,例如 LMGTFY 的链接或者“本群已和 Google 达成战略合作”等等。)

通常,用这两句之一回答你的人会给你一份包含你需要内容的手册或者一个网址,而且他们打这些字的时候也正在读着。这些答复意味着回答者认为

  • 你需要的信息非常容易获得
  • 你自己去搜索这些信息比灌给你,能让你学到更多

你不应该因此不爽;依照黑客的标准,他已经表示了对你一定程度的关注,而没有对你的要求视而不见。你应该对他祖母般的慈祥表示感谢。

其实如果汝有努力的在提问前做好功课的话,应该不会收到这样的回复的吧……

我得到一个回复,但这是啥?

看来似乎是 zentry 卡住了;你应该先清除它。

如果汝看不懂回应,别立刻要求对方解释。先像以前试着自己解决问题时那样(利用手册,FAQ,网络,身边的高手),先试着去搞懂他的回应。如果真的需要对方解释,记得表现出汝已经从中学到了点什么。

哦~~~我看过说明了但是只有 -z 和 -p 两个参数中提到了 zentries,而且还都没有清楚的解释如何清除它。你是指这两个中的哪一个吗?还是我看漏了什么?

我没得到答案,怎么办?

如果仍得不到回答,请不要以为我们觉得无法帮助你。有时只是看到你问题的人不知道答案罢了。没有回应不代表你被忽视,虽然不可否认这种差别很难区分。

总的来说,简单的重复张贴问题是个很糟的点子。这将被视为无意义的喧闹。有点耐心,知道你问题答案的人可能生活在不同的时区,可能正在睡觉,也有可能你的问题一开始就没有组织好。

你可以通过其他渠道获得帮助,这些渠道通常更适合初学者的需要。

有许多网上的以及本地的使用者群组,由热情的软件爱好者(即使他们可能从没亲自写过任何软件)组成。通常人们组建这样的团体来互相帮助并帮助新手。

另外,你可以向很多商业公司寻求帮助,不论公司大还是小。别为要付费才能获得帮助而感到沮丧!毕竟,假使你的汽车发动机汽缸密封圈爆掉了 —— 完全可能如此 —— 你还得把它送到修车铺,并且为维修付费。就算软件没花费你一分钱,你也不能强求技术支持总是免费的。

对像是 GNU/Linux 这种大众化的软件,每个开发者至少会对应到上万名使用者。根本不可能由一个人来处理来自上万名使用者的求助电话。要知道,即使你要为这些协助付费,和你所购买的同类软件相比,你所付出的也是微不足道的(通常私有软件的技术支持费用比开放源代码软件的要高得多,且内容也没那么丰富)。

我得到了能解决我的问题的答案,然后呢?

问题解决以后,别忘了感谢拿些帮助过汝的人啦,以及:

  • 写一个补充说明,不用太长。这样做的好处不止可以为汝赢得声誉(以及可能在下次提问时尝到甜头),也可能会帮助到未来遇到相似问题的家伙们。
  • 思考一下怎样才能避免他人将来也遇到类似的问题,思考一下写一份文件或加到常见问题(FAQ)中会不会有帮助。如果是的话就将它们发给文档维护者。

在黑客中,这种良好的后继行动实际上比传统的礼节更为重要,也是你如何透过善待他人而赢得声誉的方式,这是非常有价值的资产。

by Horo at September 14, 2020 09:00 AM

Phoenix Nemo

软件工程实践上的一点思考

曾经大学时对于软件工程这类理论课不屑一顾,认为这些课本都是只在大学里讲学而并不实际参与工程的教授写的东西。但是经过这些年从自己开发程序编写代码,到与公司团队同学、兴趣圈的朋友一起开发项目,也积累、总结了一些经验和教训。正巧昨晚在游戏建设里参与了这类讨论,于是记下一些思考免得忘记。

案例 1

命令方块是 Minecraft 里用于执行游戏命令、实现各种触发性或持续性功能的方块。在游戏地图中需要展示一些浮空的名称标签,便是用命令方块生成隐形盔甲架实现的。这些盔甲架参数复杂且需要在地图里很多特定位置生成,负责的同学便在每个生成的位置下面放了重新生成的命令方块,生成的坐标是相对坐标,因此写好标签的命令方块便可被无限复用。

由于盔甲架属于实体,而实体在 Minecraft 中被认为是不可靠的:有无数种可能这实体会被移动或被清除。
因此我的建议是:将这些命令方块全部放到控制室,坐标写成绝对坐标并加上统一标签,便可做到一键生成全部、一键清除全部。

该同学表示:不想写绝对坐标,因为很麻烦。

案例 2

由于游戏玩法的需要,编写了新的插件。几天后按照原计划应当可以准备第一次基本功能测试时,负责开发的同学表示只写了大约 1/4 的功能。进度很慢的原因是 Minecraft 的实现过于糟糕,而 Spigot 和 Paper 等修改版也没有很好封装 API 导致几乎所有的事件都需要手动处理。

接下来的协同开发中该同学又在反复尝试对配置文件中属性类似的部分使用同一个序列化/反序列化方法处理、对不同配置文件中的不同物品记录项也加上了一层包装来使得其能够被一个序列化/反序列化方法处理、在其他一些程序逻辑上也在尝试复用代码减少冗余度。

我说,你先专心把功能快速叠出来,然后再去想优化的事情。
这位同学表示不能接受,他认为代码应该从编写时就是整洁的。

论点:矫枉过正的代码复用

代码复用是很常见的代码结构优化方式。更少的代码冗余可以减少维护的复杂度,也降低出错的可能。

但是在案例 1 中,如此复用代码(放置同样的命令方块)却实际上造成了更多的冗余:如果要修改一个属性,就需要记录整个世界里每一个命令方块的位置,然后一个个去修改它。相反,由于游戏世界地图里的建筑几乎不可能变化(虽然现实需求很少会有这种条件),统一放在控制室、hard code
所有的坐标作为一个大方法调用,却是在这需求前提下的更好的实现方式。如果需要修改属性,可以只在一个地方修改所有的命令方块。

或者说,重复放置命令方块的过程,就是 copy’n’paste 冗余代码的过程。

而案例 2 则更具有代表性。在项目初期,是否应当关注代码质量?
我认为是应当关注的,但是这基于开发者的工程实践经验。优秀的、熟练的开发者应当在代码编写时就能灵活使用各种简单的优化手段减少初期的代码冗余,但是对于在校大学生没有足够的项目经验时,面对紧凑的项目时间安排应当集中更多精力实现功能。此时过分关注代码优化会被分心导致各种问题——例如这位同学编写的代码基本没有能够一次通过所有测试的情况,而且绝大多数的错误都看起来只是粗心,并不是不理解、写不出的问题。

论点:实现,调整,优化

“Make it work, make it right, make it fast.” – Kent Beck

这是很多软件工程推崇的敏捷开发指导方向。在案例 1 中,该同学只做了第一步——复用同样的、带有相对坐标的命令方块(方法)快速实现了所有的功能。但是从后续维护的角度来讲,这样的实现没有 make it right,更不用提 fast。

而在案例 2 中,这位同学将三个阶段在初期就全部揉进去,但是由于工程经验不足,在思考优化方案时花费了过多的精力,也导致了代码精度不够,反复修改也无法顺利通过测试。

从个人经验来看,前期的代码编写应注重功能实现,并在编码能力基础上直接编写清晰的代码结构。功能实现后,再根据需求和测试中的问题「重构」打磨细节、尝试更好的实现方式。这个过程不仅在完善整个程序,对自己的系统架构把握和设计经验也有很大的提升。最后一个阶段,则是针对性的优化少量代码使整个系统更加稳定、高效。

论点:架构的改动

这是一个比较小型的项目。需求和基本功能架构从一开始便已经讨论清楚。后续的调整不大,但是每当有少量的需求修改或架构微调,都导致了很大的代码变动。而按照这位同学的思路,每次改动都要重新思考代码结构,这浪费很多的时间。

从实际工程角度,需求变化并带来架构的微调甚至大改动都可以说是很常见的事情。在前期编码实现阶段如果揉入过多对于代码结构的过多考量,每次改动都可能会使这些思考的时间被浪费。因此,在前期编码时不应为架构考虑消耗过多的时间,而在重构过程中,由于已经完成基本的功能实现,且对已有代码还处在熟悉的热度,可以快速适配需要修改、调整的架构,并基于前期编码时的各种尝试和实验的结论选择最佳的实现方式。

以上是基于近期项目中的讨论,在软件工程层面上的思考。如有缺漏不当之处,欢迎指正。

曾经大学时对于软件工程这类理论课不屑一顾,认为这些课本都是只在大学里讲学而并不实际参与工程的教授写的东西。但是经过这些年从自己开发程序编写代码,到与公司团队同学、兴趣圈的朋友一起开发项目,也积累、总结了一些经验和教训。正巧昨晚在游戏建设里参与了这类讨论,于是记下一些思考免得忘记。

September 14, 2020 04:20 AM

WireGuard 真香

真是老了跟不上时代了,这么好的东西为什么我现在才开始用??

其实这东西刚出来就在关注了不过确实前段时间才有机会尝试折腾一下。优点很多,也有无数人写过文章介绍,所以就不再多废话。主要看中它的 PtP 特性(服务器之间)和支持漫游(服务器-客户端)。当然目前在梯子方面的表现,即便是优秀的隧道方案,但由于折腾的人多了,面对万里城墙,这谁顶得住哇。

所以本文只讨论 WireGuard 作为访问企业网的隧道方案,算是初步折腾的笔记。

服务器配置

一个基本的 PtP 配置结构 /etc/wireguard/wg0.conf

1
2
3
4
5
6
7
8
9
[Interface]
Address = 10.0.0.1/32
PrivateKey = [CLIENT PRIVATE KEY]

[Peer]
PublicKey = [SERVER PUBLICKEY]
AllowedIPs = 10.0.0.0/24, 10.123.45.0/24, 1234:4567:89ab::/48
Endpoint = [SERVER ENDPOINT]:48574
PersistentKeepalive = 25

生成私钥

1
2
wg genkey > privatekey
chmod 600 privatekey

基于私钥生成本机的公钥

1
wg pubkey < privatekey > publickey

或者一步完成的操作

1
wg genkey | tee privatekey | wg pubkey > publickey

额外生成预共享密钥来进一步增强安全性

1
wg genpsk > preshared

这样服务器之间的互联配置就基本完成了。使用 wg-quick up <config> 来快速启动 WireGuard。

如果要配合客户端使用,则需要配置 NAT。顺便如果客户端没有 IPv6,也可以通过此法来给客户端提供 IPv6 Enablement。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Interface]
Address = 10.200.200.1/24
Address = fd42:42:42::1/64
SaveConfig = true
ListenPort = 51820
PrivateKey = [SERVER PRIVATE KEY]

# note - substitute eth0 in the following lines to match the Internet-facing interface
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

[Peer]
# client foo
PublicKey = [FOO's PUBLIC KEY]
PresharedKey = [PRE-SHARED KEY]
AllowedIPs = 10.200.200.2/32, fd42:42:42::2/128

[Peer]
# client bar
PublicKey = [BAR's PUBLIC KEY]
AllowedIPs = 10.200.200.3/32, fd42:42:42::3/128

在此例中需注意 Allowed IPs 不可 overlap 否则会造成包转发错误。

客户端

与上文中服务器配置相照应的客户端配置示例如下:

1
2
3
4
5
6
7
8
9
10
11
[Interface]
Address = 10.200.200.2/24
Address = fd42:42:42::2/64
PrivateKey = [FOO's PRIVATE KEY]
DNS = 1.1.1.1

[Peer]
PublicKey = [SERVER PUBLICKEY]
PresharedKey = [PRE-SHARED KEY]
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = [SERVER PUBLIC IP ADDRESS]:51820

客户端的 AllowedIPs 如果使用 catch-all 0.0.0.0/0, ::/0 也就会默认转发所有的流量到服务器。该选项实际作用是路由表,控制哪些流量需要经由服务器转发。

配置完毕即可使用 wg-quick up <config> 启动 WireGuard。如果一切顺利,通过路由追踪应该可以看到流量已经交由服务器转发。

总结

由于工作需要,经常合上笔记本动身前往其他地方。在接入传统企业网例如 L2TP/IPSec 甚至 AnyConnect 都无法保证设备下次进入工作状态时可以立即恢复连接。而 WireGuard 在不同网络、不同地域、不同网络中断时间等各种情况下均可在下次进入网络覆盖时立即恢复连接,再也不必担心网络中断恢复时手忙脚乱配置隧道或者不小心泄密啦。

目前唯一的不足,大概就是还没有 Windows 客户端,没有办法推广到非技术部门(虽然影响不到我…

总之,真香.jpg

Reference:

[1] https://wiki.archlinux.org/index.php/WireGuard

真是老了跟不上时代了,这么好的东西为什么我现在才开始用??

September 14, 2020 04:20 AM

制作 Arch Linux 内存系统启动盘

之前尝试过 Arch Linux in RAM 完全运行在内存中的轻量业务系统,最近在维护一些物理服务器看到没有安装系统的服务器不断重启,想到了可以制作类似的内存系统启动盘,以高效完成系统测试、安装、远程维护等任务。

这时候就要祭出 mkarchiso 大法了。这是自动化制作最新版 Arch Live 镜像的工具集,当然也可用于制作定制化的 Arch 镜像。

准备

首先安装 archiso

1
~> sudo pacman -Syy archiso

它提供了两种配置方案,一种是只包含基本系统的 baseline,一种是可以制作定制 ISO 的 releng。要制作维护用 ISO,当然是复制 releng 配置啦。

1
2
~> cp -r /usr/share/archiso/configs/releng/ archlive
~> cd archlive

定制

整个过程不要太简单。先来了解下各个文件的用途:

  • build.sh - 用于制作镜像的自动化脚本,可以在这里修改一些名称变量或制作过程的逻辑。
  • packages.x86_64 - 一份要安装的包列表,一行一个。
  • pacman.conf - pacman 的配置文件,不用多说了吧。
  • airootfs - Live 系统的 rootfs,除了安装的包之外,其他的定制(以及启动执行脚本等)都在这里。遵循 rootfs 的目录规则。
  • efiboot / syslinux / isolinux 用于设置 BIOS / EFI 启动的配置。

[archlinuxcn] 仓库加入 pacman.conf

1
2
[archlinuxcn]
Server = https://cdn.repo.archlinuxcn.org/$arch

然后修改 packages.x86_64,加入 archlinuxcn-keyring 和其他需要预安装的包:

1
2
3
4
5
archlinuxcn-keyring
htop
iftop
iotop
ipmitool

按需修改即可啦。

要启动为内存系统,需要加启动参数 copytoram

修改文件 syslinux/archiso_pxe.cfgsyslinux/archiso_sys.cfg 文件,在启动参数后加 copytoram,像这样:

1
2
3
4
5
6
7
8
9
10
11
INCLUDE boot/syslinux/archiso_head.cfg

LABEL arch64
TEXT HELP
Boot the Arch Linux (x86_64) live medium.
It allows you to install Arch Linux or perform system maintenance.
ENDTEXT
MENU LABEL Boot Arch Linux (x86_64)
LINUX boot/x86_64/vmlinuz
INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img
APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram

启动时即可将整个 SquashFS 文件复制到内存。如果内存比较小,也可以指定 copytoram_size 来限制 tmpfs 占用内存的最大数量。

同样,也需要修改 efiboot/loader/entries/archiso-x86_64-usb.conf 的启动参数。在 options 行添加

1
options archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram

制作

创建工作目录和输出目录

1
mkdir -p work out

最后一步,只需要以 root 权限执行 ./build.sh 就可以啦。

要看具体执行过程的话,加 -v-h 看所有参数。

完成后,即可在 out 目录得到准备好的 ISO 文件。将其 dd 到 USB 闪存盘,大功告成(‘・ω・’)

Ref:

  1. https://wiki.archlinux.org/index.php/Archiso
  2. https://git.archlinux.org/archiso.git/tree/docs/README.bootparams#n53

之前尝试过 Arch Linux in RAM 完全运行在内存中的轻量业务系统,最近在维护一些物理服务器看到没有安装系统的服务器不断重启,想到了可以制作类似的内存系统启动盘,以高效完成系统测试、安装、远程维护等任务。

September 14, 2020 04:20 AM