initramfs是一个cpio格式的包,压缩方式可以选择xz,但initramfs并不按照任何文件系统的格式组织数据。initramfs可以理解为initramfs=特定的文件夹(文件),把这些文件夹按照cpio格式打包再压缩就是initramfs(如生成最终文件initramfs.cpio.xz)。
系统使用 initramfs 作为它的根文件系统(或临时根文件系统)甚至不需要将文件系统驱动程序内建到kernel,因为没有块设备要用来做文件的块设备,只是存在内存中的文件罢了。initramfs执行的init程序不返回内核。
有两种方式生成initramfs打包文件:
1, 编译kernel的时候以一个cpio包的形式被包裹到kernel的镜像中,当kernel启动的时候,把initramfs解开,把里面的文件夹(文件)复制到一个tmpfs格式的分区中(roofs的根目录 “/”),那么就可以把这个分区作为根文件系统(或临时作为根文件系统,在initramfs的init脚本最后可以调用switch_root挂载并切换到真正的根文件系统中);
2, 不把initramfs编译进内核镜像中,而是单独组织生成一个cpio格式打包文件,然后可以kernel镜像打包在一起生在一个FIT镜像,然后由uboot代码负责解析出initramfs打包文件,uboot可以通过修改设备树信息(如修改chosen节点)进而将initramfs打包文件所处的物理地址信息传递给kernel,当kernel启动的时候,把initramfs解开,把里面的文件夹(文件)复制到一个tmpfs格式的分区中(roofs的根目录 “/”),那么就可以把这个分区作为根文件系统(或临时作为根文件系统,在initramfs的init脚本最后可以调用switch_root挂载并切换到真正的根文件系统中)。
uboot代码从FIT镜像中解析出initramfs打包文件,调用操作设备树代码(common/fdt_support.c)为chosen节点添加linux,initrd-start,linux,initrd-end属性,即设备initramfs打包文件load到的起始物理地址与结束物理地址。kernel启动阶段从r2寄存器拿到fdt后,通过early_init_dt_scan_chosen->early_init_dt_check_for_initrd(node)函数从设备树chosen节点获取到initramfs的物理地址。
系统使用 initramfs 作为它的根文件系统(或临时根文件系统)甚至不需要将文件系统驱动程序内建到kernel,因为没有块设备要用来做文件的块设备,只是存在内存中的文件罢了。initramfs执行的init程序不返回内核。
有两种方式生成initramfs打包文件:
1, 编译kernel的时候以一个cpio包的形式被包裹到kernel的镜像中,当kernel启动的时候,把initramfs解开,把里面的文件夹(文件)复制到一个tmpfs格式的分区中(roofs的根目录 “/”),那么就可以把这个分区作为根文件系统(或临时作为根文件系统,在initramfs的init脚本最后可以调用switch_root挂载并切换到真正的根文件系统中);
2, 不把initramfs编译进内核镜像中,而是单独组织生成一个cpio格式打包文件,然后可以kernel镜像打包在一起生在一个FIT镜像,然后由uboot代码负责解析出initramfs打包文件,uboot可以通过修改设备树信息(如修改chosen节点)进而将initramfs打包文件所处的物理地址信息传递给kernel,当kernel启动的时候,把initramfs解开,把里面的文件夹(文件)复制到一个tmpfs格式的分区中(roofs的根目录 “/”),那么就可以把这个分区作为根文件系统(或临时作为根文件系统,在initramfs的init脚本最后可以调用switch_root挂载并切换到真正的根文件系统中)。
uboot代码从FIT镜像中解析出initramfs打包文件,调用操作设备树代码(common/fdt_support.c)为chosen节点添加linux,initrd-start,linux,initrd-end属性,即设备initramfs打包文件load到的起始物理地址与结束物理地址。kernel启动阶段从r2寄存器拿到fdt后,通过early_init_dt_scan_chosen->early_init_dt_check_for_initrd(node)函数从设备树chosen节点获取到initramfs的物理地址。
设备树chosen节点:
chosen {
bootargs = "console=ttyAMA0,115200 loglevel=7 panic=3 isolcpus=1
linux,initrd-start = <0x27a35000>; //uboot代码设置的物理地址
linux,initrd-end = <0x27fffae8>;
rsr = <0x2>;
crtm_partition = "primary";
chosen {
bootargs = "console=ttyAMA0,115200 loglevel=7 panic=3 isolcpus=1
linux,initrd-start = <0x27a35000>; //uboot代码设置的物理地址
linux,initrd-end = <0x27fffae8>;
rsr = <0x2>;
crtm_partition = "primary";
uboot串口打印:
## Loading init Ramdisk from FIT Image at 10000000 ...
Using 'config@1' configuration
Trying 'ramdisk@1' ramdisk subimage
Description: initramfs.cpio.xz
Type: RAMDisk Image
Compression: uncompressed
Data Start: 0x101f0924
Data Size: 6066252 Bytes = 5.8 MiB
Architecture: ARM
OS: Linux
Load Address: 0x01000000
Entry Point: 0x001f08c0
Hash algo: crc32
Hash value: c4249d83
Verifying Hash Integrity ... crc32+ OK
Trying 'ramdisk@1' ramdisk subimage
Description: initramfs.cpio.xz
Type: RAMDisk Image
Compression: uncompressed
Data Start: 0x101f0924
Data Size: 6066252 Bytes = 5.8 MiB
Architecture: ARM
OS: Linux
Load Address: 0x01000000
Entry Point: 0x001f08c0
Hash algo: crc32
Hash value: c4249d83
Verifying Hash Integrity ... crc32+ OK
kernel加载代码(按调用顺序, 以使用initramfs.cpio.xz为例,关键代码处加了注释):
#ifdef CONFIG_BLK_DEV_INITRD
/**
* early_init_dt_check_for_initrd - Decode initrd location from flat tree
* @node: reference to node containing initrd location ('chosen')
*/
void __init early_init_dt_check_for_initrd(unsigned long node)
{
unsigned long start, end, len;
__be32 *prop;
pr_debug("Looking for initrd properties... ");
prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
if (!prop)
return;
start = of_read_ulong(prop, len/4);
prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
if (!prop)
return;
end = of_read_ulong(prop, len/4);
early_init_dt_setup_initrd_arch(start, end);
pr_debug("initrd_start=0x%lx initrd_end=0x%lx\n", start, end);
}
#else
inline void early_init_dt_check_for_initrd(unsigned long node)
{
}
#endif /* CONFIG_BLK_DEV_INITRD */
#ifdef CONFIG_OF_FLATTREE
void __init early_init_dt_setup_initrd_arch(unsigned long start, unsigned long end)
{
phys_initrd_start = start; //获取到了initramfs.cpio.xz文件的物理起始地址与size
phys_initrd_size = end - start;
}
#endif /* CONFIG_OF_FLATTREE */
void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)
{
int i;
for (i = 0; i < mi->nr_banks; i++)
memblock_add(mi->bank[i].start, mi->bank[i].size);
/* Register the kernel text, kernel data and initrd with memblock. */
#ifdef CONFIG_XIP_KERNEL
memblock_reserve(__pa(_sdata), _end - _sdata);
#else
memblock_reserve(__pa(_stext), _end - _stext);
#endif
#ifdef CONFIG_BLK_DEV_INITRD
if (phys_initrd_size &&
!memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
pr_err("INITRD: 0x%08lx+0x%08lx is not a memory region - disabling initrd\n",
phys_initrd_start, phys_initrd_size);
phys_initrd_start = phys_initrd_size = 0;
}
if (phys_initrd_size &&
memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
pr_err("INITRD: 0x%08lx+0x%08lx overlaps in-use memory region - disabling initrd\n",
phys_initrd_start, phys_initrd_size);
phys_initrd_start = phys_initrd_size = 0;
}
if (phys_initrd_size) {
memblock_reserve(phys_initrd_start, phys_initrd_size);
/* Now convert initrd to virtual addresses */
initrd_start = __phys_to_virt(phys_initrd_start);//将initramfs.cpio.xz文件所处物理地址转化为虚拟地址(+PAGE_OFFSET)
initrd_end = initrd_start + phys_initrd_size;
}
#endif
static int __init populate_rootfs(void)
{
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
if (err)
panic(err); /* Failed to decompress INTERNAL initramfs */
if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM //使用initramfs时该宏没有定义,使用ramdisk时才定义该宏
int fd;
printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start);
if (!err) {
free_initrd();
goto done;
} else {
clean_rootfs();
unpack_to_rootfs(__initramfs_start, __initramfs_size);
}
printk(KERN_INFO "rootfs image is not initramfs (%s)"
"; looks like an initrd\n", err);
fd = sys_open("/initrd.image",
O_WRONLY|O_CREAT, 0700);
if (fd >= 0) {
sys_write(fd, (char *)initrd_start,
initrd_end - initrd_start);
sys_close(fd);
free_initrd();
}
done:
#else
printk(KERN_INFO "Unpacking initramfs...\n");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start); //将initramfs.cpio.xz解包解压到roofs的根目录("/")
if (err)
printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
free_initrd();
#endif
/*
* Try loading default modules from initramfs. This gives
* us a chance to load before device_initcalls.
*/
load_default_modules();
}
return 0;
}
rootfs_initcall(populate_rootfs);
static noinline void __init kernel_init_freeable(void)
{
struct stat console_stat;
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
/* Now the scheduler is fully set up and can do blocking allocations */
gfp_allowed_mask = __GFP_BITS_MASK;
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_MEMORY]);
/*
* init can run on any cpu.
*/
set_cpus_allowed_ptr(current, cpu_all_mask);
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
do_pre_smp_initcalls();
lockup_detector_init();
smp_init();
sched_init_smp();
do_basic_setup();//上述populate_rootfs是在该函数中调用的(do_initcalls)
/* Use /dev/console to infer if the rootfs is setup properly */
if (sys_newlstat((char __user *) "/dev/console", (struct stat __user *) &console_stat)
|| !S_ISCHR(console_stat.st_mode)) {
panic("/dev/console is missing or not a character device!\nPlease ensure your rootfs is properly configured\n");
}
/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
pr_err("Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
if (!ramdisk_execute_command) { //因为没有使用ramdisk,所以该值为null
ramdisk_execute_command = "/init"; //将其赋值,实际上"/init"就是刚才在populate_rootfs对initramfs.cpio.xz解包解压到"/"后的得到的init
}
//因为在populate_rootfs中已将将initramfs.cpio.xz解包解压到了rootfs的根目录,所以这里判断"/init"是不是可以访问的,答案是肯定的,因为该文件已经存在了
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace(); //所以在使用initramfs时,这里不会执行。但使用ramdisk时,会执行到这里,因为ramdisk在"/"还是一个image文件,还没有挂载,所以在"/"之下还不存在init文件
}
/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*/
/* rootfs is available now, try loading default modules */
load_default_modules();
}
static int __ref kernel_init(void *unused)
{
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
flush_delayed_fput();
if (ramdisk_execute_command) { //因为已经设置了该变量,所以下面就开始执行rootfs根下init脚本了,他继承了kernel_thread(kernel_init,...)内核线程的PID=1,开始转向用户空间执行
if (!run_init_process(ramdisk_execute_command))
return 0;
pr_err("Failed to execute %s\n", ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
if (!run_init_process(execute_command))
return 0;
pr_err("Failed to execute %s. Attempting defaults...\n",
execute_command);
}
if (!run_init_process("/sbin/init") ||
!run_init_process("/etc/init") ||
!run_init_process("/bin/init") ||
!run_init_process("/bin/sh"))
return 0;
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
下面看看init脚本,可以在最后挂载真正的根文件系统(如果initramfs不是作为最终根文件系统的话):
exec /sbin/switch_root $ROOTFS_MNT /sbin/init #
ROOTFS_MNT = /mnt
在执行 switch_root之前,在init脚本中可以从远程server通过wget下载真正的根文件系统,并解压到/mnt目录下,然后在最后执行switch_root。
switch_root会做这些工作: switch_root moves already mounted /proc, /dev and /sys to newroot and makes newroot the new root filesystem and starts init process(就是 /lib/systemd/systemd,然后systemd就可启动各种APP的service了,直至systemd达到多用户target,至此启动完毕).
在执行 switch_root之前,在init脚本中可以从远程server通过wget下载真正的根文件系统,并解压到/mnt目录下,然后在最后执行switch_root。
switch_root会做这些工作: switch_root moves already mounted /proc, /dev and /sys to newroot and makes newroot the new root filesystem and starts init process(就是 /lib/systemd/systemd,然后systemd就可启动各种APP的service了,直至systemd达到多用户target,至此启动完毕).
root@tian:~# ls -all /sbin/init
lrwxrwxrwx 1 root root 20 Jan 1 1970 /sbin/init -> /lib/systemd/systemd