初始化根文件系统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数据的长度 - 检测目的 : 判断
initrd是initramfs格式还是传统的镜像文件系统格式
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处理:
-
sys_open("/initrd.image", O_WRONLY|O_CREAT, 700): 创建文件- 文件名:
/initrd.image - 标志: 只写 + 创建,权限700
- 文件名:
-
sys_write(): 将initrd数据写入文件- 将整个
initrd镜像保存到根文件系统中
- 将整个
-
sys_close(): 关闭文件 -
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(): 写入缓冲区,这里因为state是Start,所以是读取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: 切换到收集状态等待更多数据
完整工作流程
- 初始化: 设置输入缓冲区和长度计数器
- 状态循环: 根据当前状态索引执行对应的处理函数
- 状态转换 : 每个处理函数通过修改
state变量来决定下一个状态 - 数据收集: 当需要的数据不足时,进入收集状态等待更多输入
- 完成处理: 当某个状态函数返回非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: 全局变量,记录系统中可用的物理页面总数- 重要性: 这个计数影响内存分配决策和系统统计信息