2012年2月26日 星期日

kernel, initrd, root filesystem, boot loader

Boot Loader: 機器的啟動程式,可以將一些參數傳遞的給 kernel,如:root 的參數
kernel: OS 本身,會被 boot loader 讀進來
initrd: bootloader 會 load 進來,避免一些 driver 在 root filesystem 尚未 mount 時,無法取得
   ==> initrd 應該還是 kernel load 進來,因為 bootloader 應該不會管到 OS 該如何處理 initrd(否則其他 OS 該怎辦?),所以是 kernel load initrd 進來,並且執行 linuxrc 程式。
   Kernel:主要是處理真正 OS 要做的事情
   initrd:用來解決雜七雜八,但好像需要 kernel 要執行的東西,但為讓 kernel 更為乾淨,將這些東西移到  initrd 的 file system 中,該 file system 的 init 為第一個執行檔,可用來 mount 真正的 root file system。
root filesystem:OS 所需要的 file system。如:C lib 等

Ramdisk 是其中一種在記憶體上實現檔案系統的方法,一般嵌入式系統當中通常沒有硬碟的存在,但是有作業系統就會有檔案系統,因此嵌入式系統就要從記憶體裡面分出一塊區域,來做為檔案系統之用,這個區域就叫Ramdisk。


根檔案系統(Root File System、RFS)

在嵌入式系統中,並不像PC 有著大容量硬碟存在,需利用Ramdisk 模擬的方法執行系統。
Ramdisk 是利用既有的RAM 或Flash,模擬一段磁碟空間,放置Root File System 供程式執行用。

當kernel 開機完成,隨即進入RFS 內執行程式,根檔案系統內容主要如下:
/bin:放置命令檔之目錄。
/etc:放置設定檔之目錄。
/dev:放置硬體節點檔之目錄。
/lib:放置Glibc 之目錄。
/mnt:用於暫時掛載檔案系統之目錄。
/proc:Kernel 虛擬的檔案系統,提供使用者方便
觀看行程資訊與硬體狀況。

做initramfs開機時的錯誤

原因是"/"下需要有init,可以link到/sbin/init

==================================
Linux 内核启动系统时,它必须找到并执行第一个用户程序,通常是 init。
用户程序存在于文件系统,故 Linux 内核必须找到并挂载上第一个(根)文件系统,方能成功开机。

通常,可用的文件系统都列在 /etc/fstab,所以 mount 可以找到它们。但 /etc/fstab 它本身就是一个文件,存在于文件系统中。找到第一个文件系统成为鸡生蛋蛋生鸡的问题,而且为了解决它,内核开发者建立内核命令列选项 root=,用来指定 root 文件系统存在于哪个设备上


十五年前,root= 很容易解释。它可以是软盘或硬盘上的分区(Partition)
如今 root 文件系统可以存在于各种不同类型的硬件(SCSI, SATA, flash MTD) ,或是由不同类型硬件所建立的 RAID 上。

它的位置随着不同启动而不同,像可热插拔的 USB 设备被插到有多个 USB 孔的系统上 - 当有多个 USB 设备时,哪一个是正确的?

root 文件系统也可能被压缩(如何?),被加密(用什么 keys?),或 loopback 挂载(哪里?)。它甚至可以存在外部的网络服务器,需要内核去取得 DHCP 地址,完成 DNS lookup,并登入到远程服务器(需账号及密码),全部都在内核可以找到并执行第一个 userspace 程序之前。

如今,root = 已没有足够的信息。即使将所有特殊案例的行为都放进内核也无法帮助设备列举,加密,或网络登入这些随着系统不同而不同的系统。

更糟的是,替核心加入这些复杂的工作,就像是用汇编语言写 web 软件 :可以做到,但使用适当的工具会更容易完成。核心是被设计成服从命令,而不是给命令。

为了这个不断增加复杂度的工作, 核心开发者决定去寻求更好的方法来解决这整个问题。

解决方法
Linux 2.6 核心将一个小的 ram-based initial root filesystem(initramfs) 包进内核,且若这个文件系统包含一个程序 init,核心会将它当作第一个程序执行。此时,找寻其它文件系统并执行其它程序已不再是内核的问题,而是新程序的工作。

initramfs 的内容不需是一般功能。若给定系统的 root 文件系统存在于一个加密过的网络块设备,且网络地址、登入、加密都存在 USB 设备 "larry" (需密码方能存取)里,系统的 initramfs 可以有特殊功能的程序,它知道这些事,并使这可以运作。

