vinix (2)
Table of Contents
make ISO
前回vinixのISO作りを途中で諦めてしまっていた。もったいないので、空き時間を 利用して、作成にいそしんでみた。最近は何でもChatGPTに聞けば結論がサッと 得られるようになってるけど、これじゃ途中の苦労がさっぱり伝わってこないので 馬鹿を承知でやってみた次第。
sakae@lu:srcs$ du -sh vinix/ 24G vinix/ sakae@lu:vinix$ ls -ltr 合計 1135352 : drwxrwxr-x 6 sakae sakae 4096 3月 16 06:08 util-vinix/ -rwxrwxr-x 1 sakae sakae 57638 3月 16 06:46 jinx* drwxrwxr-x 2 sakae sakae 4096 3月 17 06:36 recipes/ drwxrwxr-x 8 sakae sakae 4096 3月 22 14:17 kernel/ drwxr-xr-x 46 sakae sakae 4096 3月 22 16:37 builds/ drwxr-xr-x 46 sakae sakae 4096 3月 22 16:37 pkgs/ drwxr-xr-x 8 sakae sakae 4096 3月 22 16:37 sysroot/ drwxr-xr-x 139 sakae sakae 12288 3月 22 16:37 sources/ drwxr-xr-x 12 sakae sakae 4096 3月 22 16:38 host-builds/ drwxr-xr-x 12 sakae sakae 4096 3月 22 16:39 host-pkgs/ -rw-rw-r-- 1 sakae sakae 384573440 3月 22 16:39 initramfs.tar drwxrwxr-x 4 sakae sakae 4096 3月 22 16:39 iso_root/ -rw-rw-r-- 1 sakae sakae 389208064 3月 22 16:39 vinix.iso
全体がこんなに膨れるんですねぇ。これだけやるにも都合4時間ぐらいは優に必要かな。 だって、ユーザーランドをコンパイルするのに必要な道具類も作成してますから。 本当の職人さんは手に馴染む道具から自作するってのは、本当だな。
どこかのHPで自慢してた。最近のラズパイはARMの石だけじゃなくてrisc-Vの石も 内蔵してるとか。んでもって、risc-VでLチカしましょですって。アセンブラでゴリゴリ 記述するんかと思って期待したら、C言語ですよ。そんな高級言語を使ってrisc-Vだって。 臍でお茶を沸かすだな。
折角なんで、ISOが作成される過程を見てみる。
sakae@lu:vinix$ make all make vinix.iso make[1]: ディレクトリ '/var/my/srcs/vinix' に入ります ./build-support/makeiso.sh + rm -rf sysroot + set -f + ./jinx install sysroot base resolving dependencies... installing base... installing base-files... installing kernel... installing init... installing bash... installing binutils... : installing lz4... checking for conflicts... synchronising package files to sysroot 'sysroot'... + set +f + [ -d host-pkgs/limine ] + cd sysroot + tar cf ../initramfs.tar bin etc lib lib64 root run sbin tmp usr var + rm -rf iso_root + mkdir -pv iso_root/boot mkdir: ディレクトリ 'iso_root' を作成しました mkdir: ディレクトリ 'iso_root/boot' を作成しました + cp sysroot/usr/share/vinix/vinix iso_root/boot/ + cp initramfs.tar iso_root/boot/ + cp build-support/limine.conf iso_root/boot/ + cp host-pkgs/limine/usr/local/share/limine/limine-bios.sys iso_root/boot/ + cp host-pkgs/limine/usr/local/share/limine/limine-bios-cd.bin iso_root/boot/ + cp host-pkgs/limine/usr/local/share/limine/limine-uefi-cd.bin iso_root/boot/ + mkdir -pv iso_root/EFI/BOOT : mkdir: ディレクトリ 'iso_root/EFI' を作成しました mkdir: ディレクトリ 'iso_root/EFI/BOOT' を作成しました + cp host-pkgs/limine/usr/local/share/limine/BOOTIA32.EFI host-pkgs/limine/usr/local/share/limine/BOOTX64.EFI iso_root/EFI/BOOT + xorriso -as mkisofs -R -r -J -b boot/limine-bios-cd.bin -no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus -apm-block- size 2048 --efi-boot boot/limine-uefi-cd.bin -efi-boot-part --efi-boot-image --protective-msdos-label iso_root -o vinix.iso xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project. :
sysrootってdirがゲストOSの/になるんだな。そこに向けてユーザーランドをインストール。 それから、/ をinitramfs.tarにまとめる。次は、ISOのdirを作成して、そこに必要な 物をぶち込む。ISOファイルシステムなんで、ローダーであるlimeineがハンドリング 出来るとな。最後の工程は、xorrisoとか言うアプリでISOに焼くとな。
How to debug kernel
見様見真似で、カーネルのデバッグってか閲覧はできるかな。
sakae@lu:vinix$ rm vinix.iso sakae@lu:vinix$ rm -rf pkgs/kernel/ sakae@lu:vinix$ rm -rf builds/kernel/ sakae@lu:vinix$ cp builds/kernel/bin/vinix pkgs/kernel/usr/share/vinix/ sakae@lu:vinix$ make debug
肝は、stripされる前のvinixを使う事。
無理してgdbしてみると …
sakae@lu:kernel$ gdb bin/vinix : ;; waiting long time. why??? (gdb) bt #0 0xffffffff8001fc0b in sched__await () at obj/blob.c:14058 #1 sched__scheduler_isr (_d1=<optimized out>, gpr_state=0xffff8000012c2f48) at obj/blob.c:13595 #2 0xffffffff8005504f in interrupt_thunk_34 () at asm/int_thunks_asm.S:98 #3 0x0000000000000030 in ?? () #4 0x0000000000000030 in ?? () #5 0x00000000013090c0 in ?? () #6 0x0000000000000000 in ?? ()
userland__syscall_fork
にBPを置いて、適当にdateをしてみた。
(gdb) bt #0 userland__syscall_fork (gpr_state=0xffff80002d4e9f48) at obj/blob.c:12520 #1 0xffffffff80000203 in syscall__syscall_entry () at obj/blob.c:11346 #2 0x000000000000003b in ?? () (gdb) l 12515 flanterm_write(flanterm_ctx, s, len); 12516 klock__Lock_release(&terminal_print_lock); 12517 } 12518 proc__Thread* proc__current_thread(void) { 12519 proc__Thread* ret = ((proc__Thread*)(((void*)0))); 12520 __asm__ volatile ( 12521 "mov %%gs:8, %[ret];" 12522 : [ret] "=r" (ret) 12523 ); 12524 return ret; (gdb) up #1 0xffffffff80000203 in syscall__syscall_entry () at obj/blob.c:11346 11346 __asm__ volatile (
どうやらkernel/main.v のコンパイル結果が、obj/blob.cに展開されるみたい。 vlangとは似ても似つかない形になってて、追跡するには不向きだな。
make debug
これをやっておくと、下記の様に、システムコール(っぽい奴)がロギングされる様になる。 この機能を使って下さいって事だな。
sakae@lu:vinix$ cat jinx-config-debug . ${base_dir}/jinx-config PROD=false TARGET_CFLAGS="$TARGET_CFLAGS -g"
Topに有るGNUMakefileから辿っていくと、これで有効になるんだけど、実際には何処でフックしてるんだろう?
kernel/modules/dev/serial/serial.v
__global ( com1_lock klock.Lock // Lock for COM1 kernel debug reporting. )
これっぽいけど、上のdebug設定と関連が取れないんだよなあ。いや、
static void _putchar(int character, void *extra_arg) { (void)character; (void)extra_arg; #ifndef PROD dev__serial__out(character); #endif
こちらはc/printf.cに有るんだけど、より直接的なコンパイル時の決定事項 だよな。こんな些細な事に拘っていないで、、、
sakae@lu:vinix$ make run-kvm qemu-system-x86_64 -enable-kvm -cpu host -M q35,smm=off -m 4G -cdrom vinix.iso -serial stdio -smp 1 -s pmm: Higher half direct map at: 0xffff800000000000 pmm: Memory map entry 0: 0x1000->0x57000 0x5 pmm: Memory map entry 1: 0x58000->0x47000 0x0 : pmm: Bitmap size: 196608 pmm: Free pages: 952686 vmm: Kernel physical base: 0x7fb5f000 vmm: Kernel virtual base: 0xffffffff80000000 vmm: Kernel: Mapping 0x7fb5f000 to 0xffffffff80000000, length: 0x5d000 vmm: Kernel: Mapping 0x7fbbc000 to 0xffffffff8005d000, length: 0x11000 vmm: Kernel: Mapping 0x7fbcd000 to 0xffffffff8006e000, length: 0xb4000 acpi: Revision: 0 : starting uACPI, version 2.0.0 RSDP 0x00000000000F52A0 00000000 v00 (BOCHS ) RSDT 0x000000007FFE22FC 00000038 v01 (BOCHS BXPC ) DSDT 0x000000007FFE0040 000020B4 v01 (BOCHS BXPC ) FACP 0x000000007FFE20F4 000000F4 v03 (BOCHS BXPC ) APIC 0x000000007FFE21E8 00000078 v03 (BOCHS BXPC ) HPET 0x000000007FFE2260 00000038 v01 (BOCHS BXPC ) MCFG 0x000000007FFE2298 0000003C v01 (BOCHS BXPC ) WAET 0x000000007FFE22D4 00000028 v01 (BOCHS BXPC ) FACS 0x000000007FFE0000 00000040 successfully loaded 1 AML blob, 1705 ops in 2ms (avg 799137/s) namespace initialization done in 2ms: 36 devices, 0 thermal zones : vfs: Mounted tmpfs to `/` vfs: Mounted devtmpfs to `/dev` initramfs: Address: 0xffff800168ef0000 initramfs: Size: 386990080 initramfs: Unpacking... initramfs: Done. urandom: rdrand available urandom: rdseed available : []: set_fs_base(0x404254a0) []: returning : /sbin/init[1]: mmap(0x0, 0x80000, 0x700000020, -1, 0) /sbin/init[1]: returning : /sbin/init[1]: chdir(/root) /sbin/init[1]: returning : /sbin/init[1]: fork() /sbin/init[1]: returning : /sbin/init[1]: waitpid(2, 0x6fffffffd38, 0) : /sbin/init[1][2]: execve(/bin/sh, [omit], [omit]) : ;; many syscall /bin/sh[2]: execve(/usr/bin/bash, [omit], [omit]) : /usr/bin/bash[2]: mmap(0x0, 0x80000, 0x700000020, -1, 0) : /usr/bin/bash[2][3]: execve(/usr/bin/date, [omit], [omit]) /usr/bin/date[3]: mmap(0x0, 0x80000, 0x700000020, -1, 0) : /usr/bin/date[3]: exit(0) : /usr/bin/bash[2]: write(2, 0x80000778008, 0x28) /usr/bin/bash[2]: returning /usr/bin/bash[2]: ppoll(0x8000067fca0, 1, 0x0, 0x41053c40) Polling on 1 FDs fdnum 0, events 1
[]内の数字はpidっぽいな。何だかstraceみたいで面白い。sh -> bash な2段構えって vinix特有なのかな? 最後はbashからの入力待ちで、ppollが発っせられたな。
kernel
いよいよソースを見ていく。
外観検査
matzさんが、まずは外観を眺めてokって吠えていたから。
sakae@lu:kernel$ ls GNUmakefile c/ freestnd-c-hdrs/ linker.ld modules/ v.mod asm/ cc-runtime/ get-deps* main.v uacpi-repository/
kernelは完全に自給自足の生活を強いられるんで、runtimeを同梱するとな。c/ の中は ほとんどがヘッダーファイルだった。uacpiは面倒なAML言語でのパワーマネジメントを サポートするコード一式だ。面倒をユーザーに強いる頑強。 後は、main.vとその仲間達のクラスターであるmodulesって構成。
SECTIONS { /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ /* and because that is what the Limine spec mandates. */ /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ /* that is the beginning of the region. */ . = 0xffffffff80000000; text_start = .; .text : { *(.text .text.*) } :text :
kernel独自のリンカー・スクリプトが用意されてて注意書きがしてあった。ブートローダー の事も考えてあげましょうってね。ちなみに現役のカーネルを検査すると、こんなだった。
sakae@lu:kernel$ LANG=C readelf -h bin/vinix ELF Header: Entry point address: 0xffffffff80038110 sakae@lu:kernel$ nm bin/vinix | grep ffffffff80038110 ffffffff80038110 T main__kmain
main.v
呼出の階層図
main() kmain() memory.pmm_init() // Initialize the memory allocator. C._vinit(0, 0) // Call Vinit to initialise the runtime gdt.initialise() // Initialize the earliest arch structures. idt.initialise() isr.initialise() term.initialise() // Init terminal serial.early_initialise() memory.vmm_init() acpi.initialise() // ACPI init hpet.initialise() pci.initialise() smp.initialise() time.initialise() sched.initialise() spawn kmain_thread() term.framebuffer_init() table.init_syscall_table() socket.initialise() pipe.initialise() futex.initialise() fs.initialise() fs.mount(vfs_root, '', '/', 'tmpfs') or {} fs.create(vfs_root, '/dev', 0o644 | stat.ifdir) or {} fs.mount(vfs_root, '', '/dev', 'devtmpfs') or {} initramfs.initialise() streams.initialise() random.initialise() fbdev.initialise() fbdev.register_driver(simple.get_driver()) console.initialise() serial.initialise() mouse.initialise() hda.initialize() userland.start_program(false, vfs_root, '/sbin/init', ['/sbin/init'], [], '/dev/console', '/dev/console', '/dev/console') or { panic('Could not start init process') } sched.dequeue_and_die() sched.await()
kmainの中でハード関係のイニシャライズが実行されてって舞台の準備
が実施される。そして、 kmain_thread
で、ソフト的な準備をする。
最後の所で、/sbin/initを実行して、ユーザーランドのアプリbashが
起動するって流れだな。
Limine
Limine から辿れるFAQによると、サポートするファイルシステムは、FAT*, ISO9660 だけだよ。grub2みたいに、多彩なサポートはしないよとな。シンプルが一番です。
The Limine Boot Protocol 一番大事なのはこれ。確かに色々な石の対応が説明されてる。今回はx86-64の所を かいつまんで見ればいいのだな。
2025-01-20にやった risc-Vの1000行OS に通じるものが有るな。
linime: Loading executable 'boot():/boot/vinix'... linime: Loading module 'boot():/boot/initramfs.ar'...
これが起動直後のメッセージだ。confに記述された情報を元にロードしてるな。
今度は起動メニューからEditorを開いてモジュール名を変更してみた。そしてF10 キーで起動させると、
PANIC: linime: Faild to open module with path 'boot:/boot/fs.tar' Is the path collect? Stacktrace: [0x11c3c] <panic+0x76> [0x2b152] <limine_load+0x2412> [0x364fb] <boot+0x6b> [0x3749c] <_menu+0xe9c> End of trace. Press a key to return to editor.
無い袖は振れませんとばかりにパニくった。これでlinimeへダイビングする時の 入口が手にはいりましたねぇ。
initramfs.tar
このtar玉をカーネルはどうやって展開してるんだろう? 普通に考えたらカーネルが ロードされた時にtar玉もローダーによって展開されてる。そりゃ浅はかだよ。 ファイルシステムを知ってるのはカーネルなんだから、展開するのはカーネルの仕事だ。 だったら、カーネルはtar玉の名前だけでも知る必要が有る。そんなの簡単。os.argv[1] とかで得られるじゃん。待て、まだ浅はかだぞ。環境変数を誰が用意してくれるねん?
ここは絶対に自主独立じゃなか、あかん。孤高のカーネルを強いられるんだ。ってな訳で モジュールinitramfsですよ。そこに上記の疑問への回答がつまっているはず。 もう一度debug-logを再掲する。
initramfs: Address: 0xffff800168ef0000 initramfs: Size: 386990080 initramfs: Unpacking... initramfs: Done.
コードと対面する。
@[_linker_section: '.requests'] @[cinit] __global ( volatile module_req = limine.LimineModuleRequest{ response: unsafe { nil } } )
カーネルファイルの一部にローダーであるlimineとの通信エリアが 用意されてるんだな。それを利用して、
pub fn initialise() { if module_req.response == unsafe { nil } { panic('Modules bootloader response missing') } if module_req.response.module_count < 1 { panic('No initramfs') } mut modules := module_req.response.modules initramfs_begin := unsafe { modules[0].address } initramfs_size := unsafe { modules[0].size } println('initramfs: Address: 0x${voidptr(initramfs_begin):x}') println('initramfs: Size: ${u32(initramfs_size):u}') print('initramfs: Unpacking...') ;; シグネチャ(utar)を確認後に、tar玉を展開する print('\ninitramfs: Done.\n')
linimeによってtar玉は(勿論カーネルも)最高位のメモリーにロードされる。その先頭アドレスとサイズを入手 する訳だな。メモリーに載ってしまえば(ISO)ファイルシステムの理解なんて不要だ。
tar x
tar xf の間違いじゃないからね! (fはファイル指定のオプション) メモリーの上位に 有るデータをカーネルの管理域(ramfs)にコピーする。
下記は、dirや普通のファイルを作成してる部分だ。
match unsafe { USTARFileType(current_header.filetype) } { .gnu_long_path { // limit for safety if size >= 65536 { panic('initramfs: long file name exceeds 65536 characters.') } name_override = unsafe { tos(voidptr(u64(current_header) + 512), int(size)) } } .directory { fs.create(vfs_root, name, u32(mode | stat.ifdir)) or { panic('initramfs: failed to create directory ${name}') } } .regular_file { new_node := fs.create(vfs_root, name, u32(mode | stat.ifreg)) or { panic('initramfs: failed to create file ${name}') } mut new_resource := new_node.resource buf := voidptr(u64(current_header) + 512) new_resource.write(0, buf, 0, size) or { panic('initramfs: failed to write file ${name}') } }
これで安心。つぎ何を見よう? 際限が無くなりそうなので、一旦終了する。
README
栃木県の宇都宮市は餃子だけじゃないよ。カクテルの街って、どこかで聞いたような。 オリオンは静かに詠う なんて小説を読んだんだけど、百人一首のまち でもあるらしい。知らなかったなあ。知らないと言えば、この小説の根幹である 競技カルタの事も良く知らなかった。文中で説明が出てくるんだけど、まとめって ことで。
何となく、補完を思い出しちゃったぞ。