--------------------------------------------------------------------------
標題:[OS Project-1] 親手打造 Floppy Linux 環境
作者:黃敬群,國立成功大學資訊工程系
文件版次:0.1                      
最後修訂日期:Nov 14, 2001
版權聲明:允許在保留作者出處及本聲明之前提下,以任何人類可讀之形式自由散佈
--------------------------------------------------------------------------

	[OS Project-1] 親手打造 Floppy Linux 環境
	
					黃敬群 

■ 前言

撰寫本文的目的,就是希望能夠將筆者自行製作的 Floppy-Linux Distro (目前版本為
0.2pre2) 是如何一步一步建構出來,透過書面紀錄的方式,從最簡單的「開機片」,
逐漸修改製作方式、植入若干工具,到討論改進方式等體驗「親手打造」的過程,對需
要製作包含常用工具之 Rescue Disk 者,或是有心從事 embedded Linux 者,如此的建
構過程或許能提供些幫助。本文以 [如何製作簡易 Floppy Liunx] 一文為主軸,輔以各
種延伸功能 (如網路、editor 等) 進行擴充。因才疏學淺之故,諸多謬誤在所難免,還
請不吝指正。


■ 讀者背景

1. 對 UNIX 指令稍有涉獵
2. 曾經親手編譯過 Linux Kernel


■ 開機過程簡述

尚未提及如可製作 Floppy Linux 前,首先要瞭解整個系統開機的過程,為了普及性,
這邊以最容易取得的 PC 個人電腦討論對象,處理器是 Intel 80486 以上等級。

當啟動電源之際,電腦的 BIOS 就會找尋有無系統開機磁片,此時會有兩種狀況產生:

    第一種狀況:找到系統開機磁片 --

        此時就會從系統開機磁片中的第 0 磁區、第 0 磁柱載入可開機磁區

    另一種狀況:找不到系統開機磁片 --
 
        BIOS 就會找尋硬碟的 MBR (Master Boot Record),並且執行記錄在 MBR 上
	的開機載入程式 (Boot Loader) 進行開機

無論是從軟碟開機也好,還是從硬碟開機也好,首先 OS Loader (就 Linux 來講就可
能是 LILO -- LInux LOader) 會載入 Linux Kernel,而 Kernel 一旦起動後,第一件
事就是切入保護模式 (protected mode),此時,所有的硬體交由 Kernel 來控制,也
意味掙脫 BIOS 的羈絆。

當 Kernel 載入完畢後,便開始初始化系統所有硬體設備。而當所有的硬體初始化的動
作也告完成之際,系統將嘗試掛載 (mount) root filesystem。Root filesystem 就是
被掛上當作 "/" 目錄的 filesystem (以下簡稱 fs),當然,如果無法正確 mount 上 
root fs,巧婦難為無米之炊的 Linux Kernel 也只好秀個訊息:

	VFS: Unable to mount root fs on XXX

然後就會停止運作 (halt),這邊的 XXX 是指那種 fs,這部份的訊息,可以參考 kernel
source 中 fs/super.c 的程式碼。

當 root filesystem 成功的掛載後,就會去執行 init 這個程式。

init 會檢查 /etc/inittab,找出該檔中標明 sysinit 這一行,並執行該 script,在 
Redhat Linux 上為 rc.sysinit,於是 rc.sysinit 肩負系統的初始化的大任,不外乎
有以下任務:

	. 呼叫 /sbin/initlog 紀錄系統初始化過程
	. 設定 path、hostname 等資訊
	. banner 畫面:一般我們看到 "Welcome to RedHat Linux" 的訊息
	. Mount /proc
	. Load system font
	. Configure kernel parameters
	. Set the system clock
	. Load keymap
	. Start up swapping、turn on swap
	. Remount the root filesystem read-write、Clear mtab
	. Finding module dependencies、Load modules
	. Check filesystems