对系统而言,不需要很大的 root 文件系统,也不需要寻址或切换到任何其它 root 文件系统。
这跟 initrd 有何不同?

Linux kernel 已经有方法提供 ram-based root filesystem,initrd 机制。对 2.4 及更早的 kernel 来说,initrd 仍然是唯一的方法去做这一连串的事。但 kernel 开发者选择在 2.6 实现一个新的机制是有原因的。

ramdisk vs ramfs
1. ramdisk (如 initrd) 是 基于ram的块设备,这表明它是一块固定大小的内存,它可以被格式化及挂载,就像磁盘一样。
这表明 ramdisk 的内容需先格式化并用特殊的工具(像是 mke2fs 及 losetup)做前置作业,而且如同所有的块设备,它需要文件系统驱动程序在执行时期解释数据。这也有人工的大小限制不论是浪费空间(若 ramdisk 没有满,已被占用的额外的内存也不能用来做其它事)或容量限制(若 ramdisk 满了,但其它仍有闲置的内存,也不能不经由重新格式化将它扩展)。

但 ramdisk 由于缓冲机制(caching)实际上浪费了更多内存。Linux 被设计为将所有的文件及目录做缓存,不论是对块设备的读出或写入,所以 Linux 复制数据到 ramdisk及从 ramdisk 复制数据出来,page cache 给 file data 用,而 dentry cache 给目录用。ramdisk 的下面则伪装为块设备。

几年前,Linus Torvalds 有一个巧妙的想法:Linux 的缓存是否可以被挂载一个文件系统?只要保持文件在缓存中且不要将它们清除,直到它们被删除或系统重新启动?Linus 写了一小段程序将缓存包起来,称它为 ramfs,而其它的 kernel 开发者建立一个加强版本称为 tmpfs(它可以写数据到 swap,及限制挂载点的大小,所以在它消耗完所有可用的内存前它会填满)。initramfs 就是 tmpfs 的一个实例。

这些基于ram 的文件系统自己改变大小以符合数据所需的大小。增加文件到 ramfs(或增大原有的文件)会自动配置更多的内存,并删除或截去文件以释放内存。在块设备及缓存间没有复制动作,因为没有实际的块设备。在缓存中的只 是数据的复制。更好的是这并不是新的程序代码,而是已存在的 Linux 缓存程序代码新的应用,这表示它几乎没有增加大小,非常简单,且基于已经历测试的基础上。

系统使用 initramfs 作为它的 root 文件系统甚至不需要将文件系统驱动程序内建到 kernel,因为没有块设备要用来做文件服务器。只是存在内存中的文件罢了。

initrd vs initramfs
底层架构的改变是 kernel 开发者建立一个新的实现的理由,但当他们在那里时他们清除了很多不好的行为及假设。

initrd 被设计为旧的 root= 的 root 设备检测程序代码的前端,而不是取代它。
它执行 /linuxrc,这被用来完成设定功能(像是登入网络,决定哪个设备含有 root 分区,或用文件做为 loopback 设备),告诉 kernel 哪个块设备含有真的 root 设备(通过写入de_t 数据到 /proc/sys/kernel/real-root-dev),且回传给 kernel,所以 kernel 可以挂载真的 root 设备及执行真的 init 程序。

这里假设“真的根设备”是块设备而不是网络共享的,同时也假设 initrd 自己不是做为真的 root 文件系统。kernel 也不会执行 /linuxrc 而做为特殊的进程(ID=1),因为这个 process ID(它有特殊的属性,像是做为唯一无法被以 kill -9 的 process) 被保留给 init,kernel 在它挂载真的 root 文件系统后会等它执行。

用 initramfs,kernel 开发者移除所有的假设。当 kernel 启动了在 initramfs 外的 /init,kernel 即做好决定并回去等待接受命令。
用 initramfs,kernel 不需要关心真的 root 档案系统在哪里,而在 initramfs 的 /init 被执行为真的 init,以 PID 1。(若 initramfs 的 init 需要不干涉特别的 PID 给其它程序,它可以用 exec() 系统呼叫,就像其它人一样)

总结
传统的 root= kernel 命令列选项仍然被支持且可用。但在开发支持initial RAM disk支持内核时,提供了许多优化和灵活性。


