开源鸿蒙内核源码分析系列 | 内存规则 | 紫禁城皇上和奴才如何相处(转载)

开源鸿蒙内核源码分析系列 | 内存规则 | 紫禁城皇上和奴才如何相处(转载)

主子和奴才

看本篇之前建议先看 《 调度故事 | 大郎,该喝药了》。请想一个问题,内核本身也是程序要在内存运行, 用户程序一样也要在内存运行,大家都在一个窝里吃饭, 你凭什么就管我了。好像内核程序是主子,用户程序是奴才似的。

哎! 其实用户进程就是内核的一个个奴才, 被捏的死死的。  按不住奴才那这主子就不合格,就不是一个稳定系统。请想想实际内存就这么点大, 如何满足众多用户进程的需求?内核空间和用户空间如何隔离?如何防止访问乱串?如何分配/释放,防止碎片化?空间不够了又如何置换到硬盘?   想想头都大了。内核这当家的主子真是不容易,这些都是他要解决的问题, 但欲戴其冠,必承其重。

先说如果没有内存管理会怎样?

那就是个奴才们能把主子给活活踩死, 想想主奴不分,吃喝拉撒睡都在一起,称兄道弟的想干啥?没规矩不成方圆嘛,这事业肯定搞不大,单片机时代就是这种情况。裸机编程,指针可以随便乱飞,数据可以随意覆盖,没有划定边界,没有明确职责,没有特权指令,没有地址保护,你还想像java开发一样,只管new内存,不去释放,应用可以随便崩但系统跑的妥妥的?想的美! 直接系统死机,甚至开机都开不了,主板直接报废了。所以不能运行很复杂的程序,尽量可控,而且更是不可能支持应用的动态加载运行。队伍大了就不好带了,方法得换, 游击队的做法不适合规模作战,内存就需要管理了,而且是 5A级的严格管理。

内存管理在管什么?

简单说就是给主子赋能,拥有超级权利,为什么就他有?因为他先来,掌握了先机。它定好了游戏规则,你们来玩。有哪些游戏规则?

第一:主奴有别,主子即是裁判又是运动员,主子有主子地方,奴才们有奴才们待的地方,主子可以在你的空间走来走去,但你只能在主人划定的区域活动。奴才把自己玩崩了也只是奴才狗屁了, 但主人和其他人还会是好好的。主子有所有特权,比如某个奴才太嚣张了,就直接拖到午门问斩。

第二:奴奴有分,奴才们基本都是平等的,虽有高级和低级奴才区分,但本质都是奴才。奴才之间是不能随意勾连,登门问客的,防止一块搞政变。他们都有属于自己的活动空间,而且活动空间还巨大巨大,大到奴才们觉得整个紫荆城都是他们家的,给你这么大空间你干活才有动力,奴才们是铆足了劲一个个尽情的表演各种剧本,有玩电子商务的,有玩游戏的,有搞直播的等等…不愧是紫荆城的主人很有一套,明明只有一个紫禁城,硬被他整出了N个紫荆城的感觉。而且这套驾奴本领还取了个很好听的名字叫:虚拟内存。

看图:

不好意思,上错了,下面才是内存“紫禁城”的图:


