Linux中初始化根文件系统populate_rootfs的实现

初始化根文件系统populate_rootfs

c 复制代码
void __init populate_rootfs(void)
{
	char *err = unpack_to_rootfs(&__initramfs_start,
			 &__initramfs_end - &__initramfs_start, 0);
	if (err)
		panic(err);
#ifdef CONFIG_BLK_DEV_INITRD
	if (initrd_start) {
		int fd;
		printk(KERN_INFO "checking if image is initramfs...");
		err = unpack_to_rootfs((char *)initrd_start,
			initrd_end - initrd_start, 1);
		if (!err) {
			printk(" it is\n");
			unpack_to_rootfs((char *)initrd_start,
				initrd_end - initrd_start, 0);
			free_initrd_mem(initrd_start, initrd_end);
			return;
		}
		printk("it isn't (%s); looks like an initrd\n", err);
		fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 700);
		if (fd >= 0) {
			sys_write(fd, (char *)initrd_start,
					initrd_end - initrd_start);
			sys_close(fd);
			free_initrd_mem(initrd_start, initrd_end);
		}
	}
#endif
}

函数功能概述

populate_rootfs 函数负责在内核启动过程中初始化根文件系统,处理内嵌的initramfs和可选的initrd镜像,为系统提供最初的根文件系统环境

代码详细解析