译者注
1. 查看initramfs的内容
# mkdir initrd
# cd intrd
# cp /boot/initrd.img initrd.img
# gunzip initrd.img
# cpio -i --make-directories < initrd.img
#

2. 创建initramfs
2.a. mkinitramf
# mkinitramfs -o /boot/initrd.img 2.6.2
Note: 2.6.25是需要创建initramfs的kernel版本号,如果是给当前kernel制作initramfs,可以用uname -r查看当前的版本号。提供kernel版本号的主要目的是为了在initramfs中添加指定kernel的驱动模块。mkinitramfs会把 /lib/modules/${kernel_version}/ 目录下的一些启动会用到的模块添加到initramfs中。
2.b. update-initramfs
更新当前kernel的initramfs
# update-initramfs -u
在添加模块时,initramfs tools只会添加一些必要模块,用户可以通过在/etc/initramfs-tools/modules文件中加入模块名称来指定必须添加的模块。
命令:mkinitramfs, update-initramfs

3. mkinitcpio
在Arch Linux中,有一个新一代的initramfs制作工具。相对于老的mkinitrd和mkinitramfs,它有以下很多优点。查看详细《使用mkinitcpio》。

参考链接:
精通initramfs构建 http://linuxman.blog.ccidnet.com/blog-htm-do-list-uid-60710-type-blog-dirid-14402.html
制作initramfs镜像 http://www.diybl.com/course/6_system/linux/Linuxjs/200888/135080.html

=====================================================


Table of Contents
I. 隨手筆記
note0002 -- initrd(initial RAM disk) 何時被 load
note0001 -- PID = 1 的 process 如何被產生

I. 隨手筆記

Table of Contents
note0002 -- initrd(initial RAM disk) 何時被 load
note0001 -- PID = 1 的 process 如何被產生

note0002


Name

note0002 -- initrd(initial RAM disk) 何時被 load

initrd(initial RAM disk) 何時被 load

在 kernel thread init 生出 user thread init 前就會先將 initrd 給載入, 這樣才有第一個 root file system。
Kernel thread init 中有一段 code:
    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0)
    {
 ramdisk_execute_command = NULL;
 prepare_namespace();
    }
  
called functions:
    prepare_namespace()@.../init/do_mounts.c
               ↓
    initrd_load()@.../init/do_mounts_initrd.c
               ↓
    handle_initrd()@.../init/do_mounts_initrd.c
  