extern "C"{
#endif /* _cplusplus */
#endif /* _cplusplus */
/*********************************************************************** @note_pic
*   开源鸿蒙虚拟内存全景图 从 0x00000000U 至 0xFFFFFFFFU
************************************************************************
+------------------------+  0xFFFFFFFFU
|    I/O设备未缓          |
|    PERIPH_PMM_SIZE     |
+------------------------+  外围设备未缓存基地址  PERIPH_UNCACHED_BASE
|    I/O设备未缓存         |
|    PERIPH_PMM_SIZE     |
+------------------------+  外围设备缓存基地址  PERIPH_CACHED_BASE 
|    包括I/O设备          |
|    PERIPH_PMM_SIZE     |
+------------------------+  外围设备基地址  PERIPH_DEVICE_BASE
|                        |            
|    kernel heap         |
|    128M                |
+------------------------+  内核动态分配开始地址  VMALLOC_START
|    DDR_MEM_SIZE        |
|                        |
+------------------------+   未缓存虚拟空间基地址  UNCACHED_VMM_BASE
|    内核虚拟空间          |
|    KERNEL_VMM_SIZE     |
|    .bss                |
|    .rodata             |
|    .text               |
+------------------------+  内核空间开始地址  KERNEL_ASPACE_BASE = KERNEL_VMM_BASE 
|    16M预留区            |
+------------------------+  用户空间栈顶  USER_ASPACE_TOP_MAX = USER_ASPACE_BASE + USER_ASPACE_SIZE
|                        |
|    用户空间             |
|    USER_ASPACE_SIZE    |
|    用户栈区(stack)     |
|    映射区(map)        |
|    堆区(heap)         |
|    .bss                |
|    .data  .text        |
+------------------------+  用户空间开始地址 USER_ASPACE_BASE
|    16M预留区            |
+------------------------+  0x00000000U

这是整个内存“紫禁城”的全貌图,里面的内核虚拟空间是主人专用的,里面放的是主人的资料,数据,奴才永远进不去,kernel heap 也是给主人专用的动态内存空间,管理奴才和日常运作开销很多时候需要动态申请内存,这个是专门用来提供给主人使用的。而所有奴才的空间都在叫用户空间的那一块。你没看错,是所有奴才的都在那。当然实际情况是用户空间比图中的大的多,因为主人其实用不了多少空间,大部分是留给奴才们干活用了,因为篇幅的限制笔者把用户空间压缩了下。再来看看奴才空间是啥样的。看图:


extern "C"{
#endif /* _cplusplus */
#endif /* _cplusplus */
/*********************************************************************** @note_pic
*   开源鸿蒙虚拟内存——用户空间图 从 USER_ASPACE_BASE 至 USER_ASPACE_TOP_MAX
************************************************************************
// |          /\            |
// |          ||            |
// |------------------------|  内核空间结束位置 KERNEL_ASPACE_BASE + KERNEL_ASPACE_SIZE
// |        内核空间         |
// |                        |
// |------------------------|  内核空间开始位置 KERNEL_ASPACE_BASE
// |                        |
// |         16M预留         |
// |------------------------|  用户空间栈顶  USER_ASPACE_TOP_MAX = USER_ASPACE_BASE + USER_ASPACE_SIZE
// |                        |
// |    stack区 自上而下      |
// |          ||            |  
// |          ||            |            
// |          ||            |
// |          \/            |
// |------------------------|  映射区结束位置 USER_MAP_BASE + USER_MAP_SIZE
// |  虚拟地址-物理地址映射区   |
// |                        |
// |------------------------|  映射区开始位置 USER_MAP_BASE
// |                        |
// |          /\            |
// |          ||            |
// |          ||            |
// |       heap 自下而上     |
// |------------------------|  用户空间堆区开始位置  USER_HEAP_BASE = USER_ASPACE_TOP_MAX >> 2
// |         .bss           |
// |         .data          |
// |         .text          |
// |------------------------|  用户空间开始位置 USER_ASPACE_BASE = 0x01000000UL
// |    16M预留区            |
// |------------------------|  虚拟内存开始位置 0x0000000
/* user address space, default to below kernel space with a 16MB guard gap on either side */
#ifndef USER_ASPACE_BACE //用户地址空间,默认为低于内核空间,两侧各有16MB的保护间隙
#define USER_ASPACE_BASE            ((vaddr_t)oxo1ooooooUL) //用户空间基地址  从16M位置开始
#endif

这张图是第一张图的局部用户空间放大图。里面放的是奴才的私人用品,数据,task运行栈区,动态分配内存的堆区,堆区自下而上,栈区自上而下中间由映射区(L1,L2表)隔开。这么多奴才在里面不挤吗?答案是:真不挤 。主人手眼通天,因为用了一个好帮手解决了这个问题,这个帮手名叫 MMU(李大总管)

MMU是干什么事的?

看下某度对MMU定义:它是一种负责处理中央处理器(CPU)的内存访问请求的计算机硬件.它的功能包括虚拟地址到物理地址的转换(即虚拟内存管理)、内存保护、中央处理器高速缓存的控制.通过它的一番操作,把物理空间成倍成倍的放大,他们之间的映射关系存放在页面中。

好像看懂又好像没看懂是吧,到底是干啥的?其实就是个地址映射登记中心。记住这两个字:映射 看下图:

物理内存可以理解为真实世界的紫禁城,虚拟内存就是被MMU虚拟出来的比物理页面大的多的空间。举例说明大概说明下过程:

有A(厨师),B(文艺青年) 两个奴才来到紫禁城,每个人都很有抱负,主子规定要先跑去登记处登记活动范围,领回来一张表 叫 L1页表,上面说了大半个紫禁城你可以跑动,都是你的,L1页表记录你每个房间的编号。其实奴才们的表都一样,能跑的范围也都一样。李大总管也有一张私人表叫TLB表,具体玩的呢,看个例子就明白了。

举例说明

TLB表(李总管的私人表)

真实房间当前谁在用
7A
8C
9B

李大总管的私人表叫 TLB(translation lookaside buffer)可翻译为“地址转换后援缓冲器”,也可简称为“快表”。从TLB表可以看出,有三个真实的房间, 7,8,9,目前是分配给了A,B,C使用。

奴才虚拟房间真实房间作用
A奴才17厨房拿菜
A奴才28洗手间
A奴才39卧室
B奴才38音乐室
B奴才19美术室
B奴才27武术室

奴才们的L1页表(当然可以有无数的奴才表,每个奴才人手一张)

再模拟一个他们的活动场景:

奴才动作1动作2动作3动作4
A厨房拿菜卧室睡觉上洗手间
B武术室美术室音乐室

第一:A要去1号间厨房拿菜,提交表给李总管,李总管拿表和自己的表对照,发现1号虚拟房间对应的是7号真实房间,7号刚好分配给了A用,盖章同意。A拿到了自己菜。此时李总管的表没变化。

真实房间当前谁在用
7A
8C
9B

 第二:B要去2号间练武术,提交表给李总管,李总管拿表和自己的表对照,发现1号虚拟房间对应的是7号真实房间,7号是A在用,不属于B,里面放的都还是菜呢,咋办?简单,把菜挪出去,把B奴才的武术设备装进来,更改自己的表变成了。

真实房间当前谁在用
7B
8C
9B

此时李总管的表变了,三个真实房间B用了两个了。 

第三: A要去3号间睡觉了,又提交表给李总管,李总管拿表和自己的表对照,发现3号虚拟房间对应的是9号真实房间,9号刚好分配给了B用了,此时里面放的还是美术用品呢。咋办?简单,挪出去,把A奴才的睡觉设备装进来,再更改自己的表变成了:

真实房间当前谁在用
7B
8C
9A

此时李总管的表变了,9号给了A了,而8号一直在C手里,因为过程中没人用到了8号房。但继续跑下去肯定会易主。

明白了吗?这就是 映射的核心思想! 对A,B来说,它们只认 1,2,3房间,记得自己的房间是干什么用的就行,完全不必知道背后的7,8,9是谁在用, 用房间之前提交表单就行了,后面的不用管。而且各自1,2,3可以重新映射到不一样的房间, A,B映射是完全独立的,看清没有它们的123对应的可不都是789的顺序。

上面的1,2,3就叫虚拟地址,也叫线性地址。而789就是物理地址。如此只有三个房间都可以给很多很多的奴才使用,让他们觉得这三个房间都是自己的。完美!!! 当然AB也可以有自己虚拟地址789,例如:

奴才虚拟房间真实房间作用
A奴才17厨房拿菜
A奴才28洗手间
A奴才39卧室
A奴才719洗澡
A奴才888去皇上寝宫偷看
A奴才945御膳房

百文说内核 | 抓住主脉络

子曰:“诗三百,一言以蔽之,曰‘思无邪’。”——《论语》:为政篇。

百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在开源鸿蒙内核源码加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。
百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。

与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。百篇博客系列思维导图结构如下:

根据上图的思维导图,我们未来将要和大家一一分享以上大部分关键技术点的博客文章。

百万汉字注解.精读内核源码

如果大家觉得看文章不过瘾,想直接撸代码的话,可以去下面四大码仓围观同步注释内核源码:

gitee仓

https://gitee.com/weharmony/kernel_liteos_a_note

github仓 :

https://github.com/kuangyufei/kernel_liteos_a_note

codechina仓

https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note

coding仓

https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files

写在最后

我们最近正带着大家玩嗨OpenHarmony。如果你有用OpenHarmony开发的好玩的东东,或者有对OpenHarmony的深度技术剖析,想通过我们平台让更多的小伙伴知道和分享的,欢迎投稿,让我们一起嗨起来!有点子,有想法,有Demo,立刻联系我们:

合作邮箱:zzliang@atomsource.org