一、基本概念介紹
1.1 rootfs
什麼是根檔案系統?理論上說一個嵌入式裝置如果核心能執行起來,且不需要使用者程序的話(估計這種情況很少),是不需要檔案系統的。檔案系統簡單的說就是一種目錄結構,由於linux
作業系統的裝置在系統中是以檔案的形式存在,將這些檔案分類管理以及提供和核心互動的介面,就形成了一定的目錄結構也就是檔案系統。檔案系統是為使用者反映系統的一種形式,為使用者提供一個檢測控制系統的介面。
而根檔案系統,就是一種特殊的檔案系統。那麼根檔案系統和普通的檔案系統有什麼區別呢?
借用書上的話說就是,根檔案系統就是核心啟動時掛載的第一個檔案系統。由於根檔案系統是啟動時掛載的第一個檔案系統,那麼根檔案系統就要包括linux
啟動時所必須的目錄和關鍵性的檔案,例如linux
啟動時都需要有使用者程序init
對應的檔案,在linux
掛載分割槽時一定要會找/etc/fstab
這個掛載檔案等,根檔案系統中還包括了許多的應用程式,如 /bin
目錄下的命令等。任何linux
啟動時所必須的檔案的檔案系統都可以稱為根檔案系統。
根檔案系統,對應/
目錄節點,分為虛擬rootfs
和真實rootfs
。
1.1.1 虛擬rootfs
虛擬rootfs
由核心自己建立和載入,僅僅存在於記憶體之中,其檔案系統是tmpfs
型別或者ramfs
型別。
1.1.2 真實rootfs
真實rootfs
則是指根檔案系統存在於儲存裝置上,核心在啟動過程中會在虛擬rootfs
上掛載這個儲存裝置,然後將/
目錄節點切換到這個儲存裝置上,這樣儲存裝置上的檔案系統就會被作為根檔案系統使用。
1.2 initrd
initrd
總的來說目前有兩種格式:image
格式和cpio
格式。
當系統啟動的時候,bootloader
會把initrd
檔案讀到記憶體中,然後把initrd
檔案在記憶體中的起始地址和大小傳遞給核心;
- 可以透過
bootargs
引數initrd
指定其地址範圍; - 也可以透過備樹
dts
裡的chosen
節點的中的linux,initrd-start
和linux,initrd-end
屬性指定其地址範圍;
核心在啟動初始化過程中會解壓縮initrd
檔案,然後將解壓後的initrd
掛載為根目錄,然後執行根目錄中的/linuxrc
指令碼;
cpio
格式的initrd
為/init
;image
格式的initrd
為/initrc
;,
我們可以在這個指令碼中載入真實檔案系統。這樣,就可以mount
真正的根目錄,並切換到這個根目錄中來。
1.2.1 image-initrd
image-initrd
是將一塊記憶體當作物理磁碟,然後在上面載入檔案系統,比如我們在《Rockchip RK3399 - busybox 1.36.0
製作根檔案系統》製作的ramdisk
檔案系統就是就屬於這一種。
1.2.1.1 核心ramdisk
配置
為了能夠使用ramdisk
,核心必須要支援ramdisk
,即:在編譯核心時,要選中如下配置;
Device Drivers --->
[*] Block devices --->
<*> RAM block device support
(1) Default number of RAM disks
(131072) Default RAM disk size (kbytes)
配置完成,會在.config
生成如下配置:
CONFIG_BLK_DEV_RAM=y
CONFIG_BLK_DEV_RAM_COUNT=1
CONFIG_BLK_DEV_RAM_SIZE=131072
同時為了讓核心有能力在核心載入階段就能裝入ramdisk
,並執行其中的內容,要選中:
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
會在配置檔案中定義CONFIG_BLK_DEV_INITRD
。
1.2.1.2 啟動引數
在uboot
啟動核心時指定根檔案系統的位置;修改uboot
啟動引數bootargs
中的root
屬性為root=/dev/ram0
,表示根目錄掛載點為/dev/ram0
塊裝置;
假設ramdisk.gz
檔案被載入到記憶體指定位置0x42000000
。修改bootargs
加入如下配置:
initrd=0x42000000,0x14000000
initrd
引數格式為:地址,長度,這裡裝置RAM
地址為0x42000000
起始,只要是在核心RAM
實體地址空間內。長度這裡只要比ramdisk.gz
壓縮包大小大就可以了。
1.2.1.3 掛載方式
當系統啟動的時候,bootloader
會把image-initrd
檔案讀到記憶體中,核心將image-initrd
儲存在rootfs
下的initrd.image
中, 並將其讀入/dev/ram0
中,根據root
是否等於/dev/ram0
做不同的處理;
root != /dev/ram0
:bootloader
- >kernel
->image-initrd
(載入訪問real rootfs
的必備驅動) ->/linuxrc
指令碼(載入real rootfs
),核心解除安裝/dev/ram0
,釋放initrd
記憶體,最後核心啟動init
程序(/sbin/init
);root = /dev/ram0
:bootloader
->kernel
->image initrd
直接將/dev/ram0
作為根檔案系統, 核心啟動init
程序/sbin/init
。
1.2.2 cpio-initrd
特指使用cpio
格式建立的initrd
映像,和編譯進核心的initramfs
格式是一樣的,只不過它是獨立存在的,也被稱為外部initramfs
,
1.2.2.1 核心配置
需要在make menuconfig
中配置以下選項就可以了:
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
1.2.2.2 啟動引數
透過備樹dts
裡的chosen
節點的中的linux,initrd-start
和linux,initrd-end
屬性指定其地址範圍;
chosen {
linux,initrd-start=xxxxxx
linux,initrd-end=xxxxxx
}
1.2.2.3 掛載方式
當系統啟動的時候,bootloader
會把cpio-initrd
檔案讀到記憶體中,核心將cpio-initrd
釋放到rootfs
,結束核心對cpio-initrd
的操作。
bootloader
-> kernel
-> cpio-initrd
(載入訪問real rootfs
的必備驅動等)-> /init
指令碼(載入real rootfs
,並啟動了init
程序/sbin/init
)。
1.3 initramfs
在linux 2.5
核心開始引入initramfs
技術,是一個基於ram
的檔案系統,只支援cpio
格式。
它的作用和cpio-initrd
類似,initramfs
和核心一起編譯到了一個新的映象檔案。
initramfs
被連結進了核心中特殊的資料段.init.ramfs
上,其中全域性變數__initramfs_start
和__initramfs_end
分別指向這個資料段的起始地址和結束地址。核心啟動時會對.init.ramfs
段中的資料進行解壓,然後使用它作為臨時的根檔案系統。
1.3.1 核心配置
要製作這樣的核心,我們只需要在make menuconfig
中配置以下選項就可以了:
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/opt/filesystem) Initramfs source file(s)
其中/opt/filesystem
就是根目錄,這裡可以使一個現成的gzip
壓縮的cpio
,也可以使一個目錄。
1.3.2 掛載方式
initramfs
和cpio-initrd
的區別, initramfs
是將cpio rootfs
編譯進核心,而cpio-initrd
中cpio rootfs
是不編譯入核心,是外部的。其掛載方式和cpio-initrd
是一致的。
當系統啟動的時候,核心將initramfs
釋放到rootfs
,結束核心對initramfs
的操作。
bootloader
-> kernel
-> initramfs
(載入訪問real rootfs
的必備驅動等)-> /init
指令碼(載入real rootfs
,並啟動了init
程序/sbin/init
)。
二、原始碼分析
核心有關根檔案系統掛載的呼叫鏈路如下:
start_kernel
vfs_caches_init()
mnt_init()
// 建立虛擬根檔案系統
init_rootfs()
register_filesystem(&rootfs_fs_type)
init_ramfs_fs
register_filesystem(&ramfs_fs_type)
// 註冊根檔案系統
init_mount_tree()
rest_init
kernel_init
kernel_init_freeable
if(!ramdisk_execute_command)
ramdisk_execute_command="/"
do_basic_setup
do_initcalls
populate_rootfs
unpack_to_rootfs
run_init_process(ramdisk_execute_command)
2.1 VFS
的註冊
首先不得不從linux
系統的函式start_kernel
說起。函式start_kernel
中會去呼叫vfs_caches_init
來初始化VFS
,函式位於fs/dcache.c
;
void __init vfs_caches_init(void)
{
names_cachep = kmem_cache_create_usercopy("names_cache", PATH_MAX, 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, 0, PATH_MAX, NULL);
dcache_init();
inode_init();
files_init();
files_maxfiles_init();
mnt_init();
bdev_cache_init();
chrdev_init();
}
函式mnt_init
會建立一個rootfs
,這是個虛擬的rootfs
,即記憶體檔案系統,後面還會指向真實的檔案系統。
2.1.1 mnt_init
mnt_init
函式位於fs/namespace.c
:
void __init mnt_init(void)
{
int err;
mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct mount),
0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
mount_hashtable = alloc_large_system_hash("Mount-cache",
sizeof(struct hlist_head),
mhash_entries, 19,
HASH_ZERO,
&m_hash_shift, &m_hash_mask, 0, 0);
mountpoint_hashtable = alloc_large_system_hash("Mountpoint-cache",
sizeof(struct hlist_head),
mphash_entries, 19,
HASH_ZERO,
&mp_hash_shift, &mp_hash_mask, 0, 0);
if (!mount_hashtable || !mountpoint_hashtable)
panic("Failed to allocate mount hash table\n");
kernfs_init();
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__);
// 建立虛擬根檔案系統
init_rootfs();
// 註冊根檔案系統
init_mount_tree();
}
2.1.2 init_rootfs
init_rootfs
定義在init/do_mounts.c
;
static struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.mount = rootfs_mount,
.kill_sb = kill_litter_super,
};
int __init init_rootfs(void)
{
int err = register_filesystem(&rootfs_fs_type);
if (err)
return err;
if (IS_ENABLED(CONFIG_TMPFS) && !saved_root_name[0] &&
(!root_fs_names || strstr(root_fs_names, "tmpfs"))) {
err = shmem_init();
is_tmpfs = true;
} else {
err = init_ramfs_fs();
}
if (err)
unregister_filesystem(&rootfs_fs_type);
return err;
}
2.1.3 init_mount_tree
init_mount_tree
函式位於fs/namespace.c
:
static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct mnt_namespace *ns;
struct path root;
struct file_system_type *type;
type = get_fs_type("rootfs");
if (!type)
panic("Can't find rootfs type");
// 建立虛擬檔案系統
mnt = vfs_kern_mount(type, 0, "rootfs", NULL);
put_filesystem(type);
if (IS_ERR(mnt))
panic("Can't create rootfs");
ns = create_mnt_ns(mnt);
if (IS_ERR(ns))
panic("Can't allocate initial namespace");
init_task.nsproxy->mnt_ns = ns;
get_mnt_ns(ns);
root.mnt = mnt;
root.dentry = mnt->mnt_root;
mnt->mnt_flags |= MNT_LOCKED;
set_fs_pwd(current->fs, &root);
// 將當前的檔案系統配置為根檔案系統
set_fs_root(current->fs, &root);
}
可能有人會問,為什麼不直接把真實的檔案系統配置為根檔案系統?
答案很簡單,核心中沒有根檔案系統的裝置驅動,如usb
、eMMC
等存放根檔案系統的裝置驅動,而且即便你將根檔案系統的裝置驅動編譯到核心中,此時它們還尚未載入,其實所有的驅動是由在後面的kernel_init
執行緒進行載入,所以需要initrd/initramfs
。
2.2 VFS
的掛載
參考文章
[1] linux
核心啟動initramfs
與initrd
及其掛載
[2] 嵌入式軟體開發之------淺析linux
根檔案系統掛載(九)
[3] 根檔案系統的含義和相關重要概念以及載入程式碼分析