操作系统实践之路——六、内存(2.如何实现内存页面初始化?)


前言
本章节主要讨论如何实现内存页面的初始化?

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;
    for (u64_t mnr = 0; mnr < msanr; mnr++)
    {
        if ((msastart[mnr].md_phyadrs.paf_padrs << PSHRSIZE) == ocpystat)
        {
            //找出开始地址对应的第一个msadsc_t结构,就跳转到step1
            fstatmp = &msastart[mnr];
            goto step1;
        }
    }
step1:
    fsmsnr = 0;
    if (NULL == fstatmp)
    {
        return 0;
    }
    for (u64_t tmpadr = ocpystat; tmpadr < ocpyend; tmpadr += PAGESIZE, fsmsnr++)
    {
        //从开始地址对应的第一个msadsc_t结构开始设置,直到结束地址对应的最后一个masdsc_t结构
        mphyadr = fstatmp[fsmsnr].md_phyadrs.paf_padrs << PSHRSIZE;
        if (mphyadr != tmpadr)
        {
            return 0;
        }
        if (MF_MOCTY_FREE != fstatmp[fsmsnr].md_indxflgs.mf_mocty ||
            0 != fstatmp[fsmsnr].md_indxflgs.mf_uindx ||
            PAF_NO_ALLOC != fstatmp[fsmsnr].md_phyadrs.paf_alloc)
        {
            return 0;
        }
        //设置msadsc_t结构为已经分配,已经分配给内核
        fstatmp[fsmsnr].md_indxflgs.mf_mocty = MF_MOCTY_KRNL;
        fstatmp[fsmsnr].md_indxflgs.mf_uindx++;
        fstatmp[fsmsnr].md_phyadrs.paf_alloc = PAF_ALLOC;
    }
    //进行一些数据的正确性检查
    u64_t ocpysz = ocpyend - ocpystat;
    if ((ocpysz & 0xfff) != 0)
    {
        if (((ocpysz >> PSHRSIZE) + 1) != fsmsnr)
        {
            return 0;
        }
        return fsmsnr;
    }
    if ((ocpysz >> PSHRSIZE) != fsmsnr)
    {
        return 0;
    }
    return fsmsnr;
}
bool_t search_krloccupymsadsc_core(machbstart_t *mbsp)
{
    u64_t retschmnr = 0;
    msadsc_t *msadstat = (msadsc_t *)phyadr_to_viradr((adr_t)mbsp->mb_memmappadr);
    u64_t msanr = mbsp->mb_memmapnr;
    //搜索BIOS中断表占用的内存页所对应msadsc_t结构
    retschmnr = search_segment_occupymsadsc(msadstat, msanr, 0, 0x1000);
    if (0 == retschmnr)
    {
        return FALSE;
    }
    //搜索内核栈占用的内存页所对应msadsc_t结构
    retschmnr = search_segment_occupymsadsc(msadstat, msanr, mbsp->mb_krlinitstack & (~(0xfffUL)), mbsp->mb_krlinitstack);
    if (0 == retschmnr)
    {
        return FALSE;
    }
    //搜索内核占用的内存页所对应msadsc_t结构
    retschmnr = search_segment_occupymsadsc(msadstat, msanr, mbsp->mb_krlimgpadr, mbsp->mb_nextwtpadr);
    if (0 == retschmnr)
    {
        return FALSE;
    }
    //搜索内核映像文件占用的内存页所对应msadsc_t结构
    retschmnr = search_segment_occupymsadsc(msadstat, msanr, mbsp->mb_imgpadr, mbsp->mb_imgpadr + mbsp->mb_imgsz);
    if (0 == retschmnr)
    {
        return FALSE;
    }
    return TRUE;
}
//初始化搜索内核占用的内存页面
void init_search_krloccupymm(machbstart_t *mbsp)
{
    //实际初始化搜索内核占用的内存页面
    if (search_krloccupymsadsc_core(mbsp) == FALSE)
    {
        system_error("search_krloccupymsadsc_core fail\n");
    }
    return;
}

3.如何让内存页和内存区联系起来

整体上可以分成两步:

1.确定内存页属于哪个区,即标定一系列 msadsc_t 结构是属于哪个 memarea_t 结构的。

​ 我们先来做第一件事,这件事比较简单,我们只要遍历每个 memarea_t 结构,遍历过程中根据特定的 memarea_t 结构,然后去扫描整个 msadsc_t 结构数组,最后依次对比 msadsc_t 的物理地址,看它是否落在 memarea_t 结构的地址区间中。

