百篇博客系列篇.本篇为:

任务管理相关篇为:

本篇说清楚并发并行

读本篇之前建议先读

理解并发概念

  • 并发(Concurrent):多个线程在单个核心运行,同一时间只能一个线程运行,内核不停切换线程,看起来像同时运行,实际上是线程被高速的切换.

  • 通俗好理解的比喻就是高速单行道,单行道指的是CPU的核数,跑的车就是线程(任务),进程就是管理车的公司,一个公司可以有很多台车.并发和并行跟CPU的核数有关.车道上同时只能跑一辆车,但因为指挥系统很牛,够快,在毫秒级内就能换车跑,人根本感知不到切换.所以外部的感知会是同时在进行,实现了微观上的串行,宏观上的并行.

  • 线程切换的本质是CPU要换场地上班,去哪里上班由哪里提供场地,那个场地就是任务栈,每个任务栈中保存了上班的各种材料,来了就行立马干活.那些材料就是任务上下文.简单的说就是上次活干到那里了,回来继续接着干.上下文由任务栈自己保存,CPU不管的,它来了只负责任务交过来的材料,材料显示去哪里搬砖它就去哪里搬砖.

记住一个单词就能记住并行并发的区别, 发单,发单(并发单行).

理解并行概念

并行(Parallel)每个线程分配给独立的CPU核心,线程真正的同时运行.

通俗好理解的比喻就是高速多行道,实现了微观和宏观上同时进行. 并行当然是快,人多了干活就不那么累,但干活人多了必然会带来人多的管理问题,会把问题变复杂,请想想会出现哪些问题?

理解协程概念

这里说下协程,例如go语言是有协程支持的,其实协程跟内核层没有关系,是应用层的概念.是在线程之上更高层的封装,用通俗的比喻来说就是在车内另外搞了几条车道玩.其对内核来说没有新东西,内核只负责车的调度,至于车内你想怎么弄那是应用程序自己的事.本质的区别是CPU根本没有换地方上班(没有被调度),而并发/并行都是换地方上班了.

内核如何描述CPU

    typedef struct {
SortLinkAttribute taskSortLink; /* task sort link */ //每个CPU core 都有一个task排序链表
SortLinkAttribute swtmrSortLink; /* swtmr sort link */ //每个CPU core 都有一个定时器排序链表 UINT32 idleTaskID; /* idle task id */ //空闲任务ID 见于 OsIdleTaskCreate
UINT32 taskLockCnt; /* task lock flag */ //任务锁的数量,当 > 0 的时候,需要重新调度了
UINT32 swtmrHandlerQueue; /* software timer timeout queue id */ //软时钟超时队列句柄
UINT32 swtmrTaskID; /* software timer task id */ //软时钟任务ID UINT32 schedFlag; /* pending scheduler flag */ //调度标识 INT_NO_RESCH INT_PEND_RESCH
#if (LOSCFG_KERNEL_SMP == YES)
UINT32 excFlag; /* cpu halt or exc flag */ //CPU处于停止或运行的标识
#endif
} Percpu; Percpu g_percpu[LOSCFG_KERNEL_CORE_NUM];//全局CPU数组

这是内核对CPU的描述,主要是两个排序链表,一个是任务的排序,一个是定时器的排序.什么意思?

在系列篇中多次提过,任务是内核的调度单元,注意可不是进程,虽然调度也需要进程参与,也需要切换进程,切换用户空间.但调度的核心是切换任务,每个任务的代码指令才是CPU的粮食,它吃的是一条条的指令.每个任务都必须指定取粮地址(即入口函数).

另外还有一个东西能提供入口函数,就是定时任务.很重要也很常用,没它某宝每晚9点的准时秒杀实现不了.在内核每个CPU都有自己独立的任务和定时器链表.

每次Tick的到来,处理函数会去扫描这两个链表,看有没有定时器超时的任务需要执行,有则立即执行定时任务,定时任务是所有任务中优先级最高的,0号优先级,在系列篇中有专门讲定时器任务,可自行翻看.

LOSCFG_KERNEL_SMP

