Linux中完成根文件系统的最终准备和切换prepare_namespace函数的实现

Linux系统根文件系统准备和挂载完整流程

阶段一:设备环境准备 (prepare_namespace前半部分)

  1. 设备文件系统建立

    c 复制代码
    mount_devfs();                    // 挂载临时devfs
    md_run_setup();                   // 初始化软件RAID
    • 提供临时的设备节点访问能力
    • 确保RAID设备在根文件系统挂载前就绪
  2. 根设备配置解析

    c 复制代码
    if (saved_root_name[0]) {
        root_device_name = saved_root_name;
        ROOT_DEV = name_to_dev_t(root_device_name);
    }
    • 解析内核命令行root=参数
    • 将设备名称转换为设备号
    • 为后续挂载提供准确的设备标识
  3. 初始RAM磁盘处理

    c 复制代码
    if (initrd_load()) goto out;
    • 加载并处理initrd镜像
    • 如果initrd加载成功,直接使用initrd作为临时根
    • 为驱动加载和真正的根文件系统挂载提供桥梁

阶段二:根文件系统挂载 (mount_root及相关函数)

  1. 网络根文件系统支持

    c 复制代码
    #ifdef CONFIG_ROOT_NFS
    if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {
        if (mount_nfs_root()) return;
    }
    #endif
    • 尝试挂载NFS网络根文件系统
    • 支持无盘工作站的启动场景
  2. 传统设备根文件系统挂载

    c 复制代码
    create_dev("/dev/root", ROOT_DEV, root_device_name);
    mount_block_root("/dev/root", root_mountflags);
    • 创建统一的根设备文件/dev/root
    • 启动文件系统探测和挂载流程
  3. 多文件系统类型探测

    c 复制代码
    get_fs_names(fs_names);
    for (p = fs_names; *p; p += strlen(p)+1) {
        do_mount_root(name, p, flags, data);
    }
    • 获取内核支持的所有文件系统类型
    • 逐个尝试直到找到匹配的文件系统
    • 智能错误处理和重试机制

阶段三:命名空间最终切换 (prepare_namespace后半部分)

  1. 临时环境清理

    c 复制代码
    umount_devfs("/dev");
    • 卸载临时使用的设备文件系统
    • 释放初始化阶段的临时资源
  2. 根文件系统提升

    c 复制代码
    sys_mount(".", "/", NULL, MS_MOVE, NULL);
    sys_chroot(".");
    • 将当前挂载的文件系统移动到根位置
    • 执行chroot操作完成根目录切换
    • 这是Unix系统根目录设置的最终步骤
  3. 安全和服务初始化

    c 复制代码
    security_sb_post_mountroot();
    mount_devfs_fs();
    • 调用安全模块的根文件系统挂载后处理
    • 在新的根环境下重新挂载设备文件系统

实际效果总结

最终达到的状态:

  1. 根文件系统就绪

    • / 目录指向真正的根文件系统
    • 所有必要的设备文件可用
    • 文件系统权限和标志正确设置
  2. 进程环境建立

    • 当前工作目录设置为根目录
    • 进程的根目录正确限制
    • 安全策略初始化完成

完成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=参数保存的设备名

设备名称处理:

  1. root_device_name = saved_root_name: 设置根设备名称
  2. ROOT_DEV = name_to_dev_t(root_device_name): 将设备名转换为设备号
    • name_to_dev_t(): 解析设备名(如"/dev/sda1")为设备号
  3. if (strncmp(root_device_name, "/dev/", 5) == 0): 如果设备名以"/dev/"开头
  4. 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系统根文件系统的最终准备和切换

核心处理阶段

  1. 设备准备阶段

    • 提供临时的设备文件访问
    • 配置软件RAID设备
    • 解析根设备参数
  2. 根文件系统加载阶段

    • 尝试加载initrd作为临时根
    • 处理软盘等特殊设备的RAM磁盘加载
    • 挂载真正的根文件系统
  3. 命名空间切换阶段

    • 清理临时文件系统
    • 移动挂载点到根位置
    • 执行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/root
  • mount_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 功能

主要功能:系统启动时挂载根文件系统的总控函数

