linux使用RAM_DISK根文件系统基本过程

本文介绍在 Linux 系统中使用 RAM Disk 作为根文件系统的基本过程,包括 U-Boot 阶段的加载与配置、内核对 RAM Disk 的处理及挂载过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

linux使用RAM_DISK根文件系统基本过程


         以下只是讲述了内存文件系统从uboot到内核、最终挂载到根目录的一个基本流程,至于每个环节的细节,尚未深究,有兴趣的可以再深入学习一下。

 

一、            内存文件系统

1、 分类

ramdisk、ramfs、tmpfs

 

2、 ramdik

先格式化(格式化成相应的文件系统)再挂载,大小固定,之后不能随便更改。基于虚拟在内存中的其他文件系统,例如:

mke2fs/dev/ram0; mount /dev/ram0 /mnt/mtd

 

3、 ramfs

内存文件系统,处于虚拟文件系统(VFS)层,无需格式化,可以创建多个,只要内存足够,在创建时可以执行最大能使用的内存大小,针对物理内存。例如:

mount –tramfs /dev/mem /mnt/mtd or

mount –tramfs none /mnt/mtd –o maxsize=2000(kbyte为单位)

 

4、 tmpfs

虚拟内存文件系统,可以使用物理内存,也可以使用交换分区

 

二、            基本原理

         Flash的mtdblock1存放根文件系统的相关文件,包括uImage、cramfs.initrd.img等。uboot启动后会将uImage和cramfs.initrd.img加载到内存的相应地址,然后uboot引导内核启动。内核启动根据cramfs.initrd.img存放地址将其挂载到根目录下,因此我们进入shell所看见的根目录就是对应的内存文件系统内容。挂载成功之后,就可以将mtdblock1的其他目录文件挂载到内存文件系统下,进行文件方式访问、操作等。

 

三、            Uboot阶段加载initrd.img

 

1、加载uImage和initrd.img文件到系统内存

         在加载uImage的地方添加加载内存根文件系统cramfs.initrd.img文件,两个文件的地址不能重叠,另外不要存放在内核启动的地址0x80008000。这里分别存放在0x82000000和0x81000000的地方,代码如下:

int do_fsload(cmd_tbl_t*cmdtp, int flag, int argc, char *argv[])

{

         …

size = squashfs_fload((char *) offset, part,filename);   ^M

#ifdefCONFIG_HI3531_V100

size = squashfs_fload((char *)CONFIG_INITRDRAMFS_LOAD_ADDR, part, "/boot/cramfs.initrd.img");

#endif

         …

}

 

2、修改bootargs为内存根文件系统

#define BOOTARGS_3531_V100 "mem=245M,console=ttyAMA0,115200 rootfstype=squashfs "  "root=/dev/ram"

 

3、启动内核bootm

         启动内核使用如下命令:bootm 0x82000000 0x81000000。跟随两个参数,第一个为uImage加载的起始地址,第二个参数为cramfs.initrd.img的起始地址,两个参数顺序不能反,这个bootm命令实现决定,具体分析见后面。

(1)启动内核do_bootm源码解析

intdo_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

         ….

if (bootm_start(cmdtp,flag, argc, argv)) //获取uImage和initrd.img的镜像文件头

        return 1;

         …

         ret = bootm_load_os(images.os,&load_end, 1); //解压uImage,此处不讲解

         …

         boot_fn = boot_os[images.os.os];= do_bootm_linux //调用内核最终启动函数,见后面讲解

         ….

boot_fn(0, argc, argv, &images); //此处成功的情况下不会执行reset

do_reset (cmdtp, flag, argc, argv);

}

(2)获取镜像头bootm_start源码解析

staticint bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

         memset ((void *)&images, 0, sizeof(images));  //对images头文件进行初始化

         …

         os_hdr = boot_get_kernel (cmdtp, flag,argc, argv,&images, &images.os.image_start, &images.os.image_len);     //获取内核镜像头,主要是获取起始地址和内核数据长度