當 rc.sysinit 執行完畢,控制權立即移轉回 init 手中,進入預設 runlevel:

  . 若內定的 runlevel 為 3:
  
    init 執行 /sbin/mingetty 啟動 virtual console,並且以 "login:" 提示讓使
    用者登入,以完成開機。登入後系統會提供一個 shell 給使用者,就可以使用 
    Linux 。
    
  . 若 runlevel 為 5:
  
    在開啟 virtual console 後,init 會再執行 xdm 啟動 X window system,讓使
    用者以 xdm 界面登入。

以上就是 Linux 開機的過程。


■ 起點:開機片

Floppy Linux 的製作方式相當多,在不訴諸商業 Embedded Linux Toolkit (如 
BlueCat 等) 的情況下,可由 Linux Distribution 內提供製作 Boot/Rescure Disk 的
script 下手。筆者以手頭這台安裝 Mandrake Linux 8.0的例子來說,就是 
/sbin/mkbootdisk,請以 root 身份執行:

	# /sbin/mkbootdisk 

可看到使用方式:

usage: mkbootdisk [--version] [--noprompt] [--mkinitrdargs ] 
                  [--appendargs ] [--device ] 
		  [--verbose -v] [--compact] 
(ex: mkbootdisk --device /dev/fd1 2.0.31)

依據指示 [Press  to continue],不消多久就建構出一個 Floppy Linux,咱們
來看看這張 floppy 有什麼內容:

	# mount -t ext2 /dev/fd0 /mnt/floppy/
	# tree /mnt/floppy/

/mnt/floppy/
|-- boot
|   |-- boot.021C
|   |-- boot.b
|   |-- map
|   `-- message			========> LILO 啟動後秀出的訊息
|-- dev
|   |-- fd0H1440
|   `-- hda6
|-- etc
|   `-- lilo.conf		=========> lilo 組態設定
|-- initrd.img			=========> initrd image
|-- lost+found
`-- vmlinuz-2.4.3-20mdk		=========> Linux Kernel

回顧 [開機過程簡述] 一節所提,開機片的啟動過程如下:

	BIOS --> LILO --> Kernel --> init --> linuxrc --> mount Hard Disk
	                                                  (root fs)

mount 上 Hard Disk (筆者的系統是 hda6) 後,控制權就交給硬碟中的 Linux,這片
開機片也功成身退。

接著,讓我們一步接一步的探究,首先對 lilo 開刀:

	# cat /mnt/floppy/etc/lilo.conf 

boot=/dev/fd0H1440
timeout=100
message=/boot/message
prompt
image=/vmlinuz-2.4.3-20mdk
	label=linux
	root=/dev/hda6
	append=""
	initrd=/initrd.img

initrd.img 立刻吸引我們的目光,這是由 mkinitrd 工具程式所建構。以 Mandrake
Distribution 內附的 Linux Kernel (vmlinuz-2.4.3-20mdk) 來說,算是「一般化的
kernel」,裡頭並不包含任何 SCSI 卡、CD-ROM,或是其他的驅動程式,而在必要時,
才以 module 方式載入上述驅動程式,如此的需求,mkinitrd 就因應而生,mkinitrd 
會自動載入 /etc/conf.modules 檔案裡的的設定項目,如此一來,便能方便地讓 
kernel 管理 module 形式的驅動程式。

initrd 可以在載入核心前就先載入 RAM disk,也可載入壓縮過的檔案系統檔,如果您
留意 Linux Distribution 安裝的過程中,有 [Rescue] 的選項,就是提供 rescue.gz
這個壓縮過的檔案系統檔 (可以在安裝光碟中找到),裡面包含些工具程式。

我們可以由此當作出發點,稍微修改,使開機片脫離 Hard Disk 的牽連:大體上,只
要想辦法讓 kernel 能載入 rescue.gz 進 RAMDisk 再 mount 成 root fs 就行了。根
據 initrc 的文件, 要在 initrd 的 /linuxrc 內寫入這個程序

首先,修改開機片的 lilo.conf 檔,指示 kernel 把 RAMDisk 當 root,並以 Read-
write 模式掛上(不然 kernel 不會 re-mount)

	# cat /mnt/floppy/etc/lilo.conf
	
