整理技术文章内容

    搜索 @note_pic 可查看绘制的全部字符图
    搜索 @note_why 是尚未看明白的地方,有看明白的,请Pull Request完善
    搜索 @note_thinking 是一些的思考和建议
    搜索 @note_#if0 是由第三方项目提供不在内核源码中定义的极为重要结构体,为方便理解而添加的。
    搜索 @note_good 是给源码点赞的地方
    https://weharmony.github.io/
    公众号: 鸿蒙内核源码分析
上级 d66ee1a6
......@@ -887,7 +887,7 @@ EXIT:
OsDeInitPCB(processCB);//删除进程控制块,归还内存
return ret;
}
//初始化 2号进程,即内核根进程
LITE_OS_SEC_TEXT_INIT UINT32 OsKernelInitProcess(VOID)
{
LosProcessCB *processCB = NULL;
......
......@@ -206,7 +206,7 @@ LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID)
OsTicklessStart();
}
#endif
Wfi();
Wfi();//WFI指令:arm core 立即进入low-power standby state,等待中断,进入休眠模式。
}
}
......
git add -A
git commit -m '鸿蒙内核源码分析(时钟任务篇) | 时钟是触发调度最大的源动力 | 中文注解HarmonyOS源码 | v3.04
git commit -m '整理技术文章内容
搜索 @note_pic 可查看绘制的全部字符图
搜索 @note_why 是尚未看明白的地方,有看明白的,请Pull Request完善
搜索 @note_thinking 是一些的思考和建议
......
......@@ -5,12 +5,33 @@ module.exports = {
nav: [
{ text: '回首页', link: 'https://weharmony.github.io/' },
{
text: '博客文章列表',
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' },
......@@ -29,27 +50,6 @@ module.exports = {
{ text: '鸿蒙源码分析系列(总目录)', link: 'https://weharmony.github.io/guide/鸿蒙源码分析系列(总目录).html' },
]
},
{
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: 'CSDN', link: 'https://blog.csdn.net/kuangyufei/article/details/108727970' },
{ text: '开源中国', link: 'https://my.oschina.net/u/3751245/blog/4626852' },
{ text: '21盒子', link: 'https://harmonyos.21yunbox.com/' },
{ text: 'HarmonyOS', link: 'https://weharmony.github.io/' },
]
}
],
sidebar: {
"/guide/": "auto",
......
......@@ -3,46 +3,11 @@
[鸿蒙源码分析系列篇 【 CSDN ](https://blog.csdn.net/kuangyufei/article/details/108727970)[| OSCHINA ](https://my.oschina.net/u/3751245/blog/4626852)[| HarmonyOS 】](https://weharmony.github.io/)问答式导读, 生活式比喻, 图形化展示, 层层剥开内核神秘外衣.
---
## **一、任务即线程**
**目录**
在鸿蒙内核中,广义上可理解为一个任务就是一个线程
[前言](#%E5%89%8D%E8%A8%80)
[一、怎么理解Task](#%E4%B8%80%E3%80%81%E6%80%8E%E4%B9%88%E7%90%86%E8%A7%A3Task)
[1\. 官方文档是怎么描述线程](#1.%20%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E6%98%AF%E6%80%8E%E4%B9%88%E6%8F%8F%E8%BF%B0%E7%BA%BF%E7%A8%8B)
[2\. 执行task命令](#2.%20%E6%89%A7%E8%A1%8Ctask%E5%91%BD%E4%BB%A4)
[3\. task长得什么样子?](#3.%20task%E9%95%BF%E5%BE%97%E4%BB%80%E4%B9%88%E6%A0%B7%E5%AD%90%EF%BC%9F)
[二、Task怎么管理](#%E4%BA%8C%E3%80%81Task%E6%80%8E%E4%B9%88%E7%AE%A1%E7%90%86)
[1.什么是任务池?](#1.%E4%BB%80%E4%B9%88%E6%98%AF%E4%BB%BB%E5%8A%A1%E6%B1%A0%EF%BC%9F)
[2.就绪队列是怎么回事](#2.%E5%B0%B1%E7%BB%AA%E9%98%9F%E5%88%97%E6%98%AF%E6%80%8E%E4%B9%88%E5%9B%9E%E4%BA%8B)
[3.任务栈是怎么回事](#3.%E4%BB%BB%E5%8A%A1%E6%A0%88%E6%98%AF%E6%80%8E%E4%B9%88%E5%9B%9E%E4%BA%8B)
[4.任务栈初始化](#4.%E4%BB%BB%E5%8A%A1%E6%A0%88%E5%88%9D%E5%A7%8B%E5%8C%96)
[三、Task函数集](#%E4%B8%89%E3%80%81Task%E5%87%BD%E6%95%B0%E9%9B%86)
[1.使用场景和功能](#1.%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E5%92%8C%E5%8A%9F%E8%83%BD)
[2.创建任务的过程](#2.%E5%88%9B%E5%BB%BA%E4%BB%BB%E5%8A%A1%E7%9A%84%E8%BF%87%E7%A8%8B)
[3.Task部分还有哪些重要内容没讲到?](#%E7%BA%BF%E7%A8%8B%E9%83%A8%E9%97%A8%E8%BF%98%E6%9C%89%E5%93%AA%E4%BA%9B%E9%87%8D%E8%A6%81%E5%86%85%E5%AE%B9%E6%B2%A1%E8%AE%B2%E5%88%B0%EF%BC%9F)
# [前言](#)
[在鸿蒙内核中,广义上可理解为一个Task就是一个线程](#)
---
# 一、怎么理解Task
## 1\. 官方文档是怎么描述线程
### **官方是怎么描述线程的**
基本概念
从系统的角度看,线程是竞争系统资源的最小运行单元。线程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它线程运行。
......@@ -71,20 +36,20 @@
![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/43b868b7e3822f66ab7654b38bb6443e.png)
注意官方文档说的是线程,没有提到task(任务),但内核源码中却有大量 task代码,很少有线程(thread)代码 ,这是怎么回事?
其实在鸿蒙内核中, task就是线程, 初学者完全可以这么理解,但二者还是有区别,否则干嘛要分两个词描述。
到底有什么区别?是管理上的区别,task是调度层面的概念,线程是进程层面的概念。 就像同一个人在不同的管理体系中会有不同的身份一样,一个男人既可以是 孩子,爸爸,丈夫,或者程序员,视角不同功能也会不同。
有什么区别?是管理上的区别,task是调度层面的概念,线程是进程层面的概念。 就像同一个人在不同的管理体系中会有不同的身份一样,一个男人既可以是 孩子,爸爸,丈夫,或者程序员,视角不同功能也会不同。
如何证明是一个东西,继续再往下看。
## 2\. 执行task命令
### **执行task命令**
鸿蒙 task 命令的执行结果:
看shell task 命令的执行结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/912b298080e5e4ce01e4188baf6e0df0.png)
task命令 查出每个任务在生命周期内的运行情况,它运行的内存空间,优先级,时间片,入口执行函数,进程ID,状态等等信息,非常的复杂。这么复杂的信息就需要一个结构体来承载。而这个结构体就是 LosTaskCB(任务控制块)
对应张大爷的故事:task就是一个用户的节目清单里的一个节目,用户总清单就是一个进程,所以上面会有很多的节目。
## 3\. task长得什么样子?
### **task长得什么样子**
说LosTaskCB之前先说下官方文档任务状态对应的 define,可以看出task和线程是一个东西。
......@@ -98,8 +63,6 @@ task命令 查出每个任务在生命周期内的运行情况,它运行的内
#define OS_TASK_STATUS_TIMEOUT 0x0040U
#define OS_TASK_STATUS_PEND_TIME 0x0080U
#define OS_TASK_STATUS_EXIT 0x0100U
```
LosTaskCB长什么样?抱歉,它确实有点长,但还是要全部贴出全貌。
......@@ -167,9 +130,9 @@ typedef struct {
结构体LosTaskCB内容很多,各代表什么含义?
LosTaskCB相当于任务在内核中的身份证,它反映出每个任务在生命周期内的运行情况。既然是周期就会有状态,要运行就需要内存空间,就需要被内核算法调度,被选中CPU就去执行代码段指令,CPU要执行就需要告诉它从哪里开始执行,因为是多线程,但只有一个CPU就需要不断的切换任务,那执行会被中断,也需要再恢复后继续执行,又如何保证恢复的任务执行不会出错,这些问题都需要说明白。
# 二、Task怎么管理
## **二、Task怎么管理**
## 1.什么是任务池?
### **什么是任务池?**
前面已经说了任务是内核调度层面的概念,调度算法保证了task有序的执行,调度机制详见其他姊妹篇的介绍。
如此多的任务怎么管理和执行?管理靠任务池和就绪队列,执行靠调度算法。
......@@ -178,17 +141,18 @@ LosTaskCB相当于任务在内核中的身份证,它反映出每个任务在
```cpp
LITE_OS_SEC_TEXT_INIT UINT32 OsTaskInit(VOID)
{
UINT32 index;
UINT32 ret;
UINT32 size;
g_taskMaxNum = LOSCFG_BASE_CORE_TSK_LIMIT;//任务池中最多默认128个,可谓铁打的任务池流水的线程
size = (g_taskMaxNum + 1) * sizeof(LosTaskCB);
/* * This memory is resident memory and is used to save the system resources * of task control block and will not be freed. */
size = (g_taskMaxNum + 1) * sizeof(LosTaskCB);//计算需分配内存总大小
/*
* This memory is resident memory and is used to save the system resources
* of task control block and will not be freed.
*/
g_taskCBArray = (LosTaskCB *)LOS_MemAlloc(m_aucSysMem0, size);//任务池 常驻内存,不被释放
if (g_taskCBArray == NULL) {
return LOS_ERRNO_TSK_NO_MEMORY;
}
(VOID)memset_s(g_taskCBArray, size, 0, size);
......@@ -196,40 +160,33 @@ LITE_OS_SEC_TEXT_INIT UINT32 OsTaskInit(VOID)
LOS_ListInit(&g_losFreeTask);//空闲任务链表
LOS_ListInit(&g_taskRecyleList);//需回收任务链表
for (index = 0; index < g_taskMaxNum; index++) {
g_taskCBArray[index].taskStatus = OS_TASK_STATUS_UNUSED;
g_taskCBArray[index].taskID = index;//任务ID最大默认127
LOS_ListTailInsert(&g_losFreeTask, &g_taskCBArray[index].pendList);//都插入空闲任务链表
}
LOS_ListTailInsert(&g_losFreeTask, &g_taskCBArray[index].pendList);//都插入空闲任务列表
}//注意:这里挂的是pendList节点,所以取TCB要通过 OS_TCB_FROM_PENDLIST 取.
ret = OsPriQueueInit();//创建32个任务优先级队列,即32个双向循环链表
if (ret != LOS_OK) {
return LOS_ERRNO_TSK_NO_MEMORY;
}
/* init sortlink for each core */
for (index = 0; index < LOSCFG_KERNEL_CORE_NUM; index++) {
ret = OsSortLinkInit(&g_percpu[index].taskSortLink);//每个CPU内核都有一个执行任务链表
if (ret != LOS_OK) {
return LOS_ERRNO_TSK_NO_MEMORY;
}
}
return LOS_OK;
}
```
g_taskCBArray 就是个任务池,默认创建128个任务,常驻内存,不被释放。
g_losFreeTask是空闲任务链表,想创建任务时来这里申请一个空闲任务,用完了就回收掉,继续给后面的申请使用。
g_taskRecyleList是回收任务链表,专用来回收exit 任务,任务所占资源被确认归还后被彻底删除,就像员工离职一样,得有个离职队列和流程,要归还电脑,邮箱,有没有借钱要还的 等操作。
对应张大爷的故事:用户要来场馆领取表格填节目单,场馆只准备了128张表格,领完就没有了,但是节目表演完了会回收表格,这样多了一张表格就可以给其他人领取了,这128张表格对应鸿蒙内核这就是任务池,简单吧。
## 2.就绪队列是怎么回事
### **就绪队列是怎么回事**
CPU执行速度是很快的,鸿蒙内核默认一个时间片是 10ms, 资源有限,需要在众多任务中来回的切换,所以绝不能让CPU等待任务,CPU就像公司最大的领导,下面很多的部门等领导来审批,吃饭。只有大家等领导,哪有领导等你们的道理,所以工作要提前准备好,每个部门的优先级又不一样,所以每个部门都要有个任务队列,里面放的是领导能直接处理的任务,没准备好的不要放进来,因为这是给CPU提前准备好的粮食!
这就是就绪队列的原理,一共有32个就绪队列,进程和线程都有,因为线程的优先级是默认32个, 每个队列中放同等优先级的task.
......@@ -263,7 +220,7 @@ UINT32 OsPriQueueInit(VOID)
对应张大爷的故事:就是门口那些排队的都是至少有一个节目单是符合表演标准的,资源都到位了,没有的连排队的资格都木有,就慢慢等吧。
## 3.任务栈是怎么回事
### **任务栈是怎么回事**
每个任务都是独立开的,任务之间也相互独立,之间通讯通过IPC,这里的“独立”指的是每个任务都有自己的运行环境 —— 栈空间,称为任务栈,栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等等
但系统中只有一个CPU,任务又是独立的,调度的本质就是CPU执行一个新task,老task在什么地方被中断谁也不清楚,是随机的。那如何保证老任务被再次调度选中时还能从上次被中断的地方继续玩下去呢?
......@@ -289,10 +246,7 @@ typedef struct {
UINT32 LR; /* R14 */
UINT32 PC; /* R15 */
} TaskContext;
```
发现基本都是CPU寄存器的恢复现场值, 具体各寄存器有什么作用大家可以去网上详查,后续也有专门的文章来介绍。这里说其中的三个寄存器 SP, LR, PC
LR
......@@ -304,11 +258,11 @@ PC(Program Counter)
SP
每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。
## 4.任务栈初始化
### **任务栈初始化**
任务栈的初始化就是任务上下文的初始化,因为任务没开始执行,里面除了上下文不会有其他内容,注意上下文存放的位置在栈的底部。初始状态下 sp就是指向的栈底, 栈顶内容永远是 0xCCCCCCCC "烫烫烫烫",这几个字应该很熟悉吗? 如果不是那几个字了,那说明栈溢出了, 后续篇会详细说明这块,大家也可以自行去看代码,很有意思.
# 三、Task函数集
## **三、Task函数集**
```cpp
LITE_OS_SEC_TEXT_INIT VOID *OsTaskStackInit(UINT32 taskID, UINT32 stackSize, VOID *topStack, BOOL initFlag)
......@@ -356,11 +310,8 @@ LITE_OS_SEC_TEXT_INIT VOID *OsTaskStackInit(UINT32 taskID, UINT32 stackSize, VOI
return (VOID *)taskContext;
}
```
## 1.使用场景和功能
### **使用场景和功能**
任务创建后,内核可以执行锁任务调度,解锁任务调度,挂起,恢复,延时等操作,同时也可以设置任务优先级,获取任务优先级。任务结束的时候,则进行当前任务自删除操作。
Huawei LiteOS 系统中的任务管理模块为用户提供下面几种功能。
......@@ -387,35 +338,30 @@ Huawei LiteOS 系统中的任务管理模块为用户提供下面几种功能。
|   | LOS_TaskInfoMonitor | 监控所有任务,获取所有任务的信息。 |
|   | LOS_NextTaskIDGet | 获取即将被调度的任务的ID。 |
## 2.创建任务的过程
### **创建任务的过程**
创建任务之前先了解另一个结构体 tagTskInitParam
```cpp
typedef struct tagTskInitParam {
TSK_ENTRY_FUNC pfnTaskEntry; /**< Task entrance function */
UINT16 usTaskPrio; /**< Task priority */
UINT16 policy; /**< Task policy */
UINTPTR auwArgs[4]; /**< Task parameters, of which the maximum number is four */
UINT32 uwStackSize; /**< Task stack size */
CHAR *pcName; /**< Task name */
typedef struct tagTskInitParam {//Task的初始化参数
TSK_ENTRY_FUNC pfnTaskEntry; /**< Task entrance function */ //任务的入口函数
UINT16 usTaskPrio; /**< Task priority */ //任务优先级
UINT16 policy; /**< Task policy */ //任务调度方式
UINTPTR auwArgs[4]; /**< Task parameters, of which the maximum number is four */ //入口函数的参数,最多四个
UINT32 uwStackSize; /**< Task stack size */ //任务栈大小
CHAR *pcName; /**< Task name */ //任务名称
#if (LOSCFG_KERNEL_SMP == YES)
UINT16 usCpuAffiMask; /**< Task cpu affinity mask */
UINT16 usCpuAffiMask; /**< Task cpu affinity mask */ //任务cpu亲和力掩码
#endif
UINT32 uwResved; /**< It is automatically deleted if set to LOS_TASK_STATUS_DETACHED. It is unable to be deleted if set to 0. */
UINT16 consoleID; /**< The console id of task belongs */
UINT32 processID;
UserTaskParam userParam;
UINT32 uwResved; /**< It is automatically deleted if set to LOS_TASK_STATUS_DETACHED.
It is unable to be deleted if set to 0. */ //如果设置为LOS_TASK_STATUS_DETACHED,则自动删除。如果设置为0,则无法删除
UINT16 consoleID; /**< The console id of task belongs */ //任务的控制台id所属
UINT32 processID; //进程ID
UserTaskParam userParam; //在用户态运行时栈参数
} TSK_INIT_PARAM_S;
```
这些初始化参数是外露的任务初始参数,`pfnTaskEntry 对java来说就是你new进程的run(),`需要上层使用者提供.
看个例子吧:shell中敲 ping 命令看下它创建的过程
```cpp
u32_t osShellPing(int argc, const char **argv)
{
......@@ -454,12 +400,8 @@ ping_error:
lwip_ping_usage();
return LOS_NOK;
}
```
发现ping的调度优先级是8,比shell 还高,那shell的是多少?答案是:看源码是 9
```cpp
LITE_OS_SEC_TEXT_MINOR UINT32 ShellTaskInit(ShellCB *shellCB)
{
......@@ -486,13 +428,9 @@ LITE_OS_SEC_TEXT_MINOR UINT32 ShellTaskInit(ShellCB *shellCB)
(VOID)LOS_EventInit(&shellCB->shellEvent);
return LOS_TaskCreate(&shellCB->shellTaskHandle, &initParam);
}
```
关于shell后续会详细介绍,请持续关注。
前置条件了解清楚后,具体看任务是如何一步步创建的,如何和进程绑定,加入调度就绪队列,还是继续看源码
```cpp
//创建Task
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *initParam)
......@@ -542,19 +480,16 @@ LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *in
return LOS_OK;
}
```
对应张大爷的故事:就是节目单要怎么填,按格式来,从哪里开始演,要多大的空间,王场馆好协调好现场的环境。这里注意 在同一个节目单只要节目没演完,王场馆申请场地的空间就不能给别人用,这个场地空间对应的就是鸿蒙任务的栈空间,除非整个节目单都完了,就回收了。把整个场地干干净净的留给下一个人的节目单来表演。
至此的创建已经完成,已各就各位,源码最后还申请了一次LOS_Schedule();因为鸿蒙的调度方式是抢占式的,如何本次task的任务优先级高于其他就绪队列,那么接下来要执行的任务就是它了!
## 3.Task部分还有哪些重要内容没讲到?
## **四、Task部分还有哪些重要内容没讲到?**
Task 状态间如何运作,如何阻塞, 如何被唤醒? 内存怎么分配的,task之间怎么通讯,运行期间堆栈是怎么执行的,在系列篇内存,IPC中有详细的描述
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
......
......@@ -4,7 +4,7 @@
---
### 谁是鸿蒙内核最重要的结构体? 
## 谁是鸿蒙内核最重要的结构体? 
答案一定是: LOS\_DL\_LIST(双向链表),它长这样.
......
......@@ -6,7 +6,7 @@
调度算法让CPU在不同的进程和任务之间切换穿梭,但问题是
### **调度算法的驱动力在哪里? 谁负责推着调度算法走?**
## **调度算法的驱动力在哪里? 谁负责推着调度算法走?**
有多股力量在推动,但最大的推力应该是:系统时钟.
......@@ -16,7 +16,7 @@
对应张大爷的故事:系统时钟就是场馆的那个大钟,很准时, 每10分响一次,一次就是一个Tick(节拍)
### **鸿蒙内核的节拍频率是怎样的呢? 看代码**
### **鸿蒙内核的节拍频率是怎样的**
```cpp
/**
* @ingroup los_config
......
......@@ -4,25 +4,9 @@
---
**[内核代码详细结构 >> 进入阅读代码](https://my.oschina.net/u/3751245/blog/4869137)**
# 建议先阅读
**目录**
[建议先阅读](#%E5%BB%BA%E8%AE%AE%E5%85%88%E9%98%85%E8%AF%BB)
[为什么学一个东西要学那么多的概念?](#%E5%85%88%E8%AF%B4%E5%87%A0%E4%B8%AA%E6%A6%82%E5%BF%B5)
[进程和线程的状态迁移图](#%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E8%BF%81%E7%A7%BB%E5%9B%BE)
[谁来触发调度工作?](#%E8%B0%83%E5%BA%A6%E6%98%AF%E5%A6%82%E4%BD%95%E8%A7%A6%E5%8F%91%E7%9A%84%EF%BC%9F)
[源码告诉你调度过程是怎样的?](#%E8%B0%83%E5%BA%A6%E8%BF%87%E7%A8%8B)
[请读懂内核最美函数 OsGetTopTask()](#OsGetTopTask())
## 建议先阅读
阅读之前建议先读本系列其他文章,进入[鸿蒙系统源码分析(总目录)](https://blog.csdn.net/kuangyufei/article/details/108727970),以便对本文任务调度机制的理解。
阅读之前建议先读本系列其他文章,以便对本文任务调度机制的理解。
## 为什么学一个东西要学那么多的概念?
......@@ -42,7 +26,7 @@
渠道很多,可能是shell 的一个命令,也可能由内核创建,更多的是大家编写应用程序new出来的一个线程。
调度的内容task已经有了,那他们是如何被有序调度的呢?答案:是32个进程和线程就绪队列,各32个哈,为什么是32个,[鸿蒙系统源码分析(总目录)](https://blog.csdn.net/kuangyufei/article/details/108727970)文章里有详细说明,自己去翻。这张进程状态迁移示意图一定要看明白,线程的状态迁移大家去官方文档看,不一一列出来,太多了占地方。
调度的内容task已经有了,那他们是如何被有序调度的呢?答案:是32个进程和线程就绪队列,各32个哈,为什么是32个,鸿蒙系统源码分析(总目录) 文章里有详细说明,自行去翻。这张进程状态迁移示意图一定要看明白.
注意:进程和线程的队列内的内容只针对就绪状态,其他状态内核并没有用队列去描述它,(线程的阻塞状态用的是pendlist链表),因为就绪就意味着工作都准备好了就等着被调度到CPU来执行了。所以理解就绪队列很关键,有三种情况会加入就绪队列。
......@@ -104,7 +88,6 @@ LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
#endif
}
```
里面对任务进行了扫描,时间片到了或就绪队列有高或同级task, 会执行调度。
- 第二个是各种软硬中断,如何USB插拔,键盘,鼠标这些外设引起的中断,需要去执行中断处理函数。
......@@ -188,7 +171,7 @@ VOID OsSchedResched(VOID)
OsCurrUserTaskSet(newTask->userArea);//设置任务空间
}
/* do the task context switch */
OsTaskSchedule(newTask, runTask); //切换CPU任务上下文
OsTaskSchedule(newTask, runTask); //切换CPU任务上下文,汇编代码实现
}
```
......@@ -201,7 +184,7 @@ VOID OsSchedResched(VOID)
5. 用户模式下需要设置task运行空间,因为每个task栈是不一样的.空间部分具体在系列篇内存中查看
6. 是最重要的,切换任务上下文,参数是新老两个任务,一个要保存现场,一个要恢复现场。
什么是任务上下文?看[鸿蒙系统源码分析(总目录)](https://blog.csdn.net/kuangyufei/article/details/108727970)其他文章,有专门的介绍。这里要说明的是 在CPU的层面,它只认任务上下文!这里看不到任何代码了,因为这是跟CPU相关的,不同的CPU需要去适配不同的汇编代码,所以这些汇编代码不会出现在一个通用工程中。请留意后续 鸿蒙内核源码分析(汇编指令篇)。
什么是任务上下文?看鸿蒙系统源码分析(总目录)其他文章,有专门的介绍。这里要说明的是 在CPU的层面,它只认任务上下文!这里看不到任何代码了,因为这是跟CPU相关的,不同的CPU需要去适配不同的汇编代码.
## 请读懂内核最美函数 OsGetTopTask()
......@@ -253,7 +236,6 @@ OUT:
#if __cplusplus
}
```
### **喜欢就关注下吧,您的关注真的很重要**
![在这里插入图片描述](https://gitee.com/weharmony/kernel_liteos_a_note/raw/master/zzz/pic/other/wxcode.png)
......
......@@ -4,33 +4,11 @@
---
**[内核代码详细结构 >> 进入阅读代码](https://my.oschina.net/u/3751245/blog/4869137)**
**目录**
[调度队列篇](#Task%E9%98%9F%E5%88%97%E7%AF%87)
[为何单独讲调度队列?](#%E4%B8%BA%E4%BD%95%E5%8D%95%E7%8B%AC%E8%AE%B2%E8%B0%83%E5%BA%A6%E9%98%9F%E5%88%97%EF%BC%9F)
[涉及函数](#%E6%B6%89%E5%8F%8A%E5%87%BD%E6%95%B0)
[位图调度器](#%E4%BD%8D%E5%9B%BE%E8%B0%83%E5%BA%A6%E5%99%A8)
[进程就绪队列机制](#%E4%BB%BB%E5%8A%A1%E5%B0%B1%E7%BB%AA%E9%98%9F%E5%88%97%E6%9C%BA%E5%88%B6)
[几个常用函数](#%E5%87%A0%E4%B8%AA%E5%B8%B8%E7%94%A8%E5%87%BD%E6%95%B0)
[同一个进程下的线程的优先级可以不一样吗?](#%E5%90%8C%E4%B8%80%E4%B8%AA%E8%BF%9B%E7%A8%8B%E4%B8%8B%E7%9A%84%E7%BA%BF%E7%A8%8B%E7%9A%84%E4%BC%98%E5%85%88%E7%BA%A7%E5%8F%AF%E4%BB%A5%E4%B8%8D%E4%B8%80%E6%A0%B7%E5%90%97%EF%BC%9F)
[线程调度器](#%E6%80%8E%E4%B9%88%E7%90%86%E8%A7%A3%E7%BA%BF%E7%A8%8B%E7%BB%84%EF%BC%9F)
## 为何单独讲调度队列?
# 为何单独讲调度队列?
鸿蒙内核代码中有两个源文件是关于队列的,一个是用于调度的队列,另一个是用于线程间通讯的IPC队列。
本文详细讲述调度队列,详见代码: kernel\_liteos\_a/kernel/base/sched/sched\_sq/los\_priqueue.c
IPC队列后续有专门的博文讲述,这两个队列的数据结构实现采用的都是双向循环链表,LOS\_DL\_LIST实在是太重要了,是理解鸿蒙内核的关键,说是最重要的代码一点也不为过,源码出现在 sched\_sq模块,说明是用于任务的调度的,sched\_sq模块只有两个文件,另一个los_sched.c就是调度代码。
IPC队列后续有专门的博文讲述,这两个队列的数据结构实现采用的都是双向循环链表,再说一遍LOS_DL_LIST实在是太重要了,是理解鸿蒙内核的关键,说是最重要的代码一点也不为过,源码出现在 sched_sq模块,说明是用于任务的调度的,sched_sq模块只有两个文件,另一个los_sched.c就是调度代码。
## 涉及函数
......@@ -45,23 +23,13 @@ IPC队列后续有专门的博文讲述,这两个队列的数据结构实现
```cpp
//* 0x80000000U = 10000000000000000000000000000000(32位,1是用于移位的,设计之精妙,点赞)
#define PRIQUEUE_PRIOR0_BIT 0x80000000U
#ifndef CLZ
#define CLZ(value) (__clz(value)) //汇编指令
#endif
LITE_OS_SEC_BSS LOS_DL_LIST *g_priQueueList = NULL; //所有的队列 原始指针
LITE_OS_SEC_BSS UINT32 g_priQueueBitmap; // 位图调度
// priority = CLZ(bitmap); // 获取最高优先级任务队列 调度位
```
整个[los_priqueue.c](https://gitee.com/weharmony/kernel_liteos_a/blob/master/kernel/base/sched/sched_sq/los_priqueue.c)就只有两个全部变量,一个是 LOS\_DL\_LIST *g\_priQueueList 是32个进程就绪队列的头指针,在就绪队列中会讲另一个UINT32 g\_priQueueBitmap  估计很多人会陌生,是一个32位的变量,叫位图调度器。怎么理解它呢?
整个los_priqueue.c就只有两个全部变量,一个是 LOS_DL_LIST *g_priQueueList 是32个进程就绪队列的头指针,在就绪队列中会讲另一个UINT32 g_priQueueBitmap  估计很多人会陌生,是一个32位的变量,叫位图调度器。怎么理解它呢?
鸿蒙系统的调度是抢占式的,task分成32个优先级,如何快速的知道哪个队列是空的,哪个队列里有任务需要一个标识,而且要极高效的实现?答案是:位图调度器。
简单说就是一个变量的位来标记对应队列中是否有任务,在位图调度下,任务优先级的值越小则代表具有越高的优先级,每当需要进行调度时,从最低位向最高位查找出第一个置 1 的位的所在位置,即为当前最高优先级,然后从对应优先级就绪队列获得相应的任务控制块,整个调度器的实现复杂度是 O(1),即无论任务多少,其调度时间是固定的。
系列篇已有专门讲位图管理的文章,自行翻看.简单说就是一个变量的位来标记对应队列中是否有任务,在位图调度下,任务优先级的值越小则代表具有越高的优先级,每当需要进行调度时,从最低位向最高位查找出第一个置 1 的位的所在位置,即为当前最高优先级,然后从对应优先级就绪队列获得相应的任务控制块,整个调度器的实现复杂度是 O(1),即无论任务多少,其调度时间是固定的。
## 进程就绪队列机制
......@@ -86,24 +54,14 @@ UINT32 OsPriQueueInit(VOID)
}
return LOS_OK;
}
```
因TASK 有32个优先级,在初始化时内核一次性创建了32个双向循环链表,每种优先级都有一个队列来记录就绪状态的tasks的位置,g\_priQueueList分配的是一个连续的内存块,存放了32个LOS\_DL\_LIST,再看一下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;
```
因TASK 有32个优先级,在初始化时内核一次性创建了32个双向循环链表,每种优先级都有一个队列来记录就绪状态的tasks的位置,g_priQueueList分配的是一个连续的内存块,存放了32个双向链表
## 几个常用函数
还是看入队和出队的源码吧,注意bitmap的变化!
从代码中可以知道,调用了LOS_ListTailInsert(&priQueueList\[priority\], priqueueItem); 注意是从循环链表的尾部插入的,也就是同等优先级的TASK被排在了最后一个执行,只要每次都是从尾部插入,就形成了一个按顺序执行的队列。鸿蒙内核的设计可谓非常巧妙,用极少的代码,极高的效率实现了队列功能。
从代码中可以知道,调用了LOS_ListTailInsert,注意是从循环链表的尾部插入的,也就是同等优先级的TASK被排在了最后一个执行,只要每次都是从尾部插入,就形成了一个按顺序执行的队列。鸿蒙内核的设计可谓非常巧妙,用极少的代码,极高的效率实现了队列功能。
```cpp
VOID OsPriQueueEnqueue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority)
......@@ -148,11 +106,7 @@ VOID OsPriQueueDequeue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *p
*bitMap &= ~(PRIQUEUE_PRIOR0_BIT >> task->priority);//队列空了,对应优先级位 置0
}
}
```
## 同一个进程下的线程的优先级可以不一样吗?
请先想一下这个问题。
......@@ -193,8 +147,7 @@ VOID OsPriQueueDequeue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *p
当进程的主线程或所有线程运行结束后,进程由运行态转为僵尸态,等待父进程回收资源。
注意看上面红色的部分,一个进程竟然可以两种状态共存!
从文档中可知,一个进程是可以两种状态共存的.
```cpp
UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently
......@@ -202,9 +155,7 @@ VOID OsPriQueueDequeue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *p
processCB->processStatus &= ~(status | OS_PROCESS_STATUS_PEND);//取反后的与位运算
processCB->processStatus |= OS_PROCESS_STATUS_READY;//或位运算
```
一个变量存两种状态,怎么做到的?答案还是 按位保存啊。还记得上面的位图调度 g_priQueueBitmap吗,那可是存了32种状态的。其实这在任何一个系统的内核源码中都很常见,类似的还有 左移 <<,右移 >>等等
继续说进程和线程的关系,线程的优先级必须和进程一样吗?他们可以不一样吗?答案是:当然不一样,否则怎么会有设置task优先级的函数。其实task有专门的bitmap来记录它曾经有过的优先级记录, 比如在调度过程中如果遇到阻塞,内核往往会提高持有锁的task的优先级,让它能以最大概率被下一轮调度选中而快速释放锁资源.
......@@ -218,9 +169,7 @@ VOID OsPriQueueDequeue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *p
process */
LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the
priority hash table */
```
咋一看怎么进程的结构体里也有32个队列,其实这就是task的就绪状态队列。threadScheduleMap就是进程自己的位图调度器。具体看进程入队和出队的源码。调度过程是先去进程就绪队列里找最高优先级的进程,然后去该进程找最高优先级的线程来调度。具体看笔者认为的内核最美函数OsGetTopTask,能欣赏到他的美就读懂了就绪队列是怎么管理的。
```cpp
......@@ -264,8 +213,6 @@ LITE_OS_SEC_TEXT_MINOR LosTaskCB *OsGetTopTask(VOID)
OUT:
return newTask;
}
```
映射张大爷的故事:张大爷喊到张全蛋时进场时表演时,张全蛋要决定自己的哪个节目先表演,也要查下他的清单上优先级,它同样也有个张大爷同款记分牌,就这么简单。
......
......@@ -4,7 +4,7 @@
---
**官方基本概念**
## **官方基本概念**
从系统的角度看,进程是资源管理单元。进程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它进程运行。
......@@ -20,7 +20,7 @@ OpenHarmony内核的进程一共有32个优先级(0-31),用户进程可配置
用户态根进程init由内核态创建,其它用户态进程均由init进程fork而来。
**进程状态说明:**
### **进程状态说明:**
- 初始化(Init):该进程正在被创建。
......@@ -32,13 +32,8 @@ OpenHarmony内核的进程一共有32个优先级(0-31),用户进程可配置
- 僵尸态(Zombies):该进程运行结束,等待父进程回收其控制块资源。
**图 1** 进程状态迁移示意图
![](https://img-blog.csdnimg.cn/img_convert/925ff9ae641e32b2502d7b5a155b8579.png)
**进程状态迁移说明:**
- Init→Ready:
进程创建或fork时,拿到该进程控制块后进入Init状态,处于进程初始化阶段,当进程初始化完成将进程插入调度队列,此时进程进入就绪状态。
......@@ -70,25 +65,25 @@ OpenHarmony内核的进程一共有32个优先级(0-31),用户进程可配置
当进程的主线程或所有线程运行结束后,进程由运行态转为僵尸态,等待父进程回收资源。
## 使用场景
### **使用场景**
进程创建后,用户只能操作自己进程空间的资源,无法操作其它进程的资源(共享资源除外)。 用户态允许进程挂起,恢复,延时等操作,同时也可以设置用户态进程调度优先级和调度策略,获取进程调度优先级和调度策略。进程结束的时候,进程会主动释放持有的进程资源,但持有的进程pid资源需要父进程通过wait/waitpid或父进程退出时回收。
## 开始正式分析
### **开始正式分析**
对应张大爷的故事,进程就是那些在场馆外32个队列里排队的,那些队列就是进程的就绪队列。
请注意 进程是资源管理单元 ,而非最终调度单元,调度单元是谁?是 Task ,看下官方对应状态的define
```cpp
#define OS_PROCESS_STATUS_INIT 0x0010U
#define OS_PROCESS_STATUS_READY 0x0020U
#define OS_PROCESS_STATUS_RUNNING 0x0040U
#define OS_PROCESS_STATUS_PEND 0x0080U
#define OS_PROCESS_STATUS_ZOMBIES 0x100U
#define OS_PROCESS_STATUS_INIT 0x0010U //进程初始状态
#define OS_PROCESS_STATUS_READY 0x0020U //进程就绪状态
#define OS_PROCESS_STATUS_RUNNING 0x0040U //进程运行状态
#define OS_PROCESS_STATUS_PEND 0x0080U //进程阻塞状态
#define OS_PROCESS_STATUS_ZOMBIES 0x100U //进程僵死状态
```
一个进程从创建到消亡过程,在内核肯定是极其复杂的。为了方便理解进程,整个系列文章笔者会用张大爷的故事打比方,从生活中的例子来将神秘的系统内核外化解剖出来给大家看。一件这么复杂的事情肯定会有个复杂的结构体来承载,它就是LosProcessCB(进程控制块),代码很长但必须全部拿出来,长是长了点,您就忍忍吧!
一个进程从创建到消亡过程,在内核肯定是极其复杂的。为了方便理解进程,整个系列文章笔者会用张大爷的故事打比方,从生活中的例子来将神秘的系统内核外化解剖出来给大家看。一件这么复杂的事情肯定会有个复杂的结构体来承载,它就是LosProcessCB(进程控制块),代码很长但必须全部拿出来.
```cpp
......@@ -97,51 +92,50 @@ LITE_OS_SEC_BSS LosProcessCB *g_processCBArray = NULL;//进程池,最大进程
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcess;//记录空闲的进程链表
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_processRecyleList;//记录回收的进程列表
typedef struct ProcessCB {
CHAR processName[OS_PCB_NAME_LEN]; /**< Process name */
UINT32 processID; /**< process ID = leader thread ID */
CHAR processName[OS_PCB_NAME_LEN]; /**< Process name */ //进程名称
UINT32 processID; /**< process ID = leader thread ID */ //进程ID,由进程池分配,范围[0,64]
UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently
running in the process */
UINT16 priority; /**< process priority */
UINT16 policy; /**< process policy */
UINT16 timeSlice; /**< Remaining time slice */
UINT16 consoleID; /**< The console id of task belongs */
UINT16 processMode; /**< Kernel Mode:0; User Mode:1; */
UINT32 parentProcessID; /**< Parent process ID */
UINT32 exitCode; /**< process exit status */
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 */
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 */
running in the process *///这里设计很巧妙.用一个16表示了两层逻辑 数量和状态,点赞!
UINT16 priority; /**< process priority */ //进程优先级
UINT16 policy; /**< process policy */ //进程的调度方式,默认抢占式
UINT16 timeSlice; /**< Remaining time slice *///进程时间片,默认2个tick
UINT16 consoleID; /**< The console id of task belongs *///任务的控制台id归属
UINT16 processMode; /**< Kernel Mode:0; User Mode:1; */ //模式指定为内核还是用户进程
UINT32 parentProcessID; /**< Parent process ID */ //父进程ID
UINT32 exitCode; /**< process exit status */ //进程退出状态码
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 */
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 */
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
#if (LOSCFG_KERNEL_SMP == YES)
UINT32 timerCpu; /**< CPU core number of this task is delayed or pended */
UINT32 timerCpu; /**< CPU core number of this task is delayed or pended *///统计各线程被延期或阻塞的时间
#endif
UINTPTR sigHandler; /**< signal handler */
sigset_t sigShare; /**< signal share bit */
UINTPTR sigHandler; /**< signal handler */ //信号处理函数,处理如 SIGSYS 等信号
sigset_t sigShare; /**< signal share bit */ //信号共享位
#if (LOSCFG_KERNEL_LITEIPC == YES)
ProcIpcInfo ipcInfo; /**< memory pool for lite ipc */
ProcIpcInfo ipcInfo; /**< memory pool for lite ipc */ //用于进程间通讯的虚拟设备文件系统,设备装载点为 /dev/lite_ipc
#endif
LosVmSpace *vmSpace; /**< VMM space for processes */
LosVmSpace *vmSpace; /**< VMM space for processes */ //虚拟空间,描述进程虚拟内存的数据结构,linux称为内存描述符
#ifdef LOSCFG_FS_VFS
struct files_struct *files; /**< Files held by the process */
#endif
struct files_struct *files; /**< Files held by the process */ //进程所持有的所有文件,注者称之为进程的文件管理器
#endif //每个进程都有属于自己的文件管理器,记录对文件的操作. 注意:一个文件可以被多个进程操作
timer_t timerID; /**< iTimer */
#ifdef LOSCFG_SECURITY_CAPABILITY
User *user;
UINT32 capability;
#ifdef LOSCFG_SECURITY_CAPABILITY //安全能力
User *user; //进程的拥有者
UINT32 capability; //安全能力范围 对应 CAP_SETGID
#endif
#ifdef LOSCFG_SECURITY_VID
TimerIdMap timerIdMap;
......@@ -156,46 +150,32 @@ typedef struct ProcessCB {
进程的模式有两种,内核态和用户态,能想到main函数中肯定会创建一个内核态的最高优先级进程,他就是 KProcess
调用过程如下
![](https://img-blog.csdnimg.cn/20200915113346417.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
通过task命令查看任务运行状态,可以看到 KProcess 进程 ,看名字就知道是一个内核进程,在系统启动时创建,图中可以看到 KProcess 的task运行情况,从表里可以看到KProcess内有 10几个task
![鸿蒙task](https://img-blog.csdnimg.cn/20200916142650716.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
## 进程初始化
### **进程模块是如何初始化的**
KProcess 在张大爷的故事里相当于场馆的工作人员,他们也要接受张大爷的调度排队进场,但他们的优先级是最高的0级,他们进场后需完成场馆的准备工作,再开门做生意。如果需要多个工作人员怎么办,就是通过fork,简单说就是复制一个,复制的前提是需要有一个,鸿蒙里就是KProcess,其他工作人员都是通过它fork的。
那用户怎么来的呢?就是真正要排队的人也是一样,先创建一个用户祖先,其他用户皆由祖先fork来的。
注意用户进程和内核进程的祖先是不一样的,有各自的祖先根.分别是g_userInitProcess(1号) 和 g_kernelInitProcess(2号)
```cpp
//单核CPU只有并发(Concurrent),多核才会有并行(Parallel) LITE_OS_SEC_BSS 和 LITE_OS_SEC_DATA_INIT 是告诉编译器这些全局变量放在哪个数据段
LITE_OS_SEC_BSS LosProcessCB *g_runProcess[LOSCFG_KERNEL_CORE_NUM];// CPU内核个数,这才是真正的并行
/******************************************************************************
并发(Concurrent):多个线程在单个核心运行,同一时间一个线程运行,系统不停切换线程,
看起来像同时运行,实际上是线程不停切换
并行(Parallel)每个线程分配给独立的CPU核心,线程同时运行
单核CPU多个进程或多个线程内能实现并发(微观上的串行,宏观上的并行)
多核CPU线程间可以实现宏观和微观上的并行
LITE_OS_SEC_BSS 和 LITE_OS_SEC_DATA_INIT 是告诉编译器这些全局变量放在哪个数据段
******************************************************************************/
LITE_OS_SEC_BSS LosProcessCB *g_runProcess[LOSCFG_KERNEL_CORE_NUM];// CPU内核个数,超过一个就实现了并行
LITE_OS_SEC_BSS LosProcessCB *g_processCBArray = NULL; // 进程池数组
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcess;// 空闲状态下可供分配的进程,此时进程白纸一张
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcess;// 空闲状态下的进程链表, .个人觉得应该取名为 g_freeProcessList @note_thinking
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_processRecyleList;// 需要回收的进程列表
LITE_OS_SEC_BSS UINT32 g_userInitProcess = OS_INVALID_VALUE;// 用户态的初始init进程,用户态下其他进程由它 fork
LITE_OS_SEC_BSS UINT32 g_kernelInitProcess = OS_INVALID_VALUE;// 内核态初始Kprocess进程,内核态下其他进程由它 fork
LITE_OS_SEC_BSS UINT32 g_kernelIdleProcess = OS_INVALID_VALUE;// 内核态idle进程,由Kprocess fork
LITE_OS_SEC_BSS UINT32 g_processMaxNum;// 进程最大数量
LITE_OS_SEC_BSS ProcessGroup *g_processGroup = NULL;// 进程组
```
以上是进程模块全局变量。注释是笔者添加的,鸿蒙内核的注释很少,查看更多注释前往以下仓库
鸿蒙内核源码注释中文版 进入>>【[CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note) | [Gitee仓](https://gitee.com/weharmony/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)】阅读,四大仓库正加注同步更新中....
KProcess 在张大爷的故事里相当于场馆的工作人员,他们也要接受张大爷的调度排队进场,但他们的优先级是最高的0级,他们进场后需完成场馆的准备工作,再开门做生意。如果需要多个工作人员怎么办,就是通过fork,简单说就是复制一个,复制的前提是需要有一个,鸿蒙里就是KProcess,其他工作人员都是通过它fork的。那用户怎么来的呢?就是真正要排队的人也是一样,先创建一个用户爸爸,其他用户就用爸爸fork来的,注意是fork 不是那个什么K,哈哈,你懂得。
还是直接看代码吧
```cpp
/**
* @ingroup los_config
* Maximum supported number of process rather than the number of usable processes.
*/
#ifndef LOSCFG_BASE_CORE_PROCESS_LIMIT
#define LOSCFG_BASE_CORE_PROCESS_LIMIT 64
#endif
LITE_OS_SEC_BSS UINT32 g_processMaxNum;// 进程最大数量,默认64个
LITE_OS_SEC_BSS ProcessGroup *g_processGroup = NULL;// 全局进程组,负责管理所有进程组
//进程模块初始化,被编译放在代码段 .init 中
LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID)
......@@ -217,10 +197,10 @@ LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID)
for (index = 0; index < g_processMaxNum; index++) {//进程池循环创建
g_processCBArray[index].processID = index;//进程ID[0-g_processMaxNum]赋值
g_processCBArray[index].processStatus = OS_PROCESS_FLAG_UNUSED;// 默认都是白纸一张,臣妾干净着呢
LOS_ListTailInsert(&g_freeProcess, &g_processCBArray[index].pendList);// 初始全是可分配进程描述符
g_processCBArray[index].processStatus = OS_PROCESS_FLAG_UNUSED;// 默认都是白纸一张,贴上未使用标签
LOS_ListTailInsert(&g_freeProcess, &g_processCBArray[index].pendList);//注意g_freeProcess挂的是pendList节点,所以使用要通过OS_PCB_FROM_PENDLIST找到进程实体.
}
// ????? 为啥用户模式的根进程 选1 ,内核模式的根进程选2
g_userInitProcess = 1; /* 1: The root process ID of the user-mode process is fixed at 1 *///用户模式的根进程
LOS_ListDelete(&g_processCBArray[g_userInitProcess].pendList);// 清空g_userInitProcess pend链表
......@@ -232,21 +212,38 @@ LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID)
```
代码已经很清楚,创建了一个进程池,默认64个进程,也就是不改宏LOSCFG\_BASE\_CORE\_PROCESS\_LIMIT的情况下 系统最多是64个进程,但有两个进程先被占用,用户态和内核态各一个,他们是后续创建进程的爹,所以最多留给外面的只有 62个进程可创建,代码的最后两个爸爸的task阻塞链表被清空了,因为没有阻塞任务当然要清空.
代码已经很清楚,创建了一个进程池,默认64个进程,也就是不改宏LOSCFG_BASE_CORE_PROCESS_LIMIT的情况下 系统最多是64个进程,但有两个进程先被占用,用户态和内核态各一个,他们是后续创建进程的根,所以最多留给外面的只有 62个进程可创建,代码的最后两个根进程的task阻塞链表被清空了,因为没有阻塞任务当然要清空.
## 创建内核态Kprocess的过程
### **内核态根进程创建过程**
创建核心态进程,也就是线程池中的 \[2\] 号进程,task 命令中 Kprocess PID = 2, 参数是 内核态和 最高优先级 0
创建"Kprocess"进程,也就是线程池中的2号进程g_kernelInitProcess,设为最高优先级 0
### OsKernelInitProcess
```cpp
//初始化 2号进程,即内核根进程
LITE_OS_SEC_TEXT_INIT UINT32 OsKernelInitProcess(VOID)
{
LosProcessCB *processCB = NULL;
UINT32 ret;
![](https://img-blog.csdnimg.cn/20200915120418742.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
ret = OsProcessInit();// 初始化进程模块全部变量,创建各循环双向链表
if (ret != LOS_OK) {
return ret;
}
### OsCreateIdleProcess
processCB = OS_PCB_FROM_PID(g_kernelInitProcess);// 以PID方式得到一个进程
ret = OsProcessCreateInit(processCB, OS_KERNEL_MODE, "KProcess", 0);// 初始化进程,最高优先级0,鸿蒙进程一共有32个优先级(0-31) 其中0-9级为内核进程,用户进程可配置的优先级有22个(10-31)
if (ret != LOS_OK) {
return ret;
}
代码的把kprocess 设为当前进程,并且fork了一个 KIdle(内核态的空闲进程)
processCB->processStatus &= ~OS_PROCESS_STATUS_INIT;// 进程初始化位 置1
g_processGroup = processCB->group;//全局进程组指向了KProcess所在的进程组
LOS_ListInit(&g_processGroup->groupList);// 进程组链表初始化
OsCurrProcessSet(processCB);// 设置为当前进程
return OsCreateIdleProcess();// 创建一个空闲状态的进程
}
```cpp
//创建一个名叫"KIdle"的进程,给CPU空闲的时候使用
STATIC UINT32 OsCreateIdleProcess(VOID)
{
......@@ -279,35 +276,8 @@ STATIC UINT32 OsCreateIdleProcess(VOID)
}
```
### OsIdleTask
CPU空闲时是跑在idleTask中的,这里是CPU休息的地方,进入低电量模式,等待被事件唤醒上班
```cpp
//空闲任务 注意 #define WEAK __attribute__((weak)) 是用于防止crash的
LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID)
{
while (1) {//只有一个死循环
#ifdef LOSCFG_KERNEL_TICKLESS
if (OsTickIrqFlagGet()) {
OsTickIrqFlagSet(0);
OsTicklessStart();
}
#endif
Wfi();
}
}
VOID Wfi(VOID)//WFI指令:arm core 立即进入low-power standby state,直到有WFI Wakeup events发生
{
__asm__ __volatile__ ("wfi" : : : "memory");//一般用于cpuidle
}
```
## 创建用户态进程的过程是怎样的?看代码
### **用户态根进程创建过程**
创建"Init"进程,也就是线程池中的1号进程g_userInitProcess,优先级为 28,好低啊
```cpp
/**
* @ingroup los_process
......@@ -377,22 +347,11 @@ ERROR:
return ret;
}
```
发现用户态init 和 创建的过程类似 内核态,并且用户态爸爸进程的优先级是 28,好低啊。
对应张大爷的故事:工作人员和用户的工作环境是不一样的,工作人员可以全场活动,但用户不能去管理处溜达,你只能在属于你表演的场地活动,这个场地就是用户空间,这涉及到内存的管理,非常复杂, 具体去系列篇看怎么管理内存的文章。
## 留下两个小问题请大家思考
OsUserInitProcess里有些关于内存的代码,源码中找不到值,比如:
```cpp
CHAR *userInitTextStart = (CHAR *)&__user_init_entry;
CHAR *userInitBssStart = (CHAR *)&__user_init_bss;
CHAR *userInitEnd = (CHAR *)&__user_init_end;
```
### **留个小问题请大家思考**
为什么会这样?另外两个爸爸对应的PID是 1和2,那进程池里的0号进程又去哪里了呢?
1号进程和2号进程分别是用户态和内核态的根进程,那进程池里的0号进程去哪里了呢?
### **喜欢就关注下吧,您的关注真的很重要**
......
!import[\zzz\mdmerge\head.md]
## **一、任务即线程**
**目录**
在鸿蒙内核中,广义上可理解为一个任务就是一个线程
[前言](#%E5%89%8D%E8%A8%80)
[一、怎么理解Task](#%E4%B8%80%E3%80%81%E6%80%8E%E4%B9%88%E7%90%86%E8%A7%A3Task)
[1\. 官方文档是怎么描述线程](#1.%20%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E6%98%AF%E6%80%8E%E4%B9%88%E6%8F%8F%E8%BF%B0%E7%BA%BF%E7%A8%8B)
[2\. 执行task命令](#2.%20%E6%89%A7%E8%A1%8Ctask%E5%91%BD%E4%BB%A4)
[3\. task长得什么样子?](#3.%20task%E9%95%BF%E5%BE%97%E4%BB%80%E4%B9%88%E6%A0%B7%E5%AD%90%EF%BC%9F)
[二、Task怎么管理](#%E4%BA%8C%E3%80%81Task%E6%80%8E%E4%B9%88%E7%AE%A1%E7%90%86)
[1.什么是任务池?](#1.%E4%BB%80%E4%B9%88%E6%98%AF%E4%BB%BB%E5%8A%A1%E6%B1%A0%EF%BC%9F)
[2.就绪队列是怎么回事](#2.%E5%B0%B1%E7%BB%AA%E9%98%9F%E5%88%97%E6%98%AF%E6%80%8E%E4%B9%88%E5%9B%9E%E4%BA%8B)
[3.任务栈是怎么回事](#3.%E4%BB%BB%E5%8A%A1%E6%A0%88%E6%98%AF%E6%80%8E%E4%B9%88%E5%9B%9E%E4%BA%8B)
[4.任务栈初始化](#4.%E4%BB%BB%E5%8A%A1%E6%A0%88%E5%88%9D%E5%A7%8B%E5%8C%96)
[三、Task函数集](#%E4%B8%89%E3%80%81Task%E5%87%BD%E6%95%B0%E9%9B%86)
[1.使用场景和功能](#1.%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E5%92%8C%E5%8A%9F%E8%83%BD)
[2.创建任务的过程](#2.%E5%88%9B%E5%BB%BA%E4%BB%BB%E5%8A%A1%E7%9A%84%E8%BF%87%E7%A8%8B)
[3.Task部分还有哪些重要内容没讲到?](#%E7%BA%BF%E7%A8%8B%E9%83%A8%E9%97%A8%E8%BF%98%E6%9C%89%E5%93%AA%E4%BA%9B%E9%87%8D%E8%A6%81%E5%86%85%E5%AE%B9%E6%B2%A1%E8%AE%B2%E5%88%B0%EF%BC%9F)
# [前言](#)
[在鸿蒙内核中,广义上可理解为一个Task就是一个线程](#)
---
# 一、怎么理解Task
## 1\. 官方文档是怎么描述线程
### **官方是怎么描述线程的**
基本概念
从系统的角度看,线程是竞争系统资源的最小运行单元。线程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它线程运行。
......@@ -67,20 +32,20 @@
![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/43b868b7e3822f66ab7654b38bb6443e.png)
注意官方文档说的是线程,没有提到task(任务),但内核源码中却有大量 task代码,很少有线程(thread)代码 ,这是怎么回事?
其实在鸿蒙内核中, task就是线程, 初学者完全可以这么理解,但二者还是有区别,否则干嘛要分两个词描述。
到底有什么区别?是管理上的区别,task是调度层面的概念,线程是进程层面的概念。 就像同一个人在不同的管理体系中会有不同的身份一样,一个男人既可以是 孩子,爸爸,丈夫,或者程序员,视角不同功能也会不同。
有什么区别?是管理上的区别,task是调度层面的概念,线程是进程层面的概念。 就像同一个人在不同的管理体系中会有不同的身份一样,一个男人既可以是 孩子,爸爸,丈夫,或者程序员,视角不同功能也会不同。
如何证明是一个东西,继续再往下看。
## 2\. 执行task命令
### **执行task命令**
鸿蒙 task 命令的执行结果:
看shell task 命令的执行结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/912b298080e5e4ce01e4188baf6e0df0.png)
task命令 查出每个任务在生命周期内的运行情况,它运行的内存空间,优先级,时间片,入口执行函数,进程ID,状态等等信息,非常的复杂。这么复杂的信息就需要一个结构体来承载。而这个结构体就是 LosTaskCB(任务控制块)
对应张大爷的故事:task就是一个用户的节目清单里的一个节目,用户总清单就是一个进程,所以上面会有很多的节目。
## 3\. task长得什么样子?
### **task长得什么样子**
说LosTaskCB之前先说下官方文档任务状态对应的 define,可以看出task和线程是一个东西。
......@@ -94,8 +59,6 @@ task命令 查出每个任务在生命周期内的运行情况,它运行的内
#define OS_TASK_STATUS_TIMEOUT 0x0040U
#define OS_TASK_STATUS_PEND_TIME 0x0080U
#define OS_TASK_STATUS_EXIT 0x0100U
```
LosTaskCB长什么样?抱歉,它确实有点长,但还是要全部贴出全貌。
......@@ -163,9 +126,9 @@ typedef struct {
结构体LosTaskCB内容很多,各代表什么含义?
LosTaskCB相当于任务在内核中的身份证,它反映出每个任务在生命周期内的运行情况。既然是周期就会有状态,要运行就需要内存空间,就需要被内核算法调度,被选中CPU就去执行代码段指令,CPU要执行就需要告诉它从哪里开始执行,因为是多线程,但只有一个CPU就需要不断的切换任务,那执行会被中断,也需要再恢复后继续执行,又如何保证恢复的任务执行不会出错,这些问题都需要说明白。
# 二、Task怎么管理
## **二、Task怎么管理**
## 1.什么是任务池?
### **什么是任务池?**
前面已经说了任务是内核调度层面的概念,调度算法保证了task有序的执行,调度机制详见其他姊妹篇的介绍。
如此多的任务怎么管理和执行?管理靠任务池和就绪队列,执行靠调度算法。
......@@ -174,17 +137,18 @@ LosTaskCB相当于任务在内核中的身份证,它反映出每个任务在
```cpp
LITE_OS_SEC_TEXT_INIT UINT32 OsTaskInit(VOID)
{
UINT32 index;
UINT32 ret;
UINT32 size;
g_taskMaxNum = LOSCFG_BASE_CORE_TSK_LIMIT;//任务池中最多默认128个,可谓铁打的任务池流水的线程
size = (g_taskMaxNum + 1) * sizeof(LosTaskCB);
/* * This memory is resident memory and is used to save the system resources * of task control block and will not be freed. */
size = (g_taskMaxNum + 1) * sizeof(LosTaskCB);//计算需分配内存总大小
/*
* This memory is resident memory and is used to save the system resources
* of task control block and will not be freed.
*/
g_taskCBArray = (LosTaskCB *)LOS_MemAlloc(m_aucSysMem0, size);//任务池 常驻内存,不被释放
if (g_taskCBArray == NULL) {
return LOS_ERRNO_TSK_NO_MEMORY;
}
(VOID)memset_s(g_taskCBArray, size, 0, size);
......@@ -192,40 +156,33 @@ LITE_OS_SEC_TEXT_INIT UINT32 OsTaskInit(VOID)
LOS_ListInit(&g_losFreeTask);//空闲任务链表
LOS_ListInit(&g_taskRecyleList);//需回收任务链表
for (index = 0; index < g_taskMaxNum; index++) {
g_taskCBArray[index].taskStatus = OS_TASK_STATUS_UNUSED;
g_taskCBArray[index].taskID = index;//任务ID最大默认127
LOS_ListTailInsert(&g_losFreeTask, &g_taskCBArray[index].pendList);//都插入空闲任务链表
}
LOS_ListTailInsert(&g_losFreeTask, &g_taskCBArray[index].pendList);//都插入空闲任务列表
}//注意:这里挂的是pendList节点,所以取TCB要通过 OS_TCB_FROM_PENDLIST 取.
ret = OsPriQueueInit();//创建32个任务优先级队列,即32个双向循环链表
if (ret != LOS_OK) {
return LOS_ERRNO_TSK_NO_MEMORY;
}
/* init sortlink for each core */
for (index = 0; index < LOSCFG_KERNEL_CORE_NUM; index++) {
ret = OsSortLinkInit(&g_percpu[index].taskSortLink);//每个CPU内核都有一个执行任务链表
if (ret != LOS_OK) {
return LOS_ERRNO_TSK_NO_MEMORY;
}
}
return LOS_OK;
}
```
g_taskCBArray 就是个任务池,默认创建128个任务,常驻内存,不被释放。
g_losFreeTask是空闲任务链表,想创建任务时来这里申请一个空闲任务,用完了就回收掉,继续给后面的申请使用。
g_taskRecyleList是回收任务链表,专用来回收exit 任务,任务所占资源被确认归还后被彻底删除,就像员工离职一样,得有个离职队列和流程,要归还电脑,邮箱,有没有借钱要还的 等操作。
对应张大爷的故事:用户要来场馆领取表格填节目单,场馆只准备了128张表格,领完就没有了,但是节目表演完了会回收表格,这样多了一张表格就可以给其他人领取了,这128张表格对应鸿蒙内核这就是任务池,简单吧。
## 2.就绪队列是怎么回事
### **就绪队列是怎么回事**
CPU执行速度是很快的,鸿蒙内核默认一个时间片是 10ms, 资源有限,需要在众多任务中来回的切换,所以绝不能让CPU等待任务,CPU就像公司最大的领导,下面很多的部门等领导来审批,吃饭。只有大家等领导,哪有领导等你们的道理,所以工作要提前准备好,每个部门的优先级又不一样,所以每个部门都要有个任务队列,里面放的是领导能直接处理的任务,没准备好的不要放进来,因为这是给CPU提前准备好的粮食!
这就是就绪队列的原理,一共有32个就绪队列,进程和线程都有,因为线程的优先级是默认32个, 每个队列中放同等优先级的task.
......@@ -259,7 +216,7 @@ UINT32 OsPriQueueInit(VOID)
对应张大爷的故事:就是门口那些排队的都是至少有一个节目单是符合表演标准的,资源都到位了,没有的连排队的资格都木有,就慢慢等吧。
## 3.任务栈是怎么回事
### **任务栈是怎么回事**
每个任务都是独立开的,任务之间也相互独立,之间通讯通过IPC,这里的“独立”指的是每个任务都有自己的运行环境 —— 栈空间,称为任务栈,栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等等
但系统中只有一个CPU,任务又是独立的,调度的本质就是CPU执行一个新task,老task在什么地方被中断谁也不清楚,是随机的。那如何保证老任务被再次调度选中时还能从上次被中断的地方继续玩下去呢?
......@@ -285,10 +242,7 @@ typedef struct {
UINT32 LR; /* R14 */
UINT32 PC; /* R15 */
} TaskContext;
```
发现基本都是CPU寄存器的恢复现场值, 具体各寄存器有什么作用大家可以去网上详查,后续也有专门的文章来介绍。这里说其中的三个寄存器 SP, LR, PC
LR
......@@ -300,11 +254,11 @@ PC(Program Counter)
SP
每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。
## 4.任务栈初始化
### **任务栈初始化**
任务栈的初始化就是任务上下文的初始化,因为任务没开始执行,里面除了上下文不会有其他内容,注意上下文存放的位置在栈的底部。初始状态下 sp就是指向的栈底, 栈顶内容永远是 0xCCCCCCCC "烫烫烫烫",这几个字应该很熟悉吗? 如果不是那几个字了,那说明栈溢出了, 后续篇会详细说明这块,大家也可以自行去看代码,很有意思.
# 三、Task函数集
## **三、Task函数集**
```cpp
LITE_OS_SEC_TEXT_INIT VOID *OsTaskStackInit(UINT32 taskID, UINT32 stackSize, VOID *topStack, BOOL initFlag)
......@@ -352,11 +306,8 @@ LITE_OS_SEC_TEXT_INIT VOID *OsTaskStackInit(UINT32 taskID, UINT32 stackSize, VOI
return (VOID *)taskContext;
}
```
## 1.使用场景和功能
### **使用场景和功能**
任务创建后,内核可以执行锁任务调度,解锁任务调度,挂起,恢复,延时等操作,同时也可以设置任务优先级,获取任务优先级。任务结束的时候,则进行当前任务自删除操作。
Huawei LiteOS 系统中的任务管理模块为用户提供下面几种功能。
......@@ -383,35 +334,30 @@ Huawei LiteOS 系统中的任务管理模块为用户提供下面几种功能。
|   | LOS_TaskInfoMonitor | 监控所有任务,获取所有任务的信息。 |
|   | LOS_NextTaskIDGet | 获取即将被调度的任务的ID。 |
## 2.创建任务的过程
### **创建任务的过程**
创建任务之前先了解另一个结构体 tagTskInitParam
```cpp
typedef struct tagTskInitParam {
TSK_ENTRY_FUNC pfnTaskEntry; /**< Task entrance function */
UINT16 usTaskPrio; /**< Task priority */
UINT16 policy; /**< Task policy */
UINTPTR auwArgs[4]; /**< Task parameters, of which the maximum number is four */
UINT32 uwStackSize; /**< Task stack size */
CHAR *pcName; /**< Task name */
typedef struct tagTskInitParam {//Task的初始化参数
TSK_ENTRY_FUNC pfnTaskEntry; /**< Task entrance function */ //任务的入口函数
UINT16 usTaskPrio; /**< Task priority */ //任务优先级
UINT16 policy; /**< Task policy */ //任务调度方式
UINTPTR auwArgs[4]; /**< Task parameters, of which the maximum number is four */ //入口函数的参数,最多四个
UINT32 uwStackSize; /**< Task stack size */ //任务栈大小
CHAR *pcName; /**< Task name */ //任务名称
#if (LOSCFG_KERNEL_SMP == YES)
UINT16 usCpuAffiMask; /**< Task cpu affinity mask */
UINT16 usCpuAffiMask; /**< Task cpu affinity mask */ //任务cpu亲和力掩码
#endif
UINT32 uwResved; /**< It is automatically deleted if set to LOS_TASK_STATUS_DETACHED. It is unable to be deleted if set to 0. */
UINT16 consoleID; /**< The console id of task belongs */
UINT32 processID;
UserTaskParam userParam;
UINT32 uwResved; /**< It is automatically deleted if set to LOS_TASK_STATUS_DETACHED.
It is unable to be deleted if set to 0. */ //如果设置为LOS_TASK_STATUS_DETACHED,则自动删除。如果设置为0,则无法删除
UINT16 consoleID; /**< The console id of task belongs */ //任务的控制台id所属
UINT32 processID; //进程ID
UserTaskParam userParam; //在用户态运行时栈参数
} TSK_INIT_PARAM_S;
```
这些初始化参数是外露的任务初始参数,`pfnTaskEntry 对java来说就是你new进程的run(),`需要上层使用者提供.
看个例子吧:shell中敲 ping 命令看下它创建的过程
```cpp
u32_t osShellPing(int argc, const char **argv)
{
......@@ -450,12 +396,8 @@ ping_error:
lwip_ping_usage();
return LOS_NOK;
}
```
发现ping的调度优先级是8,比shell 还高,那shell的是多少?答案是:看源码是 9
```cpp
LITE_OS_SEC_TEXT_MINOR UINT32 ShellTaskInit(ShellCB *shellCB)
{
......@@ -482,13 +424,9 @@ LITE_OS_SEC_TEXT_MINOR UINT32 ShellTaskInit(ShellCB *shellCB)
(VOID)LOS_EventInit(&shellCB->shellEvent);
return LOS_TaskCreate(&shellCB->shellTaskHandle, &initParam);
}
```
关于shell后续会详细介绍,请持续关注。
前置条件了解清楚后,具体看任务是如何一步步创建的,如何和进程绑定,加入调度就绪队列,还是继续看源码
```cpp
//创建Task
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *initParam)
......@@ -538,18 +476,15 @@ LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *in
return LOS_OK;
}
```
对应张大爷的故事:就是节目单要怎么填,按格式来,从哪里开始演,要多大的空间,王场馆好协调好现场的环境。这里注意 在同一个节目单只要节目没演完,王场馆申请场地的空间就不能给别人用,这个场地空间对应的就是鸿蒙任务的栈空间,除非整个节目单都完了,就回收了。把整个场地干干净净的留给下一个人的节目单来表演。
至此的创建已经完成,已各就各位,源码最后还申请了一次LOS_Schedule();因为鸿蒙的调度方式是抢占式的,如何本次task的任务优先级高于其他就绪队列,那么接下来要执行的任务就是它了!
## 3.Task部分还有哪些重要内容没讲到?
## **四、Task部分还有哪些重要内容没讲到?**
Task 状态间如何运作,如何阻塞, 如何被唤醒? 内存怎么分配的,task之间怎么通讯,运行期间堆栈是怎么执行的,在系列篇内存,IPC中有详细的描述
!import[\zzz\mdmerge\foot.md]
!export[\zzz\md\docs\guide\鸿蒙内核源码分析(任务管理篇).md]
\ No newline at end of file
!import[\zzz\mdmerge\head.md]
### 谁是鸿蒙内核最重要的结构体? 
## 谁是鸿蒙内核最重要的结构体? 
答案一定是: LOS\_DL\_LIST(双向链表),它长这样.
......
......@@ -2,7 +2,7 @@
调度算法让CPU在不同的进程和任务之间切换穿梭,但问题是
### **调度算法的驱动力在哪里? 谁负责推着调度算法走?**
## **调度算法的驱动力在哪里? 谁负责推着调度算法走?**
有多股力量在推动,但最大的推力应该是:系统时钟.
......@@ -12,7 +12,7 @@
对应张大爷的故事:系统时钟就是场馆的那个大钟,很准时, 每10分响一次,一次就是一个Tick(节拍)
### **鸿蒙内核的节拍频率是怎样的呢? 看代码**
### **鸿蒙内核的节拍频率是怎样的**
```cpp
/**
* @ingroup los_config
......@@ -287,4 +287,4 @@ VOID OsSchedResched(VOID)
最后的 OsTaskSchedule 是由汇编实现的,后续会详细讲解各个汇编文件,除了tick 会触发调度,还有哪些情况会触发调度?
!import[\zzz\mdmerge\foot.md]
!export[\zzz\md\docs\guide\鸿蒙内核源码分析(时钟管理篇).md]
\ No newline at end of file
!export[\zzz\md\docs\guide\鸿蒙内核源码分析(时钟任务篇).md]
\ No newline at end of file
!import[\zzz\mdmerge\head.md]
**[内核代码详细结构 >> 进入阅读代码](https://my.oschina.net/u/3751245/blog/4869137)**
# 建议先阅读
**目录**
[建议先阅读](#%E5%BB%BA%E8%AE%AE%E5%85%88%E9%98%85%E8%AF%BB)
[为什么学一个东西要学那么多的概念?](#%E5%85%88%E8%AF%B4%E5%87%A0%E4%B8%AA%E6%A6%82%E5%BF%B5)
[进程和线程的状态迁移图](#%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E8%BF%81%E7%A7%BB%E5%9B%BE)
[谁来触发调度工作?](#%E8%B0%83%E5%BA%A6%E6%98%AF%E5%A6%82%E4%BD%95%E8%A7%A6%E5%8F%91%E7%9A%84%EF%BC%9F)
[源码告诉你调度过程是怎样的?](#%E8%B0%83%E5%BA%A6%E8%BF%87%E7%A8%8B)
[请读懂内核最美函数 OsGetTopTask()](#OsGetTopTask())
## 建议先阅读
阅读之前建议先读本系列其他文章,进入[鸿蒙系统源码分析(总目录)](https://blog.csdn.net/kuangyufei/article/details/108727970),以便对本文任务调度机制的理解。
阅读之前建议先读本系列其他文章,以便对本文任务调度机制的理解。
## 为什么学一个东西要学那么多的概念?
......@@ -38,7 +22,7 @@
渠道很多,可能是shell 的一个命令,也可能由内核创建,更多的是大家编写应用程序new出来的一个线程。
调度的内容task已经有了,那他们是如何被有序调度的呢?答案:是32个进程和线程就绪队列,各32个哈,为什么是32个,[鸿蒙系统源码分析(总目录)](https://blog.csdn.net/kuangyufei/article/details/108727970)文章里有详细说明,自己去翻。这张进程状态迁移示意图一定要看明白,线程的状态迁移大家去官方文档看,不一一列出来,太多了占地方。
调度的内容task已经有了,那他们是如何被有序调度的呢?答案:是32个进程和线程就绪队列,各32个哈,为什么是32个,鸿蒙系统源码分析(总目录) 文章里有详细说明,自行去翻。这张进程状态迁移示意图一定要看明白.
注意:进程和线程的队列内的内容只针对就绪状态,其他状态内核并没有用队列去描述它,(线程的阻塞状态用的是pendlist链表),因为就绪就意味着工作都准备好了就等着被调度到CPU来执行了。所以理解就绪队列很关键,有三种情况会加入就绪队列。
......@@ -100,7 +84,6 @@ LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
#endif
}
```
里面对任务进行了扫描,时间片到了或就绪队列有高或同级task, 会执行调度。
- 第二个是各种软硬中断,如何USB插拔,键盘,鼠标这些外设引起的中断,需要去执行中断处理函数。
......@@ -184,7 +167,7 @@ VOID OsSchedResched(VOID)
OsCurrUserTaskSet(newTask->userArea);//设置任务空间
}
/* do the task context switch */
OsTaskSchedule(newTask, runTask); //切换CPU任务上下文
OsTaskSchedule(newTask, runTask); //切换CPU任务上下文,汇编代码实现
}
```
......@@ -197,7 +180,7 @@ VOID OsSchedResched(VOID)
5. 用户模式下需要设置task运行空间,因为每个task栈是不一样的.空间部分具体在系列篇内存中查看
6. 是最重要的,切换任务上下文,参数是新老两个任务,一个要保存现场,一个要恢复现场。
什么是任务上下文?看[鸿蒙系统源码分析(总目录)](https://blog.csdn.net/kuangyufei/article/details/108727970)其他文章,有专门的介绍。这里要说明的是 在CPU的层面,它只认任务上下文!这里看不到任何代码了,因为这是跟CPU相关的,不同的CPU需要去适配不同的汇编代码,所以这些汇编代码不会出现在一个通用工程中。请留意后续 鸿蒙内核源码分析(汇编指令篇)。
什么是任务上下文?看鸿蒙系统源码分析(总目录)其他文章,有专门的介绍。这里要说明的是 在CPU的层面,它只认任务上下文!这里看不到任何代码了,因为这是跟CPU相关的,不同的CPU需要去适配不同的汇编代码.
## 请读懂内核最美函数 OsGetTopTask()
......@@ -249,6 +232,5 @@ OUT:
#if __cplusplus
}
```
!import[\zzz\mdmerge\foot.md]
!export[\zzz\md\docs\guide\鸿蒙内核源码分析(调度机制篇).md]
\ No newline at end of file
!import[\zzz\mdmerge\head.md]
**[内核代码详细结构 >> 进入阅读代码](https://my.oschina.net/u/3751245/blog/4869137)**
**目录**
[调度队列篇](#Task%E9%98%9F%E5%88%97%E7%AF%87)
[为何单独讲调度队列?](#%E4%B8%BA%E4%BD%95%E5%8D%95%E7%8B%AC%E8%AE%B2%E8%B0%83%E5%BA%A6%E9%98%9F%E5%88%97%EF%BC%9F)
[涉及函数](#%E6%B6%89%E5%8F%8A%E5%87%BD%E6%95%B0)
[位图调度器](#%E4%BD%8D%E5%9B%BE%E8%B0%83%E5%BA%A6%E5%99%A8)
[进程就绪队列机制](#%E4%BB%BB%E5%8A%A1%E5%B0%B1%E7%BB%AA%E9%98%9F%E5%88%97%E6%9C%BA%E5%88%B6)
[几个常用函数](#%E5%87%A0%E4%B8%AA%E5%B8%B8%E7%94%A8%E5%87%BD%E6%95%B0)
[同一个进程下的线程的优先级可以不一样吗?](#%E5%90%8C%E4%B8%80%E4%B8%AA%E8%BF%9B%E7%A8%8B%E4%B8%8B%E7%9A%84%E7%BA%BF%E7%A8%8B%E7%9A%84%E4%BC%98%E5%85%88%E7%BA%A7%E5%8F%AF%E4%BB%A5%E4%B8%8D%E4%B8%80%E6%A0%B7%E5%90%97%EF%BC%9F)
[线程调度器](#%E6%80%8E%E4%B9%88%E7%90%86%E8%A7%A3%E7%BA%BF%E7%A8%8B%E7%BB%84%EF%BC%9F)
## 为何单独讲调度队列?
# 为何单独讲调度队列?
鸿蒙内核代码中有两个源文件是关于队列的,一个是用于调度的队列,另一个是用于线程间通讯的IPC队列。
本文详细讲述调度队列,详见代码: kernel\_liteos\_a/kernel/base/sched/sched\_sq/los\_priqueue.c
IPC队列后续有专门的博文讲述,这两个队列的数据结构实现采用的都是双向循环链表,LOS\_DL\_LIST实在是太重要了,是理解鸿蒙内核的关键,说是最重要的代码一点也不为过,源码出现在 sched\_sq模块,说明是用于任务的调度的,sched\_sq模块只有两个文件,另一个los_sched.c就是调度代码。
IPC队列后续有专门的博文讲述,这两个队列的数据结构实现采用的都是双向循环链表,再说一遍LOS_DL_LIST实在是太重要了,是理解鸿蒙内核的关键,说是最重要的代码一点也不为过,源码出现在 sched_sq模块,说明是用于任务的调度的,sched_sq模块只有两个文件,另一个los_sched.c就是调度代码。
## 涉及函数
......@@ -41,23 +19,13 @@ IPC队列后续有专门的博文讲述,这两个队列的数据结构实现
```cpp
//* 0x80000000U = 10000000000000000000000000000000(32位,1是用于移位的,设计之精妙,点赞)
#define PRIQUEUE_PRIOR0_BIT 0x80000000U
#ifndef CLZ
#define CLZ(value) (__clz(value)) //汇编指令
#endif
LITE_OS_SEC_BSS LOS_DL_LIST *g_priQueueList = NULL; //所有的队列 原始指针
LITE_OS_SEC_BSS UINT32 g_priQueueBitmap; // 位图调度
// priority = CLZ(bitmap); // 获取最高优先级任务队列 调度位
```
整个[los_priqueue.c](https://gitee.com/weharmony/kernel_liteos_a/blob/master/kernel/base/sched/sched_sq/los_priqueue.c)就只有两个全部变量,一个是 LOS\_DL\_LIST *g\_priQueueList 是32个进程就绪队列的头指针,在就绪队列中会讲另一个UINT32 g\_priQueueBitmap  估计很多人会陌生,是一个32位的变量,叫位图调度器。怎么理解它呢?
整个los_priqueue.c就只有两个全部变量,一个是 LOS_DL_LIST *g_priQueueList 是32个进程就绪队列的头指针,在就绪队列中会讲另一个UINT32 g_priQueueBitmap  估计很多人会陌生,是一个32位的变量,叫位图调度器。怎么理解它呢?
鸿蒙系统的调度是抢占式的,task分成32个优先级,如何快速的知道哪个队列是空的,哪个队列里有任务需要一个标识,而且要极高效的实现?答案是:位图调度器。
简单说就是一个变量的位来标记对应队列中是否有任务,在位图调度下,任务优先级的值越小则代表具有越高的优先级,每当需要进行调度时,从最低位向最高位查找出第一个置 1 的位的所在位置,即为当前最高优先级,然后从对应优先级就绪队列获得相应的任务控制块,整个调度器的实现复杂度是 O(1),即无论任务多少,其调度时间是固定的。
系列篇已有专门讲位图管理的文章,自行翻看.简单说就是一个变量的位来标记对应队列中是否有任务,在位图调度下,任务优先级的值越小则代表具有越高的优先级,每当需要进行调度时,从最低位向最高位查找出第一个置 1 的位的所在位置,即为当前最高优先级,然后从对应优先级就绪队列获得相应的任务控制块,整个调度器的实现复杂度是 O(1),即无论任务多少,其调度时间是固定的。
## 进程就绪队列机制
......@@ -82,24 +50,14 @@ UINT32 OsPriQueueInit(VOID)
}
return LOS_OK;
}
```
因TASK 有32个优先级,在初始化时内核一次性创建了32个双向循环链表,每种优先级都有一个队列来记录就绪状态的tasks的位置,g\_priQueueList分配的是一个连续的内存块,存放了32个LOS\_DL\_LIST,再看一下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;
```
因TASK 有32个优先级,在初始化时内核一次性创建了32个双向循环链表,每种优先级都有一个队列来记录就绪状态的tasks的位置,g_priQueueList分配的是一个连续的内存块,存放了32个双向链表
## 几个常用函数
还是看入队和出队的源码吧,注意bitmap的变化!
从代码中可以知道,调用了LOS_ListTailInsert(&priQueueList\[priority\], priqueueItem); 注意是从循环链表的尾部插入的,也就是同等优先级的TASK被排在了最后一个执行,只要每次都是从尾部插入,就形成了一个按顺序执行的队列。鸿蒙内核的设计可谓非常巧妙,用极少的代码,极高的效率实现了队列功能。
从代码中可以知道,调用了LOS_ListTailInsert,注意是从循环链表的尾部插入的,也就是同等优先级的TASK被排在了最后一个执行,只要每次都是从尾部插入,就形成了一个按顺序执行的队列。鸿蒙内核的设计可谓非常巧妙,用极少的代码,极高的效率实现了队列功能。
```cpp
VOID OsPriQueueEnqueue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority)
......@@ -144,11 +102,7 @@ VOID OsPriQueueDequeue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *p
*bitMap &= ~(PRIQUEUE_PRIOR0_BIT >> task->priority);//队列空了,对应优先级位 置0
}
}
```
## 同一个进程下的线程的优先级可以不一样吗?
请先想一下这个问题。
......@@ -189,8 +143,7 @@ VOID OsPriQueueDequeue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *p
当进程的主线程或所有线程运行结束后,进程由运行态转为僵尸态,等待父进程回收资源。
注意看上面红色的部分,一个进程竟然可以两种状态共存!
从文档中可知,一个进程是可以两种状态共存的.
```cpp
UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently
......@@ -198,9 +151,7 @@ VOID OsPriQueueDequeue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *p
processCB->processStatus &= ~(status | OS_PROCESS_STATUS_PEND);//取反后的与位运算
processCB->processStatus |= OS_PROCESS_STATUS_READY;//或位运算
```
一个变量存两种状态,怎么做到的?答案还是 按位保存啊。还记得上面的位图调度 g_priQueueBitmap吗,那可是存了32种状态的。其实这在任何一个系统的内核源码中都很常见,类似的还有 左移 <<,右移 >>等等
继续说进程和线程的关系,线程的优先级必须和进程一样吗?他们可以不一样吗?答案是:当然不一样,否则怎么会有设置task优先级的函数。其实task有专门的bitmap来记录它曾经有过的优先级记录, 比如在调度过程中如果遇到阻塞,内核往往会提高持有锁的task的优先级,让它能以最大概率被下一轮调度选中而快速释放锁资源.
......@@ -214,9 +165,7 @@ VOID OsPriQueueDequeue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *p
process */
LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the
priority hash table */
```
咋一看怎么进程的结构体里也有32个队列,其实这就是task的就绪状态队列。threadScheduleMap就是进程自己的位图调度器。具体看进程入队和出队的源码。调度过程是先去进程就绪队列里找最高优先级的进程,然后去该进程找最高优先级的线程来调度。具体看笔者认为的内核最美函数OsGetTopTask,能欣赏到他的美就读懂了就绪队列是怎么管理的。
```cpp
......@@ -260,8 +209,6 @@ LITE_OS_SEC_TEXT_MINOR LosTaskCB *OsGetTopTask(VOID)
OUT:
return newTask;
}
```
映射张大爷的故事:张大爷喊到张全蛋时进场时表演时,张全蛋要决定自己的哪个节目先表演,也要查下他的清单上优先级,它同样也有个张大爷同款记分牌,就这么简单。
......
!import[\zzz\mdmerge\head.md]
**官方基本概念**
## **官方基本概念**
从系统的角度看,进程是资源管理单元。进程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它进程运行。
......@@ -16,7 +16,7 @@ OpenHarmony内核的进程一共有32个优先级(0-31),用户进程可配置
用户态根进程init由内核态创建,其它用户态进程均由init进程fork而来。
**进程状态说明:**
### **进程状态说明:**
- 初始化(Init):该进程正在被创建。
......@@ -28,13 +28,8 @@ OpenHarmony内核的进程一共有32个优先级(0-31),用户进程可配置
- 僵尸态(Zombies):该进程运行结束,等待父进程回收其控制块资源。
**图 1** 进程状态迁移示意图
![](https://img-blog.csdnimg.cn/img_convert/925ff9ae641e32b2502d7b5a155b8579.png)
**进程状态迁移说明:**
- Init→Ready:
进程创建或fork时,拿到该进程控制块后进入Init状态,处于进程初始化阶段,当进程初始化完成将进程插入调度队列,此时进程进入就绪状态。
......@@ -66,25 +61,25 @@ OpenHarmony内核的进程一共有32个优先级(0-31),用户进程可配置
当进程的主线程或所有线程运行结束后,进程由运行态转为僵尸态,等待父进程回收资源。
## 使用场景
### **使用场景**
进程创建后,用户只能操作自己进程空间的资源,无法操作其它进程的资源(共享资源除外)。 用户态允许进程挂起,恢复,延时等操作,同时也可以设置用户态进程调度优先级和调度策略,获取进程调度优先级和调度策略。进程结束的时候,进程会主动释放持有的进程资源,但持有的进程pid资源需要父进程通过wait/waitpid或父进程退出时回收。
## 开始正式分析
### **开始正式分析**
对应张大爷的故事,进程就是那些在场馆外32个队列里排队的,那些队列就是进程的就绪队列。
请注意 进程是资源管理单元 ,而非最终调度单元,调度单元是谁?是 Task ,看下官方对应状态的define
```cpp
#define OS_PROCESS_STATUS_INIT 0x0010U
#define OS_PROCESS_STATUS_READY 0x0020U
#define OS_PROCESS_STATUS_RUNNING 0x0040U
#define OS_PROCESS_STATUS_PEND 0x0080U
#define OS_PROCESS_STATUS_ZOMBIES 0x100U
#define OS_PROCESS_STATUS_INIT 0x0010U //进程初始状态
#define OS_PROCESS_STATUS_READY 0x0020U //进程就绪状态
#define OS_PROCESS_STATUS_RUNNING 0x0040U //进程运行状态
#define OS_PROCESS_STATUS_PEND 0x0080U //进程阻塞状态
#define OS_PROCESS_STATUS_ZOMBIES 0x100U //进程僵死状态
```
一个进程从创建到消亡过程,在内核肯定是极其复杂的。为了方便理解进程,整个系列文章笔者会用张大爷的故事打比方,从生活中的例子来将神秘的系统内核外化解剖出来给大家看。一件这么复杂的事情肯定会有个复杂的结构体来承载,它就是LosProcessCB(进程控制块),代码很长但必须全部拿出来,长是长了点,您就忍忍吧!
一个进程从创建到消亡过程,在内核肯定是极其复杂的。为了方便理解进程,整个系列文章笔者会用张大爷的故事打比方,从生活中的例子来将神秘的系统内核外化解剖出来给大家看。一件这么复杂的事情肯定会有个复杂的结构体来承载,它就是LosProcessCB(进程控制块),代码很长但必须全部拿出来.
```cpp
......@@ -93,51 +88,50 @@ LITE_OS_SEC_BSS LosProcessCB *g_processCBArray = NULL;//进程池,最大进程
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcess;//记录空闲的进程链表
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_processRecyleList;//记录回收的进程列表
typedef struct ProcessCB {
CHAR processName[OS_PCB_NAME_LEN]; /**< Process name */
UINT32 processID; /**< process ID = leader thread ID */
CHAR processName[OS_PCB_NAME_LEN]; /**< Process name */ //进程名称
UINT32 processID; /**< process ID = leader thread ID */ //进程ID,由进程池分配,范围[0,64]
UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently
running in the process */
UINT16 priority; /**< process priority */
UINT16 policy; /**< process policy */
UINT16 timeSlice; /**< Remaining time slice */
UINT16 consoleID; /**< The console id of task belongs */
UINT16 processMode; /**< Kernel Mode:0; User Mode:1; */
UINT32 parentProcessID; /**< Parent process ID */
UINT32 exitCode; /**< process exit status */
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 */
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 */
running in the process *///这里设计很巧妙.用一个16表示了两层逻辑 数量和状态,点赞!
UINT16 priority; /**< process priority */ //进程优先级
UINT16 policy; /**< process policy */ //进程的调度方式,默认抢占式
UINT16 timeSlice; /**< Remaining time slice *///进程时间片,默认2个tick
UINT16 consoleID; /**< The console id of task belongs *///任务的控制台id归属
UINT16 processMode; /**< Kernel Mode:0; User Mode:1; */ //模式指定为内核还是用户进程
UINT32 parentProcessID; /**< Parent process ID */ //父进程ID
UINT32 exitCode; /**< process exit status */ //进程退出状态码
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 */
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 */
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
#if (LOSCFG_KERNEL_SMP == YES)
UINT32 timerCpu; /**< CPU core number of this task is delayed or pended */
UINT32 timerCpu; /**< CPU core number of this task is delayed or pended *///统计各线程被延期或阻塞的时间
#endif
UINTPTR sigHandler; /**< signal handler */
sigset_t sigShare; /**< signal share bit */
UINTPTR sigHandler; /**< signal handler */ //信号处理函数,处理如 SIGSYS 等信号
sigset_t sigShare; /**< signal share bit */ //信号共享位
#if (LOSCFG_KERNEL_LITEIPC == YES)
ProcIpcInfo ipcInfo; /**< memory pool for lite ipc */
ProcIpcInfo ipcInfo; /**< memory pool for lite ipc */ //用于进程间通讯的虚拟设备文件系统,设备装载点为 /dev/lite_ipc
#endif
LosVmSpace *vmSpace; /**< VMM space for processes */
LosVmSpace *vmSpace; /**< VMM space for processes */ //虚拟空间,描述进程虚拟内存的数据结构,linux称为内存描述符
#ifdef LOSCFG_FS_VFS
struct files_struct *files; /**< Files held by the process */
#endif
struct files_struct *files; /**< Files held by the process */ //进程所持有的所有文件,注者称之为进程的文件管理器
#endif //每个进程都有属于自己的文件管理器,记录对文件的操作. 注意:一个文件可以被多个进程操作
timer_t timerID; /**< iTimer */
#ifdef LOSCFG_SECURITY_CAPABILITY
User *user;
UINT32 capability;
#ifdef LOSCFG_SECURITY_CAPABILITY //安全能力
User *user; //进程的拥有者
UINT32 capability; //安全能力范围 对应 CAP_SETGID
#endif
#ifdef LOSCFG_SECURITY_VID
TimerIdMap timerIdMap;
......@@ -152,46 +146,32 @@ typedef struct ProcessCB {
进程的模式有两种,内核态和用户态,能想到main函数中肯定会创建一个内核态的最高优先级进程,他就是 KProcess
调用过程如下
![](https://img-blog.csdnimg.cn/20200915113346417.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
通过task命令查看任务运行状态,可以看到 KProcess 进程 ,看名字就知道是一个内核进程,在系统启动时创建,图中可以看到 KProcess 的task运行情况,从表里可以看到KProcess内有 10几个task
![鸿蒙task](https://img-blog.csdnimg.cn/20200916142650716.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
## 进程初始化
### **进程模块是如何初始化的**
KProcess 在张大爷的故事里相当于场馆的工作人员,他们也要接受张大爷的调度排队进场,但他们的优先级是最高的0级,他们进场后需完成场馆的准备工作,再开门做生意。如果需要多个工作人员怎么办,就是通过fork,简单说就是复制一个,复制的前提是需要有一个,鸿蒙里就是KProcess,其他工作人员都是通过它fork的。
那用户怎么来的呢?就是真正要排队的人也是一样,先创建一个用户祖先,其他用户皆由祖先fork来的。
注意用户进程和内核进程的祖先是不一样的,有各自的祖先根.分别是g_userInitProcess(1号) 和 g_kernelInitProcess(2号)
```cpp
//单核CPU只有并发(Concurrent),多核才会有并行(Parallel) LITE_OS_SEC_BSS 和 LITE_OS_SEC_DATA_INIT 是告诉编译器这些全局变量放在哪个数据段
LITE_OS_SEC_BSS LosProcessCB *g_runProcess[LOSCFG_KERNEL_CORE_NUM];// CPU内核个数,这才是真正的并行
/******************************************************************************
并发(Concurrent):多个线程在单个核心运行,同一时间一个线程运行,系统不停切换线程,
看起来像同时运行,实际上是线程不停切换
并行(Parallel)每个线程分配给独立的CPU核心,线程同时运行
单核CPU多个进程或多个线程内能实现并发(微观上的串行,宏观上的并行)
多核CPU线程间可以实现宏观和微观上的并行
LITE_OS_SEC_BSS 和 LITE_OS_SEC_DATA_INIT 是告诉编译器这些全局变量放在哪个数据段
******************************************************************************/
LITE_OS_SEC_BSS LosProcessCB *g_runProcess[LOSCFG_KERNEL_CORE_NUM];// CPU内核个数,超过一个就实现了并行
LITE_OS_SEC_BSS LosProcessCB *g_processCBArray = NULL; // 进程池数组
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcess;// 空闲状态下可供分配的进程,此时进程白纸一张
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcess;// 空闲状态下的进程链表, .个人觉得应该取名为 g_freeProcessList @note_thinking
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_processRecyleList;// 需要回收的进程列表
LITE_OS_SEC_BSS UINT32 g_userInitProcess = OS_INVALID_VALUE;// 用户态的初始init进程,用户态下其他进程由它 fork
LITE_OS_SEC_BSS UINT32 g_kernelInitProcess = OS_INVALID_VALUE;// 内核态初始Kprocess进程,内核态下其他进程由它 fork
LITE_OS_SEC_BSS UINT32 g_kernelIdleProcess = OS_INVALID_VALUE;// 内核态idle进程,由Kprocess fork
LITE_OS_SEC_BSS UINT32 g_processMaxNum;// 进程最大数量
LITE_OS_SEC_BSS ProcessGroup *g_processGroup = NULL;// 进程组
```
以上是进程模块全局变量。注释是笔者添加的,鸿蒙内核的注释很少,查看更多注释前往以下仓库
鸿蒙内核源码注释中文版 进入>>【[CSDN仓](https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note) | [Gitee仓](https://gitee.com/weharmony/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)】阅读,四大仓库正加注同步更新中....
KProcess 在张大爷的故事里相当于场馆的工作人员,他们也要接受张大爷的调度排队进场,但他们的优先级是最高的0级,他们进场后需完成场馆的准备工作,再开门做生意。如果需要多个工作人员怎么办,就是通过fork,简单说就是复制一个,复制的前提是需要有一个,鸿蒙里就是KProcess,其他工作人员都是通过它fork的。那用户怎么来的呢?就是真正要排队的人也是一样,先创建一个用户爸爸,其他用户就用爸爸fork来的,注意是fork 不是那个什么K,哈哈,你懂得。
还是直接看代码吧
```cpp
/**
* @ingroup los_config
* Maximum supported number of process rather than the number of usable processes.
*/
#ifndef LOSCFG_BASE_CORE_PROCESS_LIMIT
#define LOSCFG_BASE_CORE_PROCESS_LIMIT 64
#endif
LITE_OS_SEC_BSS UINT32 g_processMaxNum;// 进程最大数量,默认64个
LITE_OS_SEC_BSS ProcessGroup *g_processGroup = NULL;// 全局进程组,负责管理所有进程组
//进程模块初始化,被编译放在代码段 .init 中
LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID)
......@@ -213,10 +193,10 @@ LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID)
for (index = 0; index < g_processMaxNum; index++) {//进程池循环创建
g_processCBArray[index].processID = index;//进程ID[0-g_processMaxNum]赋值
g_processCBArray[index].processStatus = OS_PROCESS_FLAG_UNUSED;// 默认都是白纸一张,臣妾干净着呢
LOS_ListTailInsert(&g_freeProcess, &g_processCBArray[index].pendList);// 初始全是可分配进程描述符
g_processCBArray[index].processStatus = OS_PROCESS_FLAG_UNUSED;// 默认都是白纸一张,贴上未使用标签
LOS_ListTailInsert(&g_freeProcess, &g_processCBArray[index].pendList);//注意g_freeProcess挂的是pendList节点,所以使用要通过OS_PCB_FROM_PENDLIST找到进程实体.
}
// ????? 为啥用户模式的根进程 选1 ,内核模式的根进程选2
g_userInitProcess = 1; /* 1: The root process ID of the user-mode process is fixed at 1 *///用户模式的根进程
LOS_ListDelete(&g_processCBArray[g_userInitProcess].pendList);// 清空g_userInitProcess pend链表
......@@ -228,21 +208,38 @@ LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID)
```
代码已经很清楚,创建了一个进程池,默认64个进程,也就是不改宏LOSCFG\_BASE\_CORE\_PROCESS\_LIMIT的情况下 系统最多是64个进程,但有两个进程先被占用,用户态和内核态各一个,他们是后续创建进程的爹,所以最多留给外面的只有 62个进程可创建,代码的最后两个爸爸的task阻塞链表被清空了,因为没有阻塞任务当然要清空.
代码已经很清楚,创建了一个进程池,默认64个进程,也就是不改宏LOSCFG_BASE_CORE_PROCESS_LIMIT的情况下 系统最多是64个进程,但有两个进程先被占用,用户态和内核态各一个,他们是后续创建进程的根,所以最多留给外面的只有 62个进程可创建,代码的最后两个根进程的task阻塞链表被清空了,因为没有阻塞任务当然要清空.
## 创建内核态Kprocess的过程
### **内核态根进程创建过程**
创建核心态进程,也就是线程池中的 \[2\] 号进程,task 命令中 Kprocess PID = 2, 参数是 内核态和 最高优先级 0
创建"Kprocess"进程,也就是线程池中的2号进程g_kernelInitProcess,设为最高优先级 0
### OsKernelInitProcess
```cpp
//初始化 2号进程,即内核根进程
LITE_OS_SEC_TEXT_INIT UINT32 OsKernelInitProcess(VOID)
{
LosProcessCB *processCB = NULL;
UINT32 ret;
![](https://img-blog.csdnimg.cn/20200915120418742.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2t1YW5neXVmZWk=,size_16,color_FFFFFF,t_70)
ret = OsProcessInit();// 初始化进程模块全部变量,创建各循环双向链表
if (ret != LOS_OK) {
return ret;
}
### OsCreateIdleProcess
processCB = OS_PCB_FROM_PID(g_kernelInitProcess);// 以PID方式得到一个进程
ret = OsProcessCreateInit(processCB, OS_KERNEL_MODE, "KProcess", 0);// 初始化进程,最高优先级0,鸿蒙进程一共有32个优先级(0-31) 其中0-9级为内核进程,用户进程可配置的优先级有22个(10-31)
if (ret != LOS_OK) {
return ret;
}
代码的把kprocess 设为当前进程,并且fork了一个 KIdle(内核态的空闲进程)
processCB->processStatus &= ~OS_PROCESS_STATUS_INIT;// 进程初始化位 置1
g_processGroup = processCB->group;//全局进程组指向了KProcess所在的进程组
LOS_ListInit(&g_processGroup->groupList);// 进程组链表初始化
OsCurrProcessSet(processCB);// 设置为当前进程
return OsCreateIdleProcess();// 创建一个空闲状态的进程
}
```cpp
//创建一个名叫"KIdle"的进程,给CPU空闲的时候使用
STATIC UINT32 OsCreateIdleProcess(VOID)
{
......@@ -275,35 +272,8 @@ STATIC UINT32 OsCreateIdleProcess(VOID)
}
```
### OsIdleTask
CPU空闲时是跑在idleTask中的,这里是CPU休息的地方,进入低电量模式,等待被事件唤醒上班
```cpp
//空闲任务 注意 #define WEAK __attribute__((weak)) 是用于防止crash的
LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID)
{
while (1) {//只有一个死循环
#ifdef LOSCFG_KERNEL_TICKLESS
if (OsTickIrqFlagGet()) {
OsTickIrqFlagSet(0);
OsTicklessStart();
}
#endif
Wfi();
}
}
VOID Wfi(VOID)//WFI指令:arm core 立即进入low-power standby state,直到有WFI Wakeup events发生
{
__asm__ __volatile__ ("wfi" : : : "memory");//一般用于cpuidle
}
```
## 创建用户态进程的过程是怎样的?看代码
### **用户态根进程创建过程**
创建"Init"进程,也就是线程池中的1号进程g_userInitProcess,优先级为 28,好低啊
```cpp
/**
* @ingroup los_process
......@@ -373,22 +343,11 @@ ERROR:
return ret;
}
```
发现用户态init 和 创建的过程类似 内核态,并且用户态爸爸进程的优先级是 28,好低啊。
对应张大爷的故事:工作人员和用户的工作环境是不一样的,工作人员可以全场活动,但用户不能去管理处溜达,你只能在属于你表演的场地活动,这个场地就是用户空间,这涉及到内存的管理,非常复杂, 具体去系列篇看怎么管理内存的文章。
## 留下两个小问题请大家思考
OsUserInitProcess里有些关于内存的代码,源码中找不到值,比如:
```cpp
CHAR *userInitTextStart = (CHAR *)&__user_init_entry;
CHAR *userInitBssStart = (CHAR *)&__user_init_bss;
CHAR *userInitEnd = (CHAR *)&__user_init_end;
```
### **留个小问题请大家思考**
为什么会这样?另外两个爸爸对应的PID是 1和2,那进程池里的0号进程又去哪里了呢?
1号进程和2号进程分别是用户态和内核态的根进程,那进程池里的0号进程去哪里了呢?
!import[\zzz\mdmerge\foot.md]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册