….

switch(genimg_get_format (os_hdr)) { //获取image的相关信息

    case IMAGE_FORMAT_LEGACY:

             images.os.type= image_get_type (os_hdr);

        images.os.comp = image_get_comp(os_hdr);

        images.os.os = image_get_os (os_hdr);

        images.os.end = image_get_image_end(os_hdr);

        images.os.load = image_get_load(os_hdr);

     break;

         …

ret = boot_get_ramdisk (argc, argv, &images,IH_INITRD_ARCH, &images.rd_start, &images.rd_end); //获取initrd的起始结束地址        

….

}

(3)启动内核do_bootm_linux

         将initrd的相关参数加入内核参数列表传递给内核。

int do_bootm_linux(intflag, int argc, char *argv[], bootm_headers_t *images)

{

         …

#ifdefined (CONFIG_SETUP_MEMORY_TAGS) || \

    defined (CONFIG_CMDLINE_TAG) || \

    defined (CONFIG_INITRD_TAG) || \

    defined (CONFIG_SERIAL_TAG) || \

    defined (CONFIG_REVISION_TAG)

setup_start_tag (bd); //初始化boot参数bd->bi_boot_params;

#ifdefCONFIG_INITRD_TAG //将initrd的相关参数加入参数列表,具体实现见后面

    if (images->rd_start &&images->rd_end)

        setup_initrd_tag(bd, images->rd_start, images->rd_end);

#endif

         …

    setup_end_tag (bd);

#endif

         …

         theKernel (0, machid,bd->bi_boot_params); //进入kernel阶段

}

 

static void setup_initrd_tag (bd_t *bd, ulong initrd_start, ulonginitrd_end)

{  

    params->hdr.tag = ATAG_INITRD2; //内核使用initrd2相关函数解析该tag,见内核阶段

    params->hdr.size = tag_size(tag_initrd);

   

    params->u.initrd.start = initrd_start;//initrd的起始地址

    params->u.initrd.size = initrd_end -initrd_start; //initrd的长度

   

    params = tag_next (params);

}

 

4、kernel各个镜像地址(hi3536为例)

Images.os的各个信息:

start:0x42000000

end:0x42252000

image_start:0x42000040

image_len:0x251fc0,

load:0x40008000

load_end= load + image_len

comp:0x0: IH_COMP_NONE

type:0x2 :IH_TYPE_KERNEL

os:0x5 :IH_OS_LINUX

imagesep:0x40008000

rd_start:0x41000040

rd_end:0x4115c040

 

*p_virtaddr++= 0xe51ff004; /* ldr  pc, [pc, #-4] */ 该地址为功能PC对应的指令代码

*p_virtaddr++= jump_addr;  /* pc jump phy address */

 

四、            Kernel阶段处理initrd.img

 

1、获取initrd.img的相关参数

         进入内核后,获取uboot传递过来的initrd.img参数的调用流程如下:

start_kernel()—> setup_arch() —> setup_machine_tags(),该函数会对uboot传递进来的所有tags进行解析:

if(tags->hdr.tag == ATAG_CORE)

{

if (meminfo.nr_banks != 0)

            squash_mem_tags(tags);

        save_atags(tags);

        parse_tags(tags);

}

其中函数parse_tag_initrd2就是解析内存文件系统的参数:获取内存文件系统的内存地址和长度,这个可以和前面的uboot对应起来:

staticint __init parse_tag_initrd2(const struct tag *tag)

{  

    //dump_stack();

    phys_initrd_start = tag->u.initrd.start;

    phys_initrd_size = tag->u.initrd.size;

    return 0;

}

 

2、内存文件系统占用的内存处理

         调用流程如下:start_kernel()—> setup_arch() —> arm_memblock_init(),该函数会对上面获取到的内存文件系统占用内存进行处理:

