0%

rCore源码阅读-Bootloader

Bootloader的主要功能是,通过适当设置搭建起OS的运行环境,并将OS内核代码从镜像中拷贝到内存中,并跳转到内核入口点。

rBoot

目前的x86_64平台上的bootloader基于uefi-rs开发,这里是一个简单的使用教程,教我们如何用这个库进行简单的物理内存探测。

那么我们来看rBoot里面是怎么做的。入口点在main.rs中的efi_main函数中,这个函数的接口是uefi-rs约定的。

接下来进行了如下工作:

  1. 初始化SystemTable<Boot>类型的变量st,并利用服务st.services()(事实上后面的操作也基本上都用到了这个服务,记为bs)从位置固定的配置文件rboot.conf中读取,并load到buf中,并通过config子模块进行解析,将结果存储在config变量中。

    config.rs中可以看出rboot.conf中主要配置三个参数:

    • physical_memory_offset: u64
    • kernel_path: &'a str'
    • resolution: Option<(usize, usize)>

    他们分别是内核栈放置的地址,内核可执行文件(elf格式)的存储路径,以及图形输出的分辨率。

  2. 调用init_graphic函数进行图形输出初始化。利用bs服务获取gop,接着尝试使用filter在gop.modes()中找到相同resolution的mode。如果有的话,调用gop.set_mode(&mode)进行模式设置。

    接下来只需调用gop提供的接口,返回GraphicInfo,内含参数:

    • mode,即图形输出模式;
    • fb_addr; fb_size,即framebuffer的地址及大小

    这将被保存到变量graphic_info中。

  3. st.config_table()中寻找guid域为uefi::table::cfg::ACPI2_GUID的一个entry,并将其address域存储到变量acpi2_addr中,作为acpi2地址,不过目前我还不知道acpi2是干啥的。好像功能挺多的,能够控制电源和一些外设,等用到的时候再看具体用到了什么功能。

  4. 读入内核可执行文件,并进行elf解析,将解析结果存储在变量elf中。并找到内核入口点地址ENTRY以及从之前的设置中找到的物理内存偏移量PHYSICAL_MEMORY_OFFSET

    注意我们在内核中也只能传入虚拟地址,经过硬件的地址转换机制转化为物理地址,而不能直接访问物理地址。为了访问所有的物理内存,我们要将这部分物理内存加上一个偏移量映射到虚拟内存空间中,也就是说,为了访问物理地址0,在内核中我们要访问虚拟地址PHYSICAL_MEMORY_OFFSET。这是一种最简单的映射机制,使用封装好的OffsetPageTable实现,这里介绍了更多映射机制。

    接下来通过st获取当前memory map的字节数max_mmap_size,随后使用Box::leak分配一个数组(这里不知道为啥用leak)mmap_storage,随后将当前的memory map存放在mmap_storage中,同时得到memory map的迭代器mmap_iter

    接下来,遍历迭代器,找到memory map中的最大物理地址。

    调用current_page_table()函数得到当前页表,并存储在变量page_table中。其原理是使用从cr3寄存器中读出当前页表地址,得到一个&mut PageTable,使用这个构造一个偏移量为0OffsetPageTable

    下面可以看到,写了一个实现了FrameAllocator接口的UEFIFrameAllocator,并使用bsallocate_pages()实现接口规定的allocate_frame()函数。

    接下来,调用page_table.rs中实现的map_elf,map_physical_memory分别实现将rCore内核载入内存,以及将物理空间[0, max_phys_addr)映射到向上平移physical_memory_offset后的虚拟内存。

    这里面的核心就是map_to函数,将一个虚拟页page映射到物理页帧frame,还要带上一个page_table_flags,还需要传入一个frame_allocator完成物理页帧分配。注意page_table需要实现Mapper<>接口;frame_allocator需要实现FrameAllocator接口。具体是怎么回事目前还没有理解。

  5. 调用start_aps函数开启多核。

  6. 退出boot service。

  7. 将配置信息保存到bootinfo,并通过内联汇编实现参数的传递,跳转到内核入口。