c 复制代码
void __init populate_rootfs(void)
{

函数声明:

  • void __init: 返回类型为void,__init宏表示该函数在初始化完成后会被从内存中移除
  • populate_rootfs(void): 函数名,意为"填充根文件系统"

内嵌initramfs解压

c 复制代码
	char *err = unpack_to_rootfs(&__initramfs_start,
			 &__initramfs_end - &__initramfs_start, 0);
  • unpack_to_rootfs(): 核心解压函数,将压缩的根文件系统数据解压到根文件系统
  • 参数1 &__initramfs_start: 内嵌initramfs数据的起始地址
  • 参数2 &__initramfs_end - &__initramfs_start: 计算内嵌initramfs数据的长度
  • 参数3 0: 标志位,0表示实际执行解压操作
  • 内嵌initramfs : 这是在编译内核时直接嵌入内核镜像的cpio归档文件

内嵌initramfs错误处理

c 复制代码
	if (err)
		panic(err);

错误处理:

  • 检查unpack_to_rootfs的返回值
  • 如果返回非NULL(表示错误信息),立即调用panic()使内核崩溃
  • 重要性 : 内嵌initramfs是系统启动的基础,如果解压失败系统无法继续启动

外部initrd处理开始

c 复制代码
#ifdef CONFIG_BLK_DEV_INITRD
	if (initrd_start) {
  • #ifdef CONFIG_BLK_DEV_INITRD: 条件编译,只在配置了初始RAM磁盘支持时编译以下代码
  • if (initrd_start): 检查是否存在外部initrd镜像
  • initrd: Initial RAM Disk,由bootloader加载到内存中的临时根文件系统

initrd类型检测

c 复制代码
		int fd;
		printk(KERN_INFO "checking if image is initramfs...");
		err = unpack_to_rootfs((char *)initrd_start,
			initrd_end - initrd_start, 1);
  • printk(KERN_INFO ...): 输出检测信息,注意这里没有换行符
  • unpack_to_rootfs(..., 1): 关键调用,第三个参数为1表示仅检测不实际解压
  • 参数1 (char *)initrd_start: initrd数据的起始地址
  • 参数2 initrd_end - initrd_start: initrd数据的长度
  • 检测目的 : 判断initrdinitramfs格式还是传统的镜像文件系统格式

initramfs格式处理

c 复制代码
		if (!err) {
			printk(" it is\n");
			unpack_to_rootfs((char *)initrd_start,
				initrd_end - initrd_start, 0);
			free_initrd_mem(initrd_start, initrd_end);
			return;
		}

处理initramfs格式的initrd

  • if (!err): 如果检测成功(返回NULL)
  • printk(" it is\n"): 补全输出信息,确认是initramfs格式
  • unpack_to_rootfs(..., 0): 实际解压initrd到根文件系统
  • free_initrd_mem(): 释放initrd占用的内存空间
  • return: 函数提前返回,initrd处理完成

传统initrd格式处理

c 复制代码
		printk("it isn't (%s); looks like an initrd\n", err);

传统initrd识别:

  • 输出信息表明这不是initramfs格式,而是传统initrd
  • (%s): 输出具体的错误信息,说明为什么不是initramfs格式

保存传统initrd到文件

c 复制代码
		fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 700);
		if (fd >= 0) {
			sys_write(fd, (char *)initrd_start,
					initrd_end - initrd_start);
			sys_close(fd);
			free_initrd_mem(initrd_start, initrd_end);
		}

传统initrd处理:

  1. sys_open("/initrd.image", O_WRONLY|O_CREAT, 700): 创建文件

    • 文件名: /initrd.image
    • 标志: 只写 + 创建,权限700
  2. sys_write(): 将initrd数据写入文件

    • 将整个initrd镜像保存到根文件系统中
  3. sys_close(): 关闭文件

  4. free_initrd_mem(): 释放initrd占用的内存

作用 : 传统initrd需要由用户空间的init程序后续挂载和使用,所以先保存为文件

关键数据结构说明

1. 内嵌initramfs符号

c 复制代码
extern char __initramfs_start[], __initramfs_end[];

这些符号由链接器脚本定义,指向编译时嵌入内核的initramfs数据的开始和结束位置

2. initrd内存地址

c 复制代码
/* 由bootloader设置 */
extern unsigned long initrd_start, initrd_end;

这些变量由bootloader在加载内核时设置,指向initrd在内存中的位置。

3. 解压函数参数

c 复制代码
char *unpack_to_rootfs(char *src, unsigned long len, int check_only)
  • src: 源数据地址
  • len: 数据长度
  • check_only: 0=实际解压, 1=仅检测格式

不同类型根文件系统的处理

1. 内嵌initramfs

  • 位置: 编译时嵌入内核镜像
  • 处理: 必须成功解压,失败则panic
  • 用途: 提供最基本的根文件系统

2. 外部initramfs格式initrd

  • 位置: 由bootloader单独加载
  • 处理 : 检测为initramfs后立即解压并释放内存
  • 用途: 提供更完整的临时根文件系统

3. 传统文件系统格式initrd

  • 位置: 由bootloader单独加载
  • 处理 : 保存为/initrd.image文件供后续使用
  • 用途: 包含传统文件系统镜像,需要手动挂载

处理initramfs解压unpack_to_rootfs

c 复制代码
char * __init unpack_to_rootfs(char *buf, unsigned len, int check_only)
{
	int written;
	dry_run = check_only;
	header_buf = malloc(110);
	symlink_buf = malloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1);
	name_buf = malloc(N_ALIGN(PATH_MAX));
	window = malloc(WSIZE);
	if (!window || !header_buf || !symlink_buf || !name_buf)
		panic("can't allocate buffers");
	state = Start;
	this_header = 0;
	message = NULL;
	while (!message && len) {
		loff_t saved_offset = this_header;
		if (*buf == '0' && !(this_header & 3)) {
			state = Start;
			written = write_buffer(buf, len);
			buf += written;
			len -= written;
			continue;
		}
		if (!*buf) {
			buf++;
			len--;
			this_header++;
			continue;
		}
		this_header = 0;
		insize = len;
		inbuf = buf;
		inptr = 0;
		outcnt = 0;		/* bytes in output buffer */
		bytes_out = 0;
		crc = (ulg)0xffffffffL; /* shift register contents */
		makecrc();
		gunzip();
		if (state != Reset)
			error("junk in gzipped archive");
		this_header = saved_offset + inptr;
		buf += inptr;
		len -= inptr;
	}
	free(window);
	free(name_buf);
	free(symlink_buf);
	free(header_buf);
	return message;
}

函数功能概述

unpack_to_rootfs 函数负责解压内嵌的initramfs数据到根文件系统,支持gzip压缩格式,并可以工作在检测模式或实际解压模式

代码详细解析

c 复制代码
char * __init unpack_to_rootfs(char *buf, unsigned len, int check_only)
{
	int written;

函数声明:

  • char * __init: 返回错误信息字符串指针,__init表示初始化完成后内存会被释放
  • 参数 :
    • buf: 指向initramfs数据的指针
    • len: 数据长度
    • check_only: 模式标志,1=仅检测,0=实际解压

模式设置和缓冲区分配

c 复制代码
	dry_run = check_only;
	header_buf = malloc(110);
	symlink_buf = malloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1);
	name_buf = malloc(N_ALIGN(PATH_MAX));
	window = malloc(WSIZE);

