如何初始化物理内存?
鸿蒙内核物理内存采用了段页式管理,先看两个主要结构体.结构体的每个成员变量的含义都已经注解出来,请结合源码理解.
#define vm_list_order_max 9 //伙伴算法分组数量,从 2^0,2^1,...,2^8 (256*4k)=1m #define vm_phys_seg_max 32 //最大支持32个段typedef struct vmphysseg {//物理段描述符 paddr_t start; /* the start of physical memory area */ //物理内存段的开始地址 size_t size; /* the size of physical memory area */ //物理内存段的大小 losvmpage *pagebase; /* the first page address of this area */ //本段首个物理页框地址 spin_lock_s freelistlock; /* the buddy list spinlock */ //伙伴算法自旋锁,用于操作freelist上锁 struct vmfreelist freelist[vm_list_order_max]; /* the free pages in the buddy list */ //伙伴算法的分组,默认分成10组 2^0,2^1,...,2^vm_list_order_max spin_lock_s lrulock; //用于置换的自旋锁,用于操作lrulist size_t lrusize[vm_nr_lru_lists]; //5个双循环链表大小,如此方便得到size los_dl_list lrulist[vm_nr_lru_lists]; //页面置换算法,5个双循环链表头,它们分别描述五中不同类型的链表} losvmphysseg;//注意: vmpage 中并没有虚拟地址,只有物理地址typedef struct vmpage { //物理页框描述符 los_dl_list node; /**freelist[order]物理页框链表上 uint32 index; /**< vm page index to vm object */ //索引位置 paddr_t physaddr; /**< vm page physical addr */ //物理页框起始物理地址,只能用于计算,不会用于操作(读/写数据==) atomic refcounts; /**< vm page ref count */ //被引用次数,共享内存会被多次引用 uint32 flags; /**< vm page flags */ //页标签,同时可以有多个标签(共享/引用/活动/被锁==) uint8 order; /**< vm page in which order list */ //被安置在伙伴算法的几号序列( 2^0,2^1,2^2,...,2^order) uint8 segid; /**< the segment id of vm page */ //所属段id uint16 npages; /**> page_shift;//本段总页数 for (page = seg->pagebase, pa = seg->start; page pagebase + npage;//遍历,算出每个页框的物理地址 page++, pa += page_size) { osvmpageinit(page, pa, segid);//对物理页框进行初始化,注意每页的物理地址都不一样 } osvmpageorderlistinit(seg->pagebase, npage);//伙伴算法初始化,将所有页加入空闲链表供分配 }} 结合中文注释,代码很好理解, 此番操作之后全局变量里的值就都各就各位了,可以开始工作了.
如何分配/回收物理内存? 答案是伙伴算法
伙伴算法系列篇中有说过好几篇,这里再看图理解下什么伙伴算法,伙伴算法注重物理内存的连续性,注意是连续性!
结合图比如,要分配4(2^2)页(16k)的内存空间,算法会先从free_area2中查看free链表是否为空,如果有空闲块,则从中分配,如果没有空闲块,就从它的上一级free_area3(每块32k)中分配出16k,并将多余的内存(16k)加入到free_area2中去。如果free_area3也没有空闲,则从更上一级申请空间,依次递推,直到free_area max_order,如果顶级都没有空间,那么就报告分配失败。
释放是申请的逆过程,当释放一个内存块时,先在其对于的free_area链表中查找是否有伙伴存在,如果没有伙伴块,直接将释放的块插入链表头。如果有或板块的存在,则将其从链表摘下,合并成一个大块,然后继续查找合并后的块在更大一级链表中是否有伙伴的存在,直至不能合并或者已经合并至最大块2^max_order为止。
看过系列篇文章的可能都发现了,笔者喜欢用讲故事和打比方来说明内核运作机制, 为了更好的理解,同样打个比方, 笔者认为伙伴算法很像是卖标准猪肉块的算法.
物理内存是一整头猪,已经切成了1斤1斤的了,但是还都连在一起,每一斤上都贴了个标号, 而且老板只按 1斤(2^0), 2斤(2^1), 4斤(2^2),...256斤(2^8)的方式来卖.售货柜上分成了9组
张三来了要7斤猪肉,怎么办? **给8斤,注意是给8斤啊 ,因为它要严格按它的标准来卖.**张三如果归还了,查看现有8斤组里有没有序号能连在一块的,有的话2个8斤合成16斤,放到16斤组里去. 如果没有这8斤猪肉将挂到上图中第2组(2^3)再卖.
大家脑海中有画面了吗? 那么问题来了,它为什么要这么卖猪肉,好处是什么? 简单啊:至少两个好处:
第一:卖肉速度快,效率高,标准化的东西最好卖了.
第二:可防止碎肉太多,后面的人想买大块的猪肉买不到了. 请仔细想想是不是这样的?如果每次客户来了要多少就割多少出去,运行一段时候后你还能买到10斤连在一块的猪肉吗? 很可能给是一包碎肉,里面甚至还有一两一两的边角肉,碎肉的结果必然是管理麻烦,效率低啊.如果按伙伴算法的结果是运行一段时候后,图中0,1,2各组中都有可卖的猪肉啊,张三哥归还了那8斤(其实他指向要7斤)猪肉,王五兄弟来了要6斤,直接把张三哥归还的给王五就行了.效率极高.
那么问题又来了,凡事总有两面性,它的坏处是什么? 也简单啊 :至少两个坏处:
第一:浪费了!,白给的三斤对王五没用啊,浪费的问题有其他办法解决,但不是在这个层面去解决,而是由 slab分配器解决,这里不重点说后续会专门讲slab分配器是如何解决这个问题的.
第二:合并要求太严格了,一定得是伙伴(连续)才能合并成更大的块.这样也会导致时间久了很难有大块的连续性的猪肉块.
比方打完了,鸿蒙内核是如何实现卖肉算法的呢? 请看代码
losvmpage *osvmphyspagesalloc(struct vmphysseg *seg, size_t npages){ struct vmfreelist *list = null; losvmpage *page = null; uint32 order; uint32 neworder; if ((seg == null) || (npages == 0)) { return null; } //因为伙伴算法分配单元是 1,2,4,8 页,比如npages = 3时,就需要从 4号空闲链表中分,剩余的1页需要劈开放到1号空闲链表中 order = osvmpagestoorder(npages);//根据页数计算出用哪个块组 if (order node)) {//理想情况链表为空,说明没找到 continue;//继续找更大块的 } page = los_dl_list_entry(los_dl_list_first(&list->node), losvmpage, node);//找第一个节点就行,因为链表上挂的都是同样大小物理页框 goto done; } } return null;done: osvmphysfreelistdelunsafe(page);//将物理页框从链表上摘出来 osvmphyspagesspiltunsafe(page, order, neworder);//将物理页框劈开,把用不了的页再挂到对应的空闲链表上 return page;}/****************************************************************************** 本函数很像卖猪肉的,拿一大块肉剁,先把多余的放回到小块肉堆里去. oldorder:原本要买 2^2肉 neworder:却找到个 2^8肉块******************************************************************************/static void osvmphyspagesspiltunsafe(losvmpage *page, uint8 oldorder, uint8 neworder){ uint32 order; losvmpage *buddypage = null; for (order = neworder; order > oldorder;) {//把肉剁碎的过程,把多余的肉块切成2^7,2^6...标准块, order--;//越切越小,逐一挂到对应的空闲链表上 buddypage = &page[vm_order_to_pages(order)];//@note_good 先把多余的肉割出来,这句代码很赞!因为losvmpage本身是在一个大数组上,page[npages]可直接定位 los_assert(buddypage->order == vm_list_order_max);//没挂到伙伴算法对应组块空闲链表上的物理页框的order必须是vm_list_order_max osvmphysfreelistaddunsafe(buddypage, order);//将劈开的节点挂到对应序号的链表上,buddypage->order = order }} 为了方便理解代码细节, 这里说一种情况: 比如三哥要买3斤的,发现4斤,8斤的都没有了,只有16斤的怎么办? 注意不会给16斤,只会给4斤.这时需要把肉劈开,劈成 8,4,4,其中4斤给张三哥,将剩下的8斤,4斤挂到对应链表上. osvmphyspagesspiltunsafe 干的就是劈猪肉的活.
伙伴算法的链表是怎么初始化的,再看段代码
//初始化空闲链表,分配物理页框使用伙伴算法static inline void osvmphysfreelistinit(struct vmphysseg *seg){ int i; uint32 intsave; struct vmfreelist *list = null; los_spininit(&seg->freelistlock);//初始化用于分配的自旋锁 los_spinlocksave(&seg->freelistlock, &intsave); for (i = 0; i freelist[i]; //一个个来 los_listinit(&list->node); //losvmpage.node将挂到list->node上 list->listcnt = 0; //链表上的数量默认0 } los_spinunlockrestore(&seg->freelistlock, intsave);} 鸿蒙是面向未来设计的系统,高瞻远瞩,格局远大,设计精良, 海量知识点, 对内核源码加上中文注解已有三个多月,越深入精读内核源码,越能感受到设计者的精巧用心,创新突破, 向开发者致敬. 可以毫不夸张的说鸿蒙内核源码可作为大学c语言,数据结构,操作系统,汇编语言 四门课程的教学项目.如此宝库,不深入研究实在是暴殄天物,于心不忍.
传华为汽车BU重大人事调整,余承东何去何从
随着硬件和软件的发展 AI出现交互新形式
单片机中的时钟周期是如何定义的
智能家电转型升级,如何实现高效节能?
CSA集团发布家用和类似用途器具耦合器标准
鸿蒙内核如何初始化物理内存?
盘古大模型 for HR,中软国际打造全链路人才管理大模型
为MindSDK搭建Keil MDK开发环境
我国最新款复兴号首次载人跨省运行
企业可用人工智能实践挖掘数据并分析
今日看点丨壁仞回应被美列入实体清单:强烈反对;理想纯电 MPV MEGA 官图公布
分布式汽车电气/电子系统设计和实现架构
英特尔揭开了其Iris Xe MAX图形的序幕
5G应用的发展将使得VR/AR迎来黄金期
Gems捷迈压力变送器提供个性化服务
ReID技术不仅能进行人脸识别 还能准确搜索追踪
Imagination与GLOBALFOUNDRIES携手为物联网应用提供超低功耗连接解决方案
悲催!Windows 10份额仍被Win7压制:年用户增长仅5%
浅谈温度对电池容量和充放电产生的影响
美国批准520亿美元拨款只为对抗中国?