将markdown部分移到https://gitee.com/weharmony/docs

    搜索 @note_pic 可查看绘制的全部字符图
    搜索 @note_why 是尚未看明白的地方,有看明白的,请Pull Request完善
    搜索 @note_thinking 是一些的思考和建议
    搜索 @note_#if0 是由第三方项目提供不在内核源码中定义的极为重要结构体,为方便理解而添加的。
    搜索 @note_good 是给源码点赞的地方
    https://weharmony.github.io/
    公众号: 鸿蒙内核源码分析
上级 32beb02d
git add -A
git commit -m '整理技术文章内容
git commit -m '将markdown部分移到https://gitee.com/weharmony/docs
搜索 @note_pic 可查看绘制的全部字符图
搜索 @note_why 是尚未看明白的地方,有看明白的,请Pull Request完善
搜索 @note_thinking 是一些的思考和建议
......
node_modules
docs/.vuepress/dist
#!/usr/bin/env sh
# 确保脚本抛出遇到的错误
set -e
# 生成静态文件
yarn build
# 进入生成的文件夹
cd docs/.vuepress/dist
# 如果是发布到自定义域名
# echo 'www.example.com' > CNAME
git init
git add -A
git commit -m 'deploy'
# 如果发布到 https://<USERNAME>.github.io
git push -f git@github.com:weharmony/weharmony.github.io.git master #https://weharmony.github.io
git push -f git@gitee.com:weharmony/www.weharmonyos.com.git master #https://harmonyos.21yunbox.com/,https://www.weharmonyos.com
# 如果发布到 https://<USERNAME>.github.io/<REPO>
# git push -f git@github.com:kuangyufei/kernel_liteos_a_note.git master:gh-pages
# 进入markdown的文件夹,推送三大平台wiki
cd ../../guide
git init
git add -A
git commit -m 'deploy wiki'
git push -f git@gitee.com:weharmony/kernel_liteos_a_note.wiki.git master
git push -f git@github.com:kuangyufei/kernel_liteos_a_note.wiki.git master
git push -f git@codechina.csdn.net:kuangyufei/kernel_liteos_a_note.wiki.git master
#git push -f git@e.coding.net:weharmony/harmony/kernel_liteos_a_note.wiki.git master
cd -
\ No newline at end of file
module.exports = {
title: 'HarmonyOS , 兴趣是最好的老师',
description: '鸿蒙内核源码注释中文版 | 深挖内核地基工程',
themeConfig: {
nav: [
{ text: '回首页', link: 'https://weharmony.github.io/' },
{
text: '注解仓库',
ariaLabel: 'Language Menu',
items: [
{ text: 'Gitee仓', link: 'https://gitee.com/weharmony/kernel_liteos_a_note' },
{ text: 'Github仓', link: 'https://github.com/kuangyufei/kernel_liteos_a_note' },
{ text: 'CSDN仓', link: 'https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note' },
{ text: 'Coding仓', link: 'https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files' }
]
},
{
text: '博客站点',
ariaLabel: 'Language Menu',
items: [
{ text: '公众号', link: 'https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png' },
{ text: 'CSDN', link: 'https://blog.csdn.net/kuangyufei/article/details/108727970' },
{ text: '开源中国', link: 'https://my.oschina.net/u/3751245/blog/4626852' },
{ text: 'HarmonyOS', link: 'https://weharmony.github.io/' },
{ text: '21盒子', link: 'https://harmonyos.21yunbox.com/' },
]
},
{
text: '博客系列篇',
ariaLabel: 'Language Menu',
items: [
{ text: '鸿蒙内核源码分析(双向链表篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(双向链表篇).html' },
{ text: '鸿蒙内核源码分析(进程管理篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(进程管理篇).html' },
{ text: '鸿蒙内核源码分析(时钟任务篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(时钟任务篇).html' },
{ text: '鸿蒙内核源码分析(任务管理篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(任务管理篇).html' },
{ text: '鸿蒙内核源码分析(调度队列篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(调度队列篇).html' },
{ text: '鸿蒙内核源码分析(调度机制篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(调度机制篇).html' },
{ text: '鸿蒙内核源码分析(必读故事篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(必读故事篇).html' },
{ text: '鸿蒙内核源码分析(内存概念篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(内存概念篇).html' },
{ text: '鸿蒙内核源码分析(内存管理篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(内存管理篇).html' },
{ text: '鸿蒙内核源码分析(内存分配篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(内存分配篇).html' },
{ text: '鸿蒙内核源码分析(内存汇编篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(内存汇编篇).html' },
{ text: '鸿蒙内核源码分析(内存映射篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(内存映射篇).html' },
{ text: '鸿蒙内核源码分析(源码注释篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(源码注释篇).html' },
{ text: '鸿蒙内核源码分析(内存规则篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(内存规则篇).html' },
{ text: '鸿蒙内核源码分析(物理内存篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(物理内存篇).html' },
{ text: '鸿蒙内核源码分析(源码结构篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(源码结构篇).html' },
{ text: '鸿蒙内核源码分析(位图管理篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(位图管理篇).html' },
{ text: '鸿蒙内核源码分析(用栈方式篇)', link: 'https://weharmony.github.io/guide/鸿蒙内核源码分析(用栈方式篇).html' },
{ text: '鸿蒙源码分析系列(总目录)', link: 'https://weharmony.github.io/guide/鸿蒙源码分析系列(总目录).html' },
]
},
],
sidebar: {
"/guide/": "auto",
},
}
}
---
home: true
#heroImage: /hero.png
heroText: 鸿蒙内核源码注释中文版
tagline: 精读内核源码 深挖地基工程
actionText: 注解仓库地址 →
actionLink: https://gitee.com/weharmony/kernel_liteos_a_note
features:
- title: 中文注解官方源码
details: 以官方源码为基础,中文详细注解内核实现细节,深挖地基工程,构建底层网图
- title: 故事说内核
details: 问答式导读, 生活式比喻, 表格化说明, 图形化展示, 轻轻松松,层层递进剥开内核神秘外衣
- title: 四大码仓,三大网站同步更新
details: https://gitee.com/weharmony/kernel_liteos_a_note
https://github.com/kuangyufei/kernel_liteos_a_note
footer: MIT Licensed | Copyright © 2021-present weharmony www.weharmonyos.com
---
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
- ### **鸿蒙源码分析系列篇**
- [鸿蒙源码分析系列(总目录) | 持续更新中... 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)|[ HarmonyOS 】](https://weharmony.github.io)
* [|- 鸿蒙内核源码分析(用栈方式篇) | 栈是构建底层运行的基础 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/112534331) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4893388)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(用栈方式篇).html)
* [|- 鸿蒙内核源码分析(位图管理篇) | 为何进程和线程都是32个优先级? 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/112394982) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4888467)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(位图管理篇).html)
* [|- 鸿蒙内核源码分析(源码结构篇) | 内核500问你能答对多少? 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/111938348) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4869137)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(源码结构篇).html)
* [|- 鸿蒙内核源码分析(物理内存篇) | 伙伴算法是在卖标准猪肉块吗?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/111765600) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4842408)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(物理内存篇).html)
* [|- 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109437223) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4698384)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存规则篇).html)
* [|- 鸿蒙内核源码分析(源码注释篇) | 精读内核源码有哪些好处?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109251754) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4686747)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(源码注释篇).html)
* [|- 鸿蒙内核源码分析(内存映射篇) | 虚拟内存-物理内存是如何映射的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109032636) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4694841)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存映射篇).html)
* [|- 鸿蒙内核源码分析(内存汇编篇) | 什么是虚拟内存的实现基础?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108994081) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4692156)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存汇编篇).html)
* [|- 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108989906) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4646802)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存分配篇).html)
* [|- 鸿蒙内核源码分析(内存管理篇) | 鸿蒙虚拟内存全景图是怎样的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108821442) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4652284)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存管理篇).html)
* [|- 鸿蒙内核源码分析(内存概念篇) | 虚拟内存虚在哪里?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108723672) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4646802)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存概念篇).html)
* [|- 鸿蒙内核源码分析(必读故事篇) | 西门和金莲的那点破事【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108745174) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4634668)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(必读故事篇).html)
* [|- 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108705968) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4623040)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(调度机制篇).html)
* [|- 鸿蒙内核源码分析(调度队列篇) | 就绪队列对调度的作用【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108626671) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4606916)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(调度队列篇).html)
* [|- 鸿蒙内核源码分析(任务管理篇) | 任务是内核调度的单元【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108621428) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4603919)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(任务管理篇).html)
* [|- 鸿蒙内核源码分析(时钟管理篇) | 触发调度最大的动力来自哪里?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108603468) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4574493)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(时钟管理篇).html)
* [|- 鸿蒙内核源码分析(进程管理篇) | 进程是内核资源管理单元【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108595941) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4574429)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(进程管理篇).html)
* [|- 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108585659) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4572304)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(双向链表篇).html)
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
\ No newline at end of file
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
先看四个宏定义,进程和线程(线程就是任务)最高和最低优先级定义,\[0,31\]区间,即32级,优先级用于调度,CPU根据这个来决定先运行哪个进程和任务。
```cpp
#define OS_PROCESS_PRIORITY_HIGHEST 0 //进程最高优先级
#define OS_PROCESS_PRIORITY_LOWEST 31 //进程最低优先级
#define OS_TASK_PRIORITY_HIGHEST 0 //任务最高优先级,软时钟任务就是最高级任务,见于 OsSwtmrTaskCreate
#define OS_TASK_PRIORITY_LOWEST 31 //任务最低优先级
```
## 为何进程和线程都是32个优先级?
回答这个问题之前,先回答另一个问题,为什么人类几乎所有的文明都是用十进制的计数方式。答案掰手指就知道了,因为人有十根手指头。玛雅人的二十进制那是把脚指头算上了,但其实也算是十进制的表示。
这是否说明一个问题,认知受环境的影响,方向是怎么简单/方便怎么来。这也可以解释为什么人类语言发音包括各种方言对妈妈这个词都很类似,因为婴儿说mama是最容易的。 注意认识这点很重要!
而计算机的世界是二进制的,是是非非,清清楚楚,特别的简单,二进制已经最简单了,到底啦,不可能有更简单的了。还记得双向链表篇中说过的吗,因为简单所以才不简单啊,大道若简,计算机就靠着这01码,表述万千世界。
但人类的大脑不擅长存储,二进制太长了数到100就撑爆了大脑,记不住,为了记忆和运算方便,编程常用靠近10进制的 16进制来表示 ,0x9527ABCD 看着比 0011000111100101010100111舒服多了。
## 应用开发和内核开发有哪些区别?
区别还是很大的,这里只说一点,就是对位的控制能力,内核会出现大量的按位运算(&,|,~,^) , 一个变量的不同位表达不同的含义,但这在应用程序员那是很少看到的,他们用的更多的是逻辑运算(&&,||,!)
```cpp
#define OS_TASK_STATUS_INIT 0x0001U //初始化状态
#define OS_TASK_STATUS_READY 0x0002U //就绪状态的任务都将插入就绪队列
#define OS_TASK_STATUS_RUNNING 0x0004U //运行状态
#define OS_TASK_STATUS_SUSPEND 0x0008U //挂起状态
#define OS_TASK_STATUS_PEND 0x0010U //阻塞状态
```
这是任务各种状态(注者后续将比如成贴标签)表述,将它们还原成二进制就是:
0000000000000001 = 0x0001U
0000000000000010 = 0x0002U
0000000000000100 = 0x0004U
0000000000001000 = 0x0008U
0000000000010000 = 0x0010U
发现二进制这边的区别没有,用每一位来表示一种不同的状态,1表示是,0表示不是。
这样的好处有两点:
1.可以多种标签同时存在 比如 0x07 = 0b00000111,对应以上就是任务有三个标签(初始,就绪,和运行),进程和线程在运行期间是允许多种标签同时存在的。
2.节省了空间,一个变量就搞定了,如果是应用程序员要实现这三个标签同时存在,习惯上要定义三个变量的,因为你的排他性颗粒度是一个变量而不是一个位。
而对位的管理/运算就需要有个专门的管理器:位图管理器 (见源码 los_bitmap.c )
## 什么是位图管理器?
直接上部分代码,代码关键地方都加了中文注释,简单说就是对位的各种操作,比如如何在某个位上设1?如何找到最高位为1的是哪个位置?这些函数都是有大用途的。
```cpp
//对状态字的某一标志位进行置1操作
VOID LOS_BitmapSet(UINT32 *bitmap, UINT16 pos)
{
if (bitmap == NULL) {
return;
}
*bitmap |= 1U << (pos & OS_BITMAP_MASK);//在对应位上置1
}
//对状态字的某一标志位进行清0操作
VOID LOS_BitmapClr(UINT32 *bitmap, UINT16 pos)
{
if (bitmap == NULL) {
return;
}
*bitmap &= ~(1U << (pos & OS_BITMAP_MASK));//在对应位上置0
}
/********************************************************
杂项算术指令
CLZ 用于计算操作数最高端0的个数,这条指令主要用于一下两个场合
  计算操作数规范化(使其最高位为1)时需要左移的位数
  确定一个优先级掩码中最高优先级
********************************************************/
//获取状态字中为1的最高位 例如: 00110110 返回 5
UINT16 LOS_HighBitGet(UINT32 bitmap)
{
if (bitmap == 0) {
return LOS_INVALID_BIT_INDEX;
}
return (OS_BITMAP_MASK - CLZ(bitmap));
}
//获取状态字中为1的最低位, 例如: 00110110 返回 2
UINT16 LOS_LowBitGet(UINT32 bitmap)
{
if (bitmap == 0) {
return LOS_INVALID_BIT_INDEX;
}
return CTZ(bitmap);//
}
```
## 位图在哪些地方应用?
内核很多模块在使用位图,这里只说进程和线程模块,还记得开始的问题吗,为何进程和线程都是32个优先级?因为他们的优先级是由位图管理的,管理一个UINT32的变量,所以是32级,一个位一个级别,最高位优先级最低。
```cpp
UINT32 priBitMap; /**< BitMap for recording the change of task priority, //任务在执行过程中优先级会经常变化,这个变量用来记录所有曾经变化
the priority can not be greater than 31 */ //过的优先级,例如 ..01001011 曾经有过 0,1,3,6 优先级
```
这是任务控制块中对调度优先级位图的定义,注意一个任务的优先级在运行过程中可不是一成不变的,内核会根据运行情况而改变它的,这个变量是用来保存这个任务曾经有过的所有优先级历史记录。
比如 任务A的优先级位图是 00000001001011 ,可以看出它曾经有过四个调度等级记录,那如果想知道优先级最低的记录是多少时怎么办呢?
诶,上面的位图管理器函数 UINT16 LOS_HighBitGet(UINT32 bitmap) 就很有用啦 ,它返回的是1在高位出现的位置,可以数一下是 6
因为任务的优先级0最大,所以最终的意思就是A任务曾经有过的最低优先级是6
一定要理解位图的操作,内核中大量存在这类代码,尤其到了汇编层,对寄存器的操作大量的出现。
比如以下这段汇编代码。
```cpp
MSR CPSR_c, #(CPSR_INT_DISABLE | CPSR_SVC_MODE) @禁止中断并切到管理模式
LDRH R1, [R0, #4] @将存储器地址为R0+4 的低16位数据读入寄存器R1,并将R1的高16 位清零
ORR R1, #OS_TASK_STATUS_RUNNING @或指令 R1=R1|OS_TASK_STATUS_RUNNING
STRH R1, [R0, #4] @将寄存器R1中的低16位写入以R0+4为地址的存储器中
```
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
## 最难讲的章节
        内存模块占了 HarmonyOS 内核约15%代码量, 近20个.c文件,很复杂。系列篇将用九篇来介绍HarmonyOS内存部分,分别是 鸿蒙内核源码分析(内存概念篇) | 鸿蒙内核源码分析(内存管理篇) | 鸿蒙内核源码分析(内存汇编篇) |  鸿蒙内核源码分析(内存分配篇)  |  鸿蒙内核源码分析(内存映射篇)  | 鸿蒙内核源码分析(内存空间篇)  | 鸿蒙内核源码分析(内存置换篇)  | 鸿蒙内核源码分析(内存共享篇) | 鸿蒙内核源码分析(内存故事篇) 故事篇中会用生活场景张大爷故事里的另一个主角王场馆来举例 ,这些篇幅内容也会反复修改完善。想想也是内存何等重要,内核本身也是程序要运行空间, 用户程序也要在运行空间,大家都在一个窝里吃饭, 你凭什么就管我了, 凭什么的问题会在系列篇的最后结尾 鸿蒙内核源码分析(主奴机制篇) 中详细介绍, 哎! 其实用户进程就是内核的一个个奴才, 被捏的死死的.  按不住奴才那这主子就不合格,就不是一个稳定的运作系统. 试着想想 实际内存就这么点大, 如何满足众多用户进程的需求? 内核空间和用户空间如何隔离? 如何防止访问乱串? 如何分配如何释放防止碎片化? 空间不够了又如何置换到硬盘?   想想头都大了。内核这当家的主子真是不容易,这些都是他要解决的问题, 欲戴其冠,必承其重嘛.本章是笔者认真读完鸿蒙内存模块代码后的总结,获益良多,讲之前先重新梳理下内存的一些概念再来详细阐述内存模块的其他细节. 那开始吧. 
## 先说如果没有内存管理会怎样?
       那就是个奴才们能把主子给活活踩死, 想想主奴不分,吃喝拉撒睡都在一起,称兄道弟的想干啥? 没规矩不成方圆嘛,这事业肯定搞不大,单片机时代就是这种情况. 裸机编程,指针可以随便乱飞,数据可以随意覆盖,没有划定边界,没有明确职责,没有特权指令,没有地址保护,你还想像java开发一样,只管new内存,不去释放,应用可以随便崩但系统跑的妥妥的?想的美! 直接系统死机,甚至开机都开不了,主板直接报废了. 所以不能运行很复杂的程序,尽量可控,而且更是不可能支持应用的动态加载运行. 队伍大了就不好带了,方法得换, 游击队的做法不适合规模作战,内存就需要管理了,而且是 5A级的严格管理。
## 内存管理在管什么?
        简单说就是给主子赋能,拥有超级权利,为什么就他有? 因为他先来,掌握了先机.它定好了游戏规则,你们来玩.有哪些游戏规则?
        第一: 主奴有别,主子即是裁判又是运动员,主子有主子地方,奴才们有奴才们待的地方,主子可以在你的空间走来走去,但你只能在主人划定的区域活动.奴才把自己玩崩了也只是奴才狗屁了, 但主人和其他人还会是好好的. 主子有所有特权,比如某个奴才太嚣张了,就直接拖到午门问斩。
        第二: 奴奴有分,奴才们基本都是平等的,虽有高级和低级奴才区分,但本质都是奴才。奴才之间是不能随意勾连,登门问客的,防止一块搞政变. 他们都有属于自己的活动空间,而且活动空间还巨大巨大,大到奴才们觉得整个紫荆城都是他们家的,给你这么大空间你干活才有动力,奴才们是铆足了劲一个个尽情的表演各种剧本,有玩电子商务的,有玩游戏的,有搞直播的等等。。。不愧是紫荆城的主人很有一套,明明只有一个紫禁城,硬被他整出了N个紫荆城的感觉。而且这套驾奴本领还取了个很好听的名字叫:虚拟内存。HarmonyOS关于内存部分的中文注解已基本完成,可前往代码仓库 [鸿蒙内核源码注释中文版 【 Gitee仓](https://gitee.com/weharmony/kernel_liteos_a_note) | [CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note) | [Github仓](https://github.com/kuangyufei/kernel_liteos_a_note) | [Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)详细阅读。虚拟内存长啥样?鸿蒙虚拟内存全景图:
![](https://img-blog.csdnimg.cn/20201029221604209.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
这是整个紫荆城的全貌图,里面的内核虚拟空间是主人专用的,里面放的是主人的资料,数据,奴才永远进不去,kernel heap 也是给主人专用的动态内存空间,管理奴才和日常运作开销很多时候需要动态申请内存,这个是专门用来提供给主人使用的。而所有奴才的空间都在叫用户空间的那一块。你没看错,是所有奴才的都在那。当然实际情况是用户空间比图中的大的多,因为主人其实用不了多少空间,大部分是留给奴才们干活用了,因为篇幅的限制笔者把用户空间压缩了下。 再来看看奴才空间是啥样的。看图
![](https://img-blog.csdnimg.cn/20201029222858522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
这张图是第一张图的局部用户空间放大图。里面放的是奴才的私人用品,数据,task运行栈区,动态分配内存的堆区,堆区自下而上,栈区自上而下中间有虚拟地址<--->物理地址的映射区隔开。这么多奴才在里面不挤吗?答案是:真不挤 。主人手眼通天,因为用了一个好帮手解决了这个问题,这个帮手名叫 MMU(李大总管)
## MMU是干什么事的?
看下某度对MMU定义:它是一种负责处理[中央处理器](https://baike.baidu.com/item/%E4%B8%AD%E5%A4%AE%E5%A4%84%E7%90%86%E5%99%A8)(CPU)的[内存](https://baike.baidu.com/item/%E5%86%85%E5%AD%98)访问请求的[计算机硬件](https://baike.baidu.com/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A1%AC%E4%BB%B6)。它的功能包括[虚拟地址](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80)[物理地址](https://baike.baidu.com/item/%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80)的转换(即[虚拟内存](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98)管理)、内存保护、中央处理器[高速缓存](https://baike.baidu.com/item/%E9%AB%98%E9%80%9F%E7%BC%93%E5%AD%98)的控制。通过它的一番操作,把物理空间成倍成倍的放大,他们之间的映射关系存放在页面中。
好像看懂又好像没看懂是吧,到底是干啥的?其实就是个地址映射登记中心。记住这两个字:映射 看下图
![](https://img-blog.csdnimg.cn/2020092619274388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
物理内存可以理解为真实世界的紫禁城,虚拟内存就是被MMU虚拟出来的比物理页面大的多的空间。举例说明大概说明下过程:
有A,B,C 三个奴才来到紫禁城,每个人都很有抱负,主子规定要先跑去登记处登记活动范围,领回来一张表 叫 L1页表,上面说了大半个紫禁城你可以跑动,都是你的,L1页表记录你详细了每个房间的编号。其实奴才们的表都一样,能跑的范围也都一样。
怎么跑呢?在CPU单核的情况下同时只能有一个人在跑,CPU双核就是两个人跑,其他人都看着他们跑,等待主人叫到自己的号去跑。每个人跑之前先把领的表交给地址映射中心的负责人李大总管,比如A需要把自己编号999号房的菜拿出来切炒,李大总管拿表一看 999号对应的是 88号真实房间,而88号已经有B奴才的东西了,怎么办?简单啊,把B奴才的东西移到城外的仓库,再把A数据从城外移到88号房。这样A就拿到了他认为的是在999号房的菜,它全程都不用知道 88号房。记住这是映射的核心机制!!! 这个过程涉及到两个内存概念:缺页中断和页面置换。缺页中断就是88号房被B占了发出一个中断信号,页面置换就是把88号房的东西先搬到城外B的仓库,才从A仓库的菜搬到88号房。李大总管最后更新下内部表,88号仓给了A奴才用。李大总管的私人表叫 TLB(translation lookaside buffer)可翻译为“地址转换后援缓冲器”,也可简称为“快表”。简单地说,TLB就是页表的Cache
置换就要有算法,常用的是 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
虚拟内存和物理内存的管理分配方式是不一样的,但都是以页(page)为单位来管理,一页4K字节。
物理内存:段页管理,伙伴算法分配(buddy算法)。LOS_PhysPagesAllocContiguous 这里只到函数级,物理内存的管理和分配很简单,知道伙伴算法的原理就搞清楚了物理内存的管理。啥是伙伴算法?简单说就是把蛋糕按 2^0,2^1...2^order先切成一块块的, 2^0的统一放一起,2^order的统一放一起,有几个order就用几个链表管理,颗粒粗,分配至少一页起。
虚拟内存:线性区管理,内存池分配(slab算法)。LOS_MemAlloc 内存池分配,细颗粒分配,从物理内存拿了一页空间,根据需要再割给申请方。
举例说下流程:A用户向虚拟内存申请1K内存,虚拟内存一看内存池里只有半K(512)了,不够了就转身向物理内存管理方要了一页(4K),再割1K给A。
虚拟内存管理在鸿蒙主要由 VmMapRegion 这个结构体来串联,很复杂,什么红黑树,映射(file,swap),共享内存,访问权限等等都由它来完成。这些在其他篇幅中有详细介绍。
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
## MMU的本质
虚拟地址(VA): 就是线性地址, 鸿蒙内存部分全是VA的身影, 是由编译器和链接器在定位程序时分配的,每个应用程序都使用相同的虚拟内存地址空间,而这些虚拟内存地址空间实际上分别映射到不同的实际物理内存空间上。CPU只知道虚拟地址,向虚拟地址要数据,但在其保护模式下很悲催地址信号在路上被MMU拦截了,MMU把虚拟地址换成了物理地址,从而拿到了真正的数据。
物理地址(PA):程序的指令和常量数据,全局变量数据以及运行时动态申请内存所分配的实际物理内存存放位置。
MMU采用页表(page table)来实现虚实地址转换,页表项除了描述虚拟页到物理页直接的转换外,还提供了页的访问权限(读,写,可执行)和存储属性。 MMU的本质是拿虚拟地址的高位(20位)做文章,低12位是页内偏移地址不会变。也就是说虚拟地址和物理地址的低12位是一样的,本篇详细讲述MMU是如何变戏法的。
MMU是通过两级页表结构:L1和L2来实现映射功能的,鸿蒙内核当然也实现了这两级页表转换的实现。本篇是系列篇关于内存部分最满意的一篇,也是最不好理解的一篇, 强烈建议结合源码看, [鸿蒙内核源码注释中文版 【 Gitee仓](https://gitee.com/weharmony/kernel_liteos_a_note)|[CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[Github仓](https://github.com/kuangyufei/kernel_liteos_a_note)|[Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)内存部分的注释已经基本完成 .
## 一级页表L1
L1页表将全部的4G地址空间划分为4096个1M的节,页表中每一项(页表项)32位,其内容是L2页表基地址或某个1M物理内存的基地址。虚拟地址的高12位用于对页表项定位,也就是4096个页面项的索引,L1页表的基地址,也叫转换表基地址,存放在CP15的C2(TTB)寄存器中,鸿蒙内核源码分析(内存汇编篇)中有详细的描述,自行翻看。
L1页表项有三种描述格式,鸿蒙源码如下。
```cpp
/* L1 descriptor type */
#define MMU_DESCRIPTOR_L1_TYPE_INVALID (0x0 << 0)
#define MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE (0x1 << 0)
#define MMU_DESCRIPTOR_L1_TYPE_SECTION (0x2 << 0)
#define MMU_DESCRIPTOR_L1_TYPE_MASK (0x3 << 0)
```
第一种:Fault(INVALID)页表项,表示对应虚拟地址未被映射,访问将产生一个数据中止异常。
第二种:PAGE_TABLE页表项,指向L2页表的页表项,意思就是把1M分成更多的页(256*4K)
第三种:SECTION页表项 ,指向1M节的页表项
![](https://img-blog.csdnimg.cn/20201012233233550.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
页表项的最低二位\[1:0\],用于定义页表项的类型,section页表项对应1M的节,直接使用页表项的最高12位替代虚拟地址的高12位即可得到物理地址。还是直接看鸿蒙源码来的清晰,每一行都加了详细的注释。
## LOS_ArchMmuQuery通过虚拟地址查询物理地址和flags
```cpp
//通过虚拟地址查询物理地址
STATUS_T LOS_ArchMmuQuery(const LosArchMmu *archMmu, VADDR_T vaddr, PADDR_T *paddr, UINT32 *flags)
{//archMmu->virtTtb:转换表基地址
PTE_T l1Entry = OsGetPte1(archMmu->virtTtb, vaddr);//获取PTE vaddr右移20位 得到L1描述子地址
PTE_T l2Entry;
PTE_T* l2Base = NULL;
if (OsIsPte1Invalid(l1Entry)) {//判断L1描述子地址是否有效
return LOS_ERRNO_VM_NOT_FOUND;//无效返回虚拟地址未查询到
} else if (OsIsPte1Section(l1Entry)) {// section页表项: l1Entry低二位是否为 10
if (paddr != NULL) {//物理地址 = 节基地址(section页表项的高12位) + 虚拟地址低20位
*paddr = MMU_DESCRIPTOR_L1_SECTION_ADDR(l1Entry) + (vaddr & (MMU_DESCRIPTOR_L1_SMALL_SIZE - 1));
}
if (flags != NULL) {
OsCvtSecAttsToFlags(l1Entry, flags);//获取虚拟内存的flag信息
}
} else if (OsIsPte1PageTable(l1Entry)) {//PAGE_TABLE页表项: l1Entry低二位是否为 01
l2Base = OsGetPte2BasePtr(l1Entry);//获取L2转换表基地址
if (l2Base == NULL) {
return LOS_ERRNO_VM_NOT_FOUND;
}
l2Entry = OsGetPte2(l2Base, vaddr);//获取L2描述子地址
if (OsIsPte2SmallPage(l2Entry) || OsIsPte2SmallPageXN(l2Entry)) {
if (paddr != NULL) {//物理地址 = 小页基地址(L2页表项的高20位) + 虚拟地址低12位
*paddr = MMU_DESCRIPTOR_L2_SMALL_PAGE_ADDR(l2Entry) + (vaddr & (MMU_DESCRIPTOR_L2_SMALL_SIZE - 1));
}
if (flags != NULL) {
OsCvtPte2AttsToFlags(l1Entry, l2Entry, flags);//获取虚拟内存的flag信息
}
} else if (OsIsPte2LargePage(l2Entry)) {//鸿蒙目前暂不支持64K大页,未来手机版应该会支持。
LOS_Panic("%s %d, large page unimplemented\n", __FUNCTION__, __LINE__);
} else {
return LOS_ERRNO_VM_NOT_FOUND;
}
}
return LOS_OK;
}
```
 这是鸿蒙内核对地址使用最频繁的功能,通过虚拟地址得到物理地址和flag信息,看下哪些地方会调用到它。
![](https://img-blog.csdnimg.cn/20201013060100352.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
## 二级页表L2
L1页表项表示1M的地址范围,L2把1M分成更多的小页,鸿蒙内核 一页按4K算,所以被分成 256个小页。
L2页表中包含256个页表项,每个32位(4个字节),L2页表需要 256*4 = 1K的空间,必须按1K对齐,每个L2页表项将4K的虚拟内存地址转换为物理地址,每个L2页面项都给出了一个4K的页基地址。
L2页表项有三种格式:
```cpp
/* L2 descriptor type */
#define MMU_DESCRIPTOR_L2_TYPE_INVALID (0x0 << 0)
#define MMU_DESCRIPTOR_L2_TYPE_LARGE_PAGE (0x1 << 0)
#define MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE (0x2 << 0)
#define MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE_XN (0x3 << 0)
#define MMU_DESCRIPTOR_L2_TYPE_MASK (0x3 << 0)
```
第一种:Fault(INVALID)页表项,表示对应虚拟地址未被映射,访问将产生一个数据中止异常。
第二种:大页表项,包含一个指向64K页的指针,但鸿蒙内核并没有实现大页表的支持,给出了未实现的提示
```cpp
if (OsIsPte2LargePage(l2Entry)) {
LOS_Panic("%s %d, large page unimplemented\n", __FUNCTION__, __LINE__);
}
```
第三种:小页表项,包含一个指向4K页的指针。
![](https://img-blog.csdnimg.cn/20201012234935435.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
## 映射初始化的过程
先看调用和被调用的关系
![](https://img-blog.csdnimg.cn/20201012173807776.png)
```cpp
//启动映射初始化
VOID OsInitMappingStartUp(VOID)
{
OsArmInvalidateTlbBarrier();//使TLB失效
OsSwitchTmpTTB();//切换到临时TTB
OsSetKSectionAttr();//设置内核段(text,rodata,bss)映射
OsArchMmuInitPerCPU();//初始化CPU与mmu相关信息
}
```
 干脆利落,调用了四个函数,其中三个在鸿蒙内核源码分析(内存汇编篇)有涉及,不展开讲,这里说OsSetKSectionAttr
它实现了内核空间各个区的映射,内核本身也是程序,鸿蒙把内核空间在物理内存上就独立开来了,也就是说在物理内存上有一段区域是只给内核空间享用的,从根上就把内核和APP 空间隔离了,里面放的是内核的重要数据(包括代码,常量和全局变量),具体看代码,代码很长,整个函数全贴出来了,都加上了注释。
## OsSetKSectionAttr 内核空间的设置和映射
```cpp
typedef struct ArchMmuInitMapping {
PADDR_T phys;//物理地址
VADDR_T virt;//虚拟地址
size_t size;//大小
unsigned int flags;//标识 读/写/.. VM_MAP_REGION_FLAG_PERM_*
const char *name;//名称
} LosArchMmuInitMapping;
VADDR_T *OsGFirstTableGet()
{
return (VADDR_T *)g_firstPageTable;//UINT8 g_firstPageTable[MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS]
}
//设置内核空间段属性,可看出内核空间是固定映射到物理地址
STATIC VOID OsSetKSectionAttr(VOID)
{
/* every section should be page aligned */
UINTPTR textStart = (UINTPTR)&__text_start;//代码段开始位置
UINTPTR textEnd = (UINTPTR)&__text_end;//代码段结束位置
UINTPTR rodataStart = (UINTPTR)&__rodata_start;//常量只读段开始位置
UINTPTR rodataEnd = (UINTPTR)&__rodata_end;//常量只读段结束位置
UINTPTR ramDataStart = (UINTPTR)&__ram_data_start;//全局变量段开始位置
UINTPTR bssEnd = (UINTPTR)&__bss_end;//bss结束位置
UINT32 bssEndBoundary = ROUNDUP(bssEnd, MB);
LosArchMmuInitMapping mmuKernelMappings[] = {
{
.phys = SYS_MEM_BASE + textStart - KERNEL_VMM_BASE,//映射物理内存位置
.virt = textStart,//内核代码区
.size = ROUNDUP(textEnd - textStart, MMU_DESCRIPTOR_L2_SMALL_SIZE),//代码区大小
.flags = VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_EXECUTE,//代码段可读,可执行
.name = "kernel_text"
},
{
.phys = SYS_MEM_BASE + rodataStart - KERNEL_VMM_BASE,//映射物理内存位置
.virt = rodataStart,//内核常量区
.size = ROUNDUP(rodataEnd - rodataStart, MMU_DESCRIPTOR_L2_SMALL_SIZE),//4K对齐
.flags = VM_MAP_REGION_FLAG_PERM_READ,//常量段只读
.name = "kernel_rodata"
},
{
.phys = SYS_MEM_BASE + ramDataStart - KERNEL_VMM_BASE,//映射物理内存位置
.virt = ramDataStart,
.size = ROUNDUP(bssEndBoundary - ramDataStart, MMU_DESCRIPTOR_L2_SMALL_SIZE),
.flags = VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE,//全局变量区可读可写
.name = "kernel_data_bss"
}
};
LosVmSpace *kSpace = LOS_GetKVmSpace();//获取内核空间
status_t status;
UINT32 length;
paddr_t oldTtPhyBase;
int i;
LosArchMmuInitMapping *kernelMap = NULL;//内核映射
UINT32 kmallocLength;
/* use second-level mapping of default READ and WRITE */
kSpace->archMmu.virtTtb = (PTE_T *)g_firstPageTable;//__attribute__((section(".bss.prebss.translation_table"))) UINT8 g_firstPageTable[MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS];
kSpace->archMmu.physTtb = LOS_PaddrQuery(kSpace->archMmu.virtTtb);//通过TTB虚拟地址查询TTB物理地址
status = LOS_ArchMmuUnmap(&kSpace->archMmu, KERNEL_VMM_BASE,
(bssEndBoundary - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT);//解绑 bssEndBoundary - KERNEL_VMM_BASE 映射
if (status != ((bssEndBoundary - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) {//解绑失败
VM_ERR("unmap failed, status: %d", status);
return;
}
//映射 textStart - KERNEL_VMM_BASE 区
status = LOS_ArchMmuMap(&kSpace->archMmu, KERNEL_VMM_BASE, SYS_MEM_BASE,
(textStart - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT,
VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE |
VM_MAP_REGION_FLAG_PERM_EXECUTE);
if (status != ((textStart - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) {
VM_ERR("mmap failed, status: %d", status);
return;
}
length = sizeof(mmuKernelMappings) / sizeof(LosArchMmuInitMapping);
for (i = 0; i < length; i++) {//对mmuKernelMappings一一映射好
kernelMap = &mmuKernelMappings[i];
status = LOS_ArchMmuMap(&kSpace->archMmu, kernelMap->virt, kernelMap->phys,
kernelMap->size >> MMU_DESCRIPTOR_L2_SMALL_SHIFT, kernelMap->flags);
if (status != (kernelMap->size >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) {
VM_ERR("mmap failed, status: %d", status);
return;
}
LOS_VmSpaceReserve(kSpace, kernelMap->size, kernelMap->virt);//保留区
}
//将剩余空间映射好
kmallocLength = KERNEL_VMM_BASE + SYS_MEM_SIZE_DEFAULT - bssEndBoundary;
status = LOS_ArchMmuMap(&kSpace->archMmu, bssEndBoundary,
SYS_MEM_BASE + bssEndBoundary - KERNEL_VMM_BASE,
kmallocLength >> MMU_DESCRIPTOR_L2_SMALL_SHIFT,
VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE);
if (status != (kmallocLength >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) {
VM_ERR("unmap failed, status: %d", status);
return;
}
LOS_VmSpaceReserve(kSpace, kmallocLength, bssEndBoundary);
/* we need free tmp ttbase */
oldTtPhyBase = OsArmReadTtbr0();//读取TTB值
oldTtPhyBase = oldTtPhyBase & MMU_DESCRIPTOR_L2_SMALL_FRAME;
OsArmWriteTtbr0(kSpace->archMmu.physTtb | MMU_TTBRx_FLAGS);//内核页表基地址写入CP15 c2(TTB寄存器)
ISB;
/* we changed page table entry, so we need to clean TLB here */
OsCleanTLB();//清空TLB缓冲区
(VOID)LOS_MemFree(m_aucSysMem0, (VOID *)(UINTPTR)(oldTtPhyBase - SYS_MEM_BASE + KERNEL_VMM_BASE));//释放内存池
}
```
## LOS_ArchMmuMap 生成L1,L2页表项,实现映射的过程
mmu的map 就是生成L1,L2页表项的过程,以供虚实地址的转换使用,还是直接看代码吧,代码说明一切!
```cpp
//所谓的 map 就是 生成L1,L2页表项的过程
status_t LOS_ArchMmuMap(LosArchMmu *archMmu, VADDR_T vaddr, PADDR_T paddr, size_t count, UINT32 flags)
{
PTE_T l1Entry;
UINT32 saveCounts = 0;
INT32 mapped = 0;
INT32 checkRst;
checkRst = OsMapParamCheck(flags, vaddr, paddr);//检查参数
if (checkRst < 0) {
return checkRst;
}
/* see what kind of mapping we can use */
while (count > 0) {
if (MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(vaddr) && //虚拟地址和物理地址对齐 0x100000(1M)时采用
MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(paddr) && //section页表项格式
count >= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1) { //MMU_DESCRIPTOR_L2_NUMBERS_PER_L1 = 0x100
/* compute the arch flags for L1 sections cache, r ,w ,x, domain and type */
saveCounts = OsMapSection(archMmu, flags, &vaddr, &paddr, &count);//生成L1 section类型页表项并保存
} else {
/* have to use a L2 mapping, we only allocate 4KB for L1, support 0 ~ 1GB */
l1Entry = OsGetPte1(archMmu->virtTtb, vaddr);//获取L1页面项
if (OsIsPte1Invalid(l1Entry)) {//L1 fault页面项类型
OsMapL1PTE(archMmu, &l1Entry, vaddr, flags);//生成L1 page table类型页表项并保存
saveCounts = OsMapL2PageContinous(l1Entry, flags, &vaddr, &paddr, &count);//生成L2 页表项目并保存
} else if (OsIsPte1PageTable(l1Entry)) {//L1 page table页面项类型
saveCounts = OsMapL2PageContinous(l1Entry, flags, &vaddr, &paddr, &count);//生成L2 页表项目并保存
} else {
LOS_Panic("%s %d, unimplemented tt_entry %x\n", __FUNCTION__, __LINE__, l1Entry);
}
}
mapped += saveCounts;
}
return mapped;
}
STATIC UINT32 OsMapL2PageContinous(PTE_T pte1, UINT32 flags, VADDR_T *vaddr, PADDR_T *paddr, UINT32 *count)
{
PTE_T *pte2BasePtr = NULL;
UINT32 archFlags;
UINT32 saveCounts;
pte2BasePtr = OsGetPte2BasePtr(pte1);
if (pte2BasePtr == NULL) {
LOS_Panic("%s %d, pte1 %#x error\n", __FUNCTION__, __LINE__, pte1);
}
/* compute the arch flags for L2 4K pages */
archFlags = OsCvtPte2FlagsToAttrs(flags);
saveCounts = OsSavePte2Continuous(pte2BasePtr, OsGetPte2Index(*vaddr), *paddr | archFlags, *count);
*paddr += (saveCounts << MMU_DESCRIPTOR_L2_SMALL_SHIFT);
*vaddr += (saveCounts << MMU_DESCRIPTOR_L2_SMALL_SHIFT);
*count -= saveCounts;
return saveCounts;
}
```
![](https://img-blog.csdnimg.cn/20201013062331731.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
OsMapL2PageContinous 没有加注释,希望你别太懒,赶紧动起来,到这里应该都能看懂了!最好能结合 鸿蒙内核源码分析(内存汇编篇)一起看理解会更深透。
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
## 最难讲的章节
        内存模块占了 HarmonyOS 内核约15%代码量, 近20个.c文件,很复杂。系列篇将用九篇来介绍HarmonyOS内存部分,分别是 鸿蒙内核源码分析(内存概念篇) | 鸿蒙内核源码分析(内存管理篇) | 鸿蒙内核源码分析(内存汇编篇) |  鸿蒙内核源码分析(内存分配篇)  |  鸿蒙内核源码分析(内存映射篇)  | 鸿蒙内核源码分析(内存空间篇)  | 鸿蒙内核源码分析(内存置换篇)  | 鸿蒙内核源码分析(内存共享篇) | 鸿蒙内核源码分析(内存故事篇) 故事篇中会用生活场景张大爷故事里的另一个主角王场馆来举例 ,这些篇幅内容也会反复修改完善。想想也是内存何等重要,内核本身也是程序要运行空间, 用户程序也要在运行空间,大家都在一个窝里吃饭, 你凭什么就管我了, 凭什么的问题会在系列篇的最后结尾 鸿蒙内核源码分析(主奴机制篇) 中详细介绍, 哎! 其实用户进程就是内核的一个个奴才, 被捏的死死的.  按不住奴才那这主子就不合格,就不是一个稳定的运作系统. 试着想想 实际内存就这么点大, 如何满足众多用户进程的需求? 内核空间和用户空间如何隔离? 如何防止访问乱串? 如何分配如何释放防止碎片化? 空间不够了又如何置换到硬盘?   想想头都大了。内核这当家的主子真是不容易,这些都是他要解决的问题, 欲戴其冠,必承其重嘛.本章是笔者认真读完鸿蒙内存模块代码后的总结,获益良多,讲之前先重新梳理下内存的一些概念再来详细阐述内存模块的其他细节. 那开始吧. 
## 先说如果没有内存管理会怎样?
       那就是个奴才们能把主子给活活踩死, 想想主奴不分,吃喝拉撒睡都在一起,称兄道弟的想干啥? 没规矩不成方圆嘛,这事业肯定搞不大,单片机时代就是这种情况. 裸机编程,指针可以随便乱飞,数据可以随意覆盖,没有划定边界,没有明确职责,没有特权指令,没有地址保护,你还想像java开发一样,只管new内存,不去释放,应用可以随便崩但系统跑的妥妥的?想的美! 直接系统死机,甚至开机都开不了,主板直接报废了. 所以不能运行很复杂的程序,尽量可控,而且更是不可能支持应用的动态加载运行. 队伍大了就不好带了,方法得换, 游击队的做法不适合规模作战,内存就需要管理了,而且是 5A级的严格管理。
## 内存管理在管什么?
        简单说就是给主子赋能,拥有超级权利,为什么就他有? 因为他先来,掌握了先机.它定好了游戏规则,你们来玩.有哪些游戏规则?
        第一: 主奴有别,主子即是裁判又是运动员,主子有主子地方,奴才们有奴才们待的地方,主子可以在你的空间走来走去,但你只能在主人划定的区域活动.奴才把自己玩崩了也只是奴才狗屁了, 但主人和其他人还会是好好的. 主子有所有特权,比如某个奴才太嚣张了,就直接拖到午门问斩。
        第二: 奴奴有分,奴才们基本都是平等的,虽有高级和低级奴才区分,但本质都是奴才。奴才之间是不能随意勾连,登门问客的,防止一块搞政变. 他们都有属于自己的活动空间,而且活动空间还巨大巨大,大到奴才们觉得整个紫荆城都是他们家的,给你这么大空间你干活才有动力,奴才们是铆足了劲一个个尽情的表演各种剧本,有玩电子商务的,有玩游戏的,有搞直播的等等。。。不愧是紫荆城的主人很有一套,明明只有一个紫禁城,硬被他整出了N个紫荆城的感觉。而且这套驾奴本领还取了个很好听的名字叫:虚拟内存。HarmonyOS关于内存部分的中文注解已基本完成,可前往代码仓库 [鸿蒙内核源码注释中文版 【 Gitee仓](https://gitee.com/weharmony/kernel_liteos_a_note) | [CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note) | [Github仓](https://github.com/kuangyufei/kernel_liteos_a_note) | [Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)详细阅读。虚拟内存长啥样?鸿蒙虚拟内存全景图:
![](https://img-blog.csdnimg.cn/20201029221604209.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
这是整个紫荆城的全貌图,里面的内核虚拟空间是主人专用的,里面放的是主人的资料,数据,奴才永远进不去,kernel heap 也是给主人专用的动态内存空间,管理奴才和日常运作开销很多时候需要动态申请内存,这个是专门用来提供给主人使用的。而所有奴才的空间都在叫用户空间的那一块。你没看错,是所有奴才的都在那。当然实际情况是用户空间比图中的大的多,因为主人其实用不了多少空间,大部分是留给奴才们干活用了,因为篇幅的限制笔者把用户空间压缩了下。 再来看看奴才空间是啥样的。看图
![](https://img-blog.csdnimg.cn/20201029222858522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
这张图是第一张图的局部用户空间放大图。里面放的是奴才的私人用品,数据,task运行栈区,动态分配内存的堆区,堆区自下而上,栈区自上而下中间有虚拟地址<--->物理地址的映射区隔开。这么多奴才在里面不挤吗?答案是:真不挤 。主人手眼通天,因为用了一个好帮手解决了这个问题,这个帮手名叫 MMU(李大总管)
## MMU是干什么事的?
看下某度对MMU定义:它是一种负责处理[中央处理器](https://baike.baidu.com/item/%E4%B8%AD%E5%A4%AE%E5%A4%84%E7%90%86%E5%99%A8)(CPU)的[内存](https://baike.baidu.com/item/%E5%86%85%E5%AD%98)访问请求的[计算机硬件](https://baike.baidu.com/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A1%AC%E4%BB%B6)。它的功能包括[虚拟地址](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80)[物理地址](https://baike.baidu.com/item/%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80)的转换(即[虚拟内存](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98)管理)、内存保护、中央处理器[高速缓存](https://baike.baidu.com/item/%E9%AB%98%E9%80%9F%E7%BC%93%E5%AD%98)的控制。通过它的一番操作,把物理空间成倍成倍的放大,他们之间的映射关系存放在页面中。
好像看懂又好像没看懂是吧,到底是干啥的?其实就是个地址映射登记中心。记住这两个字:映射 看下图
![](https://img-blog.csdnimg.cn/2020092619274388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
物理内存可以理解为真实世界的紫禁城,虚拟内存就是被MMU虚拟出来的比物理页面大的多的空间。举例说明大概说明下过程:
有A,B,C 三个奴才来到紫禁城,每个人都很有抱负,主子规定要先跑去登记处登记活动范围,领回来一张表 叫 L1页表,上面说了大半个紫禁城你可以跑动,都是你的,L1页表记录你详细了每个房间的编号。其实奴才们的表都一样,能跑的范围也都一样。
怎么跑呢?在CPU单核的情况下同时只能有一个人在跑,CPU双核就是两个人跑,其他人都看着他们跑,等待主人叫到自己的号去跑。每个人跑之前先把领的表交给地址映射中心的负责人李大总管,比如A需要把自己编号999号房的菜拿出来切炒,李大总管拿表一看 999号对应的是 88号真实房间,而88号已经有B奴才的东西了,怎么办?简单啊,把B奴才的东西移到城外的仓库,再把A数据从城外移到88号房。这样A就拿到了他认为的是在999号房的菜,它全程都不用知道 88号房。记住这是映射的核心机制!!! 这个过程涉及到两个内存概念:缺页中断和页面置换。缺页中断就是88号房被B占了发出一个中断信号,页面置换就是把88号房的东西先搬到城外B的仓库,才从A仓库的菜搬到88号房。李大总管最后更新下内部表,88号仓给了A奴才用。李大总管的私人表叫 TLB(translation lookaside buffer)可翻译为“地址转换后援缓冲器”,也可简称为“快表”。简单地说,TLB就是页表的Cache
置换就要有算法,常用的是 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
虚拟内存和物理内存的管理分配方式是不一样的,但都是以页(page)为单位来管理,一页4K字节。
物理内存:段页管理,伙伴算法分配(buddy算法)。LOS_PhysPagesAllocContiguous 这里只到函数级,物理内存的管理和分配很简单,知道伙伴算法的原理就搞清楚了物理内存的管理。啥是伙伴算法?简单说就是把蛋糕按 2^0,2^1...2^order先切成一块块的, 2^0的统一放一起,2^order的统一放一起,有几个order就用几个链表管理,颗粒粗,分配至少一页起。
虚拟内存:线性区管理,内存池分配(slab算法)。LOS_MemAlloc 内存池分配,细颗粒分配,从物理内存拿了一页空间,根据需要再割给申请方。
举例说下流程:A用户向虚拟内存申请1K内存,虚拟内存一看内存池里只有半K(512)了,不够了就转身向物理内存管理方要了一页(4K),再割1K给A。
虚拟内存管理在鸿蒙主要由 VmMapRegion 这个结构体来串联,很复杂,什么红黑树,映射(file,swap),共享内存,访问权限等等都由它来完成。这些在其他篇幅中有详细介绍。
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
本篇讲解 内存的汇编部分 源码详见:[/kernel/base/vm](https://codechina.csdn.net/openharmony/kernel_liteos_a/blob/master/kernel/base/vm) -- [kernel\_liteos\_a\\arch\\arm\\arm](https://codechina.csdn.net/openharmony/kernel_liteos_a/blob/master/arch/arm/arm)
**目录**
ARM-CP15协处理器
先拆解一段汇编代码
CP15有哪些寄存器
TTB寄存器(Translation table base)
mmu上下文
TLB(translation lookaside buffer)
asid寄存器
---
## ARM-CP15协处理器
ARM处理器使用协处理器15(CP15)的寄存器来控制cache、TCM和存储器管理。CP15的寄存器只能被MRC和MCR(Move to Coprocessor from ARM Register )指令访问,包含16个32位的寄存器,其编号为0~15。本篇重点讲解其中的 C7,C2,C13三个寄存器。
### 先拆解一段汇编代码
上来看段汇编,读懂内核源码不会点汇编是不行的 , 但不用发怵,没那么恐怖,由浅入深, 内核其实挺好玩的。见于 arm.h,里面全是这些玩意。
```cpp
#define DSB __asm__ volatile("dsb" ::: "memory")
#define ISB __asm__ volatile("isb" ::: "memory")
#define DMB __asm__ volatile("dmb" ::: "memory")
STATIC INLINE VOID OsArmWriteBpiallis(UINT32 val)
{
__asm__ volatile("mcr p15, 0, %0, c7,c1,6" ::"r"(val));
__asm__ volatile("isb" ::: "memory");
}
```
![](https://oscimg.oschina.net/oscnet/up-a8bd073a2f0f27c5cc1df7aec69d1444eca.png)
这句汇编的指令字面意思是: 将ARM寄存器R0的数据写到CP15中编号为7的寄存器中,值由外面传进来。
例如 OsArmWriteBpiallis(0) 做了4个动作
1.把0值写入R0寄存器,注意这个寄存器是ARM即CPU的寄存器,::"r"(val) 意思代表向GCC编译器声明,会修改R0寄存器的值,改之前提前打好招呼,都是绅士文明人。其实编译器的功能是非常强大的,不仅仅是大家普遍认为的只是编译代码的工具而已。
2.volatile的意思还是告诉编译器,不要去优化这段代码,原封不动的生成目标指令。
3."isb" ::: "memory" 还是告诉编译器内存的内容可能被更改了,需要无效所有Cache,并访问实际的内容,而不是Cache!
4.再把R0的值写入到C7中,C7是CP15协处理器的寄存器。C7寄存器是负责什么的?对照下面的表。
### CP15有哪些寄存器
![](https://oscimg.oschina.net/oscnet/up-f53d1995525e122da4676a47b24ea1e7e31.png)
这句话真正的意思是:关闭高速缓存和写缓存控制!,其他部分寄存器下面会讲,先有个大概印象。
mmu从哪里获取 page table 的信息?答案是: TTB
### TTB寄存器(Translation table base)
参考上表可知TTB寄存器是CP15协处理器的C2寄存器,存页表的基地址,即一级映射描述符表的基地址。围绕着TTB鸿蒙提供了以下读取函数。简单说就是内核从外面不断的修改和读取寄存器值,而MMU只会直接通过硬件读取这个寄存器的值,以达到MMU获取不一样的页表进行进程虚拟地址和物理地址的转换。还记得吗?每个进程的页表都是独立的!
![](https://img-blog.csdnimg.cn/20201011154644582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
那么什么情况下会修改里面的值呢?换页表意味着 mmu在进行上下文的切换!还是直接看代码吧。
### mmu上下文
![](https://img-blog.csdnimg.cn/20201011183432518.png)只被这一个函数调用。毫无疑问LOS_ArchMmuContextSwitch是关键函数。
```cpp
typedef struct ArchMmu {
LosMux mtx; /**< arch mmu page table entry modification mutex lock */
VADDR_T *virtTtb; /**< translation table base virtual addr */
PADDR_T physTtb; /**< translation table base phys addr */
UINT32 asid; /**< TLB asid */
LOS_DL_LIST ptList; /**< page table vm page list */
} LosArchMmu;
// mmu 上下文切换
VOID LOS_ArchMmuContextSwitch(LosArchMmu *archMmu)
{
UINT32 ttbr;
UINT32 ttbcr = OsArmReadTtbcr();//读取TTB寄存器的状态值
if (archMmu) {
ttbr = MMU_TTBRx_FLAGS | (archMmu->physTtb);//进程TTB物理地址值
/* enable TTBR0 */
ttbcr &= ~MMU_DESCRIPTOR_TTBCR_PD0;//使能TTBR0
} else {
ttbr = 0;
/* disable TTBR0 */
ttbcr |= MMU_DESCRIPTOR_TTBCR_PD0;
}
/* from armv7a arm B3.10.4, we should do synchronization changes of ASID and TTBR. */
OsArmWriteContextidr(LOS_GetKVmSpace()->archMmu.asid);//这里先把asid切到内核空间的ID
ISB;
OsArmWriteTtbr0(ttbr);//通过r0寄存器将进程页面基址写入TTB
ISB;
OsArmWriteTtbcr(ttbcr);//写入TTB状态位
ISB;
if (archMmu) {
OsArmWriteContextidr(archMmu->asid);//通过R0寄存器写入进程标识符至C13寄存器
ISB;
}
}
// c13 asid(Adress Space ID)进程标识符
STATIC INLINE VOID OsArmWriteContextidr(UINT32 val)
{
__asm__ volatile("mcr p15, 0, %0, c13,c0,1" ::"r"(val));
__asm__ volatile("isb" ::: "memory");
}
```
再看下那些地方会调用 LOS_ArchMmuContextSwitch,下图一目了然。
![](https://img-blog.csdnimg.cn/20201011160718822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
有四个地方会切换mmu上下文
第一:通过调度算法,被选中的进程的空间改变了,自然映射页表就跟着变了,需要切换mmu上下文,还是直接看代码。代码不是很多,就都贴出来了,都加了注释,不记得调度算法的可去系列篇中看 鸿蒙内核源码分析(调度机制篇),里面有详细的阐述。
```cpp
//调度算法-进程切换
STATIC VOID OsSchedSwitchProcess(LosProcessCB *runProcess, LosProcessCB *newProcess)
{
if (runProcess == newProcess) {
return;
}
#if (LOSCFG_KERNEL_SMP == YES)
runProcess->processStatus = OS_PROCESS_RUNTASK_COUNT_DEC(runProcess->processStatus);
newProcess->processStatus = OS_PROCESS_RUNTASK_COUNT_ADD(newProcess->processStatus);
LOS_ASSERT(!(OS_PROCESS_GET_RUNTASK_COUNT(newProcess->processStatus) > LOSCFG_KERNEL_CORE_NUM));
if (OS_PROCESS_GET_RUNTASK_COUNT(runProcess->processStatus) == 0) {//获取当前进程的任务数量
#endif
runProcess->processStatus &= ~OS_PROCESS_STATUS_RUNNING;
if ((runProcess->threadNumber > 1) && !(runProcess->processStatus & OS_PROCESS_STATUS_READY)) {
runProcess->processStatus |= OS_PROCESS_STATUS_PEND;
}
#if (LOSCFG_KERNEL_SMP == YES)
}
#endif
LOS_ASSERT(!(newProcess->processStatus & OS_PROCESS_STATUS_PEND));//断言进程不是阻塞状态
newProcess->processStatus |= OS_PROCESS_STATUS_RUNNING;//设置进程状态为运行状态
if (OsProcessIsUserMode(newProcess)) {//用户模式下切换进程mmu上下文
LOS_ArchMmuContextSwitch(&newProcess->vmSpace->archMmu);//新进程->虚拟空间中的->Mmu部分入参
}
#ifdef LOSCFG_KERNEL_CPUP
OsProcessCycleEndStart(newProcess->processID, OS_PROCESS_GET_RUNTASK_COUNT(runProcess->processStatus) + 1);
#endif /* LOSCFG_KERNEL_CPUP */
OsCurrProcessSet(newProcess);//将进程置为 g_runProcess
if ((newProcess->timeSlice == 0) && (newProcess->policy == LOS_SCHED_RR)) {//为用完时间片或初始进程分配时间片
newProcess->timeSlice = OS_PROCESS_SCHED_RR_INTERVAL;//重新分配时间片,默认 20ms
}
}
```
这里再啰嗦一句,系列篇中已经说了两个上下文切换了,一个是这里的因进程切换引起的mmu上下文切换,还一个是因task切换引起的CPU的上下文切换,还能想起来吗?
第二:是加载ELF文件的时候会切换mmu,一个崭新的进程诞生了,具体将在 鸿蒙内核源码分析(启动加载篇) 会细讲,敬请关注系列篇动态。
其余是虚拟空间回收和刷新空间的时候,这个就自己看代码去吧。
mmu是如何快速的通过虚拟地址找到物理地址的呢?答案是:TLB ,注意上面还有个TTB,一个是寄存器, 一个是cache,别搞混了。
### TLB(translation lookaside buffer)
TLB是硬件上的一个cache,因为页表一般都很大,并且存放在内存中,所以处理器引入MMU后,读取指令、数据需要访问两次内存:首先通过查询页表得到物理地址,然后访问该物理地址读取指令、数据。为了减少因为MMU导致的处理器性能下降,引入了TLB,可翻译为“地址转换后援缓冲器”,也可简称为“快表”。简单地说,TLB就是页表的Cache,其中存储了当前最可能被访问到的页表项,其内容是部分页表项的一个副本。只有在TLB无法完成地址翻译任务时,才会到内存中查询页表,这样就减少了页表查询导致的处理器性能下降。详细看
![](https://img-blog.csdnimg.cn/img_convert/2eb1099aa3fa2bfbedf464c24b4b776c.png)
照着图说吧,步骤是这样的。
1. 图中的page table的基地址就是上面TTB寄存器值,整个page table非常大,有多大接下来会讲,所以只能存在内存里,TTB中只是存一个开始位置而已。
2\. 虚拟地址是程序的地址逻辑地址,也就是喂给CPU的地址,必须经过MMU的转换后变成物理内存才能取到真正的指令和数据。
3. TLB是page table的迷你版,MMU先从TLB里找物理页,找不到了再从page table中找,从page table中找到后会放入TLB中,注意这一步非常非常的关键。因为page table是属于进程的会有很多个,而TLB只有一个,不放入就会出现多个进程的page table都映射到了同一个物理页框而不自知。一个物理页同时只能被一个page table所映射。但除了TLB的唯一性外,要做到不错乱还需要了一个东西,就是进程在映射层面的唯一标识符 - asid。
### asid寄存器
asid(Adress Space ID) 进程标识符,属于CP15协处理器的C13号寄存器,ASID可用来唯一标识进程,并为进程提供地址空间保护。当TLB试图解析虚拟页号时,它确保当前运行进程的ASID与虚拟页相关的ASID相匹配。如果不匹配,那么就作为TLB失效。除了提供地址空间保护外,ASID允许TLB同时包含多个进程的条目。如果TLB不支持独立的ASID,每次选择一个页表时(例如,上下文切换时),TLB就必须被冲刷(flushed)或删除,以确保下一个进程不会使用错误的地址转换。
TLB页表中有一个bit来指明当前的entry是global(nG=0,所有process都可以访问)还是non-global(nG=1,only本process允许访问)。如果是global类型,则TLB中不会tag ASID;如果是non-global类型,则TLB会tag上ASID,且MMU在TLB中查询时需要判断这个ASID和当前进程的ASID是否一致,只有一致才证明这条entry当前process有权限访问。
看到了吗?如果每次mmu上下文切换时,把TLB全部刷新已保证TLB中全是新进程的映射表,固然是可以,但效率太低了!!!进程的切换其实是秒级亚秒级的,地址的虚实转换是何等的频繁啊,怎么会这么现实呢,真实的情况是TLB中有很多很多其他进程占用的物理内存的记录还在,当然他们对物理内存的使用权也还在。所以当应用程序 new了10M内存以为是属于自己的时候,其实在内核层面根本就不属于你,还是别人在用,只有你用了1M的那一瞬间真正1M物理内存才属于你,而且当你的进程被其他进程切换后,很大可能你用的那1M也已经不在物理内存中了,已经被置换到硬盘上了。明白了吗?只关注应用开发的同学当然可以说这关我鸟事,给我的感觉有就行了,但想熟悉内核的同学就必须要明白,这是每分每秒都在发生的事情。
最后一个函数留给大家,asid是如何分配的?
```cpp
/* allocate and free asid */
status_t OsAllocAsid(UINT32 *asid)
{
UINT32 flags;
LOS_SpinLockSave(&g_cpuAsidLock, &flags);
UINT32 firstZeroBit = LOS_BitmapFfz(g_asidPool, 1UL << MMU_ARM_ASID_BITS);
if (firstZeroBit >= 0 && firstZeroBit < (1UL << MMU_ARM_ASID_BITS)) {
LOS_BitmapSetNBits(g_asidPool, firstZeroBit, 1);
*asid = firstZeroBit;
LOS_SpinUnlockRestore(&g_cpuAsidLock, flags);
return LOS_OK;
}
LOS_SpinUnlockRestore(&g_cpuAsidLock, flags);
return firstZeroBit;
}
```
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
本文分析虚拟内存模块源码 详见:../kernel/base/vm 本篇源码超级多,很烧脑,但笔者关键处都加了注释。废话不多说,开始吧。
**目录**
初始化整个内存
鸿蒙虚拟内存全景图
内核空间是怎么初始化的?
Page是如何初始化的?
进程是如何申请内存的?
task是如何申请内存的?
### 初始化整个内存
![](https://img-blog.csdnimg.cn/20200927094931590.png)
从main()跟踪可看内存部分初始化是在OsSysMemInit()中完成的。
```cpp
UINT32 OsSysMemInit(VOID)
{
    STATUS_T ret;
    OsKSpaceInit();//内核空间初始化
    ret = OsKHeapInit(OS_KHEAP_BLOCK_SIZE);// 内核动态内存初始化 512K   
    if (ret != LOS_OK) {
        VM_ERR("OsKHeapInit fail");
        return LOS_NOK;
    }
    OsVmPageStartup();// page初始化
    OsInitMappingStartUp();// 映射初始化
    ret = ShmInit();// 共享内存初始化
    if (ret < 0) {
        VM_ERR("ShmInit fail");  
        return LOS_NOK;
    }
    return LOS_OK;
}
```
### 鸿蒙虚拟内存整体布局图
![](https://oscimg.oschina.net/oscnet/up-ba26317141bf49e1f29bf4129a9d77109b4.png)
```cpp
// HarmonyOS 内核空间包含以下各段:
extern CHAR __int_stack_start; // 运行系统函数栈的开始地址
extern CHAR __rodata_start; // ROM开始地址 只读
extern CHAR __rodata_end; // ROM结束地址
extern CHAR __bss_start; // bss开始地址
extern CHAR __bss_end; // bss结束地址
extern CHAR __text_start; // 代码区开始地址
extern CHAR __text_end; // 代码区结束地址
extern CHAR __ram_data_start; // RAM开始地址 可读可写
extern CHAR __ram_data_end; // RAM结束地址
extern UINT32 __heap_start; // 堆区开始地址
extern UINT32 __heap_end; // 堆区结束地址
```
内存一开始一张白纸,这些extern就是给它画大界线的,从哪到哪是属于什么段。这些值大小取决实际项目内存条的大小,不同的内存条,地址肯定会不一样,所以必须由外部提供,鸿蒙内核采用了Linux的段管理方式。结合上图对比以下的解释自行理解下位置。 
 BSS段 (bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。该段用于存储未初始化的全局变量或者是默认初始化为0的全局变量,它不占用程序文件的大小,但是占用程序运行时的内存空间。
data段 该段用于存储初始化的全局变量,初始化为0的全局变量出于编译优化的策略还是被保存在BSS段。
细心的读者可能发现了,鸿蒙内核几乎所有的全局变量都没有赋初始化值或NULL,这些变量经过编译后是放在了BSS段的,运行时占用内存空间,如此编译出来的ELF包就变小了。
.rodata段,该段也叫常量区,用于存放常量数据,ro就是Read Only之意。
text段 是用于存放程序代码的,编译时确定,只读。更进一步讲是存放处理器的机器指令,当各个源文件单独编译之后生成目标文件,经连接器链接各个目标文件并解决各个源文件之间函数的引用,与此同时,还得将所有目标文件中的.text段合在一起。
stack栈段,是由系统负责申请释放,用于存储参数变量及局部变量以及函数的执行。
heap段 它由用户申请和释放,申请时至少分配虚存,当真正存储数据时才分配相应的实存,释放时也并非立即释放实存,而是可能被重复利用。
### 内核空间是怎么初始化的?
```cpp
LosMux g_vmSpaceListMux;//虚拟空间互斥锁,一般和g_vmSpaceList配套使用
LOS_DL_LIST_HEAD(g_vmSpaceList);//g_vmSpaceList把所有虚拟空间挂在一起,
LosVmSpace g_kVmSpace;    //内核空间地址
LosVmSpace g_vMallocSpace;//虚拟分配空间地址
//鸿蒙内核空间有两个(内核进程空间和内核动态分配空间),共用一张L1页表
VOID OsKSpaceInit(VOID)
{
    OsVmMapInit();// 初始化互斥量
    OsKernVmSpaceInit(&g_kVmSpace, OsGFirstTableGet());// 初始化内核虚拟空间,OsGFirstTableGet 为L1表基地址
    OsVMallocSpaceInit(&g_vMallocSpace, OsGFirstTableGet());// 初始化动态分配区虚拟空间,OsGFirstTableGet 为L1表基地址
}//g_kVmSpace g_vMallocSpace 共用一个L1页表
//初始化内核堆空间
STATUS_T OsKHeapInit(size_t size)
{
    STATUS_T ret;
    VOID *ptr = NULL;
    /*
     * roundup to MB aligned in order to set kernel attributes. kernel text/code/data attributes
     * should page mapping, remaining region should section mapping. so the boundary should be
     * MB aligned.
     */
     //向上舍入到MB对齐是为了设置内核属性。内核文本/代码/数据属性应该是页映射,其余区域应该是段映射,所以边界应该对齐。
    UINTPTR end = ROUNDUP(g_vmBootMemBase + size, MB);//用M是因为采用section mapping 鸿蒙内核源码分析(内存映射篇)有阐述
    size = end - g_vmBootMemBase;
    //ROUNDUP(0x00000200+512,1024) = 1024  ROUNDUP(0x00000201+512,1024) = 2048 此处需细品! 
    ptr = OsVmBootMemAlloc(size);//因刚开机,使用引导分配器分配
    if (!ptr) {
        PRINT_ERR("vmm_kheap_init boot_alloc_mem failed! %d\n", size);
        return -1;
    }
    m_aucSysMem0 = m_aucSysMem1 = ptr;//内存池基地址,取名auc还用0和1来标识有何深意,一直没整明白, 哪位大神能告诉下?
    ret = LOS_MemInit(m_aucSysMem0, size);//初始化内存池
    if (ret != LOS_OK) {
        PRINT_ERR("vmm_kheap_init LOS_MemInit failed!\n");
        g_vmBootMemBase -= size;//分配失败时需归还size, g_vmBootMemBase是很野蛮粗暴的
        return ret;
    }
    LOS_MemExpandEnable(OS_SYS_MEM_ADDR);//地址可扩展
    return LOS_OK;
}
```
内核空间用了三个全局变量,其中一个是互斥LosMux,IPC部分会详细讲,这里先不展开。 比较有意思的是LOS\_DL\_LIST_HEAD,看内核源码过程中经常会为这样的代码点头称赞,会心一笑。点赞!
```cpp
#define LOS_DL_LIST_HEAD(list) LOS_DL_LIST list = { &(list), &(list) }
```
### Page是如何初始化的?
page是映射的最小单位,是物理地址<--->虚拟地址映射的数据结构的基础
```cpp
// page初始化
VOID OsVmPageStartup(VOID)
{
    struct VmPhysSeg *seg = NULL;
    LosVmPage *page = NULL;
    paddr_t pa;
    UINT32 nPage;
    INT32 segID;
    OsVmPhysAreaSizeAdjust(ROUNDUP((g_vmBootMemBase - KERNEL_ASPACE_BASE), PAGE_SIZE));//校正 g_physArea size
    nPage = OsVmPhysPageNumGet();//得到 g_physArea 总页数
    g_vmPageArraySize = nPage * sizeof(LosVmPage);//页表总大小
    g_vmPageArray = (LosVmPage *)OsVmBootMemAlloc(g_vmPageArraySize);//申请页表存放区域
    OsVmPhysAreaSizeAdjust(ROUNDUP(g_vmPageArraySize, PAGE_SIZE));// g_physArea 变小
    OsVmPhysSegAdd();// 段页绑定
    OsVmPhysInit();// 加入空闲链表和设置置换算法,LRU(最近最久未使用)算法
    for (segID = 0; segID < g_vmPhysSegNum; segID++) {
        seg = &g_vmPhysSeg[segID];
        nPage = seg->size >> PAGE_SHIFT;
        for (page = seg->pageBase, pa = seg->start; page <= seg->pageBase + nPage;
             page++, pa += PAGE_SIZE) {
            OsVmPageInit(page, pa, segID);//page初始化
        }
        OsVmPageOrderListInit(seg->pageBase, nPage);// 页面分配的排序
    }
}
```
### 进程是如何申请内存的?
进程的主体是来自进程池,进程池是统一分配的,怎么创建进程池的去翻系列篇里的文章,所以创建一个进程的时候只需要分配虚拟内存LosVmSpace,这里要分内核模式和用户模式下的申请。
```cpp
//初始化进程的 用户空间 或 内核空间
//初始化PCB块
STATIC UINT32 OsInitPCB(LosProcessCB *processCB, UINT32 mode, UINT16 priority, UINT16 policy, const CHAR *name)
{
    UINT32 count;
    LosVmSpace *space = NULL;
    LosVmPage *vmPage = NULL;
    status_t status;
    BOOL retVal = FALSE;
    processCB->processMode = mode;//用户态进程还是内核态进程
    processCB->processStatus = OS_PROCESS_STATUS_INIT;//进程初始状态
    processCB->parentProcessID = OS_INVALID_VALUE;//爸爸进程,外面指定
    processCB->threadGroupID = OS_INVALID_VALUE;//所属线程组
    processCB->priority = priority;//优先级
    processCB->policy = policy;//调度算法 LOS_SCHED_RR
    processCB->umask = OS_PROCESS_DEFAULT_UMASK;//掩码
    processCB->timerID = (timer_t)(UINTPTR)MAX_INVALID_TIMER_VID;
    LOS_ListInit(&processCB->threadSiblingList);//初始化任务/线程链表
    LOS_ListInit(&processCB->childrenList);        //初始化孩子链表
    LOS_ListInit(&processCB->exitChildList);    //初始化记录哪些孩子退出了的链表    
    LOS_ListInit(&(processCB->waitList));        //初始化等待链表
    for (count = 0; count < OS_PRIORITY_QUEUE_NUM; ++count) { //根据 priority数 创建对应个数的队列
        LOS_ListInit(&processCB->threadPriQueueList[count]);  
    }
    if (OsProcessIsUserMode(processCB)) {// 是否为用户态进程
        space = LOS_MemAlloc(m_aucSysMem0, sizeof(LosVmSpace));
        if (space == NULL) {
            PRINT_ERR("%s %d, alloc space failed\n", __FUNCTION__, __LINE__);
            return LOS_ENOMEM;
        }
        VADDR_T *ttb = LOS_PhysPagesAllocContiguous(1);//分配一个物理页用于存储L1页表 4G虚拟内存分成 (4096*1M)
        if (ttb == NULL) {//这里直接获取物理页ttb
            PRINT_ERR("%s %d, alloc ttb or space failed\n", __FUNCTION__, __LINE__);
            (VOID)LOS_MemFree(m_aucSysMem0, space);
            return LOS_ENOMEM;
        }
        (VOID)memset_s(ttb, PAGE_SIZE, 0, PAGE_SIZE);
        retVal = OsUserVmSpaceInit(space, ttb);//初始化虚拟空间和本进程 mmu
        vmPage = OsVmVaddrToPage(ttb);//通过虚拟地址拿到page
        if ((retVal == FALSE) || (vmPage == NULL)) {//异常处理
            PRINT_ERR("create space failed! ret: %d, vmPage: %#x\n", retVal, vmPage);
            processCB->processStatus = OS_PROCESS_FLAG_UNUSED;//进程未使用,干净
            (VOID)LOS_MemFree(m_aucSysMem0, space);//释放虚拟空间
            LOS_PhysPagesFreeContiguous(ttb, 1);//释放物理页,4K
            return LOS_EAGAIN;
        }
        processCB->vmSpace = space;//设为进程虚拟空间
        LOS_ListAdd(&processCB->vmSpace->archMmu.ptList, &(vmPage->node));//将空间映射页表挂在 空间的mmu L1页表, L1为表头
    } else {
        processCB->vmSpace = LOS_GetKVmSpace();//内核共用一个虚拟空间,内核进程 常驻内存
    }
#ifdef LOSCFG_SECURITY_VID
    status = VidMapListInit(processCB);
    if (status != LOS_OK) {
        PRINT_ERR("VidMapListInit failed!\n");
        return LOS_ENOMEM;
    }
#endif
#ifdef LOSCFG_SECURITY_CAPABILITY
    OsInitCapability(processCB);
#endif
    if (OsSetProcessName(processCB, name) != LOS_OK) {
        return LOS_ENOMEM;
    }
    return LOS_OK;
}
LosVmSpace *LOS_GetKVmSpace(VOID)
{
return &g_kVmSpace;
}
```
从代码可以看出,内核空间固定只有一个g_kVmSpace,而每个用户进程的虚拟内存空间都是独立的。请细品! 
### task是如何申请内存的?
task的主体是来自进程池,task池是统一分配的,怎么创建task池的去翻系列篇里的文章。这里task只需要申请stack空间,还是直接上看源码吧,用OsUserInitProcess函数看应用程序的main() 是如何被内核创建任务和运行的。
```cpp
//所有的用户进程都是使用同一个用户代码段描述符和用户数据段描述符,它们是__USER_CS和__USER_DS,也就是每个进程处于用户态时,它们的CS寄存器和DS寄存器中的值是相同的。当任何进程或者中断异常进入内核后,都是使用相同的内核代码段描述符和内核数据段描述符,它们是__KERNEL_CS和__KERNEL_DS。这里要明确记得,内核数据段实际上就是内核态堆栈段。
LITE_OS_SEC_TEXT_INIT UINT32 OsUserInitProcess(VOID)
{
    INT32 ret;
    UINT32 size;
    TSK_INIT_PARAM_S param = { 0 };
    VOID *stack = NULL;
    VOID *userText = NULL;
    CHAR *userInitTextStart = (CHAR *)&__user_init_entry;//代码区开始位置 ,所有进程
    CHAR *userInitBssStart = (CHAR *)&__user_init_bss;// 未初始化数据区(BSS)。在运行时改变其值
    CHAR *userInitEnd = (CHAR *)&__user_init_end;// 结束地址
    UINT32 initBssSize = userInitEnd - userInitBssStart;
    UINT32 initSize = userInitEnd - userInitTextStart;
    LosProcessCB *processCB = OS_PCB_FROM_PID(g_userInitProcess);
    ret = OsProcessCreateInit(processCB, OS_USER_MODE, "Init", OS_PROCESS_USERINIT_PRIORITY);// 初始化用户进程,它将是所有应用程序的父进程
    if (ret != LOS_OK) {
        return ret;
    }
    userText = LOS_PhysPagesAllocContiguous(initSize >> PAGE_SHIFT);// 分配连续的物理页
    if (userText == NULL) {
        ret = LOS_NOK;
        goto ERROR;
    }
    (VOID)memcpy_s(userText, initSize, (VOID *)&__user_init_load_addr, initSize);// 安全copy 经加载器load的结果 __user_init_load_addr -> userText
    ret = LOS_VaddrToPaddrMmap(processCB->vmSpace, (VADDR_T)(UINTPTR)userInitTextStart, LOS_PaddrQuery(userText),
                               initSize, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE |
                               VM_MAP_REGION_FLAG_PERM_EXECUTE | VM_MAP_REGION_FLAG_PERM_USER);// 虚拟地址与物理地址的映射
    if (ret < 0) {
        goto ERROR;
    }
    (VOID)memset_s((VOID *)((UINTPTR)userText + userInitBssStart - userInitTextStart), initBssSize, 0, initBssSize);// 除了代码段,其余都清0
    stack = OsUserInitStackAlloc(g_userInitProcess, &size);// 初始化堆栈区
    if (stack == NULL) {
        PRINTK("user init process malloc user stack failed!\n");
        ret = LOS_NOK;
        goto ERROR;
    }
    param.pfnTaskEntry = (TSK_ENTRY_FUNC)userInitTextStart;// 从代码区开始执行,也就是应用程序main 函数的位置
    param.userParam.userSP = (UINTPTR)stack + size;// 指向栈底
    param.userParam.userMapBase = (UINTPTR)stack;// 栈顶
    param.userParam.userMapSize = size;// 栈大小
    param.uwResved = OS_TASK_FLAG_PTHREAD_JOIN;// 可结合的(joinable)能够被其他线程收回其资源和杀死
    ret = OsUserInitProcessStart(g_userInitProcess, &param);// 创建一个任务,来运行main函数
    if (ret != LOS_OK) {
        (VOID)OsUnMMap(processCB->vmSpace, param.userParam.userMapBase, param.userParam.userMapSize);
        goto ERROR;
    }
    return LOS_OK;
ERROR:
    (VOID)LOS_PhysPagesFreeContiguous(userText, initSize >> PAGE_SHIFT);//释放物理内存块
    OsDeInitPCB(processCB);//删除PCB块
    return ret;
}
```
所有的用户进程都是通过init进程 fork来的, 可以看到创建进程的同时创建了一个task, 入口函数就是代码区的第一条指令,也就是应用程序 main函数。这里再说下stack的大小,不同空间下的task栈空间是不一样的,鸿蒙内核中有三种栈空间size,如下
```cpp
#define LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE SIZE(0x800)//内核进程,运行在内核空间2K
#define OS_USER_TASK_SYSCALL_SATCK_SIZE 0x3000 //用户进程,通过系统调用创建的task运行在内核空间的 12K
#define OS_USER_TASK_STACK_SIZE 0x100000//用户进程运行在用户空间的1M
```
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
## 鸿蒙虚拟内存全景图
![](https://img-blog.csdnimg.cn/20201029221604209.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
## 再看鸿蒙用户空间全景图
![](https://img-blog.csdnimg.cn/20201029222858522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
以上两图是笔者阅读完鸿蒙内核源码内存模块所绘制,[给鸿蒙内核源码逐行加上中文注释 【 Gitee仓](https://gitee.com/weharmony/kernel_liteos_a_note) | [CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note) | [Github仓](https://github.com/kuangyufei/kernel_liteos_a_note) | [Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)已正式上线,四大码仓每日同步更新。更多图在仓库中用 @note_pic 搜索查看。
## 内存在内核的比重极大
内存模块占了鸿蒙内核约15%代码量, 近50个文件,非常复杂。想想也是,内存何等重要,内核本身和应用程序一样,也是程序,也要在内存中运行, 大家都在一个窝里吃饭, 你凭什么就管我了, 凭什么的问题会在系列篇的最后结尾 鸿蒙内核源码分析(主奴机制篇) 中详细介绍,  内核就是皇上,应用进程就是奴才。 奴才们被皇上捏的死死的.  按不住这帮奴才那这主子就不合格, 没有稳定的运作系统还谈什么万里江山。 而本篇要说的内存就是紫禁城!请大家试着想想,我们的实际内存就这么点大, 如何满足众多应用程序的需求? 要看抖音,玩微信,逛京东,吃鸡,作为用户巴不得全打开,还要快速响应不能有时延,这些都要在内存中完成,是怎么做到的呢?内核空间和用户空间如何隔离? 如何防止访问乱串? 如何分配如何释放防止碎片化? 空间不够了又如何置换到硬盘?   想想头都大了。内核这当家的主子真是不容易, 这些都是他要解决的问题, 但欲戴其冠,必承其重,没有两把刷子还当什么皇上. 
## 先说如果没有内存管理会怎样?
       那就是个奴强主弱的时代!紫禁城乱成一锅粥了。奴才们个个能把主子给活活踩死,  想想主奴不分,吃喝拉撒睡都在一起。称兄道弟,平起平坐能干成个啥? 没规矩不成方圆嘛,这事业肯定搞不大,单片机时代就是这种情况. 裸机编程,指针可以随便乱飞,数据可以随意覆盖,没有划定边界,没有明确职责,没有特权指令,没有地址保护,你还想像java开发一样,只管new内存,不去释放,应用可以随便崩但系统跑的妥妥的?想的美! 直接系统死机,甚至开机都开不了,主板直接报废了. 所以不能运行很复杂的程序,尽量可控,队伍一旦大了就不好带了,方法得换, 游击队的做法不适合规模作战,内存就需要管理了,而且是需要5A级的严格管理。
## 内存管理在管什么?
简单说就是给主子赋能,拥有超级权利,为什么就他有? 因为他先来,掌握了先机!它定好了游戏规则,你们来玩.那有哪些游戏规则呢?
第一: 主奴有别,主子即是裁判又是运动员,主子有主子地方,奴才们有奴才们待的地方,主子可以在你的空间走来走去,但你只能在主人划定的区域活动.奴才把自己玩崩了也只是奴才狗屁了, 但主人和其他人还会是好好的. 主子有所有特权,比如某个奴才太嚣张了,就直接拖到午门问斩。
第二: 奴奴有分,奴才们基本都是平等的,虽有高级和低级奴才区分,但本质都是奴才。奴才之间是不能随意勾连,登门拜访的,防止一块搞政变. 他们都有属于自己的活动空间,而且活动空间还巨大巨大,大到奴才们觉得整个紫荆城都是他们家的,给你这么大空间你干活才有动力,奴才们是铆足了劲一个个尽情的表演各种剧本,有玩电子商务的,有玩游戏的,有搞直播的等等。。。不愧是紫荆城的主人很有一套,明明只有一个紫禁城,硬被他整出了N个紫荆城的感觉。而且这套管理紫禁城的本领还取了个很好听的名字叫:虚拟内存。
鸿蒙虚拟内存全景图 里面的内核虚拟空间是主人专用的,里面放的是主人的资料,数据,奴才永远进不去,kernel heap 也是给主人专用的动态内存空间,管理奴才和日常运作开销很多时候需要动态申请内存,这个是专门用来提供给主人使用的。而所有奴才的空间都在图中叫用户空间的那一块。您没看错,那就是所有奴才们活动的区域。鸿蒙用户空间全景图 是 鸿蒙虚拟内存全景图 的局部用户空间放大图,里面放的是奴才的私人用品,数据,task运行栈区,动态分配内存的堆区,堆区自下而上,栈区自上而下,中间由 虚拟地址<--->物理地址的映射区隔开,所谓映射区其实就是页表(L1,L2)存放的地方。那么问题来了,这么多奴才都在用户空间区里面不挤吗?答案是:真不挤 。咱皇上是谁啊,那手眼通天了去,因为用了一个好帮手解决了这个问题,这个帮手名叫 MMU(李大总管)
## MMU是干什么事的?
看下某度对MMU定义:它是一种负责处理[中央处理器](https://baike.baidu.com/item/%E4%B8%AD%E5%A4%AE%E5%A4%84%E7%90%86%E5%99%A8)(CPU)的[内存](https://baike.baidu.com/item/%E5%86%85%E5%AD%98)访问请求的[计算机硬件](https://baike.baidu.com/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A1%AC%E4%BB%B6)。它的功能包括[虚拟地址](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80)[物理地址](https://baike.baidu.com/item/%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80)的转换(即[虚拟内存](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98)管理)、内存保护、中央处理器[高速缓存](https://baike.baidu.com/item/%E9%AB%98%E9%80%9F%E7%BC%93%E5%AD%98)的控制。通过它的一番操作,把物理空间成倍成倍的放大,他们之间的映射关系存放在页面中。
好像看懂又好像没看懂是吧,到底是干啥的?其实就是个地址映射登记中心。记住这两个字:映射 看下图
![](https://img-blog.csdnimg.cn/2020092619274388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
物理内存可以理解为真实世界的紫禁城,虚拟内存就是被MMU虚拟出来的比物理页面大的多的空间。举例说明大概说明下过程:
有A,B,C 三个奴才来到紫禁城,每个人都很有抱负,主子规定要先跑去登记处登记活动范围,领回来一张表 叫 L1页表,上面说了大半个紫禁城你可以跑动,都是你的,L1页表记录你详细了每个房间的编号。其实奴才们的表拿回来的时候内容都一样,能跑的范围也都一样,一开始表上并没什么内容,内容是你在使用过程中慢慢添加上去的。具体怎么添的呢?举例说明:
先说个前提 在CPU单核的情况下同时只能有一个奴才在做事,CPU双核就是两个人奴才做事,其他人都看着他们做事,等待主人叫到自己的号去再去做事。每个人做事之前先把领的表交给地址映射中心的负责人李大总管。
现轮到A奴才了,它负责美食业务的,需要仓库存放肉,菜。仓库向李大总管申请,李大总管同意了,告诉他编号999号仓库给你用,你的东西可以放里面,并在A奴才的表中添加了一条  A | 999 |88 的记录,李大总管自己也有张表,也记录了一条 A|999|88
但实际情况是A不可能一直运行下去,他的工作很可能被打断了,比如皇上想先看直播再吃饭那A就要先停止,让直播的B来工作了。B当然也有张表要交给李大总管并且B要申请个仓库放直播设备,李大总管一看没地方了,怎么办?很简单把自己的表改成了B|777|88,告诉B 777号仓库可以放设备。并在B的表上登记上了 B|777|88
到这里肯定有人问,里面的 88 是什么意思,88就是紫禁城里真实的88号仓库地址,也叫物理地址,999 和 777 都是虚拟出来的,也叫虚拟地址,根本就不存在。李大总管到底是怎么玩出花来的呢?
答案是这样的,当A重新需要把自己编号999号房的菜拿出来切炒时,李大总管拿A的表一看 999号对应的是 88号真实仓库,再看自己的私有表上88对应的是B|777|88, 说明88号已经有B奴才的东西了,怎么办?简单啊,把B奴才的东西移到城外的仓库,再把A数据从城外移到88号房。这样A就拿到了他认为的是在999号房的菜,A全程都不用知道 88号房的存在,它只认999号是我的仓库,里面会有我的菜就行了,记住这是映射的核心机制!!! 这个过程涉及到两个内存概念:缺页中断和页面置换。缺页中断就是88号房被B占了发出一个中断信号,页面置换就是把88号房的东西先搬到城外B的仓库,又从A仓库的菜搬到88号房。李大总管最后又更新下内部表 A|999|88,代表88号仓又给了A奴才用 。明白了吗?李大总管的私人表叫 TLB(translation lookaside buffer)可翻译为“地址转换后缓冲器”,也可简称为“快表”。简单地说,TLB就是页表的Cache ,具体代码可以去 [给鸿蒙内核源码逐行加上中文注释 【 Gitee仓](https://gitee.com/weharmony/kernel_liteos_a_note) | [CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note) | [Github仓](https://github.com/kuangyufei/kernel_liteos_a_note) | [Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)看代码,里面每一行都加上了中文注释。那奴才们的表存放在哪里呢?还记得上面说的 “所谓映射区其实就是页表(L1,L2)存放的地方”那句话吗?
置换就要有算法,常用的是 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
虚拟内存和物理内存的管理分配方式是不一样的,但都是以页(page)为单位来管理,一页4K字节。
物理内存:段页管理,伙伴算法分配(buddy算法)。LOS_PhysPagesAllocContiguous 这里只到函数级,物理内存的管理和分配不复杂,知道伙伴算法的原理就搞清楚了物理内存的管理。啥是伙伴算法?简单说就是把蛋糕按 2^0,2^1...2^order先切成一块块的, 2^0的统一放一起,2^order的统一放一起,有几个order就用几个链表管理,颗粒粗,分配至少一页起,这句话很重要。
虚拟内存:线性区管理,内存池分配(slab算法)。LOS_MemAlloc 内存池分配,细颗粒分配,从物理内存拿了一页空间,根据需要再割给申请方。
举例说下流程:A用户向虚拟内存申请 1K内存,虚拟内存一看内存池里只有半K(512)了,不够就向物理内存管理方要了一页(4K),再割1K给A。
虚拟内存管理在鸿蒙主要由 VmMapRegion 这个结构体来串联,很复杂,什么红黑树,映射(file,swap),共享内存,访问权限等等都由它来完成。这些在其他篇幅中有详细介绍。
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
## 谁是鸿蒙内核最重要的结构体? 
答案一定是: LOS\_DL\_LIST(双向链表),它长这样.
```cpp
typedef struct LOS_DL_LIST {//双向链表,内核最重要结构体
struct LOS_DL_LIST *pstPrev; /**< Current node's pointer to the previous node *///前驱节点(左手)
struct LOS_DL_LIST *pstNext; /**< Current node's pointer to the next node *///后继节点(右手)
} LOS_DL_LIST;
```
结构体够简单了吧,只有前后两个指向自己的指针,但恰恰是因为太简单,所以才太不简单. 就像氢原子一样,宇宙中无处不在,占比最高,原因是因为它最简单,最稳定!
内核的各自模块都能看到双向链表的身影,下图是各处初始化双向链表的操作,因为太多了,只截取了部分:
![](https://img-blog.csdnimg.cn/20200917171547946.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
很多人问图怎么来的, source insight 4.0 是阅读大型C/C++工程的必备工具,要用4.0否则中文有乱码.
可以豪不夸张的说理解LOS\_DL\_LIST及相关函数是读懂鸿蒙内核的关键。前后指针(注者后续将比喻成一对左右触手)灵活的指挥着系统精准的运行,越是深入分析内核源码,越能感受到内核开发者对LOS\_DL\_LIST非凡的驾驭能力,笔者仿佛看到了无数双手前后相连,拉起了一个个双向循环链表,把指针的高效能运用到了极致,这也许就是编程的艺术吧!这么重要的结构体还是需详细讲解一下.
### 基本概念
双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向其前一个节点的指针。其头指针head是唯一确定的。从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。
### 功能接口
鸿蒙系统中的双向链表模块为用户提供下面几个接口。
![](https://img-blog.csdnimg.cn/img_convert/2991b499e4191cb7fdd733f10e13ae05.png)
请结合下面的代码和图去理解双向链表,不管花多少时间,一定要理解它的插入/删除动作, 否则后续内容将无从谈起. 
```cpp
//将指定节点初始化为双向链表节点
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListInit(LOS_DL_LIST *list)
{
list->pstNext = list;
list->pstPrev = list;
}
//将指定节点挂到双向链表头部
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListAdd(LOS_DL_LIST *list, LOS_DL_LIST *node)
{
node->pstNext = list->pstNext;
node->pstPrev = list;
list->pstNext->pstPrev = node;
list->pstNext = node;
}
//将指定节点从链表中删除,自己把自己摘掉
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListDelete(LOS_DL_LIST *node)
{
node->pstNext->pstPrev = node->pstPrev;
node->pstPrev->pstNext = node->pstNext;
node->pstNext = NULL;
node->pstPrev = NULL;
}
```
![](https://img-blog.csdnimg.cn/20210108142534112.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
### 具体用法
举例 ProcessCB(进程控制块)是描述一个进程的所有信息,其中用到了 7个双向链表,这简直比章鱼还牛逼,章鱼也才四双触手,但进程有7双(14只)触手.
```cpp
typedef struct ProcessCB {
LOS_DL_LIST pendList; /**< Block list to which the process belongs */ //进程所属的阻塞列表,如果因拿锁失败,就由此节点挂到等锁链表上
LOS_DL_LIST childrenList; /**< my children process list */ //孩子进程都挂到这里,形成双循环链表
LOS_DL_LIST exitChildList; /**< my exit children process list */ //那些要退出孩子进程挂到这里,白发人送黑发人。
LOS_DL_LIST siblingList; /**< linkage in my parent's children list */ //兄弟进程链表, 56个民族是一家,来自同一个父进程.
ProcessGroup *group; /**< Process group to which a process belongs */ //所属进程组
LOS_DL_LIST subordinateGroupList; /**< linkage in my group list */ //进程是组长时,有哪些组员进程
UINT32 threadGroupID; /**< Which thread group , is the main thread ID of the process */ //哪个线程组是进程的主线程ID
UINT32 threadScheduleMap; /**< The scheduling bitmap table for the thread group of the
process */ //进程的各线程调度位图
LOS_DL_LIST threadSiblingList; /**< List of threads under this process *///进程的线程(任务)列表
LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the
priority hash table */ //进程的线程组调度优先级哈希表
volatile UINT32 threadNumber; /**< Number of threads alive under this process */ //此进程下的活动线程数
UINT32 threadCount; /**< Total number of threads created under this process */ //在此进程下创建的线程总数
LOS_DL_LIST waitList; /**< The process holds the waitLits to support wait/waitpid *///进程持有等待链表以支持wait/waitpid
} LosProcessCB;
```
看个简单点的 pendList表示这个进程中所有被阻塞的任务(task)都会挂到这个链表上管理. 任务阻塞的原因很多,可能是申请互斥锁失败,可能等待事件读消息队列,还可能开了一个定时任务等等.
再来看一个复杂点的 threadPriQueueList\[OS\_PRIORITY\_QUEUE_NUM\] ,这又是干嘛的?从名字可以看出来是线程的队列链表,在鸿蒙内核线程就是任务(task),任务分等了32个优先级,同级的任务放在同一个双向链表中, 32级就是32个双向链表,所以是个链表数组,每条链表中存放的是已就绪等待被调度的任务.
双向链表是内核最重要的结构体,精读内核的路上它会反复的映入你的眼帘,理解它是理解内存运作的关键所在!
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
### 本篇是必读篇,太重要了!
# 前言
本篇只讲一个故事,听完希望大家能把故事的场景过程记在脑子里,系列文章会反复的提这个故事,在后续具体源码解读过程中都会去用故事里的细节导读源码。如果看了也喜欢也请分享给更多的人知道这个故事。故事就得有个名字,就叫张大爷的故事吧,故事开始。
## 场馆介绍
   某地有一个运动场馆,分成室内馆(400平米)和室外馆(4万平米),管理处在室内馆,那是工作人员办公的地方,非工作人员不得进入。场馆的定位是供本地公司/组织/团体安排举办各种活动使用的,在舞台上表演(统称舞台剧),同时表演的舞台剧只能一个,但因为生意太好,申请人太多了,所以用馆要先申请,场馆里面有一座永远很准时,不会停的大钟表,每十分钟就自动响一次,场馆里有很多的资源,有篮球,酒馆,小卖部,桌椅,还有演员(人也算资源),反正就是应有尽有,但是数量有限,资源由管理处统一管理,这些资源也得先申请才能使用.场地外有个大屏幕,屏幕实时对外公布场馆舞台剧情况,屏幕内容如下:
|舞台剧名|优先级|状态|进行中节目|就绪节目|
| ------:|------:|------:|------:|------:|
|管理处| 0 |正在工作|打扫场地卫生|无|
|三国演义| 19|已就绪|无|骂死王朗|
|淘宝直播| 20|已就绪|无|薇娅9点直播|
没错,场馆的内部工作也是个剧,只不过它的优先级最高.而且注意这里只展示正在和就绪的剧情节目,就绪是万事俱备,只欠登台表演的意思.例如表中有两个剧都准备好了,组成了一个就绪队列,都等着管理处打扫完卫生后表演,但同时只能演一个剧,而三国演义的优先级更高(场馆规定越小的优先级越高),所以不出意外,下一个表演的节目就是三国演义之骂死王朗,这里请记住就绪队列,后续会反复的提它,很重要!
## 如何申请用馆?
用馆者需提交你舞台剧的剧本,剧本可以是玩游戏,拍电视剧,直播电商等等,反正精彩的世界任你书写,场馆内有专人(统称导演)负责跟进你的剧本上演。剧本由各种各样的场景剧组成(统称节目),比如要拍个水浒传的剧本. 被分成武松打虎,西门和金莲那点破事等等节目剧.申请流程是去管理处先填一张电子节目表,节目表有固定的格式,填完点提交你的工作就完成了,接下来就是导演的事了.节目表单格式如下.
| 剧本名称| 节目章回| 内容 |优先级| 所需资源 |状态|
| ------:| -----------:|-----------: |-----------: |-----------: |-----------: |
| 水浒传| 第18回 | 武松打虎|12|武松,老虎一只,酒18碗|未开始|
| 水浒传| 第28回 | 西门和金莲那点破事|2|西门庆,金莲,王婆,一个炕|未开始|
| 水浒传| 第36回 | 武松拳打蒋门神|14|武松,蒋门神,猪肉|未开始|
故事写到这里,大家脑子里有个画面了吧,记住这两张表,继续走起。
## 场馆是怎么运营的?
     场馆都会给每个用馆单位发个标号代表你使用场馆的优先级,剧本中每个场景节目也有优先级,都是0级最高,31级最低,这里比如水浒传优先级为8,西门庆和金莲那点破事节目为2,节目资源是需要两位主角(西门,金莲)和王婆,一个炕等资源,这些资源要向场馆负责人申请好,节目资源申请到位了就可以进入就绪队列,如果你的剧本里没有一个节目的资源申请到了那对不起您连排号的资格都没有。这里假如水浒传审核通过,并只有西门大官人节目资源申请成功,而管理处卫生打扫完了,以上两个表格的内容将做如下更新
|舞台剧名|优先级|状态|进行中节目|就绪节目|
| ------:|------:|------:|------:|------:|
|水浒传| 8|正在进行|西门和金莲那点破事|无|
|三国演义| 19|已就绪|无|骂死王朗|
|淘宝直播| 20|已就绪|无|薇娅9点直播|
注意虽然三国演义先来,但此时水浒传排在三国的前面,是因为它的优先级高,优先级分32级,0最高,31最低.
| 剧本名称| 节目章回| 内容 |优先级| 所需资源 |状态|表演位置|
| ------:| -----------:|-----------: |-----------: |-----------: |-----------: |-----------: |
| 水浒传| 第18回|武松打虎|12|武松,老虎一只,酒18碗|未开始|暂无|
| 水浒传| 第28回|西门和金莲那点破事|2|西门,金莲,王婆,炕一个|正在进行|西门火急火燎的跑进金莲屋内|
| 水浒传| 第36回| 武松拳打蒋门神|14|武松,蒋门神,猪肉|未开始|暂无|
注意看表中状态的变化和优先级,一个是剧本的优先级,一个是同一个剧本中节目的优先级.而之前优先级最高的管理处,因为没有其他节目要运行,所以移出了就绪队列.
## 表演的过程是怎样的?
    场馆会根据节目上的内容把节目演完。每个节目十分钟,时间到了要回去重新排队,如果还是你就可以继续你的表演。但这里经常会有异常情况发生,比如上级领导给场馆来个电话临时有个更高优先级节目要插进来,没办法西门的好事要先停止,场地要让给别人办事,西门得回到就绪队列排队去,但请放心会在你西门退场前记录下来表演到哪个位置了(比如:西门官人已脱完鞋),以便回来了继续接着表演。这里如果管理处接待完了上峰领导后,并且西门的优先级还是最高的就先还原现场再继续上次被打断的地方办事就完了,绝不重复西门前面的准备工作,否则西门绝不答应。节目表演完所有资源要回收,这个节目从此消亡,如果你剧本里所有节目都表演完了,那你的整个剧本也可以拜拜了,导演退出又可以去接下一部戏了.
这里调整下当西门和金莲那点破事被场馆紧急电话打断后表的变化是怎样的,如下:
| 剧本名称|优先级|状态|进行中节目|就绪节目|
| ------:|------:|------:|------:|------:|
|管理处| 0 |正在工作|接听上级电话|无|
|水浒传| 8|已就绪|无|西门和金莲那点破事|
|三国演义| 19|已就绪|无|骂死王朗|
|淘宝直播| 20|已就绪|无|薇娅9点直播|
| 剧本名称| 节目章回| 内容 |优先级| 所需资源 |状态|表演位置|
| ------:| -----------: |-----------: |-----------: |-----------: |-----------: |-----------: |
| 水浒传| 第18回|武松打虎|12|武松,老虎一只,酒18碗|未开始|暂无|
| 水浒传| 第28回|西门和金莲那点破事|2|西门庆,金莲,王婆,一个炕|就绪|西门官人脱完鞋|
| 水浒传| 第36回|武松拳打蒋门神|14|武松,蒋门神,猪肉|未开始|暂无|
## 表演给谁看呢?
我们就是一个个的观众呀,游戏公司设计了游戏的剧本,电商公司设计了电商剧本,场馆会按你的剧本来表演,当然也可以互动,表演的场景需要观众操作时,观众在外面可以操作,发送指令。想想你玩游戏输入名字登录的场景。场馆里面有三个团队,张大爷团队负责导演剧本,王场馆负责场地的使用规划的,李后勤负责搞搞后勤.
## 张大爷团队做什么的?
上面这些工作都是张大爷团队的工作,接待剧本,管理剧本清单,指派导演跟进,申请节目资源,调整剧本优先级,控制时间,以使舞台能被公平公正的被调度使用等等
## 王场馆是做什么的?
看名字能知道负责场地用度的,你想想这么多节目,场地只有这么点,怎么合理的规划才能效率最大化呢,这是王场馆的工作,不在这个故事里说明,后续有专门讲王场馆如何高效的管理内外场地.
## 李后勤是做什么的?
场馆每天的开业,歇业,场地清理,对外事务,接听电话,有人闹事了怎么处理,收钱开发票 等等也有很多工作统称为后勤工作要有专门的团队来对接,具体不在这里说明,后续也有专门讲这块的故事.
     
## 故事想说什么呢?
   笔者到底想说什么呢?这就是操作系统的调度机制,熟悉了这个故事就熟悉了鸿蒙系统内核任务调度的工作原理!操作系统就是管理场馆和确保工作人员有序工作的系统解决方案商,外面公司只要提供个剧本,就能按节目单把这台戏演好给广大观众观看。有了这个故事垫底,鸿蒙内核源码分析系列就有了一个非常好的开始基础。
真的是这样的吗?必须的。
## 内核和故事的关系映射
| 故事概念|内核概念|备注|
| ------:| ------:|------:|
| 脚本| 程序 | 一个脚本一个负责人,跑起来的程序叫进程 |
| 脚本负责人| 进程 |进程记录脚本整个运行过程,是资源管理单元,任务也是一种资源|
| 节目| 线程/任务 | 任务记录节目的整个运行过程,任务是调度的单元|
| 西门被打断| 保存现场 | 本质是保存寄存器(PC,LR,FP,SP)的状态 |
| 西门继续来| 恢复现场 | 本质是还原寄存器(PC,LR,FP,SP)的状态 |
| 表演场地| 用户空间 | 所有节目都在同一块场地表演 |
| 管理处| 内核空间 | 管理处非工作人员不得入内|
| 外部场地| 磁盘空间 | 故事暂未涉及,留在内存故事中讲解 |
| 节目内容| 代码段 | 任务涉及的具体代码段 |
| 管理处的服务| 系统调用 | 软中断实现,切换至内核栈 |
| 场馆大钟| 系统时钟 | 十分钟响一次代表一个节拍(tick) |
| 节目20分钟| 时间片 | 鸿蒙时间片默认 2个tick,20ms|
| 上级电话| 中断 | 硬中断,直接跳到中断处理函数执行 |
| 表演顺序| 优先级 | 进程和线程都是32个优先级,[0-31],从高到低 |
| 张大爷| 进程/线程管理 | 抢占式调度,优先级高者运行 |
| 王场馆| 内存管理 | 虚拟内存,内存分配,缺页置换 == |
| 李后勤| 异常接管 | 中断,跟踪,异常接管 == |
## 请牢记这个故事
当然还有很多的细节在故事里没有讲到,比如王场馆和李后勤的工作细节,还有后续故事一一拆解.太细不可能真的在一个故事里全面讲完,笔者想说的是框架,架构思维,要先有整体框架再顺藤摸瓜寻细节,层层深入,否则很容易钻进死胡同里出不来。读着读着就放弃了,其实真没那么难。当你摸清了整个底层的运作机制再看上层的应用,就会有了拨开云雾见阳光,神清气爽的感觉。具体的我们在后续的章节里一一展开,用这个故事去理解鸿蒙系统内核,没毛病,请务必牢记这个故事。
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
调度算法让CPU在不同的进程和任务之间切换穿梭,但问题是
## **调度算法的驱动力在哪里? 谁负责推着调度算法走?**
有多股力量在推动,但最大的推力应该是:系统时钟.
我们整个学生阶段有个东西很重要,就是校园铃声. 它控制着上课,下课,吃饭,睡觉的节奏.没有它学校的管理就乱套了,老师拖课想拖多久就多久,那可不行,下课铃声一响就是在告诉老师时间到了,该停止了让学生HAPPY去了.
应用开发人员对定时器不会陌生,电商平台的每晚10点准时的秒杀活动没有定时任务是不可能实现的,那内核也一定要提供这样的功能被封装给应用层使用,那这个功能在内核层的表述就是系统时钟,它发出来的节奏叫tick(节拍).
对应张大爷的故事:系统时钟就是场馆的那个大钟,很准时, 每10分响一次,一次就是一个Tick(节拍)
### **鸿蒙内核的节拍频率是怎样的**
```cpp
/**
* @ingroup los_config
* Number of Ticks in one second
*/
#ifndef LOSCFG_BASE_CORE_TICK_PER_SECOND
#define LOSCFG_BASE_CORE_TICK_PER_SECOND 100 //默认每秒100次触发,当然这是可以改的
#endif
```
每秒100个tick,时间单位为10毫秒, 即每秒调用时钟中断处理程序100次.
### **时钟中断处理程序是怎样的?**
```cpp
/*
* Description : Tick interruption handler
*///节拍中断处理函数 ,鸿蒙默认10ms触发一次
LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
{
//...
OsTimesliceCheck();//进程和任务的时间片检查
OsTaskScan(); /* task timeout scan *///任务扫描
#if (LOSCFG_BASE_CORE_SWTMR == YES)
OsSwtmrScan();//定时器扫描,看是否有超时的定时器
#endif
}
```
它主要干了三件事情
### **第一:检查当前任务的时间片,任务执行一次分配多少时间呢?答案是2个时间片,即 20ms.**
```cpp
#ifndef LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT
#define LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT 2 //2个时间片,20ms
#endif
//检查进程和任务的时间片,如果没有时间片了直接调度
LITE_OS_SEC_TEXT VOID OsTimesliceCheck(VOID)
{
LosTaskCB *runTask = NULL;
LosProcessCB *runProcess = OsCurrProcessGet();//获取当前进程
if (runProcess->policy != LOS_SCHED_RR) {//进程调度算法是否是抢占式
goto SCHED_TASK;//进程不是抢占式调度直接去检查任务的时间片
}
if (runProcess->timeSlice != 0) {//进程还有时间片吗?
runProcess->timeSlice--;//进程时间片减少一次
if (runProcess->timeSlice == 0) {//没有时间片了
LOS_Schedule();//进程时间片用完,发起调度
}
}
SCHED_TASK:
runTask = OsCurrTaskGet();//获取当前任务
if (runTask->policy != LOS_SCHED_RR) {//任务调度算法是否是抢占式
return;//任务不是抢占式调度直接结束检查
}
if (runTask->timeSlice != 0) {//任务还有时间片吗?
runTask->timeSlice--;//任务时间片也减少一次
if (runTask->timeSlice == 0) {//没有时间片了
LOS_Schedule();//任务时间片用完,发起调度
}
}
}
```
### **第二:扫描任务,主要是检查被阻塞的任务是否可以被重新调度**
```cpp
LITE_OS_SEC_TEXT VOID OsTaskScan(VOID)
{
SortLinkList *sortList = NULL;
LosTaskCB *taskCB = NULL;
BOOL needSchedule = FALSE;
UINT16 tempStatus;
LOS_DL_LIST *listObject = NULL;
SortLinkAttribute *taskSortLink = NULL;
taskSortLink = &OsPercpuGet()->taskSortLink;//获取任务的排序链表
taskSortLink->cursor = (taskSortLink->cursor + 1) & OS_TSK_SORTLINK_MASK;
listObject = taskSortLink->sortLink + taskSortLink->cursor;//只处理这个游标上的链表,因为系统对超时任务都已经规链表了.
//当任务因超时而挂起时,任务块处于超时排序链接上,(每个cpu)和ipc(互斥锁、扫描电镜等)的块同时被唤醒
/*不管是超时还是相应的ipc,它都在等待。现在使用synchronize sortlink precedure,因此整个任务扫描需要保护,防止另一个核心同时删除sortlink。
* When task is pended with timeout, the task block is on the timeout sortlink
* (per cpu) and ipc(mutex,sem and etc.)'s block at the same time, it can be waken
* up by either timeout or corresponding ipc it's waiting.
*
* Now synchronize sortlink preocedure is used, therefore the whole task scan needs
* to be protected, preventing another core from doing sortlink deletion at same time.
*/
LOS_SpinLock(&g_taskSpin);
if (LOS_ListEmpty(listObject)) {
LOS_SpinUnlock(&g_taskSpin);
return;
}
sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);//拿本次Tick对应链表的SortLinkList的第一个节点sortLinkNode
ROLLNUM_DEC(sortList->idxRollNum);//滚动数--
while (ROLLNUM(sortList->idxRollNum) == 0) {//找到时间到了节点,注意这些节点都是由定时器产生的,
LOS_ListDelete(&sortList->sortLinkNode);
taskCB = LOS_DL_LIST_ENTRY(sortList, LosTaskCB, sortList);//拿任务,这里的任务都是超时任务
taskCB->taskStatus &= ~OS_TASK_STATUS_PEND_TIME;
tempStatus = taskCB->taskStatus;
if (tempStatus & OS_TASK_STATUS_PEND) {
taskCB->taskStatus &= ~OS_TASK_STATUS_PEND;
#if (LOSCFG_KERNEL_LITEIPC == YES)
taskCB->ipcStatus &= ~IPC_THREAD_STATUS_PEND;
#endif
taskCB->taskStatus |= OS_TASK_STATUS_TIMEOUT;
LOS_ListDelete(&taskCB->pendList);
taskCB->taskSem = NULL;
taskCB->taskMux = NULL;
} else {
taskCB->taskStatus &= ~OS_TASK_STATUS_DELAY;
}
if (!(tempStatus & OS_TASK_STATUS_SUSPEND)) {
OS_TASK_SCHED_QUEUE_ENQUEUE(taskCB, OS_PROCESS_STATUS_PEND);
needSchedule = TRUE;
}
if (LOS_ListEmpty(listObject)) {
break;
}
sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
}
LOS_SpinUnlock(&g_taskSpin);
if (needSchedule != FALSE) {//需要调度
LOS_MpSchedule(OS_MP_CPU_ALL);//核间通讯,给所有CPU发送调度信号
LOS_Schedule();//开始调度
}
}
```
### **第三:定时器扫描,看是否有超时的定时器**
```cpp
/*
* Description: Tick interrupt interface module of software timer
* Return : LOS_OK on success or error code on failure
*///OsSwtmrScan 由系统时钟中断处理函数调用
LITE_OS_SEC_TEXT VOID OsSwtmrScan(VOID)//扫描定时器,如果碰到超时的,就放入超时队列
{
SortLinkList *sortList = NULL;
SWTMR_CTRL_S *swtmr = NULL;
SwtmrHandlerItemPtr swtmrHandler = NULL;
LOS_DL_LIST *listObject = NULL;
SortLinkAttribute* swtmrSortLink = &OsPercpuGet()->swtmrSortLink;//拿到当前CPU的定时器链表
swtmrSortLink->cursor = (swtmrSortLink->cursor + 1) & OS_TSK_SORTLINK_MASK;
listObject = swtmrSortLink->sortLink + swtmrSortLink->cursor;
//由于swtmr是在特定的sortlink中,所以需要很小心的处理它,但其他CPU Core仍然有机会处理它,比如停止计时器
/*
* it needs to be carefully coped with, since the swtmr is in specific sortlink
* while other cores still has the chance to process it, like stop the timer.
*/
LOS_SpinLock(&g_swtmrSpin);
if (LOS_ListEmpty(listObject)) {
LOS_SpinUnlock(&g_swtmrSpin);
return;
}
sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
ROLLNUM_DEC(sortList->idxRollNum);
while (ROLLNUM(sortList->idxRollNum) == 0) {
sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
LOS_ListDelete(&sortList->sortLinkNode);
swtmr = LOS_DL_LIST_ENTRY(sortList, SWTMR_CTRL_S, stSortList);
swtmrHandler = (SwtmrHandlerItemPtr)LOS_MemboxAlloc(g_swtmrHandlerPool);//取出一个可用的软时钟处理项
if (swtmrHandler != NULL) {
swtmrHandler->handler = swtmr->pfnHandler;
swtmrHandler->arg = swtmr->uwArg;
if (LOS_QueueWrite(OsPercpuGet()->swtmrHandlerQueue, swtmrHandler, sizeof(CHAR *), LOS_NO_WAIT)) {
(VOID)LOS_MemboxFree(g_swtmrHandlerPool, swtmrHandler);
}
}
if (swtmr->ucMode == LOS_SWTMR_MODE_ONCE) {
OsSwtmrDelete(swtmr);
if (swtmr->usTimerID < (OS_SWTMR_MAX_TIMERID - LOSCFG_BASE_CORE_SWTMR_LIMIT)) {
swtmr->usTimerID += LOSCFG_BASE_CORE_SWTMR_LIMIT;
} else {
swtmr->usTimerID %= LOSCFG_BASE_CORE_SWTMR_LIMIT;
}
} else if (swtmr->ucMode == LOS_SWTMR_MODE_NO_SELFDELETE) {
swtmr->ucState = OS_SWTMR_STATUS_CREATED;
} else {
swtmr->ucOverrun++;
OsSwtmrStart(swtmr);
}
if (LOS_ListEmpty(listObject)) {
break;
}
sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
}
LOS_SpinUnlock(&g_swtmrSpin);
}
```
### **最后看调度算法的实现**
```cpp
//调度算法的实现
VOID OsSchedResched(VOID)
{
LosTaskCB *runTask = NULL;
LosTaskCB *newTask = NULL;
LosProcessCB *runProcess = NULL;
LosProcessCB *newProcess = NULL;
LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//必须持有任务自旋锁,自旋锁是不是进程层面去抢锁,而是CPU各自核之间去争夺锁
if (!OsPreemptableInSched()) {//是否置了重新调度标识位
return;
}
runTask = OsCurrTaskGet();//获取当前任务
newTask = OsGetTopTask();//获取优先级最最最高的任务
/* always be able to get one task */
LOS_ASSERT(newTask != NULL);//不能没有需调度的任务
if (runTask == newTask) {//当前任务就是最高任务,那还调度个啥的,直接退出.
return;
}
runTask->taskStatus &= ~OS_TASK_STATUS_RUNNING;//当前任务状态位置成不在运行状态
newTask->taskStatus |= OS_TASK_STATUS_RUNNING;//最高任务状态位置成在运行状态
runProcess = OS_PCB_FROM_PID(runTask->processID);//通过进程ID索引拿到进程实体
newProcess = OS_PCB_FROM_PID(newTask->processID);//同上
OsSchedSwitchProcess(runProcess, newProcess);//切换进程,里面主要涉及进程空间的切换,也就是MMU的上下文切换.
#if (LOSCFG_KERNEL_SMP == YES)//CPU多核的情况
/* mask new running task's owner processor */
runTask->currCpu = OS_TASK_INVALID_CPUID;//当前任务不占用CPU
newTask->currCpu = ArchCurrCpuid();//让新任务占用CPU
#endif
(VOID)OsTaskSwitchCheck(runTask, newTask);//切换task的检查
#if (LOSCFG_KERNEL_SCHED_STATISTICS == YES)
OsSchedStatistics(runTask, newTask);
#endif
if ((newTask->timeSlice == 0) && (newTask->policy == LOS_SCHED_RR)) {//没有时间片且是抢占式调度的方式,注意 非抢占式都不需要时间片的.
newTask->timeSlice = LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT;//给新任务时间片 默认 20ms
}
OsCurrTaskSet((VOID*)newTask);//设置新的task为CPU核的当前任务
if (OsProcessIsUserMode(newProcess)) {//用户模式下会怎么样?
OsCurrUserTaskSet(newTask->userArea);//设置task栈空间
}
PRINT_TRACE("cpu%d run process name: (%s) pid: %d status: %x threadMap: %x task name: (%s) tid: %d status: %x ->\n"
" new process name: (%s) pid: %d status: %x threadMap: %x task name: (%s) tid: %d status: %x!\n",
ArchCurrCpuid(),
runProcess->processName, runProcess->processID, runProcess->processStatus,
runProcess->threadScheduleMap, runTask->taskName, runTask->taskID, runTask->taskStatus,
newProcess->processName, newProcess->processID, newProcess->processStatus,
newProcess->threadScheduleMap, newTask->taskName, newTask->taskID, newTask->taskStatus);
/* do the task context switch */
OsTaskSchedule(newTask, runTask);//切换任务上下文,注意OsTaskSchedule是一个汇编函数 见于 los_dispatch.s
}
```
最后的 OsTaskSchedule 是由汇编实现的,后续会详细讲解各个汇编文件,除了tick 会触发调度,还有哪些情况会触发调度?
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
# **鸿蒙内核源码注释中文版 | 深挖内核地基工程**
[![star](https://gitee.com/weharmony/kernel_liteos_a_note/badge/star.svg?theme=dark)](https://gitee.com/weharmony/kernel_liteos_a_note)[![fork](https://gitee.com/weharmony/kernel_liteos_a_note/badge/fork.svg?theme=dark)](https://gitee.com/weharmony/kernel_liteos_a_note)
每个码农,学职生涯,都应精读一遍内核源码.是浇筑计算机知识大厦的地基工程,地基纵深坚固程度,决定了大厦能盖多高。为何一定要精读?因为内核代码本身并不太多,都是浓缩的精华,精读就是让各个知识点高频出现,闪爆大脑,短时间内容易结成一张高浓度,高密度的底层网,形成永久大脑记忆。跟骑单车一样,一旦学会,即便多年不骑,照样跨上就走,游刃有余。
## **做了些什么呢?**
**[WeHarmony/kernel\_liteos\_a_note](https://gitee.com/weharmony/kernel_liteos_a_note)** 是在鸿蒙官方开源项目 **[OpenHarmony/kernel\_liteos\_a](https://gitee.com/openharmony/kernel_liteos_a)** 基础上给源码加上中文注解的版本,目前几大核心模块加注已基本完成, **整体加注完成70%**, 正持续加注完善中 ...
- ### **为何想给鸿蒙内核源码加上中文注释**
源于大学时阅读linux 2.6 内核痛苦经历,一直有个心愿,如何让更多对内核感兴趣的同学减少阅读时间,加速对计算机系统级的理解,而不至于过早的放弃.但因过程种种,多年一直没有行动,基本要放弃这件事了. 恰逢 2020/9/10 鸿蒙正式开源,重新激活了多年的心愿,就有那么点一发不可收拾了. :|P
- ### **致敬鸿蒙内核开发者**
感谢开放原子开源基金会,鸿蒙内核开发者提供了如此优秀的源码,一了多年的夙愿,津津乐道于此.精读内核源码当然是件很困难的事,时间上要以月甚至年为单位,但正因为很难才值得去做! 干困难事,必有所得. 专注聚焦,必有所获. 致敬内核开发者,从内核一行行的代码中能深深体会到开发者各中艰辛与坚持,及鸿蒙生态对未来的价值,可以毫不夸张的说鸿蒙内核源码可作为大学C语言,数据结构,操作系统,汇编语言 四门课程的教学项目.如此宝库,不深入研究实在是暴殄天物,于心不忍,注者坚信鸿蒙大势所趋,未来可期,是其坚定的追随者和传播者.
- ### **加注方式是怎样的?**
因鸿蒙内核6W+代码量,本身只有较少的注释, 中文注解以不对原有代码侵入为前提,源码中所有英文部分都是原有注释,所有中文部分都是中文版的注释,尽量不去增加代码的行数,不破坏文件的结构,试图把知识讲透彻,注释多类似以下的方式:
在每个模块的.c文件开始位置先对模块功能做整体的介绍,例如异常接管模块注解如图所示:
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/ycjg.png)
注解过程中查阅了很多的资料和书籍,在具体代码处都附上了参考链接.
而函数级注解会详细到重点行,甚至每一行, 例如申请互斥锁的主体函数,不可谓不重要,而官方注释仅有一行,如图所示
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/sop.png)
另外画了一些字符图方便理解,直接嵌入到头文件中,比如虚拟内存的全景图,因没有这些图是很难理解虚拟内存是如何管理的.
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/vm.png)
- ### **理解内核的三个层级**
注者认为理解内核可分三个层级:
第一: **普通概念映射级** 这一级不涉及专业知识,用大众所熟知的公共认知就能听明白是个什么概念,也就是说用一个普通人都懂的概念去诠释或者映射一个他们从没听过的概念.说别人能听得懂的话这很重要!!! 一个没学过计算机知识的卖菜大妈就不可能知道内核的基本运作了吗? 不一定!,在系列篇中试图用 **[鸿蒙内核源码分析(必读故事篇) | 西门和金莲的那点破事【 CSDN](https://blog.csdn.net/kuangyufei/article/details/108745174) [| OSCHINA](https://my.oschina.net/u/3751245/blog/write/4634668) [| HarmonyOS 】](https://weharmony.github.io/)** 去引导这一层级的认知,希望能卷入更多的人来关注基础软件,尤其是那些资本大鳄,加大对基础软件的投入.
第二: **专业概念抽象级** 对抽象的专业逻辑概念具体化认知, 比如虚拟内存,老百姓是听不懂的,学过计算机的人都懂,具体怎么实现的很多人又都不懂了,但这并不妨碍成为一个优秀的上层应用程序员,因为虚拟内存已经被抽象出来,目的是要屏蔽上层对它的现实认知.试图用 **[鸿蒙源码分析系列篇 【 CSDN](https://blog.csdn.net/kuangyufei/article/details/108727970) [| OSCHINA](https://my.oschina.net/u/3751245/blog/4626852) [| HarmonyOS 】](https://weharmony.github.io/)** 去拆解那些已经被抽象出来的专业概念, 希望能卷入更多对内核感兴趣的应用软件人才流入基础软件生态, 应用软件咱们是无敌宇宙,但基础软件却很薄弱.
第三: **具体微观代码级** 这一级是具体到每一行代码的实现,到了用代码指令级的地步,这段代码是什么意思?为什么要这么设计? **[kernel\_liteos\_a_note:鸿蒙内核源码注释中文版](https://gitee.com/weharmony/kernel_liteos_a_note)** 试图从细微处去解释代码实现层,英文真的是天生适合设计成编程语言的人类语言,计算机的01码映射到人类世界的26个字母,诞生了太多的伟大奇迹.但我们的母语注定了很大部分人存在着自然语言层级的理解映射,希望注释中文版能让更多爱好者快速的理解内核,共同进步.
- ### **鸿蒙源码分析系列篇**
- [鸿蒙源码分析系列(总目录) | 持续更新中... 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)|[ HarmonyOS 】](https://weharmony.github.io)
* [|- 鸿蒙内核源码分析(用栈方式篇) | 栈是构建底层运行的基础 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/112534331) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4893388)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(用栈方式篇).html)
* [|- 鸿蒙内核源码分析(位图管理篇) | 为何进程和线程都是32个优先级? 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/112394982) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4888467)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(位图管理篇).html)
* [|- 鸿蒙内核源码分析(源码结构篇) | 内核500问你能答对多少? 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/111938348) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4869137)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(源码结构篇).html)
* [|- 鸿蒙内核源码分析(物理内存篇) | 伙伴算法是在卖标准猪肉块吗?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/111765600) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4842408)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(物理内存篇).html)
* [|- 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109437223) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4698384)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存规则篇).html)
* [|- 鸿蒙内核源码分析(源码注释篇) | 精读内核源码有哪些好处?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109251754) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4686747)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(源码注释篇).html)
* [|- 鸿蒙内核源码分析(内存映射篇) | 虚拟内存-物理内存是如何映射的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109032636) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4694841)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存映射篇).html)
* [|- 鸿蒙内核源码分析(内存汇编篇) | 什么是虚拟内存的实现基础?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108994081) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4692156)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存汇编篇).html)
* [|- 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108989906) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4646802)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存分配篇).html)
* [|- 鸿蒙内核源码分析(内存管理篇) | 鸿蒙虚拟内存全景图是怎样的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108821442) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4652284)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存管理篇).html)
* [|- 鸿蒙内核源码分析(内存概念篇) | 虚拟内存虚在哪里?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108723672) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4646802)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存概念篇).html)
* [|- 鸿蒙内核源码分析(必读故事篇) | 西门和金莲的那点破事【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108745174) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4634668)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(必读故事篇).html)
* [|- 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108705968) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4623040)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(调度机制篇).html)
* [|- 鸿蒙内核源码分析(调度队列篇) | 就绪队列对调度的作用【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108626671) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4606916)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(调度队列篇).html)
* [|- 鸿蒙内核源码分析(任务管理篇) | 任务是内核调度的单元【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108621428) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4603919)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(任务管理篇).html)
* [|- 鸿蒙内核源码分析(时钟任务篇) | 触发调度最大的动力来自哪里?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108603468) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4574493)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(时钟管理篇).html)
* [|- 鸿蒙内核源码分析(进程管理篇) | 进程是内核资源管理单元【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108595941) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4574429)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(进程管理篇).html)
* [|- 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108585659) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4572304)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(双向链表篇).html)
- ### **通过fork及时同步最新注解内容**
注解几乎占用了所有的空闲时间,每天都会更新,每天都有新感悟,一行行源码在不断的刷新和拓展对内核知识的认知边界. 对已经关注和fork的同学请及时同步最新的注解内容. 内核知识点体量实在太过巨大,过程会反复修正完善,力求言简意赅,词达本意.肯定会有诸多错漏之处,请多包涵. :)
- ### **有哪些特殊的记号**
搜索 **[@note_pic]()** 可查看绘制的全部字符图
搜索 **[@note_why]()** 是尚未看明白的地方,有看明白的,请Pull Request完善
搜索 **[@note_thinking]()** 是一些的思考和建议
搜索 **[@note_#if0]()** 是由第三方项目提供不在内核源码中定义的极为重要结构体,为方便理解而添加的。
搜索 **[@note_good]()** 是给源码点赞的地方
- ### **新增的zzz目录是干什么的?**
中文加注版比官方版无新增文件,只多了一个zzz的目录,里面放了一些文件,它与内核代码无关,大家可以忽略它,取名zzz是为了排在最后,减少对原有代码目录级的侵入,zzz的想法源于微信中名称为AAA的那帮朋友,你的微信里应该也有他们熟悉的身影吧 :|P
- ### **参与贡献**
1. Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> [新建 Pull Request](https://gitee.com/weharmony/kernel_liteos_a_note/pull/new/weharmony:master...weharmony:master)
2. [新建 Issue](https://gitee.com/weharmony/kernel_liteos_a_note/issues)
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
## **[鸿蒙内核源码注释中文版 | 深挖内核地基工程](https://gitee.com/weharmony/kernel_liteos_a_note)**
点击目录和文件查看详细源码中文注解,走进内核的世界.
---
- [kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/)
* [kernel](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/)
+ [base](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/)
+ [core](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/core/) -> []() -> 这个core指的是与CPU core相关的文件
+ [los_bitmap.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/core/los_bitmap.c) -> []() -> 位图管理器有什么作用 ? 在内核常应用于哪些场景 ?
+ [los_process.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/core/los_process.c) -> [鸿蒙内核源码分析(进程管理篇)](https://blog.csdn.net/kuangyufei/article/details/108595941) -> 进程是内核的资源管理单元,它是如何管理 任务, 内存,文件的 ? 进程间是如何协作的 ?
+ [los_sortlink.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/core/los_sortlink.c) -> []() -> 排序链表的实现,它的应用场景是怎样的 ?
+ [los_swtmr.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/core/los_swtmr.c) -> []() -> 内核的定时器是如何实现和管理的 ?
+ [los_sys.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/core/los_sys.c) -> []() -> 几个跟tick相关的转化函数
+ [los_task.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/core/los_task.c) -> [鸿蒙内核源码分析(Task管理篇)](https://blog.csdn.net/kuangyufei/article/details/108661248) -> Task是内核调度的单元,它解决了什么问题 ? 如何调度 ?
+ [los_tick.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/core/los_tick.c) -> [鸿蒙内核源码分析(时钟管理篇)](https://blog.csdn.net/kuangyufei/article/details/108603468) -> 是谁在一直触发调度 ? 硬时钟中断都干了些什么事?
+ [los_timeslice.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/core/los_timeslice.c) -> []() -> 进程和任务能一直占有CPU吗 ? 怎么合理的分配时间 ?
+ [ipc](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/ipc/) -> []() -> 进程间通讯有哪些方式 ? 请说出三种 ? 是如何实现的 ?
+ [los_event.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/ipc/los_event.c) -> []() -> 事件解决了什么问题 ? 怎么管理的 ?
+ [los_futex.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/ipc/base/los_futex.c) -> []() -> futex 是Fast Userspace muTexes的缩写(快速用户空间互斥体),它有什么作用 ?
+ [los_ipcdebug.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/ipc/base/los_ipcdebug.c) -> []() -> 进程间通讯如何调试 ?
+ [los_mux.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/ipc/los_mux.c) -> []() -> 互斥量,有你没我的零和博弈, 为什么需要互斥量 ? 是如何实现的 ?
+ [los_queue.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/ipc/los_queue.c) -> []() -> 内核消息队列是如何实现的 ? 对长度和大小有限制吗 ?
+ [los_queue_debug.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/ipc/los_queue_debug.c) -> []() -> 如何调试消息队列 ?
+ [los_sem.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/ipc/los_sem.c) -> []() -> 信号量解决了什么问题 ? 它的本质是什么 ?
+ [los_sem_debug.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/ipc/los_sem_debug.c) -> []() -> 如何调试信号量 ?
+ [los_signal.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/ipc/los_signal.c) -> []() -> 信号解决了什么问题? 你知道哪些信号 ?
+ [mem](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mem/) -> []() -> 内存管理模块管理系统的内存资源,它是操作系统的核心模块之一
+ [bestfit](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mem/bestfit/) -> []() -> 动态内存管理的优点是按需分配,那缺点又是什么?
+ [los_memory.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mem/bestfit/los_memory.c) -> []() -> 鸿蒙内核中动态内存池由哪三个部分组成 ?
+ [los_multipledlinkhead.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mem/bestfit/los_multipledlinkhead.c) -> []() -> 什么是最佳适应算法? 是如何实现 ?
+ [bestfit_little](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mem/bestfit_little/) -> []() -> bestfit_little算法是在最佳适配算法的基础上加入slab机制形成的算法。
+ [los_heap.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mem/bestfit/los_heap.c) -> []() -> slab算法机制是怎样的? 又是如何实现的 ?
+ [common](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mem/common/) -> []() ->
+ [membox](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mem/membox/) -> []() -> 静态内存池的优点是分配和释放效率高,无碎片, 那缺点呢 ?
+ [los_membox.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mem/membox/los_membox.c) -> []() -> 静态内存有什么用? 是如何实现的?
+ [misc](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/misc/) -> []() ->
+ [kill_shellcmd.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/misc/kill_shellcmd.c) -> []() -> shell命令kill的实现,熟悉的 kill 9 18 的背后发生了什么?
+ [los_misc.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/misc/los_misc.c) -> []() ->
+ [los_stackinfo.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/misc/los_stackinfo.c) -> []() -> 栈有哪些信息 ? 如何检测栈是否异常 ?
+ [mempt_shellcmd.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/misc/mempt_shellcmd.c) -> []() -> 和内存相关的shell命令有哪些 ?
+ [swtmr_shellcmd.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/misc/swtmr_shellcmd.c) -> []() -> 和软时钟相关的shell命令有哪些 ?
+ [sysinfo_shellcmd.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/misc/sysinfo_shellcmd.c) -> []() -> 和系统信息相关的shell命令有哪些 ?
+ [task_shellcmd.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/misc/task_shellcmd.c) -> []() -> 和任务相关的shell命令有哪些 ?
+ [vm_shellcmd.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/misc/vm_shellcmd.c) -> []() -> 和虚拟内存相关的shell命令有哪些 ?
+ [mp](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mp/) -> []() -> MP指支持多处理器的模块
+ [los_lockdep.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mp/los_lockdep.c) -> []() -> 死锁是怎么发生的 ? 如何检测死锁 ?
+ [los_mp.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mp/los_mp.c) -> []() -> 鸿蒙最大支持多少个CPU ? 它们是如何工作的? CPU之间是如何通讯的 ?
+ [los_percpu.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mp/los_percpu.c) -> []() -> CPU有哪些信息 ?
+ [los_stat.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/mp/los_stat.c) -> []() -> CPU的运行信息如何统计 ?
+ [om](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/om/) -> []() ->
+ [los_err.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/om/los_err.c) -> []() ->
+ [sched/sched_sq](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/sched/sched_sq/) -> []() ->
+ [los_priqueue.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/sched/sched_sq/los_priqueue.c) -> [鸿蒙内核源码分析(调度队列篇)](https://blog.csdn.net/kuangyufei/article/details/108626671) -> 为什么只有就绪状态才会有队列 ?
+ [los_sched.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/sched/sched_sq/los_sched.c) -> [鸿蒙内核源码分析(调度机制篇)](https://blog.csdn.net/kuangyufei/article/details/108705968) -> 哪些情况下会触发调度 ? 调度算法是怎样的 ?
+ [vm](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/) -> [鸿蒙内核源码分析(内存规则篇)](https://blog.csdn.net/kuangyufei/article/details/109437223) -> 什么是虚拟内存 ? 虚拟内存全景图是怎样的 ?
+ [los_vm_boot.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/los_vm_boot.c) -> []() -> 开机阶段内存是如何初始化的 ?
+ [los_vm_dump.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/los_vm_dump.c) -> []() -> 如何 dump 内存数据 ?
+ [los_vm_fault.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/los_vm_fault.c) -> []() -> 为什么会缺页 ? 缺页怎么处理 ?
+ [los_vm_filemap.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/los_vm_filemap.c) -> []() -> 文件和内存是如何映射? 什么是 写时拷贝技术(cow) ?
+ [los_vm_iomap.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/los_vm_iomap.c) -> []() -> 设备和内存是如何映射 ?
+ [los_vm_map.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/los_vm_map.c) -> [鸿蒙内核源码分析(内存映射篇)](https://blog.csdn.net/kuangyufei/article/details/109032636) -> 内核空间,用户空间,线性区是如何分配的,虚拟内存<-->物理内存是如何映射的 ?
+ [los_vm_page.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/los_vm_page.c) -> []() -> 什么是物理页框,哪些地方会用到它 ?
+ [los_vm_phys.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/los_vm_phys.c) -> [鸿蒙内核源码分析(物理内存篇)](https://blog.csdn.net/kuangyufei/article/details/111765600) -> 段页式管理,物理内存是如何分配和回收的 ?
+ [los_vm_scan.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/los_vm_scan.c) -> []() -> LRU算法是如何运作的 ?
+ [los_vm_syscall.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/los_vm_syscall.c) -> []() -> 系统调用之内存, 用户进程如何申请内存 ? 底层发生了什么 ?
+ [oom.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/oom.c) -> []() -> 内存溢出是如何检测的 ?
+ [shm.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/base/vm/shm.c) -> []() -> 共享内存是如何实现的 ?
+ [common](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/) -> []() ->
+ [console.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/console.c) -> []() -> 熟悉的控制台是如何实现的 ?
+ [hwi_shell.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/hwi_shell.c) -> []() -> 如何查询硬件中断 ?
+ [los_cir_buf.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/los_cir_buf.c) -> []() -> 环形缓冲区的读写是如何实现的 ? 常用于什么场景下 ?
+ [los_config.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/los_config.c) -> []() -> 内核有哪些配置信息 ?
+ [los_exc_interaction.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/los_exc_interaction.c) -> []() -> 任务出现异常如何检测 ?
+ [los_excinfo.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/los_excinfo.c) -> []() -> 异常有哪些信息 ? 如何记录异常信息 ?
+ [los_hilog.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/los_hilog.c) -> []() -> 内核是如何封装日志的 ?
+ [los_magickey.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/los_magickey.c) -> []() -> 魔法键有什么作用 ?
+ [los_printf.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/los_printf.c) -> []() -> 内核对 printf 做了哪些封装 ?
+ [los_rootfs.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/los_rootfs.c) -> []() -> 什么是根文件系统 ? 为什么需要它 ?
+ [los_seq_buf.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/los_seq_buf.c) -> []() ->
+ [virtual_serial.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/common/virtual_serial.c) -> []() -> 如何实现访问串口如同访问文件一样方便 ?
+ [extended](https://gitee.com/weharmony/kernel_liteos_a_note/kernel/extended/tree/master/) -> []() ->
+ [cppsupport](https://gitee.com/weharmony/kernel_liteos_a_note/kernel/extended/tree/master/cppsupport/) -> []() ->
+ [los_cppsupport.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/cppsupport/los_cppsupport.c) -> []() -> 对C++是如何支持的 ?
+ [cpup](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/cpup/) -> []() ->
+ [cpup_shellcmd.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/cpup/cpup_shellcmd.c) -> []() -> 如何实时查询系统CPU的占用率 ?
+ [los_cpup.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/cpup/los_cpup.c) -> []() -> 内核如何做到实时统计CPU性能的 ?
+ [dynload/src](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/dynload/src/) []() ->
+ [los_exec_elf.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/dynload/src/los_exec_elf.c) -> []() -> 鸿蒙如何运行ELF ? 什么是腾笼换鸟技术 ?
+ [los_load_elf.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/dynload/src/los_load_elf.c) -> []() -> 鸿蒙如何动态加载 ELF ?
+ [liteipc](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/liteipc/) -> []() ->
+ [hm_liteipc.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/liteipc/hm_liteipc.c) -> []() -> 如何用文件的方式读取消息队列 ? liteipc和普通消息队列区别有哪些 ?
+ [tickless](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/tickless/) -> []() ->
+ [los_tickless.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/tickless/los_tickless.c) -> []() -> 新定时机制新在哪里 ? 它解决了哪些问题 ?
+ [trace](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/trace/) -> []() ->
+ [los_trace.c](https://gitee.com/weharmony/kernel_liteos_a_notetree/master/kernel/extended/los_trace.c) -> []() -> 如何实现跟踪 ? 内核在跟踪什么 ?
+ [vdso](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/vdso/) -> []() -> 用户空间访问内核空间有哪些途径 ?
+ [src](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/vdso/src/) -> []() ->
+ [los_vdso.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/vdso/src/los_vdso.c) -> []() -> VDSO(Virtual Dynamically-linked Shared Object) 是如何实现的 ?
+ [los_vdso_text.S](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/vdso/src/los_vdso_text.S) -> []() ->
+ [usr](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/vdso/usr/) -> []() ->
+ [los_vdso_sys.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/extended/vdso/usr/los_vdso_sys.c) -> []() ->
+ [user/src](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/user/src/) -> []() ->
+ [los_user_init.c](https://gitee.com/weharmony/kernel_liteos_a_note/tree/master/kernel/user/src/los_user_init.c) -> []() ->
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
[**内核代码详细结构 >> 进入阅读代码**](https://my.oschina.net/u/3751245/blog/4869137)
## 如何初始化物理内存?
 鸿蒙内核物理内存采用了段页式管理,先看两个主要结构体.结构体的每个成员变量的含义都已经注解出来,请结合源码理解.
```cpp
#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; /**< vm object dl list */ //虚拟内存节点,通过它挂/摘到全局g_vmPhysSeg[segID]->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; /**< the vm page is used for kernel heap */ //分配页数,标识从本页开始连续的几页将一块被分配
} LosVmPage;//注意:关于nPages和order的关系说明,当请求分配为5页时,order是等于3的,因为只有2^3才能满足5页的请求
```
理解它们是理解物理内存管理的关键,尤其是 **LosVmPage ,**鸿蒙内存模块代码通篇都能看到它的影子.内核默认最大允许管理32个段.
段页式管理简单说就是先将物理内存切成一段段,每段再切成单位为 4K 的物理页框, 页是在内核层的操作单元, 物理内存的分配,置换,缺页,内存共享,文件高速缓存的读写,都是以页为单位的,所以**LosVmPage 很重要,很重要!**
结构体的每个变量代表了一个个的功能点, 例如:LosVmPhysSeg.freeList\[order\].node,就是伙伴算法的各组块双向循环链表(LOS\_DL\_LIST), LOS\_DL\_LIST是整个鸿蒙内核最重要的结构体,在系列篇开篇就专门讲过它的重要性.
再比如 LosVmPage.refCounts 页被引用的次数,可理解被进程拥有的次数,当refCounts大于1时,被多个进程所拥有,说明这页就是共享页.当等于0时,说明没有进程在使用了,这时就可以被释放了.
看到这里熟悉JAVA的同学是不是似曾相识,这像是Java的内存回收机制.在内核层面,引用的概念不仅仅适用于内存模块,也适用于其他模块,比如文件/设备模块,同样都存在共享的场景.这些模块不在这里展开说,后续有专门的章节细讲.
段一开始是怎么划分的 ? 需要方案提供商手动配置,存在静态的全局变量中,鸿蒙默认只配置了一段.
```cpp
struct VmPhysSeg g_vmPhysSeg[VM_PHYS_SEG_MAX];//物理段数组,最大32段
INT32 g_vmPhysSegNum = 0; //总段数
LosVmPage *g_vmPageArray = NULL;//物理页框数组
size_t g_vmPageArraySize;//总物理页框数
/* Physical memory area array */
STATIC struct VmPhysArea g_physArea[] = {//这里只有一个区域,即只生成一个段
{
.start = SYS_MEM_BASE, //整个物理内存基地址,#define SYS_MEM_BASE DDR_MEM_ADDR , 0x80000000
.size = SYS_MEM_SIZE_DEFAULT,//整个物理内存总大小 0x07f00000
},
};
```
有了段和这些全局变量,就可以对内存初始化了. OsVmPageStartup 是对物理内存的初始化, 它被整个系统内存初始化 OsSysMemInit所调用.  直接上代码.
```cpp
/******************************************************************************
完成对物理内存整体初始化,本函数一定运行在实模式下
1.申请大块内存g_vmPageArray存放LosVmPage,按4K一页划分物理内存存放在数组中.
******************************************************************************/
VOID OsVmPageStartup(VOID)
{
struct VmPhysSeg *seg = NULL;
LosVmPage *page = NULL;
paddr_t pa;
UINT32 nPage;
INT32 segID;
OsVmPhysAreaSizeAdjust(ROUNDUP((g_vmBootMemBase - KERNEL_ASPACE_BASE), PAGE_SIZE));//校正 g_physArea size
nPage = OsVmPhysPageNumGet();//得到 g_physArea 总页数
g_vmPageArraySize = nPage * sizeof(LosVmPage);//页表总大小
g_vmPageArray = (LosVmPage *)OsVmBootMemAlloc(g_vmPageArraySize);//实模式下申请内存,此时还没有初始化MMU
OsVmPhysAreaSizeAdjust(ROUNDUP(g_vmPageArraySize, PAGE_SIZE));//
OsVmPhysSegAdd();// 完成对段的初始化
OsVmPhysInit();// 加入空闲链表和设置置换算法,LRU(最近最久未使用)算法
for (segID = 0; segID < g_vmPhysSegNum; segID++) {//遍历物理段,将段切成一页一页
seg = &g_vmPhysSeg[segID];
nPage = seg->size >> PAGE_SHIFT;//本段总页数
for (page = seg->pageBase, pa = seg->start; page <= seg->pageBase + nPage;//遍历,算出每个页框的物理地址
page++, pa += PAGE_SIZE) {
OsVmPageInit(page, pa, segID);//对物理页框进行初始化,注意每页的物理地址都不一样
}
OsVmPageOrderListInit(seg->pageBase, nPage);//伙伴算法初始化,将所有页加入空闲链表供分配
}
}
```
 结合中文注释,代码很好理解, 此番操作之后全局变量里的值就都各就各位了,可以开始工作了.
## 如何分配/回收物理内存? 答案是伙伴算法
伙伴算法系列篇中有说过好几篇,这里再看图理解下什么伙伴算法,伙伴算法注重**物理内存的连续性,注意是连续性!**
![伙伴算法](https://img-blog.csdnimg.cn/20201226190500400.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
结合图比如,要分配4(2^2)页(16k)的内存空间,算法会先从free\_area\[2\]中查看nr\_free是否为空,如果有空闲块,则从中分配,如果没有空闲块,就从它的上一级free\_area\[3\](每块32K)中分配出16K,并将多余的内存(16K)加入到free\_area\[2\]中去。如果free\_area\[3\]也没有空闲,则从更上一级申请空间,依次递推,直到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分配器是如何解决这个问题的.
第二:合并要求太严格了,一定得是伙伴(连续)才能合并成更大的块.这样也会导致时间久了很难有大块的连续性的猪肉块. 
比方打完了,鸿蒙内核是如何实现卖肉算法的呢? 请看代码
```cpp
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 < VM_LIST_ORDER_MAX) {//order不能大于9 即:256*4K = 1M 可理解为向内核堆申请内存一次不能超过1M
for (newOrder = order; newOrder < VM_LIST_ORDER_MAX; newOrder++) {//没有就找更大块
list = &seg->freeList[newOrder];//从最合适的块处开始找
if (LOS_ListEmpty(&list->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 干的就是劈猪肉的活.
伙伴算法的链表是怎么初始化的,再看段代码
```cpp
//初始化空闲链表,分配物理页框使用伙伴算法
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 < VM_LIST_ORDER_MAX; i++) {//遍历伙伴算法空闲块组链表
list = &seg->freeList[i]; //一个个来
LOS_ListInit(&list->node); //LosVmPage.node将挂到list->node上
list->listCnt = 0; //链表上的数量默认0
}
LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
}
```
鸿蒙是面向未来设计的系统,高瞻远瞩,格局远大,设计精良, 海量知识点, 对内核源码加上中文注解已有三个多月,越深入精读内核源码,越能感受到设计者的精巧用心,创新突破, 向开发者致敬. 可以毫不夸张的说鸿蒙内核源码可作为大学C语言,数据结构,操作系统,汇编语言 四门课程的教学项目.如此宝库,不深入研究实在是暴殄天物,于心不忍.
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
精读内核源码就绕不过汇编语言,鸿蒙内核有6个汇编文件,读不懂它们就真的很难理解以下问题.
### 1.系统调用是如何实现的?
### 2.CPU是如何切换任务和进程上下文的?
### 3.硬件中断是如何处理的?
### 4.main函数到底是怎么来的?
### 5.开机最开始到底发生了什么?
### 6.关机最后的最后到底发生了什么?
以下是一个很简单的C文件编译成汇编代码后的注解. 读懂这些注解会发现汇编很可爱,甚至还会上瘾,并没有想象中的那么恐怖,读懂它会颠覆你对汇编和栈的认知. 先读懂这个简单的汇编文件,后续将详细分析鸿蒙内核各个汇编文件的作用.
```cpp
#include <stdio.h>
#include <math.h>
int square(int a,int b){
return a*b;
}
int fp(int b)
{
int a = 1;
return square(a+b,a+b);
}
int main()
{
int sum = 1;
for(int a = 0;a < 100; a++){
sum = sum + fp(a);
}
return sum;
}
```
```cpp
//编译器: armv7-a clang (trunk)
square(int, int):
sub sp, sp, #8 @sp减去8,意思为给square分配栈空间,只用2个栈空间完成计算
str r0, [sp, #4] @第一个参数入栈
str r1, [sp] @第二个参数入栈
ldr r1, [sp, #4] @取出第一个参数给r1
ldr r2, [sp] @取出第二个参数给r2
mul r0, r1, r2 @执行a*bR0,返回值的工作一直是交给R0
add sp, sp, #8 @函数执行完了,要释放申请的栈空间
bx lr @子程序返回,等同于mov pc,lr,即跳到调用处
fp(int):
push {r11, lr} @r11(fp)/lr入栈,保存调用者main的位置
mov r11, sp @r11用于保存sp,函数栈开始位置
sub sp, sp, #8 @sp减去8,意思为给fp分配栈空间,只用2个栈空间完成计算
str r0, [sp, #4] @先保存参数值,放在SP+4,此时r0中存放的是参数
mov r0, #1 @r0=1
str r0, [sp] @再把1也保存在SP的位置
ldr r0, [sp] @把SP的值给R0
ldr r1, [sp, #4] @把SP+4的值给R1
add r1, r0, r1 @执行r1=a+b
mov r0, r1 @r0=r1,r0,r1传参
bl square(int, int)@先mov lr, pc mov pc square(int, int)
mov sp, r11 @函数执行完了,要释放申请的栈空间
pop {r11, lr} @弹出r11lr,lr是专用标签,弹出就自动复制给lr寄存器
bx lr @子程序返回,等同于mov pc,lr,即跳到调用处
main:
push {r11, lr} @r11(fp)/lr入栈,保存调用者的位置
mov r11, sp @r11用于保存sp,函数栈开始位置
sub sp, sp, #16 @sp减去8,意思为给main分配栈空间,只用2个栈空间完成计算
mov r0, #0 @初始化r0
str r0, [r11, #-4] @作用是保存SUM的初始值
str r0, [sp, #8] @sum将始终占用SP+8的位置
str r0, [sp, #4] @a将始终占用SP+4的位置
b .LBB1_1 @跳到循环开始位置
.LBB1_1: @循环开始位置入口
ldr r0, [sp, #4] @取出a的值给r0
cmp r0, #99 @跟99比较
bgt .LBB1_4 @大于99,跳出循环 mov pc .LBB1_4
b .LBB1_2 @继续循环,直接 mov pc .LBB1_2
.LBB1_2: @符合循环条件入口
ldr r0, [sp, #8] @取出sum的值给r0,sp+8用于写SUM的值
str r0, [sp] @先保存SUM的值,SP的位置用于读SUM
ldr r0, [sp, #4] @r0用于传参,取出A的值给r0作为fp的参数
bl fp(int) @先mov lr, pcmov pc fp(int)
mov r1, r0 @fp的返回值为r0,保存到r1
ldr r0, [sp] @取出SUM的值
add r0, r0, r1 @计算新sum的值,R0保存
str r0, [sp, #8] @将新sum保存到SP+8的位置
b .LBB1_3 @无条件跳转,直接 mov pc .LBB1_3
.LBB1_3: @完成a++操作入口
ldr r0, [sp, #4] @SP+4中记录是a的值,赋给r0
add r0, r0, #1 @r0增加1
str r0, [sp, #4] @把新的a值放回SP+4里去
b .LBB1_1 @跳转到比较 a < 100
.LBB1_4: @循环结束入口
ldr r0, [sp, #8] @最后SUM的结果给R0,返回值的工作一直是交给R0
mov sp, r11 @函数执行完了,要释放申请的栈空间
pop {r11, lr} @弹出r11lr,lr是专用标签,弹出就自动复制给lr寄存器
bx lr @子程序返回,跳转到lr处等同于 MOV PC, LR
```
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
# 建议先阅读
阅读之前建议先读本系列其他文章,以便对本文任务调度机制的理解。
## 为什么学一个东西要学那么多的概念?
鸿蒙的内核中 Task 和 线程 在广义上可以理解为是一个东西,但狭义上肯定会有区别,区别在于管理体系的不同,Task是调度层面的概念,线程是进程层面概念。比如 main() 函数中首个函数 OsSetMainTask(); 就是设置启动任务,但此时啥都还没开始呢,Kprocess 进程都没创建,怎么会有大家一般意义上所理解的线程呢。狭义上的后续有 鸿蒙内核源码分析(启动过程篇) 来说明。不知道大家有没有这种体会,学一个东西的过程中要接触很多新概念,尤其像 Java/android 的生态,概念贼多,很多同学都被绕在概念中出不来,痛苦不堪。那问题是为什么需要这么多的概念呢?
举个例子就明白了:
假如您去深圳参加一个面试老板问你哪里人?你会说是 江西人,湖南人... 而不会说是张家村二组的张全蛋,这样还谁敢要你。但如果你参加同乡会别人问你同样问题,你不会说是来自东北那旮沓的,却反而要说张家村二组的张全蛋。明白了吗?张全蛋还是那个张全蛋,但因为场景变了,您的说法就得必须跟着变,否则没法愉快的聊天。程序设计就是源于生活,归于生活,大家对程序的理解就是要用生活中的场景去打比方,更好的理解概念。
那在内核的调度层面,咱们只说task, task是内核调度的单元,调度就是围着它转。
## 进程和线程的状态迁移图
先看看task从哪些渠道产生:
![](https://img-blog.csdnimg.cn/20200921143623251.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
渠道很多,可能是shell 的一个命令,也可能由内核创建,更多的是大家编写应用程序new出来的一个线程。
调度的内容task已经有了,那他们是如何被有序调度的呢?答案:是32个进程和线程就绪队列,各32个哈,为什么是32个,鸿蒙系统源码分析(总目录) 文章里有详细说明,自行去翻。这张进程状态迁移示意图一定要看明白.
注意:进程和线程的队列内的内容只针对就绪状态,其他状态内核并没有用队列去描述它,(线程的阻塞状态用的是pendlist链表),因为就绪就意味着工作都准备好了就等着被调度到CPU来执行了。所以理解就绪队列很关键,有三种情况会加入就绪队列。
![](https://img-blog.csdnimg.cn/img_convert/925ff9ae641e32b2502d7b5a155b8579.png)
- Init→Ready:
进程创建或fork时,拿到该进程控制块后进入Init状态,处于进程初始化阶段,当进程初始化完成将进程插入调度队列,此时进程进入就绪状态。
- Pend→Ready / Pend→Running:
阻塞进程内的任意线程恢复就绪态时,进程被加入到就绪队列,同步转为就绪态,若此时发生进程切换,则进程状态由就绪态转为运行态。
- Running→Ready:
进程由运行态转为就绪态的情况有以下两种:
- 有更高优先级的进程创建或者恢复后,会发生进程调度,此刻就绪列表中最高优先级进程变为运行态,那么原先运行的进程由运行态变为就绪态。
- 若进程的调度策略为SCHED_RR,且存在同一优先级的另一个进程处于就绪态,则该进程的时间片消耗光之后,该进程由运行态转为就绪态,另一个同优先级的进程由就绪态转为运行态。
## 谁来触发调度工作?
就绪队列让task各就各位,在其生命周期内不停的进行状态流转,调度是让task交给CPU处理,那又是什么让调度去工作的呢?它是如何被触发的?
笔者能想到的触发方式是以下四个:
- Tick(时钟管理),类似于JAVA的定时任务,时间到了就触发。系统定时器是内核时间机制中最重要的一部分,它提供了一种周期性触发中断机制,即系统定时器以HZ(时钟节拍率)为频率自行触发时钟中断。当时钟中断发生时,内核就通过时钟中断处理程序OsTickHandler对其进行处理。鸿蒙内核默认是10ms触发一次,执行以下中断函数:
```cpp
/*
* Description : Tick interruption handler
*/
LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
{
UINT32 intSave;
TICK_LOCK(intSave);
g_tickCount[ArchCurrCpuid()]++;
TICK_UNLOCK(intSave);
#ifdef LOSCFG_KERNEL_VDSO
OsUpdateVdsoTimeval();
#endif
#ifdef LOSCFG_KERNEL_TICKLESS
OsTickIrqFlagSet(OsTicklessFlagGet());
#endif
#if (LOSCFG_BASE_CORE_TICK_HW_TIME == YES)
HalClockIrqClear(); /* diff from every platform */
#endif
OsTimesliceCheck();//时间片检查
OsTaskScan(); /* task timeout scan *///任务扫描,发起调度
#if (LOSCFG_BASE_CORE_SWTMR == YES)
OsSwtmrScan();//软时钟扫描检查
#endif
}
```
里面对任务进行了扫描,时间片到了或就绪队列有高或同级task, 会执行调度。
- 第二个是各种软硬中断,如何USB插拔,键盘,鼠标这些外设引起的中断,需要去执行中断处理函数。
- 第三个是程序主动中断,比如运行过程中需要申请其他资源,而主动让出控制权,重新调度。
- 最后一个是创建一个新进程或新任务后主动发起的抢占式调度,新进程会默认创建一个main task, task的首条指令(入口函数)就是我们上层程序的main函数,它被放在代码段的第一的位置。
- 哪些地方会申请调度?看一张图。
- ![](https://img-blog.csdnimg.cn/20200921155607747.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
这里提下图中的 OsCopyProcess(), 这是fork进程的主体函数,可以看出fork之后立即申请了一次调度。
```cpp
LITE_OS_SEC_TEXT INT32 LOS_Fork(UINT32 flags, const CHAR *name, const TSK_ENTRY_FUNC entry, UINT32 stackSize)
{
UINT32 cloneFlag = CLONE_PARENT | CLONE_THREAD | CLONE_VFORK | CLONE_FILES;
if (flags & (~cloneFlag)) {
PRINT_WARN("Clone dont support some flags!\n");
}
flags |= CLONE_FILES;
return OsCopyProcess(cloneFlag & flags, name, (UINTPTR)entry, stackSize);
}
STATIC INT32 OsCopyProcess(UINT32 flags, const CHAR *name, UINTPTR sp, UINT32 size)
{
UINT32 intSave, ret, processID;
LosProcessCB *run = OsCurrProcessGet();
LosProcessCB *child = OsGetFreePCB();
if (child == NULL) {
return -LOS_EAGAIN;
}
processID = child->processID;
ret = OsForkInitPCB(flags, child, name, sp, size);
if (ret != LOS_OK) {
goto ERROR_INIT;
}
ret = OsCopyProcessResources(flags, child, run);
if (ret != LOS_OK) {
goto ERROR_TASK;
}
ret = OsChildSetProcessGroupAndSched(child, run);
if (ret != LOS_OK) {
goto ERROR_TASK;
}
LOS_MpSchedule(OS_MP_CPU_ALL);
if (OS_SCHEDULER_ACTIVE) {
LOS_Schedule();// 申请调度
}
return processID;
ERROR_TASK:
SCHEDULER_LOCK(intSave);
(VOID)OsTaskDeleteUnsafe(OS_TCB_FROM_TID(child->threadGroupID), OS_PRO_EXIT_OK, intSave);
ERROR_INIT:
OsDeInitPCB(child);
return -ret;
}
```
原来创建一个进程这么简单,真的就是在COPY!
## 源码告诉你调度过程是怎样的
以上是需要提前了解的信息,接下来直接上源码看调度过程吧,文件就三个函数,主要就是这个了:
```cpp
VOID OsSchedResched(VOID)
{
LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//调度过程要上锁
newTask = OsGetTopTask(); //获取最高优先级任务
OsSchedSwitchProcess(runProcess, newProcess);//切换进程
(VOID)OsTaskSwitchCheck(runTask, newTask);//任务检查
OsCurrTaskSet((VOID*)newTask);//*设置当前任务
if (OsProcessIsUserMode(newProcess)) {//判断是否为用户态,使用用户空间
OsCurrUserTaskSet(newTask->userArea);//设置任务空间
}
/* do the task context switch */
OsTaskSchedule(newTask, runTask); //切换CPU任务上下文,汇编代码实现
}
```
函数有点长,笔者留了最重要的几行,看这几行就够了,流程如下:
1.  调度过程要自旋锁,多核情况下只能被一个CPU core 执行. 不允许任何中断发生, 没错,说的是任何事是不能去打断它,否则后果太严重了,这可是内核在切换进程和线程的操作啊。
2. 在就绪队列里找个最高优先级的task
3. 切换进程,就是task归属的那个进程设为运行进程,这里要注意,老的task和老进程只是让出了CPU指令执行权,其他都还在内存,资源也都没有释放.
4. 设置新任务为当前任务
5. 用户模式下需要设置task运行空间,因为每个task栈是不一样的.空间部分具体在系列篇内存中查看
6. 是最重要的,切换任务上下文,参数是新老两个任务,一个要保存现场,一个要恢复现场。
什么是任务上下文?看鸿蒙系统源码分析(总目录)其他文章,有专门的介绍。这里要说明的是 在CPU的层面,它只认任务上下文!这里看不到任何代码了,因为这是跟CPU相关的,不同的CPU需要去适配不同的汇编代码.
## 请读懂内核最美函数 OsGetTopTask()
最后留个作业,读懂这个笔者认为的内核最美函数,就明白了就绪队列是怎么回事了。这里提下goto语句,几乎所有内核代码都会大量的使用goto语句,鸿蒙内核有617个goto远大于264个break,还有人说要废掉goto,你知道内核开发者青睐goto的真正原因吗?
```
LITE_OS_SEC_TEXT_MINOR LosTaskCB *OsGetTopTask(VOID)
{
UINT32 priority, processPriority;
UINT32 bitmap;
UINT32 processBitmap;
LosTaskCB *newTask = NULL;
#if (LOSCFG_KERNEL_SMP == YES)
UINT32 cpuid = ArchCurrCpuid();
#endif
LosProcessCB *processCB = NULL;
processBitmap = g_priQueueBitmap;
while (processBitmap) {
processPriority = CLZ(processBitmap);
LOS_DL_LIST_FOR_EACH_ENTRY(processCB, &g_priQueueList[processPriority], LosProcessCB, pendList) {
bitmap = processCB->threadScheduleMap;
while (bitmap) {
priority = CLZ(bitmap);
LOS_DL_LIST_FOR_EACH_ENTRY(newTask, &processCB->threadPriQueueList[priority], LosTaskCB, pendList) {
#if (LOSCFG_KERNEL_SMP == YES)
if (newTask->cpuAffiMask & (1U << cpuid)) {
#endif
newTask->taskStatus &= ~OS_TASK_STATUS_READY;
OsPriQueueDequeue(processCB->threadPriQueueList,
&processCB->threadScheduleMap,
&newTask->pendList);
OsDequeEmptySchedMap(processCB);
goto OUT;
#if (LOSCFG_KERNEL_SMP == YES)
}
#endif
}
bitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - priority - 1));
}
}
processBitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - processPriority - 1));
}
OUT:
return newTask;
}
#ifdef __cplusplus
#if __cplusplus
}
```
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
# 为何单独讲调度队列?
鸿蒙内核代码中有两个源文件是关于队列的,一个是用于调度的队列,另一个是用于线程间通讯的IPC队列。
IPC队列后续有专门的博文讲述,这两个队列的数据结构实现采用的都是双向循环链表,再说一遍LOS_DL_LIST实在是太重要了,是理解鸿蒙内核的关键,说是最重要的代码一点也不为过,源码出现在 sched_sq模块,说明是用于任务的调度的,sched_sq模块只有两个文件,另一个los_sched.c就是调度代码。
## 涉及函数
![](https://oscimg.oschina.net/oscnet/up-b18bdc78ada27a4812601e628afcff86300.png)
鸿蒙内核进程和线程各有32个就绪队列,进程队列用全局变量存放, 创建进程时入队, 任务队列放在进程的threadPriQueueList中。
映射张大爷的故事:就绪队列就是在外面排队的32个通道,按优先级0-31依次排好,张大爷的办公室有个牌子,类似打篮球的记分牌,一共32个,一字排开,队列里有人时对应的牌就是1,没有就是0 ,这样张大爷每次从0位开始看,看到的第一个1那就是最高优先级的那个人。办公室里的记分牌就是位图调度器。
## 位图调度器
```cpp
//* 0x80000000U = 10000000000000000000000000000000(32位,1是用于移位的,设计之精妙,点赞)
#define PRIQUEUE_PRIOR0_BIT 0x80000000U
LITE_OS_SEC_BSS LOS_DL_LIST *g_priQueueList = NULL; //所有的队列 原始指针
LITE_OS_SEC_BSS UINT32 g_priQueueBitmap; // 位图调度
```
整个los_priqueue.c就只有两个全部变量,一个是 LOS_DL_LIST *g_priQueueList 是32个进程就绪队列的头指针,在就绪队列中会讲另一个UINT32 g_priQueueBitmap  估计很多人会陌生,是一个32位的变量,叫位图调度器。怎么理解它呢?
鸿蒙系统的调度是抢占式的,task分成32个优先级,如何快速的知道哪个队列是空的,哪个队列里有任务需要一个标识,而且要极高效的实现?答案是:位图调度器。
系列篇已有专门讲位图管理的文章,自行翻看.简单说就是一个变量的位来标记对应队列中是否有任务,在位图调度下,任务优先级的值越小则代表具有越高的优先级,每当需要进行调度时,从最低位向最高位查找出第一个置 1 的位的所在位置,即为当前最高优先级,然后从对应优先级就绪队列获得相应的任务控制块,整个调度器的实现复杂度是 O(1),即无论任务多少,其调度时间是固定的。
## 进程就绪队列机制
CPU执行速度是很快的,其运算速度和内存的读写速度是数量级的差异,与硬盘的读写更是指数级。 鸿蒙内核默认一个时间片是 10ms,  资源很宝贵,它不断在众多任务中来回的切换,所以绝不能让CPU等待任务,CPU时间很宝贵,没准备好的任务不要放进来。这就是进程和线程就绪队列的机制,一共有32个任务就绪队列,因为线程的优先级是默认32个, 每个队列中放同等优先级的task. 队列初始化做了哪些工作?详细看代码
```cpp
#define OS_PRIORITY_QUEUE_NUM 32
//内部队列初始化
UINT32 OsPriQueueInit(VOID)
{
UINT32 priority;
/* system resident resource *///常驻内存
g_priQueueList = (LOS_DL_LIST *)LOS_MemAlloc(m_aucSysMem0, (OS_PRIORITY_QUEUE_NUM * sizeof(LOS_DL_LIST)));//分配32个队列头节点
if (g_priQueueList == NULL) {
return LOS_NOK;
}
for (priority = 0; priority < OS_PRIORITY_QUEUE_NUM; ++priority) {
LOS_ListInit(&g_priQueueList[priority]);//队列初始化,前后指针指向自己
}
return LOS_OK;
}
```
因TASK 有32个优先级,在初始化时内核一次性创建了32个双向循环链表,每种优先级都有一个队列来记录就绪状态的tasks的位置,g_priQueueList分配的是一个连续的内存块,存放了32个双向链表
## 几个常用函数
还是看入队和出队的源码吧,注意bitmap的变化!
从代码中可以知道,调用了LOS_ListTailInsert,注意是从循环链表的尾部插入的,也就是同等优先级的TASK被排在了最后一个执行,只要每次都是从尾部插入,就形成了一个按顺序执行的队列。鸿蒙内核的设计可谓非常巧妙,用极少的代码,极高的效率实现了队列功能。
```cpp
VOID OsPriQueueEnqueue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority)
{
/*
* Task control blocks are inited as zero. And when task is deleted,
* and at the same time would be deleted from priority queue or
* other lists, task pend node will restored as zero.
*/
LOS_ASSERT(priqueueItem->pstNext == NULL);
if (LOS_ListEmpty(&priQueueList[priority])) {
*bitMap |= PRIQUEUE_PRIOR0_BIT >> priority;//对应优先级位 置1
}
LOS_ListTailInsert(&priQueueList[priority], priqueueItem);
}
VOID OsPriQueueEnqueueHead(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority)
{
/*
* Task control blocks are inited as zero. And when task is deleted,
* and at the same time would be deleted from priority queue or
* other lists, task pend node will restored as zero.
*/
LOS_ASSERT(priqueueItem->pstNext == NULL);
if (LOS_ListEmpty(&priQueueList[priority])) {
*bitMap |= PRIQUEUE_PRIOR0_BIT >> priority;//对应优先级位 置1
}
LOS_ListHeadInsert(&priQueueList[priority], priqueueItem);
}
VOID OsPriQueueDequeue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem)
{
LosTaskCB *task = NULL;
LOS_ListDelete(priqueueItem);
task = LOS_DL_LIST_ENTRY(priqueueItem, LosTaskCB, pendList);
if (LOS_ListEmpty(&priQueueList[task->priority])) {
*bitMap &= ~(PRIQUEUE_PRIOR0_BIT >> task->priority);//队列空了,对应优先级位 置0
}
}
```
## 同一个进程下的线程的优先级可以不一样吗?
请先想一下这个问题。
进程和线程是一对多的父子关系,内核调度的单元是任务(线程),鸿蒙内核中任务和线程是一个东西,只是不同的身份。一个进程可以有多个线程,线程又有各自独立的状态,那进程状态该怎么界定?例如:ProcessA 有 TaskA(阻塞状态) ,TaskB(就绪状态) 两个线程,ProcessA是属于阻塞状态还是就绪状态呢?
先看官方文档的说明后再看源码。
**进程状态迁移说明:**
- Init→Ready:
进程创建或fork时,拿到该进程控制块后进入Init状态,处于进程初始化阶段,当进程初始化完成将进程插入调度队列,此时进程进入就绪状态。
- Ready→Running:
进程创建后进入就绪态,发生进程切换时,就绪列表中最高优先级的进程被执行,从而进入运行态。若此时该进程中已无其它线程处于就绪态,则该进程从就绪列表删除,只处于运行态;若此时该进程中还有其它线程处于就绪态,则该进程依旧在就绪队列,此时进程的就绪态和运行态共存。
- Running→Pend:
进程内所有的线程均处于阻塞态时,进程在最后一个线程转为阻塞态时,同步进入阻塞态,然后发生进程切换。
- Pend→Ready / Pend→Running:
阻塞进程内的任意线程恢复就绪态时,进程被加入到就绪队列,同步转为就绪态,若此时发生进程切换,则进程状态由就绪态转为运行态。
- Ready→Pend:
进程内的最后一个就绪态线程处于阻塞态时,进程从就绪列表中删除,进程由就绪态转为阻塞态。
- Running→Ready:
进程由运行态转为就绪态的情况有以下两种:
1. 有更高优先级的进程创建或者恢复后,会发生进程调度,此刻就绪列表中最高优先级进程变为运行态,那么原先运行的进程由运行态变为就绪态。
2. 若进程的调度策略为SCHED_RR,且存在同一优先级的另一个进程处于就绪态,则该进程的时间片消耗光之后,该进程由运行态转为就绪态,另一个同优先级的进程由就绪态转为运行态。
- Running→Zombies:
当进程的主线程或所有线程运行结束后,进程由运行态转为僵尸态,等待父进程回收资源。
从文档中可知,一个进程是可以两种状态共存的.
```cpp
UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently
running in the process */
processCB->processStatus &= ~(status | OS_PROCESS_STATUS_PEND);//取反后的与位运算
processCB->processStatus |= OS_PROCESS_STATUS_READY;//或位运算
```
一个变量存两种状态,怎么做到的?答案还是 按位保存啊。还记得上面的位图调度 g_priQueueBitmap吗,那可是存了32种状态的。其实这在任何一个系统的内核源码中都很常见,类似的还有 左移 <<,右移 >>等等
继续说进程和线程的关系,线程的优先级必须和进程一样吗?他们可以不一样吗?答案是:当然不一样,否则怎么会有设置task优先级的函数。其实task有专门的bitmap来记录它曾经有过的优先级记录, 比如在调度过程中如果遇到阻塞,内核往往会提高持有锁的task的优先级,让它能以最大概率被下一轮调度选中而快速释放锁资源.
## task调度器
真正让CPU工作的是task,进程只是个装task的容器,task有任务栈空间,进程结构体LosProcessCB 有一个这样的定义。看名字就知道了,那是跟调度相关的。
```cpp
UINT32 threadScheduleMap; /**< The scheduling bitmap table for the thread group of the
process */
LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the
priority hash table */
```
咋一看怎么进程的结构体里也有32个队列,其实这就是task的就绪状态队列。threadScheduleMap就是进程自己的位图调度器。具体看进程入队和出队的源码。调度过程是先去进程就绪队列里找最高优先级的进程,然后去该进程找最高优先级的线程来调度。具体看笔者认为的内核最美函数OsGetTopTask,能欣赏到他的美就读懂了就绪队列是怎么管理的。
```cpp
LITE_OS_SEC_TEXT_MINOR LosTaskCB *OsGetTopTask(VOID)
{
UINT32 priority, processPriority;
UINT32 bitmap;
UINT32 processBitmap;
LosTaskCB *newTask = NULL;
#if (LOSCFG_KERNEL_SMP == YES)
UINT32 cpuid = ArchCurrCpuid();
#endif
LosProcessCB *processCB = NULL;
processBitmap = g_priQueueBitmap;
while (processBitmap) {
processPriority = CLZ(processBitmap);
LOS_DL_LIST_FOR_EACH_ENTRY(processCB, &g_priQueueList[processPriority], LosProcessCB, pendList) {
bitmap = processCB->threadScheduleMap;
while (bitmap) {
priority = CLZ(bitmap);
LOS_DL_LIST_FOR_EACH_ENTRY(newTask, &processCB->threadPriQueueList[priority], LosTaskCB, pendList) {
#if (LOSCFG_KERNEL_SMP == YES)
if (newTask->cpuAffiMask & (1U << cpuid)) {
#endif
newTask->taskStatus &= ~OS_TASK_STATUS_READY;
OsPriQueueDequeue(processCB->threadPriQueueList,
&processCB->threadScheduleMap,
&newTask->pendList);
OsDequeEmptySchedMap(processCB);
goto OUT;
#if (LOSCFG_KERNEL_SMP == YES)
}
#endif
}
bitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - priority - 1));
}
}
processBitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - processPriority - 1));
}
OUT:
return newTask;
}
```
映射张大爷的故事:张大爷喊到张全蛋时进场时表演时,张全蛋要决定自己的哪个节目先表演,也要查下他的清单上优先级,它同样也有个张大爷同款记分牌,就这么简单。
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
- ### **鸿蒙源码分析系列篇**
- [鸿蒙源码分析系列(总目录) | 持续更新中... 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)|[ HarmonyOS 】](https://weharmony.github.io)
* [|- 鸿蒙内核源码分析(用栈方式篇) | 栈是构建底层运行的基础 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/112534331) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4893388)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(用栈方式篇).html)
* [|- 鸿蒙内核源码分析(位图管理篇) | 为何进程和线程都是32个优先级? 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/112394982) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4888467)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(位图管理篇).html)
* [|- 鸿蒙内核源码分析(源码结构篇) | 内核500问你能答对多少? 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/111938348) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4869137)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(源码结构篇).html)
* [|- 鸿蒙内核源码分析(物理内存篇) | 伙伴算法是在卖标准猪肉块吗?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/111765600) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4842408)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(物理内存篇).html)
* [|- 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109437223) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4698384)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存规则篇).html)
* [|- 鸿蒙内核源码分析(源码注释篇) | 精读内核源码有哪些好处?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109251754) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4686747)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(源码注释篇).html)
* [|- 鸿蒙内核源码分析(内存映射篇) | 虚拟内存-物理内存是如何映射的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109032636) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4694841)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存映射篇).html)
* [|- 鸿蒙内核源码分析(内存汇编篇) | 什么是虚拟内存的实现基础?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108994081) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4692156)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存汇编篇).html)
* [|- 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108989906) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4646802)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存分配篇).html)
* [|- 鸿蒙内核源码分析(内存管理篇) | 鸿蒙虚拟内存全景图是怎样的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108821442) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4652284)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存管理篇).html)
* [|- 鸿蒙内核源码分析(内存概念篇) | 虚拟内存虚在哪里?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108723672) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4646802)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存概念篇).html)
* [|- 鸿蒙内核源码分析(必读故事篇) | 西门和金莲的那点破事【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108745174) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4634668)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(必读故事篇).html)
* [|- 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108705968) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4623040)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(调度机制篇).html)
* [|- 鸿蒙内核源码分析(调度队列篇) | 就绪队列对调度的作用【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108626671) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4606916)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(调度队列篇).html)
* [|- 鸿蒙内核源码分析(任务管理篇) | 任务是内核调度的单元【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108621428) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4603919)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(任务管理篇).html)
* [|- 鸿蒙内核源码分析(时钟任务篇) | 触发调度最大的动力来自哪里?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108603468) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4574493)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(时钟管理篇).html)
* [|- 鸿蒙内核源码分析(进程管理篇) | 进程是内核资源管理单元【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108595941) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4574429)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(进程管理篇).html)
* [|- 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108585659) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4572304)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(双向链表篇).html)
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
{
"name": "weharmonyos",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"vuepress": "^1.8.0"
},
"scripts": {
"dev": "vuepress dev docs",
"build": "vuepress build docs"
}
}
此差异已折叠。
此差异已折叠。
- ### **鸿蒙源码分析系列篇**
- [鸿蒙源码分析系列(总目录) | 持续更新中... 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)|[ HarmonyOS 】](https://weharmony.github.io)
* [|- 鸿蒙内核源码分析(用栈方式篇) | 栈是构建底层运行的基础 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/112534331) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4893388)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(用栈方式篇).html)
* [|- 鸿蒙内核源码分析(位图管理篇) | 为何进程和线程都是32个优先级? 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/112394982) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4888467)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(位图管理篇).html)
* [|- 鸿蒙内核源码分析(源码结构篇) | 内核500问你能答对多少? 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/111938348) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4869137)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(源码结构篇).html)
* [|- 鸿蒙内核源码分析(物理内存篇) | 伙伴算法是在卖标准猪肉块吗?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/111765600) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4842408)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(物理内存篇).html)
* [|- 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109437223) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4698384)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存规则篇).html)
* [|- 鸿蒙内核源码分析(源码注释篇) | 精读内核源码有哪些好处?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109251754) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4686747)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(源码注释篇).html)
* [|- 鸿蒙内核源码分析(内存映射篇) | 虚拟内存-物理内存是如何映射的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/109032636) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4694841)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存映射篇).html)
* [|- 鸿蒙内核源码分析(内存汇编篇) | 什么是虚拟内存的实现基础?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108994081) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4692156)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存汇编篇).html)
* [|- 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108989906) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4646802)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存分配篇).html)
* [|- 鸿蒙内核源码分析(内存管理篇) | 鸿蒙虚拟内存全景图是怎样的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108821442) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4652284)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存管理篇).html)
* [|- 鸿蒙内核源码分析(内存概念篇) | 虚拟内存虚在哪里?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108723672) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4646802)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(内存概念篇).html)
* [|- 鸿蒙内核源码分析(必读故事篇) | 西门和金莲的那点破事【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108745174) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4634668)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(必读故事篇).html)
* [|- 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108705968) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4623040)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(调度机制篇).html)
* [|- 鸿蒙内核源码分析(调度队列篇) | 就绪队列对调度的作用【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108626671) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4606916)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(调度队列篇).html)
* [|- 鸿蒙内核源码分析(任务管理篇) | 任务是内核调度的单元【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108621428) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4603919)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(任务管理篇).html)
* [|- 鸿蒙内核源码分析(时钟任务篇) | 触发调度最大的动力来自哪里?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108603468) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4574493)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(时钟管理篇).html)
* [|- 鸿蒙内核源码分析(进程管理篇) | 进程是内核资源管理单元【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108595941) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4574429)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(进程管理篇).html)
* [|- 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体?【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108585659) [| OSCHINA ](https://my.oschina.net/u/3751245/blog/4572304)|[ HarmonyOS 】](https://weharmony.github.io/guide/鸿蒙内核源码分析(双向链表篇).html)
\ No newline at end of file
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
作者邮箱:weharmony@126.com
---
[![WeHarmony/kernel_liteos_a_note](https://gitee.com/weharmony/kernel_liteos_a_note/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/weharmony/kernel_liteos_a_note)
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文详细注解.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
\ No newline at end of file
[鸿蒙内核源码注释中文版 【 Gitee仓 ](https://gitee.com/weharmony/kernel_liteos_a_note)|[ CSDN仓 ](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note)|[ Github仓 ](https://github.com/kuangyufei/kernel_liteos_a_note)|[ Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)精读内核源码,中文注解分析.深挖地基工程,构建底层网图.
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 表格化说明, 图形化展示, 层层剥开内核神秘外衣.
---
\ No newline at end of file
!import[\zzz\mdmerge\head.md]
先看四个宏定义,进程和线程(线程就是任务)最高和最低优先级定义,\[0,31\]区间,即32级,优先级用于调度,CPU根据这个来决定先运行哪个进程和任务。
```cpp
#define OS_PROCESS_PRIORITY_HIGHEST 0 //进程最高优先级
#define OS_PROCESS_PRIORITY_LOWEST 31 //进程最低优先级
#define OS_TASK_PRIORITY_HIGHEST 0 //任务最高优先级,软时钟任务就是最高级任务,见于 OsSwtmrTaskCreate
#define OS_TASK_PRIORITY_LOWEST 31 //任务最低优先级
```
## 为何进程和线程都是32个优先级?
回答这个问题之前,先回答另一个问题,为什么人类几乎所有的文明都是用十进制的计数方式。答案掰手指就知道了,因为人有十根手指头。玛雅人的二十进制那是把脚指头算上了,但其实也算是十进制的表示。
这是否说明一个问题,认知受环境的影响,方向是怎么简单/方便怎么来。这也可以解释为什么人类语言发音包括各种方言对妈妈这个词都很类似,因为婴儿说mama是最容易的。 注意认识这点很重要!
而计算机的世界是二进制的,是是非非,清清楚楚,特别的简单,二进制已经最简单了,到底啦,不可能有更简单的了。还记得双向链表篇中说过的吗,因为简单所以才不简单啊,大道若简,计算机就靠着这01码,表述万千世界。
但人类的大脑不擅长存储,二进制太长了数到100就撑爆了大脑,记不住,为了记忆和运算方便,编程常用靠近10进制的 16进制来表示 ,0x9527ABCD 看着比 0011000111100101010100111舒服多了。
## 应用开发和内核开发有哪些区别?
区别还是很大的,这里只说一点,就是对位的控制能力,内核会出现大量的按位运算(&,|,~,^) , 一个变量的不同位表达不同的含义,但这在应用程序员那是很少看到的,他们用的更多的是逻辑运算(&&,||,!)
```cpp
#define OS_TASK_STATUS_INIT 0x0001U //初始化状态
#define OS_TASK_STATUS_READY 0x0002U //就绪状态的任务都将插入就绪队列
#define OS_TASK_STATUS_RUNNING 0x0004U //运行状态
#define OS_TASK_STATUS_SUSPEND 0x0008U //挂起状态
#define OS_TASK_STATUS_PEND 0x0010U //阻塞状态
```
这是任务各种状态(注者后续将比如成贴标签)表述,将它们还原成二进制就是:
0000000000000001 = 0x0001U
0000000000000010 = 0x0002U
0000000000000100 = 0x0004U
0000000000001000 = 0x0008U
0000000000010000 = 0x0010U
发现二进制这边的区别没有,用每一位来表示一种不同的状态,1表示是,0表示不是。
这样的好处有两点:
1.可以多种标签同时存在 比如 0x07 = 0b00000111,对应以上就是任务有三个标签(初始,就绪,和运行),进程和线程在运行期间是允许多种标签同时存在的。
2.节省了空间,一个变量就搞定了,如果是应用程序员要实现这三个标签同时存在,习惯上要定义三个变量的,因为你的排他性颗粒度是一个变量而不是一个位。
而对位的管理/运算就需要有个专门的管理器:位图管理器 (见源码 los_bitmap.c )
## 什么是位图管理器?
直接上部分代码,代码关键地方都加了中文注释,简单说就是对位的各种操作,比如如何在某个位上设1?如何找到最高位为1的是哪个位置?这些函数都是有大用途的。
```cpp
//对状态字的某一标志位进行置1操作
VOID LOS_BitmapSet(UINT32 *bitmap, UINT16 pos)
{
if (bitmap == NULL) {
return;
}
*bitmap |= 1U << (pos & OS_BITMAP_MASK);//在对应位上置1
}
//对状态字的某一标志位进行清0操作
VOID LOS_BitmapClr(UINT32 *bitmap, UINT16 pos)
{
if (bitmap == NULL) {
return;
}
*bitmap &= ~(1U << (pos & OS_BITMAP_MASK));//在对应位上置0
}
/********************************************************
杂项算术指令
CLZ 用于计算操作数最高端0的个数,这条指令主要用于一下两个场合
  计算操作数规范化(使其最高位为1)时需要左移的位数
  确定一个优先级掩码中最高优先级
********************************************************/
//获取状态字中为1的最高位 例如: 00110110 返回 5
UINT16 LOS_HighBitGet(UINT32 bitmap)
{
if (bitmap == 0) {
return LOS_INVALID_BIT_INDEX;
}
return (OS_BITMAP_MASK - CLZ(bitmap));
}
//获取状态字中为1的最低位, 例如: 00110110 返回 2
UINT16 LOS_LowBitGet(UINT32 bitmap)
{
if (bitmap == 0) {
return LOS_INVALID_BIT_INDEX;
}
return CTZ(bitmap);//
}
```
## 位图在哪些地方应用?
内核很多模块在使用位图,这里只说进程和线程模块,还记得开始的问题吗,为何进程和线程都是32个优先级?因为他们的优先级是由位图管理的,管理一个UINT32的变量,所以是32级,一个位一个级别,最高位优先级最低。
```cpp
UINT32 priBitMap; /**< BitMap for recording the change of task priority, //任务在执行过程中优先级会经常变化,这个变量用来记录所有曾经变化
the priority can not be greater than 31 */ //过的优先级,例如 ..01001011 曾经有过 0,1,3,6 优先级
```
这是任务控制块中对调度优先级位图的定义,注意一个任务的优先级在运行过程中可不是一成不变的,内核会根据运行情况而改变它的,这个变量是用来保存这个任务曾经有过的所有优先级历史记录。
比如 任务A的优先级位图是 00000001001011 ,可以看出它曾经有过四个调度等级记录,那如果想知道优先级最低的记录是多少时怎么办呢?
诶,上面的位图管理器函数 UINT16 LOS_HighBitGet(UINT32 bitmap) 就很有用啦 ,它返回的是1在高位出现的位置,可以数一下是 6
因为任务的优先级0最大,所以最终的意思就是A任务曾经有过的最低优先级是6
一定要理解位图的操作,内核中大量存在这类代码,尤其到了汇编层,对寄存器的操作大量的出现。
比如以下这段汇编代码。
```cpp
MSR CPSR_c, #(CPSR_INT_DISABLE | CPSR_SVC_MODE) @禁止中断并切到管理模式
LDRH R1, [R0, #4] @将存储器地址为R0+4 的低16位数据读入寄存器R1,并将R1的高16 位清零
ORR R1, #OS_TASK_STATUS_RUNNING @或指令 R1=R1|OS_TASK_STATUS_RUNNING
STRH R1, [R0, #4] @将寄存器R1中的低16位写入以R0+4为地址的存储器中
```
!import[\zzz\mdmerge\foot.md]
!export[\zzz\md\docs\guide\鸿蒙内核源码分析(位图管理篇).md]
\ No newline at end of file
!import[\zzz\mdmerge\head.md]
## 最难讲的章节
        内存模块占了 HarmonyOS 内核约15%代码量, 近20个.c文件,很复杂。系列篇将用九篇来介绍HarmonyOS内存部分,分别是 鸿蒙内核源码分析(内存概念篇) | 鸿蒙内核源码分析(内存管理篇) | 鸿蒙内核源码分析(内存汇编篇) |  鸿蒙内核源码分析(内存分配篇)  |  鸿蒙内核源码分析(内存映射篇)  | 鸿蒙内核源码分析(内存空间篇)  | 鸿蒙内核源码分析(内存置换篇)  | 鸿蒙内核源码分析(内存共享篇) | 鸿蒙内核源码分析(内存故事篇) 故事篇中会用生活场景张大爷故事里的另一个主角王场馆来举例 ,这些篇幅内容也会反复修改完善。想想也是内存何等重要,内核本身也是程序要运行空间, 用户程序也要在运行空间,大家都在一个窝里吃饭, 你凭什么就管我了, 凭什么的问题会在系列篇的最后结尾 鸿蒙内核源码分析(主奴机制篇) 中详细介绍, 哎! 其实用户进程就是内核的一个个奴才, 被捏的死死的.  按不住奴才那这主子就不合格,就不是一个稳定的运作系统. 试着想想 实际内存就这么点大, 如何满足众多用户进程的需求? 内核空间和用户空间如何隔离? 如何防止访问乱串? 如何分配如何释放防止碎片化? 空间不够了又如何置换到硬盘?   想想头都大了。内核这当家的主子真是不容易,这些都是他要解决的问题, 欲戴其冠,必承其重嘛.本章是笔者认真读完鸿蒙内存模块代码后的总结,获益良多,讲之前先重新梳理下内存的一些概念再来详细阐述内存模块的其他细节. 那开始吧. 
## 先说如果没有内存管理会怎样?
       那就是个奴才们能把主子给活活踩死, 想想主奴不分,吃喝拉撒睡都在一起,称兄道弟的想干啥? 没规矩不成方圆嘛,这事业肯定搞不大,单片机时代就是这种情况. 裸机编程,指针可以随便乱飞,数据可以随意覆盖,没有划定边界,没有明确职责,没有特权指令,没有地址保护,你还想像java开发一样,只管new内存,不去释放,应用可以随便崩但系统跑的妥妥的?想的美! 直接系统死机,甚至开机都开不了,主板直接报废了. 所以不能运行很复杂的程序,尽量可控,而且更是不可能支持应用的动态加载运行. 队伍大了就不好带了,方法得换, 游击队的做法不适合规模作战,内存就需要管理了,而且是 5A级的严格管理。
## 内存管理在管什么?
        简单说就是给主子赋能,拥有超级权利,为什么就他有? 因为他先来,掌握了先机.它定好了游戏规则,你们来玩.有哪些游戏规则?
        第一: 主奴有别,主子即是裁判又是运动员,主子有主子地方,奴才们有奴才们待的地方,主子可以在你的空间走来走去,但你只能在主人划定的区域活动.奴才把自己玩崩了也只是奴才狗屁了, 但主人和其他人还会是好好的. 主子有所有特权,比如某个奴才太嚣张了,就直接拖到午门问斩。
        第二: 奴奴有分,奴才们基本都是平等的,虽有高级和低级奴才区分,但本质都是奴才。奴才之间是不能随意勾连,登门问客的,防止一块搞政变. 他们都有属于自己的活动空间,而且活动空间还巨大巨大,大到奴才们觉得整个紫荆城都是他们家的,给你这么大空间你干活才有动力,奴才们是铆足了劲一个个尽情的表演各种剧本,有玩电子商务的,有玩游戏的,有搞直播的等等。。。不愧是紫荆城的主人很有一套,明明只有一个紫禁城,硬被他整出了N个紫荆城的感觉。而且这套驾奴本领还取了个很好听的名字叫:虚拟内存。HarmonyOS关于内存部分的中文注解已基本完成,可前往代码仓库 [鸿蒙内核源码注释中文版 【 Gitee仓](https://gitee.com/weharmony/kernel_liteos_a_note) | [CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note) | [Github仓](https://github.com/kuangyufei/kernel_liteos_a_note) | [Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)详细阅读。虚拟内存长啥样?鸿蒙虚拟内存全景图:
![](https://img-blog.csdnimg.cn/20201029221604209.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
这是整个紫荆城的全貌图,里面的内核虚拟空间是主人专用的,里面放的是主人的资料,数据,奴才永远进不去,kernel heap 也是给主人专用的动态内存空间,管理奴才和日常运作开销很多时候需要动态申请内存,这个是专门用来提供给主人使用的。而所有奴才的空间都在叫用户空间的那一块。你没看错,是所有奴才的都在那。当然实际情况是用户空间比图中的大的多,因为主人其实用不了多少空间,大部分是留给奴才们干活用了,因为篇幅的限制笔者把用户空间压缩了下。 再来看看奴才空间是啥样的。看图
![](https://img-blog.csdnimg.cn/20201029222858522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
这张图是第一张图的局部用户空间放大图。里面放的是奴才的私人用品,数据,task运行栈区,动态分配内存的堆区,堆区自下而上,栈区自上而下中间有虚拟地址<--->物理地址的映射区隔开。这么多奴才在里面不挤吗?答案是:真不挤 。主人手眼通天,因为用了一个好帮手解决了这个问题,这个帮手名叫 MMU(李大总管)
## MMU是干什么事的?
看下某度对MMU定义:它是一种负责处理[中央处理器](https://baike.baidu.com/item/%E4%B8%AD%E5%A4%AE%E5%A4%84%E7%90%86%E5%99%A8)(CPU)的[内存](https://baike.baidu.com/item/%E5%86%85%E5%AD%98)访问请求的[计算机硬件](https://baike.baidu.com/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A1%AC%E4%BB%B6)。它的功能包括[虚拟地址](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80)[物理地址](https://baike.baidu.com/item/%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80)的转换(即[虚拟内存](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98)管理)、内存保护、中央处理器[高速缓存](https://baike.baidu.com/item/%E9%AB%98%E9%80%9F%E7%BC%93%E5%AD%98)的控制。通过它的一番操作,把物理空间成倍成倍的放大,他们之间的映射关系存放在页面中。
好像看懂又好像没看懂是吧,到底是干啥的?其实就是个地址映射登记中心。记住这两个字:映射 看下图
![](https://img-blog.csdnimg.cn/2020092619274388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
物理内存可以理解为真实世界的紫禁城,虚拟内存就是被MMU虚拟出来的比物理页面大的多的空间。举例说明大概说明下过程:
有A,B,C 三个奴才来到紫禁城,每个人都很有抱负,主子规定要先跑去登记处登记活动范围,领回来一张表 叫 L1页表,上面说了大半个紫禁城你可以跑动,都是你的,L1页表记录你详细了每个房间的编号。其实奴才们的表都一样,能跑的范围也都一样。
怎么跑呢?在CPU单核的情况下同时只能有一个人在跑,CPU双核就是两个人跑,其他人都看着他们跑,等待主人叫到自己的号去跑。每个人跑之前先把领的表交给地址映射中心的负责人李大总管,比如A需要把自己编号999号房的菜拿出来切炒,李大总管拿表一看 999号对应的是 88号真实房间,而88号已经有B奴才的东西了,怎么办?简单啊,把B奴才的东西移到城外的仓库,再把A数据从城外移到88号房。这样A就拿到了他认为的是在999号房的菜,它全程都不用知道 88号房。记住这是映射的核心机制!!! 这个过程涉及到两个内存概念:缺页中断和页面置换。缺页中断就是88号房被B占了发出一个中断信号,页面置换就是把88号房的东西先搬到城外B的仓库,才从A仓库的菜搬到88号房。李大总管最后更新下内部表,88号仓给了A奴才用。李大总管的私人表叫 TLB(translation lookaside buffer)可翻译为“地址转换后援缓冲器”,也可简称为“快表”。简单地说,TLB就是页表的Cache
置换就要有算法,常用的是 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
虚拟内存和物理内存的管理分配方式是不一样的,但都是以页(page)为单位来管理,一页4K字节。
物理内存:段页管理,伙伴算法分配(buddy算法)。LOS_PhysPagesAllocContiguous 这里只到函数级,物理内存的管理和分配很简单,知道伙伴算法的原理就搞清楚了物理内存的管理。啥是伙伴算法?简单说就是把蛋糕按 2^0,2^1...2^order先切成一块块的, 2^0的统一放一起,2^order的统一放一起,有几个order就用几个链表管理,颗粒粗,分配至少一页起。
虚拟内存:线性区管理,内存池分配(slab算法)。LOS_MemAlloc 内存池分配,细颗粒分配,从物理内存拿了一页空间,根据需要再割给申请方。
举例说下流程:A用户向虚拟内存申请1K内存,虚拟内存一看内存池里只有半K(512)了,不够了就转身向物理内存管理方要了一页(4K),再割1K给A。
虚拟内存管理在鸿蒙主要由 VmMapRegion 这个结构体来串联,很复杂,什么红黑树,映射(file,swap),共享内存,访问权限等等都由它来完成。这些在其他篇幅中有详细介绍。
!import[\zzz\mdmerge\foot.md]
!export[\zzz\md\docs\guide\鸿蒙内核源码分析(内存分配篇).md]
\ No newline at end of file
!import[\zzz\mdmerge\head.md]
## 最难讲的章节
        内存模块占了 HarmonyOS 内核约15%代码量, 近20个.c文件,很复杂。系列篇将用九篇来介绍HarmonyOS内存部分,分别是 鸿蒙内核源码分析(内存概念篇) | 鸿蒙内核源码分析(内存管理篇) | 鸿蒙内核源码分析(内存汇编篇) |  鸿蒙内核源码分析(内存分配篇)  |  鸿蒙内核源码分析(内存映射篇)  | 鸿蒙内核源码分析(内存空间篇)  | 鸿蒙内核源码分析(内存置换篇)  | 鸿蒙内核源码分析(内存共享篇) | 鸿蒙内核源码分析(内存故事篇) 故事篇中会用生活场景张大爷故事里的另一个主角王场馆来举例 ,这些篇幅内容也会反复修改完善。想想也是内存何等重要,内核本身也是程序要运行空间, 用户程序也要在运行空间,大家都在一个窝里吃饭, 你凭什么就管我了, 凭什么的问题会在系列篇的最后结尾 鸿蒙内核源码分析(主奴机制篇) 中详细介绍, 哎! 其实用户进程就是内核的一个个奴才, 被捏的死死的.  按不住奴才那这主子就不合格,就不是一个稳定的运作系统. 试着想想 实际内存就这么点大, 如何满足众多用户进程的需求? 内核空间和用户空间如何隔离? 如何防止访问乱串? 如何分配如何释放防止碎片化? 空间不够了又如何置换到硬盘?   想想头都大了。内核这当家的主子真是不容易,这些都是他要解决的问题, 欲戴其冠,必承其重嘛.本章是笔者认真读完鸿蒙内存模块代码后的总结,获益良多,讲之前先重新梳理下内存的一些概念再来详细阐述内存模块的其他细节. 那开始吧. 
## 先说如果没有内存管理会怎样?
       那就是个奴才们能把主子给活活踩死, 想想主奴不分,吃喝拉撒睡都在一起,称兄道弟的想干啥? 没规矩不成方圆嘛,这事业肯定搞不大,单片机时代就是这种情况. 裸机编程,指针可以随便乱飞,数据可以随意覆盖,没有划定边界,没有明确职责,没有特权指令,没有地址保护,你还想像java开发一样,只管new内存,不去释放,应用可以随便崩但系统跑的妥妥的?想的美! 直接系统死机,甚至开机都开不了,主板直接报废了. 所以不能运行很复杂的程序,尽量可控,而且更是不可能支持应用的动态加载运行. 队伍大了就不好带了,方法得换, 游击队的做法不适合规模作战,内存就需要管理了,而且是 5A级的严格管理。
## 内存管理在管什么?
        简单说就是给主子赋能,拥有超级权利,为什么就他有? 因为他先来,掌握了先机.它定好了游戏规则,你们来玩.有哪些游戏规则?
        第一: 主奴有别,主子即是裁判又是运动员,主子有主子地方,奴才们有奴才们待的地方,主子可以在你的空间走来走去,但你只能在主人划定的区域活动.奴才把自己玩崩了也只是奴才狗屁了, 但主人和其他人还会是好好的. 主子有所有特权,比如某个奴才太嚣张了,就直接拖到午门问斩。
        第二: 奴奴有分,奴才们基本都是平等的,虽有高级和低级奴才区分,但本质都是奴才。奴才之间是不能随意勾连,登门问客的,防止一块搞政变. 他们都有属于自己的活动空间,而且活动空间还巨大巨大,大到奴才们觉得整个紫荆城都是他们家的,给你这么大空间你干活才有动力,奴才们是铆足了劲一个个尽情的表演各种剧本,有玩电子商务的,有玩游戏的,有搞直播的等等。。。不愧是紫荆城的主人很有一套,明明只有一个紫禁城,硬被他整出了N个紫荆城的感觉。而且这套驾奴本领还取了个很好听的名字叫:虚拟内存。HarmonyOS关于内存部分的中文注解已基本完成,可前往代码仓库 [鸿蒙内核源码注释中文版 【 Gitee仓](https://gitee.com/weharmony/kernel_liteos_a_note) | [CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note) | [Github仓](https://github.com/kuangyufei/kernel_liteos_a_note) | [Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)详细阅读。虚拟内存长啥样?鸿蒙虚拟内存全景图:
![](https://img-blog.csdnimg.cn/20201029221604209.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
这是整个紫荆城的全貌图,里面的内核虚拟空间是主人专用的,里面放的是主人的资料,数据,奴才永远进不去,kernel heap 也是给主人专用的动态内存空间,管理奴才和日常运作开销很多时候需要动态申请内存,这个是专门用来提供给主人使用的。而所有奴才的空间都在叫用户空间的那一块。你没看错,是所有奴才的都在那。当然实际情况是用户空间比图中的大的多,因为主人其实用不了多少空间,大部分是留给奴才们干活用了,因为篇幅的限制笔者把用户空间压缩了下。 再来看看奴才空间是啥样的。看图
![](https://img-blog.csdnimg.cn/20201029222858522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
这张图是第一张图的局部用户空间放大图。里面放的是奴才的私人用品,数据,task运行栈区,动态分配内存的堆区,堆区自下而上,栈区自上而下中间有虚拟地址<--->物理地址的映射区隔开。这么多奴才在里面不挤吗?答案是:真不挤 。主人手眼通天,因为用了一个好帮手解决了这个问题,这个帮手名叫 MMU(李大总管)
## MMU是干什么事的?
看下某度对MMU定义:它是一种负责处理[中央处理器](https://baike.baidu.com/item/%E4%B8%AD%E5%A4%AE%E5%A4%84%E7%90%86%E5%99%A8)(CPU)的[内存](https://baike.baidu.com/item/%E5%86%85%E5%AD%98)访问请求的[计算机硬件](https://baike.baidu.com/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A1%AC%E4%BB%B6)。它的功能包括[虚拟地址](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80)[物理地址](https://baike.baidu.com/item/%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80)的转换(即[虚拟内存](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98)管理)、内存保护、中央处理器[高速缓存](https://baike.baidu.com/item/%E9%AB%98%E9%80%9F%E7%BC%93%E5%AD%98)的控制。通过它的一番操作,把物理空间成倍成倍的放大,他们之间的映射关系存放在页面中。
好像看懂又好像没看懂是吧,到底是干啥的?其实就是个地址映射登记中心。记住这两个字:映射 看下图
![](https://img-blog.csdnimg.cn/2020092619274388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
物理内存可以理解为真实世界的紫禁城,虚拟内存就是被MMU虚拟出来的比物理页面大的多的空间。举例说明大概说明下过程:
有A,B,C 三个奴才来到紫禁城,每个人都很有抱负,主子规定要先跑去登记处登记活动范围,领回来一张表 叫 L1页表,上面说了大半个紫禁城你可以跑动,都是你的,L1页表记录你详细了每个房间的编号。其实奴才们的表都一样,能跑的范围也都一样。
怎么跑呢?在CPU单核的情况下同时只能有一个人在跑,CPU双核就是两个人跑,其他人都看着他们跑,等待主人叫到自己的号去跑。每个人跑之前先把领的表交给地址映射中心的负责人李大总管,比如A需要把自己编号999号房的菜拿出来切炒,李大总管拿表一看 999号对应的是 88号真实房间,而88号已经有B奴才的东西了,怎么办?简单啊,把B奴才的东西移到城外的仓库,再把A数据从城外移到88号房。这样A就拿到了他认为的是在999号房的菜,它全程都不用知道 88号房。记住这是映射的核心机制!!! 这个过程涉及到两个内存概念:缺页中断和页面置换。缺页中断就是88号房被B占了发出一个中断信号,页面置换就是把88号房的东西先搬到城外B的仓库,才从A仓库的菜搬到88号房。李大总管最后更新下内部表,88号仓给了A奴才用。李大总管的私人表叫 TLB(translation lookaside buffer)可翻译为“地址转换后援缓冲器”,也可简称为“快表”。简单地说,TLB就是页表的Cache
置换就要有算法,常用的是 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
虚拟内存和物理内存的管理分配方式是不一样的,但都是以页(page)为单位来管理,一页4K字节。
物理内存:段页管理,伙伴算法分配(buddy算法)。LOS_PhysPagesAllocContiguous 这里只到函数级,物理内存的管理和分配很简单,知道伙伴算法的原理就搞清楚了物理内存的管理。啥是伙伴算法?简单说就是把蛋糕按 2^0,2^1...2^order先切成一块块的, 2^0的统一放一起,2^order的统一放一起,有几个order就用几个链表管理,颗粒粗,分配至少一页起。
虚拟内存:线性区管理,内存池分配(slab算法)。LOS_MemAlloc 内存池分配,细颗粒分配,从物理内存拿了一页空间,根据需要再割给申请方。
举例说下流程:A用户向虚拟内存申请1K内存,虚拟内存一看内存池里只有半K(512)了,不够了就转身向物理内存管理方要了一页(4K),再割1K给A。
虚拟内存管理在鸿蒙主要由 VmMapRegion 这个结构体来串联,很复杂,什么红黑树,映射(file,swap),共享内存,访问权限等等都由它来完成。这些在其他篇幅中有详细介绍。
!import[\zzz\mdmerge\foot.md]
!export[\zzz\md\docs\guide\鸿蒙内核源码分析(内存概念篇).md]
\ No newline at end of file
!import[\zzz\mdmerge\head.md]
## 鸿蒙虚拟内存全景图
![](https://img-blog.csdnimg.cn/20201029221604209.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
## 再看鸿蒙用户空间全景图
![](https://img-blog.csdnimg.cn/20201029222858522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
以上两图是笔者阅读完鸿蒙内核源码内存模块所绘制,[给鸿蒙内核源码逐行加上中文注释 【 Gitee仓](https://gitee.com/weharmony/kernel_liteos_a_note) | [CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note) | [Github仓](https://github.com/kuangyufei/kernel_liteos_a_note) | [Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)已正式上线,四大码仓每日同步更新。更多图在仓库中用 @note_pic 搜索查看。
## 内存在内核的比重极大
内存模块占了鸿蒙内核约15%代码量, 近50个文件,非常复杂。想想也是,内存何等重要,内核本身和应用程序一样,也是程序,也要在内存中运行, 大家都在一个窝里吃饭, 你凭什么就管我了, 凭什么的问题会在系列篇的最后结尾 鸿蒙内核源码分析(主奴机制篇) 中详细介绍,  内核就是皇上,应用进程就是奴才。 奴才们被皇上捏的死死的.  按不住这帮奴才那这主子就不合格, 没有稳定的运作系统还谈什么万里江山。 而本篇要说的内存就是紫禁城!请大家试着想想,我们的实际内存就这么点大, 如何满足众多应用程序的需求? 要看抖音,玩微信,逛京东,吃鸡,作为用户巴不得全打开,还要快速响应不能有时延,这些都要在内存中完成,是怎么做到的呢?内核空间和用户空间如何隔离? 如何防止访问乱串? 如何分配如何释放防止碎片化? 空间不够了又如何置换到硬盘?   想想头都大了。内核这当家的主子真是不容易, 这些都是他要解决的问题, 但欲戴其冠,必承其重,没有两把刷子还当什么皇上. 
## 先说如果没有内存管理会怎样?
       那就是个奴强主弱的时代!紫禁城乱成一锅粥了。奴才们个个能把主子给活活踩死,  想想主奴不分,吃喝拉撒睡都在一起。称兄道弟,平起平坐能干成个啥? 没规矩不成方圆嘛,这事业肯定搞不大,单片机时代就是这种情况. 裸机编程,指针可以随便乱飞,数据可以随意覆盖,没有划定边界,没有明确职责,没有特权指令,没有地址保护,你还想像java开发一样,只管new内存,不去释放,应用可以随便崩但系统跑的妥妥的?想的美! 直接系统死机,甚至开机都开不了,主板直接报废了. 所以不能运行很复杂的程序,尽量可控,队伍一旦大了就不好带了,方法得换, 游击队的做法不适合规模作战,内存就需要管理了,而且是需要5A级的严格管理。
## 内存管理在管什么?
简单说就是给主子赋能,拥有超级权利,为什么就他有? 因为他先来,掌握了先机!它定好了游戏规则,你们来玩.那有哪些游戏规则呢?
第一: 主奴有别,主子即是裁判又是运动员,主子有主子地方,奴才们有奴才们待的地方,主子可以在你的空间走来走去,但你只能在主人划定的区域活动.奴才把自己玩崩了也只是奴才狗屁了, 但主人和其他人还会是好好的. 主子有所有特权,比如某个奴才太嚣张了,就直接拖到午门问斩。
第二: 奴奴有分,奴才们基本都是平等的,虽有高级和低级奴才区分,但本质都是奴才。奴才之间是不能随意勾连,登门拜访的,防止一块搞政变. 他们都有属于自己的活动空间,而且活动空间还巨大巨大,大到奴才们觉得整个紫荆城都是他们家的,给你这么大空间你干活才有动力,奴才们是铆足了劲一个个尽情的表演各种剧本,有玩电子商务的,有玩游戏的,有搞直播的等等。。。不愧是紫荆城的主人很有一套,明明只有一个紫禁城,硬被他整出了N个紫荆城的感觉。而且这套管理紫禁城的本领还取了个很好听的名字叫:虚拟内存。
鸿蒙虚拟内存全景图 里面的内核虚拟空间是主人专用的,里面放的是主人的资料,数据,奴才永远进不去,kernel heap 也是给主人专用的动态内存空间,管理奴才和日常运作开销很多时候需要动态申请内存,这个是专门用来提供给主人使用的。而所有奴才的空间都在图中叫用户空间的那一块。您没看错,那就是所有奴才们活动的区域。鸿蒙用户空间全景图 是 鸿蒙虚拟内存全景图 的局部用户空间放大图,里面放的是奴才的私人用品,数据,task运行栈区,动态分配内存的堆区,堆区自下而上,栈区自上而下,中间由 虚拟地址<--->物理地址的映射区隔开,所谓映射区其实就是页表(L1,L2)存放的地方。那么问题来了,这么多奴才都在用户空间区里面不挤吗?答案是:真不挤 。咱皇上是谁啊,那手眼通天了去,因为用了一个好帮手解决了这个问题,这个帮手名叫 MMU(李大总管)
## MMU是干什么事的?
看下某度对MMU定义:它是一种负责处理[中央处理器](https://baike.baidu.com/item/%E4%B8%AD%E5%A4%AE%E5%A4%84%E7%90%86%E5%99%A8)(CPU)的[内存](https://baike.baidu.com/item/%E5%86%85%E5%AD%98)访问请求的[计算机硬件](https://baike.baidu.com/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A1%AC%E4%BB%B6)。它的功能包括[虚拟地址](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80)[物理地址](https://baike.baidu.com/item/%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80)的转换(即[虚拟内存](https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98)管理)、内存保护、中央处理器[高速缓存](https://baike.baidu.com/item/%E9%AB%98%E9%80%9F%E7%BC%93%E5%AD%98)的控制。通过它的一番操作,把物理空间成倍成倍的放大,他们之间的映射关系存放在页面中。
好像看懂又好像没看懂是吧,到底是干啥的?其实就是个地址映射登记中心。记住这两个字:映射 看下图
![](https://img-blog.csdnimg.cn/2020092619274388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
物理内存可以理解为真实世界的紫禁城,虚拟内存就是被MMU虚拟出来的比物理页面大的多的空间。举例说明大概说明下过程:
有A,B,C 三个奴才来到紫禁城,每个人都很有抱负,主子规定要先跑去登记处登记活动范围,领回来一张表 叫 L1页表,上面说了大半个紫禁城你可以跑动,都是你的,L1页表记录你详细了每个房间的编号。其实奴才们的表拿回来的时候内容都一样,能跑的范围也都一样,一开始表上并没什么内容,内容是你在使用过程中慢慢添加上去的。具体怎么添的呢?举例说明:
先说个前提 在CPU单核的情况下同时只能有一个奴才在做事,CPU双核就是两个人奴才做事,其他人都看着他们做事,等待主人叫到自己的号去再去做事。每个人做事之前先把领的表交给地址映射中心的负责人李大总管。
现轮到A奴才了,它负责美食业务的,需要仓库存放肉,菜。仓库向李大总管申请,李大总管同意了,告诉他编号999号仓库给你用,你的东西可以放里面,并在A奴才的表中添加了一条  A | 999 |88 的记录,李大总管自己也有张表,也记录了一条 A|999|88
但实际情况是A不可能一直运行下去,他的工作很可能被打断了,比如皇上想先看直播再吃饭那A就要先停止,让直播的B来工作了。B当然也有张表要交给李大总管并且B要申请个仓库放直播设备,李大总管一看没地方了,怎么办?很简单把自己的表改成了B|777|88,告诉B 777号仓库可以放设备。并在B的表上登记上了 B|777|88
到这里肯定有人问,里面的 88 是什么意思,88就是紫禁城里真实的88号仓库地址,也叫物理地址,999 和 777 都是虚拟出来的,也叫虚拟地址,根本就不存在。李大总管到底是怎么玩出花来的呢?
答案是这样的,当A重新需要把自己编号999号房的菜拿出来切炒时,李大总管拿A的表一看 999号对应的是 88号真实仓库,再看自己的私有表上88对应的是B|777|88, 说明88号已经有B奴才的东西了,怎么办?简单啊,把B奴才的东西移到城外的仓库,再把A数据从城外移到88号房。这样A就拿到了他认为的是在999号房的菜,A全程都不用知道 88号房的存在,它只认999号是我的仓库,里面会有我的菜就行了,记住这是映射的核心机制!!! 这个过程涉及到两个内存概念:缺页中断和页面置换。缺页中断就是88号房被B占了发出一个中断信号,页面置换就是把88号房的东西先搬到城外B的仓库,又从A仓库的菜搬到88号房。李大总管最后又更新下内部表 A|999|88,代表88号仓又给了A奴才用 。明白了吗?李大总管的私人表叫 TLB(translation lookaside buffer)可翻译为“地址转换后缓冲器”,也可简称为“快表”。简单地说,TLB就是页表的Cache ,具体代码可以去 [给鸿蒙内核源码逐行加上中文注释 【 Gitee仓](https://gitee.com/weharmony/kernel_liteos_a_note) | [CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note) | [Github仓](https://github.com/kuangyufei/kernel_liteos_a_note) | [Coding仓 】](https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files)看代码,里面每一行都加上了中文注释。那奴才们的表存放在哪里呢?还记得上面说的 “所谓映射区其实就是页表(L1,L2)存放的地方”那句话吗?
置换就要有算法,常用的是 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
虚拟内存和物理内存的管理分配方式是不一样的,但都是以页(page)为单位来管理,一页4K字节。
物理内存:段页管理,伙伴算法分配(buddy算法)。LOS_PhysPagesAllocContiguous 这里只到函数级,物理内存的管理和分配不复杂,知道伙伴算法的原理就搞清楚了物理内存的管理。啥是伙伴算法?简单说就是把蛋糕按 2^0,2^1...2^order先切成一块块的, 2^0的统一放一起,2^order的统一放一起,有几个order就用几个链表管理,颗粒粗,分配至少一页起,这句话很重要。
虚拟内存:线性区管理,内存池分配(slab算法)。LOS_MemAlloc 内存池分配,细颗粒分配,从物理内存拿了一页空间,根据需要再割给申请方。
举例说下流程:A用户向虚拟内存申请 1K内存,虚拟内存一看内存池里只有半K(512)了,不够就向物理内存管理方要了一页(4K),再割1K给A。
虚拟内存管理在鸿蒙主要由 VmMapRegion 这个结构体来串联,很复杂,什么红黑树,映射(file,swap),共享内存,访问权限等等都由它来完成。这些在其他篇幅中有详细介绍。
!import[\zzz\mdmerge\foot.md]
!export[\zzz\md\docs\guide\鸿蒙内核源码分析(内存规则篇).md]
\ No newline at end of file
!import[\zzz\mdmerge\head.md]
!import[\zzz\mdmerge\bloglist.md]
!import[\zzz\mdmerge\foot.md]
!export[\zzz\md\docs\guide\鸿蒙源码分析系列(总目录).md]
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册