Linux系统根文件系统准备和挂载完整流程
阶段一:设备环境准备 (prepare_namespace前半部分)
-
设备文件系统建立
cmount_devfs(); // 挂载临时devfs md_run_setup(); // 初始化软件RAID- 提供临时的设备节点访问能力
- 确保RAID设备在根文件系统挂载前就绪
-
根设备配置解析
cif (saved_root_name[0]) { root_device_name = saved_root_name; ROOT_DEV = name_to_dev_t(root_device_name); }- 解析内核命令行
root=参数 - 将设备名称转换为设备号
- 为后续挂载提供准确的设备标识
- 解析内核命令行
-
初始RAM磁盘处理
cif (initrd_load()) goto out;- 加载并处理
initrd镜像 - 如果
initrd加载成功,直接使用initrd作为临时根 - 为驱动加载和真正的根文件系统挂载提供桥梁
- 加载并处理
阶段二:根文件系统挂载 (mount_root及相关函数)
-
网络根文件系统支持
c#ifdef CONFIG_ROOT_NFS if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) { if (mount_nfs_root()) return; } #endif- 尝试挂载NFS网络根文件系统
- 支持无盘工作站的启动场景
-
传统设备根文件系统挂载
ccreate_dev("/dev/root", ROOT_DEV, root_device_name); mount_block_root("/dev/root", root_mountflags);- 创建统一的根设备文件
/dev/root - 启动文件系统探测和挂载流程
- 创建统一的根设备文件
-
多文件系统类型探测
cget_fs_names(fs_names); for (p = fs_names; *p; p += strlen(p)+1) { do_mount_root(name, p, flags, data); }- 获取内核支持的所有文件系统类型
- 逐个尝试直到找到匹配的文件系统
- 智能错误处理和重试机制
阶段三:命名空间最终切换 (prepare_namespace后半部分)
-
临时环境清理
cumount_devfs("/dev");- 卸载临时使用的设备文件系统
- 释放初始化阶段的临时资源
-
根文件系统提升
csys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot(".");- 将当前挂载的文件系统移动到根位置
- 执行chroot操作完成根目录切换
- 这是Unix系统根目录设置的最终步骤
-
安全和服务初始化
csecurity_sb_post_mountroot(); mount_devfs_fs();- 调用安全模块的根文件系统挂载后处理
- 在新的根环境下重新挂载设备文件系统
实际效果总结
最终达到的状态:
-
根文件系统就绪
/目录指向真正的根文件系统- 所有必要的设备文件可用
- 文件系统权限和标志正确设置
-
进程环境建立
- 当前工作目录设置为根目录
- 进程的根目录正确限制
- 安全策略初始化完成
完成Linux系统根文件系统的最终准备和切换prepare_namespace
c
void __init prepare_namespace(void)
{
int is_floppy;
mount_devfs();
md_run_setup();
if (saved_root_name[0]) {
root_device_name = saved_root_name;
ROOT_DEV = name_to_dev_t(root_device_name);
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
if (initrd_load())
goto out;
if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;
mount_root();
out:
umount_devfs("/dev");
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
security_sb_post_mountroot();
mount_devfs_fs ();
}
函数功能概述
prepare_namespace 函数负责在内核启动过程中准备根文件系统,包括处理initrd、挂载根文件系统、设置最终的系统根目录等关键操作
代码详细解析
函数声明和变量定义
c
void __init prepare_namespace(void)
{
int is_floppy;
函数声明:
void __init: 初始化函数,完成后内存可被释放prepare_namespace(void): 准备命名空间的主函数int is_floppy: 标志变量,表示根设备是否是软盘
挂载devfs文件系统
c
mount_devfs();
挂载设备文件系统:
mount_devfs(): 挂载devfs(设备文件系统)到/dev目录- 作用: 提供设备文件管理,允许动态创建设备节点
软件RAID设置
c
md_run_setup();
多设备驱动设置:
md_run_setup(): 初始化并运行软件RAID设置- md: Multiple Device,Linux的软件RAID系统
- 作用: 处理内核命令行中的md=参数,配置RAID设备
- 重要性: 确保RAID设备在根文件系统挂载前就绪
根设备名称处理
c
if (saved_root_name[0]) {
root_device_name = saved_root_name;
ROOT_DEV = name_to_dev_t(root_device_name);
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
根设备配置:
if (saved_root_name[0]): 检查是否有保存的根设备名称saved_root_name: 从内核命令行root=参数保存的设备名
设备名称处理:
root_device_name = saved_root_name: 设置根设备名称ROOT_DEV = name_to_dev_t(root_device_name): 将设备名转换为设备号name_to_dev_t(): 解析设备名(如"/dev/sda1")为设备号
if (strncmp(root_device_name, "/dev/", 5) == 0): 如果设备名以"/dev/"开头root_device_name += 5: 跳过"/dev/"前缀,只保留设备名部分
软盘设备检查
c
is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
检测根设备类型:
MAJOR(ROOT_DEV): 提取根设备的主设备号FLOPPY_MAJOR: 软盘设备的主设备号常量is_floppy = ...: 判断根设备是否是软盘驱动器- 作用: 软盘设备需要特殊处理,因为访问速度较慢
initrd加载
c
if (initrd_load())
goto out;
初始RAM磁盘加载:
initrd_load(): 加载并处理initrd(初始RAM磁盘)- 返回值: 成功加载返回1,否则返回0
goto out: 如果initrd加载成功,跳转到out标签initrd作用: 提供临时的根文件系统,用于加载必要的驱动后再挂载真正的根文件系统
软盘根设备处理
c
if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;
软盘根设备特殊情况:
is_floppy: 根设备是软盘rd_doload: 应该从软盘加载RAM磁盘的标志rd_load_disk(0): 从软盘加载RAM磁盘到内存ROOT_DEV = Root_RAM0: 如果加载成功,将根设备设置为RAM磁盘
挂载根文件系统
c
mount_root();
挂载根文件系统:
mount_root(): 核心函数,尝试挂载真正的根文件系统- 功能 :
- 尝试不同的文件系统类型
- 在指定设备上查找可挂载的文件系统
- 执行根文件系统的实际挂载操作
out标签:命名空间最终设置
c
out:
umount_devfs("/dev");
卸载临时devfs:
umount_devfs("/dev"): 卸载之前挂载的临时devfs
移动挂载点
c
sys_mount(".", "/", NULL, MS_MOVE, NULL);
移动当前挂载点到根:
sys_mount(".", "/", NULL, MS_MOVE, NULL): 将当前目录移动到根目录- 参数 :
".": 源路径(当前挂载点)"/": 目标路径(新的根目录)MS_MOVE: 移动挂载点标志
- 作用: 将当前挂载的文件系统提升为系统的根文件系统
改变根目录
c
sys_chroot(".");
改变根目录:
sys_chroot("."): 将当前目录设置为进程的根目录- 作用: 完成chroot操作,限制进程的文件系统视图
- 重要性: 这是Unix系统根目录设置的最终步骤
安全子系统后处理
c
security_sb_post_mountroot();
安全模块回调:
security_sb_post_mountroot(): 调用安全子系统挂载根后的处理函数- 作用 : 允许
SELinux等安全模块在根文件系统挂载后执行必要的初始化
重新挂载devfs
c
mount_devfs_fs ();
}
重新挂载devfs:
mount_devfs_fs(): 在最终的根文件系统上重新挂载devfs- 作用: 在新的根文件系统环境下提供设备文件管理
- 函数结束
关键函数功能
initrd_load():
- 加载
initrd到内存 - 解压并处理
initrd内容 - 返回1表示成功并使用
initrd作为根
mount_root():
- 尝试在根设备上挂载文件系统
- 支持多种文件系统类型探测
- 处理挂载失败的各种情况
sys_mount() with MS_MOVE:
- 原子性地移动挂载点
- 保持挂载选项和标志
- 用于提升文件系统为根
函数功能总结
主要功能:完成Linux系统根文件系统的最终准备和切换
核心处理阶段:
-
设备准备阶段
- 提供临时的设备文件访问
- 配置软件RAID设备
- 解析根设备参数
-
根文件系统加载阶段
- 尝试加载
initrd作为临时根 - 处理软盘等特殊设备的RAM磁盘加载
- 挂载真正的根文件系统
- 尝试加载
-
命名空间切换阶段
- 清理临时文件系统
- 移动挂载点到根位置
- 执行chroot完成根目录切换
- 重新建立设备文件系统
根文件系统挂载mount_root
c
void __init mount_root(void)
{
#ifdef CONFIG_ROOT_NFS
if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {
if (mount_nfs_root())
return;
printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.\n");
ROOT_DEV = Root_FD0;
}
#endif
#ifdef CONFIG_BLK_DEV_FD
if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {
/* rd_doload is 2 for a dual initrd/ramload setup */
if (rd_doload==2) {
if (rd_load_disk(1)) {
ROOT_DEV = Root_RAM1;
root_device_name = NULL;
}
} else
change_floppy("root floppy");
}
#endif
create_dev("/dev/root", ROOT_DEV, root_device_name);
mount_block_root("/dev/root", root_mountflags);
}
void __init mount_block_root(char *name, int flags)
{
char *fs_names = __getname();
char *p;
char b[BDEVNAME_SIZE];
get_fs_names(fs_names);
retry:
for (p = fs_names; *p; p += strlen(p)+1) {
int err = do_mount_root(name, p, flags, root_mount_data);
switch (err) {
case 0:
goto out;
case -EACCES:
flags |= MS_RDONLY;
goto retry;
case -EINVAL:
continue;
}
/*
* Allow the user to distinguish between failed sys_open
* and bad superblock on root device.
*/
__bdevname(ROOT_DEV, b);
printk("VFS: Cannot open root device \"%s\" or %s\n",
root_device_name, b);
printk("Please append a correct \"root=\" boot option\n");
panic("VFS: Unable to mount root fs on %s", b);
}
panic("VFS: Unable to mount root fs on %s", __bdevname(ROOT_DEV, b));
out:
putname(fs_names);
}
static int __init do_mount_root(char *name, char *fs, int flags, void *data)
{
int err = sys_mount(name, "/root", fs, flags, data);
if (err)
return err;
sys_chdir("/root");
ROOT_DEV = current->fs->pwdmnt->mnt_sb->s_dev;
printk("VFS: Mounted root (%s filesystem)%s.\n",
current->fs->pwdmnt->mnt_sb->s_type->name,
current->fs->pwdmnt->mnt_sb->s_flags & MS_RDONLY ?
" readonly" : "");
return 0;
}
mount_root 函数
函数声明
c
void __init mount_root(void)
__init:这个函数只在系统初始化时使用,初始化完成后内存会被释放void:没有返回值- 这是系统启动时挂载根文件系统的主函数
NFS根文件系统支持
c
#ifdef CONFIG_ROOT_NFS
if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {
if (mount_nfs_root())
return;
printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.\n");
ROOT_DEV = Root_FD0;
}
#endif
CONFIG_ROOT_NFS:NFS根文件系统配置选项MAJOR(ROOT_DEV) == UNNAMED_MAJOR:检查根设备是否为未命名主设备号(通常用于网络文件系统)mount_nfs_root():尝试挂载NFS根文件系统- 如果成功,直接返回
- 如果失败,打印错误信息
软盘根设备支持
c
#ifdef CONFIG_BLK_DEV_FD
if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {
/* rd_doload is 2 for a dual initrd/ramload setup */
if (rd_doload==2) {
if (rd_load_disk(1)) {
ROOT_DEV = Root_RAM1;
root_device_name = NULL;
}
} else
change_floppy("root floppy");
}
#endif
CONFIG_BLK_DEV_FD:软盘设备驱动配置选项MAJOR(ROOT_DEV) == FLOPPY_MAJOR:检查根设备是否为软盘rd_doload==2:检查是否为双initrd/ramdisk设置rd_load_disk(1):从软盘加载ramdisk- 如果加载成功,切换到RAM disk作为根设备
- 否则,调用
change_floppy提示用户插入根文件系统软盘
创建设备和挂载根文件系统
c
create_dev("/dev/root", ROOT_DEV, root_device_name);
mount_block_root("/dev/root", root_mountflags);
}
create_dev("/dev/root", ROOT_DEV, root_device_name):创建设备文件/dev/rootmount_block_root("/dev/root", root_mountflags):挂载块设备根文件系统
mount_block_root 函数
函数声明和变量定义
c
void __init mount_block_root(char *name, int flags)
{
char *fs_names = __getname();
char *p;
char b[BDEVNAME_SIZE];
name:根设备名称(如/dev/root)flags:挂载标志fs_names = __getname():分配内核空间获取文件系统名称列表p:文件系统名称遍历指针b[BDEVNAME_SIZE]:设备名缓冲区
获取文件系统名称列表
c
get_fs_names(fs_names);
get_fs_names(fs_names):获取内核支持的文件系统类型名称列表
文件系统遍历和重试循环
c
retry:
for (p = fs_names; *p; p += strlen(p)+1) {
int err = do_mount_root(name, p, flags, root_mount_data);
retry::重试标签,用于只读重试for (p = fs_names; *p; p += strlen(p)+1):遍历所有文件系统类型p += strlen(p)+1:移动到下一个文件系统名称(跳过\0)do_mount_root(name, p, flags, root_mount_data):尝试用当前文件系统类型挂载
错误处理开关
c
switch (err) {
case 0:
goto out;
case -EACCES:
flags |= MS_RDONLY;
goto retry;
case -EINVAL:
continue;
}
case 0:挂载成功,跳转到清理代码case -EACCES:权限错误,添加只读标志并重试所有文件系统case -EINVAL:无效参数,继续尝试下一个文件系统
挂载失败处理
c
/*
* Allow the user to distinguish between failed sys_open
* and bad superblock on root device.
*/
__bdevname(ROOT_DEV, b);
printk("VFS: Cannot open root device \"%s\" or %s\n",
root_device_name, b);
printk("Please append a correct \"root=\" boot option\n");
panic("VFS: Unable to mount root fs on %s", b);
}
- 允许用户区分打开设备失败和超级块错误
__bdevname(ROOT_DEV, b):将设备号转换为设备名称- 打印详细的错误信息,指导用户修正启动参数
- 触发内核panic,系统无法继续启动
所有文件系统尝试失败
c
panic("VFS: Unable to mount root fs on %s", __bdevname(ROOT_DEV, b));
out:
putname(fs_names);
}
- 如果所有文件系统类型都尝试失败,触发panic
out::成功挂载的退出点putname(fs_names):释放文件系统名称列表内存
do_mount_root 函数
函数声明
c
static int __init do_mount_root(char *name, char *fs, int flags, void *data)
name:设备名称(如/dev/root)fs:文件系统类型(如 "ext4")flags:挂载标志data:文件系统特定的挂载数据
执行挂载操作
c
int err = sys_mount(name, "/root", fs, flags, data);
if (err)
return err;
sys_mount(name, "/root", fs, flags, data):执行实际的挂载系统调用- 参数:源设备、挂载点、文件系统类型、标志、数据
- 如果挂载失败,返回错误码
切换到根目录和设置
c
sys_chdir("/root");
ROOT_DEV = current->fs->pwdmnt->mnt_sb->s_dev;
sys_chdir("/root"):改变当前工作目录到新挂载的根文件系统ROOT_DEV = current->fs->pwdmnt->mnt_sb->s_dev:更新全局根设备号为实际挂载的设备
打印挂载信息
c
printk("VFS: Mounted root (%s filesystem)%s.\n",
current->fs->pwdmnt->mnt_sb->s_type->name,
current->fs->pwdmnt->mnt_sb->s_flags & MS_RDONLY ?
" readonly" : "");
return 0;
}
- 打印成功挂载的信息,包括文件系统类型和是否只读
- 返回0表示成功
函数功能详解
mount_root 功能
主要功能:系统启动时挂载根文件系统的总控函数
详细执行流程:
-
网络根文件系统尝试
- 检查是否配置了NFS根文件系统支持
- 如果根设备是网络设备,尝试挂载NFS
- 失败时回退到本地设备
-
软盘根设备处理
- 检查软盘驱动支持
- 支持从软盘加载
ramdisk的双重启动设置 - 提供用户交互提示
-
设备文件准备
- 创建统一的根设备文件
/dev/root - 为后续挂载提供标准接口
- 创建统一的根设备文件
mount_block_root 功能
主要功能:尝试多种文件系统类型挂载根文件系统
详细执行流程:
-
文件系统发现
- 获取内核编译时支持的所有文件系统类型
- 准备遍历尝试
-
智能重试机制
- 遍历所有文件系统类型
- 针对权限错误自动切换到只读模式重试
- 跳过不兼容的文件系统类型
-
详细错误报告
- 区分设备打开失败和超级块错误
- 提供清晰的用户指导信息
- 确保故障原因可诊断
do_mount_root 功能
主要功能:执行实际的根文件系统挂载操作
详细执行流程:
-
挂载执行
- 调用系统挂载调用
- 处理文件系统特定的挂载数据
-
环境切换
- 改变当前工作目录到新根文件系统
- 更新全局根设备信息
-
状态报告
- 打印成功的挂载信息
- 记录文件系统类型和挂载模式
控制initrd的加载和执行流程initrd_load
c
int __init initrd_load(void)
{
if (mount_initrd) {
create_dev("/dev/ram", Root_RAM0, NULL);
/*
* Load the initrd data into /dev/ram0. Execute it as initrd
* unless /dev/ram0 is supposed to be our actual root device,
* in that case the ram disk is just set up here, and gets
* mounted in the normal path.
*/
if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {
sys_unlink("/initrd.image");
handle_initrd();
return 1;
}
}
sys_unlink("/initrd.image");
return 0;
}
int __init rd_load_image(char *from)
{
int res = 0;
int in_fd, out_fd;
unsigned long rd_blocks, devblocks;
int nblocks, i, disk;
char *buf = NULL;
unsigned short rotate = 0;
#if !defined(CONFIG_ARCH_S390) && !defined(CONFIG_PPC_ISERIES)
char rotator[4] = { '|' , '/' , '-' , '\\' };
#endif
out_fd = sys_open("/dev/ram", O_RDWR, 0);
if (out_fd < 0)
goto out;
in_fd = sys_open(from, O_RDONLY, 0);
if (in_fd < 0)
goto noclose_input;
nblocks = identify_ramdisk_image(in_fd, rd_image_start);
if (nblocks < 0)
goto done;
if (nblocks == 0) {
#ifdef BUILD_CRAMDISK
if (crd_load(in_fd, out_fd) == 0)
goto successful_load;
#else
printk(KERN_NOTICE
"RAMDISK: Kernel does not support compressed "
"RAM disk images\n");
#endif
goto done;
}
/*
* NOTE NOTE: nblocks is not actually blocks but
* the number of kibibytes of data to load into a ramdisk.
* So any ramdisk block size that is a multiple of 1KiB should
* work when the appropriate ramdisk_blocksize is specified
* on the command line.
*
* The default ramdisk_blocksize is 1KiB and it is generally
* silly to use anything else, so make sure to use 1KiB
* blocksize while generating ext2fs ramdisk-images.
*/
if (sys_ioctl(out_fd, BLKGETSIZE, (unsigned long)&rd_blocks) < 0)
rd_blocks = 0;
else
rd_blocks >>= 1;
if (nblocks > rd_blocks) {
printk("RAMDISK: image too big! (%dKiB/%ldKiB)\n",
nblocks, rd_blocks);
goto done;
}
/*
* OK, time to copy in the data
*/
if (sys_ioctl(in_fd, BLKGETSIZE, (unsigned long)&devblocks) < 0)
devblocks = 0;
else
devblocks >>= 1;
if (strcmp(from, "/initrd.image") == 0)
devblocks = nblocks;
if (devblocks == 0) {
printk(KERN_ERR "RAMDISK: could not determine device size\n");
goto done;
}
buf = kmalloc(BLOCK_SIZE, GFP_KERNEL);
if (buf == 0) {
printk(KERN_ERR "RAMDISK: could not allocate buffer\n");
goto done;
}
printk(KERN_NOTICE "RAMDISK: Loading %dKiB [%ld disk%s] into ram disk... ",
nblocks, ((nblocks-1)/devblocks)+1, nblocks>devblocks ? "s" : "");
for (i = 0, disk = 1; i < nblocks; i++) {
if (i && (i % devblocks == 0)) {
printk("done disk #%d.\n", disk++);
rotate = 0;
if (sys_close(in_fd)) {
printk("Error closing the disk.\n");
goto noclose_input;
}
change_floppy("disk #%d", disk);
in_fd = sys_open(from, O_RDONLY, 0);
if (in_fd < 0) {
printk("Error opening disk.\n");
goto noclose_input;
}
printk("Loading disk #%d... ", disk);
}
sys_read(in_fd, buf, BLOCK_SIZE);
sys_write(out_fd, buf, BLOCK_SIZE);
#if !defined(CONFIG_ARCH_S390) && !defined(CONFIG_PPC_ISERIES)
if (!(i % 16)) {
printk("%c\b", rotator[rotate & 0x3]);
rotate++;
}
#endif
}
printk("done.\n");
successful_load:
res = 1;
done:
sys_close(in_fd);
noclose_input:
sys_close(out_fd);
out:
kfree(buf);
sys_unlink("/dev/ram");
return res;
}
static void __init handle_initrd(void)
{
int error;
int i, pid;
real_root_dev = new_encode_dev(ROOT_DEV);
create_dev("/dev/root.old", Root_RAM0, NULL);
/* mount initrd on rootfs' /root */
mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
sys_mkdir("/old", 0700);
root_fd = sys_open("/", 0, 0);
old_fd = sys_open("/old", 0, 0);
/* move initrd over / and chdir/chroot in initrd root */
sys_chdir("/root");
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
mount_devfs_fs ();
pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);
if (pid > 0) {
while (pid != sys_wait4(-1, &i, 0, NULL))
yield();
}
/* move initrd to rootfs' /old */
sys_fchdir(old_fd);
sys_mount("/", ".", NULL, MS_MOVE, NULL);
/* switch root and cwd back to / of rootfs */
sys_fchdir(root_fd);
sys_chroot(".");
sys_close(old_fd);
sys_close(root_fd);
umount_devfs("/old/dev");
if (new_decode_dev(real_root_dev) == Root_RAM0) {
sys_chdir("/old");
return;
}
ROOT_DEV = new_decode_dev(real_root_dev);
mount_root();
printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);
if (!error)
printk("okay\n");
else {
int fd = sys_open("/dev/root.old", O_RDWR, 0);
printk("failed\n");
printk(KERN_NOTICE "Unmounting old root\n");
sys_umount("/old", MNT_DETACH);
printk(KERN_NOTICE "Trying to free ramdisk memory ... ");
if (fd < 0) {
error = fd;
} else {
error = sys_ioctl(fd, BLKFLSBUF, 0);
sys_close(fd);
}
printk(!error ? "okay\n" : "failed\n");
}
}
initrd_load 函数
函数声明和初始检查
c
int __init initrd_load(void)
{
if (mount_initrd) {
create_dev("/dev/ram", Root_RAM0, NULL);
__init:初始化函数,完成后内存可释放mount_initrd:内核配置选项,控制是否启用initrdcreate_dev("/dev/ram", Root_RAM0, NULL):创建设备文件/dev/ram
initrd 镜像加载和处理
c
/*
* Load the initrd data into /dev/ram0. Execute it as initrd
* unless /dev/ram0 is supposed to be our actual root device,
* in that case the ram disk is just set up here, and gets
* mounted in the normal path.
*/
if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {
sys_unlink("/initrd.image");
handle_initrd();
return 1;
}
}
- 将
initrd数据加载到/dev/ram0,除非/dev/ram0就是实际的根设备 rd_load_image("/initrd.image"):加载initrd镜像文件ROOT_DEV != Root_RAM0:检查RAM disk不是最终的根设备- 如果加载成功且不是最终根设备,调用
handle_initrd()处理initrd - 返回1表示使用了initrd
清理和返回
c
sys_unlink("/initrd.image");
return 0;
}
- 删除
initrd镜像文件 - 返回0表示没有使用
initrd
rd_load_image 函数
函数声明和变量定义
c
int __init rd_load_image(char *from)
{
int res = 0;
int in_fd, out_fd;
unsigned long rd_blocks, devblocks;
int nblocks, i, disk;
char *buf = NULL;
unsigned short rotate = 0;
#if !defined(CONFIG_ARCH_S390) && !defined(CONFIG_PPC_ISERIES)
char rotator[4] = { '|' , '/' , '-' , '\\' };
#endif
from:initrd镜像文件路径res:结果标志in_fd, out_fd:输入输出文件描述符rd_blocks, devblocks:块数量nblocks, i, disk:循环计数器和磁盘编号buf:数据缓冲区rotate, rotator:进度指示器旋转动画
打开输出设备(RAM disk)
c
out_fd = sys_open("/dev/ram", O_RDWR, 0);
if (out_fd < 0)
goto out;
- 以读写方式打开RAM disk设备
- 如果打开失败,跳转到清理代码
打开输入文件(initrd镜像)
c
in_fd = sys_open(from, O_RDONLY, 0);
if (in_fd < 0)
goto noclose_input;
- 以只读方式打开
initrd镜像文件 - 如果打开失败,跳转到不关闭输入的清理代码
识别RAM disk镜像格式
c
nblocks = identify_ramdisk_image(in_fd, rd_image_start);
if (nblocks < 0)
goto done;
identify_ramdisk_image():识别镜像格式并返回块数- 如果识别失败,跳转到完成代码
压缩镜像处理
c
if (nblocks == 0) {
#ifdef BUILD_CRAMDISK
if (crd_load(in_fd, out_fd) == 0)
goto successful_load;
#else
printk(KERN_NOTICE
"RAMDISK: Kernel does not support compressed "
"RAM disk images\n");
#endif
goto done;
}
nblocks == 0表示压缩的镜像- 如果支持压缩RAM disk,调用
crd_load加载 - 否则打印不支持压缩镜像的警告
容量检查
c
/*
* NOTE NOTE: nblocks is not actually blocks but
* the number of kibibytes of data to load into a ramdisk.
*/
if (sys_ioctl(out_fd, BLKGETSIZE, (unsigned long)&rd_blocks) < 0)
rd_blocks = 0;
else
rd_blocks >>= 1;
if (nblocks > rd_blocks) {
printk("RAMDISK: image too big! (%dKiB/%ldKiB)\n",
nblocks, rd_blocks);
goto done;
}
nblocks实际上是KiB数而不是块数BLKGETSIZE获取设备大小(单位是512字节扇区)>> 1转换为KiB单位- 检查镜像是否超过RAM disk容量
确定设备大小
c
if (sys_ioctl(in_fd, BLKGETSIZE, (unsigned long)&devblocks) < 0)
devblocks = 0;
else
devblocks >>= 1;
if (strcmp(from, "/initrd.image") == 0)
devblocks = nblocks;
if (devblocks == 0) {
printk(KERN_ERR "RAMDISK: could not determine device size\n");
goto done;
}
- 获取输入设备的大小
- 如果是
initrd镜像文件,直接使用nblocks作为设备大小 - 检查设备大小是否有效
分配缓冲区
c
buf = kmalloc(BLOCK_SIZE, GFP_KERNEL);
if (buf == 0) {
printk(KERN_ERR "RAMDISK: could not allocate buffer\n");
goto done;
}
- 分配块大小的缓冲区
- 如果分配失败,打印错误并跳转
数据拷贝循环
c
printk(KERN_NOTICE "RAMDISK: Loading %dKiB [%ld disk%s] into ram disk... ",
nblocks, ((nblocks-1)/devblocks)+1, nblocks>devblocks ? "s" : "");
for (i = 0, disk = 1; i < nblocks; i++) {
- 打印加载信息,包括总大小和磁盘数量
- 开始数据拷贝循环
多磁盘处理
c
if (i && (i % devblocks == 0)) {
printk("done disk #%d.\n", disk++);
rotate = 0;
if (sys_close(in_fd)) {
printk("Error closing the disk.\n");
goto noclose_input;
}
change_floppy("disk #%d", disk);
in_fd = sys_open(from, O_RDONLY, 0);
if (in_fd < 0) {
printk("Error opening disk.\n");
goto noclose_input;
}
printk("Loading disk #%d... ", disk);
}
- 处理多磁盘情况(当数据跨越多张软盘时)
- 关闭当前磁盘,提示用户更换磁盘,打开新磁盘
数据读写和进度显示
c
sys_read(in_fd, buf, BLOCK_SIZE);
sys_write(out_fd, buf, BLOCK_SIZE);
#if !defined(CONFIG_ARCH_S390) && !defined(CONFIG_PPC_ISERIES)
if (!(i % 16)) {
printk("%c\b", rotator[rotate & 0x3]);
rotate++;
}
#endif
}
printk("done.\n");
- 读取一个块的数据并写入RAM disk
- 每16个块显示一次旋转进度指示器
- 循环结束后打印完成信息
清理和返回
c
successful_load:
res = 1;
done:
sys_close(in_fd);
noclose_input:
sys_close(out_fd);
out:
kfree(buf);
sys_unlink("/dev/ram");
return res;
}
- 设置成功标志
- 关闭文件描述符
- 释放缓冲区
- 删除临时设备文件
- 返回结果
handle_initrd 函数
函数声明和初始设置
c
static void __init handle_initrd(void)
{
int error;
int i, pid;
real_root_dev = new_encode_dev(ROOT_DEV);
create_dev("/dev/root.old", Root_RAM0, NULL);
- 保存真实的根设备号
- 创建设备文件
/dev/root.old指向RAM disk
挂载initrd
c
/* mount initrd on rootfs' /root */
mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
sys_mkdir("/old", 0700);
root_fd = sys_open("/", 0, 0);
old_fd = sys_open("/old", 0, 0);
- 以读写方式挂载
initrd到/root - 创建
/old目录用于后续操作 - 保存根目录和old目录的文件描述符
切换到initrd环境
c
/* move initrd over / and chdir/chroot in initrd root */
sys_chdir("/root");
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
mount_devfs_fs ();
- 切换到
initrd的挂载点 - 将
initrd移动到根位置 - 执行chroot切换到
initrd环境 - 挂载设备文件系统
执行initrd中的linuxrc
c
pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);
if (pid > 0) {
while (pid != sys_wait4(-1, &i, 0, NULL))
yield();
}
- 创建内核线程执行
/linuxrc脚本 - 等待
linuxrc执行完成
恢复原始根环境
c
/* move initrd to rootfs' /old */
sys_fchdir(old_fd);
sys_mount("/", ".", NULL, MS_MOVE, NULL);
/* switch root and cwd back to / of rootfs */
sys_fchdir(root_fd);
sys_chroot(".");
sys_close(old_fd);
sys_close(root_fd);
umount_devfs("/old/dev");
- 切换回原始根文件系统
- 将
initrd移动到/old - 恢复原始根目录
- 关闭文件描述符
- 卸载设备文件系统
处理RAM disk作为根设备的情况
c
if (new_decode_dev(real_root_dev) == Root_RAM0) {
sys_chdir("/old");
return;
}
- 如果真实的根设备就是RAM disk,直接使用
initrd - 切换到
/old目录并返回
挂载真正的根文件系统
c
ROOT_DEV = new_decode_dev(real_root_dev);
mount_root();
- 恢复真实的根设备号
- 挂载真正的根文件系统
清理initrd
c
printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);
if (!error)
printk("okay\n");
else {
int fd = sys_open("/dev/root.old", O_RDWR, 0);
printk("failed\n");
printk(KERN_NOTICE "Unmounting old root\n");
sys_umount("/old", MNT_DETACH);
printk(KERN_NOTICE "Trying to free ramdisk memory ... ");
if (fd < 0) {
error = fd;
} else {
error = sys_ioctl(fd, BLKFLSBUF, 0);
sys_close(fd);
}
printk(!error ? "okay\n" : "failed\n");
}
}
- 尝试将旧的
initrd移动到/initrd - 如果移动失败,卸载并释放RAM disk内存
函数功能详解
initrd_load 功能
主要功能 :控制initrd的加载和执行流程
详细执行流程:
-
配置检查
- 检查是否启用了
initrd支持 - 创建设备文件为加载做准备
- 检查是否启用了
-
镜像加载决策
- 加载
initrd镜像到RAM disk - 根据根设备配置决定是否执行
initrd - 只在RAM disk不是最终根设备时执行
initrd
- 加载
-
资源清理
- 删除临时文件
- 返回执行状态
rd_load_image 功能
主要功能 :将initrd镜像加载到RAM disk中
详细执行流程:
-
设备准备
- 打开RAM disk设备作为输出
- 打开
initrd镜像文件作为输入
-
镜像识别
- 识别镜像格式(压缩或未压缩)
- 处理压缩镜像的特殊加载
-
容量验证
- 检查RAM disk容量是否足够
- 验证设备参数
-
数据传输
- 分块读取和写入数据
- 支持多磁盘切换
- 提供进度显示
-
资源管理
- 正确关闭文件和释放内存
- 清理临时资源
handle_initrd 功能
主要功能 :执行initrd中的初始化脚本并切换到真正的根文件系统
详细执行流程:
-
环境准备
- 保存真实根设备信息
- 挂载
initrd为临时根文件系统
-
脚本执行
- 切换到
initrd环境 - 执行
/linuxrc初始化脚本 - 等待脚本完成
- 切换到
-
环境恢复
- 切换回原始根文件系统
- 挂载真正的根文件系统
-
资源清理
- 移动或清理
initrd - 释放RAM disk内存
- 移动或清理
系统启动中的关键作用
initrd机制的核心价值:
- 模块化启动:在挂载真正根文件系统前加载必要驱动
- 硬件检测:执行硬件特定的初始化脚本