#ifdefCONFIG_BLK_DEV_INITRD

    if (phys_initrd_size &&!memblock_is_region_memory(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)) {

        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);

        initrd_end= initrd_start + phys_initrd_size;

    }

#endif

其中initrd_start和initrd_end为ramfs占用的虚拟内存起始和结束地址。

 

3、各种init调用顺序

         在讲内存文件系统挂载之前,先讲一下各种initcall。arch/arm/kernel/vmlinux.lds文件规定了各个initcall的存放段:

  __tagtable_begin = .;

   *(.taglist.init)

  __tagtable_end = .;

  __pv_table_begin = .;

   *(.pv_table)

  __pv_table_end = .;

  . = ALIGN(16); __setup_start = .;*(.init.setup) __setup_end = .;

  __initcall_start= .; *(.initcallearly.init) __early_initcall_end= .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init)*(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init)*(.initcall3s.init) *(.initcall4.init) * (.initcall4s.init) *(.initcall5.init)*(.initcall5s.init) *(.initcallrootfs.init)*(.initcall6.init) *(.initcall6s.init) *(. initcall7.init) *(.initcall7s.init) __initcall_end = .;

  __con_initcall_start = .;*(.con_initcall.init) __con_initcall_end = .;

  __security_initcall_start = .; *(.security_initcall.init)__security_initcall_end = .;

  . = ALIGN(4); __initramfs_start = .;*(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info)

  __init_begin = _stext;

  *(.init.data) *(.cpuinit.data)*(.meminit.data) *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata) . =ALIGN(32);       __dtb_start = .;*(.dtb.init.rodata) __dtb_end = .;

 

 }

上面列出了tags、setup、initcall所占的各个字段,同时说明了各个初始化顺序(即优先级),(.initcallrootfs.init)为根文件系统的初始化优先级,比较靠后。

 

4、内核对内存文件系统初始化

         调用流程:start_kernel()—> rest_init () ,该函数创建kernel_init,该线程对上面所有注册的initcall按照顺序进行初始化。

         下面对线程kernel_init相关的函数进行说明:

(1)initcallearly初始化

         由函数do_pre_smp_initcalls实现,具体如下:

staticvoid __init do_pre_smp_initcalls(void)

{  

    initcall_t *fn;

    for (fn = __initcall_start;fn < __early_initcall_end; fn++)

        do_one_initcall(*fn);

}

上述蓝色地址见上面vmlinux.lds

 

(2)其他initcall初始化

         do_basic_setup()—>do_initcalls()(do_basic_setup函数里还完成驱动等的初始化),该函数具体实现如下:

staticvoid __init do_initcalls(void)

{

    initcall_t *fn;

    for (fn = __early_initcall_end;fn < __initcall_end; fn++)

        do_one_initcall(*fn);

}

这个函数会调用initcallroofs根文件系统的初始化函数populate_rootfs()

 

(3)初始化根文件系统populate_rootfs()(细节未深入)

         该函数先判断initrd_start值为非0,然后将内存文件系统数据写入文件/initrd.image:

fd =sys_open((const char __user __force *) "/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();

        }

5、内存文件系统挂载/目录

         kernel_init()—>prepare_namespace(),该函数完成根目录挂载:

void__init prepare_namespace(void)

{

         ….

         if (initrd_load())

        goto out;

         ….

    mount_root();

out:

    devtmpfs_mount("dev");

    sys_mount(".", "/",NULL, MS_MOVE, NULL);

    sys_chroot((const char __user __force*)".");

}

上面代码末尾就是挂载文件系统到根目录,initrd_load()就是加载内存文件系统。

 

(2)initrd_load()实现

int__init initrd_load(void)

{      

    printk("*** mount_initrd:=%d\n",mount_initrd);

    if (mount_initrd) {

        create_dev("/dev/ram",Root_RAM0);

        if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0){

           sys_unlink("/initrd.image");

            handle_initrd();

            return 1;

        }

    }

    sys_unlink("/initrd.image");

    return 0;

}

