鸿蒙内核源码分析 (并发并行篇) | 内核如何管理多个 CPU?

2024-04-10 1913阅读

理解并发概念

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

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

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

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

    理解并行概念

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

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

    理解协程概念

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

    内核如何描述 CPU

    Python
        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

    Python
    # 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 ,因文件不大,这里把代码都贴出来了.

      Python
          #if (LOSCFG_KERNEL_SMP == YES)
          //给参数CPU发送调度信号
          VOID LOS_MpSchedule(UINT32 target)//target每位对应CPU core 
          {
              UINT32 cpuid = ArchCurrCpuid();
              target &= ~(1U 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 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 核的初始化

      Python
          //次级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,正式开始工作,跑任务

        为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

        《鸿蒙开发学习手册》:

        如何快速入门:https://qr21.cn/FV7h05

        1. 基本概念
        2. 构建第一个ArkTS应用
        3. ……

        鸿蒙内核源码分析 (并发并行篇) | 内核如何管理多个 CPU?

        开发基础知识:https://qr21.cn/FV7h05

        1. 应用基础知识
        2. 配置文件
        3. 应用数据管理
        4. 应用安全管理
        5. 应用隐私保护
        6. 三方应用调用管控机制
        7. 资源分类与访问
        8. 学习ArkTS语言
        9. ……

        鸿蒙内核源码分析 (并发并行篇) | 内核如何管理多个 CPU?

        基于ArkTS 开发:https://qr21.cn/FV7h05

        1. Ability开发
        2. UI开发
        3. 公共事件与通知
        4. 窗口管理
        5. 媒体
        6. 安全
        7. 网络与链接
        8. 电话服务
        9. 数据管理
        10. 后台任务(Background Task)管理
        11. 设备管理
        12. 设备使用信息统计
        13. DFX
        14. 国际化开发
        15. 折叠屏系列
        16. ……

        鸿蒙内核源码分析 (并发并行篇) | 内核如何管理多个 CPU?

        鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

        鸿蒙内核源码分析 (并发并行篇) | 内核如何管理多个 CPU?

        鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

        1.项目开发必备面试题

        2.性能优化方向

        3.架构方向

        4.鸿蒙开发系统底层方向

        5.鸿蒙音视频开发方向

        6.鸿蒙车载开发方向

        7.鸿蒙南向开发方向

        鸿蒙内核源码分析 (并发并行篇) | 内核如何管理多个 CPU?

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]