​ 如果是,就把这个 memarea_t 结构的类型值写入 msadsc_t 结构中,这样就一个一个打上了标签,遍历 memarea_t 结构结束之后,每个 msadsc_t 结构就只归属于某一个 memarea_t 结构了。

2.把特定的内存页合并,然后挂载到特定的内存区下的 memdivmer_t 结构中的 dm_mdmlielst 数组中。

​ 当确定内存页属于哪个区之后,就来到了第二次遍历 memarea_t 结构,合并其中的 msadsc_t 结构,并把它们挂载到其中的 memdivmer_t 结构下的 dm_mdmlielst 数组中。

//给msadsc_t结构打上标签
uint_t merlove_setallmarflgs_onmemarea(memarea_t *mareap, msadsc_t *mstat, uint_t msanr)
{
    u32_t muindx = 0;
    msadflgs_t *mdfp = NULL;
    //获取内存区类型
    switch (mareap->ma_type){
    case MA_TYPE_HWAD:
        muindx = MF_MARTY_HWD << 5;//硬件区标签
        mdfp = (msadflgs_t *)(&muindx);
        break;
    case MA_TYPE_KRNL:
        muindx = MF_MARTY_KRL << 5;//内核区标签
        mdfp = (msadflgs_t *)(&muindx);
        break;
    case MA_TYPE_PROC:
        muindx = MF_MARTY_PRC << 5;//应用区标签
        mdfp = (msadflgs_t *)(&muindx);
        break;
    }
    u64_t phyadr = 0;
    uint_t retnr = 0;
    //扫描所有的msadsc_t结构
    for (uint_t mix = 0; mix < msanr; mix++)
    {
        if (MF_MARTY_INIT == mstat[mix].md_indxflgs.mf_marty)
        {    //获取msadsc_t结构对应的地址
            phyadr = mstat[mix].md_phyadrs.paf_padrs << PSHRSIZE;
            //和内存区的地址区间比较 
            if (phyadr >= mareap->ma_logicstart && ((phyadr + PAGESIZE) - 1) <= mareap->ma_logicend)
            {
                //设置msadsc_t结构的标签
                mstat[mix].md_indxflgs.mf_marty = mdfp->mf_marty;
                retnr++;
            }
        }
    }
    return retnr;
}
bool_t merlove_mem_core(machbstart_t *mbsp)
{
    //获取msadsc_t结构的首地址
    msadsc_t *mstatp = (msadsc_t *)phyadr_to_viradr((adr_t)mbsp->mb_memmappadr);
    //获取msadsc_t结构的个数
    uint_t msanr = (uint_t)mbsp->mb_memmapnr, maxp = 0;
    //获取memarea_t结构的首地址
    memarea_t *marea = (memarea_t *)phyadr_to_viradr((adr_t)mbsp->mb_memznpadr);
    uint_t sretf = ~0UL, tretf = ~0UL;
    //遍历每个memarea_t结构
    for (uint_t mi = 0; mi < (uint_t)mbsp->mb_memznnr; mi++)
    {
        //针对其中一个memarea_t结构给msadsc_t结构打上标签
        sretf = merlove_setallmarflgs_onmemarea(&marea[mi], mstatp, msanr);
        if ((~0UL) == sretf)
        {
            return FALSE;
        }
    }
     //遍历每个memarea_t结构
    for (uint_t maidx = 0; maidx < (uint_t)mbsp->mb_memznnr; maidx++)
    {
        //针对其中一个memarea_t结构对msadsc_t结构进行合并
        if (merlove_mem_onmemarea(&marea[maidx], mstatp, msanr) == FALSE)
        {
            return FALSE;
        }
        maxp += marea[maidx].ma_maxpages;
    }
    return TRUE;
}
//初始化页面合并
void init_merlove_mem()
{
    if (merlove_mem_core(&kmachbsp) == FALSE)
    {
        system_error("merlove_mem_core fail\n");
    }
    return;
}


这个操作就稍微有点复杂了。

第一,它要保证其中所有的 msadsc_t 结构挂载到 dm_mdmlielst 数组中合适的 bafhlst_t 结构中。

第二,它要保证多个 msadsc_t 结构有最大的连续性。

举个例子,比如一个内存区中有 12 个页面,其中 10 个页面是连续的地址为 0~0x9000,还有两个页面其中一个地址为 0xb000,另一个地址为 0xe000。这样的情况下,需要多个页面保持最大的连续性,还有在 m_mdmlielst 数组中找到合适的 bafhlst_t 结构。那么:0~0x7000 这 8 个页面就要挂载到 m_mdmlielst 数组中第 3 个 bafhlst_t 结构中;0x8000~0x9000 这 2 个页面要挂载到 m_mdmlielst 数组中第 1 个 bafhlst_t 结构中,而 0xb000 和 0xe000 这 2 个页面都要挂载到 m_mdmlielst 数组中第 0 个 bafhlst_t 结构中。