在 handle_initrd() 中可看到會產生一個 kernel thread 'do_linuxrc',以執行 /linuxrc 這個 script。
另外,initrd 與真正的 root file system 的先後關係看參考下圖 (取自 http://www.almesberger.net/cv/papers/ols2k-9.pdf)及 .../Document/initrd.txt。

note0001


Name

note0001 -- PID = 1 的 process 如何被產生

PID = 1 的 process 如何被產生

booting 流程正式進入 linux kernel,並執行到 start_kernel() 後,在 start_kernel() 裏的最後一步會執行 rest_init()。在 rest_init() 中會 呼叫
kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
以產生另一個 kernel thread,也就 init。注意第一個參數,實際上這個 init 是 定義在同一個檔案裏的一個 function:static int init(void * unused)。而在 init() 的最後幾行會看到以下片斷:

 if (execute_command)
  run_init_process(execute_command);

 run_init_process("/sbin/init");
 run_init_process("/etc/init");
 run_init_process("/bin/init");
 run_init_process("/bin/sh");

 panic("No init found.  Try passing init= option to kernel.");
                

run_init_process() 會嘗試執行這個四個執行檔。如果執行成功,就不會 return。 。換句話說,init()@.../init/main.c 這個 thread 會被上面四個執行檔之一給取代 掉。
所以到此時,系統中會存在二個 thread:一是 start_kernel,一是 /sbin/init (或其餘三個執行檔之一),也就是 user space 的第一個 processs (PID 為 1)。 其中,start_kernel() 最終會進入 cpu_idle()@.../arch/arm/kernel/process.c 。注意裏頭有一個無窮迴圈,start_kernel() thread 就會在這裏不斷 loop。

 start_kernel --> cpu_idle() --> loop forever
  |
  | spawned by kernel_thread()
  |
  |--> init() --> /sbin/init (1st user space process)

==========================================================

Kernel與initrd的基本建造

之前的Kernel或者initrd,我們都是拿人家已經做好的來使用而以,所以我們要來講如何用工具自己創造自己合適的kernel跟initrd.

Kernel編譯的注意事項

目前2.6的比較簡單,不用make depmod; make clean。
  • .confg 這是所有編譯的選項,可直接改裡面的參數。
    • 小心,必須不可以有重複的CONFIG_XXX存在,因為comment掉的後面會蓋掉前面的。例如

      .config

      CONFIG_IGB=m
      CONFIG_IGBVF=m
      CONFIG_IGB_DCA=y
      # CONFIG_IGB is not set
      # CONFIG_IGBVF is not set
      這樣IGB這個module是不會被compile的,所以用make menuconfig是比較簡單的。
    • kernel debug info不要編譯,會使kernel跟modules變得超級肥大。
  • make menuconfig 選擇編譯選項的Text界面,需要安裝libcurse跟tcl,也可以用存檔就是存.config
  • make bzImage 建立壓縮的kernel。make -j 8可以平行處理8個 job。
  • make modules 編譯各模組
  • make modules_install 把各模組放到/lib/modules/`uname -r`/下面去,並且建立module間的dependacny
  • depmod -a 這會重新尋找目前kernel下的所有模組,重新建立彼此的dependancy,如果你是third party的driver,基本上會放在/lib/modules/`uname -r`/extra底下,這時必須重建modules.dep。
modprobe是用來裝module的,所有的kernel模組都在/lib/modules/`uname -r`/kernel/下,以xxx.ko存在。系統一起來的時候,通常/etc/init.d/下的script會去叫modprobe,他會去下面兩個地方找相關設定
  • /etc/modprobe.conf
  • /etc/modprobe.d/xxx
modprobe.conf與modprobe.d下的檔案設定形式與格式是一樣的。
  • alias wildcard modulename : 這通常是compatible問題,以前模組名稱變化,或者長名字變短名字。
    • alias usbdevfs usbcore
    • alias nfs* nfs
    • modprobe nfs4時,其實是會帶上nfs這模組。
  • options modulename option
    • options loop max_int=16
    • modprobe loop時,會自動帶上max_int=16這個參數給loop這模組。
  • install modulename command
    • "install probe-ethernet /sbin/modprobe e100 || /sbin/modprobe eepro100"
    • modprobe probe-ethernet 將會先找e100再找eepro100
  • remove modulename command 跟install一樣只是是rmmod發生作用。
  • include filename 可以include其他的檔案。
另外debian的有一個/etc/modules這個不標準的設定,因為本來模組driver由udev來驅動,但有的模組不是硬體,所以udev無法驅動,這只有從/etc/init.d/下自己驅動,但每一個都要這麼作就要重複寫一樣的script,所以debian有一個/etc/modules來驅動純software的模組像loop這種。

kernel的參數傳遞

在grub/lilo/syslinux等Linux Loader都可以傳某些參數過去,因為這些OS dependent的loader是懂得Linux的特殊記憶體位置在做什麼的。所以像syslinux裡面可以用append來傳進去給kernel。/proc/cmdline裡面可以看到這些傳過去的參數,這些參數如果對kernel有意義,則kernel會拿進來用,如果對kernel沒有意義,則kernel會把他們放進環境變數裡面,不過最保險的方法還是去讀/proc/cmdline。因為如果不是很了解kernel,可能傳進去的剛好是某個kernel要的參數名字,那就不會出現在環境變數中了。 一些基本的kernel參數:
  • root
  • initrd
  • init
  • nfsroot

initrd探討

在作業系統沒有起來前,有些狀況是需要一個小的OS環境來作些工作的。例如全新安裝,crash急救,網路開機等等。這時我們只需要一點點的工具,然後load到記憶體中,把某段記憶體當成disk使用,就有一個小的環境跟工具可以使用了。這就是initrd (init ram disk)。這裡面其實就是一個小的暫時root file system。在boot的時候,最重要就是要掛上root file system,所以initrd裡面最重要的就是要確定最後真正root device是哪一個,並且在這小小的檔案中把負責啟動root device的driver load進來。最後再掛到真正的root去把其他的driver跟init完成。所以如果當時file system是有切割很多partiitions的,例如/var /home等等,這些都是在真實root上的/etc/fstab下設定的,只要最後進到真正的root,那麼就可以執行真正root下的init了。 root device的可能性
  • IDE/SCSI - 這會去load driver module
  • PXE NFS - 這要先把nfs root編進kernel
  • SAN boot - 跟SCSI卡的driver大同小異,一般使用者就不用管firmware怎麼implement的。
所以如果你的root device是IDE,那initrd裡面有driver了就會load進來,這時候掛上真的硬碟上的root file system,然後再一一掛上其他的driver,所以root device的driver跟其他device的driver掛上的時間點是不一樣的。root device掛上的是在initrd裡面的/lib/modules/`uname -r`/,其他的是在真正root下的/lib/module/`uname -r`/。

2.4與2.6的不同

最後雖然都是initrd但是2.4 2.6的格式與init的方法不太一樣。2.4的格式必須是一個檔案系統,2.6的支援檔案系統跟cpio形式的。都可以用gzip壓起來。
這不僅僅是格式不同,在kernel內部的處理方式也不同。
2.4的initrd是一個init ram disk, 是一個block device上掛載著一個一般的file system。
而2.6的是一個initramfs是一個rootfs的image,rootfs他是藏在kernel裡面的一個小file system就跟ext2,ext3...一樣有目錄檔案讀寫等等,不須額外編譯 ,在filesystems的link list結構裡面就自動帶有這麼一個filesystem,就像網路device自動帶有lo這個interface一樣。
rootfs是base在以前的ramfs上的,在kernel裡面的fs下面有一個ramfs這個filesystem,現在裡面init時自動會帶上一個rootfs。cpio的initramfs image展開來就load到rootfs的相對記憶體上了。

file system格式的initrd (2.4 版)

  • 使用一個ramdisk /dev/ram0來當作這個file system的block device
  • 這個file system必須在compile kernel時就要進去,不能是module,在debian通常會用cramfs。
  • 執行/linuxrc
  • /linuxrc結束時,會回到kernel,必須先用pivot_root把root掛上,kernel會認為真正的root已經掛上去了,直接執行/sbin/init為第一個user space process,
  • 使用pivot_root來改變整個系統root到真正root上面去。

cpio格式的initramfs (2.6 版)

  • 此種initrd為使用一個kernel內部rootfs的initramfs image,這是定義在kernel內部一個固定的address下。kernel會展開initrd放到特定的地方去。
  • 所以不需要特別編譯一個中介的file system到kernel內。
  • 執行/init
  • /init就是第一個user space process,不再回到kernel。必須使用klibc的run-init 或者busybox的switch_root跳到真實root file system上。並且自己執行/sbin/init。
  • rootfs的initramfs image內容無法umount,唯一能做的是delete掉所有內容來釋放記憶體。
  • 所以傳統pivot_root命令工具使用上很麻煩,必須自己implement新的流程完成,或使用新的工具,busybox提供switch_root或者klibc提供run-init命令。

解開initrd

  • kernel 2.4的initrd: # mount -o loop initrd.img /mnt/initrd
  • kernel 2.6的initrd: # gzip -dc ../initrd.gz | cpio -id --no-absolute-filenames
2.4的initrd有的mount不會自動解開gzip,所以要自己先解開。另外file system也要看有沒有load進來,像debian kernel 2.4都喜歡用cramfs來建造initrd,這就要把這個檔案系統編譯進來。
我們用debian的CDROM裡面的initrd來解開,解開後我們觀察/lib/modules/`uname -r`的driver會發現他只有放block device的跟檔案系統而已,這是因為我們只需要mount root所需要的東西就可以了。

建造initrd

建造initrd有兩種狀況,一種是要boot原本已有的系統,這需要了解不同distro的需求,好在各distro已經有script幫我們了,只要一行命令就可,另一種是我們想要自己的distribution或者我們要做自己的急救開機片,或者我們embadded系統上開機要使用,這時要自己建造一個。   

distros的initrd建造

不同的distribution有他們自己的工具創造initrd,主要是要符合自己的init程序,各家的內定init方法雖然大同小異但不是完全一樣的。還有initrd內放的軟體內容也不一樣的。
  • Debian : # mkinitrd -o xxx.img 2.6.16
  • Fedora : # mkinitrd xxx.img 2.6.18-164.el5
  • Ubuntu : # mkinitrd -o xxx.img 2.6.16
  • Centos : # mkinitrd xxx.img 2.6.18.1
好像大家的command都是mkinitrd,不過有些選項可能不太一樣。新的建造2.6的cpio/initramfs形式的initrd命令,像debian/ubuntu的已經變成mkinitramfs
  • mkinitramfs /boot/initrd-2.6.18-amd64.img 2.6.18

自己的initrd

這個主要是要有一個假的root, fakeroot, 然後在下面放很多將來開機用到的檔案,然後用cpio跟gzip捆綁起來一個檔案,initrd.img就好。這裡面的關鍵點有兩個,一個是init這隻script要做哪些重要的事情,一個就是做這些事情該放的檔案有哪些。基本上新的都已經換到cpio形式的了,所以只說明cpio /init的。 最簡單的/init就是執行一個shell,由initramfs的內容當成root filesystem,把該load的硬體driver帶上,可以把mp3的播放軟體放裡面就是小mp3播放embedded system。但是真正有用的話還是要去mount一個大root filesystem來得到更多的應用。kernel 2.6 /init要做的事情有:
  • mount proc sysfs tmpfs
  • 偵測硬體並且load root device所需drivers,例如raid, SAN HBA卡等等。就是正常Linux上的工具,現在通常用udev了。
  • 轉換到到真正的root file system並且從此以後使用這個root,這跟chroot不一樣的是chroot只是單一process的改變,而pivot_root是整個系統都變成新root。以往pivot_root是呼叫pivot_root的process繼續留在舊的root,系統變成新root,這個呼叫pivot_root的通常是個shell script,他會作一些舊root的 clean up工作,最後才chroot到新root去。
  • 清掉initrd的記憶體,以往2.4 initrd是改成新root後,還要返回kernel,由kernel去呼叫新root的/sbin/init,並且umount掉initrd,但是initramfs的initrd裡面的/init最後自己要變成root後就不返回kernel了,且initramfs是內部的一塊記憶體空間,無法umount,只能delete掉所有檔案。新工具busybox的switch_root或者klibc的run-init可以簡單一行命令的作這些事,不再用pivot_root了。
  • switch_root或run-init的stdin/stdout/stderr都需導向新root的/dev/console去。
  • 通常到新root去,從此以後就呼叫真正的BSD init或者sysV init來啟動整個系統了。
現在我們做一個自己的initrd並且用virtual machine來測試他,主要是udev, 尋找root device的kernel modules有哪些跟所需要的工具程式。
  • udev
    • 可以解開系統上的/boot下的initrd, copy /lib/udev, /etc/udev。
    • 也可用package的列出檔案方法找出,例如debian的用dpkg --listfiles udev就可以找出所有裝在系統上的bin跟lib有哪些
      • 用ldd 找出binary所需的library,copy到fakeroot去。
      • udev使用的modprobe可能有特別的參數,所以要確定他使用的工具執行起來是否是對的,busybox的有時候是不能用的。
      • 在/lib/udev/rules.d下面去grep program這個字串,會有每個rule偵測硬體所需的程式,一樣要copy出binary跟library.
  • kernel moduls
    • 用系統上的/boot下面的initrd,2.6的用gzip -dc initrd.img | cpio -i 解開
    • 然後copy裡面的lib/modules下的檔案到fakeroot去。
  • 工具程式
    • 可以用busybox,也可以copy系統上的mount, mknod, dhclient...等等東西
    • copy系統上的跟udev的一樣,用ldd 去找出bin下面的工具所需的library,copy到fakeroot去。
下面簡單例子用busybox還有udev來作工具。其中注意的是
  • busybox的switch_root需要是PID = 1的狀態,所以必須用exec來執行。
  • 我假設已經有一個建好的root在sda2上了。而且有/sbin/init在sda2上,其實就是用本來已有的linux系統來玩就可以了。
init script

/init

#!/bin/ash echo "Loading Gyoza..." export PATH=/bin:/sbin # device and pseudo filesystem dir [ -d /dev ]     || mkdir -m 0755 /dev [ -d /sys ]     || mkdir /sys [ -d /proc ]    || mkdir /proc [ -d /tmp ]     || mkdir /tmp # sysfs need by udev mount -nt sysfs none /sys mount -nt proc none /proc mount -nt tmpfs /tmp # udev for 2.6 kernel, no shm and pts any more. tmpfs_size='10m' mount -nt tmpfs -o size=$tmpfs_size,mode=0755 udev /dev # necessary dvices files in kernel for booting [ -e /dev/console ]     || mknod /dev/console c 5 1 [ -e /dev/null ]        || mknod /dev/null c 1 3 [ -e /dev/tty ]         || mknod /dev/tty c 5 0 [ -e /dev/tty0 ]        || mknod /dev/tty0 c 4 0 [ -e /dev/tty1 ]        || mknod /dev/tty1 c 4 1 # necessary devices for rescue the system [ -e /dev/sda ] || mknod /dev/sda b 8 0 [ -e /dev/sdb ] || mknod /dev/sdb b 8 16 [ -e /dev/sg0 ] || mknod /dev/sg0 c 21 0 [ -e /dev/hda ] || mknod /dev/hda b 3 0 [ -e /dev/hdb ] || mknod /dev/hdb b 3 64 [ -e /dev/hdc ] || mknod /dev/hdc b 22 0 [ -e /dev/sda1 ]        || mknod /dev/sda1 b 8 1 [ -e /dev/sda2 ]        || mknod /dev/sda2 b 8 2 [ -e /dev/sda3 ]        || mknod /dev/sda3 b 8 3 [ -e /dev/sda4 ]        || mknod /dev/sda4 b 8 4 [ -e /dev/sda5 ]        || mknod /dev/sda5 b 8 5 [ -e /dev/sda6 ]        || mknod /dev/sda6 b 8 6 # root [ -d /root ] || mkdir --m 0700 /root # var [ -d /var ] || mkdir --m 0755 /var  parse command line for gyoza distro # env passed by bootloader in /proc/cmdline for cmd in $(cat /proc/cmdline); do         case $cmd in                 *)                 echo "cmdline $cmd";                 ;;         esac done # all default variables will not be env in 2.6 # default variables from __setup() in the kernel code, so be careful echo "initrd $initrd" # load drivers necessary for booting to find the root device echo "loading... $driver" depmod -a /sbin/udevd --daemon /sbin/udevadm trigger /sbin/udevadm settle true # root=/dev/nfs and root=/dev/ram0 will be handled by kernel prepare_namespace() #  # change to real root
modprobe ext3
modprobe iso9660
mount /dev/hda1 /root
if test $? != 0; then
        mount /dev/sda1 /root
        if test $? != 0; then
                /bin/ash
        fi
fi
mount -o move /dev /root/dev
mount -o move /proc /root/proc
mount -o move /sys /root/sys
mount -o move /tmp /root/tmp
cd /root
exec switch_root . /sbin/init < dev/console > dev/console
暫時root的目錄檔案結構

initrd root

/init /dev /lib /bin /root /sys /tmp /proc
好了,我們用 $ cd fakeroot $ find . | cpio -o -H newc | gzip -9 > ../initrd.img 建造一個新的initrd.img。最後把他跟我們編譯的kernel放進到isolinux.cfg裡面或者pxeboot的pxelinux.cfg/default裡面就好。用VM開機。當然我們也可以不需要轉到一個真的root file system而繼續使用原本的rootfs就好,這通常可以用來upgrade firmware或者全新安裝一個Linux。

udev偵測硬體

在上面偵測硬體時,我們用上了現在新的udev工具,udev是user dev的意思,由於後來硬體都有所謂的熱插拔,或者像usb device可以任意時間內裝上卸除系統,所以udev可以根據硬體變化動態產生/dev/下的node。這工具的使用可以讓我們適用各種硬體,帶上所需driver,不是一開機後死死的用modprobe去load特定的硬體而已。initrd 主要要帶上的是root filesystem所在的  block  device驅動程式而已,所以我們還是可以用udev來load drivers。udev的使用為start一個udevd這個daemon,他會監視硬體變化的event,這是由kernel丟出來,然後udevd會去讀的,放到一個 queue去,然後用udevadm trigger產生cold plug,來一次性的處理這個event queue裡面的硬體,處理方式根據udev rules來處理,udev source code裡面本來就有寫好的一些rules,我們可以用這個來擴充我們的硬體rules.

udev編譯

目前他有一些dependancy,都是kernel.org下的那些相關tools,請看http://www.kernel.org/pub/linux/utils
  • 編譯一些工具
    • 在 http://www.kernel.org/pub/linux/utils 下面有很多關於kernel的utilities,去kernel/hotplug下面下載udev,基本packages目前為
      • util-linux
      • module-init-tools, 這會被libkmod取代,裡面就是modprobe...這些東西
      • usbutils (需要libusb)
      • udev (新版的請disable introspection)
編譯時要小心的是這是將來用在root的上面的,所以library相關參考位置都在/ 上,如果真的要作一份全新的,還是用Linux From Scratch先建立一個compile的tool chain, 再chroot去compile. udev compile時的configure不要忘了用configure --prefix=/usr來處理,configure --help可以看到一些compile選項。compile完後,新的udev的工具程式有udevd這個daemon跟udevadm這個管理工具程式。以及extra目錄下的一些工具。
  • udevd --daemon :使udevd成為daemon
  • udevadm command
    • udevadm trigger :  產生cold plug, 由於機器內部的硬體並沒有真正hot plug, 所以產生一個code plug來通知udev queue.
    • udevadm settle true : 檢查event queue裡面是不是真的全部結束
extra下工具主要是用來識別硬體Id的,這可以用在udev rules裡面,當看到某個id就去驅動某個driver。
  • scsi_id
  • vol_id
  • pci_id
不過新版的有的已經拿掉了,加上其他的工具,像是pci-db, usb-db之類的。

udev rules

udev會從/lib/udev/rules.d/下讀取default的rules,再從/etc/udev/rules.d下讀取custom的rules還有/dev/.udev/rules.d下讀取暫時的rules。 基本rules是一對key=value pair,用來表示符合的條件

udev rules

KERNEL=="sda", NAME="my_root_device" KERNEL=="hda", DRIVER=="ide-disk", SYMLINK+="my_local_hdd" SUBSYSTEM=="scsi", ENV{DEVTYPE}=="scsi_device", TEST!="[module/sg]", RUN+="/sbin/modprobe sg"
其中比較重要的一些key就是
  • KERNEL
    • kernel 內部的event device 名字,例如eth0, hda, sda, sg0...這些在/dev/下原本的device名字或者網路interface名
  • SUBSYSTEMS
    • kernel 內部subsystem的event device名字,例如block, scsi, usb...等等這種device的抽象分類系統,
  • ATTR
    • /sys下面的資訊的讀取
  • SYMLINK
    • 在/dev/下給一個symbolic link名字,通常像/dev/cdrom就是這樣來的
  • RUN
    • 這就是程式執行發動者,通常modprobe就是在RUN這裡面
  • IMPORT(program)
    • 這是使用一個外部程式來得到device的properties。例如ata_id,blk_id等等來找到device的ID。
基本上這是跟kernel內部丟出來的資訊有關的,如果不是kernel developer可以先試著用
  • udevadm info -a -p /sys/
下面的目錄得到一些資訊,例如udevadm info -a -p /sys/block/sda
在以前本來是要針對每一個硬體來modprobe,後來的新版的udev對於module會去掃出來硬體device的MODALIAS,在/sys/devices/下面可以掃出所有的modalias,然後在rule中加一個modprobe $MODALIAS就好了。這個modalias可以在/lib/modules/`uname -r`/modules.alias下面看到,也可以modinfo去看一個特定的module會看到,這info是由kernel根據driver硬體產生的。這個modalias大概長這樣
pci:v00008086d00001C26sv00001458sd00005006bc0Csc03i20
只要去modprobe他也可以load上這個driver.

Busybox與klibc

在創建自己的initrd時,我們用了很多工具是一般用的,像mount, mkfs...,這些東西都很肥大,因為他們是標準工具,有很多種選項,但是其實跟微軟的office一樣,大部分時候我們只用到一小部分功能,所以有一個瑞士刀Busybox,有很多功能,甚至有web server的功能,編譯出來的code很小,我們可以用在embedded系統上. Busybox的選項跟kernel一樣在make時要設定,
$ make menuconfig
$ make
就可以產生我們想要的busybox了。
而編譯執行檔需要的library,可以選擇最小化的klibc,這個程式庫可以提供大部分的功能,也提供一些工具程式,像是cpio, dash, fstype, mkdirmknod, mount, nfsmount, run-init, etc.等等可以來mount第一個root filesystem。gcc 多加-Os這個選項。 自己的mp3 player 所以其實我們也可以做自己的mp3 player這樣的embadded system,只要我們建好vmlinuz,跟initrd,只是這個initrd裡面有init,然後他的driver module裡面有所有的相關硬體dirver,最後擺上一個播放mp3 player的軟體,機器boot之後,就進到這個mp3 player就可以了。當然如果真要量產的話,user界面要有一些按鈕,這些按鈕按一按,必須讓相對應的mp3 player軟體作業。
kernel的header有的資料結構是user space的程式有興趣的。可以make headers_install來安裝,不過通常系統中可能都有了,會放到/usr/include/linux下面。

沒有留言: