Ubuntu Weekly Recipe

第814回1500円以下で買えるRaspberry Pi PicoサイズのRISC-VボードなMilk-V DuoでUbuntuを動かす

Ubuntu 24.04 LTSではRISC-VボードのひとつであるMilk-V Marsをサポートしました。これは中国のRISC-V製品のベンダーであるMilk-Vが作っているRaspberry Piライクなシングルボードコンピューターです[1]。ただしこのMarsはなかなか入手できない状況が続いています。Milk-Vでは他にも64コア搭載されたPCライクなPioneer、10GbEのネットワークスイッチとして使えるVegaなどもリリースしています。今回はRaspberry Pi Picoサイズの小さなボードで、比較的安価で入手性が高いMilk-V Duoシリーズに、Ubuntuをインストールしてみましょう。

図1 Raspberry Pi Picoと同じサイズのMilk-V Duo

2種類のモデルがあるMilk-V Duo

一口にMilk-V Duoと言っても実は3種類の、SoCがほぼ別物のモデルが存在します。

Milk-V Duo 64MB
SoCとして「CVITEK CV1800B」を採用したモデル。RISC-VのCPU(T-HEAD C906)が2個搭載されている(ことになっている⁠⁠。DRAMが64MBしかない。
Milk-V Duo 256MB
SoCとして「SG2002」を採用したモデル。RISC-VのCPU(T-HEAD C906)に加えて、ArmのCPU(Cortex-A53)が載っている。ただしRISC-Vとは排他で使う。DRAMが256MBある。
Milk-V Duo S
256MBベースでボードの形状を変更し、RJ-45やUSBのType-Aコネクターなどを追加したもの。DRAMが512MBもある。

DRAMのサイズについて念を押しておきますと、単位は「MB」です

そんなDRAMサイズでUbuntuが動くのかと、当然疑問に思われるかもしれませんが動きます。より良心に従った表現を心がけると、カーネル側の機能をいろいろ削っていけば動かなくもないとは言えなくもない、ぐらいです。

ちなみに64MB版のほうは、apt updateするとOOM Killerが動いてaptコマンドがエラー終了します。journaldとかいろいろなプロセスを落として、mainポケットだけ参照するようにすれば、一応aptコマンドも成功しなくもないです。これを「Ubuntuが動く」と表現して良いのであれば、動きます。

それに対して256MB版のほうは「広大なメモリ空間」を使えるため、そこまでシビアではなさそうです。ただしこれもあくまで64MBと比べたらという感じで、実用に耐えるかどうかは別の話となります。⁠Duo S」のほうはさらにSBCっぽくなっていますが、Wi-Fi/Bluetooth付きなので日本で購入して使うにはいくつか制約が出てくるでしょう。とりあえずDuo Sのことは除外して説明を続けます。

さて、Milk-Vの話に戻りましょう。Duo Sを除いた2種類のモデルの共通部分の代表的な機能は次のとおりです。

  • Raspberry Pi Picoと同じサイズ
  • Raspberry Pi Pico互換のGPIOピン配置
  • 5V/1Aで給電(USB-Cを利用可能)
  • MIPI CSI-2コネクターがありカメラを接続可能
  • TPU搭載
  • MCU搭載
  • 100MbEコネクター搭載(要はんだ付け)
  • Wi-Fi/Bluetooth非対応
  • ディスプレイ出力なし

仕組みとしてはいろいろおもしろそうなものが載っているものの、外部インターフェースの都合で、用途はかなり限定的になってしまいます。身も蓋もない言い方をすると、Ubuntuをインストールするには向いていないデバイスです。

ただ、それでも「動きそうならとりあえず動かしてみるか」と考えるのが本記事の話となります。ちなみに公式サイトでは「ここから始める$5」なんて表記になっていますが、現在日本で購入する場合は64MBで税込み1420円、256MBで2300円となっているようです[2]

Milk-V Duo 64MBの準備

それでは実際にMilk-V Duoを動かしてみましょう。必要になる機材は次のとおりです。

  • Milk-V Duo本体
  • miroSDカード(8GiB以上)
  • USB-Cで給電できるケーブル
  • シリアルケーブル
  • RNDIS対応のPC

microSDは、Milk-Vの公式イメージを使うだけなら1GiBのサイズでも問題ありません。シリアルケーブルはあったほうがデバッグが楽になりますが、必須ではありません。今回はRaspberry Pi Debug Probeを使うことにします。

シリアルケーブルがない場合は、SSHでログインして操作することになります。Ethernet用の接続線はあるのですが、自分でRJ-45コネクタなどを接続しないといけません。そこで出てくるのがUSB等をEthernetデバイスのように見せるRNDIS(Remote Network Driver Interface Specification)です。

今回の場合だとUSB-CコネクターをPCに接続し、USB CDCとして認識することで、PCとデバイス間がEthernetで接続されることになります。PC側がUbuntuであれば、特に何もしなくても自動認識します。WindowsやmacOSの場合は設定が必要です。詳細はMilk-Vのセットアップガイドを参照してください。

もしシリアルケーブルがあるなら試しに接続してみましょう。Milk-V Duo側にピンヘッダーがあるなら、Debug Probe側のコネクターを接続するだけです。まじめにやるならピンヘッダーをはんだ付けしたり、いろいろ対応方法があるのですが、今回は「とりあえず接続できれば良い」程度だったので、次のようにソケットとプラグで挟んでなんとなく接点できてれば良いという感じでつないでみました。

図2 雑すぎるつなぎ方。シリアルコンソールをPCに繋ぐと、Milk-V Duo側のLEDが赤く光る

DuoのGPIO Pinoutを参考にDebug ProbeのTx(オレンジ色)UART0/1_RX(17番ピン)に、Rx(黄色)UART0/1_TX(16番ピン)に、GNDをGND(たとえば18番ピン)につなぎます。

UbuntuにおけるUSBシリアルコンソールについては第684回のUbuntuからRaspberry Pi Picoを使うを参考にしてください。ソフトウェアはPicocomでもGNU screenでも好きなものを使えば良いと思います。ボーレートは115200です。

この状態で電源を入れると、USB-Cコネクタのそばにある青いLEDが点滅して、コンソールに次のようなメッセージが表示されます。

WD.C.SCS/0/0.WD.URPL.USBI.BS/EMMC.PS. E:load param1 (-78)
PS. E:load param1 (-78)
PS. E:load param1 (-78)
PS. E:load param1 (-78)
PS. E:load param1 (-78)
PS. E:load param1 (-78)
PS. E:load param1 (-78)
PS. E:load param1 (-78)
 E:Boot failed (8).
 E:ra=0x440a264
 E:RESET:panic:-1

microSDカードが接続されないので起動イメージがロードできずに、何度も繰り返すだけです。

公式のイメージを試してみる

次に公式のイメージをmicroSDカードに書き込んでみましょう。GitHub上のリリースページから必要なファイルをダウンロードします。

  • 64MB版:milkv-duo-v1.1.0-2024-0410.img.zip
  • 256MB版:milkv-duo256m-v1.1.0-2024-0410.img.zip
$ unzip milkv-duo-v1.1.0-2024-0410.img.zip

中身はイメージファイルなのでそのままbalenaEtcherなどで書き込みます。ちなみに次のような手順を踏めば、中身を確認できます。

$ parted milkv-duo-v1.1.0-2024-0410.img unit B print
警告: 管理者権限がありません。パーミッションに注意してください。
モデル:  (file)
ディスク /home/shibata/ダウンロード/Milk-V/milkv-duo-v1.1.0-2024-0410.img: 939524608B
セクタサイズ (論理/物理): 512B/512B
パーティションテーブル: msdos
ディスクフラグ:

番号  開始        終了        サイズ      タイプ   ファイルシステム  フラグ
 1    512B        134218239B  134217728B  primary  fat16             boot, lba
 2    134218240B  939524607B  805306368B  primary  ext4

$ mkdir mnt
$ sudo mount -o ro,loop,offset=512 milkv-duo-v1.1.0-2024-0410.img mnt/
$ ls mnt/
boot.sd  fip.bin
$ file mnt/boot.sd
mnt/boot.sd: Device Tree Blob version 17, size=2896168, boot CPU=0, string block size=100, DT structure block size=2895080
$ sudo umount mnt

最初のパーティションにあるのが、ブートローダー(fip.bin)とDTB(boot.sd)です。DTBのほうにはカーネル本体とDevice Tree本体がバイナリ形式で含まれています。

2番目のパーティションは普通のルートファイルシステムです。

$ sudo mount -o ro,loop,offset=134218240 milkv-duo-v1.1.0-2024-0410.img mnt/
$ cat mnt/etc/os-release
NAME=Buildroot
VERSION=20240410-2032
ID=buildroot
VERSION_ID=2021.05
PRETTY_NAME="Buildroot 2021.05"

$ file mnt/bin/busybox
mnt/bin/busybox: setuid ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), statically linked, stripped