bool_t continumsadsc_add_bafhlst(memarea_t *mareap, bafhlst_t *bafhp, msadsc_t *fstat, msadsc_t *fend, uint_t fmnr)
{
    fstat->md_indxflgs.mf_olkty = MF_OLKTY_ODER;
    //开始的msadsc_t结构指向最后的msadsc_t结构 
    fstat->md_odlink = fend;
    fend->md_indxflgs.mf_olkty = MF_OLKTY_BAFH;
    //最后的msadsc_t结构指向它属于的bafhlst_t结构 
    fend->md_odlink = bafhp;
    //把多个地址连续的msadsc_t结构的的开始的那个msadsc_t结构挂载到bafhlst_t结构的af_frelst中
    list_add(&fstat->md_list, &bafhp->af_frelst);
    //更新bafhlst_t的统计数据
    bafhp->af_fobjnr++;
    bafhp->af_mobjnr++;
    //更新内存区的统计数据
    mareap->ma_maxpages += fmnr;
    mareap->ma_freepages += fmnr;
    mareap->ma_allmsadscnr += fmnr;
    return TRUE;
}
bool_t continumsadsc_mareabafh_core(memarea_t *mareap, msadsc_t **rfstat, msadsc_t **rfend, uint_t *rfmnr)
{
    uint_t retval = *rfmnr, tmpmnr = 0;
    msadsc_t *mstat = *rfstat, *mend = *rfend;
    //根据地址连续的msadsc_t结构的数量查找合适bafhlst_t结构
    bafhlst_t *bafhp = find_continumsa_inbafhlst(mareap, retval);
    //判断bafhlst_t结构状态和类型对不对
    if ((BAFH_STUS_DIVP == bafhp->af_stus || BAFH_STUS_DIVM == bafhp->af_stus) && MA_TYPE_PROC != mareap->ma_type)
    {
        //看地址连续的msadsc_t结构的数量是不是正好是bafhp->af_oderpnr
        tmpmnr = retval - bafhp->af_oderpnr;
        //根据地址连续的msadsc_t结构挂载到bafhlst_t结构中
        if (continumsadsc_add_bafhlst(mareap, bafhp, mstat, &mstat[bafhp->af_oderpnr - 1], bafhp->af_oderpnr) == FALSE)
        {
            return FALSE;
        }
        //如果地址连续的msadsc_t结构的数量正好是bafhp->af_oderpnr则完成,否则返回再次进入此函数 
        if (tmpmnr == 0)
        {
            *rfmnr = tmpmnr;
            *rfend = NULL;
            return TRUE;
        }
        //挂载bafhp->af_oderpnr地址连续的msadsc_t结构到bafhlst_t中
        *rfstat = &mstat[bafhp->af_oderpnr];
        //还剩多少个地址连续的msadsc_t结构
        *rfmnr = tmpmnr;
        return TRUE;
    }
    return FALSE;
}
bool_t merlove_continumsadsc_mareabafh(memarea_t *mareap, msadsc_t *mstat, msadsc_t *mend, uint_t mnr)
{
    uint_t mnridx = mnr;
    msadsc_t *fstat = mstat, *fend = mend;
    //如果mnridx > 0并且NULL != fend就循环调用continumsadsc_mareabafh_core函数,而mnridx和fend由这个函数控制
    for (; (mnridx > 0 && NULL != fend);)
    {
    //为一段地址连续的msadsc_t结构寻找合适m_mdmlielst数组中的bafhlst_t结构
        continumsadsc_mareabafh_core(mareap, &fstat, &fend, &mnridx)
    }
    return TRUE;
}
bool_t merlove_scan_continumsadsc(memarea_t *mareap, msadsc_t *fmstat, uint_t *fntmsanr, uint_t fmsanr,
                                         msadsc_t **retmsastatp, msadsc_t **retmsaendp, uint_t *retfmnr)
{
    u32_t muindx = 0;
    msadflgs_t *mdfp = NULL;
    msadsc_t *msastat = fmstat;
    uint_t retfindmnr = 0;
    bool_t rets = FALSE;
    uint_t tmidx = *fntmsanr;
    //从外层函数的fntmnr变量开始遍历所有msadsc_t结构
    for (; tmidx < fmsanr; tmidx++)
    {
    //一个msadsc_t结构是否属于这个内存区,是否空闲
        if (msastat[tmidx].md_indxflgs.mf_marty == mdfp->mf_marty &&
            0 == msastat[tmidx].md_indxflgs.mf_uindx &&
            MF_MOCTY_FREE == msastat[tmidx].md_indxflgs.mf_mocty &&
            PAF_NO_ALLOC == msastat[tmidx].md_phyadrs.paf_alloc)
        {
        //返回从这个msadsc_t结构开始到下一个非空闲、地址非连续的msadsc_t结构对应的msadsc_t结构索引号到retfindmnr变量中
            rets = scan_len_msadsc(&msastat[tmidx], mdfp, fmsanr, &retfindmnr);
            //下一轮开始的msadsc_t结构索引
            *fntmsanr = tmidx + retfindmnr + 1;
            //当前地址连续msadsc_t结构的开始地址
            *retmsastatp = &msastat[tmidx];
            //当前地址连续msadsc_t结构的结束地址
            *retmsaendp = &msastat[tmidx + retfindmnr];
            //当前有多少个地址连续msadsc_t结构
            *retfmnr = retfindmnr + 1;
            return TRUE;
        }
    }
    return FALSE;
}
bool_t merlove_mem_onmemarea(memarea_t *mareap, msadsc_t *mstat, uint_t msanr)
{
    msadsc_t *retstatmsap = NULL, *retendmsap = NULL, *fntmsap = mstat;
    uint_t retfindmnr = 0;
    uint_t fntmnr = 0;
    bool_t retscan = FALSE;
    
    for (; fntmnr < msanr;)
    {
        //获取最多且地址连续的msadsc_t结构体的开始、结束地址、一共多少个msadsc_t结构体,下一次循环的fntmnr
        retscan = merlove_scan_continumsadsc(mareap, fntmsap, &fntmnr, msanr, &retstatmsap, &retendmsap, &retfindmnr);
        if (NULL != retstatmsap && NULL != retendmsap)
        {
        //把一组连续的msadsc_t结构体挂载到合适的m_mdmlielst数组中的bafhlst_t结构中
        merlove_continumsadsc_mareabafh(mareap, retstatmsap, retendmsap, retfindmnr)
        }
    }
    return TRUE;
}

