メインコンテンツまでスキップ

Upgrade Ubuntu 23.04 to 24.04 LTS

· 約10分

About

Raspberry Pi で Ubuntu 23.04 から 24.04 LTS にアップグレードしようとしたら、do-release-upgrade でエラーが出たのでメモ。

Log

元の環境の lunar はすでに EOL で repository が参照できない状態だったため apt update でエラーが出ていた。

$ cat /etc/os-release
PRETTY_NAME="Ubuntu 23.04"
NAME="Ubuntu"
VERSION_ID="23.04"
VERSION="23.04 (Lunar Lobster)"
VERSION_CODENAME=lunar
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=lunar
LOGO=ubuntu-logo

下記 URL を参考にして、do-release-upgrade を実行

$ sudo do-release-upgrade
Checking for a new Ubuntu release
Your Ubuntu release is not supported anymore.
For upgrade information, please visit:
http://www.ubuntu.com/releaseendoflife


= Welcome to Ubuntu 24.04 LTS 'Noble Numbat' =

The Ubuntu team is proud to announce Ubuntu 24.04 LTS 'Noble Numbat'.

To see what's new in this release, visit:
https://wiki.ubuntu.com/NobleNumbat/ReleaseNotes

Ubuntu is a Linux distribution for your desktop or server, with a fast
and easy install, regular releases, a tight selection of excellent
applications installed by default, and almost any other software you
can imagine available through the network.

We hope you enjoy Ubuntu.

== Feedback and Helping ==

If you would like to help shape Ubuntu, take a look at the list of
ways you can participate at

http://www.ubuntu.com/community/participate/

Your comments, bug reports, patches and suggestions will help ensure
that our next release is the best release of Ubuntu ever. If you feel
that you have found a bug please read:

http://help.ubuntu.com/community/ReportingBugs

Then report bugs using apport in Ubuntu. For example:

ubuntu-bug linux

will open a bug report in Launchpad regarding the linux package.

If you have a question, or if you think you may have found a bug but
aren't sure, first try asking on the #ubuntu or #ubuntu-bugs IRC
channels on Libera.Chat, on the Ubuntu Users mailing list, or on the
Ubuntu forums:

http://help.ubuntu.com/community/InternetRelayChat
http://lists.ubuntu.com/mailman/listinfo/ubuntu-users
http://www.ubuntuforums.org/


== More Information ==

You can find out more about Ubuntu on our website, IRC channel and wiki.
If you're new to Ubuntu, please visit:

http://www.ubuntu.com/


To sign up for future Ubuntu announcements, please subscribe to Ubuntu's
very low volume announcement list at:

http://lists.ubuntu.com/mailman/listinfo/ubuntu-announce


Continue [yN] y
Get:1 Upgrade tool signature [833 B]
Get:2 Upgrade tool [1277 kB]
Fetched 1278 kB in 0s (0 B/s)
authenticate 'noble.tar.gz' against 'noble.tar.gz.gpg'
extracting 'noble.tar.gz'
[screen is terminating]
Reading cache

Checking package manager

Can not upgrade

An upgrade from 'lunar' to 'noble' is not supported with this tool.
=== Command detached from window (Fri Apr 18 20:12:30 2025) ===
=== Command terminated with exit status 1 (Fri Apr 18 20:12:40 2025) ===

うまくいかなかったので調べていると下記のドキュメントがあったので実施

cd $(mktemp -d)
mkdir noble
wget http://archive.ubuntu.com/ubuntu/dists/noble-updates/main/dist-upgrader-all/current/noble.tar.gz
tar xf noble.tar.gz -C noble
cd noble
sudo ./noble

結局先ほどと同じエラーが出た。lunar -> noble のアップデートはサポートされていないみたい。

Reading cache

Checking package manager

Can not upgrade

An upgrade from 'lunar' to 'noble' is not supported with this tool.
=== Command detached from window (Fri Apr 18 22:04:35 2025) ===

一旦 lunar -> mantic にアップグレードできないか試してみる。

cd ..
wget http://old-releases.ubuntu.com/ubuntu/dists/mantic-updates/main/dist-upgrader-all/current/mantic.tar.gz
mkdir mantic
tar xf mantic.tar.gz -C mantic/
cd mantic
sudo ./mantic --frontend=DistUpgradeViewText
Reading cache

Checking package manager

Continue running under SSH?

This session appears to be running under ssh. It is not recommended
to perform a upgrade over ssh currently because in case of failure it
is harder to recover.

If you continue, an additional ssh daemon will be started at port
'1022'.
Do you want to continue?

Continue [yN] y

Starting additional sshd

To make recovery in case of failure easier, an additional sshd will
be started on port '1022'. If anything goes wrong with the running
ssh you can still connect to the additional one.
If you run a firewall, you may need to temporarily open this port. As
this is potentially dangerous it's not done automatically. You can
open the port with e.g.:
'iptables -I INPUT -p tcp --dport 1022 -j ACCEPT'