# if (LOSCFG_KERNEL_SMP == YES)
# define LOSCFG_KERNEL_CORE_NUM LOSCFG_KERNEL_SMP_CORE_NUM //多核情况下支持的CPU核数
# else
# define LOSCFG_KERNEL_CORE_NUM 1 //单核配置
# endif

多CPU核的操作系统有3种处理模式(SMP+AMP+BMP) 鸿蒙实现的是 SMP 的方式

  • 非对称多处理(Asymmetric multiprocessing,AMP)每个CPU内核运行一个独立的操作系统或同一操作系统的独立实例(instantiation)。

  • 对称多处理(Symmetric multiprocessing,SMP)一个操作系统的实例可以同时管理所有CPU内核,且应用并不绑定某一个内核。

  • 混合多处理(Bound multiprocessing,BMP)一个操作系统的实例可以同时管理所有CPU内核,但每个应用被锁定于某个指定的核心。

宏LOSCFG_KERNEL_SMP表示对多CPU核的支持,鸿蒙默认是打开LOSCFG_KERNEL_SMP的。

多CPU核支持

鸿蒙内核对CPU的操作见于 los_mp.c ,因文件不大,这里把代码都贴出来了.

    #if (LOSCFG_KERNEL_SMP == YES)
//给参数CPU发送调度信号
VOID LOS_MpSchedule(UINT32 target)//target每位对应CPU core
{
UINT32 cpuid = ArchCurrCpuid();
target &= ~(1U << cpuid);//获取除了自身之外的其他CPU
HalIrqSendIpi(target, LOS_MP_IPI_SCHEDULE);//向目标CPU发送调度信号,核间中断(Inter-Processor Interrupts),IPI
}
//硬中断唤醒处理函数
VOID OsMpWakeHandler(VOID)
{
/* generic wakeup ipi, do nothing */
}
//硬中断调度处理函数
VOID OsMpScheduleHandler(VOID)
{//将调度标志设置为与唤醒功能不同,这样就可以在硬中断结束时触发调度程序。
/*
* set schedule flag to differ from wake function,
* so that the scheduler can be triggered at the end of irq.
*/
OsPercpuGet()->schedFlag = INT_PEND_RESCH;//给当前Cpu贴上调度标签
}
//硬中断暂停处理函数
VOID OsMpHaltHandler(VOID)
{
(VOID)LOS_IntLock();
OsPercpuGet()->excFlag = CPU_HALT;//让当前Cpu停止工作 while (1) {}//陷入空循环,也就是空闲状态
}
//MP定时器处理函数, 递归检查所有可用任务
VOID OsMpCollectTasks(VOID)
{
LosTaskCB *taskCB = NULL;
UINT32 taskID = 0;
UINT32 ret; /* recursive checking all the available task */
for (; taskID <= g_taskMaxNum; taskID++) { //递归检查所有可用任务
taskCB = &g_taskCBArray[taskID]; if (OsTaskIsUnused(taskCB) || OsTaskIsRunning(taskCB)) {
continue;
} /* 虽然任务状态不是原子的,但此检查可能成功,但无法完成删除,此删除将在下次运行之前处理
* though task status is not atomic, this check may success but not accomplish
* the deletion; this deletion will be handled until the next run.
*/
if (taskCB->signal & SIGNAL_KILL) {//任务收到被干掉信号
ret = LOS_TaskDelete(taskID);//干掉任务,回归任务池
if (ret != LOS_OK) {
PRINT_WARN("GC collect task failed err:0x%x\n", ret);
}
}
}
}
//MP(multiprocessing) 多核处理器初始化
UINT32 OsMpInit(VOID)
{
UINT16 swtmrId; (VOID)LOS_SwtmrCreate(OS_MP_GC_PERIOD, LOS_SWTMR_MODE_PERIOD, //创建一个周期性,持续时间为 100个tick的定时器
(SWTMR_PROC_FUNC)OsMpCollectTasks, &swtmrId, 0);//OsMpCollectTasks为超时回调函数
(VOID)LOS_SwtmrStart(swtmrId);//开始定时任务 return LOS_OK;
}
#endif

代码一一都加上了注解,这里再一一说明下:

1.OsMpInit

多CPU核的初始化, 多核情况下每个CPU都有各自的编号, 内核有分成主次CPU, 0号默认为主CPU, OsMain()由主CPU执行,被汇编代码调用.

初始化只开了个定时任务,只干一件事就是回收不用的任务.回收的条件是任务是否收到了被干掉的信号. 例如shell命令 kill 9 14 ,意思是干掉14号线程的信号,这个信号会被线程保存起来. 可以选择自杀也可以等着被杀. 这里要注意,鸿蒙有两种情况下任务不能被干掉, 一种是系统任务不能被干掉的, 第二种是正在运行状态的任务.

2.次级CPU的初始化

同样由汇编代码调用,通过以下函数执行,完成每个CPU核的初始化

    //次级CPU初始化,本函数执行的次数由次级CPU的个数决定. 例如:在四核情况下,会被执行3次, 0号通常被定义为主CPU 执行main
LITE_OS_SEC_TEXT_INIT VOID secondary_cpu_start(VOID)
{
#if (LOSCFG_KERNEL_SMP == YES)
UINT32 cpuid = ArchCurrCpuid(); OsArchMmuInitPerCPU();//每个CPU都需要初始化MMU OsCurrTaskSet(OsGetMainTask());//设置CPU的当前任务 /* increase cpu counter */
LOS_AtomicInc(&g_ncpu); //统计CPU的数量 /* store each core's hwid */
CPU_MAP_SET(cpuid, OsHwIDGet());//存储每个CPU的 hwid
HalIrqInitPercpu(); //CPU硬件中断初始化 OsCurrProcessSet(OS_PCB_FROM_PID(OsGetKernelInitProcessID())); //设置内核进程为CPU进程
OsSwtmrInit(); //定时任务初始化,每个CPU维护自己的定时器队列
OsIdleTaskCreate(); //创建空闲任务,每个CPU维护自己的任务队列
OsStart(); //本CPU正式启动在内核层的工作
while (1) {
__asm volatile("wfi");//wait for Interrupt 等待中断,即下一次中断发生前都在此hold住不干活
}//类似的还有 WFE: wait for Events 等待事件,即下一次事件发生前都在此hold住不干活
#endif
}

可以看出次级CPU有哪些初始化步骤:

  • 初始化MMU,OsArchMmuInitPerCPU

  • 设置当前任务 OsCurrTaskSet

  • 初始化硬件中断 HalIrqInitPercpu

  • 初始化定时器队列 OsSwtmrInit

  • 创建空任务 OsIdleTaskCreate, 外面没有任务的时CPU就待在这个空任务里自己转圈圈.

  • 开始自己的工作流程 OsStart,正式开始工作,跑任务

多CPU核还有哪些问题?

  • CPU之间抢资源的情况要怎么处理?

  • CPU之间通讯(也叫核间通讯)怎么解决?

  • 如果确保两个CPU不会同时执行同一个任务?

  • 汇编代码如何实现对各CPU的调动

请前往系列篇或直接前往内核注解代码查看.这里不再做说明.

鸿蒙内核源码分析.总目录

v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o

百万汉字注解.百篇博客分析

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee| github| csdn| coding >

百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto| csdn| harmony| osc >

关注不迷路.代码即人生

QQ群:790015635 | 入群密码: 666

原创不易,欢迎转载,但请注明出处.

