Linux中完成根文件系统的最终准备和切换prepare_namespace函数的实现
Linux系统根文件系统准备和挂载完整流程
阶段一:设备环境准备 (prepare_namespace前半部分)
-
设备文件系统建立
mount_devfs(); // 挂载临时devfs md_run_setup(); // 初始化软件RAID- 提供临时的设备节点访问能力
- 确保RAID设备在根文件系统挂载前就绪
-
根设备配置解析
if (saved_root_name[0]) {root_device_name = saved_root_name;ROOT_DEV = name_to_dev_t(root_device_name); }- 解析内核命令行
root=参数 - 将设备名称转换为设备号
- 为后续挂载提供准确的设备标识
- 解析内核命令行
-
初始RAM磁盘处理
if (initrd_load()) goto out;- 加载并处理
initrd镜像 - 如果
initrd加载成功,直接使用initrd作为临时根 - 为驱动加载和真正的根文件系统挂载提供桥梁
- 加载并处理
阶段二:根文件系统挂载 (mount_root及相关函数)
-
网络根文件系统支持
#ifdef CONFIG_ROOT_NFS if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {if (mount_nfs_root()) return; } #endif- 尝试挂载NFS网络根文件系统
- 支持无盘工作站的启动场景
-
传统设备根文件系统挂载
create_dev("/dev/root", ROOT_DEV, root_device_name); mount_block_root("/dev/root", root_mountflags);- 创建统一的根设备文件
/dev/root - 启动文件系统探测和挂载流程
- 创建统一的根设备文件
-
多文件系统类型探测
get_fs_names(fs_names); for (p = fs_names; *p; p += strlen(p)+1) {do_mount_root(name, p, flags, data); }- 获取内核支持的所有文件系统类型
- 逐个尝试直到找到匹配的文件系统
- 智能错误处理和重试机制
阶段三:命名空间最终切换 (prepare_namespace后半部分)
-
临时环境清理
umount_devfs("/dev");- 卸载临时使用的设备文件系统
- 释放初始化阶段的临时资源
-
根文件系统提升
sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot(".");- 将当前挂载的文件系统移动到根位置
- 执行chroot操作完成根目录切换
- 这是Unix系统根目录设置的最终步骤
-
安全和服务初始化
security_sb_post_mountroot(); mount_devfs_fs();- 调用安全模块的根文件系统挂载后处理
- 在新的根环境下重新挂载设备文件系统
实际效果总结
最终达到的状态:
-
根文件系统就绪
/目录指向真正的根文件系统- 所有必要的设备文件可用
- 文件系统权限和标志正确设置
-
进程环境建立
- 当前工作目录设置为根目录
- 进程的根目录正确限制
- 安全策略初始化完成
完成Linux系统根文件系统的最终准备和切换prepare_namespace
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、挂载根文件系统、设置最终的系统根目录等关键操作
代码详细解析
函数声明和变量定义
void __init prepare_namespace(void)
{int is_floppy;
函数声明:
void __init: 初始化函数,完成后内存可被释放prepare_namespace(void): 准备命名空间的主函数int is_floppy: 标志变量,表示根设备是否是软盘
挂载devfs文件系统
mount_devfs();
挂载设备文件系统:
mount_devfs(): 挂载devfs(设备文件系统)到/dev目录- 作用: 提供设备文件管理,允许动态创建设备节点
软件RAID设置
md_run_setup();
多设备驱动设置:
md_run_setup(): 初始化并运行软件RAID设置- md: Multiple Device,Linux的软件RAID系统
- 作用: 处理内核命令行中的md=参数,配置RAID设备
- 重要性: 确保RAID设备在根文件系统挂载前就绪
根设备名称处理
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/"前缀,只保留设备名部分
软盘设备检查
is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
检测根设备类型:
MAJOR(ROOT_DEV): 提取根设备的主设备号FLOPPY_MAJOR: 软盘设备的主设备号常量is_floppy = ...: 判断根设备是否是软盘驱动器- 作用: 软盘设备需要特殊处理,因为访问速度较慢
initrd加载
if (initrd_load())goto out;
初始RAM磁盘加载:
initrd_load(): 加载并处理initrd(初始RAM磁盘)- 返回值: 成功加载返回1,否则返回0
goto out: 如果initrd加载成功,跳转到out标签initrd作用: 提供临时的根文件系统,用于加载必要的驱动后再挂载真正的根文件系统
软盘根设备处理
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磁盘
挂载根文件系统
mount_root();
挂载根文件系统:
mount_root(): 核心函数,尝试挂载真正的根文件系统- 功能:
- 尝试不同的文件系统类型
- 在指定设备上查找可挂载的文件系统
- 执行根文件系统的实际挂载操作
out标签:命名空间最终设置
out:umount_devfs("/dev");
卸载临时devfs:
umount_devfs("/dev"): 卸载之前挂载的临时devfs
移动挂载点
sys_mount(".", "/", NULL, MS_MOVE, NULL);
移动当前挂载点到根:
sys_mount(".", "/", NULL, MS_MOVE, NULL): 将当前目录移动到根目录- 参数:
".": 源路径(当前挂载点)"/": 目标路径(新的根目录)MS_MOVE: 移动挂载点标志
- 作用: 将当前挂载的文件系统提升为系统的根文件系统
改变根目录
sys_chroot(".");
改变根目录:
sys_chroot("."): 将当前目录设置为进程的根目录- 作用: 完成chroot操作,限制进程的文件系统视图
- 重要性: 这是Unix系统根目录设置的最终步骤
安全子系统后处理
security_sb_post_mountroot();
安全模块回调:
security_sb_post_mountroot(): 调用安全子系统挂载根后的处理函数- 作用: 允许
SELinux等安全模块在根文件系统挂载后执行必要的初始化
重新挂载devfs
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
void __init mount_root(void)
{
#ifdef CONFIG_ROOT_NFSif (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_FDif (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;}} elsechange_floppy("root floppy");}
#endifcreate_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 函数
函数声明
void __init mount_root(void)
__init:这个函数只在系统初始化时使用,初始化完成后内存会被释放void:没有返回值- 这是系统启动时挂载根文件系统的主函数
NFS根文件系统支持
#ifdef CONFIG_ROOT_NFSif (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根文件系统- 如果成功,直接返回
- 如果失败,打印错误信息
软盘根设备支持
#ifdef CONFIG_BLK_DEV_FDif (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;}} elsechange_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提示用户插入根文件系统软盘
创建设备和挂载根文件系统
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 函数
函数声明和变量定义
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]:设备名缓冲区
获取文件系统名称列表
get_fs_names(fs_names);
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);
retry::重试标签,用于只读重试for (p = fs_names; *p; p += strlen(p)+1):遍历所有文件系统类型p += strlen(p)+1:移动到下一个文件系统名称(跳过\0)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;}
case 0:挂载成功,跳转到清理代码case -EACCES:权限错误,添加只读标志并重试所有文件系统case -EINVAL:无效参数,继续尝试下一个文件系统
挂载失败处理
/** 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,系统无法继续启动
所有文件系统尝试失败
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 函数
函数声明
static int __init do_mount_root(char *name, char *fs, int flags, void *data)
name:设备名称(如/dev/root)fs:文件系统类型(如 “ext4”)flags:挂载标志data:文件系统特定的挂载数据
执行挂载操作
int err = sys_mount(name, "/root", fs, flags, data);if (err)return err;
sys_mount(name, "/root", fs, flags, data):执行实际的挂载系统调用- 参数:源设备、挂载点、文件系统类型、标志、数据
- 如果挂载失败,返回错误码
切换到根目录和设置
sys_chdir("/root");ROOT_DEV = current->fs->pwdmnt->mnt_sb->s_dev;
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;
}
- 打印成功挂载的信息,包括文件系统类型和是否只读
- 返回0表示成功
函数功能详解
mount_root 功能
主要功能:系统启动时挂载根文件系统的总控函数
详细执行流程:
-
网络根文件系统尝试
- 检查是否配置了NFS根文件系统支持
- 如果根设备是网络设备,尝试挂载NFS
- 失败时回退到本地设备
-
软盘根设备处理
- 检查软盘驱动支持
- 支持从软盘加载
ramdisk的双重启动设置 - 提供用户交互提示
-
设备文件准备
- 创建统一的根设备文件
/dev/root - 为后续挂载提供标准接口
- 创建统一的根设备文件
mount_block_root 功能
主要功能:尝试多种文件系统类型挂载根文件系统
详细执行流程:
-
文件系统发现
- 获取内核编译时支持的所有文件系统类型
- 准备遍历尝试
-
智能重试机制
- 遍历所有文件系统类型
- 针对权限错误自动切换到只读模式重试
- 跳过不兼容的文件系统类型
-
详细错误报告
- 区分设备打开失败和超级块错误
- 提供清晰的用户指导信息
- 确保故障原因可诊断
do_mount_root 功能
主要功能:执行实际的根文件系统挂载操作
详细执行流程:
-
挂载执行
- 调用系统挂载调用
- 处理文件系统特定的挂载数据
-
环境切换
- 改变当前工作目录到新根文件系统
- 更新全局根设备信息
-
状态报告
- 打印成功的挂载信息
- 记录文件系统类型和挂载模式
控制initrd的加载和执行流程initrd_load
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] = { '|' , '/' , '-' , '\\' };
#endifout_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_CRAMDISKif (crd_load(in_fd, out_fd) == 0)goto successful_load;
#elseprintk(KERN_NOTICE"RAMDISK: Kernel does not support compressed ""RAM disk images\n");
#endifgoto 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;elserd_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;elsedevblocks >>= 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 函数
函数声明和初始检查
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 镜像加载和处理
/** 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
清理和返回
sys_unlink("/initrd.image");return 0;
}
- 删除
initrd镜像文件 - 返回0表示没有使用
initrd
rd_load_image 函数
函数声明和变量定义
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)
out_fd = sys_open("/dev/ram", O_RDWR, 0);if (out_fd < 0)goto out;
- 以读写方式打开RAM disk设备
- 如果打开失败,跳转到清理代码
打开输入文件(initrd镜像)
in_fd = sys_open(from, O_RDONLY, 0);if (in_fd < 0)goto noclose_input;
- 以只读方式打开
initrd镜像文件 - 如果打开失败,跳转到不关闭输入的清理代码
识别RAM disk镜像格式
nblocks = identify_ramdisk_image(in_fd, rd_image_start);if (nblocks < 0)goto done;
identify_ramdisk_image():识别镜像格式并返回块数- 如果识别失败,跳转到完成代码
压缩镜像处理
if (nblocks == 0) {
#ifdef BUILD_CRAMDISKif (crd_load(in_fd, out_fd) == 0)goto successful_load;
#elseprintk(KERN_NOTICE"RAMDISK: Kernel does not support compressed ""RAM disk images\n");
#endifgoto done;}
nblocks == 0表示压缩的镜像- 如果支持压缩RAM disk,调用
crd_load加载 - 否则打印不支持压缩镜像的警告
容量检查
/** 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;elserd_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容量
确定设备大小
if (sys_ioctl(in_fd, BLKGETSIZE, (unsigned long)&devblocks) < 0)devblocks = 0;elsedevblocks >>= 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作为设备大小 - 检查设备大小是否有效
分配缓冲区
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");
- 读取一个块的数据并写入RAM disk
- 每16个块显示一次旋转进度指示器
- 循环结束后打印完成信息
清理和返回
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 函数
函数声明和初始设置
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
/* 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环境
/* 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
pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);if (pid > 0) {while (pid != sys_wait4(-1, &i, 0, NULL))yield();}
- 创建内核线程执行
/linuxrc脚本 - 等待
linuxrc执行完成
恢复原始根环境
/* 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作为根设备的情况
if (new_decode_dev(real_root_dev) == Root_RAM0) {sys_chdir("/old");return;}
- 如果真实的根设备就是RAM disk,直接使用
initrd - 切换到
/old目录并返回
挂载真正的根文件系统
ROOT_DEV = new_decode_dev(real_root_dev);mount_root();
- 恢复真实的根设备号
- 挂载真正的根文件系统
清理initrd
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机制的核心价值:
- 模块化启动:在挂载真正根文件系统前加载必要驱动
- 硬件检测:执行硬件特定的初始化脚本
