整理技术文章内容

    搜索 @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;
}
```
映射张大爷的故事:张大爷喊到张全蛋时进场时表演时,张全蛋要决定自己的哪个节目先表演,也要查下他的清单上优先级,它同样也有个张大爷同款记分牌,就这么简单。
......
!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;
}
```
映射张大爷的故事:张大爷喊到张全蛋时进场时表演时,张全蛋要决定自己的哪个节目先表演,也要查下他的清单上优先级,它同样也有个张大爷同款记分牌,就这么简单。
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册