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: 全局变量,记录系统中可用的物理页面总数
  • 重要性: 这个计数影响内存分配决策和系统统计信息
相关推荐
学传打活5 天前
【边打字.边学昆仑正义文化】_进阶篇5 宇宙星系的运转(续3)
微信公众平台·1024程序员节·汉字·昆仑正义文化
学传打活10 天前
【边打字.边学昆仑正义文化】_进阶篇5 宇宙星系的运转(续1)
微信公众平台·1024程序员节·汉字·昆仑正义文化
fangzhanpeng16818 天前
快速掌握C#语言基础知识点(1.基础数据类型)
开发语言·c#·1024程序员节
无己心20 天前
面向多平台 AI 搜索引擎的适配层架构设计
前端·人工智能·搜索引擎·ai·1024程序员节
u0103055271 个月前
AI驱动的安卓UI自动生成功能实现
人工智能·1024程序员节
u0103055271 个月前
长株潭智能体赋能腾讯元器链接共享
1024程序员节
云贝贝贝1 个月前
OceanBase认证体系介绍与备考指南
运维·服务器·oceanbase·1024程序员节
u0103055271 个月前
Java图像处理实战指南
人工智能·1024程序员节
u0103055271 个月前
Java AWT鼠标事件全解析
人工智能·1024程序员节
u0103055271 个月前
Java实用类应用精讲
人工智能·1024程序员节