初始化和内存分配:

  • dry_run = check_only: 设置全局标志,控制是否实际创建文件
  • header_buf = malloc(110): 分配cpio归档头缓冲区
  • symlink_buf = malloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1): 分配符号链接缓冲区
    • PATH_MAX: 最大路径长度(通常4096)
    • N_ALIGN(PATH_MAX): 对齐后的路径长度
    • 额外+1用于字符串终止符
  • name_buf = malloc(N_ALIGN(PATH_MAX)): 分配文件名缓冲区
  • window = malloc(WSIZE): 分配gzip解压窗口(通常32KB)

内存分配失败处理

c 复制代码
	if (!window || !header_buf || !symlink_buf || !name_buf)
		panic("can't allocate buffers");

错误检查:

  • 检查所有缓冲区是否分配成功
  • 如果有任何一个分配失败,调用panic()使内核崩溃
  • 重要性: 没有这些缓冲区无法进行解压操作

状态初始化

c 复制代码
	state = Start;
	this_header = 0;
	message = NULL;

解压状态初始化:

  • state = Start: 设置初始状态为开始状态
  • this_header = 0: 当前头位置清零
  • message = NULL: 错误消息指针初始化为NULL

主解压循环开始

c 复制代码
	while (!message && len) {
		loff_t saved_offset = this_header;

主循环条件:

  • !message: 没有错误发生
  • len: 还有数据需要处理
  • saved_offset = this_header: 保存当前头位置,用于错误恢复

对齐填充处理

c 复制代码
		if (*buf == '0' && !(this_header & 3)) {
			state = Start;
			written = write_buffer(buf, len);
			buf += written;
			len -= written;
			continue;
		}

处理对齐填充:

  • *buf == '0' && !(this_header & 3): 检测到'0'字符且当前偏移是4字节对齐的
  • 这可能是cpio归档的填充字节或新归档的开始
  • write_buffer(): 写入缓冲区,这里因为stateStart,所以是读取header到缓冲区
  • 更新缓冲区和长度,继续循环

空字节跳过

c 复制代码
		if (!*buf) {
			buf++;
			len--;
			this_header++;
			continue;
		}

跳过空字节:

  • 如果当前字节是0,简单地跳过它
  • 增加缓冲区指针和头位置,减少剩余长度
  • 继续处理下一个字节

gzip解压准备

c 复制代码
		this_header = 0;
		insize = len;
		inbuf = buf;
		inptr = 0;
		outcnt = 0;		/* bytes in output buffer */
		bytes_out = 0;
		crc = (ulg)0xffffffffL; /* shift register contents */

gzip解压参数设置:

  • this_header = 0: 重置头位置
  • insize = len: 设置输入数据大小
  • inbuf = buf: 设置输入缓冲区
  • inptr = 0: 输入指针归零
  • outcnt = 0: 输出缓冲区计数清零
  • bytes_out = 0: 输出字节数清零
  • crc = (ulg)0xffffffffL: 初始化CRC校验值

CRC表初始化和解压执行

c 复制代码
		makecrc();
		gunzip();
  • makecrc(): 生成CRC校验表(可能只在第一次调用时生成)
  • gunzip(): 执行实际的gzip解压操作
    • 这个函数会处理gzip流头
    • 解压数据到内部缓冲区
    • 调用相应的回调函数处理解压后的数据

解压后状态检查

c 复制代码
		if (state != Reset)
			error("junk in gzipped archive");

状态验证:

  • 检查解压后的状态是否为Reset
  • 如果不是,说明gzip归档中有垃圾数据,调用error()设置错误消息
  • Reset状态表示成功完成一个gzip流的解压

更新处理位置

c 复制代码
		this_header = saved_offset + inptr;
		buf += inptr;
		len -= inptr;
	}

位置更新:

  • this_header = saved_offset + inptr: 更新头位置(保存的偏移 + 处理的字节数)
  • buf += inptr: 前进输入缓冲区指针
  • len -= inptr: 减少剩余数据长度
  • 循环结束

资源清理和返回

c 复制代码
	free(window);
	free(name_buf);
	free(symlink_buf);
	free(header_buf);
	return message;
}

