本篇关键词:__asm__ 、volatile 、DSB

下载 >> 离线文档.鸿蒙内核源码分析(百篇博客分析.挖透鸿蒙内核).pdf

基础知识相关篇为:

什么是宏

在真正的编译开始之前,编译器也需要热热身,俗话说的好:"前戏工作不能少,后续推进质量好"。 就是编译器的前戏部分(预处理),简单的说就是复制粘贴。大多数人第一次接触 多半是因为 π

#define PI 3.1415926
1

人类进化的结果是不擅长做记忆类操作,但这并非缺点,相比3.1415926π 能减少体能的消耗,这种将复杂信息取个 别名 方便记录和传播的做法人类用了几千年。据说在猴子的世界里也有专门负责盯梢的,看到远方有狮子来了,会手舞足蹈描绘狮子的外形和模仿声音以此告知同伴什么动物来了而躲避危险。但人类只需要喊一句 "狮子来了"足以,这才是食物链顶端的做法,单给万千事物取名的这一个能力就能甩动物世界几百条街。更别说以此衍生出来的畊宏女孩山东彭于晏 等等流行网络词汇,其做法都是用极低的成本达到极快的传播。

macromacro instruction 的缩写,将某个输入映射到替代输出的一种规则或模式,输入和输出可以是一系列词汇标记或字符,或语法树。为什么中文被翻译成 可能是因为单词 宏观(macro)

若将宏只停留在 3.1415926 就简单肤浅了,要知道任何看似简单的背后都可以击鼓传花,熟能生巧,蛤蟆功练到最高境界那也能成为天下第一的。站长真正被宏的强大震撼到的是看了侯俊杰先生的 <<深入浅出MFC>> 对消息机制的实现 ,当时不由的惊呼 "靠,原来还能这么玩 ! " ,从此对宏爱不释手,所以百篇博客必须为此开一篇,更何况鸿蒙内核对宏的讲究也并没有让人失望。想了解 MFC 消息映射宏的可 查看 >> 三个奇怪的宏,一张巨大的网

一段难懂的代码

能看懂以下代码中 for 循环C语言功底非同一般,绝对的大神。