$ readelf -A mnt/bin/busybox
Attribute Section: riscv
ファイル属性
  Tag_RISCV_arch: "rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0_v0p7_zfh1p0_zvamo0p7_zvlsseg0p7_xtheadc2p0"
  Tag_RISCV_priv_spec: 1
  Tag_RISCV_priv_spec_minor: 11

sudo umount mnt

Buildrootで作られていること、さらに実行ファイルはRISC-V形式であること、R64GCだけでなくVector拡張やX-Headも有効化されていることがわかります。Ubuntuのriscv64向けバイナリだと、その属性はrv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0ですので、公式イメージのほうがより性能を引き出せるかもしれません。特にTPU等を使う場合は、公式バイナリを使うほうが良さそうです。

microSDカードにイメージを書き込んだら、Milk-V Duoに接続しましょう。microSDカードのコネクター部分が下になるように接続してください。

C.SCS/0/0.WD.URPL.SDI/25000000/6000000.BS/SD.PS.SD/0x0/0x1000/0x1000/0.PE.BS.SD/0x1000/0xba00/0xba00/0.BE.J.
FSBL Jb28g9:gc3843a968:2024-04-10T20:30:12+08:00
(中略)
RT: [1.966236]CVIRTOS Build Date:Apr 10 2024  (Time :20:30:11)
(中略)
OPENSBI: next_addr=0x80200020 arg1=0x80080000
OpenSBI v0.9
(中略)
Platform Name             : Milk-V Duo
Platform Features         : mfdeleg
Platform HART Count       : 1
Platform IPI Device       : clint
Platform Timer Device     : clint
(中略)
U-Boot 2021.10 (Apr 10 2024 - 20:30:05 +0800)cvitek_cv180x
(中略)
DRAM:  63.3 MiB
(中略)
[    0.000000] Linux version 5.10.4-tag- (root@06883d61fb65) (riscv64-unknown-linux-musl-gcc (Xuantie-900 linux-5.10.4 musl gcc Toolchain V2.6.1 B-20220906) 10.2.0, GNU ld (GNU Binutils) 2.35) #1 PREEMPT Wed Apr 10 20:30:16 CST 2024
(中略)
Starting app...

