在理解了Unikernel的基本启动流程之后,不难发现,并没有全局内存分配器,也就是说,虽然此时页表中有一段free_memory空间映射(所谓的堆空间),但没有一个管理这段虚拟内存的分配器,所以动态内存分配操作都是不支持的。
axalloc 是什么
axalloc就是要提供这样一个全局内存分配器,来管理这段虚拟地址空间,以支持标准库和内核自身对于动态内存分配的需要。注意,不是管理对应的物理内存。物理内存分配,应该是访问了某个未形成映射的虚拟地址后,产生缺页中断,再由额外的PMM(物理内存管理器)来支持的,这个可能是一个硬件支持。
但是实际上,此时实现的虚拟地址和物理地址的映射就是一个固定的偏移,无论是一开始的巨页映射,还是后面实现的paging组件实现,都是直接把这个固定映射早早地形成在页表里了,虚拟地址和物理地址就是一一对应的了,这个虚拟地址空间和真实地址空间一样大,没有什么动态映射可言,那在这个角度看,这里的axalloc其实也管理着物理内存(不需要一个额外的物理内存管理器了)
具体实现
要想使用RUST标准库Collection(与架构无关),就要有一个实现GlobalAlloc trait的类的全局静态对象,并把它通过#[global_allocator]
来注册为默认全局分配器。
主要就是要实现如下两个函数,支持按字节粒度分配一段连续且空闲的虚拟地址并支持回收。Layout指出大小和对齐要求。
1 | unsafe fn alloc(&self, layout: Layout) -> *mut u8; |
但底层的分配器肯定是按页的,因为使用了页表,所以除了字节分配器byteAllocator,还要有pageAllocator,且实际上,byteAllocator也是基于pageAllocator做的,关系如下:
此处的全部内存其实指的就是【物理内存中_ekernel标识往上的那一部分物理地址做了偏移映射之后的一段虚拟地址空间】,大概只有128M,不像真实的虚拟地址空间那么大那么灵活,实现方式很朴素,但确实可以实现功能。
综上,设计了GlobalAllocator,其实现了GlobalAlloc trait,并实例化了唯一的一个全局静态对象GLOBAL_ALLCATOR,并提供接口供内核使用。
byteAllcator和pageAllocator都可以配置各种各样的实际分配策略,此处不做详述。