首页 文章 精选 留言 我的

精选列表

搜索[HarmonyOS],共1236篇文章
优秀的个人博客,低调大师

| 中文注解HarmonyOS源码 | v21.02

鸿蒙内核源码注释>> 精读内核源码,中文注解分析,深挖地基工程,大脑永久记忆,四大码仓每日同步更新 鸿蒙内核源码分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新 本篇说清楚任务的问题. 在鸿蒙内核线程(thread)就是任务(task),也可以叫作业.线程是对外的说法,对内就叫任务.跟王二毛一样, 在公司叫你王董,回到家里还有领导,就叫二毛啊.这多亲切.在鸿蒙内核是大量的task,很少看到thread,只出现在posix层.当一个东西理解就行. 读本篇之前建议先读鸿蒙内核源码分析(必读故事篇) | 西门和金莲的那点破事 < CSDN | OSCHINA | WeHarmony | 源动力 >,里面有对任务生活场景式的比喻. 任务(LosTaskCB)原始真身如下,本篇一一剖析它,看看它的五脏六腑里到底长的是个啥. typedef struct { VOID *stackPointer; /**< Task stack pointer */ //非用户模式下的栈指针 UINT16 taskStatus; /**< Task status */ //各种状态标签,可以拥有多种标签,按位标识 UINT16 priority; /**< Task priority */ //任务优先级[0:31],默认是31级 UINT16 policy; //任务的调度方式(三种 .. LOS_SCHED_RR ) UINT16 timeSlice; /**< Remaining time slice *///剩余时间片 UINT32 stackSize; /**< Task stack size */ //非用户模式下栈大小 UINTPTR topOfStack; /**< Task stack top */ //非用户模式下的栈顶 bottom = top + size UINT32 taskID; /**< Task ID */ //任务ID,任务池本质是一个大数组,ID就是数组的索引,默认 < 128 TSK_ENTRY_FUNC taskEntry; /**< Task entrance function */ //任务执行入口函数 VOID *joinRetval; /**< pthread adaption */ //用来存储join线程的返回值 VOID *taskSem; /**< Task-held semaphore */ //task在等哪个信号量 VOID *taskMux; /**< Task-held mutex */ //task在等哪把锁 VOID *taskEvent; /**< Task-held event */ //task在等哪个事件 UINTPTR args[4]; /**< Parameter, of which the maximum number is 4 */ //入口函数的参数 例如 main (int argc,char *argv[]) CHAR taskName[OS_TCB_NAME_LEN]; /**< Task name */ //任务的名称 LOS_DL_LIST pendList; /**< Task pend node */ //如果任务阻塞时就通过它挂到各种阻塞情况的链表上,比如OsTaskWait时 LOS_DL_LIST threadList; /**< thread list */ //挂到所属进程的线程链表上 SortLinkList sortList; /**< Task sortlink node */ //挂到cpu core 的任务执行链表上 UINT32 eventMask; /**< Event mask */ //事件屏蔽 UINT32 eventMode; /**< Event mode */ //事件模式 UINT32 priBitMap; /**< BitMap for recording the change of task priority, //任务在执行过程中优先级会经常变化,这个变量用来记录所有曾经变化 the priority can not be greater than 31 */ //过的优先级,例如 ..01001011 曾经有过 0,1,3,6 优先级 INT32 errorNo; /**< Error Num */ UINT32 signal; /**< Task signal */ //任务信号类型,(SIGNAL_NONE,SIGNAL_KILL,SIGNAL_SUSPEND,SIGNAL_AFFI) sig_cb sig; //信号控制块,这里用于进程间通讯的信号,类似于 linux singal模块 #if (LOSCFG_KERNEL_SMP == YES) UINT16 currCpu; /**< CPU core number of this task is running on */ //正在运行此任务的CPU内核号 UINT16 lastCpu; /**< CPU core number of this task is running on last time */ //上次运行此任务的CPU内核号 UINT16 cpuAffiMask; /**< CPU affinity mask, support up to 16 cores */ //CPU亲和力掩码,最多支持16核,亲和力很重要,多核情况下尽量一个任务在一个CPU核上运行,提高效率 UINT32 timerCpu; /**< CPU core number of this task is delayed or pended */ //此任务的CPU内核号被延迟或挂起 #if (LOSCFG_KERNEL_SMP_TASK_SYNC == YES) UINT32 syncSignal; /**< Synchronization for signal handling */ //用于CPU之间 同步信号 #endif #if (LOSCFG_KERNEL_SMP_LOCKDEP == YES) //死锁检测开关 LockDep lockDep; #endif #if (LOSCFG_KERNEL_SCHED_STATISTICS == YES) //调度统计开关,显然打开这个开关性能会受到影响,鸿蒙默认是关闭的 SchedStat schedStat; /**< Schedule statistics */ //调度统计 #endif #endif UINTPTR userArea; //使用区域,由运行时划定,根据运行态不同而不同 UINTPTR userMapBase; //用户模式下的栈底位置 UINT32 userMapSize; /**< user thread stack size ,real size : userMapSize + USER_STACK_MIN_SIZE */ UINT32 processID; /**< Which belong process *///所属进程ID FutexNode futex; //实现快锁功能 LOS_DL_LIST joinList; /**< join list */ //联结链表,允许任务之间相互释放彼此 LOS_DL_LIST lockList; /**< Hold the lock list */ //拿到了哪些锁链表 UINT32 waitID; /**< Wait for the PID or GID of the child process */ //等待孩子的PID或GID进程 UINT16 waitFlag; /**< The type of child process that is waiting, belonging to a group or parent, a specific child process, or any child process */ #if (LOSCFG_KERNEL_LITEIPC == YES) UINT32 ipcStatus; //IPC状态 LOS_DL_LIST msgListHead; //消息队列头结点,上面挂的都是任务要读的消息 BOOL accessMap[LOSCFG_BASE_CORE_TSK_LIMIT];//访问图,指的是task之间是否能访问的标识,LOSCFG_BASE_CORE_TSK_LIMIT 为任务池总数 #endif } LosTaskCB; 结构体还是比较复杂,虽一一都做了注解,但还是不够清晰,没有模块化.这里把它分解成以下六大块逐一分析: 第一大块:多核CPU相关块 #if (LOSCFG_KERNEL_SMP == YES) //多CPU核支持 UINT16 currCpu; /**< CPU core number of this task is running on */ //正在运行此任务的CPU内核号 UINT16 lastCpu; /**< CPU core number of this task is running on last time */ //上次运行此任务的CPU内核号 UINT16 cpuAffiMask; /**< CPU affinity mask, support up to 16 cores */ //CPU亲和力掩码,最多支持16核,亲和力很重要,多核情况下尽量一个任务在一个CPU核上运行,提高效率 UINT32 timerCpu; /**< CPU core number of this task is delayed or pended */ //此任务的CPU内核号被延迟或挂起 #if (LOSCFG_KERNEL_SMP_TASK_SYNC == YES) UINT32 syncSignal; /**< Synchronization for signal handling */ //用于CPU之间 同步信号 #endif #if (LOSCFG_KERNEL_SMP_LOCKDEP == YES) //死锁检测开关 LockDep lockDep; #endif #if (LOSCFG_KERNEL_SCHED_STATISTICS == YES) //调度统计开关,显然打开这个开关性能会受到影响,鸿蒙默认是关闭的 SchedStat schedStat; /**< Schedule statistics */ //调度统计 #endif #endif 鸿蒙内核支持多CPU,谁都知道多CPU当然好,效率高,快嘛,但凡事有两面性,在享受一个东西带来好处的同时,也得承担伴随它一起带来的麻烦和风险.多核有哪些的好处和麻烦,这里不展开说,后续有专门的文章和视频说明.任务可叫线程,或叫作业.CPU就是做作业的,多个CPU就是有多个能做作业的,一个作业能一鼓作气做完吗? 答案是:往往不行,因为现实不允许,作业可以有N多,而CPU数量非常有限,所以经常做着A作业被老板打断让去做B作业.这老板就是调度算法.A作业被打断回来接着做的还会是原来那个CPU吗? 答案是:不一定. 变量cpuAffiMask叫CPU亲和力,它的作用是可以指定A的作业始终是同一个CPU来完成, 也可以随便,交给调度算法,分到谁就谁来,这方面可以不挑. 第二大块:栈空间 VOID *stackPointer; /**< Task stack pointer */ //非用户模式下的栈指针 UINT32 stackSize; /**< Task stack size */ //非用户模式下栈大小 UINTPTR topOfStack; /**< Task stack top */ //非用户模式下的栈顶 bottom = top + size UINTPTR userArea; //使用区域,由运行时划定,根据运行态不同而不同 UINTPTR userMapBase; //用户模式下的栈底位置 UINT32 userMapSize; /**< user thread stack size ,real size : userMapSize + USER_STACK_MIN_SIZE */ 关于栈空间看过很多文章说每个任务都有自己独立的用户栈空间和内核栈空间,其实这种说法不严谨,至少在鸿蒙内核不能这么去理解,否则会有很多疑惑,解释不通的.CPU做作业就需要场地,栈空间就是给CPU做作业的场地.公司(内核设计者)规定用户的作业由用户提供场地, 用户场地就叫用户栈空间,但有些作业比较敏感不方便在用户现场完成,需要回到公司指定场地做,公司的场地就叫内核栈空间.所以准确的说法是每个任务都有自己独立的用户栈空间和共用一个内核栈空间.每个CPU在公司都有自己独立的办公位,不可能每个用户作业在公司内部有对应办公位的,明显的浪费内核资源,行不通的.而那些敏感作业就叫系统调用. 第三大块:资源竞争/同步 VOID *taskSem; /**< Task-held semaphore */ //task在等哪个信号量 VOID *taskMux; /**< Task-held mutex */ //task在等哪把锁 VOID *taskEvent; /**< Task-held event */ //task在等哪个事件 UINT32 eventMask; /**< Event mask */ //事件屏蔽 UINT32 eventMode; /**< Event mode */ //事件模式 FutexNode futex; //实现快锁功能 LOS_DL_LIST joinList; /**< join list */ //联结链表,允许任务之间相互释放彼此 LOS_DL_LIST lockList; /**< Hold the lock list */ //拿到了哪些锁链表 UINT32 signal; /**< Task signal */ //任务信号类型,(SIGNAL_NONE,SIGNAL_KILL,SIGNAL_SUSPEND,SIGNAL_AFFI) sig_cb sig; 公司的资源是有限的,CPU自己也是公司的资源,除了它还有其他的设备,比如做作业用的黑板,用户A,B,C都可能用到,狼多肉少,咋搞? 互斥量(taskMux,futex)能解决这个问题,办事前先拿锁,拿到了锁的爽了,没有拿到的就需要排队,在lockList上排,注意lockList是个双向链表,它是内核最重要的结构体,开篇就提过,没印象的看这篇 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体? < CSDN | OSCHINA | WeHarmony | 源动力 >,上面挂都是等锁进房间的西门大官人.这是互斥量的原理,解决任务间资源紧张的竞争性问题. 另外一个是用于任务的同步的信号量(sig_cb),任务和任务之间是会有关联的,现实生活中公司的A,B用户之间本身有业务往来的正常,CPU在帮B做作业的时候发现前置条件是需要A完成某项作业才能进行,这时B就需要主动让出CPU先办完A的事.这就是信号量的原理,解决的是任务间的同步问题. 第四大块:任务调度 前面说过了作业N多,做作业的只有几个人,单核CPU等于只有一个人干活.那要怎么分配CPU,就需要调度算法. UINT16 taskStatus; /**< Task status */ //各种状态标签,可以拥有多种标签,按位标识 UINT16 priority; /**< Task priority */ //任务优先级[0:31],默认是31级 UINT16 policy; //任务的调度方式(三种 .. LOS_SCHED_RR ) UINT16 timeSlice; /**< Remaining time slice *///剩余时间片 CHAR taskName[OS_TCB_NAME_LEN]; /**< Task name */ //任务的名称 LOS_DL_LIST pendList; /**< Task pend node */ //如果任务阻塞时就通过它挂到各种阻塞情况的链表上,比如OsTaskWait时 LOS_DL_LIST threadList; /**< thread list */ //挂到所属进程的线程链表上 SortLinkList sortList; /**< Task sortlink node */ //挂到cpu core 的任务执行链表上 是简单的先来后到(FIFO)吗? 当然也支持这个方式.鸿蒙内核用的是抢占式调度(policy),就是可以插队,比优先级(priority)大小,[0,31]级,数字越大的优先级越低,跟考试一样,排第一才是最牛的,鸿蒙排0的最牛! 想也想得到内核的任务优先级都是很高的,比如资源回收任务排第5,定时器任务排第0.够牛了吧.普通老百姓排多少呢?默认28级,惨!!! 另外任务有时间限制timeSlice,叫时间片,默认20ms,用完了会给你重置,发起重新调度,找出优先级高的执行,阻塞的任务(比如没拿到锁的,等信号量同步的,等读写消息队列的)都挂到pendList上,方便管理. 第五大块:任务间通讯 #if (LOSCFG_KERNEL_LITEIPC == YES) UINT32 ipcStatus; //IPC状态 LOS_DL_LIST msgListHead; //消息队列头结点,上面挂的都是任务要读的消息 BOOL accessMap[LOSCFG_BASE_CORE_TSK_LIMIT];//访问图,指的是task之间是否能访问的标识,LOSCFG_BASE_CORE_TSK_LIMIT 为任务池总数 #endif 这个很重要,解决任务间通讯问题,要知道进程负责的是资源的管理功能,什么意思?就是它不并负责内容的生产和消费,它只负责管理确保你的内容到达率和完整性.生产者和消费者始终是任务.进程管了哪些东西系列篇有专门的文章,自行翻看.liteipc是鸿蒙专有的通讯消息队列实现.简单说它是基于文件的,而传统的ipc消息队列是基于内存的.有什么区别也不在这里讨论,已有专门的文章分析. 第六大块:辅助工具 要知道任务对内核来说太重要了,是任务让CPU忙里忙外的,那中间出差错了怎么办,怎么诊断你问题出哪里了,就需要一些工具,比如死锁检测,比如占用CPU,内存监控 如下: #if (LOSCFG_KERNEL_SMP_LOCKDEP == YES) //死锁检测开关 LockDep lockDep; #endif #if (LOSCFG_KERNEL_SCHED_STATISTICS == YES) //调度统计开关,显然打开这个开关性能会受到影响,鸿蒙默认是关闭的 SchedStat schedStat; /**< Schedule statistics */ //调度统计 #endif 以上就是任务的五脏六腑,看清楚它鸿蒙内核的影像会清晰很多! 喜欢就请加点源动力吧 各大站点搜 "鸿蒙内核源码分析",快速找到组织. 或者来点更简单点的方式 鸿蒙内核源码注释>> 精读内核源码,中文注解分析,深挖地基工程,大脑永久记忆,四大码仓每日同步更新 鸿蒙内核源码分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新

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

鸿蒙内核源码分析(用栈方式篇) | 栈是构建底层运行的基础 | 中文注解HarmonyOS源码 | v20.03

鸿蒙内核源码注释中文版 < Gitee仓 | CSDN仓 | Github仓 | Coding仓 >精读内核源码,中文注解分析,深挖地基工程,构建底层网图,四大码仓每日同步更新 鸿蒙源码分析系列篇 < CSDN | OSCHINA | WeHarmony | 公众号 >问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新 精读内核源码就绕不过汇编语言,鸿蒙内核有6个汇编文件,读不懂它们就真的很难理解以下问题. 1.系统调用是如何实现的? 2.CPU是如何切换任务和进程上下文的? 3.硬件中断是如何处理的? 4.main函数到底是怎么来的? 5.开机最开始发生了什么? 6.关机最后的最后又发生了什么? 以下是一个很简单的C文件编译成汇编代码后的注解.读懂这些注解会发现汇编很可爱,甚至还会上瘾,并没有想象中的那么恐怖,读懂它会颠覆你对汇编和栈的认知. #include <stdio.h> #include <math.h> int square(int a,int b){ return a*b; } int fp(int b) { int a = 1; return square(a+b,a+b); } int main() { int sum = 1; for(int a = 0;a < 100; a++){ sum = sum + fp(a); } return sum; } //编译器: armv7-a clang (trunk) square(int, int): sub sp, sp, #8 @sp减去8,意思为给square分配栈空间,只用2个栈空间完成计算 str r0, [sp, #4] @第一个参数入栈 str r1, [sp] @第二个参数入栈 ldr r1, [sp, #4] @取出第一个参数给r1 ldr r2, [sp] @取出第二个参数给r2 mul r0, r1, r2 @执行a*b给R0,返回值的工作一直是交给R0的 add sp, sp, #8 @函数执行完了,要释放申请的栈空间 bx lr @子程序返回,等同于mov pc,lr,即跳到调用处 fp(int): push {r11, lr} @r11(fp)/lr入栈,保存调用者main的位置 mov r11, sp @r11用于保存sp值,函数栈开始位置 sub sp, sp, #8 @sp减去8,意思为给fp分配栈空间,只用2个栈空间完成计算 str r0, [sp, #4] @先保存参数值,放在SP+4,此时r0中存放的是参数 mov r0, #1 @r0=1 str r0, [sp] @再把1也保存在SP的位置 ldr r0, [sp] @把SP的值给R0 ldr r1, [sp, #4] @把SP+4的值给R1 add r1, r0, r1 @执行r1=a+b mov r0, r1 @r0=r1,用r0,r1传参 bl square(int, int)@先mov lr, pc 再mov pc square(int, int) mov sp, r11 @函数执行完了,要释放申请的栈空间 pop {r11, lr} @弹出r11和lr,lr是专用标签,弹出就自动复制给lr寄存器 bx lr @子程序返回,等同于mov pc,lr,即跳到调用处 main: push {r11, lr} @r11(fp)/lr入栈,保存调用者的位置 mov r11, sp @r11用于保存sp值,函数栈开始位置 sub sp, sp, #16 @sp减去8,意思为给main分配栈空间,只用2个栈空间完成计算 mov r0, #0 @初始化r0 str r0, [r11, #-4] @作用是保存SUM的初始值 str r0, [sp, #8] @sum将始终占用SP+8的位置 str r0, [sp, #4] @a将始终占用SP+4的位置 b .LBB1_1 @跳到循环开始位置 .LBB1_1: @循环开始位置入口 ldr r0, [sp, #4] @取出a的值给r0 cmp r0, #99 @跟99比较 bgt .LBB1_4 @大于99,跳出循环 mov pc .LBB1_4 b .LBB1_2 @继续循环,直接 mov pc .LBB1_2 .LBB1_2: @符合循环条件入口 ldr r0, [sp, #8] @取出sum的值给r0,sp+8用于写SUM的值 str r0, [sp] @先保存SUM的值,SP的位置用于读SUM值 ldr r0, [sp, #4] @r0用于传参,取出A的值给r0作为fp的参数 bl fp(int) @先mov lr, pc再mov pc fp(int) mov r1, r0 @fp的返回值为r0,保存到r1 ldr r0, [sp] @取出SUM的值 add r0, r0, r1 @计算新sum的值,由R0保存 str r0, [sp, #8] @将新sum保存到SP+8的位置 b .LBB1_3 @无条件跳转,直接 mov pc .LBB1_3 .LBB1_3: @完成a++操作入口 ldr r0, [sp, #4] @SP+4中记录是a的值,赋给r0 add r0, r0, #1 @r0增加1 str r0, [sp, #4] @把新的a值放回SP+4里去 b .LBB1_1 @跳转到比较 a < 100 处 .LBB1_4: @循环结束入口 ldr r0, [sp, #8] @最后SUM的结果给R0,返回值的工作一直是交给R0的 mov sp, r11 @函数执行完了,要释放申请的栈空间 pop {r11, lr} @弹出r11和lr,lr是专用标签,弹出就自动复制给lr寄存器 bx lr @子程序返回,跳转到lr处等同于 MOV PC, LR 这个简单的汇编并不是鸿蒙的汇编,只是先打个底,由浅入深, 但看懂了它基本理解鸿蒙汇编代码没有问题, 后续将详细分析鸿蒙内核各个汇编文件的作用. 开始分析上面的汇编代码. 第一: 上面的代码和鸿蒙内核用栈方式一样,都采用了递减满栈的方式, 什么是递减满栈? 递减指的是栈底地址高于栈顶地址,满栈指的是SP指针永远在栈顶.一定要理解递减满栈,否则读不懂内核汇编代码.举例说明: square(int, int): sub sp, sp, #8 @sp减去8,意思为给square分配栈空间,只用2个栈空间完成计算 str r0, [sp, #4] @第一个参数入栈 str r1, [sp] @第二个参数入栈 ldr r1, [sp, #4] @取出第一个参数给r1 ldr r2, [sp] @取出第二个参数给r2 mul r0, r1, r2 @执行a*b给R0,返回值的工作一直是交给R0的 add sp, sp, #8 @函数执行完了,要释放申请的栈空间 bx lr @子程序返回,等同于mov pc,lr,即跳到调用处 首句汇编的含义就是申请栈空间, sp = sp - 8 ,一个栈内单元(栈空间)占4个字节,申请2个栈空间搞定函数的计算,仔细看下代码除了在函数的末尾 sp = sp + 8 又恢复在之前的位置的中间过程,SP的值是没有任务变化,它的指向是不动的, 这跟很多人对栈的认知是不一样的,它只是被用于计算,例如 ldr r1, [sp, #4] 的意思是取出SP+4这个虚拟地址的值给r1寄存器,SP的值并没有改变的,为什么要+呢,因为SP是指向栈顶的,地址是最小的. 满栈就是用栈过程中对地址的操作不能超过SP,所以你很少在计算过程中看到 把sp-4地址中的值给某个寄存器, 除非是特别的指令,否则不可能有这样的指令. 第二: sub sp, sp, #8 和 add sp, sp, #8 是成对出现的,这就跟申请内存,释放内存的道理一样,这是内核对任务的运行栈管理方式,一样用多少申请多少,用完释放.空间大小就是栈帧,这是栈帧的本质含义. 第三: push {r11, lr} 和 pop {r11, lr} 也是成对出现的,主要是用于函数调用,例如 A -> B, B要保存A的栈帧范围和指令位置, lr保存是是A函数执行到哪个指令的位置, r11干了fp的工作,其实就是指向 A的栈顶位置,如此B执行完后return回A的时候,先mov pc,lr 内核就知道改执行A的哪条指令了,同时又知道了A的栈顶位置. 第四: 频繁出现的R0寄存器的作用用于传参和返回值, A调用B之前,假如有两个参数,就把参数给r0 ,r1记录,充当了A的变量, 到了B中后,先让 r0,r1入栈,目的是保存参数值, 因为 B中要用r0,r1 ,他们变成B的变量用了. 返回值都是默认统一给r0保存. B中将返回值给r0,回到A中取出R0值对A来说这就是B的返回值. 这是以上为汇编代码的分析,追问两个问题 第一:如果是可变参数怎么办? 100个参数怎么整, 通过寄存器总共就12个,不够传参啊 第二:返回值可以有多个吗? 请在评论区留言 喜欢就点赞+关注下吧 8-) 您的关注真的很重要 :) 作者邮箱:weharmony@126.com 鸿蒙内核源码注释中文版 < Gitee仓 | CSDN仓 | Github仓 | Coding仓 >精读内核源码,中文注解分析,深挖地基工程,构建底层网图,四大码仓每日同步更新 鸿蒙源码分析系列篇 < CSDN | OSCHINA | WeHarmony | 公众号 >问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新

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

鸿蒙内核源码分析(GN应用篇) | GN语法及在鸿蒙的使用 | 百篇博客分析HarmonyOS源码 | v60.01

OpenHarmony | 鸿蒙研究站 | WeHarmony < 国内 | 国外 > 百篇博客系列篇.本篇为: v60.xx 鸿蒙内核源码分析(GN应用篇) | GN语法及在鸿蒙的使用 | 51 .c .h .o 编译模块相关篇为: v59.xx 鸿蒙内核源码分析(构建工具篇) | 顺瓜摸藤调试鸿蒙构建过程 | 51 .c .h .o v58.xx 鸿蒙内核源码分析(环境脚本篇) | 有了它编译鸿蒙好简单 | 51 .c .h .o v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 51 .c .h .o v50.xx 鸿蒙内核源码分析(编译环境篇) | docker编译鸿蒙真的很香 | 51 .c .h .o GN是什么? GN 是一个为 Ninja 生成构建文件的元构建系统。 为什么会有GN,说是有个叫even的谷歌负责构建系统的工程师在使用传统的makefile构建chrome时觉得太麻烦,不高效,所以设计了一套更简单,更高效新的构建工具,然后就被广泛的使用了. GN语法和配置 gn 有大量的内置变量和库函数,熟悉这些库函数基本就知道gn能干什么,gn的官方文档很齐全,前往 GN参考手册翻看查找 . gn的语法很简单,了解以下几点基本就能看懂gn代码.重点了解下函数的调用方式,和其他高级语言不太一样. 字符串 a = "mypath" b = "$a/foo.cc" # b -> "mypath/foo.cc" c = "foo${a}bar.cc" # c -> "foomypathbar.cc" 列表 a = [ "first" ] a += [ "second" ] # [ "first", "second" ] a += [ "third", "fourth" ] # [ "first", "second", "third", "fourth" ] b = a + [ "fifth" ] # [ "first", "second", "third", "fourth", "fifth" ] 条件语句 if (is_linux || (is_win && target_cpu == "x86")) { sources -= [ "something.cc" ] }else { ... } 循环 foreach(i, mylist) { print(i) # Note: i is a copy of each element, not a reference to it. } 函数调用 print("hello, world") assert(is_win, "This should only be executed on Windows") # 如果is_win为真,就打印后面的内容 static_library("mylibrary") { sources = [ "a.cc" ] } 解读: print,assert,static_library都是库函数,或者叫内置函数. static_library的调用有点奇怪,它是个函数,函数内部会使用sources这个内置变量,sources = [ "a.cc" ]相当于先传参给这个函数的内置变量,并调用这个函数.学习ng得习惯这种语法方式,被大量的使用. 模板 | Templates gn提供了很多内置函数,使用偏傻瓜式,若不是太复杂的系统熟悉这些内置函数,往里填空写字就可以了,如果想自己定义函数怎么办? 答案是模板,模板就是自定义函数. #定义模板, 例如:新建文件 //tools/idl_compiler.gni template("idl") { #自定义一个名称为 "idl"的函数 source_set(target_name) { #调用内置函数 source_set sources = invoker.sources #invoker为内置变量,含义为调用者内容 即:[ "a", "b" ]的内容 } } #后缀.gni 代表这是一个 gn import file import("//tools/idl_compiler.gni") #先import模板,类似 C语言的 #include idl("my_interfaces") { #等同于调用 idl sources = [ "a", "b" ] #给idl传参, 参数的接收方是 invoker.sources } 明白了模板的使用,阅读鸿蒙gn代码就不会有太大的障碍. 目标项 | Targets 目标是构建图中的一个节点。它通常表示将生成某种可执行文件或库文件。整个构建是由一个个的目标组成. 目标包含: action: 运行脚本以生成文件 executable: 生成可执行文件 group: 生成依赖关系组 shared_library: 生成.dll或.so动态链接库 static_library: 生成.lib或.a 静态链接库 ... 配置项 | Configs 记录完成目标项所需的配置信息,例如: config("myconfig") {#创建一个标签为`myconfig`的配置项 include_dirs = [ "include/common" ] defines = [ "ENABLE_DOOM_MELON" ] } executable("mything") {#生成可执行文件 configs = [ ":myconfig" ]#使用标签为`myconfig`的配置项来生成目标文件 } GN在鸿蒙中的使用 有了以上基础铺垫,正式开始gn在openharomny的使用. 从哪开始 在构建工具篇中已经说清楚了 hb的python部分有个工作任务是生成gn参数,执行命令行. /home/tools/gn gen /home/openharmony/code-v1.1.1-LTS/out/hispark_aries/ipcamera_hispark_aries \ --root=/home/openharmony/code-v1.1.1-LTS \ --dotfile=/home/openharmony/code-v1.1.1-LTS/build/lite/.gn \ --script-executable=python3 \ '--args=ohos_build_type="debug" ohos_build_compiler_specified="clang" ohos_build_compiler_dir="/home/tools/llvm" product_path="/home/openharmony/code-v1.1.1-LTS/vendor/hisilicon/hispark_aries" device_path="/home/openharmony/code-v1.1.1-LTS/device/hisilicon/hispark_aries/sdk_liteos" ohos_kernel_type="liteos_a" enable_ohos_appexecfwk_feature_ability = false ohos_full_compile=true' 解读 root,dotfile,script-executable是gn内置的固定参数,一切从dotfile指向的文件开始.即build/lite/.gn 相当于main()函数的作用, 打开 build/lite/.gn看下内容,只有两句,先配置好内部参数再工作. # The location of the build configuration file. buildconfig = "//build/lite/config/BUILDCONFIG.gn" #1.完成GN的配置工作 # The source root location. root = "//build/lite" #2.完成GN的编译工作 args为用户自定义的参数,它们将会在解析BUILD.gn,BUILDCONFIG.gn过程中被使用. BUILDCONFIG.gn | 构建配置项 BUILDCONFIG.gn为BUILD.gn做准备,填充好编译所需的配置信息.即生成配置项 详细请查看 build/lite/config/BUILDCONFIG.gn 文件全部内容,本篇只贴出部分. import("//build/lite/ohos_var.gni") import("${device_path}/config.gni") .... arch = "arm" if (ohos_kernel_type == "liteos_a") { target_triple = "$arch-liteos" } else if (ohos_kernel_type == "linux") { target_triple = "$arch-linux-ohosmusl" } ... template("executable") { #生成可执行文件 executable(target_name) { forward_variables_from(invoker, Variables_Executable) if (!defined(invoker.deps)) { deps = [ "//build/lite:prebuilts" ] } else { deps += [ "//build/lite:prebuilts" ] } if (defined(invoker.configs)) { configs = [] configs += invoker.configs } } } set_defaults("executable") {#设置目标类型的默认值 configs = default_executable_configs configs += [ "//build/lite/config:board_exe_ld_flags" ] } ... 解读 ${device_path}为命令行参数,本篇为home/openharmony/code-v1.1.1-LTS/device/hisilicon/hispark_aries/sdk_liteos 查看构建-配置内容,BUILDCONFIG主要任务就是对其中的内置变量赋值.例如: 指定编译器 clang, 如何生成可执行文件的方法 BUILD.gn | 启动构建 查看 build/lite/BUILD.gn 文件 #目的是要得到项目各个模块的编译入口 group("ohos") { deps = [] if (ohos_build_target == "") { # Step 1: Read product configuration profile. # 第一步:读取配置文件product_path的值来源于根目录的ohos_config.json,如下,内容由 hb set 命令生成 # { # "root_path": "/home/openharmony", # "board": "hispark_aries", # "kernel": "liteos_a", # "product": "ipcamera_hispark_aries", # "product_path": "/home/openharmony/vendor/hisilicon/hispark_aries", # "device_path": "/home/openharmony/device/hisilicon/hispark_aries/sdk_liteos", # "patch_cache": null #} product_cfg = read_file("${product_path}/config.json", "json") # Step 2: Loop subsystems configured by product. # 第二步:循环处理各自子系统,config.json中子系统部分格式如下hb #"subsystems": [ # { # "subsystem": "aafwk", # "components": [ # { "component": "ability", "features":[ "enable_ohos_appexecfwk_feature_ability = false" ] } # ] # }, # ... # { # "subsystem": "distributed_schedule", # "components": [ # { "component": "system_ability_manager", "features":[] }, # { "component": "foundation", "features":[] }, # { "component": "distributed_schedule", "features":[] } # ] # }, # { # "subsystem": "kernel", # "components": [ # { "component": "liteos_a", "features":[] } # ] # }, #] foreach(product_configed_subsystem, product_cfg.subsystems) {#对子系统数组遍历操作 subsystem_name = product_configed_subsystem.subsystem #读取一个子系统 aafwk,hiviewdfx,security == subsystem_info = { } # Step 3: Read OS subsystems profile. # 第三步: 读取各个子系统的配置文件 subsystem_info = read_file("//build/lite/components/${subsystem_name}.json", "json") # Step 4: Loop components configured by product. # 第四步: 循环读取子系统内各控件的配置信息 # 此处以内核为例://build/lite/components/kernel.json" # "components": [ # { # "component": "liteos_a", # 组件名称 # "description": "liteos-a kernel", # 组件一句话功能描述 # "optional": "false", # 组件是否为最小系统必选 # "dirs": [ # 组件源码路径 # "kernel/liteos_a" # ], # "targets": [ # 组件编译入口 # "//kernel/liteos_a:kernel" # ], # "rom": "1.98MB", # 组件ROM值 # "ram": "", # 组件RAM估值 # "output": [ # 组件编译输出 # "liteos.bin" # ], # "adapted_board": [ # 组件已适配的主板 # "hispark_aries", # "hispark_taurus", # "hi3518ev300", # "hi3516dv300", # ], # "adapted_kernel": [ "liteos_a" ], # 组件已适配的内核 # "features": [], # 组件可配置的特性 # "deps": { # "components": [], # 组件依赖的其他组件 # "third_party": [ # 组件依赖的三方开源软件 # "FreeBSD", # "musl", # "zlib", # "FatFs", # "Linux_Kernel", # "lwip", # "NuttX", # "mtd-utils" # ] # } # }, # ] foreach(product_configed_component, product_configed_subsystem.components) { #遍历项目控件数组 # Step 5: Check whether the component configured by product is exist. # 第五步: 检查控件配置信息是否存在 component_found = false #初始为不存在 foreach(system_component, subsystem_info.components) {#项目控件和子系统中的控件遍历对比 if (product_configed_component.component == system_component.component) { #找到了liteos_a component_found = true } } #如果没找到的信息,则打印项目控件查找失败日志 assert( component_found, "Component \"${product_configed_component.component}\" not found" + ", please check your product configuration.") # Step 6: Loop OS components and check validity of product configuration. # 第六步: 检查子系统控件的有效性并遍历控件组,处理各个控件 foreach(component, subsystem_info.components) { kernel_valid = false #检查内核 board_valid = false #检查开发板 # Step 6.1: Skip component which not configured by product. if (component.component == product_configed_component.component) { # Step 6.1.1: Loop OS components adapted kernel type. foreach(component_adapted_kernel, component.adapted_kernel) { if (component_adapted_kernel == product_cfg.kernel_type && kernel_valid == false) { #内核检测是否已适配 kernel_valid = true } } # 如果内核未适配,则打印未适配日志 assert( kernel_valid, "Invalid component configed, ${subsystem_name}:${product_configed_component.component} " + "not available for kernel: ${product_cfg.kernel_type}!") # Step 6.1.2: Add valid component for compiling. # 添加有效组件进行编译 foreach(component_target, component.targets) {//遍历组件的编译入口 deps += [ component_target ] #添加到编译列表中 } } } } } # Step 7: Add device and product target by default. # 第七步: 添加设备和项目的编译单元 # "product_path": "/home/openharmony/vendor/hisilicon/hispark_aries", # "device_path": "/home/openharmony/device/hisilicon/hispark_aries/sdk_liteos", deps += [ "${device_path}/../", #添加 //device/hisilicon/hispark_aries 进入编译项 "${product_path}" #添加 //vendor/hisilicon/hispark_aries 进入编译项 ] } else {#编译指定的组件,例如 hb build -T targetA&&targetB deps += string_split(ohos_build_target, "&&") } } 解读 有三个概念贯彻整个鸿蒙系统,子系统(subsystems),组件(components),功能(features).理解它们的定位和特点是解读鸿蒙的关键所在. 先找到product_path下的 配置文件 config.json,里面配置了项目所要使用的子系统和组件. 再遍历项目所使用的组件是否能再 //build/lite/components/*.json组件集中能找到. 将找到的组件targets加入到编译列表deps中.targets指向了要编译的组件目录.例如内核组件时指向了://kernel/liteos_a:kernel, import("//build/lite/config/component/lite_component.gni") #组件模板函数 import("//build/lite/config/subsystem/lite_subsystem.gni") #子系统模板函数 lite_subsystem("kernel") {#编译内核子系统/组件入口 subsystem_components = [] if (enable_ohos_kernel_liteos_a_ext_build == false) { subsystem_components += [ "//kernel/liteos_a/kernel", "//kernel/liteos_a/net", "//kernel/liteos_a/lib", "//kernel/liteos_a/compat", "//kernel/liteos_a/fs", "//kernel/liteos_a/arch:platform_cpu", ] if (LOSCFG_SHELL) { subsystem_components += [ "//kernel/liteos_a/shell" ] } } else { deps = [ ":make" ] } } lite_subsystem是个模板函数(自定义函数),再查看lite_subsystem.gni函数原型,它的目的只有一个填充 deps,deps是私有链接依赖关系,最终会形成一颗依赖树.gn会根据这些依赖关系生成最终的.ninja文件. # 定义一个子系统 # lite_subsystem template模板定义了子系统中包含的所有模块 # 参数 # subsystem_components (必须)) # [范围列表] 定义子系统的所有模块. template("lite_subsystem") { assert(defined(invoker.subsystem_components), "subsystem_components in required.") lite_subsystem_components = invoker.subsystem_components group(target_name) { deps = [] if(defined(invoker.deps)) { deps += invoker.deps } # add subsystem packages foreach(pkg_label, lite_subsystem_components) { deps += [ pkg_label ] } } } 生成了哪些文件 执行后ng gen 会生成如下文件和目录 turing@ubuntu:/home/openharmony/code-v1.1.1-LTS/out/hispark_aries/ipcamera_hispark_aries$ ls args.gn build.ninja build.ninja.d NOTICE_FILE obj test_info toolchain.ninja build.ninja.d中记录依赖的 BUILD.gn文件路径 build.ninja: ../../../base/global/resmgr_lite/frameworks/resmgr_lite/BUILD.gn \ ../../../base/hiviewdfx/hilog_lite/frameworks/featured/BUILD.gn \ ../../../base/hiviewdfx/hilog_lite/services/apphilogcat/BUILD.gn \ .... ng根据这些组件的BUILD.gn在obj目录下对应生成了每个组件的.ninja文件.此处列出鸿蒙L1所有的 .ninja文件, 具体ninja是如何编译成最终的库和可执行文件的,将在后续篇中详细介绍其语法和应用. turing@ubuntu:/home/openharmony/code-v1.1.1-LTS/out/hispark_aries/ipcamera_hispark_aries/obj$ tree ├── base │ ├── global │ │ └── resmgr_lite │ │ └── frameworks │ │ └── resmgr_lite │ │ └── global_resmgr.ninja │ ├── hiviewdfx │ │ └── hilog_lite │ │ ├── frameworks │ │ │ └── featured │ │ │ ├── hilog_shared.ninja │ │ │ └── hilog_static.ninja │ │ └── services │ │ ├── apphilogcat │ │ │ ├── apphilogcat.ninja │ │ │ └── apphilogcat_static.ninja │ │ └── hilogcat │ │ ├── hilogcat.ninja │ │ └── hilogcat_static.ninja │ ├── security │ │ ├── appverify │ │ │ └── interfaces │ │ │ └── innerkits │ │ │ └── appverify_lite │ │ │ ├── products │ │ │ │ └── ipcamera │ │ │ │ └── verify_base.ninja │ │ │ ├── unittest │ │ │ │ └── app_verify_test.ninja │ │ │ └── verify.ninja │ │ ├── deviceauth │ │ │ └── frameworks │ │ │ └── deviceauth_lite │ │ │ └── source │ │ │ └── hichainsdk.ninja │ │ ├── huks │ │ │ └── frameworks │ │ │ └── huks_lite │ │ │ └── source │ │ │ └── huks.ninja │ │ └── permission │ │ └── services │ │ └── permission_lite │ │ ├── ipc_auth │ │ │ └── ipc_auth_target.ninja │ │ ├── pms │ │ │ └── pms_target.ninja │ │ ├── pms_base │ │ │ └── pms_base.ninja │ │ └── pms_client │ │ └── pms_client.ninja │ └── startup │ ├── appspawn_lite │ │ └── services │ │ ├── appspawn.ninja │ │ └── test │ │ └── unittest │ │ └── common │ │ └── appspawn_test.ninja │ ├── bootstrap_lite │ │ └── services │ │ └── source │ │ └── bootstrap.ninja │ ├── init_lite │ │ └── services │ │ ├── init.ninja │ │ └── test │ │ └── unittest │ │ └── common │ │ └── init_test.ninja │ └── syspara_lite │ └── frameworks │ ├── parameter │ │ └── src │ │ └── sysparam.ninja │ ├── token │ │ └── token_shared.ninja │ └── unittest │ └── parameter │ └── ParameterTest.ninja ├── build │ └── lite │ └── config │ └── component │ ├── cJSON │ │ ├── cjson_shared.ninja │ │ └── cjson_static.ninja │ ├── openssl │ │ ├── openssl_shared.ninja │ │ └── openssl_static.ninja │ └── zlib │ ├── zlib_shared.ninja │ └── zlib_static.ninja ├── drivers │ ├── adapter │ │ └── uhdf │ │ ├── manager │ │ │ └── hdf_core.ninja │ │ ├── platform │ │ │ └── hdf_platform.ninja │ │ ├── posix │ │ │ └── hdf_posix_osal.ninja │ │ └── test │ │ └── unittest │ │ ├── common │ │ │ └── hdf_test_common.ninja │ │ ├── config │ │ │ └── hdf_adapter_uhdf_test_config.ninja │ │ ├── manager │ │ │ ├── hdf_adapter_uhdf_test_door.ninja │ │ │ ├── hdf_adapter_uhdf_test_ioservice.ninja │ │ │ ├── hdf_adapter_uhdf_test_manager.ninja │ │ │ └── hdf_adapter_uhdf_test_sbuf.ninja │ │ ├── osal │ │ │ └── hdf_adapter_uhdf_test_osal.ninja │ │ └── platform │ │ └── hdf_adapter_uhdf_test_platform.ninja │ └── peripheral │ ├── input │ │ └── hal │ │ └── hdi_input.ninja │ └── wlan │ ├── client │ │ └── wifi_driver_client.ninja │ ├── hal │ │ └── wifi_hal.ninja │ └── test │ ├── performance │ │ └── hdf_peripheral_wlan_test_performance.ninja │ └── unittest │ └── hdf_peripheral_wlan_test.ninja ├── foundation │ ├── aafwk │ │ └── aafwk_lite │ │ ├── frameworks │ │ │ ├── ability_lite │ │ │ │ └── ability.ninja │ │ │ ├── abilitymgr_lite │ │ │ │ └── abilitymanager.ninja │ │ │ └── want_lite │ │ │ └── want.ninja │ │ └── services │ │ └── abilitymgr_lite │ │ ├── abilityms.ninja │ │ ├── tools │ │ │ └── aa.ninja │ │ └── unittest │ │ └── test_lv0 │ │ └── page_ability_test │ │ └── ability_test_pageAbilityTest_lv0.ninja │ ├── ai │ │ └── engine │ │ ├── services │ │ │ ├── client │ │ │ │ ├── ai_client.ninja │ │ │ │ ├── client_executor │ │ │ │ │ └── client_executor.ninja │ │ │ │ └── communication_adapter │ │ │ │ └── ai_communication_adapter.ninja │ │ │ ├── common │ │ │ │ ├── platform │ │ │ │ │ ├── dl_operation │ │ │ │ │ │ └── dlOperation.ninja │ │ │ │ │ ├── event │ │ │ │ │ │ └── event.ninja │ │ │ │ │ ├── lock │ │ │ │ │ │ └── lock.ninja │ │ │ │ │ ├── os_wrapper │ │ │ │ │ │ └── ipc │ │ │ │ │ │ └── aie_ipc.ninja │ │ │ │ │ ├── semaphore │ │ │ │ │ │ └── semaphore.ninja │ │ │ │ │ ├── threadpool │ │ │ │ │ │ └── threadpool.ninja │ │ │ │ │ └── time │ │ │ │ │ └── time.ninja │ │ │ │ ├── protocol │ │ │ │ │ └── data_channel │ │ │ │ │ └── data_channel.ninja │ │ │ │ └── utils │ │ │ │ └── encdec │ │ │ │ └── encdec.ninja │ │ │ └── server │ │ │ ├── ai_server.ninja │ │ │ ├── communication_adapter │ │ │ │ └── ai_communication_adapter.ninja │ │ │ ├── plugin_manager │ │ │ │ └── plugin_manager.ninja │ │ │ └── server_executor │ │ │ └── server_executor.ninja │ │ └── test │ │ ├── common │ │ │ ├── ai_test_common.ninja │ │ │ └── dl_operation │ │ │ └── dl_operation_so │ │ │ └── dlOperationSo.ninja │ │ ├── function │ │ │ ├── ai_test_function.ninja │ │ │ └── death_callback │ │ │ ├── testDeathCallbackLibrary.ninja │ │ │ └── testDeathCallback.ninja │ │ ├── performance │ │ │ └── ai_test_performance_unittest.ninja │ │ └── sample │ │ ├── asyncDemoPluginCode.ninja │ │ ├── sample_plugin_1.ninja │ │ ├── sample_plugin_2.ninja │ │ └── syncDemoPluginCode.ninja │ ├── appexecfwk │ │ └── appexecfwk_lite │ │ ├── frameworks │ │ │ └── bundle_lite │ │ │ └── bundle.ninja │ │ └── services │ │ └── bundlemgr_lite │ │ ├── bundle_daemon │ │ │ └── bundle_daemon.ninja │ │ ├── bundlems.ninja │ │ └── tools │ │ └── bm.ninja │ ├── communication │ │ ├── ipc_lite │ │ │ └── liteipc_adapter.ninja │ │ └── softbus_lite │ │ └── softbus_lite.ninja │ ├── distributedschedule │ │ ├── dmsfwk_lite │ │ │ ├── dmslite.ninja │ │ │ └── moduletest │ │ │ └── dtbschedmgr_lite │ │ │ └── distributed_schedule_test_dms.ninja │ │ ├── safwk_lite │ │ │ └── foundation.ninja │ │ └── samgr_lite │ │ ├── communication │ │ │ └── broadcast │ │ │ └── broadcast.ninja │ │ ├── samgr │ │ │ ├── adapter │ │ │ │ └── samgr_adapter.ninja │ │ │ ├── samgr.ninja │ │ │ └── source │ │ │ └── samgr_source.ninja │ │ ├── samgr_client │ │ │ └── client.ninja │ │ ├── samgr_endpoint │ │ │ ├── endpoint_source.ninja │ │ │ └── store_source.ninja │ │ └── samgr_server │ │ └── server.ninja │ ├── graphic │ │ ├── surface │ │ │ ├── surface.ninja │ │ │ └── test │ │ │ └── lite_surface_unittest.ninja │ │ ├── ui │ │ │ └── ui.ninja │ │ ├── utils │ │ │ ├── graphic_hals.ninja │ │ │ ├── graphic_utils.ninja │ │ │ └── test │ │ │ ├── graphic_test_color.ninja │ │ │ ├── graphic_test_container.ninja │ │ │ ├── graphic_test_geometry2d.ninja │ │ │ ├── graphic_test_math.ninja │ │ │ └── graphic_test_style.ninja │ │ └── wms │ │ ├── wms_client.ninja │ │ └── wms_server.ninja │ └── multimedia │ ├── audio_lite │ │ └── frameworks │ │ └── audio_capturer_lite.ninja │ ├── camera_lite │ │ └── frameworks │ │ └── camera_lite.ninja │ ├── media_lite │ │ ├── frameworks │ │ │ ├── player_lite │ │ │ │ └── player_lite.ninja │ │ │ └── recorder_lite │ │ │ └── recorder_lite.ninja │ │ ├── interfaces │ │ │ └── kits │ │ │ └── player_lite │ │ │ └── js │ │ │ └── builtin │ │ │ └── audio_lite_api.ninja │ │ └── services │ │ └── media_server.ninja │ └── utils │ └── lite │ └── media_common.ninja ├── test │ ├── developertest │ │ ├── examples │ │ │ └── lite │ │ │ └── cxx_demo │ │ │ └── test │ │ │ └── unittest │ │ │ └── common │ │ │ └── CalcSubTest.ninja │ │ └── third_party │ │ └── lib │ │ └── cpp │ │ ├── gtest_main.ninja │ │ └── gtest.ninja │ └── xts │ ├── acts │ │ ├── aafwk_lite │ │ │ └── ability_posix │ │ │ └── module_ActsAbilityMgrTest.ninja │ │ ├── ai_lite │ │ │ └── ai_engine_posix │ │ │ └── base │ │ │ ├── module_ActsAiEngineTest.ninja │ │ │ └── src │ │ │ └── sample │ │ │ ├── asyncDemoPluginCode.ninja │ │ │ ├── sample_plugin_1_sync.ninja │ │ │ ├── sample_plugin_2_async.ninja │ │ │ └── syncDemoPluginCode.ninja │ │ ├── appexecfwk_lite │ │ │ └── bundle_mgr_posix │ │ │ └── module_ActsBundleMgrTest.ninja │ │ ├── communication_lite │ │ │ ├── lwip_posix │ │ │ │ └── module_ActsLwipTest.ninja │ │ │ └── softbus_posix │ │ │ └── module_ActsSoftBusTest.ninja │ │ ├── distributed_schedule_lite │ │ │ └── samgr_posix │ │ │ └── module_ActsSamgrTest.ninja │ │ ├── graphic_lite │ │ │ ├── graphic_utils │ │ │ │ ├── a │ │ │ │ │ └── module_ActsUiInterfaceTest1.ninja │ │ │ │ ├── color_posix │ │ │ │ │ └── module_ActsColorTest.ninja │ │ │ │ ├── geometry2d_posix │ │ │ │ │ └── module_ActsGeometyr2dTest.ninja │ │ │ │ ├── graphic_math_posix │ │ │ │ │ └── module_ActsGraphicMathTest.ninja │ │ │ │ ├── heap_base_posix │ │ │ │ │ └── module_ActsHeapBaseTest.ninja │ │ │ │ ├── list_posix │ │ │ │ │ └── module_ActsListTest.ninja │ │ │ │ ├── mem_api_posix │ │ │ │ │ └── module_ActsGraphMemApiTest.ninja │ │ │ │ ├── rect_posix │ │ │ │ │ └── module_ActsRectTest.ninja │ │ │ │ ├── transform_posix │ │ │ │ │ └── module_ActsTransformTest.ninja │ │ │ │ └── version_posix │ │ │ │ └── module_ActsGraphVersionTest.ninja │ │ │ ├── surface │ │ │ │ └── surface_posix │ │ │ │ └── module_ActsSurfaceTest.ninja │ │ │ └── ui │ │ │ ├── a │ │ │ │ └── module_ActsUiInterfaceTest.ninja │ │ │ ├── animator_posix │ │ │ │ └── module_ActsAnimatorTest.ninja │ │ │ ├── easing_equation_posix │ │ │ │ └── module_ActsEasingEquationTest.ninja │ │ │ ├── events_posix │ │ │ │ └── module_ActsEventsTest.ninja │ │ │ ├── flexlayout_posix │ │ │ │ └── module_ActsFlexlaoutTest.ninja │ │ │ ├── gridlayout_posix │ │ │ │ └── module_ActsGridLayoutTest.ninja │ │ │ ├── image_posix │ │ │ │ └── module_ActsImageTest.ninja │ │ │ ├── interpolation_posix │ │ │ │ └── module_ActsInterpoliationTest.ninja │ │ │ ├── layout_posix │ │ │ │ └── module_ActsLayoutTest.ninja │ │ │ ├── listlayout_posix │ │ │ │ └── module_ActsListlayoutTest.ninja │ │ │ ├── screen_posix │ │ │ │ └── module_ActsScreenTest.ninja │ │ │ ├── style_posix │ │ │ │ └── module_ActsStyleTest.ninja │ │ │ ├── theme_posix │ │ │ │ └── module_ActsThemeTest.ninja │ │ │ ├── ui_abstract_progress_posix │ │ │ │ └── module_ActsUIAbstractProgressTest.ninja │ │ │ ├── ui_analog_clock_posix │ │ │ │ └── module_ActsUIAnalogClockTest.ninja │ │ │ ├── uianimator_posix │ │ │ │ └── module_ActsUIAnimatorTest.ninja │ │ │ ├── ui_arc_lable_posix │ │ │ │ └── module_ActsUIArcLabelTest.ninja │ │ │ ├── ui_axis_posix │ │ │ │ └── module_ActsUIAxisTest.ninja │ │ │ ├── ui_box_porgress_posix │ │ │ │ └── module_ActsUIBoxProgressTest.ninja │ │ │ ├── ui_button_posix │ │ │ │ └── module_ActsUIButtonTest.ninja │ │ │ ├── ui_canvas_posix │ │ │ │ └── module_ActsUICanvasTest.ninja │ │ │ ├── ui_chart_posix │ │ │ │ └── module_ActsUIChartTest.ninja │ │ │ ├── ui_checbox_posix │ │ │ │ └── module_ActsUICheckboxTest.ninja │ │ │ ├── ui_circle_progress_posix │ │ │ │ └── module_ActsUICircleProgressTest.ninja │ │ │ ├── ui_digital_clock_posix │ │ │ │ └── module_ActsUIDigitalClockTest.ninja │ │ │ ├── ui_image_animator_posix │ │ │ │ └── module_ActsUIImageAnimatorTest.ninja │ │ │ ├── ui_image_posix │ │ │ │ └── module_ActsUIImageTest.ninja │ │ │ ├── ui_label_button_posix │ │ │ │ └── module_ActsUILabelButtonTest.ninja │ │ │ ├── ui_label_posix │ │ │ │ └── module_ActsUILabelTest.ninja │ │ │ ├── ui_list_posix │ │ │ │ └── module_ActsUIListTest.ninja │ │ │ ├── ui_picker_posix │ │ │ │ └── module_ActsUIPickerTest.ninja │ │ │ ├── ui_radio_button_posix │ │ │ │ └── module_ActsUIRadioButtonTest.ninja │ │ │ ├── ui_repeat_button_posix │ │ │ │ └── module_ActsUIRepeatButtonTest.ninja │ │ │ ├── ui_screenshot_posix │ │ │ │ └── module_ActsUIScreenshotTest.ninja │ │ │ ├── ui_scroll_view_posix │ │ │ │ └── module_ActsUIScrollViewTest.ninja │ │ │ ├── ui_slider_posix │ │ │ │ └── module_ActsUISliderTest.ninja │ │ │ ├── ui_surface_view_posix │ │ │ │ └── module_ActsUISurfaceViewTest.ninja │ │ │ ├── ui_swipe_view_posix │ │ │ │ └── module_ActsUISwipeViewTest.ninja │ │ │ ├── ui_text_posix │ │ │ │ └── module_ActsUITextTest.ninja │ │ │ ├── ui_texture_mapper_posix │ │ │ │ └── module_ActsUITextureMapperTest.ninja │ │ │ ├── ui_time_picker_posix │ │ │ │ └── module_ActsUITimePickerTest.ninja │ │ │ ├── ui_toggle_button_posix │ │ │ │ └── module_ActsUIToggleButtonTest.ninja │ │ │ ├── ui_view_group_posix │ │ │ │ └── module_ActsUIViewGroupTest.ninja │ │ │ └── ui_view_posix │ │ │ └── module_ActsUIViewTest.ninja │ │ ├── hiviewdfx_lite │ │ │ └── hilog_posix │ │ │ └── module_ActsHilogTest.ninja │ │ ├── kernel_lite │ │ │ ├── dyload_posix │ │ │ │ └── module_ActsDyloadTest.ninja │ │ │ ├── fs_posix │ │ │ │ ├── jffs │ │ │ │ │ └── module_ActsJFFS2Test.ninja │ │ │ │ ├── nfs │ │ │ │ │ └── module_ActsNFSTest.ninja │ │ │ │ ├── vfat │ │ │ │ │ └── module_ActsVFATTest.ninja │ │ │ │ └── vfat_storage │ │ │ │ └── module_ActsVFATstorageTest.ninja │ │ │ ├── futex_posix │ │ │ │ └── module_ActsFutexApiTest.ninja │ │ │ ├── io_posix │ │ │ │ └── module_ActsIoApiTest.ninja │ │ │ ├── ipc_posix │ │ │ │ ├── message_queue │ │ │ │ │ └── module_ActsIpcMqTest.ninja │ │ │ │ ├── pipe_fifo │ │ │ │ │ └── module_ActsIpcPipeTest.ninja │ │ │ │ ├── semaphore │ │ │ │ │ └── module_ActsIpcSemTest.ninja │ │ │ │ ├── shared_memory │ │ │ │ │ └── module_ActsIpcShmTest.ninja │ │ │ │ └── signal │ │ │ │ └── module_ActsIpcSignalTest.ninja │ │ │ ├── math_posix │ │ │ │ ├── complexTest.ninja │ │ │ │ └── module_ActsMathApiTest.ninja │ │ │ ├── mem_posix │ │ │ │ └── module_ActsMemApiTest.ninja │ │ │ ├── net_posix │ │ │ │ └── module_ActsNetTest.ninja │ │ │ ├── process_posix │ │ │ │ └── module_ActsProcessApiTest.ninja │ │ │ ├── sched_posix │ │ │ │ └── module_ActsSchedApiTest.ninja │ │ │ ├── sys_posix │ │ │ │ └── module_ActsSysApiTest.ninja │ │ │ ├── time_posix │ │ │ │ └── module_ActsTimeApiTest.ninja │ │ │ ├── util_posix │ │ │ │ └── module_ActsUtilApiTest.ninja │ │ │ └── utils │ │ │ ├── libfs.ninja │ │ │ ├── libmt_utils.ninja │ │ │ └── libutils.ninja │ │ ├── multimedia_lite │ │ │ └── media_lite_posix │ │ │ └── recorder_native │ │ │ └── module_ActsMediaRecorderTest.ninja │ │ ├── security_lite │ │ │ ├── datahuks_posix │ │ │ │ └── module_ActsSecurityDataTest.ninja │ │ │ └── permission_posix │ │ │ ├── capability │ │ │ │ ├── capability_shared.ninja │ │ │ │ ├── jffs │ │ │ │ │ └── module_ActsJFFS2CapabilityTest.ninja │ │ │ │ └── vfat │ │ │ │ └── module_ActsVFATCapabilityTest.ninja │ │ │ ├── dac │ │ │ │ ├── jffs │ │ │ │ │ └── module_ActsJFFS2DACTest.ninja │ │ │ │ └── vfat │ │ │ │ └── module_ActsVFATDACTest.ninja │ │ │ └── pms │ │ │ └── module_ActsPMSTest.ninja │ │ ├── startup_lite │ │ │ ├── bootstrap_posix │ │ │ │ └── module_ActsBootstrapTest.ninja │ │ │ └── syspara_posix │ │ │ └── module_ActsParameterTest.ninja │ │ └── utils_lite │ │ └── kv_store_posix │ │ └── module_ActsKvStoreTest.ninja │ └── tools │ └── lite │ ├── hcpptest │ │ ├── gmock_main.ninja │ │ ├── gmock.ninja │ │ ├── hcpptest_main.ninja │ │ └── hcpptest.ninja │ └── others │ └── query │ └── query.ninja ├── third_party │ ├── bounds_checking_function │ │ ├── libsec_shared.ninja │ │ └── libsec_static.ninja │ ├── freetype │ │ └── freetype.ninja │ ├── giflib │ │ └── libgif.ninja │ ├── iniparser │ │ └── iniparser.ninja │ ├── libjpeg │ │ └── libjpeg.ninja │ ├── libpng │ │ └── libpng.ninja │ ├── mbedtls │ │ ├── mbedtls_gt.ninja │ │ ├── mbedtls_shared.ninja │ │ └── mbedtls_static.ninja │ └── qrcodegen │ └── qrcodegen.ninja ├── utils │ └── native │ └── lite │ ├── kv_store │ │ └── src │ │ └── utils_kv_store.ninja │ └── os_dump │ └── os_dump.ninja └── vendor └── hisilicon └── hispark_aries └── hals ├── security │ └── permission_lite │ └── hal_pms.ninja └── utils ├── sys_param │ └── hal_sysparam.ninja └── token └── haltoken_shared.ninja 百篇博客.往期回顾 在加注过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 :P 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,.xx代表修改的次数,精雕细琢,言简意赅,力求打造精品内容。 v60.xx 鸿蒙内核源码分析(GN应用篇) | GN语法及在鸿蒙的使用 | 51 .c .h .o v59.xx 鸿蒙内核源码分析(构建工具篇) | 顺瓜摸藤调试鸿蒙构建过程 | 51 .c .h .o v58.xx 鸿蒙内核源码分析(环境脚本篇) | 有了它编译鸿蒙好简单 | 51 .c .h .o v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 51 .c .h .o v56.xx 鸿蒙内核源码分析(进程映像篇) | ELF是如何被加载运行的? | 51 .c .h .o v55.xx 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 51 .c .h .o v54.xx 鸿蒙内核源码分析(静态链接篇) | 完整小项目看透静态链接过程 | 51 .c .h .o v53.xx 鸿蒙内核源码分析(ELF解析篇) | 你要忘了她姐俩你就不是银 | 51 .c .h .o v52.xx 鸿蒙内核源码分析(静态站点篇) | 五一哪也没去就干了这事 | 51 .c .h .o v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51 .c .h .o v50.xx 鸿蒙内核源码分析(编译环境篇) | docker编译鸿蒙真的很香 | 51 .c .h .o v49.xx 鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行 | 51 .c .h .o v48.xx 鸿蒙内核源码分析(信号生产篇) | 年过半百,依然活力十足 | 51 .c .h .o v47.xx 鸿蒙内核源码分析(进程回收篇) | 临终前如何向老祖宗托孤 | 51 .c .h .o v46.xx 鸿蒙内核源码分析(特殊进程篇) | 龙生龙凤生凤老鼠生儿会打洞 | 51 .c .h .o v45.xx 鸿蒙内核源码分析(Fork篇) | 一次调用,两次返回 | 51 .c .h .o v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51 .c .h .o v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51 .c .h .o v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51 .c .h .o v41.xx 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 51 .c .h .o v40.xx 鸿蒙内核源码分析(汇编汇总篇) | 汇编可爱如邻家女孩 | 51 .c .h .o v39.xx 鸿蒙内核源码分析(异常接管篇) | 社会很单纯,复杂的是人 | 51 .c .h .o v38.xx 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 51 .c .h .o v37.xx 鸿蒙内核源码分析(系统调用篇) | 开发者永远的口头禅 | 51 .c .h .o v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51 .c .h .o v35.xx 鸿蒙内核源码分析(时间管理篇) | 谁是内核基本时间单位 | 51 .c .h .o v34.xx 鸿蒙内核源码分析(原子操作篇) | 谁在为原子操作保驾护航 | 51 .c .h .o v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51 .c .h .o v32.xx 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 51 .c .h .o v31.xx 鸿蒙内核源码分析(定时器篇) | 哪个任务的优先级最高 | 51 .c .h .o v30.xx 鸿蒙内核源码分析(事件控制篇) | 任务间多对多的同步方案 | 51 .c .h .o v29.xx 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 51 .c .h .o v28.xx 鸿蒙内核源码分析(进程通讯篇) | 九种进程间通讯方式速揽 | 51 .c .h .o v27.xx 鸿蒙内核源码分析(互斥锁篇) | 比自旋锁丰满的互斥锁 | 51 .c .h .o v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当立贞节牌坊 | 51 .c .h .o v25.xx 鸿蒙内核源码分析(并发并行篇) | 听过无数遍的两个概念 | 51 .c .h .o v24.xx 鸿蒙内核源码分析(进程概念篇) | 进程在管理哪些资源 | 51 .c .h .o v23.xx 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 51 .c .h .o v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 | 51 .c .h .o v21.xx 鸿蒙内核源码分析(线程概念篇) | 是谁在不断的折腾CPU | 51 .c .h .o v20.xx 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地由谁提供 | 51 .c .h .o v19.xx 鸿蒙内核源码分析(位图管理篇) | 谁能一分钱分两半花 | 51 .c .h .o v18.xx 鸿蒙内核源码分析(源码结构篇) | 内核每个文件的含义 | 51 .c .h .o v17.xx 鸿蒙内核源码分析(物理内存篇) | 怎么管理物理内存 | 51 .c .h .o v16.xx 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 51 .c .h .o v15.xx 鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里 | 51 .c .h .o v14.xx 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 51 .c .h .o v13.xx 鸿蒙内核源码分析(源码注释篇) | 鸿蒙必定成功,也必然成功 | 51 .c .h .o v12.xx 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 51 .c .h .o v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式 | 51 .c .h .o v10.xx 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 51 .c .h .o v09.xx 鸿蒙内核源码分析(调度故事篇) | 用故事说内核调度过程 | 51 .c .h .o v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51 .c .h .o v07.xx 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的 | 51 .c .h .o v06.xx 鸿蒙内核源码分析(调度队列篇) | 内核有多少个调度队列 | 51 .c .h .o v05.xx 鸿蒙内核源码分析(任务管理篇) | 任务池是如何管理的 | 51 .c .h .o v04.xx 鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 51 .c .h .o v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁的贡献最大 | 51 .c .h .o v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源 | 51 .c .h .o v01.xx 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体 | 51 .c .h .o 关于 51 .c .h .o 看系列篇文章会常看到 51 .c .h .o,希望这对大家阅读不会造成影响. 分别对应以下四个站点的首个字符,感谢这些站点一直以来对系列篇的支持和推荐,尤其是 oschina gitee ,很喜欢它的界面风格,简洁大方,让人感觉到开源的伟大! 51cto csdn harmony oschina 而巧合的是.c .h .o是C语言的头/源/目标文件,这就很有意思了,冥冥之中似有天数,将这四个宝贝以这种方式融合在一起. 51 .c .h .o , 我要CHO ,嗯嗯,hin 顺口 : ) 百万汉字注解.百篇博客分析 百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto | csdn | harmony | osc > 关注不迷路.代码即人生 热爱是所有的理由和答案 - turing 原创不易,欢迎转载,但麻烦请注明出处.

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

鸿蒙内核源码分析(静态站点篇) | 五一哪也没去就干了这事 | 百篇博客分析HarmonyOS源码 | v52.01

OpenHarmony开发者文档 国内访问 : https://openharmony.21cloudbox.com/ 国外访问 : https://openharmony.github.io/ 先看图: 在给鸿蒙内核加注和写博客期间需要不断的查找资料,觉得官方目前资料展示方式并不能满足自己的需求,浪费了很多宝贵的时间,所以在想能不能将官方文档整个静态站点出来即方便别人更方便自己,这是一劳永逸,利己利他的事干嘛不做的,刚好五一有成块的时间,本来也想出去走走,结果却哪都没去,期间遇到不少问题,但基本都解决了,有段时间没更新博客了但换来未来的一片爽朗之声, hin 值得! 这里必须要感谢下这套主体的作者 Mr.hope ,人非常的nice, 晚上12点我们还在一起解决问题.再次感谢!!! 主题地址:vuepress-theme-hope 有兴趣的可以去了解下,一个功能强大的 vuepress 主题. 静态站点将每月同步官方文档,静态站点仓库已经开放,仓库地址 欢迎拿去部署. 侧边栏 这个不用说,都恨不得多开几个屏幕,技术人没它真不行,虽用谁知道,侧边栏是按官方的目录结构来的,但目前不是最优方案,会随时调整到最佳结构. 主题色 多种主题色,可以根据您的选择自动切换模式,搞技术的注意保护好视力,个人偏向白色. 搜索极为便利,国内外双站点 这个是很重要的功能,无搜索不技术,说到这个强烈建议不要用某度,因之前从未写过博客不清楚搜索结果是怎么效果法,现在有对比,去搜下自己的文章 ..., 各搜索引擎差别真的很大,谷歌就不用去说了,有条件的推荐要用,搜狗,360都比它好, 它虽然全但更杂,会干扰你的注意力. 哎, 以后有机会要写篇文章痛批阿度,真是太耽误事了,心疼流失了这么多人宝贵的时间. 中英文切换 这个估计国内的各位都不会去看,希望能有更多友人能了解鸿蒙.搞技术的都有操作系统情怀,个人坚信鸿蒙必定成功,也必然成功,它的成功意义非凡,或者能明白的人不多,自己一定是那个摇旗呐喊者. 带私货 这是私货,是自己一直想做也正在做的事,有些事情一旦开始,就真的停不下来了.不知道你有没有这样的感受. 百篇博客.往期回顾 在加注过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆. 说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.:P 与写代码有bug需不断debug一样,文章和注解内容会将错漏之处反复修正,持续更新,.xx代表修改的次数,精雕细琢,言简意赅,力求打造精品内容. v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51 .c .h .o v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙看这篇或许真的够了 | 51 .c .h .o v49.xx 鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行 | 51 .c .h .o v48.xx 鸿蒙内核源码分析(信号生产篇) | 年过半百,依然活力十足 | 51 .c .h .o v47.xx 鸿蒙内核源码分析(进程回收篇) | 临终前如何向老祖宗托孤 | 51 .c .h .o v46.xx 鸿蒙内核源码分析(特殊进程篇) | 龙生龙凤生凤老鼠生儿会打洞 | 51 .c .h .o v45.xx 鸿蒙内核源码分析(Fork篇) | 一次调用,两次返回 | 51 .c .h .o v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51 .c .h .o v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51 .c .h .o v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51 .c .h .o v41.xx 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 51 .c .h .o v40.xx 鸿蒙内核源码分析(汇编汇总篇) | 汇编可爱如邻家女孩 | 51 .c .h .o v39.xx 鸿蒙内核源码分析(异常接管篇) | 社会很单纯,复杂的是人 | 51 .c .h .o v38.xx 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 51 .c .h .o v37.xx 鸿蒙内核源码分析(系统调用篇) | 开发者永远的口头禅 | 51 .c .h .o v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51 .c .h .o v35.xx 鸿蒙内核源码分析(时间管理篇) | 谁是内核基本时间单位 | 51 .c .h .o v34.xx 鸿蒙内核源码分析(原子操作篇) | 谁在为原子操作保驾护航 | 51 .c .h .o v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51 .c .h .o v32.xx 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 51 .c .h .o v31.xx 鸿蒙内核源码分析(定时器篇) | 哪个任务的优先级最高 | 51 .c .h .o v30.xx 鸿蒙内核源码分析(事件控制篇) | 任务间多对多的同步方案 | 51 .c .h .o v29.xx 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 51 .c .h .o v28.xx 鸿蒙内核源码分析(进程通讯篇) | 九种进程间通讯方式速揽 | 51 .c .h .o v27.xx 鸿蒙内核源码分析(互斥锁篇) | 比自旋锁丰满的互斥锁 | 51 .c .h .o v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当立贞节牌坊 | 51 .c .h .o v25.xx 鸿蒙内核源码分析(并发并行篇) | 听过无数遍的两个概念 | 51 .c .h .o v24.xx 鸿蒙内核源码分析(进程概念篇) | 进程在管理哪些资源 | 51 .c .h .o v23.xx 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 51 .c .h .o v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 | 51 .c .h .o v21.xx 鸿蒙内核源码分析(线程概念篇) | 是谁在不断的折腾CPU | 51 .c .h .o v20.xx 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地由谁提供 | 51 .c .h .o v19.xx 鸿蒙内核源码分析(位图管理篇) | 谁能一分钱分两半花 | 51 .c .h .o v18.xx 鸿蒙内核源码分析(源码结构篇) | 内核每个文件的含义 | 51 .c .h .o v17.xx 鸿蒙内核源码分析(物理内存篇) | 怎么管理物理内存 | 51 .c .h .o v16.xx 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 51 .c .h .o v15.xx 鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里 | 51 .c .h .o v14.xx 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 51 .c .h .o v13.xx 鸿蒙内核源码分析(源码注释篇) | 鸿蒙必定成功,也必然成功 | 51 .c .h .o v12.xx 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 51 .c .h .o v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式 | 51 .c .h .o v10.xx 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 51 .c .h .o v09.xx 鸿蒙内核源码分析(调度故事篇) | 用故事说内核调度过程 | 51 .c .h .o v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51 .c .h .o v07.xx 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的 | 51 .c .h .o v06.xx 鸿蒙内核源码分析(调度队列篇) | 内核有多少个调度队列 | 51 .c .h .o v05.xx 鸿蒙内核源码分析(任务管理篇) | 任务池是如何管理的 | 51 .c .h .o v04.xx 鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 51 .c .h .o v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁的贡献最大 | 51 .c .h .o v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源 | 51 .c .h .o v01.xx 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体 | 51 .c .h .o 关于 51 .c .h .o 看系列篇文章会常看到 51 .c .h .o,希望这对大家阅读不会造成影响. 分别对应以下四个站点的首个字符,感谢这些站点一直以来对系列篇的支持和推荐,尤其是 oschina gitee ,很喜欢它的界面风格,简洁大方,让人感觉到开源的伟大! 51cto csdn harmony oschina 而巧合的是.c .h .o是C语言的头/源/目标文件,这就很有意思了,冥冥之中似有天数,将这四个宝贝以这种方式融合在一起. 51 .c .h .o , 我要CHO ,嗯嗯,hin 顺口 : ) 百万汉字注解.百篇博客分析 百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto | csdn | harmony | osc > 关注不迷路.代码即人生 热爱是所有的理由和答案 - turing 原创不易,欢迎转载,但麻烦请注明出处.

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

鸿蒙内核源码分析(ELF格式篇) | 用户程序入口并不是main | 百篇博客分析HarmonyOS源码 | v51.04

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< osc | 51cto | csdn | harmony > 阅读之前的说明 先说明,本篇很长,也很枯燥,若不是绝对的技术偏执狂是看不下去的.将通过一段简单代码去跟踪编译成ELF格式后的内容.看看ELF究竟长了怎样的一副花花肠子,用readelf命令去窥视ELF的全貌,最后用objdump命令反汇编ELF.找到了大家熟悉main函数. 开始之前先说结论: ELF有两个重要概念 Segment(段) 和 Section(区),段比区大,是包含关系,一个段可由多区组成,一个区可被多段所共有. ELF 分四块,其中三块是描述信息(也叫头信息),另一块是内容,放的是所有区的内容. 第一块ELF头定义全局性信息 第二块Segment(段)头,内容描述段的名字,开始位置,类型,偏移,大小及每段由哪些区组成. 第三块内容区,放.text,.data,.bss所有区的内容,为了表述严谨,系列篇将统一说代码区,数据区,不再说代码段. 第四块Section(区)头,内容描述区的名字,开始位置,类型,偏移,大小等信息 鸿蒙对EFL的定义在 kernel\extended\dynload\include\los_ld_elf_pri.h文件中 示例代码 在windows目录E:\harmony\docker\test4harmony下创建 main.c文件,如下: #include <stdio.h> void say_hello(char *who) { printf("hello, %s!\n", who); } char *my_name = "harmony os"; int main() { say_hello(my_name); return 0; } 因在 v50.xx (编译环境篇) | 编译鸿蒙看这篇或许真的够了 < csdn | 51cto | osc > 篇中已做好了环境映射,所以文件会同时出现在docker中.编译生成ELF->运行->readelf -h查看app头部信息. root@5e3abe332c5a:/home/docker/test4harmony# ls main.c root@5e3abe332c5a:/home/docker/test4harmony# gcc -o app main.c root@5e3abe332c5a:/home/docker/test4harmony# ls app main.c root@5e3abe332c5a:/home/docker/test4harmony# ./app hello, harmony os! root@5e3abe332c5a:/home/docker/test4harmony# readelf -h app ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x1060 Start of program headers: 64 (bytes into file) Start of section headers: 14784 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 13 Size of section headers: 64 (bytes) Number of section headers: 31 Section header string table index: 30 这里留意这几个内容,下面会说明,先记住. Entry point address: 0x1060 //代码区 .text 起始位置,即程序运行开始位置 Number of program headers: 13 //段数量 Number of section headers: 31 //区数量 Section header string table index: 30 //字符串数组索引,该区记录所有区名称 ELF历史 ELF的英文全称是The Executable and Linking Format,最初是由UNIX系统实验室开发、发布的ABI(Application Binary Interface)接口的一部分,是鸿蒙的主要可执行文件格式。 ELF整体布局 ELF格式可以表达四种类型的二进制对象文件(object files): 可重定位文件(relocatable file,就是大家平常见到的.o文件)包含基础代码和数据,但它的代码及数据都没有指定绝对地址,因此它适合于与其他目标文件链接来创建可执行文件或者共享目标文件。 可执行文件(executable file, 例上述示例代码生成的app文件)包含代码和数据,是可以直接运行的程序。其代码和数据都有固定的地址 (或相对于基地址的偏移 ),系统可根据这些地址信息把程序加载到内存执行。 共享库文件(shared object file,就是.so文件,用来做动态链接)也称动态库文件,包含了代码和数据,这些数据是在链接时被链接器(ld)和运行时动态链接器(ld.so.l、libc.so.l、ld-linux.so.l)使用的。 核心转储文件(core dump file,就是core dump文件) 可重定位文件用在编译和链接阶段;可执行文件用在程序运行阶段;共享库则同时用在编译链接和运行阶段。在不同阶段,我们可以用不同视角来理解ELF文件,整体布局如下图所示: 从上图可见,ELF格式文件整体可分为四大部分: ELF Header: 在文件的开始,描述整个文件的组织。即readelf -h app看到的内容 Program Header Table: 告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件可以不需要这个表。表描述段(Segment)信息的数组,每个元素对应一个段 , 即readelf -l app看到的前半部分内容. Segments and Sections: 段(Segment)由若干区(Section)组成;段在运行时被加载到进程地址空间中,包含在可执行文件中;区是段的组成单元,包含在可执行文件和可重定位文件中 即readelf -l app后半部分内容,readelf -x ** app可查看区具体内容.注意: Segments 是从运行的角度来描述 ELF 文件,Sections 是从链接的角度来描述 ELF 文件。也就是说,在链接阶段,我们可以忽略 program header table 来处理此文件,在运行阶段可以忽略 section header table 来处理此程序(所以很多加固手段删除了section header table)。 Section Header Table:描述区(Section)信息的数组,每个元素对应一个区,通常包含在可重定位文件中,可执行文件中为可选(通常包含) 即readelf -S app看到的内容 从图中可以看出 Segment:Section(M:N)是多对多的包含关系.Segment是由多个Section组成,Section也能属于多个段. ELF头信息 ELF头部信息对应鸿蒙源码结构体为 LDElf32Ehdr, 各字段含义已一一注解,很容易理解. //kernel\extended\dynload\include\los_ld_elf_pri.h /* Elf header */ #define LD_EI_NIDENT 16 typedef struct { UINT8 elfIdent[LD_EI_NIDENT]; /* Magic number and other info *///含前16个字节,又可细分成class、data、version等字段,具体含义不用太关心,只需知道前4个字节点包含`ELF`关键字,这样可以判断当前文件是否是ELF格式 UINT16 elfType; /* Object file type *///表示具体ELF类型,可重定位文件/可执行文件/共享库文件 UINT16 elfMachine; /* Architecture *///表示cpu架构 UINT32 elfVersion; /* Object file version *///表示文件版本号 UINT32 elfEntry; /* Entry point virtual address *///对应`Entry point address`,程序入口函数地址,通过进程虚拟地址空间地址表达 UINT32 elfPhoff; /* Program header table file offset *///对应`Start of program headers`,表示program header table在文件内的偏移位置 UINT32 elfShoff; /* Section header table file offset *///对应`Start of section headers`,表示section header table在文件内的偏移位置 UINT32 elfFlags; /* Processor-specific flags *///表示与CPU处理器架构相关的信息 UINT16 elfHeadSize; /* ELF header size in bytes *///对应`Size of this header`,表示本ELF header自身的长度 UINT16 elfPhEntSize; /* Program header table entry size *///对应`Size of program headers`,表示program header table中每个元素的大小 UINT16 elfPhNum; /* Program header table entry count *///对应`Number of program headers`,表示program header table中元素个数 UINT16 elfShEntSize; /* Section header table entry size *///对应`Size of section headers`,表示section header table中每个元素的大小 UINT16 elfShNum; /* Section header table entry count *///对应`Number of section headers`,表示section header table中元素的个数 UINT16 elfShStrIndex; /* Section header string table index *///对应`Section header string table index`,表示描述各section字符名称的string table在section header table中的下标 } LDElf32Ehdr; 结构体对应readelf -h app命令内容来理解,就不再贴出来了. 段(Segment)头信息 段(Segment)信息对应鸿蒙源码结构体为 LDElf32Phdr, //kernel\extended\dynload\include\los_ld_elf_pri.h /* Program Header */ typedef struct { UINT32 type; /* Segment type */ UINT32 offset; /* Segment file offset */ UINT32 vAddr; /* Segment virtual address */ UINT32 phyAddr; /* Segment physical address */ UINT32 fileSize; /* Segment size in file */ UINT32 memSize; /* Segment size in memory */ UINT32 flags; /* Segment flags */ UINT32 align; /* Segment alignment */ } LDElf32Phdr; 用readelf -l查看app段头部表内容 root@5e3abe332c5a:/home/docker/test4harmony# readelf -l app Elf file type is DYN (Shared object file) Entry point 0x1060 There are 13 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040 0x00000000000002d8 0x00000000000002d8 R 0x8 ......... 解读 先看命令返回的前半部分: Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040 0x00000000000002d8 0x00000000000002d8 R 0x8 INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318 0x000000000000001c 0x000000000000001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000618 0x0000000000000618 R 0x1000 LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000 0x0000000000000225 0x0000000000000225 R E 0x1000 LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000 0x0000000000000190 0x0000000000000190 R 0x1000 LOAD 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8 0x0000000000000260 0x0000000000000268 RW 0x1000 DYNAMIC 0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8 0x00000000000001f0 0x00000000000001f0 RW 0x8 NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000020 0x0000000000000020 R 0x8 NOTE 0x0000000000000358 0x0000000000000358 0x0000000000000358 0x0000000000000044 0x0000000000000044 R 0x4 GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000020 0x0000000000000020 R 0x8 GNU_EH_FRAME 0x000000000000201c 0x000000000000201c 0x000000000000201c 0x000000000000004c 0x000000000000004c R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8 0x0000000000000248 0x0000000000000248 R 0x1 数一下一共13个段,其实在ELF头信息也告诉了我们共13个段 Number of program headers: 13 //段数量 仔细看下这些段的开始地址和大小,发现有些段是重叠的.那是因为一个区可以被多个段所拥有.例如:0x2db8 对应的 .init_array区就被第四LOAD 和 GNU_RELRO两段所共有. PHDR,此类型header元素描述了program header table自身的信息。从这里的内容看出,示例程序的program header table在文件中的偏移(Offset)为0x40,即64号字节处;该段映射到进程空间的虚拟地址(VirtAddr)为0x40;PhysAddr暂时不用,其保持和VirtAddr一致;该段占用的文件大小FileSiz为0x2d8;运行时占用进程空间内存大小MemSiz也为0x2d8;Flags标记表示该段的读写权限,这里R表示只读,Align对齐为8,表明本段按8字节对齐。 INTERP,此类型header元素描述了一个特殊内存段,该段内存记录了动态加载解析器的访问路径字符串。示例程序中,该段内存位于文件偏移0x318处,即紧跟program header table;映射的进程虚拟地址空间地址为0x318;文件长度和内存映射长度均为0x1c,即28个字符,具体内容为/lib64/ld-linux-x86-64.so.2;段属性为只读,并按字节对齐; LOAD,此类型header元素描述了可加载到进程空间的代码区或数据区: 第二段包含了代码区,文件内偏移为0x1000,映射到进程地址0x001000处,代码区的长度为0x618个字节,属性为只读可执行(RE),段地址按0x1000(4K)边界对齐; 第四段包含了数据区,文件内偏移为0x1000,映射到进程地址为0x1000处(按2M对齐移动),文件大小为0x230,内存大小为0x238(因为其内部包含了8字节的bss区,即未初始化数据区,该区内容不占文件空间,但在运行时需要为其分配空间并清零),属性为读写(RW),段地址也按0x1000(4K)边界对齐。 DYNAMIC,此类型header元素描述了动态加载段,其内部通常包含了一个名为.dynamic的动态加载区;这也是一个数组,每个元素描述了与动态加载相关的各方面信息,将在系列篇(动态加载篇)中介绍。该段是从文件偏移0x2dc8处开始,长度为0x1f0,并映射到进程的0x3dc8;可见该段和上一个段LOAD4 0x2db8是有重叠的。 再看命令返回内容的后半部分-段区映射关系 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .plt.got .plt.sec .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .dynamic .got .data .bss 06 .dynamic 07 .note.gnu.property 08 .note.gnu.build-id .note.ABI-tag 09 .note.gnu.property 10 .eh_frame_hdr 11 12 .init_array .fini_array .dynamic .got Segment:Section(M:N)是多对多的包含关系.Segment是由多个Section组成,Section也能属于多个段.这个很重要,说第二遍了. INTERP段只包含了.interp区 LOAD2段包含.interp、.plt、.text等区,.text代码区位于这个段. LOAD4包含.dynamic、.data、.bss`等区, 数据区位于这个段. DYNAMIC段包含.dynamic区。 区表 区(section)头表信息对应鸿蒙源码结构体为 LDElf32Shdr, //kernel\extended\dynload\include\los_ld_elf_pri.h /* Section header */ typedef struct { UINT32 shName; /* Section name (string tbl index) *///表示每个区的名字 UINT32 shType; /* Section type *///表示每个区的功能 UINT32 shFlags; /* Section flags *///表示每个区的属性 UINT32 shAddr; /* Section virtual addr at execution *///表示每个区的进程映射地址 UINT32 shOffset; /* Section file offset *///表示文件内偏移 UINT32 shSize; /* Section size in bytes *///表示区的大小 UINT32 shLink; /* Link to another section *///Link和Info记录不同类型区的相关信息 UINT32 shInfo; /* Additional section information *///Link和Info记录不同类型区的相关信息 UINT32 shAddrAlign; /* Section alignment *///表示区的对齐单位 UINT32 shEntSize; /* Entry size if section holds table *///表示区中每个元素的大小(如果该区为一个数组的话,否则该值为0) } LDElf32Shdr; 示例程序共生成31个区.其实在头文件中也已经告诉我们了 Number of section headers: 31 //区数量 通过readelf -S命令看看示例程序中 section header table的内容,如下所示。 root@5e3abe332c5a:/home/docker/test4harmony# readelf -S app There are 31 section headers, starting at offset 0x39c0: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000000318 00000318 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.gnu.propert NOTE 0000000000000338 00000338 0000000000000020 0000000000000000 A 0 0 8 [ 3] .note.gnu.build-i NOTE 0000000000000358 00000358 0000000000000024 0000000000000000 A 0 0 4 [ 4] .note.ABI-tag NOTE 000000000000037c 0000037c 0000000000000020 0000000000000000 A 0 0 4 [ 5] .gnu.hash GNU_HASH 00000000000003a0 000003a0 0000000000000024 0000000000000000 A 6 0 8 [ 6] .dynsym DYNSYM 00000000000003c8 000003c8 00000000000000a8 0000000000000018 A 7 1 8 [ 7] .dynstr STRTAB 0000000000000470 00000470 0000000000000084 0000000000000000 A 0 0 1 [ 8] .gnu.version VERSYM 00000000000004f4 000004f4 000000000000000e 0000000000000002 A 6 0 2 [ 9] .gnu.version_r VERNEED 0000000000000508 00000508 0000000000000020 0000000000000000 A 7 1 8 [10] .rela.dyn RELA 0000000000000528 00000528 00000000000000d8 0000000000000018 A 6 0 8 [11] .rela.plt RELA 0000000000000600 00000600 0000000000000018 0000000000000018 AI 6 24 8 [12] .init PROGBITS 0000000000001000 00001000 000000000000001b 0000000000000000 AX 0 0 4 [13] .plt PROGBITS 0000000000001020 00001020 0000000000000020 0000000000000010 AX 0 0 16 [14] .plt.got PROGBITS 0000000000001040 00001040 0000000000000010 0000000000000010 AX 0 0 16 [15] .plt.sec PROGBITS 0000000000001050 00001050 0000000000000010 0000000000000010 AX 0 0 16 [16] .text PROGBITS 0000000000001060 00001060 00000000000001b5 0000000000000000 AX 0 0 16 [17] .fini PROGBITS 0000000000001218 00001218 000000000000000d 0000000000000000 AX 0 0 4 [18] .rodata PROGBITS 0000000000002000 00002000 000000000000001b 0000000000000000 A 0 0 4 [19] .eh_frame_hdr PROGBITS 000000000000201c 0000201c 000000000000004c 0000000000000000 A 0 0 4 [20] .eh_frame PROGBITS 0000000000002068 00002068 0000000000000128 0000000000000000 A 0 0 8 [21] .init_array INIT_ARRAY 0000000000003db8 00002db8 0000000000000008 0000000000000008 WA 0 0 8 [22] .fini_array FINI_ARRAY 0000000000003dc0 00002dc0 0000000000000008 0000000000000008 WA 0 0 8 [23] .dynamic DYNAMIC 0000000000003dc8 00002dc8 00000000000001f0 0000000000000010 WA 7 0 8 [24] .got PROGBITS 0000000000003fb8 00002fb8 0000000000000048 0000000000000008 WA 0 0 8 [25] .data PROGBITS 0000000000004000 00003000 0000000000000018 0000000000000000 WA 0 0 8 [26] .bss NOBITS 0000000000004018 00003018 0000000000000008 0000000000000000 WA 0 0 1 [27] .comment PROGBITS 0000000000000000 00003018 000000000000002a 0000000000000001 MS 0 0 1 [28] .symtab SYMTAB 0000000000000000 00003048 0000000000000648 0000000000000018 29 46 8 [29] .strtab STRTAB 0000000000000000 00003690 0000000000000216 0000000000000000 0 0 1 [30] .shstrtab STRTAB 0000000000000000 000038a6 000000000000011a 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), l (large), p (processor specific) String Table 从上述Section Header Table示例中,我们看到有一种类型为STRTAB的区(在Section Header Table中的下标为7,29,30)。此类区叫做String Table,其作用是集中记录字符串信息,其它区在需要使用字符串的时候,只需要记录字符串起始地址在该String Table表中的偏移即可,而无需包含整个字符串内容。 我们使用readelf -x读出下标30区的数据: root@5e3abe332c5a:/home/docker/test4harmony# readelf -x 30 app Hex dump of section '.shstrtab': 0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab 0x00000010 002e7368 73747274 6162002e 696e7465 ..shstrtab..inte 0x00000020 7270002e 6e6f7465 2e676e75 2e70726f rp..note.gnu.pro 0x00000030 70657274 79002e6e 6f74652e 676e752e perty..note.gnu. 0x00000040 6275696c 642d6964 002e6e6f 74652e41 build-id..note.A 0x00000050 42492d74 6167002e 676e752e 68617368 BI-tag..gnu.hash 0x00000060 002e6479 6e73796d 002e6479 6e737472 ..dynsym..dynstr 0x00000070 002e676e 752e7665 7273696f 6e002e67 ..gnu.version..g 0x00000080 6e752e76 65727369 6f6e5f72 002e7265 nu.version_r..re 0x00000090 6c612e64 796e002e 72656c61 2e706c74 la.dyn..rela.plt 0x000000a0 002e696e 6974002e 706c742e 676f7400 ..init..plt.got. 0x000000b0 2e706c74 2e736563 002e7465 7874002e .plt.sec..text.. 0x000000c0 66696e69 002e726f 64617461 002e6568 fini..rodata..eh 0x000000d0 5f667261 6d655f68 6472002e 65685f66 _frame_hdr..eh_f 0x000000e0 72616d65 002e696e 69745f61 72726179 rame..init_array 0x000000f0 002e6669 6e695f61 72726179 002e6479 ..fini_array..dy 0x00000100 6e616d69 63002e64 61746100 2e627373 namic..data..bss 0x00000110 002e636f 6d6d656e 7400 ..comment. 可以发现,这里其实是一堆字符串,这些字符串对应的就是各个区的名字。因此section header table中每个元素的Name字段其实是这个string table的索引。再回头看看ELF header中的 elfShStrIndex, Section header string table index: 30 //字符串数组索引,该区记录所有区名称 它的值正好就是30,指向了当前的string table。 再来看下29区的内容,如下所示 root@5e3abe332c5a:/home/docker/test4harmony# readelf -x 29 app Hex dump of section '.strtab': 0x00000000 00637274 73747566 662e6300 64657265 .crtstuff.c.dere 0x00000010 67697374 65725f74 6d5f636c 6f6e6573 gister_tm_clones 0x00000020 005f5f64 6f5f676c 6f62616c 5f64746f .__do_global_dto 0x00000030 72735f61 75780063 6f6d706c 65746564 rs_aux.completed 0x00000040 2e383036 30005f5f 646f5f67 6c6f6261 .8060.__do_globa 0x00000050 6c5f6474 6f72735f 6175785f 66696e69 l_dtors_aux_fini 0x00000060 5f617272 61795f65 6e747279 00667261 _array_entry.fra 0x00000070 6d655f64 756d6d79 005f5f66 72616d65 me_dummy.__frame 0x00000080 5f64756d 6d795f69 6e69745f 61727261 _dummy_init_arra 0x00000090 795f656e 74727900 6d61696e 2e63005f y_entry.main.c._ 0x000000a0 5f465241 4d455f45 4e445f5f 005f5f69 _FRAME_END__.__i 0x000000b0 6e69745f 61727261 795f656e 64005f44 nit_array_end._D 0x000000c0 594e414d 4943005f 5f696e69 745f6172 YNAMIC.__init_ar 0x000000d0 7261795f 73746172 74005f5f 474e555f ray_start.__GNU_ 0x000000e0 45485f46 52414d45 5f484452 005f474c EH_FRAME_HDR._GL 0x000000f0 4f42414c 5f4f4646 5345545f 5441424c OBAL_OFFSET_TABL 0x00000100 455f005f 5f6c6962 635f6373 755f6669 E_.__libc_csu_fi 0x00000110 6e69006d 795f6e61 6d65005f 49544d5f ni.my_name._ITM_ 0x00000120 64657265 67697374 6572544d 436c6f6e deregisterTMClon 0x00000130 65546162 6c65005f 65646174 61007072 eTable._edata.pr 0x00000140 696e7466 4040474c 4942435f 322e322e intf@@GLIBC_2.2. 0x00000150 35005f5f 6c696263 5f737461 72745f6d 5.__libc_start_m 0x00000160 61696e40 40474c49 42435f32 2e322e35 ain@@GLIBC_2.2.5 0x00000170 005f5f64 6174615f 73746172 74005f5f .__data_start.__ 0x00000180 676d6f6e 5f737461 72745f5f 005f5f64 gmon_start__.__d 0x00000190 736f5f68 616e646c 65005f49 4f5f7374 so_handle._IO_st 0x000001a0 64696e5f 75736564 005f5f6c 6962635f din_used.__libc_ 0x000001b0 6373755f 696e6974 005f5f62 73735f73 csu_init.__bss_s 0x000001c0 74617274 006d6169 6e007361 795f6865 tart.main.say_he 0x000001d0 6c6c6f00 5f5f544d 435f454e 445f5f00 llo.__TMC_END__. 0x000001e0 5f49544d 5f726567 69737465 72544d43 _ITM_registerTMC 0x000001f0 6c6f6e65 5461626c 65005f5f 6378615f loneTable.__cxa_ 0x00000200 66696e61 6c697a65 4040474c 4942435f finalize@@GLIBC_ 0x00000210 322e322e 3500 2.2.5. 这里看到了main、say_hello字符串,这些是在示例中源码中定义的符号,由此29区是应用自身的String Table,记录了应用程序使用的字符串。这个表就是符号表的一部分. 通过readelf -s命令能拿到全部符号信息.如下 符号表 Symbol Table Section Header Table中,还有一类SYMTAB(DYNSYM)区,该区叫符号表。符号表中的每个元素对应一个符号,记录了每个符号对应的实际数值信息,通常用在重定位过程中或问题定位过程中,进程执行阶段并不加载符号表。符号表对应鸿蒙源码结构体为 LDElf32Sym. //kernel\extended\dynload\include\los_ld_elf_pri.h /* Symbol table */ typedef struct { UINT32 stName; /* Symbol table name (string tbl index) *///表示符号对应的源码字符串,为对应String Table中的索引 UINT32 stValue; /* Symbol table value *///表示符号对应的数值 UINT32 stSize; /* Symbol table size *///表示符号对应数值的空间占用大小 UINT8 stInfo; /* Symbol table type and binding *///表示符号的相关信息 如符号类型(变量符号、函数符号) UINT8 stOther; /* Symbol table visibility */ UINT16 stShndx; /* Section table index *///表示与该符号相关的区的索引,例如函数符号与对应的代码区相关 } LDElf32Sym; 用readelf -s读出示例程序中的符号表,如下所示 root@5e3abe332c5a:/home/docker/test4harmony# readelf -s app Symbol table '.dynsym' contains 7 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2) 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 6: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) Symbol table '.symtab' contains 67 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000318 0 SECTION LOCAL DEFAULT 1 2: 0000000000000338 0 SECTION LOCAL DEFAULT 2 3: 0000000000000358 0 SECTION LOCAL DEFAULT 3 4: 000000000000037c 0 SECTION LOCAL DEFAULT 4 5: 00000000000003a0 0 SECTION LOCAL DEFAULT 5 6: 00000000000003c8 0 SECTION LOCAL DEFAULT 6 7: 0000000000000470 0 SECTION LOCAL DEFAULT 7 8: 00000000000004f4 0 SECTION LOCAL DEFAULT 8 9: 0000000000000508 0 SECTION LOCAL DEFAULT 9 10: 0000000000000528 0 SECTION LOCAL DEFAULT 10 11: 0000000000000600 0 SECTION LOCAL DEFAULT 11 12: 0000000000001000 0 SECTION LOCAL DEFAULT 12 13: 0000000000001020 0 SECTION LOCAL DEFAULT 13 14: 0000000000001040 0 SECTION LOCAL DEFAULT 14 15: 0000000000001050 0 SECTION LOCAL DEFAULT 15 16: 0000000000001060 0 SECTION LOCAL DEFAULT 16 17: 0000000000001218 0 SECTION LOCAL DEFAULT 17 18: 0000000000002000 0 SECTION LOCAL DEFAULT 18 19: 000000000000201c 0 SECTION LOCAL DEFAULT 19 20: 0000000000002068 0 SECTION LOCAL DEFAULT 20 21: 0000000000003db8 0 SECTION LOCAL DEFAULT 21 22: 0000000000003dc0 0 SECTION LOCAL DEFAULT 22 23: 0000000000003dc8 0 SECTION LOCAL DEFAULT 23 24: 0000000000003fb8 0 SECTION LOCAL DEFAULT 24 25: 0000000000004000 0 SECTION LOCAL DEFAULT 25 26: 0000000000004018 0 SECTION LOCAL DEFAULT 26 27: 0000000000000000 0 SECTION LOCAL DEFAULT 27 28: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 29: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones 30: 00000000000010c0 0 FUNC LOCAL DEFAULT 16 register_tm_clones 31: 0000000000001100 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux 32: 0000000000004018 1 OBJECT LOCAL DEFAULT 26 completed.8060 33: 0000000000003dc0 0 OBJECT LOCAL DEFAULT 22 __do_global_dtors_aux_fin 34: 0000000000001140 0 FUNC LOCAL DEFAULT 16 frame_dummy 35: 0000000000003db8 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_init_array_ 36: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c 37: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 38: 000000000000218c 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__ 39: 0000000000000000 0 FILE LOCAL DEFAULT ABS 40: 0000000000003dc0 0 NOTYPE LOCAL DEFAULT 21 __init_array_end 41: 0000000000003dc8 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC 42: 0000000000003db8 0 NOTYPE LOCAL DEFAULT 21 __init_array_start 43: 000000000000201c 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR 44: 0000000000003fb8 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_ 45: 0000000000001000 0 FUNC LOCAL DEFAULT 12 _init 46: 0000000000001210 5 FUNC GLOBAL DEFAULT 16 __libc_csu_fini 47: 0000000000004010 8 OBJECT GLOBAL DEFAULT 25 my_name 48: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab 49: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start 50: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 25 _edata 51: 0000000000001218 0 FUNC GLOBAL HIDDEN 17 _fini 52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5 53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_ 54: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start 55: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 56: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle 57: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used 58: 00000000000011a0 101 FUNC GLOBAL DEFAULT 16 __libc_csu_init 59: 0000000000004020 0 NOTYPE GLOBAL DEFAULT 26 _end 60: 0000000000001060 47 FUNC GLOBAL DEFAULT 16 _start 61: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 __bss_start 62: 0000000000001174 30 FUNC GLOBAL DEFAULT 16 main 63: 0000000000001149 43 FUNC GLOBAL DEFAULT 16 say_hello 64: 0000000000004018 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__ 65: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 66: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@GLIBC_2.2 在最后位置找到了亲切的老朋友 main和say_hello 62: 0000000000001174 30 FUNC GLOBAL DEFAULT 16 main 63: 0000000000001149 43 FUNC GLOBAL DEFAULT 16 say_hello main函数符号对应的数值为0x1174,其类型为FUNC,大小为30字节,对应的代码区索引为16; say_hello函数符号对应数值为0x1149,其类型为FUNC,大小为43字节,对应的代码区索引同为16。 Section Header Table: [16] .text PROGBITS 0000000000001060 00001060 00000000000001b5 0000000000000000 AX 0 0 16 反汇编代码区 在理解了String Table和Symbol Table的作用后,通过objdump反汇编来理解一下.text代码区: root@5e3abe332c5a:/home/docker/test4harmony# objdump -j .text -l -C -S app 0000000000001149 <say_hello>: say_hello(): 1149: f3 0f 1e fa endbr64 114d: 55 push %rbp 114e: 48 89 e5 mov %rsp,%rbp 1151: 48 83 ec 10 sub $0x10,%rsp 1155: 48 89 7d f8 mov %rdi,-0x8(%rbp) 1159: 48 8b 45 f8 mov -0x8(%rbp),%rax 115d: 48 89 c6 mov %rax,%rsi 1160: 48 8d 3d 9d 0e 00 00 lea 0xe9d(%rip),%rdi # 2004 <_IO_stdin_used+0x4> 1167: b8 00 00 00 00 mov $0x0,%eax 116c: e8 df fe ff ff callq 1050 <printf@plt> 1171: 90 nop 1172: c9 leaveq 1173: c3 retq 0000000000001174 <main>: main(): 1174: f3 0f 1e fa endbr64 1178: 55 push %rbp 1179: 48 89 e5 mov %rsp,%rbp 117c: 48 8b 05 8d 2e 00 00 mov 0x2e8d(%rip),%rax # 4010 <my_name> 1183: 48 89 c7 mov %rax,%rdi 1186: e8 be ff ff ff callq 1149 <say_hello> 118b: b8 00 00 00 00 mov $0x0,%eax 1190: 5d pop %rbp 1191: c3 retq 1192: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 1199: 00 00 00 119c: 0f 1f 40 00 nopl 0x0(%rax) 0x1149 0x1174正是say_hello,main函数的入口地址.并看到了激动人心的指令 1186: e8 be ff ff ff callq 1149 <say_hello> 很佩服你还能看到这里,牛逼,牛逼! 看了这么久还记得开头的C代码的样子吗? 再看一遍 : ) #include <stdio.h> void say_hello(char *who) { printf("hello, %s!\n", who); } char *my_name = "harmony os"; int main() { say_hello(my_name); return 0; } root@5e3abe332c5a:/home/docker/test4harmony# ./app hello, harmony os! 但是!!! 晕,怎么还有but,西卡西...,上面请大家记住的还有一个地方没说到 Entry point address: 0x1060 //代码区 .text 起始位置,即程序运行开始位置 它的地址并不是main函数位置0x1174,是0x1060!而且代码区的开始位置是0x1060没错的. [16] .text PROGBITS 0000000000001060 00001060 00000000000001b5 0000000000000000 AX 0 0 16 难度main不是入口地址? 那0x1060上放的是何方神圣,再查符号表发现是 60: 0000000000001060 47 FUNC GLOBAL DEFAULT 16 _start 从反汇编堆中找到 _start 0000000000001060 <_start>: _start(): 1060: f3 0f 1e fa endbr64 1064: 31 ed xor %ebp,%ebp 1066: 49 89 d1 mov %rdx,%r9 1069: 5e pop %rsi 106a: 48 89 e2 mov %rsp,%rdx 106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 1071: 50 push %rax 1072: 54 push %rsp 1073: 4c 8d 05 96 01 00 00 lea 0x196(%rip),%r8 # 1210 <__libc_csu_fini> 107a: 48 8d 0d 1f 01 00 00 lea 0x11f(%rip),%rcx # 11a0 <__libc_csu_init> 1081: 48 8d 3d ec 00 00 00 lea 0xec(%rip),%rdi # 1174 <main> 1088: ff 15 52 2f 00 00 callq *0x2f52(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5> 108e: f4 hlt 108f: 90 nop 这才看到了0x1174的main函数.所以真正的说法是: 从内核动态加载的视角看,程序运行首个函数并不是main,而是_start. 但从应用程序开发者视角看,main就是启动函数. 鸿蒙源码百篇博客 往期回顾 在给 鸿蒙内核源码加中文注释 过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感.百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.:P 写文章比写代码累多了,越深入研究,越觉得没写好,所以文章和注解会反复修正, .xx代表修改的次数, 将持续完善源码注解和文档内容,精雕细琢,言简意赅, 尽全力打磨精品内容. v51.xx (ELF格式篇) | 用户程序入口并不是main < csdn | harmony | 51cto | osc > v50.xx (编译环境篇) | 编译鸿蒙看这篇或许真的够了 < csdn | harmony | 51cto | osc > v49.xx (信号消费篇) | 谁让CPU连续四次换栈运行 < csdn | harmony | 51cto | osc > v48.xx (信号生产篇) | 年过半百,依然活力十足 < csdn | harmony | 51cto | osc > v47.xx (进程回收篇) | 临终前如何向老祖宗托孤 < csdn | harmony | 51cto | osc > v46.xx (特殊进程篇) | 龙生龙凤生凤老鼠生儿会打洞 < csdn | harmony | 51cto | osc > v45.xx (Fork篇) | 一次调用,两次返回 < csdn | harmony | 51cto | osc > v44.xx (中断管理篇) | 江湖从此不再怕中断 < csdn | harmony | 51cto | osc > v43.xx (中断概念篇) | 海公公的日常工作 < csdn | harmony | 51cto | osc > v42.xx (中断切换篇) | 系统因中断活力四射 < csdn | harmony | 51cto | osc > v41.xx (任务切换篇) | 切换交给了单纯的汇编 < csdn | harmony | 51cto | osc > v40.xx (汇编汇总篇) | 汇编即可爱又单纯 < csdn | harmony | 51cto | osc > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | harmony | 51cto | osc > v38.xx (寄存器篇) | 小强乃宇宙最忙存储器 < csdn | harmony | 51cto | osc > v37.xx (系统调用篇) | 开发者永远的口头禅 < csdn | harmony | 51cto | osc > v36.xx (工作模式篇) | CPU是韦小宝,七个老婆 < csdn | harmony | 51cto | osc > v35.xx (时间管理篇) | 谁是操作系统基本时间单位 < csdn | harmony | 51cto | osc > v34.xx (原子操作篇) | 谁在为原子操作保驾护航 < csdn | harmony | 51cto | osc > v33.xx (消息队列篇) | 进程间如何异步传递大数据 < csdn | harmony | 51cto | osc > v32.xx (CPU篇) | 整个内核就是一个死循环 < csdn | harmony | 51cto | osc > v31.xx (定时器篇) | 哪个任务的优先级最高 < csdn | harmony | 51cto | osc > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | harmony | 51cto | osc > v29.xx (信号量篇) | 谁在负责解决任务的同步 < csdn | harmony | 51cto | osc > v28.xx (进程通讯篇) | 九种进程间通讯方式速揽 < csdn | harmony | 51cto | osc > v27.xx (互斥锁篇) | 比自旋锁丰满的互斥锁 < csdn | harmony | 51cto | osc > v26.xx (自旋锁篇) | 自旋锁当立贞节牌坊 < csdn | harmony | 51cto | osc > v25.xx (并发并行篇) | 听过无数编的两个概念 < csdn | harmony | 51cto | osc > v24.xx (进程概念篇) | 进程在管理哪些资源 < csdn | harmony | 51cto | osc > v23.xx (汇编传参篇) | 如何传递复杂的参数 < csdn | harmony | 51cto | osc > v22.xx (汇编基础篇) | CPU在哪里打卡上班 < csdn | harmony | 51cto | osc > v21.xx (线程概念篇) | 是谁在不断的折腾CPU < csdn | harmony | 51cto | osc > v20.xx (用栈方式篇) | 程序运行场地谁提供的 < csdn | harmony | 51cto | osc > v19.xx (位图管理篇) | 谁能一分钱分两半用 < csdn | harmony | 51cto | osc > v18.xx (源码结构篇) | 内核每个文件的含义 < csdn | harmony | 51cto | osc > v17.xx (物理内存篇) | 怎么管理物理内存 < csdn | harmony | 51cto | osc > v16.xx (内存规则篇) | 内存管理到底在管什么 < csdn | harmony | 51cto | osc > v15.xx (内存映射篇) | 虚拟内存虚在哪里 < csdn | harmony | 51cto | osc > v14.xx (内存汇编篇) | 谁是虚拟内存实现的基础 < csdn | harmony | 51cto | osc > v13.xx (源码注释篇) | 鸿蒙必须成功,也必然成功 < csdn | harmony | 51cto | osc > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的 < csdn | harmony | 51cto | osc > v11.xx (内存分配篇) | 内存有哪些分配方式 < csdn | harmony | 51cto | osc > v10.xx (内存主奴篇) | 皇上和奴才如何相处 < csdn | harmony | 51cto | osc > v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | harmony | 51cto | osc > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | harmony | 51cto | osc > v07.xx (调度机制篇) | 任务是如何被调度执行的 < csdn | harmony | 51cto | osc > v06.xx (调度队列篇) | 内核有多少个调度队列 < csdn | harmony | 51cto | osc > v05.xx (任务管理篇) | 任务池是如何管理的 < csdn | harmony | 51cto | osc > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | harmony | 51cto | osc > v03.xx (时钟任务篇) | 触发调度谁的贡献最大 < csdn | harmony | 51cto | osc > v02.xx (进程管理篇) | 谁在管理内核资源 < csdn | harmony | 51cto | osc > v01.xx (双向链表篇) | 谁是内核最重要结构体 < csdn | harmony | 51cto | osc > 进入 >> osc | csdn | 51cto | 掘金 | 公众号 | 头条号 | gitee | github 关注领取.免费资料.定期更新 热爱是所有的理由和答案 - turing 百万汉字注解鸿蒙源码,百篇博客深挖地基工程. 原创不易,欢迎转载,但麻烦请注明出处.

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

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

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | 51cto | csdn | harmony > 编译鸿蒙 本篇记录编译鸿蒙的过程,以备后续不用再去一大堆无效的误导式软文中搜寻芝麻大点有用的信息,那样真挺费时的. 编译环境 先安装 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,创建好容器,本文的选择是这样的,当然大家可以灵活处理,命名. 容器创建成功后可以在 vscode 右键容器inspect查看到绑定的目录. "HostConfig": { "Binds": [ "E:\\harmony\\code-1.0:/home/harmony", "E:\\harmony\\docker:/home/docker" ], 本文这样做的目的为了在windows上能方便的查看文件.harmony目录用于下载编译源码目录,另外一个docker放其他无关文件 repo方式下载源码 进入容器,vscode右键容器 Attach shell 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格式后续有专门的篇幅详细介绍,此处不做说明. 用户进程 用grep过滤下干扰的*.bin,剩下的就是平台(Hi3518)需要内核创建的用户态进程. root@5e3abe332c5a:/home/harmony/out/hispark_aries/ipcamera_hispark_aries/bin# ls | grep -v .bin ai_server apphilogcat appspawn bundle_daemon foundation hilogcat init media_server os_dump shell tftp wms_server 这些服务含义和仓库如下. ai_server AI业务子系统 https://gitee.com/openharmony/ai_engine apphilogcat 小型系统的流水日志功能 https://gitee.com/openharmony/hiviewdfx_hilog_lite base\hiviewdfx\hilog_lite\services\apphilogcat\hiview_applogcat.c appspawn 应用孵化模块进程 https://gitee.com/openharmony/startup_appspawn_lite bundle_daemon 用户程序框架内部工具接口 https://gitee.com/openharmony/appexecfwk_appexecfwk_lite foundation foundation系统进程 https://gitee.com/openharmony/distributedschedule_safwk_lite hilogcat 管理日志打印 https://gitee.com/openharmony/hiviewdfx_hilog_lite base\hiviewdfx\hilog_lite\services\hilogcat\hiview_logcat.c init 用户态祖宗进程 1号进程, -> 鸿蒙内核源码分析(特殊进程篇) media_server 播放模块框架实现 https://gitee.com/openharmony/multimedia_media_lite os_dump 备份文件系统 utils\native\lite\os_dump\os_dump.c shell 窥视内核的窗口 3号进程, -> 鸿蒙内核源码分析(shell篇) tftp 传输文件 third_party\curl\lib\tftp.c wms_server 窗口管理服务 https://gitee.com/openharmony/graphic_wms 在整个项目工程中能轻易找到他们的入口函数.比如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源码 | 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 >

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

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