详细执行流程

  1. 网络根文件系统尝试

    • 检查是否配置了NFS根文件系统支持
    • 如果根设备是网络设备,尝试挂载NFS
    • 失败时回退到本地设备
  2. 软盘根设备处理

    • 检查软盘驱动支持
    • 支持从软盘加载ramdisk的双重启动设置
    • 提供用户交互提示
  3. 设备文件准备

    • 创建统一的根设备文件 /dev/root
    • 为后续挂载提供标准接口

mount_block_root 功能

主要功能:尝试多种文件系统类型挂载根文件系统

详细执行流程

  1. 文件系统发现

    • 获取内核编译时支持的所有文件系统类型
    • 准备遍历尝试
  2. 智能重试机制

    • 遍历所有文件系统类型
    • 针对权限错误自动切换到只读模式重试
    • 跳过不兼容的文件系统类型
  3. 详细错误报告

    • 区分设备打开失败和超级块错误
    • 提供清晰的用户指导信息
    • 确保故障原因可诊断

do_mount_root 功能

主要功能:执行实际的根文件系统挂载操作

详细执行流程

  1. 挂载执行

    • 调用系统挂载调用
    • 处理文件系统特定的挂载数据
  2. 环境切换

    • 改变当前工作目录到新根文件系统
    • 更新全局根设备信息
  3. 状态报告

    • 打印成功的挂载信息
    • 记录文件系统类型和挂载模式

控制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:内核配置选项,控制是否启用initrd
  • create_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
  • frominitrd镜像文件路径
  • 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的加载和执行流程

详细执行流程

  1. 配置检查

    • 检查是否启用了initrd支持
    • 创建设备文件为加载做准备
  2. 镜像加载决策

    • 加载initrd镜像到RAM disk
    • 根据根设备配置决定是否执行initrd
    • 只在RAM disk不是最终根设备时执行initrd
  3. 资源清理

    • 删除临时文件
    • 返回执行状态

rd_load_image 功能

主要功能 :将initrd镜像加载到RAM disk中

详细执行流程

  1. 设备准备

    • 打开RAM disk设备作为输出
    • 打开initrd镜像文件作为输入
  2. 镜像识别

    • 识别镜像格式(压缩或未压缩)
    • 处理压缩镜像的特殊加载
  3. 容量验证

    • 检查RAM disk容量是否足够
    • 验证设备参数
  4. 数据传输

    • 分块读取和写入数据
    • 支持多磁盘切换
    • 提供进度显示
  5. 资源管理

    • 正确关闭文件和释放内存
    • 清理临时资源

handle_initrd 功能

主要功能 :执行initrd中的初始化脚本并切换到真正的根文件系统

详细执行流程

  1. 环境准备

    • 保存真实根设备信息
    • 挂载initrd为临时根文件系统
  2. 脚本执行

    • 切换到initrd环境
    • 执行/linuxrc初始化脚本
    • 等待脚本完成
  3. 环境恢复

    • 切换回原始根文件系统
    • 挂载真正的根文件系统
  4. 资源清理

    • 移动或清理initrd
    • 释放RAM disk内存

系统启动中的关键作用

initrd机制的核心价值

  1. 模块化启动:在挂载真正根文件系统前加载必要驱动
  2. 硬件检测:执行硬件特定的初始化脚本
相关推荐
大白的编程日记.8 小时前
【Linux学习笔记】线程安全问题之单例模式和死锁
linux·笔记·学习
---学无止境---8 小时前
Linux 2.6.10 调度器负载均衡机制深度解析:从理论到实现
linux
馨谙8 小时前
Linux 安全文件传输完全指南:sftp 与 scp 的深度解析引言
linux·运维·服务器
姓蔡小朋友8 小时前
Linux网络操作
linux·运维·服务器
linmengmeng_13148 小时前
【Centos】服务器硬盘扩容之新加硬盘扩容到现有路径下
linux·服务器·centos
边疆.8 小时前
【Linux】版本控制器Git和调试器—gdb/cgdb的使用
linux·服务器·git·gdb调试·cgdb
陌路209 小时前
Linux22 进程与线程以及内核级线程
linux·开发语言
明天…ling9 小时前
Linux+Apache+MySQL+PHP 架构下搭建 Discuz 社区论坛
linux·服务器·apache
Ghost Face...9 小时前
GRUB配置文件解析与启动流程详解
linux·运维·服务器