清理和返回:

  • 按分配顺序释放所有缓冲区
  • return message: 返回错误消息(成功时为NULL)

状态机驱动的数据解析器write_buffer

c 复制代码
static int __init write_buffer(char *buf, unsigned len)
{
	count = len;
	victim = buf;

	while (!actions[state]())
		;
	return len - count;
}
static __initdata int (*actions[])(void) = {
	[Start]		= do_start,
	[Collect]	= do_collect,
	[GotHeader]	= do_header,
	[SkipIt]	= do_skip,
	[GotName]	= do_name,
	[CopyFile]	= do_copy,
	[GotSymlink]	= do_symlink,
	[Reset]		= do_reset,
};
static int __init do_start(void)
{
	read_into(header_buf, 110, GotHeader);
	return 0;
}
static void __init read_into(char *buf, unsigned size, enum state next)
{
	if (count >= size) {
		collected = victim;
		eat(size);
		state = next;
	} else {
		collect = collected = buf;
		remains = size;
		next_state = next;
		state = Collect;
	}
}

代码功能概述

这是一个基于状态机的数据解析器,用于逐步处理输入缓冲区中的数据。它通过状态转换来管理不同的解析阶段,包括读取头部、收集数据、处理文件等操作

代码逐段解析

主入口函数

c 复制代码
static int __init write_buffer(char *buf, unsigned len)
{
	count = len;          // 设置剩余待处理字节数
	victim = buf;         // 保存输入缓冲区指针

	while (!actions[state]())  // 状态机循环:执行当前状态对应的函数
		;
	return len - count;   // 返回已处理的字节数
}
  • count = len: 初始化剩余字节计数器
  • victim = buf: 保存输入缓冲区起始地址
  • while (!actions[state]()): 状态机核心循环,持续执行当前状态函数直到返回非0值
  • return len - count: 计算并返回实际处理的字节数

状态函数指针数组

c 复制代码
static __initdata int (*actions[])(void) = {
	[Start]		= do_start,      // 初始状态
	[Collect]	= do_collect,    // 数据收集状态  
	[GotHeader]	= do_header,     // 头部处理状态
	[SkipIt]	= do_skip,       // 跳过数据状态
	[GotName]	= do_name,       // 名称处理状态
	[CopyFile]	= do_copy,       // 文件复制状态
	[GotSymlink]	= do_symlink,  // 符号链接处理状态
	[Reset]		= do_reset,      // 重置状态
};

这是一个状态到函数的映射表,每个状态对应一个处理函数

初始状态函数

c 复制代码
static int __init do_start(void)
{
	read_into(header_buf, 110, GotHeader);
	return 0;
}
  • 调用read_into读取110字节到头部缓冲区
  • 完成后转移到GotHeader状态
  • 返回0表示状态机继续运行

数据读取核心函数

c 复制代码
static void __init read_into(char *buf, unsigned size, enum state next)
{
	if (count >= size) {           // 如果剩余数据足够
		collected = victim;        // 设置收集指针指向当前数据位置
		eat(size);                // 消费指定大小的数据
		state = next;             // 直接转移到下一个状态
	} else {                      // 如果数据不足
		collect = collected = buf; // 设置收集缓冲区
		remains = size;           // 设置剩余需要收集的字节数
		next_state = next;        // 保存目标状态
		state = Collect;          // 进入收集状态
	}
}

分支1:数据充足时

  • collected = victim: 设置数据起始位置
  • eat(size): 消费数据(移动victim指针,减少count)
  • state = next: 立即跳转到目标状态

分支2:数据不足时

  • collect = collected = buf: 设置收集缓冲区指针
  • remains = size: 记录还需要多少字节
  • next_state = next: 保存最终要进入的状态
  • state = Collect: 切换到收集状态等待更多数据

完整工作流程

  1. 初始化: 设置输入缓冲区和长度计数器
  2. 状态循环: 根据当前状态索引执行对应的处理函数
  3. 状态转换 : 每个处理函数通过修改state变量来决定下一个状态
  4. 数据收集: 当需要的数据不足时,进入收集状态等待更多输入
  5. 完成处理: 当某个状态函数返回非0值时,循环结束

释放initrd内存free_initrd_mem