boot=/dev/fd0H1440
message=./boot/message
delay=0
initrd=rescue.gz		=======> 第一處修改:initrd
image=vmlinuz			=======> 換成光碟中的 Kernel,這樣整個系統才
                                         放得下)
	label=linux
	ramdisk=49152		=======> 指定 RAMDisk 大小
	root=/dev/ram		=======> 把 root fs 指定為 /dev/ram

迫不及待想來測試吧,實驗結果可以發現,的確可以載入 rescue.gz,也執行了 
/linuxrc,但卻無法 mount root fs,why?

原因很簡單,initrd 其實並非在 /dev/ram 之上,在它出現時並沒有載入核心, 所以
根本沒有 device file,更別說要 mount 了,如何解決呢?其實不難,善用 mkfs、
cp 等指令,並且寫進 /linuxrc 中:

	# cat /mnt/floppy/linuxrc
	
#!/bin/sh
echo "Making RAM Disk..."
/sbin/mkfs.minix /dev/ram 1440	=======> 採用 Minix 的 fs,可以增加使用容量
echo "Duplicating disk..."
/bin/mount -t minix /dev/ram /mnt
/bin/mkdir /mnt/mnt
/bin/cp -afx bin boot cdrom dev etc lib proc root sbin tmp usr var /mnt
/bin/umount /mnt

注意到上面,我們採用 Minix fs,由於 Minix fs 設計上比 ext2 簡易許多,相對來說
,所佔的空間就比採用 ext2 fs 小多了,但是,為了符合此需求,Kernel 必須重新編
譯,在 [File System] 項目要加選 [Minix File System]。

就這樣,一片不折不扣的 Floppy Linux 於是生焉,啟動後,就可以利用原本 Rescue
Disk 上的工具,進行操作,不過呢,此時的我們,頂多動到 OS Loader 以及 Booting
程序,其他如 root fs 的建構還是沒有太大的斬獲。

在下一個小節,我們要來實作 Floppy Linux 其他項目。


■ 進化:簡易版 Floppy Linux

在此必須說明本節的目標:製作的簡易版 Floppy Linux,是一個功能非常精簡的 Linux
,只支援軟碟、並不支援硬碟及光碟,沒有網路功能,可以執行簡單的 shell 程式及一
些常用的工具程式。

接下來,你應該準備一些發展 Floopy Linux 的 source package 及工具程式有: 

  . Linux Kernel source
  . BuzyBox	[匯集常用 UNIX 指令於單一執行檔的工具集]
  . syslinux	[Linux Kernel Loader,可以讀取 FAT 及 Ext2 fs]
  
於是,這個 Floppy Linux 的開機過程與上述開機過程稍有不同:將 LILO 更換成 
syslinux、開機完之後直接提供一個 shell 給使用者用,其完整開機方式如下:

	BIOS --> Syslinux --> Kernel --> init --> shell

首先,必須針對我們的 Floppy Linux 來量身製作它所屬 Kernel,最好也把所需的 
driver make 進核心中,如下:

	# make xconfig
	# make dep
	# make bzImage

Kernel 建構完畢後,我們還需要一些常用的工具程式,兼具便利與低容量使用,
Busybox 是個不錯的選擇。

在此對這個大名鼎鼎 BusyBox 簡單的介紹一下:BusyBox 它包含了七十多種 Linux 上
標準的工具程式,只需要的磁碟空間僅僅幾百 k (視所選擇工具程式的數目來決定大小
),在嵌入式 系統上常用到它 (例如 Linux Router Project 和 Debian boot floppy
就使用到它),可在 
 
	http://busybox.lineo.com 
	
找到參考資料及下載,同時,Busybox 是由 Lineo, Inc 這家嵌入式系統廠商所贊助的
Open Source tool。

而,Busybox 為何能夠佔有如此小的容量,卻能提供為數不少的常用工具呢?這訣竅在
於 busybox 在編譯後,雖然整體只是一個執行檔,卻可以透過 symbolic link 的方式
,將常用指令 (Busybox 的術語是 "applet") 「連」到 busybox 這個執行檔上,如:

	# tree bin/
