
v49.03 鸿蒙内核源码分析(信号消费) | 谁让CPU连续四次换栈运行 原创 精华
子曰:“回也非助我者也,于吾言无所不说。” 《论语》:先进篇
百篇博客系列篇.本篇为:
v49.xx 鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行
进程管理相关篇为:
- v02.06 鸿蒙内核源码分析(进程管理) | 谁在管理内核资源
- v24.03 鸿蒙内核源码分析(进程概念) | 如何更好的理解进程
- v45.05 鸿蒙内核源码分析(Fork) | 一次调用 两次返回
- v46.05 鸿蒙内核源码分析(特殊进程) | 老鼠生儿会打洞
- v47.02 鸿蒙内核源码分析(进程回收) | 临终托孤的短命娃
- v48.05 鸿蒙内核源码分析(信号生产) | 年过半百 活力十足
- v49.03 鸿蒙内核源码分析(信号消费) | 谁让CPU连续四次换栈运行
- v71.03 鸿蒙内核源码分析(Shell编辑) | 两个任务 三个阶段
- v72.01 鸿蒙内核源码分析(Shell解析) | 应用窥伺内核的窗口
信号消费
本篇为信号消费篇,读之前建议先阅读信号生产篇,信号部分姊妹篇如下:
本篇有相当的难度,涉及用户栈和内核栈的两轮切换,CPU四次换栈,寄存器改值,将围绕下图来说明.
解读
- 为本篇理解方便,把图做简化标签说明:
- user:用户空间
- kernel:内核空间
- source(…):源函数
- sighandle(…):信号处理函数,
- syscall(…):系统调用,参数为系统调用号,如sigreturn,N(表任意)
- user.source():表示在用户空间运行的源函数
- 系列篇已多次说过,用户态的任务有两个运行栈,一个是用户栈,一个是内核栈.栈空间分别来自用户空间和内核空间.两种空间是有严格的地址划分的,通过虚拟地址的大小就能判断出是用户空间还是内核空间.系统调用本质上是软中断,它使CPU执行指令的场地由用户栈变成内核栈.怎么变的并不复杂,就是改变(sp和cpsr寄存器的值).sp指向哪个栈就代表在哪个栈运行, 当cpu在用户栈运行时是不能访问内核空间的,但内核态任务可以访问整个空间,而且内核态任务没有用户栈.
- 理解了上面的说明,再来说下正常系统调用流程是这样的: user.source() -> kernel.syscall(N) - > user.source() ,想要回到user.source()继续运行,就必须保存用户栈现场各寄存器的值.这些值保存在内核栈中,恢复也是从内核栈恢复.
- 信号消费的过程的上图可简化表示为: user.source() -> kernel.syscall(N) ->user.sighandle() ->kernel.syscall(sigreturn) -> user.source() 在原本要回到user.source()的中间插入了信号处理函数的调用. 这正是本篇要通过代码来说清楚的核心问题.
- 顺着这个思路可以推到以下几点,实际也是这么做的:
-
kernel.syscall(N) 中必须要再次保存user.source()的上下文
sig_switch_context
,为何已经保存了一次还要再保存一次? -
因为第一次是保存在内核栈中,而内核栈这部分数据会因回到用户态user.sighandle()运行而被恢复现场出栈了.保存现场/恢复现场是成双出队的好基友,注意有些文章说会把整个内核栈清空,这是不对的.
-
第二次保存在任务结构体中,任务来源于任务池,是内核全局变量,常驻内存的.两次保存的都是user.source()运行时现场信息,再回顾下相关的结构体.关键是
sig_switch_context
-
还必须要改变原有PC/R0/R1寄存器的值.想要执行user.sighandle(),PC寄存器就必须指向它,而R0,R1就是它的参数.
-
信号处理完成后须回到内核态,怎么再次陷入内核态? 答案是:
__NR_sigreturn
,这也是个系统调用.回来后还原sig_switch_context
,即还原user.source()被打断时SP/PC等寄存器的值,使其跳回到用户栈从user.source()的被打断处继续执行.
-
- 有了这三个推论,再理解下面的代码就是吹灰之力了,涉及三个关键函数
OsArmA32SyscallHandle
,OsSaveSignalContext
,OsRestorSignalContext
本篇一一解读,彻底挖透.先看信号上下文结构体sig_switch_context
.
sig_switch_context
- 保存user.source()现场的结构体,
USP
,ULR
代表用户栈指针和返回地址. CPSR
寄存器用于设置CPU的工作模式,CPU有7种工作模式,具体可前往翻看 v36.xx (工作模式篇)谈论的用户态(usr
普通用户)和内核态(sys
超级用户)对应的只是其中的两种.二者都共用相同的寄存器.还原它就是告诉CPU内核已切到普通用户模式运行.- 其他寄存器没有保存的原因是系统调用不会用到它们,所以不需要保存.
R7
是在系统调用发生时用于记录系统调用号,在信号处理过程中,R0将获得信号编号,作为user.sighandle()的第一个参数.count
记录是否保存了信号上下文
OsArmA32SyscallHandle 系统调用总入口
解读
- 这是系统调用的总入口,所有的系统调用都要跑这里要统一处理.通过系统号(保存在R7),找到注册函数并回调.完成系统调用过程.
- 关于系统调用可查看
v37.xx (系统调用篇) | 系统调用到底经历了什么
本篇不详细说系统调用过程,只说跟信号相关的部分. OsArmA32SyscallHandle
总体理解起来是被信号的保存和还原两个函数给包夹了.注意要在运行过程中去理解调用两个函数的过程,对于同一个任务来说,一定是先执行OsSaveSignalContext
,第二次进入OsArmA32SyscallHandle
后再执行OsRestorSignalContext
.- 看
OsSaveSignalContext
,由它负责保存user.source() 的上下文,其中改变了sp,r0/r1寄存器值,切到信号处理函数user.sighandle()运行. - 在函数的开头,碰到系统调用号
__NR_sigreturn
,直接恢复信号上下文就退出了,因为这是要切回user.source()继续运行的操作.
OsSaveSignalContext 保存信号上下文
有了上面的铺垫,就不难理解这个函数的作用.
解读
-
先是判断执行条件,确实是有信号需要处理,有处理函数.自定义处理函数是由用户进程安装进来的,所有进程旗下的任务都共用,参数就是信号
signo
,注意可不是系统调用号,有区别的.信号编号长这样.系统调用号长这样,是不是看到一些很熟悉的函数.
-
最后是最最最关键的代码,改变pc寄存器的值,此值一变,在
_osExceptSwiHdl
中恢复上下文后,cpu跳到用户空间的代码段 user.sighandle(R0,R1) 开始执行,即执行信号处理函数.
OsRestorSignalContext 恢复信号上下文
解读
-
在信号处理函数完成之后,内核会触发一个
__NR_sigreturn
的系统调用,又陷入内核态,回到了OsArmA32SyscallHandle
. -
恢复的过程很简单,把之前保存的信号上下文恢复到内核栈sp开始位置,数据在栈中的保存顺序可查看 用栈方式篇 ,最重要的看这几句.
注意这里还不是真正的切换上下文,只是改变内核栈中现有的数据.这些数据将还原给寄存器.
USP
和ULR
指向的是用户栈的位置.一旦PC
,USP
,ULR
从栈中弹出赋给寄存器.才真正完成了内核栈到用户栈的切换.回到了user.source()继续运行. -
真正的切换汇编代码如下,都已添加注释,在保存和恢复上下文中夹着
OsArmA32SyscallHandle
百篇博客分析.深挖内核地基
- 给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 😛
- 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。
按功能模块:
- 前因后果 >> 总目录 | 调度故事 | 内存主奴 | 源码注释 | 源码结构 | 静态站点 |
- 基础工具 >> 双向链表 | 位图管理 | 用栈方式 | 定时器 | 原子操作 | 时间管理 |
- 加载运行 >> ELF格式 | ELF解析 | 静态链接 | 重定位 | 进程映像 |
- 进程管理 >> 进程管理 | 进程概念 | Fork | 特殊进程 | 进程回收 | 信号生产 | 信号消费 | Shell编辑 | Shell解析 |
- 编译构建 >> 编译环境 | 编译过程 | 环境脚本 | 构建工具 | gn应用 | 忍者ninja |
- 进程通讯 >> 自旋锁 | 互斥锁 | 进程通讯 | 信号量 | 事件控制 | 消息队列 |
- 内存管理 >> 内存分配 | 内存管理 | 内存汇编 | 内存映射 | 内存规则 | 物理内存 |
- 任务管理 >> 时钟任务 | 任务调度 | 任务管理 | 调度队列 | 调度机制 | 线程概念 | 并发并行 | CPU | 系统调用 | 任务切换 |
- 文件系统 >> 文件概念 | 文件系统 | 索引节点 | 挂载目录 | 根文件系统 | 字符设备 | VFS | 文件句柄 | 管道文件 |
- 硬件架构 >> 汇编基础 | 汇编传参 | 工作模式 | 寄存器 | 异常接管 | 汇编汇总 | 中断切换 | 中断概念 | 中断管理 |
百万汉字注解.精读内核源码
四大码仓中文注解 . 定期同步官方代码
鸿蒙研究站( weharmonyos ) | 每天死磕一点点,原创不易,欢迎转载,请注明出处。若能支持点赞则更佳,感谢每一份支持。
