ORE/Linux をつくろう

概要

Linuxは色々な事情によってGNU/Linuxと呼ばないといけないのだけど、Linuxカーネル自体はGNUシステムに制限されないので、GNU/LinuxではないLinuxをつくることは可能、というわけでやってみよう

(ふと思ったんだが、uClibc + busybox環境はGNU/Linuxではないのでは?)

テスト環境の構築

毎回再起動するのは面倒なので、qemuで環境を作る

ext2のイメージをつくる

これ参考にして、パーティションテーブルができてるディスクイメージを作る

mkfs.ext2 とかできあがり。

GRUBのインストール

できた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作る

initrdはただのcpioアーカイブをgzip圧縮したものです。

$ find . | cpio -o -H newc | gzip -9 > ../initrd

こんな感じで。つくれます

つくったらext2イメージへ

Linuxセオリー的に、/dev, /proc, /sys ぐらいは作っておくといいでしょう

menu.lstつくる

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を書こう

カーネルを起動すると /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/*以下を作っておいてもいいとは思うけど、せっかくなのでここらへんも実装してみる。

sysfs

Linuxのデバイスの活線挿抜の仕組みは sysfs という仕組みによって成り立っている。実際のGNOMEとかのデスクトップ環境だとその上でhalとかdbusとかが動いてるらしいけど、 そういうのはユーザ空間で実現されているので、カーネルレベルのサポートは、このsysfsまで、と考えてよいと思う

sysfsの構造は、そのままファイルシステムとして見えるようになっているので、 /sys で $find とすれば見れるかと思う

一応、今の理解で書いておくと、/devices が物理的な構造、/busが論理的な構造、/classがデバイスの種類ごとの分類、あとは昔procfsにあったドライバのインターフェースを持ってきたものだと思う。

(だとすると、/blockが/class/blockに無いのはおかしいように思うのだけど…)

大体そんな感じ。あとは各デバイスに対応したディレクトリの中にあるエントリをreadすれば、そのデバイスに関する情報が読める、というもの。

(TODO: ここらへんにnetlinkでadd,removeが飛んでくる件について書く)

ただし、ueventエントリだけはデバイスの情報を示してなくて、まあ、なんか、起動後に最初から挿されてたデバイスとかをもう一回認識させるために使うとかそういうの。 (addメッセージは挿さったときしか送られないので、udev起動前に挿さってたやつは認識できない)

参考

というわけで、デバイス認識して開くまでの手順は大体、以下のようになる

  1. netlink で kobject との通信を開始する。
  2. /sys以下を探索して各デバイスのueventに書く
  3. add されたデバイスの dev エントリを見て major/minor をぐにょる。
  4. mknod( "/dev/xx", S_IFCHR|S_IRUSR|S_IWUSR .., makedev(major,minor) );
  5. open( "/dev/xx" );

Linuxのディレクトリ

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;

ひどい