将内存文件系统数据加载到/dev/ram以便后续挂在到根目录下。

 

五、linux使用RAM_DISK根文件系统使用方法

         注下面以hi3531+3532平台为例,进行简单讲解,如有遗漏请帮忙补充

 

1、uboot修改

(1)bootargs修改

#defineBOOTARGS_3531_V100 "mem=135M@0x80000000 mem=135M@0xc0000000,console=ttyAMA0,115200 rootfstype=squashfs"   \

                        "root=/dev/ram"

rootfstype以打包时使用的压缩方式为准;使用内存文件建系统,所以root不再是flash的mtdblock分区而是内存设备

(2)fsload修改

         加载/boot/uImage之后,还需要将cramfs.initrd.img加载到指定的位置,注意uImage和cramfs加载空间不能重叠,且不要将cramfs加载到uImage启动地址。

if(squashfs_check(part))

{

size = squashfs_fload((char *) offset, part,filename);  //加载uImage

printf("### %s loading '%s' to 0x%lx\n",fsname, "/boot/cramfs.initrd.img",\

                            offset-0x1000000);

size = squashfs_fload((char *)(offset -0x1000000), part, "/boot/cramfs.initrd.img");  //加载cramfs.initrd.img

}

(3)bootm修改

         修改bootm—> bootm 0x82000000 0x8100000:当没有参数时bootm只解析uImage;第一个参数为内核加载的地址,第二参数为cramfs加载的地址,顺序不能反。

 

 

2、kernel修改

         内核配置添加对initrd的支持:

Generalsetup  --->

[*] Initial RAM filesystem and RAMdisk (initramfs/initrd) support

()    Initramfs source file(s)

[*]   Support initial ramdisks compressed usinggzip

[ ]   Support initial ramdisks compressed usingbzip2

[*]   Support initial ramdisks compressed usingLZMA

[ ]   Support initial ramdisks compressed using XZ

[ ]   Support initial ramdisks compressed usingLZO

 

DeviceDrivers  --->

         [*] Block devices  --->

<*>RAM block devicesupport                                                             

(16)    Default number of RAM disks                                                        

(4096)  Default RAM disk size (kbytes)

 

 

3、打包环境修改

(1)打包目录修改

         新增一个ramfs目录用于生成ramfs.initrd.img,该目录类似原来的romfs但是只保留了根文件系统所需的基本要素(节省内存),而占大内存和flash的Challenge.7z、lib.7z、usr目录等内容仍然保留在romfs目录。

         调整后romfs值保留了几个目录,如下所示:

/boot:

cramfs.initrd.img  uImage : 主从共用的内存文件系统、主片kernel

/slave:

u-boot.bin  uImage :如果没有从片,该目录可以节省;从片的uboot/kernel

/usr:

/bin  /data /etc  /lib  /sbin  :用户所需:challenge.7z lib.7z 图片 字库等内容

 

(2)打包工具修改

$TOOL_DIR/mkimage-A arm -O linux -T ramdisk -a 0x0  \

-e0x83000000 -n "initrd in cramfs" -d $TMP_DIR/$TMP_FILE_RAM  $TARGET_DIR/cramfs.initrd.img

cp$TARGET_DIR/cramfs.initrd.img $SRC_DIR/boot/

 

$TOOL_DIR/mkimage-A arm -O linux -T kernel -C gzip -a A0050000 \

-e0xA0d70000 -n 3531romfs -d $TMP_DIR/$TMP_FILE $TARGET_DIR/$TARGET_FILE

 

在生成romfs-x.cramfs.img前,要先生成cramfs.initrd.img,并拷贝到romfs的boot目录,然后再生成最终额romfs-x.cramfs.img。

 

以上就是内存文件系统使用的基本过程,实际情况有些偏差,仅供参考。

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值