[root@milkv-duo]~#

USB-Cを接続することで電源がオンになると、FSBL(Fist Stage Boot Loader⁠⁠、OpenSBI、U-Boot、Linuxカーネルの順に起動してログインプロンプトが表示されます。

ちなみにこの時点でIPアドレスが固定的に割り当てられています。

[root@milkv-duo]~# ip addr
(中略)
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
    link/ether 36:1e:ee:c9:8f:29 brd ff:ff:ff:ff:ff:ff
    inet 169.254.122.62/16 brd 169.254.255.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::87c8:612:c3a6:912/64 scope link
       valid_lft forever preferred_lft forever
3: usb0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 6e:0c:ca:55:68:e7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.42.1/24 brd 192.168.42.255 scope global usb0
       valid_lft forever preferred_lft forever
    inet6 fe80::6c0c:caff:fe55:68e7/64 scope link
       valid_lft forever preferred_lft forever

[root@milkv-duo]~# cat /etc/dnsmasq.conf
interface=usb0
dhcp-range=192.168.42.2,192.168.42.242,1h
dhcp-option=3
dhcp-option=6

「eth0」は100MbEのピンのほうで、⁠usb0」がPCに接続されている側です。これはdnsmasqを使ってアドレスを割り当てているようです。実際にPC側のdmesgを見ると、USBのcdc_etherが有効化されて、インターフェースが作られていることがわかります。

$ sudo dmesg
(中略)
[1797728.213065] usb 1-12.4: New USB device found, idVendor=3346, idProduct=1009, bcdDevice= 5.10
[1797728.213070] usb 1-12.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[1797728.213071] usb 1-12.4: Product: RNDIS
[1797728.213073] usb 1-12.4: Manufacturer: Cvitek
[1797728.213074] usb 1-12.4: SerialNumber: 0123456789
[1797728.271861] usbcore: registered new interface driver cdc_ether
[1797728.274326] rndis_host 1-12.4:1.0 usb0: register 'rndis_host' at usb-0000:00:14.0-12.4, RNDIS device, 02:49:31:07:1b:b6
[1797728.274385] usbcore: registered new interface driver rndis_host
[1797728.277166] usbcore: registered new interface driver rndis_wlan

$ ip addr
(中略)
8: enxee097b83c3d2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether 02:49:31:07:1b:b6 brd ff:ff:ff:ff:ff:ff

試しに適当にIPアドレスを割り振って、Milk-V DuoにSSHログインしてみましょう。

$ sudo ip addr add dev enxee097b83c3d2 192.168.42.2/24
$ ssh [email protected]
[email protected]'s password: (パスワードは「milkv」)

無事にログインできました。ちなみにカーネルは5.10でした。

[root@milkv-duo]~# uname -a
Linux milkv-duo 5.10.4-tag- #1 PREEMPT Wed Apr 10 20:30:16 CST 2024 riscv64 GNU/Linux
[root@milkv-duo]~# free -h
              total        used        free      shared  buff/cache   available
Mem:          28.5M       14.0M        5.4M       72.0K        9.1M        9.3M
Swap:             0           0           0

メモリの総量が「28.5MiB」になっています。これはカメラ機能でメモリを使うためです。他にもSSHサーバーはよりメモリ使用量の少ないdropbearを使うなど、いろいろな対策が取られています。

Ubuntu版のイメージを試してみる

実はMilk-V Duo用のイメージは、公式版とは別に非公式版として、いくつかのLinuxディストリビューションのイメージが存在します。Ubuntuの場合は、カーネルはMilk-V DuoのSDKを用いてUbuntu用にいくつかのカーネルコンフィグを有効化し、さらに機能を削ってなんとか使えるようにしたものが、XYZdimsのイメージとして公開されています。

今回はこれを使ってみることにしましょう。手順自体は公式イメージと同じで、zipファイルをダウンロードして、SDカードに書くだけです。電源を入れるとsystemdが起動して、Ubuntuのログインプロンプトが表示されます。

Ubuntu 22.04 LTS milkv-duo ttyS0

milkv-duo login: root
Password: (パスワードは「milkv」)

root@milkv-duo:~# uname -a
Linux milkv-duo 5.10.4-tag- #1 PREEMPT Wed Nov 22 16:59:12 CET 2023 riscv64 riscv64 riscv64 GNU/Linux
root@milkv-duo:~# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04 LTS
Release:        22.04
Codename:       jammy

root@milkv-duo:~# cat /proc/cpuinfo
processor       : 0
hart            : 0
isa             : rv64imafdvcsu
mmu             : sv39

XYZdimsのイメージでは、カメラ機能のサポートを切ることでメモリをたくさん使えるようにしています。しかしながらそれでも足りないので、zramを使ってswapパーティションを構築しています。

root@milkv-duo:~# free -h
               total        used        free      shared  buff/cache   available
Mem:            54Mi        29Mi       1.0Mi       0.0Ki        24Mi        21Mi
Swap:           27Mi       9.0Mi        18Mi

root@milkv-duo:~# zramctl
NAME       ALGORITHM DISKSIZE  DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 lzo-rle      27.5M 20.6M  7.8M  8.4M       1 [SWAP]

メモリが少ないことを除けば、普通のUbuntuとして使えます。ただしメモリが少なすぎるので普通のUbuntuでできることの大半はできないと覚悟しておきましょう。

Milk-V Duoからインターネットに接続する

RNDISによってMilk-V DuoとPCの間の通信はできます。でも、せっかくUbuntuを使っているのであれば、インターネットにも接続したいところです。そこでMilk-V Duoの通信をPC側でルーティングすることで、Milk-Vから外のマシンにアクセスできるようにしてみましょう。

ここからはPC側の設定になります。まずIPv4のフォワード機能を有効化します。

$ sudo sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

次にnftablesを利用して、⁠192.168.42.1」からの通信を、PC側の外向けインターフェース経由でインターネットにつながるようにします。ここで次の設定は環境によって異なるため、注意してください。

  • eno1はホスト側における外向けのインターフェースです
  • enx024931071bb6はホスト側から見えるRNDISのネットワークインターフェースです
$ sudo nft add table ip nat
$ sudo nft add chain ip nat postrouting { type nat hook postrouting priority 100 \; }
$ sudo nft add rule ip nat postrouting oif "eno1" masquerade
$ sudo nft add table inet filter
$ sudo nft add chain inet filter forward { type filter hook forward priority 0 \; policy accept \; }
$ sudo nft add rule inet filter forward iif "enx024931071bb6" ip saddr 192.168.42.1 oif "eno1" accept
$ sudo nft add rule inet filter forward iif "eno1" oif "enx024931071bb6" accept

ちなみにUbuntu版のMilk-V DuoのルーティングとDNSは最初から次のように設定されているため、特に設定の変更は必要ありません。この恩恵をうけるためには、RNDISのPC側のIPアドレスは「192.168.42.2」である必要があります。

root@milkv-duo:~# ip route
default via 192.168.42.2 dev usb0
192.168.42.0/24 dev usb0 proto kernel scope link src 192.168.42.1

root@milkv-duo:~# cat /etc/resolv.conf
nameserver 1.1.1.1
nameserver 8.8.8.8

ここまで設定できたら、実際にMilk-V Duoから接続してみましょう。

root@milkv-duo:~# ping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=3.63 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 10ms
rtt min/avg/max/mdev = 3.631/3.631/3.631/0.000 ms

root@milkv-duo:~# ping -c 1 www.google.co.jp
PING www.google.co.jp (142.250.207.99) 56(84) bytes of data.
64 bytes from kix06s11-in-f3.1e100.net (142.250.207.99): icmp_seq=1 ttl=115 time=10.9 ms

--- www.google.co.jp ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 5ms
rtt min/avg/max/mdev = 10.871/10.871/10.871/0.000 ms

名前解決も含めて特に問題ないようです。

もしPC側の設定を元に戻したければ次のように実行してください。

$ sudo nft delete rule ip nat postrouting oif "eth0" masquerade
$ sudo nft delete chain ip nat postrouting
$ sudo nft delete table ip nat
$ sudo nft flush chain inet filter forward
$ sudo nft delete chain inet filter forward
$ sudo nft delete table inet filter
$ sudo sysctl -w net.ipv4.ip_forward=0

aptを使ってパッケージをインストールする

Milk-V Duoがインターネットに繋がれば、aptを使ってパッケージをインストールできます。理論上は。

実際に実行してみましょう。まず証明書関係の判定のためにシステムの時刻を正しくしておく必要があります。今回のUbuntuイメージではsystemd-timesyncdやchrony(chronydとchronyc)がインストールされているものの、時刻同期はうまく動きませんでした。また、ntpdateなどのコマンドもないので、ここはとりあえず「手動でざっくりと時刻を合わせる」方向で回避します。

ホストPC$ ssh [email protected] date -s @$(date +%s)
[email protected]'s password:
Sun May 19 12:17:51 UTC 2024

では、満を持してのaptコマンドの実行です。

root@milkv-duo:~# apt update
Hit:1 http://ports.ubuntu.com/ubuntu-ports jammy InRelease
Get:2 http://ports.ubuntu.com/ubuntu-ports jammy-updates InRelease [119 kB]
Get:3 http://ports.ubuntu.com/ubuntu-ports jammy-security InRelease [110 kB]
Fetched 229 kB in 8min 13s (464 B/s)
[ 1007.197668] Out of memory: Killed process 397 (apt) total-vm:52436kB, anon-rss:14240kB, file-rss:32kB, shmem-rss:0kB, UID:0 pgtables:68kB oom_score_adj:0

残念ながらOOM Killerが走ってしまいました。もしがんばってapt update実行したいなら、対応方針は次の2種類です。

  1. OOM Killerを走らせないようにする
  2. 他のメモリを使っているプロセスを落とす
  3. aptのメモリ使用量を減らす

実際のところDRAMのサイズが小さすぎるので1はあまり調整の余地がありません。そこで2から試してみましょう。まずは一旦ページキャッシュをクリアしてから、実際の使用状況を確認しておきます。

root@milkv-duo:~# sync
root@milkv-duo:~# echo 3 > /proc/sys/vm/drop_caches
root@milkv-duo:~# free -h
               total        used        free      shared  buff/cache   available
Mem:            54Mi        27Mi        10Mi       0.0Ki        17Mi        23Mi
Swap:           27Mi        13Mi        13Mi

次にRSSが多い順からプロセスを並べてみます。

root@milkv-duo:~# ps aux --sort=-rss -w | grep -v '\[.*\]'
USER      PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      107  0.0 11.9  43892  6720 ?        S<s  14:24   0:01 /lib/systemd/systemd-journald
root        1  0.3 10.2 161464  5796 ?        Ss   14:23   0:04 /sbin/init
root      364  0.0  8.8  12488  5004 ?        Ss   14:31   0:00 /lib/systemd/systemd --user
root      224  0.1  6.9  24844  3928 ?        Ss   14:24   0:02 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
root      227  0.0  5.2  11260  2968 ?        Ss   14:24   0:00 /lib/systemd/systemd-logind
root      377  0.0  4.3   3460  2428 ttyS0    S    14:31   0:00 -bash
root      415  0.0  4.1   5700  2344 ttyS0    R+   14:46   0:00 ps aux --sort=-rss -w
root      369  0.0  3.4 163824  1932 ?        S    14:31   0:00 (sd-pam)
root      266  0.0  2.8   6336  1632 ttyS0    Ss   14:24   0:01 /bin/login -p --
message+  217  0.0  2.6   6356  1476 ?        Ss   14:24   0:00 @dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
syslog    226  0.0  2.3 219704  1336 ?        Ssl  14:24   0:00 /usr/sbin/rsyslogd -n -iNONE
root      154  0.0  2.1  18308  1212 ?        Ss   14:24   0:00 /lib/systemd/systemd-udevd
root      216  0.0  1.8   2836  1044 ?        Ss   14:24   0:00 /usr/sbin/cron -f -P
root      298  0.0  1.4   3372   800 ?        Ss   14:24   0:00 /usr/sbin/dropbear -p 22 -W 65536 -R
root      213  0.1  1.0   7468   604 ?        Ss   14:24   0:01 /usr/sbin/haveged --Foreground --verbose=1
dnsmasq   341  0.0  0.5   8000   300 ?        S    14:24   0:00 /usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service
  • journald/rsyslogdはログがいらなければ落としても良さそうです
  • networkd-dispatcherもdnsmasqでIPアドレス設定しているっぽいので外してもいいかもしれません
  • udevも落とせそうな気がしますが今回は除外で
root@milkv-duo:~# systemctl stop syslog.socket
root@milkv-duo:~# systemctl stop rsyslog.service
root@milkv-duo:~# systemctl stop systemd-journald.socket
root@milkv-duo:~# systemctl stop systemd-journald-dev-log.socket
root@milkv-duo:~# systemctl stop systemd-journald.service
root@milkv-duo:~# systemctl stop networkd-dispatcher
root@milkv-duo:~# free -h
               total        used        free      shared  buff/cache   available
Mem:            54Mi        20Mi        21Mi       0.0Ki        12Mi        30Mi
Swap:           27Mi       7.0Mi        20Mi

だいぶ使えるようになったようです。では、再度aptコマンドを使ってみましょう。

root@milkv-duo:~# apt update
Hit:1 http://ports.ubuntu.com/ubuntu-ports jammy InRelease
Get:2 http://ports.ubuntu.com/ubuntu-ports jammy-updates InRelease [119 kB]
Get:3 http://ports.ubuntu.com/ubuntu-ports jammy-security InRelease [110 kB]
Fetched 229 kB in 12s (18.7 kB/s)
[ 2326.146929] Out of memory: Killed process 458 (apt) total-vm:38692kB, anon-rss:13260kB, file-rss:176kB, shmem-rss:0kB, UID:0 pgtables:72kB oom_score_adj:0
Killed

残念ながらまだ足りないようです。そもそもこのUbuntuのaptは様々なリポジトリとポケットが有効化されています。これらをまとめて処理しようとしているために、メモリが足りなくなっている気がします。そこで/etc/apt/sources.listを調整してみましょう。

root@milkv-duo:~# cat /etc/apt/sources.list
#deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted
deb http://ports.ubuntu.com/ubuntu-ports jammy main

#deb http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted
deb http://ports.ubuntu.com/ubuntu-ports jammy-updates main

#deb http://ports.ubuntu.com/ubuntu-ports jammy universe
#deb http://ports.ubuntu.com/ubuntu-ports jammy-updates universe

#deb http://ports.ubuntu.com/ubuntu-ports jammy multiverse
#deb http://ports.ubuntu.com/ubuntu-ports jammy-updates multiverse

#deb http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse

#deb http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted
#deb http://ports.ubuntu.com/ubuntu-ports jammy-security universe
#deb http://ports.ubuntu.com/ubuntu-ports jammy-security multiverse
  • backportsはあまり使わないのでコメントアウトしました
  • securityとupdatesはかぶることも多いのでupdatesだけにしてみました[3]
  • restrictedとmultiverseは今回のマシンだとあまり使わないので無効化[4]
  • もういっそのことパッケージ数の多いuniverseも無効化
root@milkv-duo:~# apt update
Hit:1 http://ports.ubuntu.com/ubuntu-ports jammy InRelease
Hit:2 http://ports.ubuntu.com/ubuntu-ports jammy-updates InRelease
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
All packages are up to date.

OOM Killerに遭遇することなくapt updateが成功しました!

apt updateが成功すること」が目的になってしまっている感じがありますが、それはもう仕方がないということで。DRAMが少ないのが悪いのです。ちなみにuniverse有効化したところ、やっぱりダメでした。

aptはホストマシンで実行しておこう

この手のデバイスはあらかじめ必要なパッケージをインストールし、環境を作っておいて、実機ではそのプログラムを動かすだけとするのが常道です。そもそも実機でaptコマンドを実行しようとするのが間違いなのです。DRAMが少ないのは悪くない。

というわけでmicroSDカードをPCに接続して、chrootしてしまいましょう。いまならmicroSDカードのルートファイルシステムをコンテナとしてアクセスするほうが簡単かもしれません。今回は第491回のいまから『あえて』systemdのコンテナ機能を使ってみるで紹介されているsystemd-nspawnコマンドを使ってみましょう。amd64マシン上でQEMUを使ってriscv64バイナリを実行するため、qem-user-staticパッケージをインストールし、microSDの第2パーティションにそれをコピーします。

$ sudo apt install qemu-user-static
$ sudo mount /dev/sdb2 rootfs/
$ sudo cp /usr/bin/qemu-riscv64-static rootfs/usr/bin/

あとはsystemd-nspawnコマンドでrootfsの中に入るだけです。

$ sudo systemd-nspawn -D rootfs/
Spawning container rootfs on /home/shibata/ダウンロード/Milk-V/rootfs.
Press ^] three times within 1s to kill container.
root@rootfs:~# dpkg --print-architecture
riscv64
root@rootfs:~# apt update
Hit:1 http://ports.ubuntu.com/ubuntu-ports jammy InRelease
Hit:2 http://ports.ubuntu.com/ubuntu-ports jammy-updates InRelease
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
All packages are up to date.