鸿蒙内核源码分析(并发并行篇) | 听过无数遍的两个概念 | 百篇博客分析OpenHarmony源码 | v25.01的更多相关文章

  1. 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 百篇博客分析OpenHarmony源码 | v41.03

    百篇博客系列篇.本篇为: v41.xx 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁 ...

  2. 鸿蒙内核源码分析(系统调用篇) | 开发者永远的口头禅 | 百篇博客分析OpenHarmony源码 | v37.03

    百篇博客系列篇.本篇为: v37.xx 鸿蒙内核源码分析(系统调用篇) | 开发者永远的口头禅 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁 ...

  3. 鸿蒙内核源码分析(线程概念篇) | 是谁在不停的折腾CPU? | 百篇博客分析OpenHarmony源码 | v21.06

    百篇博客系列篇.本篇为: v21.xx 鸿蒙内核源码分析(线程概念篇) | 是谁在不断的折腾CPU | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调 ...

  4. 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的 | 百篇博客分析OpenHarmony源码 | v7.07

    百篇博客系列篇.本篇为: v07.xx 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调 ...

  5. 鸿蒙内核源码分析(调度队列篇) | 内核有多少个调度队列 | 百篇博客分析OpenHarmony源码 | v6.05

    百篇博客系列篇.本篇为: v06.xx 鸿蒙内核源码分析(调度队列篇) | 内核有多少个调度队列 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度 ...

  6. 鸿蒙内核源码分析(任务管理篇) | 任务池是如何管理的 | 百篇博客分析OpenHarmony源码 | v5.05

    百篇博客系列篇.本篇为: v05.xx 鸿蒙内核源码分析(任务管理篇) | 任务池是如何管理的 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁 ...

  7. 鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 百篇博客分析OpenHarmony源码 | v4.05

    百篇博客系列篇.本篇为: v04.xx 鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度 ...

  8. 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁的贡献最大 | 百篇博客分析OpenHarmony源码 | v3.05

    百篇博客系列篇.本篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁的贡献最大 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度 ...

  9. 鸿蒙源码分析系列(总目录) | 百万汉字注解 百篇博客分析 | 深入挖透OpenHarmony源码 | v8.23

    百篇博客系列篇.本篇为: v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o 百篇博客.往期回顾 在给OpenHarmony内核源码加注过程中,整理出以下 ...

随机推荐

  1. 微信小程序中h5跳转到登录页面,登陆成功返回携带参数,h5刷新

    公司的一个小程序,要做一个活动,需要判断登录状态. 思路:h5跳转到登录页面,登陆成功携带token自动返回. 本来以为是个非常简单的功能,没想到..... 发帖记录一下 1.登录页面 用getCur ...

  2. bicabo C#多线程详解(三)

    继续上一节的问题:调换两个新创建的线程启动顺序会是什么结果? using System; using System.Threading;namespace Test{    class TestThr ...

  3. .Net Core 集成 Redis

    首先安装RedisServer 安装教程可参照 http://www.redis.cn/download.html 或者 https://www.runoob.com/redis/redis-inst ...

  4. 快速使用 Docker 上手 Sentry-CLI - 玩转 Source Maps 使用 (create-react-app)

    系列 快速使用 Docker 上手 Sentry-CLI - 创建版本 入门 使用 sentry-cli 上传 source maps 时,您需要设置构建系统以创建版本(release)并上传与该版本 ...

  5. element-ui 用 el-checkbox-group 做权限管理

    template <el-checkbox-group v-model="menu_ide" v-for="(item,index) in menu_idss&qu ...

  6. 通过WebGoat学习java反序列化漏洞

    首发于freebuff. WebGoat-Insecure Deserialization Insecure Deserialization 01 概念 本课程描述了什么是序列化,以及如何操纵它来执行 ...

  7. Tricks

    由于本人着实有些菜,因此在此积累一些巧妙的 \(Tricks\) ,以备不时之需... 与其说是 \(Tricks\) 不如说是学习笔记?? 数学 组合数 常见的数列 斐波那契数列 图论 树论 \(P ...

  8. Python - 通过PyYaml库操作YAML文件

    PyYaml简单介绍 Python的PyYAML模块是Python的YAML解析器和生成器 它有个版本分水岭,就是5.1 读取YAML5.1之前的读取方法 def read_yaml(self, pa ...

  9. Node.js躬行记(9)——微前端实践

    后台管理系统使用的是umi框架,随着公司业务的发展,目前已经变成了一个巨石应用,越来越难维护,有必要对其进行拆分了. 计划是从市面上挑选一个成熟的微前端框架,首先选择的是 icestark,虽然文档中 ...

  10. FastReport合并多份报表为一份预览打印

    效果 比较简单,直接贴代码 //打印第一份报表 procedure TForm1.Button2Click(Sender: TObject); begin frxReport1.LoadFromFil ...