​ 从上述代码可以看出,遍历每个内存区,然后针对其中每一个内存区进行 msadsc_t 结构的合并操作,完成这个操作的是 merlove_mem_onmemarea

​ 上述代码中,整体上分为两步。

​ 第一步,通过 merlove_scan_continumsadsc 函数,返回最多且地址连续的 msadsc_t 结构体的开始、结束地址、一共多少个 msadsc_t 结构体,下一轮开始的 msadsc_t 结构体的索引号。

​ 第二步,根据第一步获取的信息调用 merlove_continumsadsc_mareabafh 函数,把第一步返回那一组连续的 msadsc_t 结构体,挂载到合适的 m_mdmlielst 数组中的 bafhlst_t 结构中。详细的逻辑已经在注释中说明。

4.初始化代码汇总

​ 根据前面内存管理数据结构的关系,很显然,它们的调用次序很重要,谁先谁后都有严格的规定,这关乎内存管理初始化的成败。 所以,现在我们就在先前的 init_memmgr 函数中去调用它们,代码如下所示:

void init_memmgr()
{
    //初始化内存页结构
    init_msadsc();
    //初始化内存区结构
    init_memarea();
    //处理内存占用
    init_search_krloccupymm(&kmachbsp);
    //合并内存页到内存区中
    init_merlove_mem();
    init_memmgrob();
    return;
}

流程梳理

整理了流程:
init_hal->init_halmm->init_memmgr
//每个页对应一个msadsc_t 结构体,循环填充msadsc_t 结构体数组
->init_msadsc
//初始化三类memarea_t,硬件区、内核区、用户区
->init_memarea
//对已使用的页打上标记,包括:BIOS中断表、内核栈、内核、内核映像
->init_search_krloccupymm(&kmachbsp);
//将页面按地址范围,分配给内存区
//然后按顺序依次查找最长连续的页面,根据连续页面的长度,
//将这些页面的msadsc_t挂载到memdivmer_t 结构下的bafhlst_t数组dm_mdmlielst中
->init_merlove_mem();
//物理地址转为虚拟地址,便于以后使用
->init_memmgrob();

参考资料

以上内容是我学习彭东老师的《操作系统实战45讲》后所进行的一个笔记记录,如有错误,还请各位大佬多多指教。

我主要参考了以下资料,十分感谢:

操作系统实战45讲——彭东老师

评论区大佬——neohope

  • 23
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值