本文所使用的内核版本: linux-4.19.87
1 内核引导过程
1.1 自解压
bl decompress_kernel /* arch/arm/boot/compressed/head.S +561 /
decompress_kernel() / arch/arm/boot/compressed/misc.c /
/ putstr(“Uncompressing Linux…”);
ret = do_decompress();
putstr(" done, booting the kernel.\n");
*/
1.2 引导
/arch/arm/kernel/head.S +149
ENTRY(stext)
ldr r13, =__mmap_switched @ address to jump to after
ENDPROC(stext)
arch/arm/kernel/head-common.S +119
__mmap_switched:
b start_kernel
ENDPROC(__mmap_switched)
init/main.c
start_kernel(void)
{
/* 进行一系列初始化 */
}
2 内核在何时解析initrd选项,如何使用的?
2.1 解析引导参数时调用early_initrd()找到initrd在内存中的物理地址和大小
arch/arm/mm/init.c
early_param("initrd", early_initrd);
调用流程如下:
start_kernel()
-->setup_arch(&command_line) // 传出命令行, 定义在 arch/arm/kernel/setup.c
-->parse_early_param();
-->parse_early_options(boot_command_line);
-->parse_args("early options", boot_command_line, NULL, 0, 0, 0, do_early_param);
/* 从boot_command_line中获取一组参数"param=val", 逐个调用parse_one()解析 */
-->parse_one(param, val, "early options", NULL, 0, 0, 0, do_early_param); /* doing为函数指针do_early_param的第三个参数 */
-->do_early_param(param, val, "early options");
/*
const struct obs_kernel_param *p; /* {str,setup_func 函数指针,early} */
for (p = __setup_start; p < __setup_end; p++) {
p->setup_func(val);
}
*/
因此early_initrd()函数在start_kernel()–>setup_arch()–>parse_early_param();中调用传递的参数是initrd=0x42000000,0x60000 中的等号后面的内容。
early_initrd(char p)函数中获取initrd物理地址和大小(uboot在引导内核前会将initrd映像文件放在该位置)
填充:
phys_initrd_start = start; / 0x42000000 /
phys_initrd_size = size; / 0x60000 */
2.2 根据initrd的物理地址填充initrd虚拟地址
start_kernel()
-->setup_arch(&command_line)
-->arm_memblock_init()
-->arm_initrd_init() /* 定义在 init/main.c */
/*
initrd_start = __phys_to_virt(phys_initrd_start);
initrd_end = initrd_start + phys_initrd_size;
*/
2.3 准备rootfs根文件
init/main.c
start_kernel()
-->vfs_caches_init(); /* 定义在 fs/dcache.c */
-->mnt_init(); /* 定义在 fs/namespace.c */
-->(1)init_rootfs(); /* 定义在 init/do_mounts.c */
-->register_filesystem(&rootfs_fs_type); /* 定义在 fs/filesystems.c */
/* rootfs_fs_type = { .name = "rootfs", .mount = rootfs_mount,};
static struct file_system_type *file_systems;
注册的实质是追加rootfs类型的文件系统到file_systems全局指针
*/
-->(2)init_mount_tree(); /* 定义在 fs/namespace.c */
-->vfs_kern_mount(type, 0, "rootfs", NULL);
/* 创建相关数据结构,挂载根文件系统 */
-->set_fs_pwd(current->fs, &root); /* 设置当前进程(current)的当前目录为rootfs */
-->set_fs_root(current->fs, &root); /* 设置当前进程(current)的根目录为rootfs */
2.4 解压内存中的initrd文件,并将其填充到上述步骤中的rootfs中
init/initramfs.c中定义 rootfs_initcall(populate_rootfs);
// 在初始化阶段 __initcall__rootfs 阶段解压内存中的initrd, 并根据释放cpio格式的文件到rootfs中。
populate_rootfs()在何处调用的流程如下:
init/main.c
start_kernel()
-->rest_init()
-->kernel_thread(kernel_init, NULL, CLONE_FS);
-->kernel_init()
-->kernel_init_freeable()
-->do_basic_setup();
-->do_initcalls();
-->do_initcall_level(level);
-->do_one_initcall(initcall_t fn) /* 调用8个阶段的初始化函数 */
怎么好像没有调用 rootfs_initcall?
include/linux/init.h
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define ___define_initcall(fn, id, __sec)
static initcall_t _initcall##fn##id __used
attribute((section(#__sec “.init”))) = fn;
对于populate_rootfs()展开为:
static initcall_t __initcall_populate_rootfsrootfs = populate_rootfs;
populate_rootfs(void)
{
/* Load the built in initramfs 如果有的话解压并释放跟内核打包到一块的initramfs */
unpack_to_rootfs(__initramfs_start, __initramfs_size);
/* 符号 __initramfs_start 定义在 include/asm-generic/vmlinux.lds.h
符号 __initramfs_size 定义在 usr/initramfs_data.S
该例子中没有将内核和initramfs打包在一起,因此__initramfs_size=0,不做解压
即没有进入unpack_to_rootfs()中的while(len)进行解压,实际调试中没有打印调试信息
*/
printk(KERN_INFO "Unpacking initramfs...\n");
err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
if (err)
printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
/*
解压 initrd_start 位置的initrd, 并将其填充到rootfs
initrd_end定义在 init/do_mounts_initrd.c +14
*/
}
unpack_to_rootfs()函数就是所谓的释放initrd到rootfs
unpack_to_rootfs(char *buf, unsigned long len)
{
decompress_fn decompress;
decompress = decompress_method(buf, len, &compress_name); /* 返回解压函数指针 */
decompress(buf, len, NULL, flush_buffer, NULL, &my_inptr, error);
}
unpack_to_rootfs()调用decompress()解压initrd, 不断调用flush_buffer()流式的获取到解压结果,使用状态机处理cpio格式的结果
参考这里
init/initramfs.c
注意:不要跟gzip具体解压操作中的状态机混淆
释放操作跟cpio格式密切相关
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,
};
状态机的GotName状态操作函数do_name() 中根据不同类型的文件创建并写入文件。
2.5 执行initrd中 /init
init/main.c
start_kernel()
-->rest_init()
-->kernel_thread(kernel_init, NULL, CLONE_FS);
-->kernel_init()
-->kernel_init_freeable(); /* 该函数中赋值ramdisk_execute_command=/init */
-->run_init_process(ramdisk_execute_command); /* 执行initrd中的/init */
/* {
argv_init[0] = init_filename;
pr_info("Run %s as init process\n", init_filename);
return do_execve(init_filename, argv_init, envp_init);
} */
附录1:
unpack_to_rootfs()调试信息及gzip格式的initrd具体的解压操作
unpack_to_rootfs(char *buf, unsigned long len)
{
decompress = decompress_method(buf, len, &compress_name);
pr_debug("Detected %s compressed data\n", compress_name);
pr_err("[--debug--]compress_name: %s", compress_name);
if (decompress) {
pr_err("[--debug--]if decompress branch");
int res = decompress(buf, len, NULL, flush_buffer, NULL,
&my_inptr, error);
if (res)
pr_err("decompressor failed");
} else if (compress_name) {
pr_err("[--debug--]else if compress_name branch");
if (!message) {
pr_err("[--debug--]if !message branch");
snprintf(msg_buf, sizeof msg_buf,
"compression method %s not configured",
compress_name);
message = msg_buf;
}
} else {
pr_err("[--debug--]else branch not decompress not compress_name ");
error("junk in compressed archive");
}
if (state != Reset){
pr_err("[--debug--]if branch state != Reset");
error("junk in compressed archive");
}
}
加了调试信息,打印如下:
[ 1.745849] Unpacking initramfs...
[ 1.751335] [--debug--]compress_name: gzip // 第一次进入
[ 1.751340] [--debug--]if decompress branch
[ 2.002768] [--debug--]compress_name: (null) // 应该是第二次进入
[ 2.009165] [--debug--]else branch not decompress not compress_name
[ 2.015992] Initramfs unpacking failed: junk in compressed archive
decompress_method(const unsigned char *inbuf, long len, const char **name)
static const struct compress_format compressed_formats[] __initconst = {
{ {0x1f, 0x8b}, "gzip", gunzip },
相当于调用 gunzip(buf, len, NULL, flush_buffer, NULL, &my_inptr, error);
lib/decompress_inflate.c
STATIC int INIT gunzip(unsigned char *buf, long len,
long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long),
unsigned char *out_buf,
long *pos,
void (*error)(char *x))
{
/* 传递下来的参数:fill=NULL, flush=flush_buffer, out_buf=NULL, pos=$my_inptr */
return __gunzip(buf, len, fill, flush, out_buf, 0, pos, error);
}
/* 压缩解压算法参考这里 */
STATIC int INIT __gunzip(unsigned char *buf, long len,
long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long),
unsigned char *out_buf, long out_len,
long *pos,
void(*error)(char *x))
{
/* 该函数中out_buf是malloc分配的,因此使用了内核的cache buffer机制,但是是如何追踪到解压后的文件系统的位置的呢? */
out_len = 0x8000; /* 32 K */
out_buf = malloc(out_len);
rc = zlib_inflateInit2(strm, -MAX_WBITS);
rc = zlib_inflate(strm, 0); // 解压buf中的gzip文件
/* 定义在lib/zlib_inflate/inflate.c */
zlib_inflateEnd(strm);
}
附录2:
挂载其他类型的根文件系统过程
1 cpio格式initrd的旧的处理方式
kernel_init_freeable(void)
-->prepare_namespace();
-->(1)initrd_load(); /* 定义在 init/do_mounts_initrd.c */
-->(1)rd_load_image("/initrd.image") /* 解压并加载 /initrd.image 的内容到 /dev/ram */
/*
rd_load_image(char *from)
{
out_fd = ksys_open("/dev/ram", O_RDWR, 0);
in_fd = ksys_open(from, O_RDONLY, 0);
for (i = 0, disk = 1; i < nblocks; i++) {
ksys_read(in_fd, buf, BLOCK_SIZE);
ksys_write(out_fd, buf, BLOCK_SIZE);
}
}
*/
-->(2)handle_initrd();
/*
挂载initrd到/root
加载模块
执行/linuxrc
挂载真实根文件系统
mount_root(void) /* 定义在 init/do_mounts.c */
mount_block_root("/dev/root", root_mountflags);
解挂initrd
释放内存空间
*/
-->(2)ksys_mount(".", "/", NULL, MS_MOVE, NULL);
-->(3)ksys_chroot(".");
若真正的根文件系统挂载出错,则报相关信息:
mount_block_root(char *name, int flags)
{
/* 该函数中打印报错信息
printk("VFS: Cannot open root device \"%s\" or %s: error %d\n", root_device_name, b, err);
printk("Please append a correct \"root=\" boot option; here are the available partitions:\n");
panic("VFS: Unable to mount root fs on %s", b);
列出可用的根块设备 */
}
2
在解析命令行流程(__setup())中解析出boot cmdline中的"root="的值赋值给saved_root_name
具体调用的root_dev_setup(char *line)函数,该函数定义在 init/do_mounts.c
init/do_mounts.h 文件中根据是否支持RAMDISK(即是否定义了CONFIG_BLK_DEV_RAM宏)定义不同的函数
unsigned long rd_size = CONFIG_BLK_DEV_RAM_SIZE;
解析 boot cmdline 中的 ramdisk_size 字段, 赋值给 rd_size 全局变量
没有ramdisk_size选项initrd也是OK的