無事にアップデートできました。他にも必要なパッケージがあればインストールしておきましょう。ただしデーモン類はメモリサイズの都合で起動できないかもしれない点に注意が必要です。

最後にmicroSDカードの中身の余計なものを削除・元に戻しておきます。

$ sudo rm rootfs/usr/bin/qemu-riscv64-static
$ echo -e 'nameserver 1.1.1.1\nnameserver 8.8.8.8' | sudo tee rootfs/etc/resolv.conf
$ sudo umount rootfs

これによりMilk-V側では純粋に必要なプログラムを動かすだけに専念できます。

しかしながらここまで読めばわかるように、少なくとも64MB版についてはUbuntuを動かすことに向いていません。この手のデバイスはBuildrootやYoctoなどを用いて、必要なソフトウェアだけが起動するようにしたほうが良いでしょう。また、Milk-V自身がSDKとしてBuildrootのツリー一式を提供しているため、Milk-V Duoを真面目に使うのであればBuildrootをおすすめします。

ただ、それはそれとして、このような非力なデバイスでもUbuntuをベースシステムとして使うこともできるのです。このメモリサイズだとsnapdなどは動かせないためUbuntu Coreも使えませんが、Raspberry Pi Picoとはまた異なるTPUやCSIが搭載されたデバイスとして、遊んでみてはいかがでしょうか。

おすすめ記事

記事・ニュース一覧