前言
本章节主要讨论如何实现内存页面的初始化?
1.如何实现内存页结构初始化?
内存页结构的初始化,其实就是初始化 msadsc_t 结构对应的变量。因为一个 msadsc_t 结构体变量代表一个物理内存页,而物理内存由多个页组成,所以最终会形成一个 msadsc_t 结构体数组。
void write_one_msadsc(msadsc_t *msap, u64_t phyadr)
{
//对msadsc_t结构做基本的初始化,比如链表、锁、标志位
msadsc_t_init(msap);
//这是把一个64位的变量地址转换成phyadrflgs_t*类型方便取得其中的地址位段
phyadrflgs_t *tmp = (phyadrflgs_t *)(&phyadr);
//把页的物理地址写入到msadsc_t结构中
msap->md_phyadrs.paf_padrs = tmp->paf_padrs;
return;
}
u64_t init_msadsc_core(machbstart_t *mbsp, msadsc_t *msavstart, u64_t msanr)
{
//获取phymmarge_t结构数组开始地址
phymmarge_t *pmagep = (phymmarge_t *)phyadr_to_viradr((adr_t)mbsp->mb_e820expadr);
u64_t mdindx = 0;
//扫描phymmarge_t结构数组
for (u64_t i = 0; i < mbsp->mb_e820exnr; i++)
{
//判断phymmarge_t结构的类型是不是可用内存
if (PMR_T_OSAPUSERRAM == pmagep[i].pmr_type)
{
//遍历phymmarge_t结构的地址区间
for (u64_t start = pmagep[i].pmr_saddr; start < pmagep[i].pmr_end; start += 4096)
{
//每次加上4KB-1比较是否小于等于phymmarge_t结构的结束地址
if ((start + 4096 - 1) <= pmagep[i].pmr_end)
{
//与当前地址为参数写入第mdindx个msadsc结构
write_one_msadsc(&msavstart[mdindx], start);
mdindx++;
}
}
}
}
return mdindx;
}
void init_msadsc()
{
u64_t coremdnr = 0, msadscnr = 0;
msadsc_t *msadscvp = NULL;
machbstart_t *mbsp = &kmachbsp;
//计算msadsc_t结构数组的开始地址和数组元素个数
if (ret_msadsc_vadrandsz(mbsp, &msadscvp, &msadscnr) == FALSE)
{
system_error("init_msadsc ret_msadsc_vadrandsz err\n");
}
//开始真正初始化msadsc_t结构数组
coremdnr = init_msadsc_core(mbsp, msadscvp, msadscnr);
if (coremdnr != msadscnr)
{
system_error("init_msadsc init_msadsc_core err\n");
}
//将msadsc_t结构数组的开始的物理地址写入kmachbsp结构中
mbsp->mb_memmappadr = viradr_to_phyadr((adr_t)msadscvp);
//将msadsc_t结构数组的元素个数写入kmachbsp结构中
mbsp->mb_memmapnr = coremdnr;
//将msadsc_t结构数组的大小写入kmachbsp结构中
mbsp->mb_memmapsz = coremdnr * sizeof(msadsc_t);
//计算下一个空闲内存的开始地址
mbsp->mb_nextwtpadr = PAGE_ALIGN(mbsp->mb_memmappadr + mbsp->mb_memmapsz);
return;
}
这会让我们的工作变得简单,我们只需要找一个内存地址,作为 msadsc_t 结构体数组的开始地址,当然这个内存地址必须是可用的,而且之后内存空间足以存放 msadsc_t 结构体数组。
然后,我们要扫描 phymmarge_t 结构体数组中的信息,只要它的类型是可用内存,就建立一个 msadsc_t 结构体,并把其中的开始地址作为第一个页面地址。
接着,要给这个开始地址加上 0x1000,如此循环,直到其结束地址。
当这个 phymmarge_t 结构体的地址区间,它对应的所有 msadsc_t 结构体都建立完成之后,就开始下一个 phymmarge_t 结构体。
依次类推,最后,我们就能建好所有可用物理内存页面对应的 msadsc_t 结构体。
2.处理初始化内存占用问题
我们初始化了内存页和内存区对应的数据结构,已经可以组织好内存页面了。现在看似已经万事俱备了,其实这有个重大的问题,你知道是什么吗?我给你分析一下。
目前我们的内存中已经有很多数据了,有 Cosmos 内核本身的执行文件,有字体文件,有 MMU 页表,有打包的内核映像文件,还有刚刚建立的内存页和内存区的数据结构,这些数据都要占用实际的物理内存。
再回头看看我们建立内存页结构 msadsc_t,所有的都是空闲状态,而它们每一个都表示一个实际的物理内存页。
假如在这种情况下,对调用内存分配接口进行内存分配,它按既定的分配算法查找空闲的 msadsc_t 结构,那它一定会找到内核占用的内存页所对应的 msadsc_t 结构,并把这个内存页分配出去,然后得到这个页面的程序对其进行改写。这样内核数据就会被覆盖,这种情况是我们绝对不能允许的。
所以,我们要把这些已经占用的内存页面所对应的 msadsc_t 结构标记出来,标记成已分配,这样内存分配算法就不会找到它们了。
要解决这个问题,我们只要给出被占用内存的起始地址和结束地址,然后从起始地址开始查找对应的 msadsc_t 结构,再把它标记为已经分配,最后直到查找到结束地址为止。
//搜索一段内存地址空间所对应的msadsc_t结构
u64_t search_segment_occupymsadsc(msadsc_t *msastart, u64_t msanr, u64_t ocpystat, u64_t ocpyend)
{
u64_t mphyadr = 0, fsmsnr = 0;
msadsc_t *fstatmp = NULL;