首页 文章 精选 留言 我的

精选列表

搜索[harmonyos开发],共10000篇文章
优秀的个人博客,低调大师

鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙看这篇或许真的够了 | 百篇博客分析HarmonyOS源码 | v50.01

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | 51cto | csdn | harmony > 编译鸿蒙 因对sourceinsight的爱不释手,所以选择了在windows环境下编译鸿蒙. 本篇记录下编译的过程,以备后续不用再去一大堆无效的误导式软文中搜寻芝麻大点有用的信息,那样真挺费时的. 编译环境 先安装 Docker Desktop 下载windows版本一直下一步. 在windows下拉取openharmony-docker官方镜像,Docker方式获取编译环境 强烈推荐这么做. docker pull swr.cn-south-1.myhuaweicloud.com/openharmony-docker/openharmony-docker:0.0.3 2.36G, 拉取看网速, 大概10分钟后成功了,有了镜像 PS E:\harmony\kernel_liteos_a_note> docker images REPOSITORY TAG IMAGE ID CREATED SIZE swr.cn-south-1.myhuaweicloud.com/openharmony-docker/openharmony-docker 0.0.3 50d0aa6ea9ba 2 weeks ago 2.36GB vscode对docker的管理插件非常的强大,管理镜像和容器的工作就交给它了. 启动docker,创建好容器,本文的选择是这样的,当然大家可以灵活处理,命名. 容器创建成功后可以inspect查看到绑定的目录. "HostConfig": { "Binds": [ "E:\\harmony\\code-1.0:/home/harmony", "E:\\harmony\\docker:/home/docker" ], 本文这样做的目的为了在windows上能方便的查看文件.harmony目录用于下载编译源码目录,另外一个docker放其他无关文件 repo方式下载源码 root@5e3abe332c5a:/home/harmony#repo init -u https://gitee.com/openharmony/manifest.git -b master --no-repo-verify root@5e3abe332c5a:/home/harmony#repo sync -c 这个过程也是比网速.慢慢等吧. 下载完成之后的样子 root@5e3abe332c5a:/home/harmony# ls applications base build build.py developtools device docs domains drivers foundation kernel ohos_config.json prebuilts test third_party utils vendor 编译过程 在源码的根目录执行如下命令安装hb root@5e3abe332c5a:/home/harmony#python3 -m pip install --user build/lite 设置编译路径,选择当前路径 root@5e3abe332c5a:/home/harmony#hb set [OHOS INFO] Input code path: . OHOS Which product do you need? (Use arrow keys) hisilicon ❯ ipcamera_hispark_aries wifiiot_hispark_pegasus ipcamera_hispark_taurus 直接回车,代表选择了ipcamera_hispark_aries,这三个对应平台的关系如下 Hi3518:ipcamera_hispark_aries@hisilicon Hi3861:wifiiot_hispark_pegasus@hisilicon Hi3516:ipcamera_hispark_taurus@hisilicon 执行编译,过程大概20分钟 root@5e3abe332c5a:/home/harmony#hb build -f 查看编译结果 每个的目录含义如下 目录名 描述 applications 应用程序样例,包括wifi-iot,camera等 base 基础软件服务子系统集&硬件服务子系统集 build 组件化编译、构建和配置脚本 docs 说明文档 domains 增强软件服务子系统集 drivers 驱动子系统 foundation 系统基础能力子系统集 kernel 内核子系统 prebuilts 编译器及工具链子系统 test 测试子系统 third_party 开源第三方组件 utils 常用的工具集 vendor 厂商提供的软件 build.py 编译脚本文件 out 编译后生成 编译输出 out为编译结果输出目录 out/hispark_aries/ipcamera_hispark_aries root@5e3abe332c5a:/home/harmony/out/hispark_aries/ipcamera_hispark_aries# ls args.gn build.log bundle_daemon_tool.map dev_tools libs NOTICE_FILE OHOS_Image.asm rootfs suites toggleButtonTest.map userfs vendor bin build.ninja config etc liteos.bin obj OHOS_Image.bin rootfs_jffs2.img test toolchain.ninja userfs_jffs2.img bm_tool.map build.ninja.d data foundation.map media_server.map OHOS_Image OHOS_Image.map server.map test_info unstripped usr 系列篇会详细讲解启动过程,此处进入bin目录瞅瞅都有些啥好宝贝. root@5e3abe332c5a:/home/harmony/out/hispark_aries/ipcamera_hispark_aries/bin# ls ai_server module_ActsAbilityMgrTest.bin module_ActsGraphVersionTest.bin module_ActsJFFS2CapabilityTest.bin module_ActsNFSTest.bin module_ActsSurfaceTest.bin os_dump apphilogcat module_ActsBootstrapTest.bin module_ActsHeapBaseTest.bin module_ActsJFFS2DACTest.bin module_ActsParameterTest.bin module_ActsSysApiTest.bin query.bin appspawn module_ActsBundleMgrTest.bin module_ActsHilogTest.bin module_ActsJFFS2Test.bin module_ActsPMSTest.bin module_ActsTimeApiTest.bin shell bundle_daemon module_ActsColorTest.bin module_ActsIoApiTest.bin module_ActsKvStoreTest.bin module_ActsProcessApiTest.bin module_ActsTransformTest.bin tftp CalcSubTest.bin module_ActsDyloadTest.bin module_ActsIpcMqTest.bin module_ActsListTest.bin module_ActsRectTest.bin module_ActsUiInterfaceTest1.bin wms_server foundation module_ActsFutexApiTest.bin module_ActsIpcPipeTest.bin module_ActsLwipTest.bin module_ActsSamgrTest.bin module_ActsUtilApiTest.bin hilogcat module_ActsGeometyr2dTest.bin module_ActsIpcSemTest.bin module_ActsMathApiTest.bin module_ActsSchedApiTest.bin module_ActsVFATCapabilityTest.bin init module_ActsGraphicMathTest.bin module_ActsIpcShmTest.bin module_ActsMemApiTest.bin module_ActsSecurityDataTest.bin module_ActsVFATDACTest.bin media_server module_ActsGraphMemApiTest.bin module_ActsIpcSignalTest.bin module_ActsNetTest.bin module_ActsSoftBusTest.bin module_ActsVFATTest.bin 这难道都是ELF格式可执行程序? 用readelf命令试下shell就知道了.果然是shell程序,加载它将创建激动人心的shell进程 root@5e3abe332c5a:/home/harmony/out/hispark_aries/ipcamera_hispark_aries/bin# readelf -h shell ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: ARM Version: 0x1 Entry point address: 0x1000 Start of program headers: 52 (bytes into file) Start of section headers: 25268 (bytes into file) Flags: 0x5000200, Version5 EABI, soft-float ABI Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 11 Size of section headers: 40 (bytes) Number of section headers: 27 Section header string table index: 26 再随便选择一个module_ActsListTest.bin看下,这些是鸿蒙用于测试的代码生成的.也是一个个的独立程序.可以在工程里找到他们的身影list_test.cpp,ActsListTest.json等文件 root@5e3abe332c5a:/home/harmony/out/hispark_aries/ipcamera_hispark_aries/bin# readelf -h module_ActsListTest.bin ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: ARM Version: 0x1 Entry point address: 0xb000 Start of program headers: 52 (bytes into file) Start of section headers: 172256 (bytes into file) Flags: 0x5000200, Version5 EABI, soft-float ABI Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 11 Size of section headers: 40 (bytes) Number of section headers: 27 Section header string table index: 26 解读 仔细比较下这两个ELF的头文件有哪里是不一样的 Entry point address: 0x1000 Entry point address: 0xb000 这是程序的入口地址,也就是大家熟悉的main()函数的地址,用户程序是从这个位置开始执行. readelf的功能很强大,有兴趣的可以玩下这个命令,看看elf里面究竟装的啥. root@5e3abe332c5a:/home/harmony/out/hispark_aries/ipcamera_hispark_aries/bin# readelf -help readelf: option requires an argument -- 'p' Usage: readelf <option(s)> elf-file(s) Display information about the contents of ELF format files Options are: -a --all Equivalent to: -h -l -S -s -r -d -V -A -I -h --file-header Display the ELF file header -l --program-headers Display the program headers --segments An alias for --program-headers -S --section-headers Display the sections' header --sections An alias for --section-headers -g --section-groups Display the section groups -t --section-details Display the section details -e --headers Equivalent to: -h -l -S -s --syms Display the symbol table --symbols An alias for --syms --dyn-syms Display the dynamic symbol table -n --notes Display the core notes (if present) -r --relocs Display the relocations (if present) -u --unwind Display the unwind info (if present) -d --dynamic Display the dynamic section (if present) -V --version-info Display the version sections (if present) -A --arch-specific Display architecture specific information (if any) -c --archive-index Display the symbol/file index in an archive -D --use-dynamic Use the dynamic section info when displaying symbols -x --hex-dump=<number|name> Dump the contents of section <number|name> as bytes -p --string-dump=<number|name> Dump the contents of section <number|name> as strings -R --relocated-dump=<number|name> Dump the contents of section <number|name> as relocated bytes -z --decompress Decompress section before dumping it -w[lLiaprmfFsoRtUuTgAckK] or --debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames, =frames-interp,=str,=loc,=Ranges,=pubtypes, =gdb_index,=trace_info,=trace_abbrev,=trace_aranges, =addr,=cu_index,=links,=follow-links] Display the contents of DWARF debug sections --dwarf-depth=N Do not display DIEs at depth N or greater --dwarf-start=N Display DIEs starting with N, at the same depth or deeper --ctf=<number|name> Display CTF info from section <number|name> --ctf-parent=<number|name> Use section <number|name> as the CTF parent --ctf-symbols=<number|name> Use section <number|name> as the CTF external symtab --ctf-strings=<number|name> Use section <number|name> as the CTF external strtab -I --histogram Display histogram of bucket list lengths -W --wide Allow output width to exceed 80 characters @<file> Read options from <file> -H --help Display this information -v --version Display the version number of readelf 具体的加载过程和elf格式后续有专门的篇幅详细介绍,此处不做说明. 用户进程 去掉bin目录下的干扰*.bin,剩下的就是平台(Hi3518)需要内核创建的用户态进程. 进程ID 进程名称 进程优先级 说明 备注 1 init 28 用户态祖宗进程 ai_server AI业务子系统 https://gitee.com/openharmony/ai_engine media_server 播放模块框架实现 https://gitee.com/openharmony/multimedia_media_lite 3 shell 9 用于窥视内核的窗口 apphilogcat 提供DFX子系统在轻量系统和小型系统的流水日志功能 https://gitee.com/openharmony/hiviewdfx_hilog_lite tftp appspawn 应用孵化模块appspawn进程 https://gitee.com/openharmony/startup_appspawn_lite wms_server 窗口管理服务 https://gitee.com/openharmony/graphic_wms bundle_daemon 用户程序框架内部使用的工具接口 https://gitee.com/openharmony/appexecfwk_appexecfwk_lite foundation foundation系统进程 https://gitee.com/openharmony/distributedschedule_safwk_lite hilogcat os_dump 在整个项目工程中能轻易找到他们的入口函数.比如appspawn的启动过程 //base\startup\appspawn_lite\services\src\main.c int main(int argc, char * const argv[]) { sleep(1); HILOG_INFO(HILOG_MODULE_HIVIEW, "[appspawn] main, enter."); // 1. ipc module init HOS_SystemInit(); // 2. register signal for SIGCHLD SignalRegist(); // 3. keep process alive HILOG_INFO(HILOG_MODULE_HIVIEW, "[appspawn] main, entering wait."); while (1) { // pause only returns when a signal was caught and the signal-catching function returned. // pause only returns -1, no need to process the return value. (void)pause(); } } 鸿蒙将服务做成了组件,为最上层的应用程度提供管理/工具类的服务.但这些都是framework层的工作,超出了内核源码分析的范畴.希望后续有机会能去剖析它们. 鸿蒙源码百篇博客 往期回顾 在给 鸿蒙内核源码加中文注释 过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感.百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.:P 写文章比写代码累多了,越深入研究,越觉得没写好,所以文章和注解会反复修正, .xx代表修改的次数, 将持续完善源码注解和文档内容,精雕细琢,言简意赅, 尽全力打磨精品内容. v50.xx (编译环境篇) | 编译鸿蒙看这篇或许真的够了 < csdn | 51cto | harmony > v49.xx (信号消费篇) | 用户栈到内核栈的两次切换 < csdn | 51cto | harmony > v48.xx (信号生产篇) | 如何安装和发送信号? < csdn | 51cto | harmony > v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | harmony > v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | harmony > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 ? < csdn | 51cto | harmony > v44.xx (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | 51cto | harmony > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | harmony > v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | 51cto | harmony > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | harmony > v40.xx (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | 51cto | harmony > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | harmony > v38.xx (寄存器篇) | arm所有寄存器一网打尽,不再神秘 < csdn | 51cto | harmony > v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | harmony > v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆? < csdn | 51cto | harmony > v35.xx (时间管理篇) | tick是操作系统的基本时间单位 < csdn | 51cto | harmony > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | 51cto | harmony > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | 51cto | harmony > v32.xx (cpu篇) | 整个内核就是一个死循环 < csdn | 51cto | harmony > v31.xx (定时器篇) | 内核最高优先级任务是谁? < csdn | 51cto | harmony > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | harmony > v29.xx (信号量篇) | 信号量解决任务同步问题 < csdn | 51cto | harmony > v28.xx (进程通讯篇) | 九种进程间通讯方式速揽 < csdn | 51cto | harmony > v27.xx (互斥锁篇) | 比自旋锁丰满许多的互斥锁 < csdn | 51cto | harmony > v26.xx (自旋锁篇) | 自旋锁当立贞节牌坊! < csdn | 51cto | harmony > v25.xx (并发并行篇) | 怎么记住并发并行的区别? < csdn | 51cto | harmony > v24.xx (进程概念篇) | 进程在管理哪些资源? < csdn | 51cto | harmony > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | 51cto | harmony > v22.xx (汇编基础篇) | cpu在哪里打卡上班? < csdn | 51cto | harmony > v21.xx (线程概念篇) | 是谁在不断的折腾cpu? < csdn | 51cto | harmony > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | harmony > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | 51cto | harmony > v18.xx (源码结构篇) | 梳理内核源文件的作用和含义 < csdn | 51cto | harmony > v17.xx (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | 51cto | harmony > v16.xx (内存规则篇) | 内存管理到底在管什么? < csdn | 51cto | harmony > v15.xx (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | 51cto | harmony > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | 51cto | harmony > v13.xx (源码注释篇) | 鸿蒙必须成功,也必然成功 < csdn | 51cto | harmony > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | 51cto | harmony > v11.xx (内存分配篇) | 内存有哪些分配方式? < csdn | 51cto | harmony > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | 51cto | harmony > v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | 51cto | harmony > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | harmony > v07.xx (调度机制篇) | 任务是如何被调度执行的? < csdn | 51cto | harmony > v06.xx (调度队列篇) | 内核有多少个调度队列? < csdn | 51cto | harmony > v05.xx (任务管理篇) | 任务池是如何管理的? < csdn | 51cto | harmony > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | harmony > v03.xx (时钟任务篇) | 调度最大的源动力来自哪里? < csdn | 51cto | harmony > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | harmony > v01.xx (双向链表篇) | 谁是内核最重要结构体? < csdn | 51cto | harmony > 进入 >> oschina | csdn | 51cto | 掘金 | harmony 参与贡献 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请「点赞+关注+收藏」 各大站点搜 「鸿蒙内核源码分析」.欢迎转载,请注明出处.

优秀的个人博客,低调大师

鸿蒙内核源码分析(汇编汇总篇) | 鸿蒙所有的汇编代码都在这里 | 中文注解HarmonyOS源码 | v40.01

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony > 汇编其实很可爱 绝大部分IT从业人员终生不用触碰到的汇编,它听着像上古时代遥远的呼唤,总觉得远却又能听到声,汇编再往下就真的是01110011了,汇编指令基本是一一对应了机器指令. 所谓内核是对硬件的驱动,对驱动之后资源的良序管理,这里说的资源是CPU(单核/多核),内存,磁盘,i/o设备.层层封装,步步遮蔽,到了应用层,不知有汉,无论魏晋才好.好是好,但有句话,其实哪有什么岁月静好,只是有人替你负重前行.难道你真的不想知道别人是怎么替你负重前行的? 越高级的语言是越接近人思维模式的,越低级的语言就是越贴近逻辑与非门的高低电平的起伏.汇编是贴着硬件飞行的,要研究内核就绕不过汇编,觉得神秘是来源于不了解,恐惧是来自于没接近. 其实当你深入分析内核源码之后就会发现,汇编其实很可爱,很容易,比c/c++/java容易太多了,真的是很傻很单纯. 鸿蒙内核源码分析系列篇至少已经有五篇涉及到了汇编,请自行翻看,但还是远远不够,要写十五篇,彻底摸透,现在才刚刚开始,本篇先整理鸿蒙内核所有汇编文件和大概说明文件的作用,后续一块一块来剥,不把这些汇编剥个精光不罢休. 汇编目录 鸿蒙所有汇编文件如下: 直接点击可以查看注解源码,有些站点会把链接去除,没办法,可直接去各大站点搜"鸿蒙内核源码分析",找到源码注解. \arch\arm\arm\src startup 启动相关 reset_vector_mp.S 多核CPU下启动代码,大文件 reset_vector_up.S 单核CPU下启动代码,大文件 armv7a cache.S 缓存相关的两个函数 los_dispatch.S 异常分发处理,大文件. los_hw_exc.S 硬件异常相关,大文件. los_hw_runstop.S OsSRSaveRegister 和 OsSRRestoreRegister 汇编实现 jmp.S 两个简单的跳转函数 hw_user_get.S 拷贝用户空间数据到内核空间 hw_user_put.S 拷贝内核空间数据到用户空间 hw_user_get.S 将用户空间数据src 拷贝到内核空间 dst // errno_t _arm_get_user(void *dst, const void *src, size_t dstTypeLen, size_t srcTypeLen) FUNCTION(_arm_get_user) stmdb sp!, {r0, r1, r2, r3, lr} @四个参数入栈,保存LR cmp r2, #0 @r2 和 0比较 beq .Lget_user_return @相等 跳到Lget_user_return 直接返回 cmp r2, r3 @r2 和 r3比较 bne .Lget_user_err @不等,说明函数要返回错误 cmp r2, #1 @r2 和 1比较 bhi .Lget_user_half @if(dstTypeLen>1) 跳转到Lget_user_half .Lget_user_byte: @按字节拷贝数据 0: ldrbt r3, [r1], #0 @r3=*r1 1: strb r3, [r0], #0 @*r0=r3 b .Lget_user_return .Lget_user_half: cmp r2, #2 @r2 和 2比较 bhi .Lget_user_word @if(dstTypeLen>2) Lget_user_word 2: ldrht r3, [r1], #0 @完成最后一个字节的拷贝 3: strh r3, [r0], #0 @完成最后一个字节的拷贝 b .Lget_user_return .Lget_user_word: cmp r2, #4 @r2 和 4比较 bhi .Lget_user_err @if(dstTypeLen>4) 跳转到Lget_user_err 4: ldrt r3, [r1], #0 5: str r3, [r0], #0 .Lget_user_return: @返回锚点 ldmia sp!, {r0, r1, r2, r3, lr} @保存的内容出栈,恢复各寄存器值 mov r0, 0 @r0保存返回值为0 bx lr @跳回调用函数继续执行,_arm_get_user到此结束! .Lget_user_err: ldmia sp!, {r0, r1, r2, r3, lr} @保存的内容出栈,恢复各寄存器值 mov r0, #-14 @r0保存返回值为-14 bx lr @跳回调用函数继续执行,_arm_get_user到此结束! .pushsection __exc_table, "a" .long 0b, .Lget_user_err .long 1b, .Lget_user_err .long 2b, .Lget_user_err .long 3b, .Lget_user_err .long 4b, .Lget_user_err .long 5b, .Lget_user_err .popsection hw_user_put.S 将内核空间数据src 拷贝到用户空间 dst // errno_t _arm_put_user(void *dst, const void *src, size_t dstTypeLen, size_t srcTypeLen) FUNCTION(_arm_put_user) stmdb sp!, {r0, r1, r2, r3, lr} cmp r2, #0 beq .Lget_user_return cmp r2, r3 bne .Lget_user_err cmp r2, #1 bhi .Lget_user_half .Lget_user_byte: 0: ldrb r3, [r1], #0 1: strbt r3, [r0], #0 b .Lget_user_return .Lget_user_half: cmp r2, #2 bhi .Lget_user_word 2: ldrh r3, [r1], #0 3: strht r3, [r0], #0 b .Lget_user_return .Lget_user_word: cmp r2, #4 bhi .Lget_user_err 4: ldr r3, [r1], #0 5: strt r3, [r0], #0 .Lget_user_return: ldmia sp!, {r0, r1, r2, r3, lr} mov r0, 0 bx lr .Lget_user_err: ldmia sp!, {r0, r1, r2, r3, lr} mov r0, #-14 bx lr .pushsection __exc_table, "a" .long 0b, .Lget_user_err .long 1b, .Lget_user_err .long 2b, .Lget_user_err .long 3b, .Lget_user_err .long 4b, .Lget_user_err .long 5b, .Lget_user_err .popsection 解读 如果仔细对比一下发现这两个函数的汇编代码是一模一样的,没有区别.这就跟让左右各一个美女陪你和左右各一个丑姑娘陪你的道理是一样,都是1+1=2,算式加法的逻辑是一样的,不会变.但给你的感觉能一样嘛,美丑的含义是上层赋予的,到了这里美丑不重要,都变成了 r0,r1,r2,r3了, 跟咱东哥一样脸盲分不清啦 用户空间和内核空间的数据为什么需要拷贝? 这是个经典问题,看了网上的一些回答,没毛病: 内核不能信任任何用户空间的指针。必须对用户空间的指针指向的数据进行验证。如果只做验证不做拷贝的话,那么在随后的运行中要随时受到其它进/线程可能修改用户空间数据的威胁。所以必须做拷贝。 在内存系列篇中已经反复的说过,每个用户进程都有自己独立的用户空间,但这个用户空间是通过MMU映射出来的,是表面上繁花似锦,背后都共用着真正的物理内存,所以在高频率的任务切换过程中,原有的用户空间地址内容很容易被覆盖掉.举个例子说明下: 用户A客户有个美女放在万聪酒店21号房说要献给内核大佬,如果内核不直接把美女接回家,而仅仅是做个记录,写着美女在万聪酒店21号房,内核立马跑去过还好不会错,但如果被其他事给耽搁了呢? 耽搁的这回功夫,调度算法把万聪酒店21号房给了B客户使用,当然B客户用之前酒店管理人员会把美女置换个地方(以至于A客户再回到酒店时,原来的东西该怎样还咋样). 等21号房空出来了,B肯定不知道原来的房间是A在用,而且里面还有个美女,更不可能晓得美女献给内核大佬了.因为B的业务需要,很可能往21号房整了个东施进来. 此时如果内核大佬事忙完了,想起A客户献美女的事了,是时候了.因为只记录了地址,直接去万聪酒店21号房抓人,那抓出来可是东施呀.这可不把事给搞砸啦. 所以需要拷贝,直接把美女接回家找个地方关起来先. reset_vector_mp.S 和 reset_vector_up.S 鸿蒙开机代码根据 CPU多核还是单核分成了两个独立文件处理. mp就是多处理器(multiprocessing)的意思: 多CPU核的操作系统3种处理模式(SMP+AMP+BMP) 鸿蒙实现的是 SMP 的方式 非对称多处理(Asymmetric multiprocessing,AMP)每个CPU内核 运行一个独立的操作系统或同一操作系统的独立实例(instantiation)。 对称多处理(Symmetric multiprocessing,SMP)一个操作系统的实例 可以同时管理所有CPU内核,且应用并不绑定某一个内核。 混合多处理(Bound multiprocessing,BMP)一个操作系统的实例可以 同时管理所有CPU内核,但每个应用被锁定于某个指定的核心。 up(unit processing )的意思,单个CPU,虽然没mp的复杂,但文件也很大 500行汇编,一小节讲不完,需要单独的一篇专讲 reset_vector 这里只列出up情况下的开机代码 reset_vector: @鸿蒙单核cpu 开机代码 /* do some early cpu setup: i/d cache disable, mmu disabled */ mrc p15, 0, r0, c1, c0, 0 bic r0, #(1<<12) bic r0, #(1<<2 | 1<<0) mcr p15, 0, r0, c1, c0, 0 /* r11: delta of physical address and virtual address */ adr r11, pa_va_offset ldr r0, [r11] sub r11, r11, r0 /* if we need to relocate to proper location or not */ adr r4, __exception_handlers /* r4: base of load address */ ldr r5, =SYS_MEM_BASE /* r5: base of physical address */ subs r12, r4, r5 /* r12: delta of load address and physical address */ beq reloc_img_to_bottom_done /* if we load image at the bottom of physical address */ /* we need to relocate image at the bottom of physical address */ ldr r7, =__exception_handlers /* r7: base of linked address (or vm address) */ ldr r6, =__bss_start /* r6: end of linked address (or vm address) */ sub r6, r7 /* r6: delta of linked address (or vm address) */ add r6, r4 /* r6: end of load address */ los_dispatch.S 和 los_hw_exc.S 异常模式处理入口和统一分发现实,之前也有提到过,很复杂,1000多行,后续单独细说实现过程. jmp.S 两个简单的函数longjmp setjmp 的实现,加注解部分请前往 鸿蒙内核源码注解分析 查看 FUNCTION(longjmp) ldmfd r0,{r4-r12} add r0,#(4 * 9) ldr r13,[r0] add r0,#4 ldr r14,[r0] cmp r1,#0 moveq r1,#1 mov r0,r1 mov pc,lr FUNCTION(setjmp) stmea r0,{r4-r12} add r0,#(4 * 9) str r13,[r0] add r0,#4 str r14,[r0] mov r0,#0 mov pc,lr los_hw_runstop.S .global OsSRSaveRegister .global OsSRRestoreRegister 两个函数的汇编现实,有点复杂,后续单独说明. cache.S 这是缓存部分的两个函数实现,此处没有加注解,试着看明白这两个函数的实现.加注解部分请前往 鸿蒙内核源码注解分析 查看 .macro DCACHE_LINE_SIZE, reg, tmp mrc p15, 0, \tmp, c0, c0, 1 lsr \tmp, \tmp, #16 and \tmp, \tmp, #0xf mov \reg, #4 mov \reg, \reg, lsl \tmp .endm FUNCTION(arm_inv_cache_range) push {r2, r3} DCACHE_LINE_SIZE r2, r3 sub r3, r2, #1 tst r0, r3 bic r0, r0, r3 mcrne p15, 0, r0, c7, c14, 1 tst r1, r3 bic r1, r1, r3 mcrne p15, 0, r1, c7, c14, 1 1: mcr p15, 0, r0, c7, c6, 1 add r0, r0, r2 cmp r0, r1 blo 1b dsb pop {r2, r3} mov pc, lr FUNCTION(arm_clean_cache_range) push {r2, r3} DCACHE_LINE_SIZE r2, r3 sub r3, r2, #1 bic r0, r0, r3 1: mcr p15, 0, r0, c7, c10, 1 add r0, r0, r2 cmp r0, r1 blo 1b dsb pop {r2, r3} mov pc, lr 参与贡献 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 欢迎转载,请注明出处,公众号转载申请方式: 关注后直接回复您的公众号名称即可. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony >

优秀的个人博客,低调大师

鸿蒙内核源码分析(汇编汇总篇) | 鸿蒙所有的汇编代码都在这里 | 中文注解HarmonyOS源码 | v40.02

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | csdn | weharmony > 汇编其实很可爱 绝大部分IT从业人员终生不用触碰到的汇编,它听着像上古时代遥远的呼唤,总觉得远却又能听到声,汇编再往下就真的是01110011了,汇编指令基本是一一对应了机器指令. 所谓内核是对硬件的驱动,对驱动之后资源的良序管理,这里说的资源是CPU(单核/多核),内存,磁盘,i/o设备.层层封装,步步遮蔽,到了应用层,不知有汉,无论魏晋才好.好是好,但有句话,其实哪有什么岁月静好,只是有人替你负重前行.难道就不想知道别人是怎么负重前行的? 越高级的语言是越接近人思维模式的,越低级的语言就是越贴近逻辑与非门的高低电平的起伏.汇编是贴着硬件飞行的,要研究内核就绕不过汇编,觉得神秘是来源于不了解,恐惧是来自于没接近. 其实深入分析内核源码之后就会发现,汇编其实很可爱,很容易,比c/c++/java容易太多了,真的是很傻很单纯. 鸿蒙内核源码分析系列篇至少已经有五篇涉及到了汇编,请自行翻看,但还是远远不够,要写十五篇,彻底摸透,现在才刚刚开始,本篇先整理鸿蒙内核所有汇编文件和大概说明文件的作用,后续一块一块来剥,不把这些汇编剥个精光不罢休. 汇编目录 鸿蒙所有汇编文件如下: 直接点击可以查看注解源码,有些站点会把链接去除,没办法,可直接去各大站点搜"鸿蒙内核源码分析",找到源码注解. \arch\arm\arm\src startup 启动相关 reset_vector_mp.S 多核CPU下启动代码,大文件 reset_vector_up.S 单核CPU下启动代码,大文件 armv7a cache.S 缓存相关的两个函数 los_dispatch.S 异常分发处理,大文件. los_hw_exc.S 硬件异常相关,大文件. los_hw_runstop.S OsSRSaveRegister 和 OsSRRestoreRegister 汇编实现 jmp.S 两个简单的跳转函数 hw_user_get.S 拷贝用户空间数据到内核空间 hw_user_put.S 拷贝内核空间数据到用户空间 hw_user_get.S 将用户空间数据src 拷贝到内核空间 dst // errno_t _arm_get_user(void *dst, const void *src, size_t dstTypeLen, size_t srcTypeLen) FUNCTION(_arm_get_user) stmdb sp!, {r0, r1, r2, r3, lr} @四个参数入栈,保存LR cmp r2, #0 @r2 和 0比较 beq .Lget_user_return @相等 跳到Lget_user_return 直接返回 cmp r2, r3 @r2 和 r3比较 bne .Lget_user_err @不等,说明函数要返回错误 cmp r2, #1 @r2 和 1比较 bhi .Lget_user_half @if(dstTypeLen>1) 跳转到Lget_user_half .Lget_user_byte: @按字节拷贝数据 0: ldrbt r3, [r1], #0 @r3=*r1 1: strb r3, [r0], #0 @*r0=r3 b .Lget_user_return .Lget_user_half: cmp r2, #2 @r2 和 2比较 bhi .Lget_user_word @if(dstTypeLen>2) Lget_user_word 2: ldrht r3, [r1], #0 @完成最后一个字节的拷贝 3: strh r3, [r0], #0 @完成最后一个字节的拷贝 b .Lget_user_return .Lget_user_word: cmp r2, #4 @r2 和 4比较 bhi .Lget_user_err @if(dstTypeLen>4) 跳转到Lget_user_err 4: ldrt r3, [r1], #0 5: str r3, [r0], #0 .Lget_user_return: @返回锚点 ldmia sp!, {r0, r1, r2, r3, lr} @保存的内容出栈,恢复各寄存器值 mov r0, 0 @r0保存返回值为0 bx lr @跳回调用函数继续执行,_arm_get_user到此结束! .Lget_user_err: ldmia sp!, {r0, r1, r2, r3, lr} @保存的内容出栈,恢复各寄存器值 mov r0, #-14 @r0保存返回值为-14 bx lr @跳回调用函数继续执行,_arm_get_user到此结束! .pushsection __exc_table, "a" .long 0b, .Lget_user_err .long 1b, .Lget_user_err .long 2b, .Lget_user_err .long 3b, .Lget_user_err .long 4b, .Lget_user_err .long 5b, .Lget_user_err .popsection 解读 用户空间和内核空间的数据为什么需要拷贝? 这是个经典问题,看了网上的一些回答,没毛病: 内核不能信任任何用户空间的指针。必须对用户空间的指针指向的数据进行验证。如果只做验证不做拷贝的话,那么在随后的运行中要随时受到其它进/线程可能修改用户空间数据的威胁。所以必须做拷贝。 在内存系列篇中已经反复的说过,每个用户进程都有自己独立的用户空间,但这个用户空间是通过MMU映射出来的,是表面上繁花似锦,背后都共用着真正的物理内存,所以在高频率的任务切换过程中,原有的用户空间地址内容很容易被覆盖掉.举个例子说明下: 用户A客户有个美女放在万聪酒店21号房说要献给内核大佬,如果内核不直接把美女接回家,而仅仅是做个记录,写着美女在万聪酒店21号房,内核立马跑去过还好不会错,但如果被其他事给耽搁了呢? 耽搁的这回功夫,调度算法把万聪酒店21号房给了B客户使用,当然B客户用之前酒店管理人员会把美女置换个地方(以至于A客户再回到酒店时,原来的东西该怎样还咋样). 等21号房空出来了,B肯定不知道原来的房间是A在用,而且里面还有个美女,更不可能晓得美女献给内核大佬了.因为B的业务需要,很可能往21号房整了个东施进来. 此时如果内核大佬事忙完了,想起A客户献美女的事了,是时候了.因为只记录了地址,直接去万聪酒店21号房抓人,那抓出来可是东施呀.这可不把事给搞砸啦. 所以需要跨空间拷贝,直接把美女接回家找个地方关起来先. reset_vector_mp.S 和 reset_vector_up.S 鸿蒙开机代码根据 CPU多核还是单核分成了两个独立文件处理. mp就是多处理器(multiprocessing)的意思: 多CPU核的操作系统3种处理模式(SMP+AMP+BMP) 鸿蒙实现的是 SMP 的方式 非对称多处理(Asymmetric multiprocessing,AMP)每个CPU内核 运行一个独立的操作系统或同一操作系统的独立实例(instantiation)。 对称多处理(Symmetric multiprocessing,SMP)一个操作系统的实例 可以同时管理所有CPU内核,且应用并不绑定某一个内核。 混合多处理(Bound multiprocessing,BMP)一个操作系统的实例可以 同时管理所有CPU内核,但每个应用被锁定于某个指定的核心。 up(unit processing )的意思,单个CPU,虽然没mp的复杂,但文件也很大 500行汇编,一小节讲不完,需要单独的一篇专讲 reset_vector 这里只列出up情况下的开机代码 reset_vector: @鸿蒙单核cpu 开机代码 /* do some early cpu setup: i/d cache disable, mmu disabled */ mrc p15, 0, r0, c1, c0, 0 bic r0, #(1<<12) bic r0, #(1<<2 | 1<<0) mcr p15, 0, r0, c1, c0, 0 /* r11: delta of physical address and virtual address */ adr r11, pa_va_offset ldr r0, [r11] sub r11, r11, r0 /* if we need to relocate to proper location or not */ adr r4, __exception_handlers /* r4: base of load address */ ldr r5, =SYS_MEM_BASE /* r5: base of physical address */ subs r12, r4, r5 /* r12: delta of load address and physical address */ beq reloc_img_to_bottom_done /* if we load image at the bottom of physical address */ /* we need to relocate image at the bottom of physical address */ ldr r7, =__exception_handlers /* r7: base of linked address (or vm address) */ ldr r6, =__bss_start /* r6: end of linked address (or vm address) */ sub r6, r7 /* r6: delta of linked address (or vm address) */ add r6, r4 /* r6: end of load address */ los_dispatch.S 和 los_hw_exc.S 异常模式处理入口和统一分发现实,之前也有提到过,很复杂,1000多行,后续单独细说实现过程. jmp.S 两个简单的函数longjmp setjmp 的实现,加注解部分请前往 鸿蒙内核源码注解分析 查看 FUNCTION(longjmp) ldmfd r0,{r4-r12} add r0,#(4 * 9) ldr r13,[r0] add r0,#4 ldr r14,[r0] cmp r1,#0 moveq r1,#1 mov r0,r1 mov pc,lr FUNCTION(setjmp) stmea r0,{r4-r12} add r0,#(4 * 9) str r13,[r0] add r0,#4 str r14,[r0] mov r0,#0 mov pc,lr los_hw_runstop.S .global OsSRSaveRegister .global OsSRRestoreRegister 两个函数的汇编现实,有点复杂,后续单独说明. cache.S 这是缓存部分的两个函数实现,此处没有加注解,试着看明白这两个函数的实现.加注解部分请前往 鸿蒙内核源码注解分析 查看 .macro DCACHE_LINE_SIZE, reg, tmp mrc p15, 0, \tmp, c0, c0, 1 lsr \tmp, \tmp, #16 and \tmp, \tmp, #0xf mov \reg, #4 mov \reg, \reg, lsl \tmp .endm FUNCTION(arm_inv_cache_range) push {r2, r3} DCACHE_LINE_SIZE r2, r3 sub r3, r2, #1 tst r0, r3 bic r0, r0, r3 mcrne p15, 0, r0, c7, c14, 1 tst r1, r3 bic r1, r1, r3 mcrne p15, 0, r1, c7, c14, 1 1: mcr p15, 0, r0, c7, c6, 1 add r0, r0, r2 cmp r0, r1 blo 1b dsb pop {r2, r3} mov pc, lr FUNCTION(arm_clean_cache_range) push {r2, r3} DCACHE_LINE_SIZE r2, r3 sub r3, r2, #1 bic r0, r0, r3 1: mcr p15, 0, r0, c7, c10, 1 add r0, r0, r2 cmp r0, r1 blo 1b dsb pop {r2, r3} mov pc, lr 参与贡献 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" .欢迎转载,请注明出处. 进入 >> 百万汉字注解 百篇博客分析 精读鸿蒙源码 深挖地基工程 < gitee | csdn | oschina >

优秀的个人博客,低调大师

鸿蒙内核源码分析(系统调用篇) | 图+代码详细追踪鸿蒙系统调用整个过程 | 中文注解HarmonyOS源码 | v37.01

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony > 本篇说清楚系统调用 读本篇之前建议先读鸿蒙内核源码分析(总目录)工作模式篇. 本篇通过一张图和七段代码详细说明系统调用的整个过程,代码一捅到底,直到汇编层再也捅不下去. 先看图,这里的模式可以理解为空间,因为模式不同运行的栈空间就不一样. 过程解读 在应用层main中使用系统调用mq_open(posix标准接口) mq_open被封装在库中,这里直接看库里的代码. mq_open中调用syscall,将参数传给寄出器 R7,R0~R6 SVC 0 完成用户模式到内核模式(SVC)的切换 _osExceptSwiHdl运行在svc模式下. PC寄存器直接指向_osExceptSwiHdl处取指令. _osExceptSwiHdl是汇编代码,先保存用户模式现场(R0~R12寄存器),并调用OsArmA32SyscallHandle完成系统调用 OsArmA32SyscallHandle中通过系统调用号(保存在R7寄存器)查询对应的注册函数SYS_mq_open SYS_mq_open是本次系统调用的实现函数,完成后return回到OsArmA32SyscallHandle OsArmA32SyscallHandle再return回到_osExceptSwiHdl _osExceptSwiHdl恢复用户模式现场(R0~R12寄存器) 从内核模式(SVC)切回到用户模式,PC寄存器也切回用户现场. 由此完成整个系统调用全过程 七段追踪代码,逐个分析 1.应用程序 main int main(void) { char mqname[NAMESIZE], msgrv1[BUFFER], msgrv2[BUFFER]; const char *msgptr1 = "test message1"; const char *msgptr2 = "test message2 with differnet length"; mqd_t mqdes; int prio1 = 1, prio2 = 2; struct timespec ts; struct mq_attr attr; int unresolved = 0, failure = 0; sprintf(mqname, "/" FUNCTION "_" TEST "_%d", getpid()); attr.mq_msgsize = BUFFER; attr.mq_maxmsg = BUFFER; mqdes = mq_open(mqname, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, &attr); if (mqdes == (mqd_t)-1) { perror(ERROR_PREFIX "mq_open"); unresolved = 1; } if (mq_send(mqdes, msgptr1, strlen(msgptr1), prio1) != 0) { perror(ERROR_PREFIX "mq_send"); unresolved = 1; } printf("Test PASSED\n"); return PTS_PASS; } 2. mq_open 发起系统调用 mqd_t mq_open(const char *name, int flags, ...) { mode_t mode = 0; struct mq_attr *attr = 0; if (*name == '/') name++; if (flags & O_CREAT) { va_list ap; va_start(ap, flags); mode = va_arg(ap, mode_t); attr = va_arg(ap, struct mq_attr *); va_end(ap); } return syscall(SYS_mq_open, name, flags, mode, attr); } 解读 SYS_mq_open 是真正的系统调用函数,对应一个系统调用号__NR_mq_open,通过宏SYSCALL_HAND_DEF将SysMqOpen注册到g_syscallHandle中. static UINTPTR g_syscallHandle[SYS_CALL_NUM] = {0}; //系统调用入口函数注册 static UINT8 g_syscallNArgs[(SYS_CALL_NUM + 1) / NARG_PER_BYTE] = {0};//保存系统调用对应的参数数量 #define SYSCALL_HAND_DEF(id, fun, rType, nArg) \ if ((id) < SYS_CALL_NUM) { \ g_syscallHandle[(id)] = (UINTPTR)(fun); \ g_syscallNArgs[(id) / NARG_PER_BYTE] |= ((id) & 1) ? (nArg) << NARG_BITS : (nArg); \ } \ #include "syscall_lookup.h" #undef SYSCALL_HAND_DEF SYSCALL_HAND_DEF(__NR_mq_open, SysMqOpen, mqd_t, ARG_NUM_4) g_syscallNArgs为注册函数的参数个数,也会一块记录下来. 四个参数为 SYS_mq_open的四个参数,后续将保存在R0~R3寄存器中 3. syscall long syscall(long n, ...) { va_list ap; syscall_arg_t a,b,c,d,e,f; va_start(ap, n); a=va_arg(ap, syscall_arg_t); b=va_arg(ap, syscall_arg_t); c=va_arg(ap, syscall_arg_t); d=va_arg(ap, syscall_arg_t); e=va_arg(ap, syscall_arg_t); f=va_arg(ap, syscall_arg_t);//最多6个参数 va_end(ap); return __syscall_ret(__syscall(n,a,b,c,d,e,f)); } static inline long __syscall4(long n, long a, long b, long c, long d) { register long a7 __asm__("a7") = n; //系统调用号 R7寄存器 register long a0 __asm__("a0") = a; //R0 register long a1 __asm__("a1") = b; //R1 register long a2 __asm__("a2") = c; //R2 register long a3 __asm__("a3") = d; //R3 __asm_syscall("r"(a7), "0"(a0), "r"(a1), "r"(a2), "r"(a3)) } 解读 可变参数实现所有系统调用的参数的管理,可以看出,在鸿蒙内核中系统调用的参数最多不能大于6个 R7寄存器保存了系统调用号,R0~R5保存具体每个参数 可变参数的具体实现后续有其余篇幅详细介绍,敬请关注. 4. svc 0 //切到SVC模式 #define __asm_syscall(...) do { \ __asm__ __volatile__ ( "svc 0" \ : "=r"(x0) : __VA_ARGS__ : "memory", "cc"); \ return x0; \ } while (0) b reset_vector @开机代码 b _osExceptUndefInstrHdl @异常处理之CPU碰到不认识的指令 b _osExceptSwiHdl @异常处理之:软中断 b _osExceptPrefetchAbortHdl @异常处理之:取指异常 b _osExceptDataAbortHdl @异常处理之:数据异常 b _osExceptAddrAbortHdl @异常处理之:地址异常 b OsIrqHandler @异常处理之:硬中断 b _osExceptFiqHdl @异常处理之:快中断 解读 svc 全称是 SuperVisor Call,完成工作模式的切换.不管之前是7个模式中的哪个模式,统一都切到SVC管理模式. 而软中断对应的处理函数为 _osExceptSwiHdl,即PC寄存器将跳到_osExceptSwiHdl执行 5. _osExceptSwiHdl @ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理 SUB SP, SP, #(4 * 16) @先申请16个栈空间用于处理本次软中断 STMIA SP, {R0-R12} @保存R0-R12寄存器值 MRS R3, SPSR @读取本模式下的SPSR值 MOV R4, LR @保存回跳寄存器LR AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode 获取中断模式 CMP R1, #CPSR_USER_MODE @ User mode 是否为用户模式 BNE OsKernelSVCHandler @ Branch if not user mode 非用户模式下跳转 @ 当为用户模式时,获取SP和LR寄出去值 @ we enter from user mode, we need get the values of USER mode r13(sp) and r14(lr). @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list). MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 STMFD SP!, {R3} @ Save the CPSR 入栈保存CPSR值 ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 保存用户模式下的SP和LR寄存器 SUB SP, SP, #4 PUSH_FPU_REGS R1 @保存中断模式(用户模式模式) MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用*/ CPSID I @执行后续指令前必须先关中断 POP_FPU_REGS R1 @弹出FP值给R1 ADD SP, SP,#4 @ 定位到保存旧SPSR值的位置 LDMFD SP!, {R3} @ Fetch the return SPSR 弹出旧SPSR值 MSR SPSR_cxsf, R3 @ Set the return mode SPSR 恢复该模式下的SPSR值 @ we are leaving to user mode, we need to restore the values of USER mode r13(sp) and r14(lr). @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list) LDMFD SP!, {R0-R12} @恢复R0-R12寄存器 LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢复用户模式的R13/R14寄存器 ADD SP, SP, #(2 * 4) @定位到保存旧PC值的位置 LDMFD SP!, {PC}^ @ Return to user 切回用户模式运行 解读 运行到此处,已经切到SVC的栈运行,所以先保存上一个模式的现场 获取中断模式,软中断的来源可不一定是用户模式,完全有可能是SVC本身,比如系统调用中又发生系统调用.就变成了从SVC模式切到SVC的模式 MOV R0, SP ;sp将作为参数传递给OsArmA32SyscallHandle 调用 OsArmA32SyscallHandle 这是所有系统调用的统一入口 注意看OsArmA32SyscallHandle的参数 UINT32 *regs 6. OsArmA32SyscallHandle LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs) { UINT32 ret; UINT8 nArgs; UINTPTR handle; UINT32 cmd = regs[REG_R7];//C7寄存器记录了触发了具体哪个系统调用 if (cmd >= SYS_CALL_NUM) {//系统调用的总数 PRINT_ERR("Syscall ID: error %d !!!\n", cmd); return regs; } if (cmd == __NR_sigreturn) {//此时运行在内核栈,程序返回的调用,从内核态返回用户态时触发 OsRestorSignalContext(regs);//恢复信号上下文,执行完函数后,切到了用户栈 return regs; } handle = g_syscallHandle[cmd];//拿到系统调用的注册函数,类似 SysRead nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */ nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);//获取参数个数 if ((handle == 0) || (nArgs > ARG_NUM_7)) {//系统调用必须有参数且参数不能大于8个 PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", cmd, nArgs); regs[REG_R0] = -ENOSYS; return regs; } //regs[0-6] 记录系统调用的参数,这也是由R7寄存器保存系统调用号的原因 switch (nArgs) {//参数的个数 case ARG_NUM_0: case ARG_NUM_1: ret = (*(SyscallFun1)handle)(regs[REG_R0]);//执行系统调用,类似 SysUnlink(pathname); break; case ARG_NUM_2://@note_thinking 如何是两个参数的系统调用,这里传的确是三个参数,任务栈中会出现怎样的情况呢? case ARG_NUM_3: ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);//类似 SysExecve(fileName, argv, envp); break; case ARG_NUM_4: case ARG_NUM_5: ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4]); break; default: //7个参数的情况 ret = (*(SyscallFun7)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4], regs[REG_R5], regs[REG_R6]); } regs[REG_R0] = ret;//R0保存系统调用返回值 OsSaveSignalContext(regs);//保存用户栈现场 /* Return the last value of curent_regs. This supports context switches on return from the exception. * That capability is only used with theSYS_context_switch system call. */ return regs;//返回寄存器的值 } 解读 参数是regs对应的就是R0~Rn R7保存的是系统调用号,R0~R3保存的是 SysMqOpen的四个参数 g_syscallHandle[cmd]就能查询到 SYSCALL_HAND_DEF(__NR_mq_open, SysMqOpen, mqd_t, ARG_NUM_4)注册时对应的 SysMqOpen函数 *(SyscallFun5)handle此时就是SysMqOpen 注意看 SysMqOpen 的参数是最开始的 main函数中的 mqdes = mq_open(mqname, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, &attr); 由此完成了真正系统调用的过程 7. SysMqOpen mqd_t SysMqOpen(const char *mqName, int openFlag, mode_t mode, struct mq_attr *attr) { mqd_t ret; int retValue; char kMqName[PATH_MAX + 1] = { 0 }; retValue = LOS_StrncpyFromUser(kMqName, mqName, PATH_MAX); if (retValue < 0) { return retValue; } ret = mq_open(kMqName, openFlag, mode, attr);//一个消息队列可以有多个进程向它读写消息 if (ret == -1) { return (mqd_t)-get_errno(); } return ret; } 解读 此处的mq_open和main函数的mq_open其实是两个函数体实现.一个是给应用层的调用,一个是内核层使用,只是名字一样而已. SysMqOpen是返回到 OsArmA32SyscallHandle regs[REG_R0] = ret; OsArmA32SyscallHandle再返回到 _osExceptSwiHdl _osExceptSwiHdl后面的代码是用于恢复用户模式现场和SPSR,PC 等寄存器. 以上为鸿蒙系统调用的整个过程. 关于寄存器(R0~R15)在每种模式下的使用方式,后续将由其他篇详细说明,敬请关注. 喜欢就大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >

优秀的个人博客,低调大师

鸿蒙内核源码分析(系统调用篇) | 一撸到底,全面解剖系统调用实现过程 | 中文注解HarmonyOS源码 | v37.02

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony > 本篇说清楚系统调用 读本篇之前建议先读鸿蒙内核源码分析(总目录)工作模式篇. 本篇通过一张图和七段代码详细说明系统调用的整个过程,代码一捅到底,直到汇编层再也捅不下去. 先看图,这里的模式可以理解为空间,因为模式不同运行的栈空间就不一样. 过程解读 在应用层main中使用系统调用mq_open(posix标准接口) mq_open被封装在库中,这里直接看库里的代码. mq_open中调用syscall,将参数传给寄出器 R7,R0~R6 SVC 0 完成用户模式到内核模式(SVC)的切换 _osExceptSwiHdl运行在svc模式下. PC寄存器直接指向_osExceptSwiHdl处取指令. _osExceptSwiHdl是汇编代码,先保存用户模式现场(R0~R12寄存器),并调用OsArmA32SyscallHandle完成系统调用 OsArmA32SyscallHandle中通过系统调用号(保存在R7寄存器)查询对应的注册函数SYS_mq_open SYS_mq_open是本次系统调用的实现函数,完成后return回到OsArmA32SyscallHandle OsArmA32SyscallHandle再return回到_osExceptSwiHdl _osExceptSwiHdl恢复用户模式现场(R0~R12寄存器) 从内核模式(SVC)切回到用户模式,PC寄存器也切回用户现场. 由此完成整个系统调用全过程 七段追踪代码,逐个分析 1.应用程序 main int main(void) { char mqname[NAMESIZE], msgrv1[BUFFER], msgrv2[BUFFER]; const char *msgptr1 = "test message1"; const char *msgptr2 = "test message2 with differnet length"; mqd_t mqdes; int prio1 = 1, prio2 = 2; struct timespec ts; struct mq_attr attr; int unresolved = 0, failure = 0; sprintf(mqname, "/" FUNCTION "_" TEST "_%d", getpid()); attr.mq_msgsize = BUFFER; attr.mq_maxmsg = BUFFER; mqdes = mq_open(mqname, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, &attr); if (mqdes == (mqd_t)-1) { perror(ERROR_PREFIX "mq_open"); unresolved = 1; } if (mq_send(mqdes, msgptr1, strlen(msgptr1), prio1) != 0) { perror(ERROR_PREFIX "mq_send"); unresolved = 1; } printf("Test PASSED\n"); return PTS_PASS; } 2. mq_open 发起系统调用 mqd_t mq_open(const char *name, int flags, ...) { mode_t mode = 0; struct mq_attr *attr = 0; if (*name == '/') name++; if (flags & O_CREAT) { va_list ap; va_start(ap, flags); mode = va_arg(ap, mode_t); attr = va_arg(ap, struct mq_attr *); va_end(ap); } return syscall(SYS_mq_open, name, flags, mode, attr); } 解读 SYS_mq_open 是真正的系统调用函数,对应一个系统调用号__NR_mq_open,通过宏SYSCALL_HAND_DEF将SysMqOpen注册到g_syscallHandle中. static UINTPTR g_syscallHandle[SYS_CALL_NUM] = {0}; //系统调用入口函数注册 static UINT8 g_syscallNArgs[(SYS_CALL_NUM + 1) / NARG_PER_BYTE] = {0};//保存系统调用对应的参数数量 #define SYSCALL_HAND_DEF(id, fun, rType, nArg) \ if ((id) < SYS_CALL_NUM) { \ g_syscallHandle[(id)] = (UINTPTR)(fun); \ g_syscallNArgs[(id) / NARG_PER_BYTE] |= ((id) & 1) ? (nArg) << NARG_BITS : (nArg); \ } \ #include "syscall_lookup.h" #undef SYSCALL_HAND_DEF SYSCALL_HAND_DEF(__NR_mq_open, SysMqOpen, mqd_t, ARG_NUM_4) g_syscallNArgs为注册函数的参数个数,也会一块记录下来. 四个参数为 SYS_mq_open的四个参数,后续将保存在R0~R3寄存器中 3. syscall long syscall(long n, ...) { va_list ap; syscall_arg_t a,b,c,d,e,f; va_start(ap, n); a=va_arg(ap, syscall_arg_t); b=va_arg(ap, syscall_arg_t); c=va_arg(ap, syscall_arg_t); d=va_arg(ap, syscall_arg_t); e=va_arg(ap, syscall_arg_t); f=va_arg(ap, syscall_arg_t);//最多6个参数 va_end(ap); return __syscall_ret(__syscall(n,a,b,c,d,e,f)); } static inline long __syscall4(long n, long a, long b, long c, long d) { register long a7 __asm__("a7") = n; //系统调用号 R7寄存器 register long a0 __asm__("a0") = a; //R0 register long a1 __asm__("a1") = b; //R1 register long a2 __asm__("a2") = c; //R2 register long a3 __asm__("a3") = d; //R3 __asm_syscall("r"(a7), "0"(a0), "r"(a1), "r"(a2), "r"(a3)) } 解读 可变参数实现所有系统调用的参数的管理,可以看出,在鸿蒙内核中系统调用的参数最多不能大于6个 R7寄存器保存了系统调用号,R0~R5保存具体每个参数 可变参数的具体实现后续有其余篇幅详细介绍,敬请关注. 4. svc 0 //切到SVC模式 #define __asm_syscall(...) do { \ __asm__ __volatile__ ( "svc 0" \ : "=r"(x0) : __VA_ARGS__ : "memory", "cc"); \ return x0; \ } while (0) b reset_vector @开机代码 b _osExceptUndefInstrHdl @异常处理之CPU碰到不认识的指令 b _osExceptSwiHdl @异常处理之:软中断 b _osExceptPrefetchAbortHdl @异常处理之:取指异常 b _osExceptDataAbortHdl @异常处理之:数据异常 b _osExceptAddrAbortHdl @异常处理之:地址异常 b OsIrqHandler @异常处理之:硬中断 b _osExceptFiqHdl @异常处理之:快中断 解读 svc 全称是 SuperVisor Call,完成工作模式的切换.不管之前是7个模式中的哪个模式,统一都切到SVC管理模式. 而软中断对应的处理函数为 _osExceptSwiHdl,即PC寄存器将跳到_osExceptSwiHdl执行 5. _osExceptSwiHdl @ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理 SUB SP, SP, #(4 * 16) @先申请16个栈空间用于处理本次软中断 STMIA SP, {R0-R12} @保存R0-R12寄存器值 MRS R3, SPSR @读取本模式下的SPSR值 MOV R4, LR @保存回跳寄存器LR AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode 获取中断模式 CMP R1, #CPSR_USER_MODE @ User mode 是否为用户模式 BNE OsKernelSVCHandler @ Branch if not user mode 非用户模式下跳转 @ 当为用户模式时,获取SP和LR寄出去值 @ we enter from user mode, we need get the values of USER mode r13(sp) and r14(lr). @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list). MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 STMFD SP!, {R3} @ Save the CPSR 入栈保存CPSR值 ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 保存用户模式下的SP和LR寄存器 SUB SP, SP, #4 PUSH_FPU_REGS R1 @保存中断模式(用户模式模式) MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用*/ CPSID I @执行后续指令前必须先关中断 POP_FPU_REGS R1 @弹出FP值给R1 ADD SP, SP,#4 @ 定位到保存旧SPSR值的位置 LDMFD SP!, {R3} @ Fetch the return SPSR 弹出旧SPSR值 MSR SPSR_cxsf, R3 @ Set the return mode SPSR 恢复该模式下的SPSR值 @ we are leaving to user mode, we need to restore the values of USER mode r13(sp) and r14(lr). @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list) LDMFD SP!, {R0-R12} @恢复R0-R12寄存器 LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢复用户模式的R13/R14寄存器 ADD SP, SP, #(2 * 4) @定位到保存旧PC值的位置 LDMFD SP!, {PC}^ @ Return to user 切回用户模式运行 解读 运行到此处,已经切到SVC的栈运行,所以先保存上一个模式的现场 获取中断模式,软中断的来源可不一定是用户模式,完全有可能是SVC本身,比如系统调用中又发生系统调用.就变成了从SVC模式切到SVC的模式 MOV R0, SP ;sp将作为参数传递给OsArmA32SyscallHandle 调用 OsArmA32SyscallHandle 这是所有系统调用的统一入口 注意看OsArmA32SyscallHandle的参数 UINT32 *regs 6. OsArmA32SyscallHandle LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs) { UINT32 ret; UINT8 nArgs; UINTPTR handle; UINT32 cmd = regs[REG_R7];//C7寄存器记录了触发了具体哪个系统调用 if (cmd >= SYS_CALL_NUM) {//系统调用的总数 PRINT_ERR("Syscall ID: error %d !!!\n", cmd); return regs; } if (cmd == __NR_sigreturn) {//此时运行在内核栈,程序返回的调用,从内核态返回用户态时触发 OsRestorSignalContext(regs);//恢复信号上下文,执行完函数后,切到了用户栈 return regs; } handle = g_syscallHandle[cmd];//拿到系统调用的注册函数,类似 SysRead nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */ nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);//获取参数个数 if ((handle == 0) || (nArgs > ARG_NUM_7)) {//系统调用必须有参数且参数不能大于8个 PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", cmd, nArgs); regs[REG_R0] = -ENOSYS; return regs; } //regs[0-6] 记录系统调用的参数,这也是由R7寄存器保存系统调用号的原因 switch (nArgs) {//参数的个数 case ARG_NUM_0: case ARG_NUM_1: ret = (*(SyscallFun1)handle)(regs[REG_R0]);//执行系统调用,类似 SysUnlink(pathname); break; case ARG_NUM_2://@note_thinking 如何是两个参数的系统调用,这里传的确是三个参数,任务栈中会出现怎样的情况呢? case ARG_NUM_3: ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);//类似 SysExecve(fileName, argv, envp); break; case ARG_NUM_4: case ARG_NUM_5: ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4]); break; default: //7个参数的情况 ret = (*(SyscallFun7)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4], regs[REG_R5], regs[REG_R6]); } regs[REG_R0] = ret;//R0保存系统调用返回值 OsSaveSignalContext(regs);//保存用户栈现场 /* Return the last value of curent_regs. This supports context switches on return from the exception. * That capability is only used with theSYS_context_switch system call. */ return regs;//返回寄存器的值 } 解读 参数是regs对应的就是R0~Rn R7保存的是系统调用号,R0~R3保存的是 SysMqOpen的四个参数 g_syscallHandle[cmd]就能查询到 SYSCALL_HAND_DEF(__NR_mq_open, SysMqOpen, mqd_t, ARG_NUM_4)注册时对应的 SysMqOpen函数 *(SyscallFun5)handle此时就是SysMqOpen 注意看 SysMqOpen 的参数是最开始的 main函数中的 mqdes = mq_open(mqname, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, &attr); 由此完成了真正系统调用的过程 7. SysMqOpen mqd_t SysMqOpen(const char *mqName, int openFlag, mode_t mode, struct mq_attr *attr) { mqd_t ret; int retValue; char kMqName[PATH_MAX + 1] = { 0 }; retValue = LOS_StrncpyFromUser(kMqName, mqName, PATH_MAX); if (retValue < 0) { return retValue; } ret = mq_open(kMqName, openFlag, mode, attr);//一个消息队列可以有多个进程向它读写消息 if (ret == -1) { return (mqd_t)-get_errno(); } return ret; } 解读 此处的mq_open和main函数的mq_open其实是两个函数体实现.一个是给应用层的调用,一个是内核层使用,只是名字一样而已. SysMqOpen是返回到 OsArmA32SyscallHandle regs[REG_R0] = ret; OsArmA32SyscallHandle再返回到 _osExceptSwiHdl _osExceptSwiHdl后面的代码是用于恢复用户模式现场和SPSR,PC 等寄存器. 以上为鸿蒙系统调用的整个过程. 关于寄存器(R0~R15)在每种模式下的使用方式,后续将由其他篇详细说明,敬请关注. 喜欢就大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony >

优秀的个人博客,低调大师

| 中文注解HarmonyOS源码 | v33.01

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony > 本篇说清楚消息队列 读本篇之前建议先读鸿蒙内核源码分析(总目录). 基本概念 队列又称消息队列,是一种常用于任务间通信的数据结构。队列接收来自任务或中断的不固定长度消息,并根据不同的接口确定传递的消息是否存放在队列空间中。 任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时,挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。如果将读队列和写队列的超时时间设置为0,则不会挂起任务,接口会直接返回,这就是非阻塞模式。 消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用。 队列用于任务间通信,可以实现消息的异步处理。同时消息的发送方和接收方不需要彼此联系,两者间是解耦的。 队列特性 消息以先进先出的方式排队,支持异步读写。 读队列和写队列都支持超时机制。 每读取一条消息,就会将该消息节点设置为空闲。 发送消息类型由通信双方约定,可以允许不同长度(不超过队列的消息节点大小)的消息。 一个任务能够从任意一个消息队列接收和发送消息。 多个任务能够从同一个消息队列接收和发送消息。 创建队列时所需的队列空间,默认支持接口内系统自行动态申请内存的方式,同时也支持将用户分配的队列空间作为接口入参传入的方式。 消息队列长什么样? #ifndef LOSCFG_BASE_IPC_QUEUE_LIMIT #define LOSCFG_BASE_IPC_QUEUE_LIMIT 1024 //队列个数 #endif LITE_OS_SEC_BSS LosQueueCB *g_allQueue = NULL;//消息队列池 LITE_OS_SEC_BSS STATIC LOS_DL_LIST g_freeQueueList;//空闲队列链表,管分配的,需要队列从这里申请 typedef struct { UINT8 *queueHandle; /**< Pointer to a queue handle */ //指向队列句柄的指针 UINT16 queueState; /**< Queue state */ //队列状态 UINT16 queueLen; /**< Queue length */ //队列中消息总数的上限值,由创建时确定,不再改变 UINT16 queueSize; /**< Node size */ //消息节点大小,由创建时确定,不再改变,即定义了每个消息长度的上限. UINT32 queueID; /**< queueID */ //队列ID UINT16 queueHead; /**< Node head */ //消息头节点位置(数组下标) UINT16 queueTail; /**< Node tail */ //消息尾节点位置(数组下标) UINT16 readWriteableCnt[OS_QUEUE_N_RW]; /**< Count of readable or writable resources, 0:readable, 1:writable */ //队列中可写或可读消息数,0表示可读,1表示可写 LOS_DL_LIST readWriteList[OS_QUEUE_N_RW]; /**< the linked list to be read or written, 0:readlist, 1:writelist */ //挂的都是等待读/写消息的任务链表,0表示读消息的链表,1表示写消息的任务链表 LOS_DL_LIST memList; /**< Pointer to the memory linked list */ //@note_why 这里尚未搞明白是啥意思 ,是共享内存吗? } LosQueueCB;//读写队列分离 解读 和进程,线程,定时器一样,消息队列也由全局统一的消息队列池管理,池有多大?默认是1024 鸿蒙内核对消息的总个数有限制,queueLen消息总数的上限,在创建队列的时候需指定,不能更改. 对每个消息的长度也有限制, queueSize 规定了消息的大小,也是在创建的时候指定. 为啥要指定总个数和每个的总大小,是因为内核一次性会把队列的总内存(queueLen*queueSize)申请下来,确保不会出现后续使用过程中内存不够的问题出现,但同时也带来了内存的浪费,因为很可能大部分时间队列并没有跑满. 队列的读取由queueHead,queueTail管理,Head表示队列中被占用的消息节点的起始位置。Tail表示被占用的消息节点的结束位置,也是空闲消息节点的起始位置。队列刚创建时,Head和Tail均指向队列起始位置 写队列时,根据readWriteableCnt[1]判断队列是否可以写入,不能对已满(readWriteableCnt[1]为0)队列进行写操作。写队列支持两种写入方式:向队列尾节点写入,也可以向队列头节点写入。尾节点写入时,根据Tail找到起始空闲消息节点作为数据写入对象,如果Tail已经指向队列尾部则采用回卷方式。头节点写入时,将Head的前一个节点作为数据写入对象,如果Head指向队列起始位置则采用回卷方式。 读队列时,根据readWriteableCnt[0]判断队列是否有消息需要读取,对全部空闲(readWriteableCnt[0]为0)队列进行读操作会引起任务挂起。如果队列可以读取消息,则根据Head找到最先写入队列的消息节点进行读取。如果Head已经指向队列尾部则采用回卷方式。 删除队列时,根据队列ID找到对应队列,把队列状态置为未使用,把队列控制块置为初始状态。如果是通过系统动态申请内存方式创建的队列,还会释放队列所占内存。 留意readWriteList,这又是两个双向链表, 双向链表是内核最重要的结构体,牢牢的寄生在宿主结构体上.readWriteList上挂的是未来读/写消息队列的任务列表. 初始化队列 LITE_OS_SEC_TEXT_INIT UINT32 OsQueueInit(VOID)//消息队列模块初始化 { LosQueueCB *queueNode = NULL; UINT32 index; UINT32 size; size = LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB);//支持1024个IPC队列 /* system resident memory, don't free */ g_allQueue = (LosQueueCB *)LOS_MemAlloc(m_aucSysMem0, size);//常驻内存 if (g_allQueue == NULL) { return LOS_ERRNO_QUEUE_NO_MEMORY; } (VOID)memset_s(g_allQueue, size, 0, size);//清0 LOS_ListInit(&g_freeQueueList);//初始化空闲链表 for (index = 0; index < LOSCFG_BASE_IPC_QUEUE_LIMIT; index++) {//循环初始化每个消息队列 queueNode = ((LosQueueCB *)g_allQueue) + index;//一个一个来 queueNode->queueID = index;//这可是 队列的身份证 LOS_ListTailInsert(&g_freeQueueList, &queueNode->readWriteList[OS_QUEUE_WRITE]);//通过写节点挂到空闲队列链表上 }//这里要注意是用 readWriteList 挂到 g_freeQueueList链上的,所以要通过 GET_QUEUE_LIST 来找到 LosQueueCB if (OsQueueDbgInitHook() != LOS_OK) {//调试队列使用的. return LOS_ERRNO_QUEUE_NO_MEMORY; } return LOS_OK; } 解读 初始队列模块,对几个全局变量赋值,创建消息队列池,所有池都是常驻内存,关于池后续有专门的文章整理,到目前为止已经解除到了进程池,任务池,定时器池,队列池,== 将LOSCFG_BASE_IPC_QUEUE_LIMIT个队列挂到空闲链表g_freeQueueList上,供后续分配和回收.熟悉内核全局资源管理的对这种方式应该不会再陌生. 创建队列 //创建一个队列,根据用户传入队列长度和消息节点大小来开辟相应的内存空间以供该队列使用,参数queueID带走队列ID LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueCreate(CHAR *queueName, UINT16 len, UINT32 *queueID, UINT32 flags, UINT16 maxMsgSize) { LosQueueCB *queueCB = NULL; UINT32 intSave; LOS_DL_LIST *unusedQueue = NULL; UINT8 *queue = NULL; UINT16 msgSize; (VOID)queueName; (VOID)flags; if (queueID == NULL) { return LOS_ERRNO_QUEUE_CREAT_PTR_NULL; } if (maxMsgSize > (OS_NULL_SHORT - sizeof(UINT32))) {// maxMsgSize上限 为啥要减去 sizeof(UINT32) ,因为前面存的是队列的大小 return LOS_ERRNO_QUEUE_SIZE_TOO_BIG; } if ((len == 0) || (maxMsgSize == 0)) { return LOS_ERRNO_QUEUE_PARA_ISZERO; } msgSize = maxMsgSize + sizeof(UINT32);//总size = 消息体内容长度 + 消息大小(UINT32) /* * Memory allocation is time-consuming, to shorten the time of disable interrupt, * move the memory allocation to here. *///内存分配非常耗时,为了缩短禁用中断的时间,将内存分配移到此处,用的时候分配队列内存 queue = (UINT8 *)LOS_MemAlloc(m_aucSysMem1, (UINT32)len * msgSize);//从系统内存池中分配,由这里提供读写队列的内存 if (queue == NULL) {//这里是一次把队列要用到的所有最大内存都申请下来了,能保证不会出现后续使用过程中内存不够的问题出现 return LOS_ERRNO_QUEUE_CREATE_NO_MEMORY;//调用处有 OsSwtmrInit sys_mbox_new DoMqueueCreate == } SCHEDULER_LOCK(intSave); if (LOS_ListEmpty(&g_freeQueueList)) {//没有空余的队列ID的处理,注意软时钟定时器是由 g_swtmrCBArray统一管理的,里面有正在使用和可分配空闲的队列 SCHEDULER_UNLOCK(intSave);//g_freeQueueList是管理可用于分配的队列链表,申请消息队列的ID需要向它要 OsQueueCheckHook(); (VOID)LOS_MemFree(m_aucSysMem1, queue);//没有就要释放 queue申请的内存 return LOS_ERRNO_QUEUE_CB_UNAVAILABLE; } unusedQueue = LOS_DL_LIST_FIRST(&g_freeQueueList);//找到一个没有被使用的队列 LOS_ListDelete(unusedQueue);//将自己从g_freeQueueList中摘除, unusedQueue只是个 LOS_DL_LIST 结点. queueCB = GET_QUEUE_LIST(unusedQueue);//通过unusedQueue找到整个消息队列(LosQueueCB) queueCB->queueLen = len; //队列中消息的总个数,注意这个一旦创建是不能变的. queueCB->queueSize = msgSize;//消息节点的大小,注意这个一旦创建也是不能变的. queueCB->queueHandle = queue; //队列句柄,队列内容存储区. queueCB->queueState = OS_QUEUE_INUSED; //队列状态使用中 queueCB->readWriteableCnt[OS_QUEUE_READ] = 0;//可读资源计数,OS_QUEUE_READ(0):可读. queueCB->readWriteableCnt[OS_QUEUE_WRITE] = len;//可些资源计数 OS_QUEUE_WRITE(1):可写, 默认len可写. queueCB->queueHead = 0;//队列头节点 queueCB->queueTail = 0;//队列尾节点 LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_READ]);//初始化可读队列链表 LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_WRITE]);//初始化可写队列链表 LOS_ListInit(&queueCB->memList);// OsQueueDbgUpdateHook(queueCB->queueID, OsCurrTaskGet()->taskEntry);//在创建或删除队列调试信息时更新任务条目 SCHEDULER_UNLOCK(intSave); *queueID = queueCB->queueID;//带走队列ID return LOS_OK; } 解读 创建和初始化一个LosQueueCB 动态分配内存来保存消息内容,LOS_MemAlloc(m_aucSysMem1, (UINT32)len * msgSize); msgSize = maxMsgSize + sizeof(UINT32);头四个字节放消息的长度,但消息最大长度不能超过maxMsgSize readWriteableCnt记录读/写队列的数量,独立计算 readWriteList挂的是等待读取队列的任务链表 将在OsTaskWait(&queueCB->readWriteList[readWrite], timeout, TRUE);中将任务挂到链表上. 关键函数OsQueueOperate 队列的读写都要经过 OsQueueOperate /************************************************ 队列操作.是读是写由operateType定 本函数是消息队列最重要的一个函数,可以分析出读取消息过程中 发生的细节,涉及任务的唤醒和阻塞,阻塞链表任务的相互提醒. ************************************************/ UINT32 OsQueueOperate(UINT32 queueID, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize, UINT32 timeout) { LosQueueCB *queueCB = NULL; LosTaskCB *resumedTask = NULL; UINT32 ret; UINT32 readWrite = OS_QUEUE_READ_WRITE_GET(operateType);//获取读/写操作标识 UINT32 intSave; SCHEDULER_LOCK(intSave); queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueID);//获取对应的队列控制块 ret = OsQueueOperateParamCheck(queueCB, queueID, operateType, bufferSize);//参数检查 if (ret != LOS_OK) { goto QUEUE_END; } if (queueCB->readWriteableCnt[readWrite] == 0) {//根据readWriteableCnt判断队列是否有消息读/写 if (timeout == LOS_NO_WAIT) {//不等待直接退出 ret = OS_QUEUE_IS_READ(operateType) ? LOS_ERRNO_QUEUE_ISEMPTY : LOS_ERRNO_QUEUE_ISFULL; goto QUEUE_END; } if (!OsPreemptableInSched()) {//不支持抢占式调度 ret = LOS_ERRNO_QUEUE_PEND_IN_LOCK; goto QUEUE_END; } //任务等待,这里很重要啊,将自己从就绪列表摘除,让出了CPU并发起了调度,并挂在readWriteList[readWrite]上,挂的都等待读/写消息的task ret = OsTaskWait(&queueCB->readWriteList[readWrite], timeout, TRUE);//任务被唤醒后会回到这里执行,什么时候会被唤醒?当然是有消息的时候! if (ret == LOS_ERRNO_TSK_TIMEOUT) {//唤醒后如果超时了,返回读/写消息失败 ret = LOS_ERRNO_QUEUE_TIMEOUT; goto QUEUE_END;// } } else { queueCB->readWriteableCnt[readWrite]--;//对应队列中计数器--,说明一条消息只能被读/写一次 } OsQueueBufferOperate(queueCB, operateType, bufferAddr, bufferSize);//发起读或写队列操作 if (!LOS_ListEmpty(&queueCB->readWriteList[!readWrite])) {//如果还有任务在排着队等待读/写入消息(当时不能读/写的原因有可能当时队列满了==) resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&queueCB->readWriteList[!readWrite]));//取出要读/写消息的任务 OsTaskWake(resumedTask);//唤醒任务去读/写消息啊 SCHEDULER_UNLOCK(intSave); LOS_MpSchedule(OS_MP_CPU_ALL);//让所有CPU发出调度申请,因为很可能那个要读/写消息的队列是由其他CPU执行 LOS_Schedule();//申请调度 return LOS_OK; } else { queueCB->readWriteableCnt[!readWrite]++;//对应队列读/写中计数器++ } QUEUE_END: SCHEDULER_UNLOCK(intSave); return ret; } 解读 queueID 指定操作消息队列池中哪个消息队列 operateType 表示本次是是读/写消息 bufferAddr,bufferSize表示如果读操作,用buf接走数据,如果写操作,将buf写入队列. timeout只用于当队列中没有读/写内容时的等待. 当读消息时发现队列中没有可读的消息,此时timeout决定是否将任务挂入等待读队列任务链表 当写消息时发现队列中没有空间用于写的消息,此时timeout决定是否将任务挂入等待写队列任务链表 if (!LOS_ListEmpty(&queueCB->readWriteList[!readWrite]))最有意思的是这行代码. 在一次读消息完成后会立即唤醒写队列任务链表的任务,因为读完了就有了剩余空间,等待写队列的任务往往是因为没有空间而进入等待状态. 在一次写消息完成后会立即唤醒读队列任务链表的任务,因为写完了队列就有了新消息,等待读队列的任务往往是因为队列中没有消息而进入等待状态. 编程实例 #include "los_task.h" #include "los_queue.h" static UINT32 g_queue; #define BUFFER_LEN 50 VOID send_Entry(VOID) { UINT32 i = 0; UINT32 ret = 0; CHAR abuf[] = "test is message x"; UINT32 len = sizeof(abuf); while (i < 5) { abuf[len -2] = '0' + i; i++; ret = LOS_QueueWriteCopy(g_queue, abuf, len, 0); if(ret != LOS_OK) { dprintf("send message failure, error: %x\n", ret); } LOS_TaskDelay(5); } } VOID recv_Entry(VOID) { UINT32 ret = 0; CHAR readBuf[BUFFER_LEN] = {0}; UINT32 readLen = BUFFER_LEN; while (1) { ret = LOS_QueueReadCopy(g_queue, readBuf, &readLen, 0); if(ret != LOS_OK) { dprintf("recv message failure, error: %x\n", ret); break; } dprintf("recv message: %s\n", readBuf); LOS_TaskDelay(5); } while (LOS_OK != LOS_QueueDelete(g_queue)) { LOS_TaskDelay(1); } dprintf("delete the queue success!\n"); } UINT32 Example_CreateTask(VOID) { UINT32 ret = 0; UINT32 task1, task2; TSK_INIT_PARAM_S initParam; initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)send_Entry; initParam.usTaskPrio = 9; initParam.uwStackSize = LOS_TASK_MIN_STACK_SIZE; initParam.pcName = "sendQueue"; #ifdef LOSCFG_KERNEL_SMP initParam.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid()); #endif initParam.uwResved = LOS_TASK_STATUS_DETACHED; LOS_TaskLock(); ret = LOS_TaskCreate(&task1, &initParam); if(ret != LOS_OK) { dprintf("create task1 failed, error: %x\n", ret); return ret; } initParam.pcName = "recvQueue"; initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)recv_Entry; ret = LOS_TaskCreate(&task2, &initParam); if(ret != LOS_OK) { dprintf("create task2 failed, error: %x\n", ret); return ret; } ret = LOS_QueueCreate("queue", 5, &g_queue, 0, BUFFER_LEN); if(ret != LOS_OK) { dprintf("create queue failure, error: %x\n", ret); } dprintf("create the queue success!\n"); LOS_TaskUnlock(); return ret; } 结果验证 create the queue success! recv message: test is message 0 recv message: test is message 1 recv message: test is message 2 recv message: test is message 3 recv message: test is message 4 recv message failure, error: 200061d delete the queue success! 总结 消息队列解决任务间大数据的传递 以一种异步,解耦的方式实现任务通讯 全局由消息队列池统一管理 在创建消息队列时申请内存块存储消息内存. 读/写操作统一管理,分开执行,A任务 读/写完消息后会立即唤醒等待写/读的B任务. 喜欢就大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册