.
|-- ash
|-- busybox <===========================\
|-- cat -> busybox	----------------|
|-- chgrp -> busybox	----------------|
|-- chmod -> busybox	----------------|
|-- chown -> busybox	----------------|
|-- cp -> busybox	----------------|
|-- date -> busybox
|-- df -> busybox
|-- dmesg -> busybox
|-- echo -> busybox
|-- false -> busybox
|-- fdflush -> busybox
|-- grep -> busybox

	[後略]

如我們所見,cat、chgrp、chown、... 都被 symbolic link 到 busybox,那,要如何
運作呢?

咱們翻開 busybox 的 source 片段來看:(busybox.c)

int main(int argc, char **argv)
{
	const char *s;
	for ( s = applet_name = argv[0]; *s != '\0'; ) {
		if ( *s++ == '/' )
			applet_name = s;	/* 取得 applet 名稱 */
	}

	run_applet_by_name( applet_name, argc, argv ); /* applet 進入點 */
	error_msg_and_die( "applet not found" );
}

想必您一定會說:「啊哈,原來就是這麼一回事」,複習一下 C 語言,main(int argc,
char **argv) 的引數中,argv[0] 是什麼呢?就是「執行時期的名稱」,換言之,就是
cp、cat、chown 等等,確定使用者所下的指令後,就丟給 run_applet_by_name() 這個
執行引擎去跑,示意圖如下:

	something(我們的指令) ---------\
	                               |          cp
	buzybox <----------------------/          / 
	                                         /---mv
	  1. 辨識 applet 名稱                    |
	  2. 丟進 run_applet_by_name() 去跑 ===> +-mkdir
	                                         |
						  
透過功能共用的方式,不僅可以共享相似的副程式 (function),更減少重複 link 的負
擔,於是乎,不需要替每個指令都製造新的執行檔,只需要在 busybox 中撰寫 applet 
的功能即可,大幅縮小空間佔有量。

嗯,接著趕緊建立出一個靜態連結的 BusyBox (Floppy Linux 這個例子用到了 init、
ls、cp、cat、mount、umount、more、ps、sh),之所以要編譯成靜態連結的原因,就是
不希望 Floppy Linux 使用到 glibc 而增加磁碟的使用空間。

以下為建立 BusyBox 的步驟:

	# tar zxvf busybox-0.51.tar.gz
	修改 Makefile 中的 DOSTATIC 參數,從 false 改為 true
	# make

到現在為止,我們已經有了 Liunx kernel 及一些常用的工具程式,似乎還少了 root
filesysem,所以進入打造迷你的 root fs 的步驟。在開始建造 root fs 之前,必須
成為 super uesr 也就是 root,因為接下來必須要用到 mknod 不得不為 root。

首先,為 root fs 建一個目錄叫做 floppy-linux,然後進入 floppy-linux 目錄內:

	# mkdir floppy-linux
	# cd floppy-linux
	
再來為 root filesystem 建立一些標準的目錄:

	# mkdir dev etc etc/rc.d bin proc mnt tmp var
	# chmod 755 dev etc  etc/rc.d bin mnt tmp var
	# chmod 555 proc
	# ln -s sbin bin
	
進入 /dev 目錄下建立一般終端機設備:

	# cd dev
	# mknod tty c 5 0
	# mkdir console c 5 1
	# chmod 666 tty console

接著建立 VGA Display 虛擬終端機設備:

	# mknod tty0 c 4 0
	# chmod 666 tty0
	
再建立 RAM disk 設備:

	# mknod ram0 b 1 0
	# chmod 600 ram

建立 floppy 設備:

	# mknod fd0 b 2 0
	# chmod 600 fd0

最後在建立 null 設備:

	# mknod null c 1 3
	# chmod 666 null

擁有 fs 的框架後,現在開始編輯有關的 shell srcipt。我們先從 /etc/inittab 這
一支 script 下手,因為我們用的是 BusyBox 上的 init,與一般所使用的 init 不太
一樣,會先執行 /etc/init.d/rcS 而非 /etc/rc.d/rc.sysinit,為了做出來的 Floppy
Linux 架構與 Redhat 的架構一樣,所以修改了 BusyBox 中的 init.c 。底下是修到的
部分內容:   

