Linuxは色々な事情によってGNU/Linuxと呼ばないといけないのだけど、Linuxカーネル自体はGNUシステムに制限されないので、GNU/LinuxではないLinuxをつくることは可能、というわけでやってみよう
(ふと思ったんだが、uClibc + busybox環境はGNU/Linuxではないのでは?)
毎回再起動するのは面倒なので、qemuで環境を作る
これ参考にして、パーティションテーブルができてるディスクイメージを作る
mkfs.ext2 とかできあがり。
できたext2パーティションの中に/boot/grubとかを作って、今の/boot/grub/*をコピー
GRUBのinfoの3.1 'Creating a GRUB boot floppy' を参考にして、フロッピーのイメージを作る
hdaにさっきのディスクイメージを、fdaに作ったgrubのイメージを置いてqemuを起動。
setup (hd0) とかする
あとは作ったext2イメージにファイルを放りこんでいけばいいです。
つくりましょう。 make menuconfig うだうだ ; make
できた bzImage をさっきのext2イメージにほうりこみます
bzImageが1MBを切るくらいまで小さくできれば、オナニー的に合格ですね。
initrdはただのcpioアーカイブをgzip圧縮したものです。
$ find . | cpio -o -H newc | gzip -9 > ../initrd
こんな感じで。つくれます
つくったらext2イメージへ
Linuxセオリー的に、/dev, /proc, /sys ぐらいは作っておくといいでしょう
default 0 timeout 3 title=image root (hd0,0) kernel /bzImage root=/dev/hda1 vga=0x318 initrd /initrd
こんな感じのをつくってext2イメージの/boot/grub/menu.lstへ。vga=xxは好きなのを
ディレクトリ構成はこんな感じになってるはず↓
/ + bzImage + initrd + boot + grub= + menu.lst
これで起動できます。
最後Kernel Panicとか出てたら多分動いてます
カーネルを起動すると /init が実行されます。適当にhello.cでも書いてやってみましょう。
起動時には特に何かする必要はないです。終了時にはshutdown処理がいります。(なくてもいいが)
#include "syscall.h" #include <linux/dirent.h> #include <linux/reboot.h> #include <fcntl.h> #include "libheboi.h" static void do_halt( void ) { pid_t pid; int s; osys_sync( ); if ( (pid=osys_fork()) == 0 ) { osys_reboot( LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_HALT, 0 ); osys_exit( 0 ); } osys_waitpid( pid, &s, 0 ); } int main() { ps("hello\n"); do_halt(); while ( 1 ) { ps("e-\n"); slp( 1 ); } }
osys_ほげほげ はLinuxのシステムコールに対応するそれだとしておいてください。(いやなんかglibcに頼るとGNUシステムになるかと思ったので…)
あと、rebootシステムコールを呼び出すのは、init以外のプロセスのほうがよいようで。(Attempted to kill initとかゆわれる)
これでとりあえずORE/Linuxシステムは完成しました。(ほんとか?)起動してメッセージ出して終了します
(ここ以降文体が変わってるのは日が空いたからということで許してほしい)
(なんで/sysはこんな構造になっとんねん。これってある程度決め打ちじゃないと無理かな…→わかた。blkdevとchrdevで分かれてる)
今のLinuxは古の(2.2ぐらい?)Linuxと違って、カーネルレベルで hotplug が実現されている。
とかいうような事情があって、「あらかじめ/devにノード作っておいてinitrdに入れる」というようなことはダサい、というような風潮がある。(かどうかは知らない)
実際、環境決め打ちでやるならば/dev/*以下を作っておいてもいいとは思うけど、せっかくなのでここらへんも実装してみる。
Linuxのデバイスの活線挿抜の仕組みは sysfs という仕組みによって成り立っている。実際のGNOMEとかのデスクトップ環境だとその上でhalとかdbusとかが動いてるらしいけど、 そういうのはユーザ空間で実現されているので、カーネルレベルのサポートは、このsysfsまで、と考えてよいと思う
sysfsの構造は、そのままファイルシステムとして見えるようになっているので、 /sys で $find とすれば見れるかと思う
一応、今の理解で書いておくと、/devices が物理的な構造、/busが論理的な構造、/classがデバイスの種類ごとの分類、あとは昔procfsにあったドライバのインターフェースを持ってきたものだと思う。
(だとすると、/blockが/class/blockに無いのはおかしいように思うのだけど…)
大体そんな感じ。あとは各デバイスに対応したディレクトリの中にあるエントリをreadすれば、そのデバイスに関する情報が読める、というもの。
(TODO: ここらへんにnetlinkでadd,removeが飛んでくる件について書く)
ただし、ueventエントリだけはデバイスの情報を示してなくて、まあ、なんか、起動後に最初から挿されてたデバイスとかをもう一回認識させるために使うとかそういうの。 (addメッセージは挿さったときしか送られないので、udev起動前に挿さってたやつは認識できない)
というわけで、デバイス認識して開くまでの手順は大体、以下のようになる
Linuxのディレクトリは getdents で読める。readdir(2)は効率が悪いためか、使わないもよう。
(getdents は一回のシステムコールで複数のディレクトリエントリが読めるが、readdirは一個しか読めない。)
ちなみに、Linuxのdirentにはd_typeが無いが、これは、direntの最後1byteのところに埋めてある。(fs/readdir.c:filldir()らへん)
goto efault; if (copy_to_user(dirent->d_name, name, namlen)) goto efault; if (__put_user(0, dirent->d_name + namlen)) goto efault; if (__put_user(d_type, (char __user *) dirent + reclen - 1)) ← これだよ。 goto efault; buf->previous = dirent; dirent = (void __user *)dirent + reclen; buf->current_dir = dirent;
ひどい