前言:
本篇文章以S3C6410公版的Linux BSP和U-Boot來進行分析,文中所有提及的名詞和數據都是以該環境為例,所有的代碼流程也是以該環境為例來進行分析。哈哈。如果有不正確或者不完善的地方,歡迎前來拍磚留言或者發郵件到[email protected]進行討論,先行謝過。
簡單的來說,根文件系統包括虛擬根文件系統和真實根文件系統。在Kernel啟動的初始階段,首先去創建虛擬的根文件系統,接下來再去調用do_mount來加載真正的文件系統,並將根文件系統切換到真正的文件系統,也即真實的文件系統。
在傳統的Windows機器上目錄結構中,可能會包括C:或者D:盤,而他們一般就稱之為特定邏輯磁盤的根目錄。從文件系統的層面來說,每一個分區都包含了一個根目錄區,也即系統中存在多個根目錄。
但是,在Linux系統中,目錄結構與Windows上有較大的不同。系統中只有一個根目錄,路徑是“/”,而其它的分區只是掛載在根目錄中的一個文件夾,如“/proc”和“system”等,這裡的“/”就是Linux中的根目錄。
對應根目錄也就存在一個根目錄文件系統的概念,我們可以將某一個分區掛載為根目錄文件系統,如6410公版中就將mtdblk2掛載為根目錄文件系統。程序中可以通過U-Boot給Kernel指定參數或者編譯選項來指定,如目前的開發板中就通過如下的編譯選項來制定根目錄文件系統:
CONFIG_CMDLINE="console=ttyS0,115200 mem=108M rdinit=/linuxrc root=/dev/mtdblock2"
簡單的來說,根目錄文件系統就是一種目錄結構,包括了Linux啟動的時候所必須的一些目錄結構和重要文件。
根文件系統有兩種,一種是虛擬根文件系統,另外一種是真實的根文件系統。一般情況下,會首先在虛擬的根文件系統中做一部分工作,然後切換到真實的根文件系統下面。
籠統的來說,虛擬的根文件系統包括三種類型,即Initramfs、cpio-initrd和image-initrd。
Initrd是在Linux中普遍采用的一種技術,就是由Bootloader加載的內存盤。在系統啟動的過程中,首先會執行Initrd中的“某一個文件” 來完成驅動模塊加載的任務,第二階段才會執行真正的根文件系統中的/sbin/init。這裡提到的第一階段是為第二階段服務的,主要是用來加載根文件系統以及根文件系統存儲介質的驅動程序。
資料中提到,存在多種類型的Initrd,實際應用中包括無Initrd、Linux Kernel和Initrd打包、Linux Kernel和Initrd分離以及RAMDisk Initrd。
目前,手中項目采用的就是第四種策略。在系統啟動的時候,U-Boot會將Linux Kernel和Rootfs加載到內存,並跳轉到Linux Kernel的入口地址執行程序。這篇文章將側重對該種情況進行分析。
首先不得不從老掉牙的Linux系統的函數start_kernel()說起。函數start_kernel()中會去調用vfs_caches_init()來初始化VFS。
下面看一下函數vfs_caches_init ()的代碼:
void __init vfs_caches_init(unsigned long mempages)
{
unsigned long reserve;
/* Base hash sizes on available memory, with a reserve equal to
150% of current kernel size */
reserve = min((mempages - nr_free_pages()) * 3/2, mempages - 1);
mempages -= reserve;
names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
dcache_init();
inode_init();
files_init(mempages);
[1] mnt_init();
bdev_cache_init();
chrdev_init();
}
代碼【1】:vfs_caches_init()中最重要的函數。函數mnt_init()會創建一個rootfs,這是個虛擬的rootfs,即內存文件系統,後面還會指向真實的文件系統。
接下來看一下函數mnt_init():
Void __init mnt_init(void)
{
unsigned u;
int err;
init_rwsem(&namespace_sem);
mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount),
0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);
if (!mount_hashtable)
panic("Failed to allocate mount hash table\n");
printk("Mount-cache hash table entries: %lu\n", HASH_SIZE);
for (u = 0; u < HASH_SIZE; u++)
INIT_LIST_HEAD(&mount_hashtable[u]);
err = sysfs_init();
if (err)
printk(KERN_WARNING "%s: sysfs_init error: %d\n",
__func__, err);
fs_kobj = kobject_create_and_add("fs", NULL);
if (!fs_kobj)
printk(KERN_WARNING "%s: kobj create error\n", __func__);
[1] init_rootfs();
[2] init_mount_tree();
}
代碼[1]:創建虛擬根文件系統;
代碼[2]:注冊根文件系統。
接下來看一下函數init_mount_tree()的代碼:
static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct mnt_namespace *ns;
struct path root;
[1] mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
if (IS_ERR(mnt))
panic("Can't create rootfs");
ns = kmalloc(sizeof(*ns), GFP_KERNEL);
if (!ns)
panic("Can't allocate initial namespace");
atomic_set(&ns->count, 1);
INIT_LIST_HEAD(&ns->list);
init_waitqueue_head(&ns->poll);
ns->event = 0;
list_add(&mnt->mnt_list, &ns->list);
ns->root = mnt;
mnt->mnt_ns = ns;
init_task.nsproxy->mnt_ns = ns;
get_mnt_ns(ns);
root.mnt = ns->root;
root.dentry = ns->root->mnt_root;
set_fs_pwd(current->fs, &root);
[2] set_fs_root(current->fs, &root);
}
代碼[1]:創建虛擬文件系統;
代碼[2]:將當前的文件系統配置為根文件系統。
可能有人會問,為什麼不直接把真實的文件系統配置為根文件系統?
答案很簡單,內核中沒有根文件系統的設備驅動,如USB等存放根文件系統的設備驅動,而且即便你將根文件系統的設備驅動編譯到內核中,此時它們還尚未加載,其實所有的Driver是由在後面的Kernel_Init線程進行加載。所以需要CPIO Initrd、Initrd和RAMDisk Initrd。另外,我們的Root設備都是以設備文件的方式指定的,如果沒有根文件系統,設備文件怎麼可能存在呢?