To be a better coder
分类: LINUX
2020-05-09 15:41:39
原文地址:initramfs的运行过程 作者:lifefocus
首先说明我从事的是嵌入式行业,所以以后的linux文章都是嵌入式相关的,除非有特别说明。
我用的芯片是欧洲Gaisler Research公司的Leon3(Sparc架构),内核是Snapgear(包含ucLinux2.0和Linux2.6),已经做好移植工作。
周一的时候,把linux2.6.21下载到leon3的板子上运行,结果莫名退出。
检查的时候主要从配置出发,结果发现gaisler的默认配置有问题。它配置initial root filesystem是romfs,其实从linux2.6内核开始这必须是initramfs。并且我们考虑到sram的大小可能不足以下载运行linux,所以我们使用了sdram。
经过一番更改后,编译下载运行,照样出错。没办法,必须要debug,可是kgdb没有for sparc/leon的patch。所以只能printk了。错误提示如下:
grlib> lo image.dsu
......
IP route cache hash table entries: 1024 (order: 0, 4096 bytes)
TCP established hash table entries: 512 (order: 0, 4096 bytes)
TCP bind hash table entries: 512 (order: -1, 2048 bytes)
TCP: Hash tables configured (established 512 bind 512)
TCP reno registered
initramfs:populate_rootfs:first line:allen
按照提示发现代码在../linux2.6.21.1/init/initramfs.c
static int __init populate_rootfs(void)
{
printk("initramfs:populate_rootfs:first line:allen\n");
(stop in here)--> char *err = unpack_to_rootfs(__initramfs_start,
__initramfs_end - __initramfs_start, 0);
......
经过读源代码和看文档,最后终于把这个问题的过程搞清楚。
首先要清楚initrd 的英文含义是 boot loader initialized RAM disk,就是由 boot loader 初始化的内存盘。在 linu2.6内核启动前, boot loader 会将存储介质中的 initrd 文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的 initrd 文件系统。在 boot loader 配置了 initrd 的情况下,内核启动被分成了两个阶段,第一阶段先执行 initrd 文件系统中的init,完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的 /sbin/init 进程。
linux2.6 内核使用的 initrd 是 cpio 格式,其核心文件是 /init。下面介绍 linux2.6 内核对initrd 的处理流程:
boot loader 把内核以及 initrd 文件加载到内存的特定位置。
内核判断initrd的文件格式,如果是cpio格式。
将initrd的内容释放到rootfs中。
执行initrd中的/init文件,执行到这一点,内核的工作全部结束,完全交给/init文件处理。
这里要清楚另一个概念:
initramfs:initramfs 是在 kernel 2.5中引入的技术,实际上它的含义就是:在内核镜像中附加一个cpio包,这个cpio包中包含了一个小型的文件系统,当内核启动时,内核将这个 cpio包解开,并且将其中包含的文件系统释放到rootfs中,内核中的一部分初始化代码会放到这个文件系统中,作为用户层进程来执行。这样带来的明显 的好处是精简了内核的初始化代码,而且使得内核的初始化过程更容易定制。
所以内核首先要生成一个cpio包,这个包中又包含了initramfs的文件系统,这个就是initfamfs.cpio.gz。生成这个包的文件是:usr/gen_init_cpio.c,编译时initramfs_data.cpio.gz被链接进内核中。这个包放在usr/下,我们的内核也已经生成了这个包。 而unpack_to_rootfs(__initramfs_start, __initramfs_end - __initramfs_start, 0)这句话的意思就是试图通过gunzip解压initfamfs.cpio.gz,在[__initramfs_start, __initramfs_end]之间找到initramfs。现在我们的gz包有了,还停在这里,所以我有理由怀疑是内核的下载地址和sdram的映射地址不相符合而引发的问题。
进一步考证,需要下星期有硬件的情况下才能完成。
还有,这个星期和Gailer Research的D哥通了一下email,他觉得内核没有问题,主要强调了grmon下运行linux2.6的配置参数,如下:
1.不要使用-u,使用minicom;
2.要加-nb;
3.要加-nosram,这样才能把sdram的地址映射到0x40000000。不然有可能映射地址0x40000000是sram;
4.配置initial root filesystem为initramfs,内核要支持initramfs。
后记:
Ok,为了了解细节,Documentation/early-userspace/buffer-format.txt是必须要看看的。initramfs.cpio.gz是为内核准备一个包(这个包最终是连接在__initramfs_start处),其格式大致如下:
initramfs := ("\0" | cpio_archive | cpio_gzip_archive)*
cpio_gzip_archive := GZIP(cpio_archive)
cpio_archive := cpio_file* + (
cpio_file := ALGN(4) + cpio_header + filename + "\0" + ALGN(4) + data
cpio_trailer := ALGN(4) + cpio_header + "TRAILER!!!\0" + ALGN(4)
下面就简单讲一下如何生成:
usr/gen_init_cpio.c在编译后生成程序gen_init_cpio(这是一个host program),它在主机上被执行,并产生一个cpio_archive(结果输出到stdout, 大家执行一下usr/gen_init_cpio就知道了)。2.6.x内核的KBUILD在调用它时用下列命令(用make V=1可以看到)将其结果重定向到文件initramfs_data.cpio:
./usr/gen_init_cpio > usr/initramfs_data.cpio
这样就生成一个未压缩的,"newc"格式的CPIO archive。然后,KBUILD再用gzip将initramfs_data.cpio压缩生成initramfs_data.cpio.gz文件。最后,KBUILD将initramfs_data.cpio.gz链接进内核中。
生成了文件就要使用了,回到../linux2.6.21.1/init/initramfs.c。至于这里为什么要解压两次,我就不太清楚了,感觉一次就能搞完了。
static int __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) {
#ifdef CONFIG_BLK_DEV_RAM
int fd;
printk(KERN_INFO "checking if image is initramfs...");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 1);
if (!err) {
unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0);
free_initrd();
return 0;
}