c 复制代码
void free_initrd_mem(unsigned long start, unsigned long end)
{
	if (start < end)
		printk (KERN_INFO "Freeing initrd memory: %ldk freed\n", (end - start) >> 10);
	for (; start < end; start += PAGE_SIZE) {
		ClearPageReserved(virt_to_page(start));
		set_page_count(virt_to_page(start), 1);
		free_page(start);
		totalram_pages++;
	}
}

函数功能概述

free_initrd_mem 函数负责释放initrd(初始RAM磁盘)占用的物理内存页面,将这些页面标记为可用并返回给系统内存管理器

代码详细解析

函数声明

c 复制代码
void free_initrd_mem(unsigned long start, unsigned long end)
{

函数声明:

  • void: 无返回值
  • free_initrd_mem: 函数名,明确表示释放initrd内存
  • unsigned long start: initrd内存区域的起始地址
  • unsigned long end: initrd内存区域的结束地址

有效性检查和信息输出

c 复制代码
	if (start < end)
		printk (KERN_INFO "Freeing initrd memory: %ldk freed\n", (end - start) >> 10);

条件检查和日志输出:

  • if (start < end): 验证地址范围的正确性,确保起始地址小于结束地址
  • printk(KERN_INFO ...): 输出内核信息日志
  • (end - start) >> 10: 计算释放的内存大小并以KB为单位显示
    • end - start: 计算总字节数
    • >> 10: 右移10位,相当于除以1024,将字节转换为KB
  • 作用 : 在系统日志中记录释放了多少initrd内存

内存释放循环

c 复制代码
	for (; start < end; start += PAGE_SIZE) {

循环初始化:

  • 没有初始表达式,直接使用传入的start值
  • start < end: 循环条件,处理整个内存区域
  • start += PAGE_SIZE: 每次循环增加一个页面大小
  • PAGE_SIZE: 系统页面大小,通常是4096字节(4KB)

页面标志清除

c 复制代码
		ClearPageReserved(virt_to_page(start));

清除保留标志:

  • virt_to_page(start): 将虚拟地址转换为对应的页面结构指针
    • 这个函数完成虚拟地址到struct page的映射
  • ClearPageReserved(): 清除页面的保留标志
  • 保留标志作用 : initrd内存在使用期间被标记为"保留",防止被其他用途占用
  • 清除必要性: 在释放前必须清除保留标志,否则页面无法被重新分配

页面引用计数设置

c 复制代码
		set_page_count(virt_to_page(start), 1);
  • virt_to_page(start): 再次获取页面结构指针

  • set_page_count(..., 1):

    c 复制代码
    #define set_page_count(p,v) 	atomic_set(&(p)->_count, v - 1)
    • 实际上是将页面的引用计数置为0

页面释放

c 复制代码
		free_page(start);

释放单个页面:

  • free_page(start): 核心释放函数,将页面返回给伙伴系统(buddy system)
  • 内部操作 :
    • 将页面添加到对应的空闲列表
    • 可能进行页面合并(与相邻空闲页面合并成更大的连续块)
    • 更新内存管理器的统计信息

系统内存计数更新

c 复制代码
		totalram_pages++;
	}
}

更新总内存计数:

  • totalram_pages++: 增加系统总可用页面计数
  • totalram_pages: 全局变量,记录系统中可用的物理页面总数
  • 重要性: 这个计数影响内存分配决策和系统统计信息
相关推荐
成长痕迹4 小时前
【Electron桌面应用完整方案】
1024程序员节
陌上明苏4 小时前
使用ssrs矩阵
1024程序员节
墨利昂4 小时前
深度学习常用优化器解析
人工智能·深度学习·机器学习·1024程序员节
asdfsdgss4 小时前
PyTorch 生成式 AI(1):模型训练过拟合处理,神经网络正则化方法详解
1024程序员节
PyHaVolask4 小时前
Metasploit网络嗅探实战:从数据包捕获到协议分析的完整指南
数据包分析·metasploit·1024程序员节·流量分析·网络嗅探
XH-hui5 小时前
【打靶日记】THL 之 Facultad
linux·网络安全·1024程序员节·thehackerlabs
.NET修仙日记5 小时前
Visual Studio 演进之路:从集成套件到AI驱动的开发平台
ide·编辑器·ai编程·visual studio·1024程序员节
lixinnnn.5 小时前
算法总结篇(枚举-分治)
算法·1024程序员节
小小小糖果人6 小时前
Linux云计算基础篇(28)-Samba文件服务
1024程序员节