STATIC VOID HPFPriorityRestore(LosTaskCB *owner, const LOS_DL_LIST *list, const SchedParam *param)
{
    LosTaskCB *pendedTask = NULL;
    // ... 
    for (pendedTask = ((LosTaskCB *)(VOID *)((CHAR *)((list)->pstNext) - ((UINTPTR)&((LosTaskCB *)0)->pendList)));       
            &(pendedTask)->pendList != (list);                                     
            pendedTask = ((LosTaskCB *)(VOID *)((CHAR *)((pendedTask)->pendList.pstNext) - ((UINTPTR)&((LosTaskCB *)0)->pendList))))
    {
        SchedHPF *pendSp = (SchedHPF *)&pendedTask->sp;
        if ((pendedTask->ops == owner->ops) && (priority != pendSp->priority)) {
            LOS_BitmapClr(&sp->priBitmap, pendSp->priority);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

天天跟这样的代码打交道容易得脑梗,如果换成以下的样子就简洁了很多

STATIC VOID HPFPriorityRestore(LosTaskCB *owner, const LOS_DL_LIST *list, const SchedParam *param)
{
    LosTaskCB *pendedTask = NULL;
    // ... 
    LOS_DL_LIST_FOR_EACH_ENTRY(pendedTask, list, LosTaskCB, pendList) {
        SchedHPF *pendSp = (SchedHPF *)&pendedTask->sp;
        if ((pendedTask->ops == owner->ops) && (priority != pendSp->priority)) {
            LOS_BitmapClr(&sp->priBitmap, pendSp->priority);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

LOS_DL_LIST_FOR_EACH_ENTRY(pendedTask, list, LosTaskCB, pendList) 含义如下:

  • 遍历一个叫 list 的双向链表,链表上挂的是一个个叫 LosTaskCB 结构体节点。
  • LosTaskCB 是通过其成员变量 pendList 挂到 list 上的。
  • 解铃还须系铃人,将节点摘下来也需通过 pendList ,并通过地址偏移量找到 LosTaskCB 的开始地址后给交给变量 pendedTask处理。
  • 使用两个嵌套宏 LOS_DL_LIST_ENTRYLOS_OFF_SET_OF 完成了以上操作 ,三个宏完整原型如下
    #define LOS_DL_LIST_FOR_EACH_ENTRY(item, list, type, member)             \
        for (item = LOS_DL_LIST_ENTRY((list)->pstNext, type, member);        \
            &(item)->member != (list);                                      \
            item = LOS_DL_LIST_ENTRY((item)->member.pstNext, type, member))
    
    #define LOS_DL_LIST_ENTRY(item, type, member) \
        ((type *)(VOID *)((CHAR *)(item) - LOS_OFF_SET_OF(type, member)))
    
    #define LOS_OFF_SET_OF(type, member) ((UINTPTR)&((type *)0)->member)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 遍历宏很简洁优雅,内核关于双向链表的遍历都可以通过它来完成,其他操作具体可翻看 双向链表篇

除了简化对双向链表操作还有对 红黑树的操作,尝试解读下以下代码的含义

  RB_WALK(pstTree, pstNode, pstWalk)
  {
      OsRbDeleteNode(pstTree, pstNode);
      (VOID)pstTree->pfFree(pstNode);
  }
  RB_WALK_END(pstTree, pstNode, pstWalk);
1
2
3
4
5
6
  #define RB_WALK(pstTree, pstNode, pstRbWalk) do {                                     \
          LosRbWalk *(pstRbWalk) = NULL;       \
          pstRbWalk = LOS_RbCreateWalk(pstTree);  \
          (pstNode) = LOS_RbWalkNext(pstRbWalk);  \
          for (; NULL != (pstNode); (pstNode) = LOS_RbWalkNext(pstRbWalk)) {

  #define RB_WALK_END(pstTree, pstNode, pstRbWalk) }                                            \
      LOS_RbDeleteWalk(pstRbWalk);                    \
      }                                            \
      while (0);  
1
2
3
4
5
6
7
8
9
10

寄存器操作

硬件设备的对外使用接口是 寄存器,阅读硬件生产商提供的 Datasheet(数据手册)是每个硬件工程师都需具备的基本素养。寄存器分 专用通用 寄存器,驱动工程师根据数据手册配置的一般是专用寄存器,对这些寄存器不同位的设置对应了不同的功能。通用寄存器的使用一般是由编译器完成,此处不展开讲,后续 编译器系列篇 中会详细说明。

以协处理器 cp15 举例 ,它是CPU的助手(详见于 协处理器篇 ),一共有 16个寄存器 32 位的寄存器,其编号为 C0 ~ C15 ,用来控制 cacheTCM 和存储器管理。cp15 寄存器都是复合功能寄存器,不同功能对应不同的内存实体,全由访问指令的参数来决定。读写这些寄存器必须使用 MRCMCR 指令。

#define CP15_REG(CRn, Op1, CRm, Op2)    "p15, "#Op1", %0, "#CRn","#CRm","#Op2

#define MIDR                CP15_REG(c0, 0, c0, 0)    /*! Main ID Register | 主ID寄存器 */
#define MPIDR               CP15_REG(c0, 0, c0, 5)    /*! Multiprocessor Affinity Register | 多处理器关联寄存器给每个CPU制定一个逻辑地址*/
#define CCSIDR              CP15_REG(c0, 1, c0, 0)    /*! Cache Size ID Registers | 缓存大小ID寄存器*/	
#define CLIDR               CP15_REG(c0, 1, c0, 1)    /*! Cache Level ID Register | 缓存登记ID寄存器*/	
#define VPIDR               CP15_REG(c0, 4, c0, 0)    /*! Virtualization Processor ID Register | 虚拟化处理器ID寄存器*/	
#define VMPIDR              CP15_REG(c0, 4, c0, 5)    /*! Virtualization Multiprocessor ID Register | 虚拟化多处理器ID寄存器*/	

#define ARM_SYSREG_READ(REG)                    \
({                                              \
    UINT32 _val;                                \
    __asm__ volatile("mrc " REG : "=r" (_val)); \
    _val;                                       \
})

#define ARM_SYSREG_WRITE(REG, val)              \
({                                              \
    __asm__ volatile("mcr " REG :: "r" (val));  \
    ISB;                                        \
})

/// 获取当前CPUID
STATIC INLINE UINT32 ArchCurrCpuid(VOID)
{
#ifdef LOSCFG_KERNEL_SMP
    return ARM_SYSREG_READ(MPIDR) & MPIDR_CPUID_MASK;
#else//ARM架构通过MPIDR(Multiprocessor Affinity Register)寄存器给每个CPU指定一个逻辑地址。
    return 0;
#endif
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

在单CPU多核的情况下,内核是需要安排并记录各任务运行在哪些核心上,ArchCurrCpuid 是获取当前任务运行在具体哪个核上,代码中将宏 ARM_SYSREG_READ(MPIDR) 展开后变成

({                                              
    UINT32 _val;                               
    __asm__ volatile("mrc p15, 0, %0, c0, c0,5" : "=r"(_val));
    _val;                                       
})
1
2
3
4
5
  • __asm__asm 用来声明一个内联汇编表达式。>> 查看内嵌汇编语法
  • __volatile__volatile 是可选的。如果用了它,则是向编译器声明不允许对该内联汇编优化。
  • 其中 %0"=r"(_val) 意思是编译器将选择 R0 寄存器来接收指令结果并将 R0 的值赋给变量 _val ,为什么要这么做呢 ? 因为对协处理器的读写必须通过寄存器,而在C语言层面是不能直接操作寄存器的。
  • _val; 可理解为代码块的 return方式 以便执行接下去的 & MPIDR_CPUID_MASK 操作

DSB | DMB | ISB

内核中经常会出现 DSBDMBISBWFI ,它们有什么含义和作用呢 ? 具体可翻看 ARM 体系参考手册 | DSB on page A8-381

#define DSB     __asm__ volatile("dsb" ::: "memory")
#define DMB     __asm__ volatile("dmb" ::: "memory")
#define ISB     __asm__ volatile("isb" ::: "memory")
#define WFI     __asm__ volatile("wfi" ::: "memory")
#define BARRIER __asm__ volatile("":::"memory") ///< 空指令
#define WFE     __asm__ volatile("wfe" ::: "memory")
#define SEV     __asm__ volatile("sev" ::: "memory")
1
2
3
4
5
6
7

如果没有这些指令的存在会导致系统发生紊乱危象,存在的原因是因为 流水线缓冲区

  • 缓冲区,写缓冲是为了提高存储器的总体访问效率而设的,但它会带出来一个副作用就是同步问题,会导致写内存的指令被延迟几个周期执行,因此对存储器的设置不能即刻生效,这会导致紧临着的下一条指令仍然使用旧的存储器设置——但程序员的本意显然是使用新的存储器设置。这种紊乱危象是后患无穷的,常会破坏未知地址的数据,有时也会产生非法地址访问。
  • 流水线
指令 全称 功能
DMB Data Memory Barrier(DMB)
数据存储器隔离
等待前面访存的指令完成后再执行后面的访存指令 A3.8.3
DSB Data Synchronization Barrier
数据同步隔离
等待所有前面的指令完成后再执行后面的访存指令 A3.8.3
ISB Instruction Synchronization Barrier(ISB)
指令同步隔离
等待流水线中所有指令执行完成后再执行后面的指令 A3.8.3
WFI Wait For Interrupt
等待中断
等待中断,进入休眠模式。 B1.8.14
WFE Wait For Event
等待事件
等待事件,如果没有之前该事件的记录,进入休眠模式;如果有的话,则清除事件锁存并继续执行; B1.8.13
SEV Send Event
发送事件
多处理器环境中向所有的处理器发送事件(包括自身)。 B1.8.13
  • 严格程度 DMB < DSB < ISB
  • ::: "memory" 强制编译器假设 RAM 所有内存单元均被汇编指令修改,这样 cpu 中的寄存器 和 cache 中已缓存的内存单元中的数据将作废。cpu 将不得不在需要的时候重新读取内存中的数据。这就阻止了 cpu 又将 寄存器, cache 中的数据用于去优化指令,而避免去访问内存。

百文说内核 | 抓住主脉络

  • 百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切。
  • 与代码需不断debug一样,文章内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。
  • 百文在 < 鸿蒙研究站 | 开源中国 | 博客园 | 51cto | csdn | 知乎 | 掘金 > 站点发布,百篇博客系列目录如下。

按功能模块:

基础知识 进程管理 任务管理 内存管理
双向链表
内核概念
源码结构
地址空间
计时单位
优雅的宏
钩子框架
位图管理
POSIX
main函数
调度故事
进程控制块
进程空间
线性区
红黑树
进程管理
Fork进程
进程回收
Shell编辑
Shell解析
任务控制块
并发并行
就绪队列
调度机制
任务管理
用栈方式
软件定时器
控制台
远程登录
协议栈
内存规则
物理内存
内存概念
虚实映射
页表管理
静态分配
TLFS算法
内存池管理
原子操作
圆整对齐
通讯机制 文件系统 硬件架构 内核汇编
通讯总览
自旋锁
互斥锁
快锁使用
快锁实现
读写锁
信号量
事件机制
信号生产
信号消费
消息队列
消息封装
消息映射
共享内存
文件概念
文件故事
索引节点
VFS
文件句柄
根文件系统
挂载机制
管道文件
文件映射
写时拷贝
芯片模式
ARM架构
指令集
协处理器
工作模式
寄存器
多核管理
中断概念
中断管理
编码方式
汇编基础
汇编传参
链接脚本
内核启动
进程切换
任务切换
中断切换
异常接管
缺页中断
编译运行 调测工具
编译过程
编译构建
GN语法
忍者无敌
ELF格式
ELF解析
静态链接
重定位
动态链接
进程映像
应用启动
系统调用
VDSO
模块监控
日志跟踪
系统安全
测试用例

百万注源码 | 处处扣细节

  • 百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。

  • < gitee | github | coding | gitcode > 四大码仓推送 | 同步官方源码。

关注不迷路 | 代码即人生

  • 互联网从业十五年,计算机硕士,技术副总裁
  • 关注我,持续更新四十年,即聊技术也聊人生
  • 交有趣靠谱的人;做难而正确的事
  • 不做作,不炒作,只唯真
  • 不唯上,不唯书,只唯实

>> 捐助名单

据说喜欢 点赞 + 分享 的,后来都成了大神。😃