To continue please press [ENTER]

Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Hit https://download.docker.com/linux/ubuntu lunar InRelease
Hit http://packages.openvpn.net/as/debian jammy InRelease
Ign http://ports.ubuntu.com/ubuntu-ports lunar InRelease
Ign http://ports.ubuntu.com/ubuntu-ports lunar-updates InRelease
Ign http://ports.ubuntu.com/ubuntu-ports lunar-backports InRelease
Ign http://ports.ubuntu.com/ubuntu-ports lunar-security InRelease
Err http://ports.ubuntu.com/ubuntu-ports lunar Release
404 Not Found [IP: 2620:2d:4000:1::19 80]
Err http://ports.ubuntu.com/ubuntu-ports lunar-updates Release
404 Not Found [IP: 2620:2d:4000:1::19 80]
Hit http://old-releases.ubuntu.com/ubuntu lunar InRelease
Err http://ports.ubuntu.com/ubuntu-ports lunar-backports Release
404 Not Found [IP: 2620:2d:4000:1::19 80]
Hit http://old-releases.ubuntu.com/ubuntu lunar-updates InRelease
Err http://ports.ubuntu.com/ubuntu-ports lunar-security Release
404 Not Found [IP: 2620:2d:4000:1::19 80]
Hit http://old-releases.ubuntu.com/ubuntu lunar-security InRelease
Fetched 0 B in 0s (0 B/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done

Checking for installed snaps

Calculating snap size requirements

Updating repository information

Third party sources disabled

Some third party entries in your sources.list were disabled. You can
re-enable them after the upgrade with the 'software-properties' tool
or your package manager.

To continue please press [ENTER]

Hit https://download.docker.com/linux/ubuntu lunar InRelease
Hit http://packages.openvpn.net/as/debian jammy InRelease
Fetched 0 B in 0s (0 B/s)

Checking package manager
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done

Invalid package information

After updating your package information, the essential package
'ubuntu-minimal' could not be located. This may be because you have
no official mirrors listed in your software sources, or because of
excessive load on the mirror you are using. See /etc/apt/sources.list
for the current list of configured software sources.
In the case of an overloaded mirror, you may want to try the upgrade
again later.


Restoring original system state

Aborting
g package lists... 3%
*** Collecting problem information

The collected information can be sent to the developers to improve the
application. This might take a few minutes.
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
=== Command terminated with exit status 1 (Fri Apr 18 22:10:12 2025) ===

ubuntu-minimal が見つからないというエラーが出ていそう? 一度 /etc/apt/sources.list を修正して http://old-releases.ubuntu.com/ubuntu/ のみにして再実行してみる。

Reading cache

Checking package manager
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Hit http://packages.openvpn.net/as/debian jammy InRelease
Hit https://download.docker.com/linux/ubuntu lunar InRelease
Hit http://old-releases.ubuntu.com/ubuntu lunar InRelease
Hit http://old-releases.ubuntu.com/ubuntu lunar-updates InRelease
Hit http://old-releases.ubuntu.com/ubuntu lunar-security InRelease
Hit http://old-releases.ubuntu.com/ubuntu lunar-backports InRelease
Fetched 0 B in 0s (0 B/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done

Checking for installed snaps

Calculating snap size requirements

Updating repository information

No valid mirror found

While scanning your repository information no mirror entry for the
upgrade was found. This can happen if you run an internal mirror or
if the mirror information is out of date.

Do you want to rewrite your 'sources.list' file anyway? If you choose
'Yes' here it will update all 'lunar' to 'mantic' entries.
If you select 'No' the upgrade will cancel.

Continue [yN] y
Hit https://download.docker.com/linux/ubuntu lunar InRelease
Hit http://packages.openvpn.net/as/debian jammy InRelease
Get:1 http://old-releases.ubuntu.com/ubuntu mantic InRelease [256 kB]
Get:2 http://old-releases.ubuntu.com/ubuntu mantic-updates InRelease [127 kB]
Get:3 http://old-releases.ubuntu.com/ubuntu mantic-security InRelease [127 kB]
Get:4 http://old-releases.ubuntu.com/ubuntu mantic-backports InRelease [127 kB]
Get:5 http://old-releases.ubuntu.com/ubuntu mantic/main arm64 Packages [1384 kB]
Get:6 http://old-releases.ubuntu.com/ubuntu mantic/main Translation-en [517 kB]
Get:7 http://old-releases.ubuntu.com/ubuntu mantic/main arm64 c-n-f Metadata [29.9 kB]
Get:8 http://old-releases.ubuntu.com/ubuntu mantic/restricted arm64 Packages [108 kB]
Get:9 http://old-releases.ubuntu.com/ubuntu mantic/restricted Translation-en [22.6 kB]
Get:10 http://old-releases.ubuntu.com/ubuntu mantic/restricted arm64 c-n-f Metadata [420 B]
Get:11 http://old-releases.ubuntu.com/ubuntu mantic/universe arm64 Packages [14.8 MB]
Get:12 http://old-releases.ubuntu.com/ubuntu mantic/universe Translation-en [5951 kB]
Get:13 http://old-releases.ubuntu.com/ubuntu mantic/universe arm64 c-n-f Metadata [295 kB]
Get:14 http://old-releases.ubuntu.com/ubuntu mantic/multiverse arm64 Packages [195 kB]
Get:15 http://old-releases.ubuntu.com/ubuntu mantic/multiverse Translation-en [113 kB]
Get:16 http://old-releases.ubuntu.com/ubuntu mantic/multiverse arm64 c-n-f Metadata [7168 B]
Get:17 http://old-releases.ubuntu.com/ubuntu mantic-updates/main arm64 Packages [369 kB]
Get:18 http://old-releases.ubuntu.com/ubuntu mantic-updates/main Translation-en [109 kB]
Get:19 http://old-releases.ubuntu.com/ubuntu mantic-updates/main arm64 c-n-f Metadata [9460 B]
Get:20 http://old-releases.ubuntu.com/ubuntu mantic-updates/restricted arm64 Packages [217 kB]
Get:21 http://old-releases.ubuntu.com/ubuntu mantic-updates/restricted Translation-en [35.7 kB]
Get:22 http://old-releases.ubuntu.com/ubuntu mantic-updates/restricted arm64 c-n-f Metadata [472 B]
Get:23 http://old-releases.ubuntu.com/ubuntu mantic-updates/universe arm64 Packages [404 kB]
Get:24 http://old-releases.ubuntu.com/ubuntu mantic-updates/universe Translation-en [158 kB]
Get:25 http://old-releases.ubuntu.com/ubuntu mantic-updates/universe arm64 c-n-f Metadata [14.3 kB]
Get:26 http://old-releases.ubuntu.com/ubuntu mantic-updates/multiverse arm64 Packages [5336 B]
Get:27 http://old-releases.ubuntu.com/ubuntu mantic-updates/multiverse Translation-en [2900 B]
Get:28 http://old-releases.ubuntu.com/ubuntu mantic-updates/multiverse arm64 c-n-f Metadata [228 B]
Get:29 http://old-releases.ubuntu.com/ubuntu mantic-security/main arm64 Packages [316 kB]
Get:30 http://old-releases.ubuntu.com/ubuntu mantic-security/main Translation-en [90.4 kB]
Get:31 http://old-releases.ubuntu.com/ubuntu mantic-security/main arm64 c-n-f Metadata [6880 B]
Get:32 http://old-releases.ubuntu.com/ubuntu mantic-security/restricted arm64 Packages [210 kB]
Get:33 http://old-releases.ubuntu.com/ubuntu mantic-security/restricted Translation-en [35.0 kB]
Get:34 http://old-releases.ubuntu.com/ubuntu mantic-security/restricted arm64 c-n-f Metadata [448 B]
Get:35 http://old-releases.ubuntu.com/ubuntu mantic-security/universe arm64 Packages [312 kB]
Get:36 http://old-releases.ubuntu.com/ubuntu mantic-security/universe Translation-en [129 kB]
Get:37 http://old-releases.ubuntu.com/ubuntu mantic-security/universe arm64 c-n-f Metadata [11.5 kB]
Get:38 http://old-releases.ubuntu.com/ubuntu mantic-security/multiverse arm64 Packages [4128 B]
Get:39 http://old-releases.ubuntu.com/ubuntu mantic-security/multiverse Translation-en [1732 B]
Get:40 http://old-releases.ubuntu.com/ubuntu mantic-security/multiverse arm64 c-n-f Metadata [232 B]
Get:41 http://old-releases.ubuntu.com/ubuntu mantic-backports/main arm64 c-n-f Metadata [112 B]
Get:42 http://old-releases.ubuntu.com/ubuntu mantic-backports/restricted arm64 c-n-f Metadata [116 B]
Get:43 http://old-releases.ubuntu.com/ubuntu mantic-backports/universe arm64 Packages [3944 B]
Get:44 http://old-releases.ubuntu.com/ubuntu mantic-backports/universe Translation-en [1392 B]
Get:45 http://old-releases.ubuntu.com/ubuntu mantic-backports/universe arm64 c-n-f Metadata [172 B]
Get:46 http://old-releases.ubuntu.com/ubuntu mantic-backports/multiverse arm64 c-n-f Metadata [116 B]
Fetched 26.5 MB in 6s (201 kB/s)

log を取りそこねたが、アップグレードにあたっていくつかのパッケージが削除されたりするけど良い?みたいな確認が出た。

Continue [yN] Details [d] とプロンプトが出たので d を選ぶと具体的なパッケージのリストが出てきた。

No longer supported: util-linux-extra


Remove: iptables-persistent

Remove (was auto installed) libevent-core-2.1-7a libsgutils2-2
netfilter-persistent


Install: appstream dhcpcd-base dracut-install libduktape207 libeac3
libsframe1 libsgutils2-1.46-2 linux-headers-6.5.0-1020-raspi
linux-image-6.5.0-1020-raspi linux-modules-6.5.0-1020-raspi
linux-raspi-headers-6.5.0-1020 netplan-generator python3-netplan
systemd-dev ubuntu-pro-client xml-core


Upgrade: adduser apparmor apport apt apt-transport-https apt-utils
...

y を選択して進めると、無事にアップグレードが完了。

$ cat /etc/os-release
PRETTY_NAME="Ubuntu 23.10"
NAME="Ubuntu"
VERSION_ID="23.10"
VERSION="23.10 (Mantic Minotaur)"
VERSION_CODENAME=mantic
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=mantic
LOGO=ubuntu-logo

再起動して ssh 接続すると下記のようにログが出た。

Your Ubuntu release is not supported anymore.
For upgrade information, please visit:
http://www.ubuntu.com/releaseendoflife

New release '24.04.2 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
$ sudo do-release-upgrade
[sudo] password for mmori:
Checking for a new Ubuntu release
Your Ubuntu release is not supported anymore.
For upgrade information, please visit:
http://www.ubuntu.com/releaseendoflife

Please install all available updates for your release before upgrading.
$ sudo apt update -y && sudo apt upgrade -y
...
E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem.
$ sudo dpkg --configure -a
$ sudo apt update -y && sudo apt upgrade -y
$ sudo systemctl reboot
$ sudo do-release-upgrade

いくつか追加で処理が必要だったが、無事にアップグレードできた。

$ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.2 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo

System76 Meerkat で wifi の問題を調べる

· 約8分

概要: iwlwifi driver のエラー

System76 Meerkat を購入して起動したところ、wifi の接続が不安定だったので調べたときのメモ。

https://system76.com/desktops/meerkat/

基本的には有線接続する予定ですが、wifi module が入っているので試しに wifi に接続したところ boot 直後に一瞬つながるもののその後切れてしまいました。

その後 wifi 設定を開くと No Wi-Fi Adapter Found と表示されている。

journalctl でログを確認すると、以下のようなエラーが出続けているのがわかった。

kernel: iwlwifi 0000:00:14.3: Queue 1 is stuck 1 3
kernel: iwlwifi 0000:00:14.3: Microcode SW error detected. Restarting 0x0.
...
journalctl
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: Queue 1 is stuck 1 3
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: Microcode SW error detected. Restarting 0x0.
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: Start IWL Error Log Dump:
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: Transport status: 0x0000004A, valid: 6
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: Loaded firmware version: 89.e9cec78e.0 ma-b0-gf-a0-89.ucode
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000084 | NMI_INTERRUPT_UNKNOWN
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00800AF4 | trm_hw_status0
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000000 | trm_hw_status1
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x002DDFC2 | branchlink2
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x002D3A62 | interruptlink1
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x002D3A62 | interruptlink2
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x0000841E | data1
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x01000000 | data2
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000000 | data3
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x3B809C27 | beacon time
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x9D2253DC | tsf low
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000013 | tsf hi
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000000 | time gp1
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x017A9FE5 | time gp2
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000001 | uCode revision type
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000059 | uCode version major
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0xE9CEC78E | uCode version minor
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000441 | hw version
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00C80002 | board version
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x0101001C | hcmd
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x80020000 | isr0
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000000 | isr1
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x48F00002 | isr2
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00C3200C | isr3
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00200000 | isr4
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x0201001C | last cmd Id
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x0000841E | wait_event
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000094 | l2p_control
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00002020 | l2p_duration
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x0000000F | l2p_mhvalid
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00060098 | l2p_addr_match
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000009 | lmpm_pmg_sel
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000000 | timestamp
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x000078B4 | flow_handler
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: Start IWL Error Log Dump:
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: Transport status: 0x0000004A, valid: 7
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x20000066 | NMI_INTERRUPT_HOST
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000000 | umac branchlink1
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x8025FE96 | umac branchlink2
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x8028481E | umac interruptlink1
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x8028481E | umac interruptlink2
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x01000000 | umac data1
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x8028481E | umac data2
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000000 | umac data3
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000059 | umac major
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0xE9CEC78E | umac minor
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x017A9FE3 | frame pointer
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0xC0886260 | stack pointer
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x005701D2 | last host cmd
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000400 | isr status reg
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: IML/ROM dump:
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000B03 | IML/ROM error/state
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x000082D5 | IML/ROM data1
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000080 | IML/ROM WFPM_AUTH_KEY_0
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: Fseq Registers:
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x65B00000 | FSEQ_ERROR_CODE
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x80840002 | FSEQ_TOP_INIT_VERSION
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x003B0000 | FSEQ_CNVIO_INIT_VERSION
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x0000A652 | FSEQ_OTP_VERSION
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000003 | FSEQ_TOP_CONTENT_VERSION
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x4552414E | FSEQ_ALIVE_TOKEN
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x01080800 | FSEQ_CNVI_ID
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00400410 | FSEQ_CNVR_ID
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x01080800 | CNVI_AUX_MISC_CHIP
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00400410 | CNVR_AUX_MISC_CHIP
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00009061 | CNVR_SCU_SD_REGS_SD_REG_DIG_DCDC_VTRIM
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00000061 | CNVR_SCU_SD_REGS_SD_REG_ACTIVE_VDIG_MIRROR
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x003B0000 | FSEQ_PREV_CNVIO_INIT_VERSION
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00840002 | FSEQ_WIFI_FSEQ_VERSION
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x00840002 | FSEQ_BT_FSEQ_VERSION
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: 0x000000E6 | FSEQ_CLASS_TP_VERSION
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: UMAC CURRENT PC: 0x802842dc
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: LMAC1 CURRENT PC: 0xd0
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: WRT: Collecting data: ini trigger 4 fired (delay=0ms).
Mar 16 10:59:02 pop-os kernel: ieee80211 phy0: Hardware restart was requested
Mar 16 10:59:02 pop-os kernel: iwlwifi 0000:00:14.3: WRT: Invalid buffer destination
Mar 16 10:59:03 pop-os kernel: iwlwifi 0000:00:14.3: Not valid error log pointer 0x0024B5C0 for RT uCode
Mar 16 10:59:03 pop-os kernel: iwlwifi 0000:00:14.3: WFPM_UMAC_PD_NOTIFICATION: 0x1f
Mar 16 10:59:03 pop-os kernel: iwlwifi 0000:00:14.3: WFPM_LMAC2_PD_NOTIFICATION: 0x1f
Mar 16 10:59:03 pop-os kernel: iwlwifi 0000:00:14.3: WFPM_AUTH_KEY_0: 0x80
Mar 16 10:59:03 pop-os kernel: iwlwifi 0000:00:14.3: CNVI_SCU_SEQ_DATA_DW9: 0x0
Mar 16 10:59:03 pop-os kernel: iwlwifi 0000:00:14.3: RFIm is deactivated, reason = 4

問題が発生した環境は以下の通り。

$ cat /etc/os-release
NAME="Pop!_OS"
VERSION="22.04 LTS"
ID=pop
ID_LIKE="ubuntu debian"
PRETTY_NAME="Pop!_OS 22.04 LTS"
VERSION_ID="22.04"
HOME_URL="https://pop.system76.com"
SUPPORT_URL="https://support.system76.com"
BUG_REPORT_URL="https://github.com/pop-os/pop/issues"
PRIVACY_POLICY_URL="https://system76.com/privacy"
VERSION_CODENAME=jammy
UBUNTU_CODENAME=jammy
LOGO=distributor-logo-pop-os

$ uname -a
Linux pop-os 6.9.3-76060903-generic #202405300957~1738770968~22.04~d5f7c84 SMP PREEMPT_DYNAMIC Wed F x86_64 x86_64 x86_64 GNU/Linux

見つけた情報↓

結論から言うと以下の2パターンで解決できた。

  1. iwlwifi の設定で 11n_disable=1 を設定する (https://support.system76.com/articles/wireless/#n-mode)
  2. 2.5GHz の wifi を使用する

Solution1: iwlwifi の設定で 11n_disable=1 を設定する

/etc/modprobe.d/iwlwifi.conf に以下のように 11n_disable=1 を追加する。

# /etc/modprobe.d/iwlwifi.conf
options iwlwifi power_save=0
options iwlwifi 11n_disable=1
# iwlwifi will dyamically load either iwldvm or iwlmvm depending on the
# microcode file installed on the system. When removing iwlwifi, first
# remove the iwl?vm module and then iwlwifi.
remove iwlwifi \
(/sbin/lsmod | grep -o -e ^iwlmvm -e ^iwldvm -e ^iwlwifi | xargs /sbin/rmmod) \
&& /sbin/modprobe -r mac80211

この設定の意味はざっくりとしかわかっていないが、wifi 規格の 802.11n の機能を有効・無効にするものらしい。

$ modinfo iwlwifi | grep 11n_disable
parm: 11n_disable:disable 11n functionality, bitmap: 1: full, 2: disable agg TX, 4: disable agg RX, 8 enable agg TX (uint)

https://elixir.bootlin.com/linux/v6.9.3/source/drivers/net/wireless/intel/iwlwifi/iwl-modparams.h#L23-L28

1 を設定すると 11n の機能を全て無効にするため、速度は遅くなるが安定するらしい。

ちなみに 2, 4, 6 (2+4), 8 でも動作確認したが、エラーは出続けた。

Solution2: 2.5GHz の wifi を使用する

これは単純に 2.5GHz の wifi を選ぶだけ。

2.5GHz の場合は iwlwifi.conf の設定を変更しなくても問題なく接続できた。

2.5GHz には機能がそもそもないのか実装が違うのかなどは不明。

まとめ

どちらの解決策を選んでもネットワークは遅くなってしまうが、動くことのほうが優先なので仕方ない…。

Redox OS を動かしてみる

· 約12分

Redox OS Image source: https://gitlab.redox-os.org/redox-os/backgrounds/-/tree/master

概要

Redox OS は Rust で書かれた Unix ライクなオペレーティングシステムです。

カーネルのアーキテクチャがマイクロカーネルであることや Scheme 、Everything is URL など、設計にも尖った特徴があります。

今回は Building Redox を参考に Redox OS をビルドして QEMU 上で動かしてみます。

(実機で動かそうとしたら失敗した…)

Prebuild image で動かす

下記に Redox OS の image が置かれているので redox_demo.* のイメージを使って動かしてみる。

https://static.redox-os.org/img/x86_64/

先に結果を言うと、下記のドキュメントにあるように qemu で動かすのが一番簡単だった。

https://doc.redox-os.org/book/ch02-01-running-vm.html

virt-manager + liveiso (or harddrive)

liveiso と harddrive でバージョンが違う場合があるようなので動かない場合は別の方法を試したほうが良さそう。

自分が試したときは harddrive のほうがバージョンが新しく、COSMIC デスクトップからポートされたエディタやファイルマネージャが使えるようになっていた。

起動したときの画面はこんな感じ。

解像度を設定する画面

ログイン画面。デフォルトユーザの情報は下記にある。

https://doc.redox-os.org/book/ch02-01-running-vm.html#using-the-emulation

デスクトップ画面。使用感は結構もっさりしていて、UI のふるさもある。

orbital というデスクトップ環境が使われていて、Terminal が透過しているが、おそらくそのせいでターミナルウィンドウを動かすと遅い。

また、自分がネットワーク周りの設定を理解していないからかもしれないが、ブラウザでネットワークに接続できなかった。

  • メリット
    • gui で比較的簡単に操作できる
  • デメリット
    • ネットワークの設定が少し複雑?
    • デスクトップ環境が重い

qemu + harddrive

下記のドキュメントを参考に qemu で動かす。

https://doc.redox-os.org/book/ch02-01-running-vm.html

image のパスだけ修正した下記コマンドを実行。

SDL_VIDEO_X11_DGAMOUSE=0 qemu-system-x86_64 -d cpu_reset,guest_errors -smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on,"" -serial chardev:debug -mon chardev=debug \
-machine q35 -device ich9-intel-hda -device hda-duplex -netdev user,id=net0 \
-device e1000,netdev=net0 -device nec-usb-xhci,id=xhci -enable-kvm -cpu host \
-drive file=`echo $HOME/os_imgs/redox_demo_x86_64_2024-05-01_993_harddrive.img`,format=raw

実行するとこんな出力が出る。

最後に VNC server running on ::1:5900 と出たので remmina で接続したところ virt-manager と同じく解像度の設定画面 -> ログイン画面 -> デスクトップ画面の流れで動かせた。

解像度設定画面

ログイン画面

デスクトップ画面

virt-manager と比べて動作は軽く、ネットワークも問題なく接続できた。

virt-manager が悪いとかではなくバージョンが違うせいかもしれない。(liveiso => v0.3.4, harddrive => v0.4.1)

  • メリット
    • ドキュメントに書いてあるとおりにやれば基本的に動く
    • ネットワークの設定をコマンドオプションで指定しただけでつながる
    • (デスクトップ環境が軽い)
  • デメリット
    • GUI がある desktop のイメージを動かすときに VNC client が必要 (server なら関係ない)

build して動かす

Redox のビルドシステム・ディレクトリ構成

Redox OS は redox-os/redox がメインのリポジトリでその下に submodule として filesystem のリポジトリや build システムの cookbook がある。

redox
├── bootstrap.sh* <-- ビルド環境を構築するスクリプト
├── build/ <-- build して生成された image ファイルなどが置かれる
├── build.sh* <-- イメージのビルドに使えるスクリプト(直接 make でビルドしたので今回は使わなかった)
├── config/ <-- ビルド時に含めるパッケージ情報がある
├── CONTRIBUTING.md
├── cookbook/ <-- kernel やアプリケーションのビルドスクリプトなどが submodule として置かれている
├── docker/
├── HARDWARE.md
├── installer/ <-- make でビルドするときに config を読んで package のリストを取得するために使われている
├── LICENSE
├── Makefile
├── mk/ <-- Makefile で include されるファイル
├── podman/
├── podman_bootstrap.sh*
├── prefix/
├── README.md
├── redoxfs/ <-- filesystem のリポジトリ
├── relibc/ <-- redox の libc
├── rust/
├── rust-toolchain.toml
└── scripts/

cookbook の中に recipe.toml があり、カーネルやアプリケーションのビルド方法が書かれている。

redox-os/cookbook

ビルド

ビルドは下記のように実行する。

make all CONFIG_NAME=<config>

# image が生成されて qemu で実行される
make qemu CONFIG_NAME=<config>

CONFIG_NAME は config/ にある toml ファイルの名前を指定する。(e.g. server.toml => server)

config/
├── aarch64/
│ └── ...
├── acid.toml
├── base.toml
├── desktop-minimal.toml
├── desktop.toml
├── dev.toml
├── i686/
│ └── ...
├── net.toml
├── redoxer-gui.toml
├── redoxer.toml
├── resist.toml
├── server-minimal.toml
├── server.toml
└── x86_64/
├── <config>.toml
├── acid.toml
├── ci.toml
├── demo.toml
├── desktop-contain.toml
├── desktop-minimal.toml
├── desktop.toml
├── dev.toml
├── jeremy.toml
├── resist.toml
├── server-minimal.toml
└── server.toml

Building Redox

試しに systemcall を追加してみる

"hello world" を出力するだけの system call を追加してみる。

ref. https://gitlab.redox-os.org/Forest0923/redox/-/tree/add-helloworld-syscall?ref_type=heads

一度 make all でビルドした場合、redox リポジトリ下の cookbook/recipes/core/kernel/source/syscall に kernel のソースコードがダウンロードされるので下記の caller, handler を追加する。

また、追加したシステムコールを呼び出すアプリケーションを boot した redox 上でコンパイルして実行する方法がぱっと見てわからなかったので、ビルド段階でテスト用のアプリケーションを追加することにした。

caller (library)

アプリケーション側から呼び出すためのライブラリが redox-os/syscall にある。

src/caller.rs に system call の呼び出しを行う関数が定義されているのでここに helloworld() を追加。

/// Hello world
pub fn helloworld() -> Result<usize> {
unsafe { syscall0(SYS_HELLOWORLD) }
}

src/number.rs に system call の番号を定義する。

pub const SYS_HELLOWORLD: usize = 999;

ちなみに syscall0 の関数は x86_64 向けの場合はマクロで下記のように定義されている。

ref. https://gitlab.redox-os.org/Forest0923/syscall/-/blob/add-helloworld-syscall/src/arch/x86_64.rs?ref_type=heads#L9-49

macro_rules! syscall {
($($name:ident($a:ident, $($b:ident, $($c:ident, $($d:ident, $($e:ident, $($f:ident, )?)?)?)?)?);)+) => {
$(
pub unsafe fn $name(mut $a: usize, $($b: usize, $($c: usize, $($d: usize, $($e: usize, $($f: usize)?)?)?)?)?) -> Result<usize> {
asm!(
"syscall",
inout("rax") $a,
$(
in("rdi") $b,
$(
in("rsi") $c,
$(
in("rdx") $d,
$(
in("r10") $e,
$(
in("r8") $f,
)?
)?
)?
)?
)?
out("rcx") _,
out("r11") _,
options(nostack),
);

Error::demux($a)
}
)+
};
}

syscall! {
syscall0(a,);
syscall1(a, b,);
syscall2(a, b, c,);
syscall3(a, b, c, d,);
syscall4(a, b, c, d, e,);
syscall5(a, b, c, d, e, f,);
}

syscall handler

kernel 側では syscall 命令が実行されたとき用にハンドラの実装が必要で redox-os/kernel にカーネル側のコードがある。

x86_64 の場合、MSR_LSTAR にシステムコールハンドラのアドレスを設定する。 その処理を行っているのは src/arch/x86_64/interrupt/syscall.rs で下記のように wrmsr で syscall_instruction を登録している。

pub unsafe fn init() {
// IA32_STAR[31:0] are reserved.

// The base selector of the two consecutive segments for kernel code and the immediately
// suceeding stack (data).
let syscall_cs_ss_base = (gdt::GDT_KERNEL_CODE as u16) << 3;
// The base selector of the three consecutive segments (of which two are used) for user code
// and user data. It points to a 32-bit code segment, which must be followed by a data segment
// (stack), and a 64-bit code segment.
let sysret_cs_ss_base = ((gdt::GDT_USER_CODE32_UNUSED as u16) << 3) | 3;
let star_high = u32::from(syscall_cs_ss_base) | (u32::from(sysret_cs_ss_base) << 16);

msr::wrmsr(msr::IA32_STAR, u64::from(star_high) << 32);
msr::wrmsr(msr::IA32_LSTAR, syscall_instruction as u64);
...

syscall_instruction は linux の entry_SYSCALL_64 で見たことがありそうな感じ。

#[no_mangle]
pub unsafe extern "C" fn __inner_syscall_instruction(stack: *mut InterruptStack) {
let allowed = ptrace::breakpoint_callback(PTRACE_STOP_PRE_SYSCALL, None)
.and_then(|_| ptrace::next_breakpoint().map(|f| !f.contains(PTRACE_FLAG_IGNORE)));

if allowed.unwrap_or(true) {
let scratch = &(*stack).scratch;

syscall::syscall(
scratch.rax,
scratch.rdi,
scratch.rsi,
scratch.rdx,
scratch.r10,
scratch.r8,
&mut *stack,
);
}

ptrace::breakpoint_callback(PTRACE_STOP_POST_SYSCALL, None);
}

#[naked]
#[allow(named_asm_labels)]
pub unsafe extern "C" fn syscall_instruction() {
core::arch::asm!(concat!(
// Yes, this is magic. No, you don't need to understand
"swapgs;", // Swap KGSBASE with GSBASE, allowing fast TSS access.
"mov gs:[{sp}], rsp;", // Save userspace stack pointer
"mov rsp, gs:[{ksp}];", // Load kernel stack pointer
"push QWORD PTR {ss_sel};", // Push fake userspace SS (resembling iret frame)
"push QWORD PTR gs:[{sp}];", // Push userspace rsp
"push r11;", // Push rflags
"push QWORD PTR {cs_sel};", // Push fake CS (resembling iret stack frame)
"push rcx;", // Push userspace return pointer

// Push context registers
"push rax;",
push_scratch!(),
push_preserved!(),

// TODO: Map PTI
// $crate::arch::x86_64::pti::map();

// Call inner funtion
"mov rdi, rsp;",
"call __inner_syscall_instruction;",
...

__inner_syscall_instructionsyscall::syscall は下記のように実装されている。syscall::syscall()

/// This function is the syscall handler of the kernel, it is composed of an inner function that returns a `Result<usize>`. After the inner function runs, the syscall
/// function calls [`Error::mux`] on it.
pub fn syscall(
a: usize,
b: usize,
c: usize,
d: usize,
e: usize,
f: usize,
stack: &mut InterruptStack,
) {
#[inline(always)]
fn inner(a: usize, b: usize, c: usize, d: usize, e: usize, f: usize) -> Result<usize> {
//SYS_* is declared in kernel/syscall/src/number.rs
match a & SYS_CLASS {
SYS_CLASS_FILE => {
let fd = FileHandle::from(b);
match a & SYS_ARG {
SYS_ARG_SLICE => match a {
SYS_WRITE => file_op_generic(fd, |scheme, number| {
scheme.kwrite(number, UserSlice::ro(c, d)?)
}),
...
_ => return Err(Error::new(ENOSYS)),
},
SYS_ARG_MSLICE => match a {
SYS_READ => file_op_generic(fd, |scheme, number| {
scheme.kread(number, UserSlice::wo(c, d)?)
}),
...
_ => return Err(Error::new(ENOSYS)),
},
_ => match a {
...
SYS_CLOSE => close(fd).map(|()| 0),
_ => return Err(Error::new(ENOSYS)),
},
}
}
SYS_CLASS_PATH => match a {
SYS_OPEN => open(UserSlice::ro(b, c)?, d).map(FileHandle::into),
...
_ => Err(Error::new(ENOSYS)),
},
_ => match a {
...
SYS_HELLOWORLD => helloworld().map(|()| 0),

_ => Err(Error::new(ENOSYS)),
},
}
}
...
}

SYS_HELLOWORLD のときの実際の処理は helloworld() で下記のように定義

ref. https://gitlab.redox-os.org/Forest0923/kernel/-/blob/4c750d4acb233777e12ded8a4a19dff99fb28f0b/src/syscall/hello.rs

use crate::syscall::error::*;
pub fn helloworld() -> Result<()> {
println!("SYS_HELLOWORLD: Hello, world!");
Ok(())
}

sample application

sys_helloworld を呼び出すアプリケーションを追加する。

https://gitlab.redox-os.org/Forest0923/helloworld

recipe の書き方は下記のようになっている。

https://doc.redox-os.org/book/ch09-01-including-programs.html#setting-up-the-recipe

[source]
git = "<repository url>"
branch = "<branch name>"

[build]
template = "custom" # custom, cargo, ...
script = """
echo "build script"
"""

redox-os/redox で config/x86_64/forest0923.toml に

include = ["../desktop.toml"]

[packages]
helloworld = {}
orbterm = {}

実行したところ QEMU のログに SYS_HELLOWORLD: Hello, world! と出力され、追加したシステムコールを呼び出すことができた。

% make qemu CONFIG_NAME=forest0923
SDL_VIDEO_X11_DGAMOUSE=0 qemu-system-x86_64 -d guest_errors -name "Redox OS x86_64" -device nec-usb-xhci,id=xhci -smp 4 -m 2048 -chardev stdio,id=debug,signal=off,mux=on,"" -serial chardev:debug -mon chardev=debug -machine q35 -device ich9-intel-hda -device hda-output -netdev user,id=net0 -device e1000,netdev=net0 -object filter-dump,id=f1,netdev=net0,file=build/x86_64/forest0923/network.pcap -enable-kvm -cpu host \
-drive file=build/x86_64/forest0923/harddrive.img,format=raw \
-drive file=build/x86_64/forest0923/extra.img,format=raw
VNC server running on ::1:5900

...

########## Redox OS ##########
# Login with the following: #
# `user` #
# `root`:`password` #
##############################

redox login: DHCP: Offer IP: [10, 0, 2, 15], Server IP: [10, 0, 2, 2]
DHCP: Message Type: [2]
DHCP: Server ID: [10, 0, 2, 2]
DHCP: Subnet Mask: [255, 255, 255, 0]
DHCP: Router: [10, 0, 2, 2]
DHCP: Domain Name Server: [10, 0, 2, 3]
DHCP: Lease Time: [0, 1, 81, 128]
DHCP: New IP: 10.0.2.15/24
DHCP: New Router: default via 10.0.2.2 dev eth0 src 10.0.2.15
127.0.0.0/8 dev loopback src 127.0.0.1
10.0.2.0/24 dev eth0 src 10.0.2.15
DHCP: New DNS: 10.0.2.3
DHCP: Sent Request
DHCP: Ack IP: [10, 0, 2, 15], Server IP: [10, 0, 2, 2]
SYS_HELLOWORLD: Hello, world!

おわり

Redox OS はまだ開発段階で不足している機能が多いですが、コンセプトも面白いし、継続的に開発もされているので引き続き注目していきたい。

おまけ

Linux などと比べたときの機能の違い

Rust で書かれた OS たち

Container の互換性を調べていたら色々と脱線した話

· 約14分

概要

Docker などのコンテナ技術では、ホスト OS のカーネルを共有してコンテナイメージにディストリビューションやアプリケーションの依存関係を含めることで、異なる実行環境を再現しています。

このとき、ホスト OS とコンテナイメージのディストリビューションが異なるとカーネルの違いによって原理的には互換性の問題が発生するはずです。 今回は実際に古いカーネルを使用しているホスト OS 上で比較的新しいカーネルを使用しているディストリビューションを使用してアプリケーションを実行してどのような問題が起こるのか試してみました。

実行環境

  • env1

    • Host OS:
      • [mmori@localhost ~]$ cat /etc/redhat-release
        CentOS Linux release 7.9.2009 (Core)
        [mmori@localhost ~]$ uname -a
        Linux localhost.localdomain 3.10.0-1160.71.1.el7.x86_64 #1 SMP Tue Jun 28 15:37:28 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
    • Container:
      • [mmori@localhost ~]$ sudo docker image ls
        REPOSITORY TAG IMAGE ID CREATED SIZE
        rockylinux 9.3 b72d2d915008 5 months ago 176MB
  • env2

    • Host OS:
      • [mmori@localhost ~]$ cat /etc/redhat-release
        Rocky Linux release 9.3 (Blue Onyx)
        [mmori@localhost ~]$ uname -a
        Linux localhost.localdomain 5.14.0-362.8.1.el9_3.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Nov 8 17:36:32 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
    • Container:
      • [mmori@localhost ~]$ sudo podman image ls
        REPOSITORY TAG IMAGE ID CREATED SIZE
        quay.io/centos/centos centos7.9.2009 8652b9f0cb4c 3 years ago 212 MB

方針

カーネルのバージョンを確認して実装されているシステムコールの差分から互換性の問題を調査する。

Red Hat Enterprise Linux のリリース日 を見ると、RHEL 7 と RHEL 9 のカーネルバージョンは以下の通り。

リリースカーネルバージョン
RHEL 7.93.10.0-1160
RHEL 9.35.14.0-362.8.1.el9_3

Linux v3.10.0 と v5.14.0 のシステムコールのテーブルを確認する。

...
309 common getcpu sys_getcpu
310 64 process_vm_readv sys_process_vm_readv
311 64 process_vm_writev sys_process_vm_writev
312 common kcmp sys_kcmp
313 common finit_module sys_finit_module

#
# x32-specific system call numbers start at 512 to avoid cache impact
# for native 64-bit operation.
#
512 x32 rt_sigaction compat_sys_rt_sigaction
513 x32 rt_sigreturn stub_x32_rt_sigreturn
514 x32 ioctl compat_sys_ioctl
515 x32 readv compat_sys_readv
516 x32 writev compat_sys_writev
...
...
309 common getcpu sys_getcpu
310 64 process_vm_readv sys_process_vm_readv
311 64 process_vm_writev sys_process_vm_writev
312 common kcmp sys_kcmp
313 common finit_module sys_finit_module
314 common sched_setattr sys_sched_setattr
315 common sched_getattr sys_sched_getattr
316 common renameat2 sys_renameat2
317 common seccomp sys_seccomp
318 common getrandom sys_getrandom
319 common memfd_create sys_memfd_create
320 common kexec_file_load sys_kexec_file_load
321 common bpf sys_bpf
322 64 execveat sys_execveat
323 common userfaultfd sys_userfaultfd
324 common membarrier sys_membarrier
325 common mlock2 sys_mlock2
326 common copy_file_range sys_copy_file_range
327 64 preadv2 sys_preadv2
328 64 pwritev2 sys_pwritev2
329 common pkey_mprotect sys_pkey_mprotect
330 common pkey_alloc sys_pkey_alloc
331 common pkey_free sys_pkey_free
332 common statx sys_statx
333 common io_pgetevents sys_io_pgetevents
334 common rseq sys_rseq
# don't use numbers 387 through 423, add new calls after the last
# 'common' entry
424 common pidfd_send_signal sys_pidfd_send_signal
425 common io_uring_setup sys_io_uring_setup
426 common io_uring_enter sys_io_uring_enter
427 common io_uring_register sys_io_uring_register
428 common open_tree sys_open_tree
429 common move_mount sys_move_mount
430 common fsopen sys_fsopen
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
435 common clone3 sys_clone3
436 common close_range sys_close_range
437 common openat2 sys_openat2
438 common pidfd_getfd sys_pidfd_getfd
439 common faccessat2 sys_faccessat2
440 common process_madvise sys_process_madvise
441 common epoll_pwait2 sys_epoll_pwait2
442 common mount_setattr sys_mount_setattr
443 common quotactl_fd sys_quotactl_fd
444 common landlock_create_ruleset sys_landlock_create_ruleset
445 common landlock_add_rule sys_landlock_add_rule
446 common landlock_restrict_self sys_landlock_restrict_self
447 common memfd_secret sys_memfd_secret

#
# Due to a historical design error, certain syscalls are numbered differently
# in x32 as compared to native x86_64. These syscalls have numbers 512-547.
# Do not add new syscalls to this range. Numbers 548 and above are available
# for non-x32 use.
#
512 x32 rt_sigaction compat_sys_rt_sigaction
513 x32 rt_sigreturn compat_sys_x32_rt_sigreturn
514 x32 ioctl compat_sys_ioctl
515 x32 readv sys_readv
516 x32 writev sys_writev
...

簡単に使えそうなシステムコールとして 318 番の getrandom があるのでこれを使ってみる。

調査、テストコード

getrandom

SYSCALL_DEFINE3(getrandom, char __user *, ubuf, size_t, len, unsigned int, flags)
{
struct iov_iter iter;
int ret;

if (flags & ~(GRND_NONBLOCK | GRND_RANDOM | GRND_INSECURE))
return -EINVAL;

/*
* Requesting insecure and blocking randomness at the same time makes
* no sense.
*/
if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM))
return -EINVAL;

if (!crng_ready() && !(flags & GRND_INSECURE)) {
if (flags & GRND_NONBLOCK)
return -EAGAIN;
ret = wait_for_random_bytes();
if (unlikely(ret))
return ret;
}

ret = import_ubuf(ITER_DEST, ubuf, len, &iter);
if (unlikely(ret))
return ret;
return get_random_bytes_user(&iter);
}
  • test code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

#define BUF_SIZE 16

void print_buf(char *buf, int size) {
printf("buf = ");
for (int i = 0; i < size; i++) {
printf("%02x,", (unsigned char)buf[i]);
}
printf("\n");
}

int main() {
int ret;
char *buf = (char *) malloc(sizeof(char) * BUF_SIZE);
memset(buf, 0, BUF_SIZE);
print_buf(buf, BUF_SIZE);
ret = syscall(SYS_getrandom, buf, BUF_SIZE, 0);
printf("ret = %d\n", ret);
print_buf(buf, BUF_SIZE);
}

pidfd_open

最初は getrandom だけで試すつもりだったが、実際に実行すると centos7 のカーネルでも getrandom が使えてしまった。 (RHEL 7 のベースとなるバージョンは Linux v3.10.0 で getrandom は実装されていないはずだが、3.10.0-1160.71.1.el7.x86_64 では追加のパッチがあたっているのだろう。)

追加で pidfd_open を試してみる。

存在する pid を引数にして実行すると process を参照する file descriptor を取得できる。 man page の use case を見ると使いみちが色々とある。

   Use cases for PID file descriptors
A PID file descriptor returned by pidfd_open() (or by clone(2)
with the CLONE_PID flag) can be used for the following purposes:

• The pidfd_send_signal(2) system call can be used to send a
signal to the process referred to by a PID file descriptor.

• A PID file descriptor can be monitored using poll(2),
select(2), and epoll(7). When the process that it refers to
terminates, these interfaces indicate the file descriptor as
readable. Note, however, that in the current implementation,
nothing can be read from the file descriptor (read(2) on the
file descriptor fails with the error EINVAL).

• If the PID file descriptor refers to a child of the calling
process, then it can be waited on using waitid(2).

• The pidfd_getfd(2) system call can be used to obtain a
duplicate of a file descriptor of another process referred to
by a PID file descriptor.

• A PID file descriptor can be used as the argument of setns(2)
in order to move into one or more of the same namespaces as
the process referred to by the file descriptor.

• A PID file descriptor can be used as the argument of
process_madvise(2) in order to provide advice on the memory
usage patterns of the process referred to by the file
descriptor.

process の監視などに使えそう。 調べてみると python の新しい機能として pidfd が使われるようになっているらしい。

実装や man page については以下の通り。

/**
* sys_pidfd_open() - Open new pid file descriptor.
*
* @pid: pid for which to retrieve a pidfd
* @flags: flags to pass
*
* This creates a new pid file descriptor with the O_CLOEXEC flag set for
* the task identified by @pid. Without PIDFD_THREAD flag the target task
* must be a thread-group leader.
*
* Return: On success, a cloexec pidfd is returned.
* On error, a negative errno number will be returned.
*/
SYSCALL_DEFINE2(pidfd_open, pid_t, pid, unsigned int, flags)
{
int fd;
struct pid *p;

if (flags & ~(PIDFD_NONBLOCK | PIDFD_THREAD))
return -EINVAL;

if (pid <= 0)
return -EINVAL;

p = find_get_pid(pid);
if (!p)
return -ESRCH;

fd = pidfd_create(p, flags);

put_pid(p);
return fd;
}
  • test code (ただ呼び出すだけのコード)
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
int pid = getpid();
if (argc != 1) {
pid = atoi(argv[1]);
}
printf("pid: %d\n", pid);
int pidfd = syscall(SYS_pidfd_open, pid, 0);
if (pidfd < 0) {
perror("pidfd_open");
return 1;
}
printf("pidfd: %d\n", pidfd);
sleep(1000);
close(pidfd);
return 0;
}
  • test code (man page のコード)
    • 監視対象のプロセスが終了すると poll で検知できる
#define _GNU_SOURCE
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>

static int
pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(SYS_pidfd_open, pid, flags);
}

int
main(int argc, char *argv[])
{
int pidfd, ready;
struct pollfd pollfd;

if (argc != 2) {
fprintf(stderr, "Usage: %s <pid>\n", argv[0]);
exit(EXIT_SUCCESS);
}

pidfd = pidfd_open(atoi(argv[1]), 0);
if (pidfd == -1) {
perror("pidfd_open");
exit(EXIT_FAILURE);
}

pollfd.fd = pidfd;
pollfd.events = POLLIN;

ready = poll(&pollfd, 1, -1);
if (ready == -1) {
perror("poll");
exit(EXIT_FAILURE);
}

printf("Events (%#x): POLLIN is %sset\n", pollfd.revents,
(pollfd.revents & POLLIN) ? "" : "not ");

close(pidfd);
exit(EXIT_SUCCESS);
}

実験

getrandom

getrandom を実行してみる。 (どちらでも実行できてしまったので今回の趣旨とは異なるが、一応載せておく)

env1

[root@4871234db7f8 workspace]# gcc getrandom.c
[root@4871234db7f8 workspace]# ./a.out
buf = 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
ret = 16
buf = 82,92,6f,1b,87,18,1e,8a,d8,ac,7e,5a,f7,9b,59,18,

env2

[root@4b84690c185e workspace]# gcc getrandom.c
[root@4b84690c185e workspace]# ./a.out
buf = 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
ret = 16
buf = 19,95,66,4c,43,bb,5f,83,b5,44,2e,6c,db,92,90,66,

pidfd_open

sleep するだけのプロセスを起動して pidfd_open で file descriptor を取得してみる。

#include <stdio.h>
#include <unistd.h>

int main() {
printf("pid: %d\n", getpid());
sleep(1000);
}

env1

host OS が centos でシステムコールが実装されていないのでエラーになる。

[root@4871234db7f8 workspace]# ./sleep
pid: 80
[root@4871234db7f8 workspace]# ./pidfd_open 80
pid: 80
pidfd_open: Function not implemented

v3.10 では do_syscall64 がなく、arch/x86/kernel/entry_64.S で syscall table を直接 call している。 直前で NR_syscall のバリデーションをしているのでここでエラーハンドリング用の badsys に飛ばされる。

飛ばされたあとはリターンしてユーザプロセスに実行が遷移し、libc の syscall() でエラーハンドリングされているはず。

env2

centos のコンテナではライブラリが古いので SYS_pidfd_open が定義されておらず、コンパイルエラーになる。

[root@4b84690c185e workspace]# gcc pidfd_open.c -o pidfd_open
pidfd_open.c: In function 'main':
pidfd_open.c:12:22: error: 'SYS_pidfd_open' undeclared (first use in this function)
int pidfd = syscall(SYS_pidfd_open, pid, 0);
^
pidfd_open.c:12:22: note: each undeclared identifier is reported only once for each function it appears in

pidfd_open は 434 番なので自分で定数を定義してコンパイルする。

#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>

+#define SYS_pidfd_open 434
+
int main(int argc, char *argv[]) {
int pid = getpid();
if (argc != 1) {
pid = atoi(argv[1]);
}
printf("pid: %d\n", pid);
int pidfd = syscall(SYS_pidfd_open, pid, 0);
if (pidfd < 0) {
perror("pidfd_open");
return 1;
}
printf("pidfd: %d\n", pidfd);
sleep(1000);
close(pidfd);
return 0;
}

実行できた。

[root@4b84690c185e workspace]# ./sleep
pid: 165
[root@4b84690c185e workspace]# ./pidfd_open 165
pid: 165
pidfd: 3

このとき、開かれた fd は procfs でみると下記のようにリンクが張られている。

[root@4b84690c185e workspace]# ls -la /proc/166/fd
total 0
dr-x------. 2 root root 0 May 3 05:28 .
dr-xr-xr-x. 9 root root 0 May 3 05:23 ..
lrwx------. 1 root root 64 May 3 05:28 0 -> /dev/pts/2
lrwx------. 1 root root 64 May 3 05:28 1 -> /dev/pts/2
lrwx------. 1 root root 64 May 3 05:28 2 -> /dev/pts/2
lrwx------. 1 root root 64 May 3 05:28 3 -> anon_inode:[pidfd]

pidfd_open + poll

man page にあるコードを使用してプロセスの監視を行ってみる。

env1

実行できないのはわかっているのでスキップ

env2

先ほどと同様に sleep するプロセスを起動して監視してみる。

[root@4b84690c185e workspace]# ./sleep
pid: 185
^C

ctrl+c で終了させると poll が検知して終了した。

[root@4b84690c185e workspace]# ./pidfd_open_poll 185
Events (0x1): POLLIN is set

追加 (pidfd_send_signal)

pidfd_send_signal を使ってみる。

こちらも試すだけなら man page のコードを使うだけで良さそう。

#define _GNU_SOURCE
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>

static int pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
unsigned int flags)
{
return syscall(SYS_pidfd_send_signal, pidfd, sig, info, flags);
}

int main(int argc, char *argv[])
{
int pidfd, sig;
char path[PATH_MAX];
siginfo_t info;

if (argc != 3) {
fprintf(stderr, "Usage: %s <pid> <signal>\n", argv[0]);
exit(EXIT_FAILURE);
}

sig = atoi(argv[2]);

/* Obtain a PID file descriptor by opening the /proc/PID directory
of the target process. */

snprintf(path, sizeof(path), "/proc/%s", argv[1]);

pidfd = open(path, O_RDONLY);
if (pidfd == -1) {
perror("open");
exit(EXIT_FAILURE);
}

/* Populate a 'siginfo_t' structure for use with
pidfd_send_signal(). */

memset(&info, 0, sizeof(info));
info.si_code = SI_QUEUE;
info.si_signo = sig;
info.si_errno = 0;
info.si_uid = getuid();
info.si_pid = getpid();
info.si_value.sival_int = 1234;

/* Send the signal. */

if (pidfd_send_signal(pidfd, sig, &info, 0) == -1) {
perror("pidfd_send_signal");
exit(EXIT_FAILURE);
}

exit(EXIT_SUCCESS);
}

sleep プロセスを作成して SIGTERM(15) を送信したところプロセスを終了させることができた。

[root@4b84690c185e workspace]# ./sleep
pid: 222
Terminated
[root@4b84690c185e workspace]# ./pidfd_send_signal 222 15

まとめ

  • Container では host os のカーネルを共有しているため、期待されるカーネルバージョンの違いによってうまく動作しないアプリケーションがある。
  • Systemcall の実装状況も影響するため、互換性を確認する際には注意が必要。
  • 自分の周囲ではこの問題がそこまで指摘されていない(顕在化していないのは)ホストOS のバージョンが比較的新しいことや新しいシステムコールなどがそこまで使用されていないからかもしれない。
    • python3.9 では pidfd が使われているようなので、centos 上のコンテナで新しい python を使うと問題が起こるかもしれない
  • pidfd + poll を使ったプロセス監視や signal の送信などが単純に面白かった

Rust in Linux part two

· 約15分

目的

以前にうまく行かなかった kernel 内での Rust の使用を再度試してみたい。

最近 rust で LKM を作成する記事を新たにいくつか見かけたので、それらを参考にしたら今回こそはうまく行くのではないかと思っています。

やること

前回は VM に Fedra をインストールして試していましたが、今回は実機の Arch Linux を使用します。

デフォルトの linux パッケージでは Rust を使用するためのオプションが有効になっていないので、カーネルをビルドする必要があります。

grep CONFIG_RUST /usr/src/linux/.config
# No output

LKM については先程の記事を参考にして、Kernel 内にある rust_minimal.rs をロードできるようにします。

具体的には下記のようになります。

.
├── Makefile
└── src
├── rust_minimal.rs
└── Makefile
Makefile
KVER ?= $(shell uname -r)
KDIR ?= /usr/lib/modules/$(KVER)/build

RUSTFMT = rustfmt
RUST_FLAGS = CROSS_COMPILE=x86_64-linux-gnu-
RUST_FLAGS += HOSTRUSTC=rustc
RUST_FLAGS += RUSTC=rustc
RUST_FLAGS += BINDGEN=bindgen
RUST_FLAGS += RUSTFMT=$(RUSTFMT)
RUST_FLAGS += RUST_LIB_SRC=$(shell rustc --print sysroot)/lib/rustlib/src/rust/library

default:
$(MAKE) LLVM=1 $(RUST_FLAGS) -C $(KDIR) M=$$PWD/src

install: default
kmodsign sha512 \
/var/lib/shim-signed/mok/MOK.priv \
/var/lib/shim-signed/mok/MOK.der \
src/hello.ko
$(MAKE) -C $(KDIR) M=$$PWD/src modules_install
depmod -A

fmt:
find . -type f -name '*.rs' | xargs $(RUSTFMT)

clean:
$(MAKE) $(RUNST_FLAGS) -C $(KDIR) M=$$PWD/src clean
src/rust_minimal.rs
// SPDX-License-Identifier: GPL-2.0

//! Rust minimal sample.

use kernel::prelude::*;

module! {
type: RustMinimal,
name: "rust_minimal",
author: "Rust for Linux Contributors",
description: "Rust minimal sample",
license: "GPL",
}

struct RustMinimal {
numbers: Vec<i32>,
}

impl kernel::Module for RustMinimal {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Rust minimal sample (init)\n");
pr_info!("Am I built-in? {}\n", !cfg!(MODULE));

let mut numbers = Vec::new();
numbers.try_push(72)?;
numbers.try_push(108)?;
numbers.try_push(200)?;

Ok(RustMinimal { numbers })
}
}

impl Drop for RustMinimal {
fn drop(&mut self) {
pr_info!("My numbers are {:?}\n", self.numbers);
pr_info!("Rust minimal sample (exit)\n");
}
}
src/Makefile
obj-m := rust_minimal.o

以下は試行錯誤の記録です。

方法1:AUR の linux-rust を使用する (失敗した)

前回はカーネルビルドでそもそも失敗していた記憶があるので、試しに AUR で調べてみると rust が有効になっているらしいカーネル (v6.6) を発見。(前回参考にした記事を書いていた人がメンテナになっていた)

試しにインストールしてみる。

paru -S linux-rust でインストールできますが、make がシングルスレッドになっていたり、ドキュメントをビルドするようになっていたりするので、少し調整してからビルドします。

Build

PKGBUILD を取得:

git clone git@github.com:rnestler/archpkg-linux-rust.git

PKGBUILD を編集:

diff --git a/PKGBUILD b/PKGBUILD
index 7f0db73..b859ccc 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -90,8 +90,8 @@ prepare() {

build() {
cd $_srcname
- make LLVM=1 all
- make LLVM=1 htmldocs
+ make LLVM=1 all -j5
+ # make LLVM=1 htmldocs
}

_package() {
@@ -245,7 +245,7 @@ _package-docs() {
pkgname=(
"$pkgbase"
"$pkgbase-headers"
- "$pkgbase-docs"
+ # "$pkgbase-docs"
)
for _p in "${pkgname[@]}"; do
eval "package_$_p() {

build:

makepkg -s

このままビルドすると headers のパッケージを作成するときの _package-headers() で target.json がないというエラーが発生。

_package-headers() {
...
# Rust support
echo "Installing Rust files..."
install -Dt "$builddir/rust" -m644 scripts/target.json
install -Dt "$builddir/rust" -m644 rust/*.rmeta
install -Dt "$builddir/rust" -m644 rust/*.so
install -Dt "$builddir" -m644 ../../rust-toolchain
...
}

実際に pkg/ 以下のビルド時のディレクトリを見てみるとたしかに scripts/target.json がない。

target.json は rust の cross compile の設定に使われるファイルらしく以下のようになっていれば良いらしい。 (https://github.com/archlinux/linux より)

{
"arch": "x86_64",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128",
"features": "-3dnow,-3dnowa,-mmx,+soft-float,+retpoline-external-thunk",
"llvm-target": "x86_64-linux-gnu",
"target-pointer-width": "64",
"emit-debug-gdb-scripts": false,
"frame-pointer": "may-omit",
"stack-probes": {"kind": "none"}
}

再度カーネルビルドすると今度は問題なく終了。

*.pkg.tar.zst が生成されるので、これをインストールする。

sudo pacman -U linux-rust-6.6.10.arch1-1-x86_64.pkg.tar.zst linux-rust-headers-6.6.10.arch1-1-x86_64.pkg.tar.zst

Setup bootloader

Systemd-boot の場合、以下のようにエントリーを追加する。 (option は環境によると思うので linux.conf からコピーする)

sudo nvim /boot/loader/entries/linux-rust.conf
/boot/loader/entries/linux-rust.conf
title Arch Linux (linux-rust)
linux /vmlinuz-linux-rust
initrd /initramfs-linux-rust.img
options <options>

grub の場合は(多分)以下のように設定をアップデートすればOK。

sudo grub-mkconfig -o /boot/grub/grub.cfg

reboot

LKM のビルド

このカーネルをインストールして LKM を作成してみたが、モジュールのビルドに失敗してしまった。 (エラーログを記録するのを忘れていたけど、ざっくり言うと core crate などの依存関係が解決できないという感じだった)

Makefile では RUST_LIB_SRC=$(shell rustc --print sysroot)/lib/rustlib/src/rust/library としていて、実際にこのディレクトリを確認したら core などの crate は存在していた。

RUST_LIB_SRC が正しく設定されていないのだろうと思ったので、原因を調べるために RUST_LIB_SRC がどのように使われているのかを調べてみた。

GitHub から clone した linux kernel で検索すると下記のように rust/MakefileRUST_LIB_SRC が使用されていた。

% fd -tf -x grep -Hni --color=always "RUST_LIB_SRC"
./Makefile:584:ifdef RUST_LIB_SRC
./Makefile:585: export RUST_LIB_SRC
./scripts/rust_is_available_test.py:271: result = self.run_script(self.Expected.FAILURE, { "RUST_LIB_SRC": self.missing })
./scripts/rust_is_available.sh:253:rustc_src=${RUST_LIB_SRC:-"$rustc_sysroot/lib/rustlib/src/rust/library"}
./rust/Makefile:45:RUST_LIB_SRC ?= $(rustc_sysroot)/lib/rustlib/src/rust/library
./rust/Makefile:112:rustdoc-core: $(RUST_LIB_SRC)/core/src/lib.rs FORCE
./rust/Makefile:411: $(RUST_LIB_SRC) $(KBUILD_EXTMOD) > \
./rust/Makefile:431:$(obj)/core.o: $(RUST_LIB_SRC)/core/src/lib.rs scripts/target.json FORCE

しかし、/usr/lib/modules/$(KVER)/build の中で RUST_LIB_SRC を検索してみると、なぜか rust/ ディレクトリが見つからなかった。

% fd -tf -x grep -Hni --color=always "RUST_LIB_SRC"
./Makefile:584:ifdef RUST_LIB_SRC
./Makefile:585: export RUST_LIB_SRC
./scripts/rust_is_available_test.py:271: result = self.run_script(self.Expected.FAILURE, { "RUST_LIB_SRC": self.missing })
./scripts/rust_is_available.sh:253:rustc_src=${RUST_LIB_SRC:-"$rustc_sysroot/lib/rustlib/src/rust/library"}

おそらくこれが LKM のビルドに失敗した原因だと思われる。

試しに rust ディレクトリをコピーして再度カーネルビルドをして、LKM をビルドするところまでやってみたがやはり失敗した。

方法2: 自分でカーネルをビルドする (失敗ver)

少し面倒ではあるが、自分でカーネルをビルドしてみることにする。

archlinux/linux からkernel を clone してビルドする。

Build

まずは menuconfig で CONFIG_RUST を有効にする。 依存関係のあるオプションが色々あるので / で検索して適宜確認し、有効にする。

make LLVM=1 olddefconfig
make LLVM=1 menuconfig

コンフィグファイルが作成できたら、ビルドする。

make LLVM=1 -j5

module のインストールと kernel のコピーを行う。

sudo make LLVM=1 modules_install
sudo cp arch/x86_64/boot/bzImage /boot/vmlinuz-linux-rust-enabled

Setup bootloader

方法1と同様に bootloader の設定を行う。

sudo nvim /boot/loader/entries/linux-rust-enabled.conf
/boot/loader/entries/linux-rust-enabled.conf
title Arch Linux (linux-rust-enabled)
linux /vmlinuz-linux-rust-enabled
initrd /initramfs-linux-rust-enabled.img
options <options>

initramfs を作成する。

sudo mkinitcpio -p linux-rust-enabled

reboot

...

起動しない。

systemd-boot で linux-rust-enabled を選択したところ画面に以下のログが出てスタックした。

EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path
EFI stub: Measured initrd data into PCR 9

一旦、通常のカーネルに戻して調査する。

とりあえず initramfs を作成したときのログをよく見てみると、以下のような警告が出ていた。

% sudo mkinitcpio -p linux-rust-enabled
==> Building image from preset: /etc/mkinitcpio.d/linux-rust-enabled.preset: 'default'
==> Using configuration file: '/etc/mkinitcpio-rust-enabled.conf'
-> -k /boot/vmlinuz-linux-rust-enabled -c /etc/mkinitcpio-rust-enabled.conf -g /boot/initramfs-linux-rust-enabled.img --microcode /boot/intel-ucode.img
==> Starting build: '6.7.1-arch1'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [autodetect]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [encrypt]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-rust-enabled.img'
==> WARNING: errors were encountered during the build. The image may not be complete.
==> Building image from preset: /etc/mkinitcpio.d/linux-rust-enabled.preset: 'fallback'
==> Using configuration file: '/etc/mkinitcpio-rust-enabled.conf'
-> -k /boot/vmlinuz-linux-rust-enabled -c /etc/mkinitcpio-rust-enabled.conf -g /boot/initramfs-linux-rust-enabled-fallback.img -S autodetect --microcode /boot/intel-ucode.img
==> Starting build: '6.7.1-arch1'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [encrypt]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-rust-enabled-fallback.img'
==> WARNING: errors were encountered during the build. The image may not be complete.

通常のカーネルでも ==> WARNING: consolefont: no font found in configuration などは出ていたが、 ==> WARNING: errors were encountered during the build. The image may not be complete. などは出ておらず、 ==> Image generation successful となっていた。

なんとなくモジュールが不足していそうなのと、RPM 9 などの記述から TPM のモジュールが必要かと思ったので、menuconfig で関連していそうなオプションを探して有効にしてみた。

結果としては同じログが出て起動はしなかった…。

(systemd-boot のログが出ていないかと思って journalctl で後で確認してみたが、それらしきログは見つからなかった。)

方法3: 自分でカーネルをビルドする (成功ver)

不足している kernel config がありそうなので、 linux-rust の config を流用してみる。

linux-rust のカーネルは LKM のビルドには失敗したものの、起動まではできたので起動する上で必要な driver などは含まれているはず。

.config をコピーして rustc のバージョンなど、いくつかのオプションを修正した状態で再度ビルドする。

ベースとするコードは archlinux/linux を使用する。

Build

config を配置した後、再度ビルド。(LLVM=1make -j5 でだけ付ければいいかも?)

make LLVM=1 olddefconfig
make LLVM=1 menuconfig
make LLVM=1 -j5
sudo make LLVM=1 modules_install
sudo cp arch/x86_64/boot/bzImage /boot/vmlinuz-linux-rust-enabled

config を一通り見ていて思ったが、失敗したときの config では nvme などの driver が有効になっていなかったようなのでそれが問題な気がしている。

明らかにコンパイルしているモジュールが多く、時間がかかっているので期待が持てる。

Setup bootloader

先程と同様に bootloader の設定を行う。

sudo nvim /boot/loader/entries/linux-rust-enabled.conf
/boot/loader/entries/linux-rust-enabled.conf
title Arch Linux (linux-rust-enabled)
linux /vmlinuz-linux-rust-enabled
initrd /initramfs-linux-rust-enabled.img
options <options>

initramfs を作成する。(無駄な kernel config が有効になっていたせいで fallback のimage サイズが大きすぎて /boot パーティションに入り切らなかったので fallback は作成しないようにした。改良の余地あり)

% sudo mkinitcpio -p linux-rust-enabled
==> Building image from preset: /etc/mkinitcpio.d/linux-rust-enabled.preset: 'default'
==> Using configuration file: '/etc/mkinitcpio-rust-enabled.conf'
-> -k /boot/vmlinuz-linux-rust-enabled -c /etc/mkinitcpio-rust-enabled.conf -g /boot/initramfs-linux-rust-enabled.img --microcode /boot/intel-ucode.img
==> Starting build: '6.7.1-arch1'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [autodetect]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
==> WARNING: Possibly missing firmware for module: 'xhci_pci'
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [encrypt]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-rust-enabled.img'
==> Image generation successful

今回はうまく作成できたようで、reboot するとちゃんと起動した。

LKM の作成

冒頭に書いた通り、rust_minimal.rs をロードできるようにする。

make を実行するとエラーもなくビルドできた。

% make
make LLVM=1 CROSS_COMPILE=x86_64-linux-gnu- HOSTRUSTC=rustc RUSTC=rustc BINDGEN=bindgen RUSTFMT=rustfmt RUST_LIB_SRC=/home/mori/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library -C /usr/lib/modules/6.7.1-arch1/build M=$PWD/src
make[1]: Entering directory '/home/mori/workspace/archlinux-kernel'
RUSTC [M] /home/mori/workspace/rust-module/module/src/rust_minimal.o
MODPOST /home/mori/workspace/rust-module/module/src/Module.symvers
CC [M] /home/mori/workspace/rust-module/module/src/rust_minimal.mod.o
LD [M] /home/mori/workspace/rust-module/module/src/rust_minimal.ko
BTF [M] /home/mori/workspace/rust-module/module/src/rust_minimal.ko
die__process_class: tag not supported 0x33 (variant_part)!
die__process_class: tag not supported 0x2f (template_type_parameter)!
make[1]: Leaving directory '/home/mori/workspace/archlinux-kernel'

モジュールをロード/アンロードしてみる。

sudo insmod src/rust_minimal.ko
sudo rmmod rust_minimal
[  160.098129] rust_minimal: loading out-of-tree module taints kernel.
[ 160.098132] rust_minimal: module verification failed: signature and/or required key missing - tainting kernel
[ 160.098596] rust_minimal: Rust minimal sample (init)
[ 160.098598] rust_minimal: Am I built-in? false
[ 180.365691] rust_minimal: My numbers are [72, 108, 200]
[ 180.365696] rust_minimal: Rust minimal sample (exit)

やっとうまくいった!

まとめ

あまり時間を取れなかったこともあって前回に試してからかなり期間が空いてしまったが、とりあえずうまく行ってよかった。

振り返ると rust のモジュールを作る上で詰まった点が 3 点ほどあった。

  • CONFIG_RUST が有効になったカーネルを使用すること
  • Rust 製 LKM の Makefile の書き方
  • Rust の toolchain の使い方

今回はなんとかそれらを解決できたので、今後は rust の LKM でもう少し色々と遊んでみたいと思う。

最近の Ubuntu kernel では CONFIG_RUST が有効になっているので、Ubuntu で試してみるのも良いかもしれない。

perf を使ったプログラムとカーネルの解析方法を学ぶ

· 約8分

Overview

便利そうだとは思いながら、使っていなかったツールの一つとして perf がある。 今回は perf の最低限の使い方を学ぶ。

Goal

  • 適当なアプリケーションを perf で解析する
  • flamegraph にすることができるらしいのでやってみたい
  • GUI で使えそうなものがあったら調べる

Preparation

perf 関連

必要なパッケージをインストールする。 perf だけで良ければこれでインストールできる。

sudo pacman -S perf

arch だと linux-tools のグループの中に色々入っているのでこれで入れても良さそう。

% paru -Sg linux-tools
linux-tools bootconfig
linux-tools bpf
linux-tools cgroup_event_listener
linux-tools cpupower
linux-tools hyperv
linux-tools perf
linux-tools tmon
linux-tools turbostat
linux-tools usbip
linux-tools x86_energy_perf_policy

flamegraph を作成する上で必要なパッケージもインストールする (AUR にある)。

paru -S flamegraph

perf でカーネルの解析をするためには perf_event_paranoid というカーネルパラメータを変更する必要がある。 これはカーネルのパフォーマンスイベントを取得するためのパラメータでデフォルトでは 2 になっているが、それだとカーネルのイベントを取得できないので -1 に変更する。

FYI: https://www.kernel.org/doc/html/latest/admin-guide/perf-security.html#unprivileged-users

cat /proc/sys/kernel/perf_event_paranoid
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid

fio

今回は I/O のベンチマークを取るためのツールである fio をテスト用のアプリケーションとして使用する。

sudo pacman -S fio

fio の使い方を調べていて、ioengine という設定項目があることがわかった。 せっかくなのでこの ioengine を色々と変えたときにどのような挙動の違いがあるかを perf で解析してみる。

ちなみに、fio の man を見ると設定できる ioengine はかなり色々あることがわかる。

FYI: https://manpages.org/fio

特にこだわりはないが、今回は libaio, sync, mmap で試してみる。

Memo about perf

  • perf stat で実行時間や CPU の使用率などを表示する
    • 試しに pwd を実行したときの結果を以下に示す
    • % perf stat pwd
      /home/mori/workspace/perf

      Performance counter stats for 'pwd':

      1.83 msec task-clock:u # 0.161 CPUs utilized
      0 context-switches:u # 0.000 /sec
      0 cpu-migrations:u # 0.000 /sec
      67 page-faults:u # 36.526 K/sec
      317,835 cycles:u # 0.173 GHz
      232,109 instructions:u # 0.73 insn per cycle
      52,250 branches:u # 28.485 M/sec
      3,235 branch-misses:u # 6.19% of all branches

      0.011358294 seconds time elapsed

      0.000000000 seconds user
      0.003322000 seconds sys
    • branch-misses は投機実行の失敗のことを指しているらしい
    • context-switches はスケジューラが切り替えた回数のことではないらしい
    • sleep コマンドでも、pthread を使ったプログラムでも、0 になった
  • perf bench はいくつかのベンチマークツールを提供している
    • % perf bench
      Usage:
      perf bench [<common options>] <collection> <benchmark> [<options>]

      # List of all available benchmark collections:

      sched: Scheduler and IPC benchmarks
      syscall: System call benchmarks
      mem: Memory access benchmarks
      numa: NUMA scheduling and MM benchmarks
      futex: Futex stressing benchmarks
      epoll: Epoll stressing benchmarks
      internals: Perf-internals benchmarks
      breakpoint: Breakpoint benchmarks
      uprobe: uprobe benchmarks
      all: All benchmarks
  • プログラムの詳細な分析には perf record を使い、結果を表示するには perf report を使う
    • perf record を実行すると perf.data というファイルに結果が保存される(-o オプションで変更可能)
    • -e オプションでイベントを指定することができ、指定できるイベントは perf list で確認できる
    • -g オプションをつけると call graph を取得することができ、後で使う flamegraph を作成するためには必要
    • perf report で -i オプションをつけて record で取得した perf.data を指定すると結果を表示することができる
  • perf top はリアルタイムの perf report のようなもの
  • perf scriptperf recoed の結果をスクリプトとして出力する
    • タイムスタンプとイベントの名前とイベントの詳細が出力される
    • flamegraph はこれをもとに作成される
  • perf annotate でアセンブリコードにアノテーションを付ける
  • perf diff は perf.data を比較するために使用する
    • パフォーマンス改善を行ったときに効果を検証するために使用できる
  • perf kvm は KVM に関するプロファイルを取得するために使用する
    • perf kvm --guest record でゲストマシンのプロファイルを取得できるらしいが、少し試したところうまくできなかったのでまた後で調べる

Example

実際に fio に対して perf を使用してみる。

スクリプトや作成した flamegraph は Forest0923/perf-flamegraph-test に上げたが、以下にも簡単に載せておく。

ioengine を libaio にしたときの perf.data を取得するコマンドは以下のようになった。

perf record -o /tmp/perf_libaio.data -g fio configs/seq_read_libaio.ini

これで得られたパフォーマンスデータを flamegraph にするには以下のようにする。

perf script -i /tmp/perf_libaio.data | stackcollapse-perf.pl | flamegraph.pl > images/perf_libaio.svg

record の際に -g をつけないと call graph を取得できないので注意。

ioengine を libaio, sync, mmap に変えたときの flamegraph は以下のようになった。

libaio:

sync:

mmap:

unknown となっている部分が多いが、fio を自前でビルドしてデバッグ情報をつけるようにすればおそらく解決する。

しっかり調査したい場合はビルドをし直して flamegraph や perf report, perf diff を使ってそれぞれの違いを調べたり改善したときに効果を確認すると良さそう。

Other tools

割と最近知ったツールで hotspot というものがある。 これは perf の結果を GUI で表示するためのツールで、flamegraph にも対応しているらしい。 (arch では aur にあるが、入れてみたところビルドエラーになった)

perf について調べていたときに qiita で紹介されていたツール。 下記コマンドで得られた test.perf をアップロードすると下の画像のように表示される。

perf script -i /tmp/perf_libaio.data -F +pid > test.perf

Wrap up

OS アップデートやアプリケーションのアップデートでパフォーマンスに影響が出たときに perf を使ってボトルネックの調査をしたり、改善するためのヒントを得ることができそうなので積極的に使っていきたい。

mdadm を使った RAID の設定

· 約12分

目的

ラズパイの外付け HDD で RAID を組みたい。

背景

RAID は複数のストレージデバイスを組み合わせて信頼性の高い一つのストレージとして使用する技術です。 なんとなくやっていることは知っていたのですが、今回はあらためて原理を調べつつ、実際に RAID を組んでファイルサーバとして使用してみようと思います。

以下は各 RAID レベルの簡単な説明です。 Arch wiki などを見たほうが詳しく書いてあるしわかりやすいですが、勉強の意味でも一応書いておきます。

RAID 0

RAID 0 は冗長化を全く施していない RAID です。 データを分散して保存しており、帯域幅が大きくなるのでうまく行けば高速に読み書きができます。 (ちょうどアクセスしたいデータが一つのストレージに保存されていた場合などは高速なアクセスは期待できないでしょうが)

冗長化による信頼性の向上は行われていないので使用用途はいまいちよくわからなかったのですが、Arch wiki を読んでみるとスワップパーティションなどの「データが消失する可能性を差し引いても速度を上げる価値がある場合」に使えるとのことでした。 大容量なメモリが使える最近ではわざわざスワップ領域を作らないことも多いと思うので本当に使いみちがわからないです。

RAID 1

RAID 1 は複数のディスクに全く同じデータを書き込むことで信頼性を向上させたRAID です。 冗長化はされていますが、ストレージの使用効率が悪いという問題があります。 基本的に RAID 1 を組む場合は2つのストレージを使いますが、この場合使用できるのは全体の 1/2 になってしまいます。

どちらかのストレージが生きていれば問題ないので RAID 0 に比べて圧倒的に安心ではあります。

RAID 1+0 (RAID 10)

RAID 10 では RAID 1 で冗長化をしながら RAID 0 と組み合わせてパフォーマンスの向上を目指しています。 冗長化の仕方は RAID 1 と同じなのでストレージの使用効率は悪いままです。

障害耐性については RAID 1 よりも向上していて、異なる RAID 1 のグループであれば同時に二つ故障しても問題ありません。 当然ですが、同じ RAID 1 のグループで同時に二つが故障したら終わりです。

RAID 5

RAID 5 はデータを分散して保存し、それに加えてパリティブロックも作成することで冗長化を行っています。 パリティの容量は最大でストレージ一つ分なので N 個のストレージで RAID 5 を組むと使える容量は (N - 1) / N になります。

パリティは XOR で計算されるので、ストレージが一つ故障しただけであれば復元ができます。 しかし、同時に二つ以上故障すると復元はできないため、ストレージの信頼性や組み合わせる数、サイズによってはあまり推奨されないとのこと。 ディスクが一つ故障した場合には新しいディスクを追加して復元を行いますが、復元時に大量に読み書きを行うのでそのときに障害が発生する可能性があるためです。 これまた Arch wiki 情報ですが、最近のストレージ界隈では非推奨だとか。

RAID 6

RAID 6 は RAID 5 のパリティをさらに増やして二つまで同時にストレージが故障しても復元できるように設計された RAID です。 ストレージは最小で4つ必要で容量の使用効率は (N - 2) / N になります。

パリティは XOR と Reed-Solomon 符号の二種類で行っているそうです。 Reed-Solomon 符号については正直良くわかっていないですが、例えば下の例で C1 と C2 のデータが失われたときに XOR の計算結果だけでは C1 と C2 の値を特定できないのでそれを解消するために導入しています。

使うもの

さて、基本的な RAID の原理がわかったところで今回の環境について書いておきます。

  • HDD: 512 GB x 5
  • Raspberry Pi 4B
    • OS: Ubuntu 23.04

RAID レベルですが、今回は RAID 5 にします。 最近ではあまり推奨されないという話でしたが、ディスク容量もそこまで大きくないので大丈夫だと信じて使います。 ちなみにソフトウェア RAID です。

RAID 設定

Linux で RAID を組む上では定番らしい mdadm を使って RAID の設定を行います。

まず、初期状態のデバイスを確認するとこんな感じでした。

lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 67.7M 1 loop /snap/core22/637
loop1 7:1 0 46.4M 1 loop /snap/snapd/19127
loop2 7:2 0 161.2M 1 loop /snap/lxd/24922
loop3 7:3 0 68.5M 1 loop /snap/core22/753
loop4 7:4 0 157.6M 1 loop /snap/lxd/24851
loop5 7:5 0 46.4M 1 loop /snap/snapd/19365
sda 8:0 0 465.8G 0 disk
└─sda1 8:1 0 465.8G 0 part
sdb 8:16 0 465.8G 0 disk
└─sdb1 8:17 0 465.8G 0 part
sdc 8:32 0 465.8G 0 disk
└─sdc1 8:33 0 465.8G 0 part
sdd 8:48 0 465.8G 0 disk
└─sdd1 8:49 0 465.8G 0 part
sde 8:64 0 465.8G 0 disk
└─sde1 8:65 0 465.8G 0 part
mmcblk0 179:0 0 116.4G 0 disk
├─mmcblk0p1 179:1 0 256M 0 part /boot/firmware
└─mmcblk0p2 179:2 0 116.1G 0 part /

/dev/sda から /dev/sde までが対象の HDD なので mdadm で RAID のデバイスを作成します。

sudo mdadm --create --verbose /dev/md0 --level=5 --raid-devices=5 /dev/sda /dev/sdb /dev/sdc /dev/sdd /dev/sde
mdadm: layout defaults to left-symmetric
mdadm: layout defaults to left-symmetric
mdadm: chunk size defaults to 512K
mdadm: partition table exists on /dev/sda
mdadm: partition table exists on /dev/sda but will be lost or
meaningless after creating array
mdadm: partition table exists on /dev/sdb
mdadm: partition table exists on /dev/sdb but will be lost or
meaningless after creating array
mdadm: partition table exists on /dev/sdc
mdadm: partition table exists on /dev/sdc but will be lost or
meaningless after creating array
mdadm: partition table exists on /dev/sdd
mdadm: partition table exists on /dev/sdd but will be lost or
meaningless after creating array
mdadm: partition table exists on /dev/sde
mdadm: partition table exists on /dev/sde but will be lost or
meaningless after creating array
mdadm: size set to 488254464K
mdadm: automatically enabling write-intent bitmap on large array
Continue creating array? y
mdadm: Defaulting to version 1.2 metadata
mdadm: array /dev/md0 started.

/proc/mdstat を見るとこんな内容になっているはずです。

cat /proc/mdstat
Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
md0 : active raid5 sde[5] sdd[3] sdc[2] sda[0] sdb[1]
1953017856 blocks super 1.2 level 5, 512k chunk, algorithm 2 [5/5] [UUUUU]
bitmap: 1/4 pages [4KB], 65536KB chunk

unused devices: <none>

/dev/md0 を ext4 でフォーマットします。

sudo mkfs.ext4 /dev/md0
mke2fs 1.47.0 (5-Feb-2023)
Creating filesystem with 488254464 4k blocks and 122068992 inodes
Filesystem UUID: 65815302-0210-4b7c-8496-a3711f5ccb2a
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968,
102400000, 214990848

Allocating group tables: done
Writing inode tables: done
Creating journal (262144 blocks): done
Writing superblocks and filesystem accounting information: done

マウントポジションは /mnt/raid5 とします。

sudo mkdir /mnt/raid5
sudo mount /dev/md0 /mnt/raid5/
lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
loop0 7:0 0 67.7M 1 loop /snap/core22/637
loop1 7:1 0 46.4M 1 loop /snap/snapd/19127
loop2 7:2 0 161.2M 1 loop /snap/lxd/24922
loop3 7:3 0 68.5M 1 loop /snap/core22/753
loop4 7:4 0 157.6M 1 loop /snap/lxd/24851
loop5 7:5 0 46.4M 1 loop /snap/snapd/19365
sda 8:0 0 465.8G 0 disk
└─md0 9:0 0 1.8T 0 raid5 /mnt/raid5
sdb 8:16 0 465.8G 0 disk
└─md0 9:0 0 1.8T 0 raid5 /mnt/raid5
sdc 8:32 0 465.8G 0 disk
└─md0 9:0 0 1.8T 0 raid5 /mnt/raid5
sdd 8:48 0 465.8G 0 disk
└─md0 9:0 0 1.8T 0 raid5 /mnt/raid5
sde 8:64 0 465.8G 0 disk
└─md0 9:0 0 1.8T 0 raid5 /mnt/raid5
mmcblk0 179:0 0 116.4G 0 disk
├─mmcblk0p1 179:1 0 256M 0 part /boot/firmware
└─mmcblk0p2 179:2 0 116.1G 0 part /
df -h
Filesystem      Size  Used Avail Use% Mounted on
tmpfs 380M 8.3M 371M 3% /run
/dev/mmcblk0p2 115G 6.3G 104G 6% /
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 100M 0 100M 0% /tmp
tmpfs 32M 496K 32M 2% /var/log
/dev/mmcblk0p1 253M 152M 101M 61% /boot/firmware
tmpfs 380M 4.0K 380M 1% /run/user/1000
/dev/md0 1.8T 2.1M 1.7T 1% /mnt/raid5

再起動したときにデバイス名が /dev/md0 でなく /dev/md127 になることがあったので少し調べてみると以下のように /etc/mdadm/mdadm.conf を修正すると良いようです。

sudo mdadm --detail --scan | sudo tee -a /etc/mdadm/mdadm.conf
+ ARRAY /dev/md/ubuntu:0 metadata=1.2 name=ubuntu:0 UUID=098f8892:ef715a43:dd38085d:46fb97c3

initramfs を更新します。(Boot パーティションはRAID 上にはないですし必要なのかわかりませんが一応)

sudo update-initramfs -u

Trouble Shooting: 自動マウント

最初は /etc/fstab に /mnt/raid5 のエントリを追加していたのですが、外付け HDD が起動されてデバイスが認識される前にマウントをしようとしてしまう問題がありました。

解決策として systemd の mount を使いました。 systemd mount では実行タイミングの詳細な設定ができるので /dev/md0 が認識されてから事項するように設定します。

sudo vim /etc/systemd/system/mnt-raid5.mount
[Unit]
Description=Mount RAID Array
After=dev-md0.device
Requires=dev-md0.device

[Mount]
What=/dev/md0
Where=/mnt/raid5
Type=ext4
Options=defaults

[Install]
WantedBy=multi-user.target
sudo systemctl enable mnt-raid5.mount

n8n で Discord に通知を送る RSS Reader を作る

· 約6分

目的

n8n を Raspberry Pi 4B にデプロイして RSS feed を Discord に送信する。

背景

最近までは同じことを IFTTT で行っていたのですが、2023/05/23 から無料プランでは実行できるアプリケーションの数が2つまでに制限されてしまいました。 似たようなサービスとしては ZapiermakeReadybot.io などがあり、2023/05/27 時点で使える範囲は下のような感じ。

  • Zapier
    • アプリケーション(Zaps)は 5 つまで
    • one trigger, one action まで設定可能
    • 100 tasks/month
    • 最短 15 分間隔で実行可能
  • make
    • アプリケーション(scenario)は 2 つまで
    • 1000 ops/month
    • 最短 15 分間隔で実行可能
  • Readybot.io
    • Discord 特化
    • アプリケーション(bot)は 5 つまで
    • 最短 10 分間隔で実行可能

Zapier と make は一日の実行回数や作れるアプリケーションの数からして自分のニーズには合わないものでした。 Readybot.io は自分の用途にぴったりで、一つの bot で複数の RSS フィードを管理できることなども相まってかなり良かったです。 しかし、あまり拡張性がなくDiscord への通知内容をカスタマイズしたりできないなど、自由度が足りないという印象でした。

そこで今回利用するのが OSS の n8ngithub)です。 n8n 側でホスティングしてもらう場合には無料プランなどがないためお金がかかりますが、セルフホスティングする場合にはかなり自由に使えます。 電気代や自分で管理する手間はあるのですが、単純に使ってみたいという興味もあるのでやってみます。

n8n のデプロイ

今回は Docker を使用するので下のコマンド実行のみでデプロイできます。

docker run -d --rm --name n8n -p 5678:5678 -v ~/.n8n:/home/node/.n8n docker.n8n.io/n8nio/n8n

http://[ip]:5678/ にアクセスしてログイン画面が出れば成功です。初回アクセス時は確か Sign Up の画面だったと思います。

ワークフローの作成

IFTTT などのサービスを使った経験があれば特に問題なくワークフローを作成できると思います。 Schedule Trigger で好きなタイミングで実行し、RSS Read でターゲットのURL を指定します。 Discord のチャンネルの webhook を Discord に登録すれば完成です。

しかし、最初に下のようにノードを組んだところ、RSS Read で出た情報を毎回すべて Discord に送信するようになってしまいました。

つまり、前回実行したときに得られたコンテンツと照合して新しい内容だけ配信するという処理が行われていないということですね。

修正したワークフローはこちらです。

変更点は Code のノードを追加したことです。 Code では JS のコードを書くことができ、Documentationを見ると n8n が提供している関数などがあります。 今回は $getWorkflowStaticData(type) を使って前回の実行を記録しておいて新しいものだけを出力するようにしました。

const workflowStaticData = $getWorkflowStaticData('global');
const lastRssLog = workflowStaticData.lastRssLog;

if (lastRssLog == null) {
workflowStaticData.lastRssLog = $('RSS Read').first();
return $('RSS Read').all().reverse();
}

let ret = []

for (let item of $('RSS Read').all()) {
if (item.json['guid'] == lastRssLog.json['guid']) {
break;
}
ret.push(item);
}

workflowStaticData.lastRssLog = $('RSS Read').first();

return ret.reverse();

Raspberry Pi のリソース使用状況

調べた限りではスペック的な問題はなさそうなのですが、一応イベントが発生したときのリソース使用状況を確認してみました。 RSS のチェックは2つのアプリケーションで5分間隔で実行しています。

平常時:

イベント発生時:

おわり

IFTTT っぽい他のサービスも色々と調べてみたのですが、無料版の制限や自由度の高さを考えると現状ではこの環境が一番良さそうだったのでやってみました。 オープンソースの似たようなアプリケーションとしては Huginn というものも見つけましたが、CPU アーキテクチャの問題で Docker イメージを実行できなかったので断念…。 最近何かとアーキテクチャの都合でコンテナが使えないのですが、Docker をもっと使いこなせるようになったらビルドし直したりして解決できるんだろうか。

Rust in Linux

· 約12分

目的

Linux v6.1 からカーネル内で Rust が使用できるようになったので rust-in-linux を試してみたい!

当初の目標として

  • LKMを作成する
  • system call を追加して rust で書いたハンドラを呼ぶ(カーネルに組み込む)

あたりができると良かったのですが、GW中にはうまく動かせないこと+今の所はそこまで手軽に使えなさそうということがわかったので後学のために記録しておきます。 一応、参考になりそうなページについては references を参照(カーネルがフォークだったりするので環境やバージョンの違いに注意)。

背景

最近ではソフトウェアを rust で書こうという動きが活発になっています。 Rust はメモリ安全性とパフォーマンスを兼ね備えたプログラミング言語として注目されており、様々なソフトウェアでの採用が進んでいます。

Dropbox や Discord など様々なソフトウェアで使用されているというのはよく知られていると思います。

OS のようなシステムソフトウェアでも Rust を採用しようという流れがあり、Windows ではすでにかなりの書き換えを行っているようです。リリースはまだのようですが。

Android での採用や影響についても述べられています。

さて、本題の Linux ではどうかというと v6.1 から使えるようになっています。

Linux カーネル自体の rewrite については今の所考えられていないようですが、ドライバなどを始めとして少しずつ導入していくつもりのようです。 Linux の脆弱性に関する分析ではメインのカーネルよりもドライバに大量のバグがあるという指摘もよくあるので、そのあたりを改善できると良さそうです。 こうなってくると、そろそろRustに慣れていかないとまずいかな、ということで Linux カーネル内のドキュメントを見ながらとRustを使ってみようと思います。

事前準備

壊れても平気な適当な環境がないので、今回は VM 上で実験します。VM の設定は以下のような感じです。

  • CPU: 6 cores, x86_64
  • Mem: 8 GB
  • Disk: 75 GB
  • OS: Fedora 38 server

とりあえず Fedora 環境でカーネルビルドができるようにします。

Source Code

Linux v6.3.1 上で実験をするのでソースをダウンロードします。 作業用ディレクトリは $HOME/linux-6.3.1/ です。

curl -O https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.3.1.tar.xz
tar xf linux-6.3.1.tar.xz

必要なソフトウェアのインストール

Documents - rust/quick-start を参考に必要そうなものをインストールします。 まずは rustup のインストール。

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

rustcのバージョンを変更します。

cd /path/to/kernel-src
rustup override set `scripts/min-tool-version.sh rustc`
rustup component add rust-src

clang をインストールします。

sudo dnf install clang

bindgen をインストールします。これははCとRustを連携させるために必要なソフトウェアのようです。

cargo install --locked --version `scripts/min-tool-version.sh bindgen` bindgen

その他の quick-start には書かれていないけれど必要なものをインストールします。

sudo dnf install kernel-devel llvm lld dwarves zstd ncurses-devel

dwarves を入れないと

BTF: .tmp_vmlinux.btf: pahole (pahole) is not available
Failed to generate BTF for vmlinux

みたいになります

Kernel build

LLVMでカーネルビルドをしていきます。

make LLVM=1 -j7
sudo make modules_install && sudo make install
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

実行したあとにうまく行ったか分かりづらいときは echo $? で確認する

Reboot して確認

reboot

Grub のメニューでカスタムカーネルを選択したときに bad shim signature とか言われた場合は Secure Boot が邪魔をしているので EFI 設定に入って disable にしてください。

$ uname -a
Linux localhost.localdomain 6.3.1 #4 SMP PREEMPT_DYNAMIC Fri May 5 15:55:31 JST 2023 x86_64 GNU/Linux

とりあえずLLVMでコンパイルしたカーネルを動かすことができました。

Rust を使えるようにする

ここから先は今回うまくできなかった部分ですが一応記録。

.config を修正する必要があるので修正します。

make menuconfig

修正箇所1: Rust サポートを有効にする

  • General setup
    • Rust support

修正箇所2: ソース内の samples/rust/* にあるサンプルコードをモジュールとして追加する

  • Kernel hacking
    • Sample kernel code
      • Rust samples
        • Minimal
        • Printing macros

以下は再度ビルドした結果、失敗している様子です。 make LLVM=1 rustavailable を実行した感じでは問題なさそうだったのでツールのインストールやパスの指定とかが問題ではなさそう?

[mmori@localhost linux-6.3.1]$ make LLVM=1 -j6
DESCEND objtool
CALL scripts/checksyscalls.sh
INSTALL libsubcmd_headers
BINDGEN rust/bindings/bindings_generated.rs
thread 'main' panicked at '"ftrace_branch_data_union_(anonymous_at__/_/include/linux/compiler_types_h_146_2)" is not a valid Ident', /home/mmori/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.56/src/fallback.rs:811:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
make[1]: *** [rust/Makefile:303: rust/bindings/bindings_generated.rs] Error 1
make[1]: *** Deleting file 'rust/bindings/bindings_generated.rs'
make: *** [Makefile:1292: prepare] Error 2
[mmori@localhost linux-6.3.1]$ make LLVM=1 -j6 RUST_BACKTRACE=1
DESCEND objtool
CALL scripts/checksyscalls.sh
INSTALL libsubcmd_headers
BINDGEN rust/bindings/bindings_generated.rs
thread 'main' panicked at '"ftrace_branch_data_union_(anonymous_at__/_/include/linux/compiler_types_h_146_2)" is not a valid Ident', /home/mmori/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.56/src/fallback.rs:811:9
stack backtrace:
0: rust_begin_unwind
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:584:5
1: core::panicking::panic_fmt
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/core/src/panicking.rs:142:14
2: proc_macro2::fallback::Ident::_new
3: proc_macro2::Ident::new
4: bindgen::ir::context::BindgenContext::rust_ident
5: <bindgen::ir::comp::CompInfo as bindgen::codegen::CodeGenerator>::codegen
6: <bindgen::ir::ty::Type as bindgen::codegen::CodeGenerator>::codegen
7: <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen
8: <bindgen::ir::comp::CompInfo as bindgen::codegen::CodeGenerator>::codegen
9: <bindgen::ir::ty::Type as bindgen::codegen::CodeGenerator>::codegen
10: <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen
11: <bindgen::ir::module::Module as bindgen::codegen::CodeGenerator>::codegen
12: <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen
13: bindgen::ir::context::BindgenContext::gen
14: bindgen::Builder::generate
15: std::panicking::try
16: bindgen::main
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
make[1]: *** [rust/Makefile:303: rust/bindings/bindings_generated.rs] Error 1
make[1]: *** Deleting file 'rust/bindings/bindings_generated.rs'
make: *** [Makefile:1292: prepare] Error 2
[mmori@localhost linux-6.3.1]$ make LLVM=1 -j6 RUST_BACKTRACE=full
DESCEND objtool
CALL scripts/checksyscalls.sh
INSTALL libsubcmd_headers
BINDGEN rust/bindings/bindings_generated.rs
thread 'main' panicked at '"ftrace_branch_data_union_(anonymous_at__/_/include/linux/compiler_types_h_146_2)" is not a valid Ident', /home/mmori/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.56/src/fallback.rs:811:9
stack backtrace:
0: 0x55b400da7ffd - std::backtrace_rs::backtrace::libunwind::trace::hb729d9642bb971eb
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/../../backtrace/src/backtrace/libunwind.rs:93:5
1: 0x55b400da7ffd - std::backtrace_rs::backtrace::trace_unsynchronized::h373bb774579df5c7
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
2: 0x55b400da7ffd - std::sys_common::backtrace::_print_fmt::hfbd4e92d240c89bb
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/sys_common/backtrace.rs:66:5
3: 0x55b400da7ffd - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h8f618991fbf64972
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/sys_common/backtrace.rs:45:22
4: 0x55b400dcdcfc - core::fmt::write::hc69b5b640d88cce8
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/core/src/fmt/mod.rs:1196:17
5: 0x55b400da47d1 - std::io::Write::write_fmt::h3403cef06a24a303
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/io/mod.rs:1654:15
6: 0x55b400da97d5 - std::sys_common::backtrace::_print::h368f27cdedea0e52
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/sys_common/backtrace.rs:48:5
7: 0x55b400da97d5 - std::sys_common::backtrace::print::ha105c9cf5a64cd17
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/sys_common/backtrace.rs:35:9
8: 0x55b400da97d5 - std::panicking::default_hook::{{closure}}::h48ed2c3707d5e20e
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:295:22
9: 0x55b400da9449 - std::panicking::default_hook::h8744fc5cea5e3110
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:314:9
10: 0x55b400da9da8 - std::panicking::rust_panic_with_hook::hc82286af2030e925
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:698:17
11: 0x55b400da9c57 - std::panicking::begin_panic_handler::{{closure}}::h1c15057c2f09081f
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:588:13
12: 0x55b400da84b4 - std::sys_common::backtrace::__rust_end_short_backtrace::h65de906a5330f8da
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/sys_common/backtrace.rs:138:18
13: 0x55b400da9989 - rust_begin_unwind
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:584:5
14: 0x55b400ba06e3 - core::panicking::panic_fmt::h741cfbfc95bc6112
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/core/src/panicking.rs:142:14
15: 0x55b400d82cbb - proc_macro2::fallback::Ident::_new::hd54a7f37b4be5ac2
16: 0x55b400d84132 - proc_macro2::Ident::new::h5b24a800de987e3d
17: 0x55b400c87dd2 - bindgen::ir::context::BindgenContext::rust_ident::hfb669cd12fde5045
18: 0x55b400c97543 - <bindgen::ir::comp::CompInfo as bindgen::codegen::CodeGenerator>::codegen::hdac53e6a48364ded
19: 0x55b400cb2df3 - <bindgen::ir::ty::Type as bindgen::codegen::CodeGenerator>::codegen::h9ea78383a1519d12
20: 0x55b400c24993 - <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen::h7f1cb457e8b5962b
21: 0x55b400c9a76e - <bindgen::ir::comp::CompInfo as bindgen::codegen::CodeGenerator>::codegen::hdac53e6a48364ded
22: 0x55b400cb2df3 - <bindgen::ir::ty::Type as bindgen::codegen::CodeGenerator>::codegen::h9ea78383a1519d12
23: 0x55b400c24993 - <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen::h7f1cb457e8b5962b
24: 0x55b400c7c063 - <bindgen::ir::module::Module as bindgen::codegen::CodeGenerator>::codegen::hcaa3f952aaaebcd2
25: 0x55b400c24963 - <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen::h7f1cb457e8b5962b
26: 0x55b400c8b3f3 - bindgen::ir::context::BindgenContext::gen::h8d593441c41f4c9e
27: 0x55b400c42e7a - bindgen::Builder::generate::hc6be0353ef288055
28: 0x55b400bbf63f - std::panicking::try::hfac0a9d96478463b
29: 0x55b400ba9766 - bindgen::main::h66dca222f748f7fb
30: 0x55b400bc0353 - std::sys_common::backtrace::__rust_begin_short_backtrace::he9f5e905c5b4f99b
31: 0x55b400bab539 - std::rt::lang_start::{{closure}}::h309e4f396102cd0f
32: 0x55b400d9f12e - core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::hf833e7144973d4be
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/core/src/ops/function.rs:280:13
33: 0x55b400d9f12e - std::panicking::try::do_call::h79761d203bfb6b46
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:492:40
34: 0x55b400d9f12e - std::panicking::try::h0561cbbe1722251d
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:456:19
35: 0x55b400d9f12e - std::panic::catch_unwind::hbca347ddd031b141
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panic.rs:137:14
36: 0x55b400d9f12e - std::rt::lang_start_internal::{{closure}}::h0492050ad281ec32
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/rt.rs:128:48
37: 0x55b400d9f12e - std::panicking::try::do_call::h3ebce69871996bb3
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:492:40
38: 0x55b400d9f12e - std::panicking::try::hbed537d20e728475
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:456:19
39: 0x55b400d9f12e - std::panic::catch_unwind::h4185e2024c6a5d05
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panic.rs:137:14
40: 0x55b400d9f12e - std::rt::lang_start_internal::h1899cfd715ca6829
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/rt.rs:128:20
41: 0x55b400ba9ad2 - main
42: 0x7fd85b1afb4a - __libc_start_call_main
43: 0x7fd85b1afc0b - __libc_start_main@@GLIBC_2.34
44: 0x55b400ba0985 - _start
45: 0x0 - <unknown>
make[1]: *** [rust/Makefile:303: rust/bindings/bindings_generated.rs] Error 1
make[1]: *** Deleting file 'rust/bindings/bindings_generated.rs'
make: *** [Makefile:1292: prepare] Error 2

LKM を書いてみる

Systemcall を追加して rust の関数を呼び出す

最後に

Linux における rust の使用はまだ導入されたばかりとはいえ、もっと気軽に使えると期待していたのでかなり残念な結果でした。 現状でうまくカーネルモジュールなどを作成している英語記事はいくつか見つけましたが、環境の違いなどもあってそのままでは使えなさそう。 Linux のメインラインを使っているのではなく Rust for Linux/linux やそのフォークの jackos/linux を使っているものも多いです。 加えて今回やろうと思っていたLKMについてはCで書いたLKMとは作り方が異なるようで気軽に作りづらそう。 多分まだ変化が大きい部分だと思うので継続的にキャッチアップしていく必要がありそうです。

Rust という言語そのものに関して言うと、個人的には使いたいけど難しい言語ナンバーワンだと思うので、カーネルをいじったりしながら少しずつ使いこなせるようになりたいです。 RedoxOS あたりから触るのも良いかもしれない?

References

Steam Deck が発売されたので中身を見ていく‼

· 約15分

steamdeck

はじめに

Steam Deck は Valve が開発したゲーム機のふりをした Linux マシンで Arch Linux をベースにした SteamOS を搭載しています.

Arch ユーザとしてはさわらずにはいられないなのでこの記事ではデスクトップモードにして Steam Deck の中身を見ていきます.

デスクトップモード

Steam Deck を起動するとゲーム用のモードで起動するので,まずは KDE のデスクトップに入る必要があります.

電源ボタンを長押しするとこのようなメニューが出てくるので,Switch to Desktop を選択します.

これで KDE のデスクトップに切り替わります!

Konsole がインストールされていたので,早速 SteamOS のインストールされた状態を見てみましょう.

ログインユーザの状態(などなど)

ログインユーザ名は deck で id, groups の結果はこんな感じでした.いたって普通ですね.

(deck@steamdeck ~)$ id
uid=1000(deck) gid=1000(deck) groups=1000(deck),998(wheel)
(deck@steamdeck ~)$ groups
wheel deck

ちなみにシェルは bash ですが,エラーコードが出るなどプロンプトは見やすくなってます.

ただ,.bashrc と .bash_profile はまっさらでした.

#
# ~/.bashrc
#

# If not running interactively, don't do anything
[[ $- != *i* ]] && return
#  SPDX-License-Identifier: MIT
#
# Copyright © 2020 Collabora Ltd.
# Copyright © 2020 Valve Corporation.
#
# This file is part of steamos-image-recipes.
#
# steamos-image-recipes is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.

#
# ~/.bash_profile
#

[[ -f ~/.bashrc ]] && . ~/.bashrc

どこか別の場所でプロンプトの環境変数だけ設定しているみたいですね.

(deck@steamdeck ~)$ echo $PS
$PS1 $PS2 $PS4
(deck@steamdeck ~)$ echo $PS1
(\[\033[1;32m\]\u@\h\[\033[1;34m\] \W\[\033[0m\])\$
(deck@steamdeck ~)$ echo $PS2
>
(deck@steamdeck ~)$ echo $PS4
+

脱線ついでに,tty の切り替えをやってみましたが,画面が真っ暗になるだけでログインプロンプトは表示されませんでした.

プリインストールされているパッケージ

それでは次にインストールされているパッケージを確認しようと思います.

Arch ベースなので sudo pacman -Q を実行したいのですが,その前にパスワードの設定をします.

passwd

パッケージリストはそこそこ多かったので gists に入れておきます.

SteamOS_preinstalled_pkgs.log

なんとなく目についたのはこのあたり.

  • AMD のチップだから amd-ucode が入ってる
  • btrfs-progs が入ってるけど Btrfs なの?
  • holo-* と steam* 関連は SteamOS 特有のなにか
  • holo-sudo は何ができるのか?
  • networkmanager は安定
  • openssh と openvpn がもともと入ってる
  • sshfs どこで使うんだろう?
  • なぜか zsh がすでに入ってる

AUR helper は yay が入っているようです.

(deck@steamdeck ~)$ yay -V
yay v10.3.0.r0.g4a93199 - libalpm v13.0.1

AUR からインストールされているパッケージはないみたい.「使いたいユーザは勝手に使えば?」みたいな感じなのかな….

ミラーリスト

ミラーリストは Steam Deck 用のサーバのみのようです.

(deck@steamdeck ~)$ cat /etc/pacman.d/mirrorlist
Server = https://steamdeck-packages.steamos.cloud/archlinux-mirror/$repo/os/$arch

Manjaro と同じで Arch のミラーよりもワンクッションおいて安定性を求める感じかなと妄想してみる.

ちなみにカーネルのバージョンは v5.13.0 のカスタムカーネルのようです.2022年10月3日なので2ヶ月位遅いのかな?(カスタムしたのが2ヶ月前なだけでベースバージョンはもっと前のやつかも)

(deck@steamdeck ~)$ uname -a
Linux steamdeck 5.13.0-valve21.3-1-neptune #1 SMP PREEMPT Mon, 03 Oct 2022 23:17:36 +0000 x86_64 GNU/Linux

Arch の現在のカーネルバージョンは v6.0.12 なので割と差が開いていますね.

mori@thinkpad-arch ~ % uname -a
Linux thinkpad-arch 6.0.12-arch1-1 #1 SMP PREEMPT_DYNAMIC Thu, 08 Dec 2022 11:03:38 +0000 x86_64 GNU/Linux

autostart, systemd

起動時に自動で実行されるアプリケーションを見てみます.ここで Steam Client が実行されているはず(多分).

(deck@steamdeck ~)$ ls -la .config/autostart/
total 8
drwxr-xr-x 2 deck deck 4096 Dec 17 17:34 .
drwxr-xr-x 17 deck deck 4096 Dec 17 20:57 ..
lrwxrwxrwx 1 deck deck 58 Dec 17 17:34 steamos-update-os-notifier.desktop -> /usr/share/applications/steamos-update-os-notifier.desktop

アップデートがあるときに通知するだけのやつみたいです.

[Desktop Entry]
Name=SteamOS Update Operating System Notifier
Comment=KUpdate Notifier for the new systemtray specification
Exec=/usr/lib/steamos/steamos-update-os-notifier
Icon=system-software-update
Type=Application
NoDisplay=true
OnlyShowIn=KDE
Keywords=kupdate-notifier;system;update;updater

じゃあ systemd にあるのかと思って systemctl list-units したけど特に見当たらず….というか user が実行しそうな気がするからそもそも systemd に登録するはずないか…?

Steam client の実行については今の所不明ですが,systemd のターゲットの中に cryptsetup を見つけました.

...
home-swapfile.swap loaded active active Swap
basic.target loaded active active Basic System
bluetooth.target loaded active active Bluetooth Support
cryptsetup.target loaded active active Local Encrypted Volumes
getty.target loaded active active Login Prompts
graphical.target loaded active active Graphical Interface
integritysetup.target loaded active active Local Integrity Protected Volumes
...

ディスク暗号化してるのかと思いましたが,パッケージリストにはそれらしきものはないので結局よくわかりませんでした.

dm-crypt であれば mkinitcpio のコンフィグになにかあるかと思いましたが,そもそも /etc/mkinitcpio.conf が存在しませんでした.

どうやって initramfs を作っているのか…?

ディスクとパーティション

接続されたデバイスは 512 GB の SSD のみでした.

なにやら複数のパーティションが作られていますね.

(deck@steamdeck ~)$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1 259:0 0 476.9G 0 disk
├─nvme0n1p1 259:1 0 64M 0 part
├─nvme0n1p2 259:2 0 32M 0 part
├─nvme0n1p3 259:3 0 32M 0 part
├─nvme0n1p4 259:4 0 5G 0 part /
├─nvme0n1p5 259:5 0 5G 0 part
├─nvme0n1p6 259:6 0 256M 0 part /var
├─nvme0n1p7 259:7 0 256M 0 part
└─nvme0n1p8 259:8 0 466.3G 0 part /var/tmp
/var/log
/var/lib/systemd/coredump
/var/lib/flatpak
/var/lib/docker
/var/cache/pacman
/srv
/root
/opt
/home

fstab ファイルの中身を確認するとこんな感じでした.

(deck@steamdeck ~)$ cat /etc/fstab
# Static information about the filesystems.
# See fstab(5) for details.

# <file system> <dir> <type> <options> <dump> <pass>
# SteamOS partitions
#/dev/disk/by-partsets/self/rootfs / ext4 defaults 0 1
#/dev/disk/by-partsets/self/var /var ext4 defaults 0 2
/dev/disk/by-partsets/self/efi /efi vfat defaults,nofail,umask=0077,x-systemd.automount,x-systemd.idle-timeout=1min 0 2
/dev/disk/by-partsets/shared/esp /esp vfat defaults,nofail,umask=0077,x-systemd.automount,x-systemd.idle-timeout=1min 0 2
/dev/disk/by-partsets/shared/home /home ext4 defaults,nofail,x-systemd.growfs 0 2

とりあえず ext4 でフォーマットしてるのかな?

lsblk したときの nvme0n1p8 の表記が btrfs のサブボリュームの表記の仕方に似ていたので btrfs なのかと思ったけど違いました.

cf:

mori@thinkpad-arch ~ % lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1 259:0 0 476.9G 0 disk
├─nvme0n1p1 259:1 0 100M 0 part /boot
├─nvme0n1p2 259:2 0 16M 0 part
├─nvme0n1p3 259:3 0 220.3G 0 part
├─nvme0n1p4 259:4 0 548M 0 part
└─nvme0n1p5 259:5 0 256G 0 part /var/log
/home
/.snapshots
/
mori@thinkpad-arch ~ % cat /etc/fstab
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: /etc/fstab
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ # Static information about the filesystems.
2 │ # See fstab(5) for details.
3 │
4 │ # <file system> <dir> <type> <options> <dump> <pass>
5 │ # /dev/nvme0n1p5
6 │ UUID=ed5e0803-dd6b-434e-8094-635498d65036 / btrfs rw,noatime,compress=lzo,ssd,space_cache=v2,subvolid=256,subvol=/@,subvol=@ 0 0
7 │
8 │ # /dev/nvme0n1p1
9 │ UUID=3C4A-3D26 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 2
10 │
11 │ # /dev/nvme0n1p5
12 │ UUID=ed5e0803-dd6b-434e-8094-635498d65036 /home btrfs rw,noatime,compress=lzo,ssd,space_cache=v2,subvolid=257,subvol=/@home,subvol=@home 0 0
13 │
14 │ # /dev/nvme0n1p5
15 │ UUID=ed5e0803-dd6b-434e-8094-635498d65036 /.snapshots btrfs rw,noatime,compress=lzo,ssd,space_cache=v2,subvolid=258,subvol=/@snapshots,subvol=@snapshots 0 0
16 │
17 │ # /dev/nvme0n1p5
18 │ UUID=ed5e0803-dd6b-434e-8094-635498d65036 /var/log btrfs rw,noatime,compress=lzo,ssd,space_cache=v2,subvolid=259,subvol=/@var_log,subvol=@var_log 0 0
19 │
───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Kernel

最後にカーネルのソースコードを見ようかと思ったのですが,/usr/src 下にコードはありませんでした….

パッケージの追加

pacman は使えそうなのでためしに neofetch をインストールしてみます.ロゴは Arch なのかSteamOS なのか….

まずはパッケージのデータベースを更新します.(jupiter と holo はArch では見たことがない名前ですね.)

(deck@steamdeck src)$ sudo pacman -Sy
[sudo] password for deck:
:: Synchronizing package databases...
jupiter is up to date
holo is up to date
core is up to date
extra is up to date
community is up to date
multilib is up to date

次に neofetch をインストールします.

...失敗しましたね.

(deck@steamdeck src)$ sudo pacman -S neofetch
resolving dependencies...
looking for conflicting packages...

Packages (1) neofetch-7.1.0-2

Total Installed Size: 0.33 MiB

:: Proceed with installation? [Y/n]
(1/1) checking keys in keyring [########################################################################] 100%
(1/1) checking package integrity [########################################################################] 100%
(1/1) loading package files [########################################################################] 100%
(1/1) checking for file conflicts [########################################################################] 100%
(1/1) checking available disk space [########################################################################] 100%
:: Processing package changes...
(1/1) installing neofetch [########################################################################] 100%
warning: warning given when extracting /usr/bin/neofetch (Can't create '/usr/bin/neofetch')
warning: warning given when extracting /usr/share/licenses/neofetch/ (Can't create '/usr/share/licenses/neofetch')
warning: warning given when extracting /usr/share/licenses/neofetch/LICENSE.md (Failed to create dir '/usr/share/licenses/neofetch')
warning: warning given when extracting /usr/share/man/man1/neofetch.1.gz (Can't create '/usr/share/man/man1/neofetch.1.gz')
Optional dependencies for neofetch
catimg: Display Images
chafa: Image to text support
feh: Wallpaper Display
imagemagick: Image cropping / Thumbnail creation / Take a screenshot
jp2a: Display Images
libcaca: Display Images
nitrogen: Wallpaper Display
w3m: Display Images
xdotool: See https://github.com/dylanaraps/neofetch/wiki/Images-in-the-terminal [installed]
xorg-xdpyinfo: Resolution detection (Single Monitor) [installed]
xorg-xprop: Desktop Environment and Window Manager [installed]
xorg-xrandr: Resolution detection (Multi Monitor + Refresh rates) [installed]
xorg-xwininfo: See https://github.com/dylanaraps/neofetch/wiki/Images-in-the-terminal [installed]
:: Running post-transaction hooks...
(1/1) Arming ConditionNeedsUpdate...
touch: setting times of '/usr': Read-only file system
error: command failed to execute correctly

ファイルを持ってくることはできたけど /usr 以下に書き込めなかったという感じでしょうか.

しかし,ls -l / した限りでは root なら書き込めそうです.

(deck@steamdeck src)$ ls -l /
total 71
lrwxrwxrwx 1 root root 7 Apr 29 2022 bin -> usr/bin
drwxr-xr-x 1 root root 136 Dec 14 08:40 boot
drwxr-xr-x 21 root root 4120 Dec 17 21:49 dev
drwx------ 4 root root 16384 Jan 1 1970 efi
drwx------ 4 root root 16384 Jan 1 1970 esp
drwxr-xr-x 1 root root 1024 Dec 17 23:24 etc
drwxr-xr-x 5 root root 4096 Dec 17 17:34 home
lrwxrwxrwx 1 root root 7 Apr 29 2022 lib -> usr/lib
lrwxrwxrwx 1 root root 7 Apr 29 2022 lib64 -> usr/lib
lrwxrwxrwx 1 root root 7 Apr 29 2022 mnt -> var/mnt
drwxr-xr-x 2 root root 4096 Dec 17 17:34 opt
dr-xr-xr-x 332 root root 0 Dec 17 17:39 proc
drwxr-x--- 6 root root 4096 Dec 17 22:43 root
drwxr-xr-x 25 root root 600 Dec 17 20:28 run
lrwxrwxrwx 1 root root 7 Apr 29 2022 sbin -> usr/bin
drwxr-xr-x 4 root root 4096 Dec 17 17:34 srv
dr-xr-xr-x 12 root root 0 Dec 17 17:39 sys
drwxrwxrwt 15 root root 580 Dec 17 21:46 tmp
drwxr-xr-x 1 root root 80 Dec 17 23:23 usr
drwxr-xr-x 14 root root 1024 Dec 17 19:02 var

'/usr': Read-only file system とあるので,アクセス権限以前の問題でしょうか?

調べてみると他のユーザも同じ状況のようです.

How to solve the read only system /usr

Developper Mode にしても Read-only は消えないようですね.

Steam Deck Developer Mode does not turn off the read-only filesystem

ただし,上記2つの URL の先でも示されているのですが,解決策はあるようです.

公式による解決策:

What if I want to do more than what’s available by flatpak?

Totally fine, though it comes with several caveats. Make sure you know what you’re doing and be careful about running random commands / scripts you find on the internet - you may get your Steam Deck into a bad state or compromise your data. In addition, anything you install outside of flatpak (via pacman for instance) may be wiped with the next SteamOS update.

With that out of the way, if you are going outside flatpak and need to edit the read-only image, you can enable that with the following command:

sudo steamos-readonly disable

See below for instructions on using sudo with Steam Deck. One more warning to complete the warning sandwich – don’t do the above unless you know what you’re doing.

そのとおりにやってみたところうまく行きました.

(deck@steamdeck src)$ sudo steamos-readonly disable
[sudo] password for deck:
(deck@steamdeck src)$ sudo pacman -S neofetch
resolving dependencies...
looking for conflicting packages...

Packages (1) neofetch-7.1.0-2

Total Installed Size: 0.33 MiB

:: Proceed with installation? [Y/n]
(1/1) checking keys in keyring [########################################################################] 100%
(1/1) checking package integrity [########################################################################] 100%
(1/1) loading package files [########################################################################] 100%
(1/1) checking for file conflicts [########################################################################] 100%
(1/1) checking available disk space [########################################################################] 100%
:: Processing package changes...
(1/1) installing neofetch [########################################################################] 100%
Optional dependencies for neofetch
catimg: Display Images
chafa: Image to text support
feh: Wallpaper Display
imagemagick: Image cropping / Thumbnail creation / Take a screenshot
jp2a: Display Images
libcaca: Display Images
nitrogen: Wallpaper Display
w3m: Display Images
xdotool: See https://github.com/dylanaraps/neofetch/wiki/Images-in-the-terminal [installed]
xorg-xdpyinfo: Resolution detection (Single Monitor) [installed]
xorg-xprop: Desktop Environment and Window Manager [installed]
xorg-xrandr: Resolution detection (Multi Monitor + Refresh rates) [installed]
xorg-xwininfo: See https://github.com/dylanaraps/neofetch/wiki/Images-in-the-terminal [installed]
:: Running post-transaction hooks...
(1/1) Arming ConditionNeedsUpdate...

この方法でインストールしたパッケージは SteamOS のアプデートで消える可能性があるようなので Flatpak を使ったほうが良いみたいですが,とりあえずこれで何でもできますね.

neofetch の実行結果は冒頭の通りでした.Steam のロゴマークでしたね.

おわり

Steam Deck は(一応)一般向けのゲーム機という位置づけなのでシステムが壊れないようにある程度の制限はあるようです.

また,全体的にディレクトリ構造がデフォルトとは違う,パーティションなどが複雑に分割されている,mkinitcpio などあるはずのものがないなど,普段使っている Arch の環境と違うことが多かったです.

Linux の勉強も兼ねてまた今度じっくり中身を見てみようと思います.