#ifndef INIT_SCRIPT
#define INIT_SRCIPT "/etc/rc.d/rc.sysinit"
#endif

請進入到 /floppy-linux/etc/rc.d 這個目錄下編輯 inittab ,內容如下:

::sysinit:/etc/rc.d/rc.sysinit
::askfirst:/bin/sh

修改 inittab 的權限:

	# chmod 644 inittab

編輯好 rc.sysinit 之後,緊接著就是編輯 /floppy-linux/etc/rc.d 底下的
rc.sysinit,其內容如下:

#!/bin/sh
mount -a

變更其權限:

	# chmod 755 rc.sysinit

再來在編輯 /floppy-linux/etc/ 底下的 fstab , fstab 內容如下:

proc   /proc    proc    defaults   0   0

修改 fstab 權限:

	# chmod 644 fstab

完成上述編輯之後,就要把靜態連結版的 BusyBox 搬到 /floppy-linux/bin 下,並做出
所需要的工具程式的連結符號,其步驟為下:

	# cd /floppy-linux/bin
	# cp /busybox-0.51/busybox ./init
	# ln -s init ls
	# ln -s init cp
        # ln -s init mount
        # ln -s init umount
	# ln -s init more
	# ln -s init ps
	# ln -s init sh

到這裡為止,可以說 floppy-linux 的 root fs 已經製作完畢,不過,一般來說,我們
會採取 RAM Disk 的方式實現。為什麼呢?從前面 [起點:開機片] 的經驗可以得知,
如果我們採取 ramdisk 的方式,而非實體 (physical) 存在於儲存媒體 (當然這裡是指
floppy) 中,那麼,我們甚至可以進一步壓縮這個 ramdisk image,以便放置更多附加
功能。如此一來,整個系統的規劃就變成:

	|------------------|    \
	|Kernel Loader     |    |
	|------------------|    |
	|   Linux Kernel   |    +--  floppy image
	|------------------|    |
	|                  |    | 透過 Kernel Loader 的協助,傳遞給 Kernel
	|  RAM Disk Image ===================================\  變成
	|                  |    |                       ||
	|------------------|    /                       ||
	                                                ||
							|| 
	/		\                               ||
	|-- bin		|                               ||
	|-- boot	|                               ||
	|-- dev		|                               ||
	|-- etc		|                               ||
	|-- home	|                               ||
	|-- lib		+=================================
	|-- mnt		|
	|-- proc	|	Kernel mount 上 ramdisk,成為 root fs
	|-- root	|
	|-- sbin	|
	|-- tmp		|
	|-- usr		|
	`-- var		/

不過呢,這樣的設計雖然多了不少彈性,卻也付出些代價:必須騰出兩倍於 floppy 容
量的記憶體,用來置放解壓縮後的 RAM Disk。當然,就 PC 來說,動輒幾百麥加 (喔,
是 "Mega" 的意思) 的記憶體容量早已司空見慣,但是考量到日後的平台如 PDA、手機
,甚至更小的嵌入式裝置,不得不僅慎行之。

現在,我們就來製作 ram disk ,其方法如下:

	# dd if=/dev/zero of=/tmp/tmp_loop bs=1k count=2048
	# losetup /dev/loop0 /tmp/tmp_loop
	# mke2fs -m 0 /dev/loop0
	# mount -t ext2 /dev/loop0 /mnt
	# cp -a /floppy-linux /mnt
	# umount /mnt
	# losetup -d /dev/loop0
	# dd if=/tmp/tmp_loop | gzip -9 > /tmp/Image.gz
	# rm -f /tmp/tmp_loop
	# sync

此時,可以在 /tmp 底下發現 Image.gz 這個檔案,這就是 ram disk 影像檔,當然啦
,假如經常修改 root fs 的內容,大可將上述步驟寫成一支 shell srcipt,省下無謂
的時間浪費。

到目前為止,我們已經完成製作 floppy-linux 的準備,接下來,就將我們努力的成果
擺入 floppy。我們的 floppy-linux 的 loader 為 syslinux ,所以先要準備好 
syslinux。首先,將空白的磁片格式化,然後載入 sysliunux,步驟如下:

	# mkdosfs /dev/fd0
	# syslinux /dev/fd0
	
完成上述步驟之後,請編輯 syslinux 的組態檔 syslinux.cfg,其 syslinux.cfg 內容
如下:

TIMEOUT 20
DEFAULT linux
LABEL linux
KERNEL linux
APPEND root=/dev/ram0 initrd=Image.gz

然後將 syslinux.cfg、kernel、Image.gz 拷貝到磁片中:

	# mount -t msdos /dev/fd0 /mnt
	# cp /usr/src/linux/arch/i386/boot/zImage /mnt/linux
	# cp /tmp/Image.gz /mnt
	# cp syslinux.cfg /mnt

嗯,大功告成!此時你會發現到我們的 Floppy Linux 的大小竟然只有幾百 k 呢,還不
賴,初試啼聲就有此表現 (據卡內基訓練的說法,適時給自己一點鼓勵,可以激發更多
的成就),現在可以測試親手打造的 Floopy Linux 唷。

■ 反省:進步的機會

剛剛提到 BusyBox 這個相當著名的解決方案,以及其背後的贊助公司:Lineo,事實上
,Lineo 在 Embedded Linux 界提供不少 Open Source 的高品質產品,可以參考網頁:

	http://opensource.lineo.com/

我們將會注意到 [Project List] 下方的幾個項目:

	. BusyBox
	. TinyLogin
	. uClibc
	. udhcp

除了第一項 BusyBox 已經提過外,其他軟體專案計畫,我們會將逐一探討,並且試圖整
合進入咱們的 Floppy Linux 中。

首先看到 uClibc (發音:micro-controller lib-c) 這個專案:

	* uClibc is a C library for embedded systems. You can actually 
	statically link a "Hello World" application under x86 that only 
	takes 4k (as opposed to 200k under GNU libc). Some features are 
	still a work in progress, but for many things, this is a very 
	sensible choice.
	
	URL> http://uclibc.org/uClibc.html
	
簡單來說,在 UNIX 環境,C 語言幾乎成為母語,而一般的 Linux Distribution 中,C
Library 大多是 GNU 的實作 -- glibc,但是隨著 i18n 的廣泛支援、security 的加強
、對 POSIX 的相容性、... 等等歷史因素,glibc 已經龐大以麥加 (mega) 來計算了。
相對來說,embedded system 幾乎都是「量身打造」、「具體而微」,上述 glibc 相當
不適合,尤其版本越新,包袱越重

■ 疑難排除

Q: 您所提及的工具程式,在我的 Linux 上沒找到,怎麼辦?
A:
    Linux 最棒的特色就是從 Kernel、Library,到上面的 Application 都可以修改,
    您大可自己抓 source 慢慢編,或是到 http://rpmfind.net/ 找所需要的 RPM 來
    裝。例如,前面提及的 genromfs 這個工具,如果恰好並未預先安置於 Linux 系統
    中,利用 Rpmfind 的 search 功能,就可以找到為數不少的套件,選擇適合者安裝
    即可

-----------------------
補充﹕

對了,我忘記說明,文章第一段提到這個 Floppy-Linux 發展到 0.2 pre,卻
沒有附上該 image,實在是我的過錯,所以我補上該 link:


http://ccns.ncku.edu.tw/~jimchyun/floppy/flinux.tgz

之前還加入些 fancy 的效果,快照如以下:


http://ccns.ncku.edu.tw/~jimchyun/floppy/screenshot/floppy1.jpg


http://ccns.ncku.edu.tw/~jimchyun/floppy/screenshot/floppy2.jpg

事實上,我在我的 O.S. 專題還有花上若干篇幅介紹 udhcp、uClibc、uCLinux、
syslinux、busybox、TinyLogin、... 等,不過因為那部份是我 partner 寫的,
剛剛找了一段時間,發現我們的報告已經不見,否則應該可以連貫起來的。