FreeBSDのkernelが仮想記憶へ入り方を自分の理解の整理のためにメモにしたいと思います。コードを全て読んでいるわけではないので、間違っていたら直します。
仮想記憶は32Bitでアドレッシングできる4Gの範囲内の物理メモリを、同じく32Bit領域に効率的に割り当てるための仕組みです。仮想記憶が作り出された当初は物理メモリはかなり小さいものでした。
仮想記憶というとメモリの二次記憶への退避や復元の機能も含まれる場合がありますが、ここでは割愛します。
ターゲットは今いじっているarmのComcerto 1000にします。FreeBSDのセカンドブートローダーは使わずu-bootから直にkernelを起動する方法について説明します。
Comcertoは0x80000000から物理メモリがあります。物理メモリのアドレスはSOCによって違います。
まずはu-bootが動いてるわけですが、u-bootは物理メモリで動いていて、物理メモリにkernelを貼り付けて、実行を移します。u-boot自体の作業領域はメモリの後半に取られています。
FreeBSD/armでは仮想記憶のkernel領域(Kernel Virtual Memory:KVM)の0xc0000000でkernelをリンクします。リンクにはsrc/conf/ldscript.armが利用されます。
% readelf -h Softbank_E-WMTA22_kernel
ELF Header:
Magic: 7f 45 4c 46 01 01 01 09 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: FreeBSD
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0xc0000180
Start of program headers: 52 (bytes into file)
Start of section headers: 4633580 (bytes into file)
Flags: 0x5000202, Version5 EABI, has entry point, software FP
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 7
Size of section headers: 40 (bytes)
Number of section headers: 42
Section header string table index: 39
ZRouterではこれをobjcopy(elfcopy)でbinaryにしてから圧縮してu-bootイメージにします。圧縮形式はターゲットのu-bootが対応している形式でおこないます。
% file Softbank_E-WMTA22_kernel.kbin.gz.uboot
Softbank_E-WMTA22_kernel.kbin.gz.uboot: u-boot legacy uImage, FreeBSD Kernel Image, Linux/ARM, OS Kernel Image (gzip), 1719169 bytes, Tue Nov 26 00:27:39 2019, Load Address: 0x80000180, Entry Point: 0x80000180, Header CRC: 0x364B1731, Data CRC: 0x495BB09C
u-bootはもちろん物理メモリで動いているので、LoadやEntryのアドレスは物理アドレスに変換してイメージを作成します。この指定はZRouterが自動的に行います。
u-bootはこのイメージを物理メモリの0x80000180に貼り付けて実行を移します。
実行が移るとkernelコードのsys/arm/arm/locore-v6.Sが実行されます。
仮想記憶に移行するすなわちmmuをonにするためには、仮想アドレスと物理アドレスの変換テーブルが必要になります。
テーブルは物理アドレスで作成してmmuをonにする前にCPUにセットします。
まずはkernel自身が張り付いている部分や変数などの領域およびIOアドレスのテーブルが必要になるので、それを作ります。具体的には仮想アドレスの0xc0000000が物理アドレスの0x80000000などです。
FreeBSDの実装ではIOのテーブルはKVMの後半に固定で用意するようです。
armでは4K(10Bit)と1M(20Bit)のメモリ領域(ページ)がサポートされています。
テーブルにはキャッシュの有効無効の設定もあり、メモリは有効にしてありIOは無効にしています。
0x80000180から実行を始めましたがmmuをonにした瞬間に0xcxxxxxxxなアドレスになります。mmuをonにしただけではPCレジスタは0x8xxxxxxxなので、直後にPCを0xcxxxxxxxに変更します。
kernelはKVMで動いているので、テーブルもKVMでのアクセスになりますが、CPUからは物理アドレスで拾い出されています。
この後のテーブルの変更処理はpmapが行いますが、キャッシュもからみ複雑になります。mmuはテーブルの通りに変換を行っているだけで、どのようにメモリを使うかはpmapの処理になります。
BareMetalでのmmu onは以前ここに書きました。
NetBSDの場合
NetBSDのFDTの場合を調べてみました。
sys/arch/arm/armv6_start.Sが最初のコードです。FDTになる前はターゲットごとにstartコードを用意していたようなのですが、FDT対応で一本にまとめたようです。またFreeBSDと似たplatformという仕組みを実装してあります。
mmuのonはarmv6_start.Sで行っていますが、その前にsys/arch/arm32/locore.Sを呼んでいます。またdevmapはsys/arch/evbarm/fdtで処理して、それぞれのターゲットごとのマップはターゲットごとに定義されています。