首页 文章 精选 留言 我的

精选列表

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

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

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< 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,文件大小为0x225,映射到进程地址0x001000处,属性为只读可执行(RE),段地址按0x1000(4K)边界对齐. 其第四段包含了数据区,文件内偏移为0x2db8,文件大小为0x260,映射到进程地址0x003db8处,属性为可读可写(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 13个段和31个区的映射关系,右边其实不止31个区,是因为一个区可以共属于多个段,例如 .dynamic ,.interp,.got Segment:Section(M:N)是多对多的包含关系.Segment是由多个Section组成,Section也能属于多个段.这个很重要,说第二遍了. INTERP段只包含了.interp区 LOAD2段包含.interp、.plt、.text等区,.text代码区位于这个段. 这个段是 'RE'属性,只读可执行的. LOAD4包含.dynamic、.data、.bss等区, 数据区位于这个段.这个段是 'RW'属性,可读可写. .data、.bss都是数据区,有何区别呢? .data(ZI data)它用来存放初始化了的(initailized)全局变量(global)和初始化了的静态变量(static). .bss(RW data )它用来存放未初始化的(uninitailized)全局变量(global)和未初始化的静态变量. 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 | v44.02

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | csdn | weharmony > 关于中断部分系列篇将用三篇详细说明整个过程. 中断概念篇 中断概念很多,比如中断控制器,中断源,中断向量,中断共享,中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.用海公公打比方说明白中断各个概念.可前往鸿蒙内核源码分析(总目录)查看. 中断管理篇(本篇) 从中断初始化HalIrqInit开始,到注册中断的LOS_HwiCreate函数,到消费中断函数的 HalIrqHandler,剖析鸿蒙内核实现中断的过程,很像设计模式中的观察者模式. 中断切换篇 用自下而上的方式,从中断源头纯汇编代码往上跟踪代码细节.说清楚保存和恢复中断现场TaskIrqContext过程.可前往鸿蒙内核源码分析(总目录)查看. 编译开关 系列篇编译平台为 hi3516dv300,整个工程可前往查看. 预编译处理过程会自动生成编译开关 menuconfig.h ,供编译阶段选择编译,可前往查看. //.... #define LOSCFG_ARCH_ARM_VER "armv7-a" #define LOSCFG_ARCH_CPU "cortex-a7" #define LOSCFG_PLATFORM "hi3516dv300" #define LOSCFG_PLATFORM_BSP_GIC_V2 1 #define LOSCFG_PLATFORM_ROOTFS 1 #define LOSCFG_KERNEL_CPPSUPPORT 1 #define LOSCFG_HW_RANDOM_ENABLE 1 #define LOSCFG_ARCH_CORTEX_A7 1 #define LOSCFG_DRIVERS_HDF_PLATFORM_RTC 1 #define LOSCFG_DRIVERS_HDF_PLATFORM_UART 1 中断初始化 hi3516dv300 中断控制器选择了 LOSCFG_PLATFORM_BSP_GIC_V2 ,对应代码为 gic_v2.c GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器. 看这种代码因为涉及硬件部分,需要对照ARM中断控制器 gic_v2.pdf文档看.可前往地址下载查看. //硬件中断初始化 VOID HalIrqInit(VOID) { UINT32 i; /* set externel interrupts to be level triggered, active low. */ //将外部中断设置为电平触发,低电平激活 for (i = 32; i < OS_HWI_MAX_NUM; i += 16) { GIC_REG_32(GICD_ICFGR(i / 16)) = 0; } /* set externel interrupts to CPU 0 */ //将外部中断设置为CPU 0 for (i = 32; i < OS_HWI_MAX_NUM; i += 4) { GIC_REG_32(GICD_ITARGETSR(i / 4)) = 0x01010101; } /* set priority on all interrupts */ //设置所有中断的优先级 for (i = 0; i < OS_HWI_MAX_NUM; i += 4) { GIC_REG_32(GICD_IPRIORITYR(i / 4)) = GICD_INT_DEF_PRI_X4; } /* disable all interrupts. */ //禁用所有中断。 for (i = 0; i < OS_HWI_MAX_NUM; i += 32) { GIC_REG_32(GICD_ICENABLER(i / 32)) = ~0; } HalIrqInitPercpu();//初始化当前CPU中断信息 /* enable gic distributor control */ GIC_REG_32(GICD_CTLR) = 1; //使能分发中断寄存器,该寄存器作用是允许给CPU发送中断信号 #if (LOSCFG_KERNEL_SMP == YES) /* register inter-processor interrupt *///注册核间中断,啥意思?就是CPU各核直接可以发送中断信号 //处理器间中断允许一个CPU向系统其他的CPU发送中断信号,处理器间中断(IPI)不是通过IRQ线传输的,而是作为信号直接放在连接所有CPU本地APIC的总线上。 LOS_HwiCreate(LOS_MP_IPI_WAKEUP, 0xa0, 0, OsMpWakeHandler, 0);//注册唤醒CPU的中断处理函数 LOS_HwiCreate(LOS_MP_IPI_SCHEDULE, 0xa0, 0, OsMpScheduleHandler, 0);//注册调度CPU的中断处理函数 LOS_HwiCreate(LOS_MP_IPI_HALT, 0xa0, 0, OsMpScheduleHandler, 0);//注册停止CPU的中断处理函数 #endif } //给每个CPU core初始化硬件中断 VOID HalIrqInitPercpu(VOID) { /* unmask interrupts */ //取消中断屏蔽 GIC_REG_32(GICC_PMR) = 0xFF; /* enable gic cpu interface */ //启用gic cpu接口 GIC_REG_32(GICC_CTLR) = 1; } 解读 上来四个循环,是对中断控制器寄存器组的初始化,也就是驱动程序,驱动程序是配置硬件寄存器的过程.寄存器分通用和专用寄存器.下图为 gic_v2 的寄存器功能 ,这里对照代码和datasheet重点说下中断配置寄存器(GICD_ICFGRn) 以下是GICD_ICFGRn的介绍 The GICD_ICFGRs provide a 2-bit Int_config field for each interrupt supported by the GIC. This field identifies whether the corresponding interrupt is edge-triggered or level-sensitive GICD_ICFGRs为GIC支持的每个中断提供一个2位配置字段。此字段标识相应的中断是边缘触发的还是电平触发的 0xC00 - 0xCFC GICD_ICFGRn RW IMPLEMENTATION DEFINED Interrupt Configuration Registers #define GICD_ICFGR(n) (GICD_OFFSET + 0xc00 + (n) * 4) /* Interrupt Configuration Registers */ //中断配置寄存器 如此一个32位寄存器可以记录16个中断的信息,这也是代码中出现 GIC_REG_32(GICD_ICFGR(i / 16))的原因. GIC-v2支持三种类型的中断 PPI:私有外设中断(Private Peripheral Interrupt),是每个CPU私有的中断。最多支持16个PPI中断,硬件中断号从ID16~ID31。PPI通常会送达到指定的CPU上,应用场景有CPU本地时钟。 SPI:公用外设中断(Shared Peripheral Interrupt),最多可以支持988个外设中断,硬件中断号从ID32~ID1019。 SGI:软件触发中断(Software Generated Interrupt)通常用于多核间通讯,最多支持16个SGI中断,硬件中断号从ID0~ID15。SGI通常在内核中被用作 IPI 中断(inter-processor interrupts),并会送达到系统指定的CPU上,函数的最后就注册了三个核间中断的函数. typedef enum {//核间中断 LOS_MP_IPI_WAKEUP, //唤醒CPU LOS_MP_IPI_SCHEDULE,//调度CPU LOS_MP_IPI_HALT, //停止CPU } MP_IPI_TYPE; 中断相关的结构体 size_t g_intCount[LOSCFG_KERNEL_CORE_NUM] = {0};//记录每个CPUcore的中断数量 HwiHandleForm g_hwiForm[OS_HWI_MAX_NUM];//中断注册表 @note_why 用 form 来表示?有种写 HTML的感觉 :P STATIC CHAR *g_hwiFormName[OS_HWI_MAX_NUM] = {0};//记录每个硬中断的名称 STATIC UINT32 g_hwiFormCnt[OS_HWI_MAX_NUM] = {0};//记录每个硬中断的总数量 STATIC UINT32 g_curIrqNum = 0; //记录当前中断号 typedef VOID (*HWI_PROC_FUNC)(VOID); //中断函数指针 typedef struct tagHwiHandleForm { HWI_PROC_FUNC pfnHook; //中断处理函数 HWI_ARG_T uwParam; //中断处理函数参数 struct tagHwiHandleForm *pstNext; //节点,指向下一个中断,用于共享中断的情况 } HwiHandleForm; typedef struct tagIrqParam { //中断参数 int swIrq; // 软件中断 VOID *pDevId; // 设备ID const CHAR *pName; //名称 } HwiIrqParam; 注册硬中断 /****************************************************************************** 创建一个硬中断 中断创建,注册中断号、中断触发模式、中断优先级、中断处理程序。中断被触发时, handleIrq会调用该中断处理程序 ******************************************************************************/ LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiCreate(HWI_HANDLE_T hwiNum, //硬中断句柄编号 默认范围[0-127] HWI_PRIOR_T hwiPrio, //硬中断优先级 HWI_MODE_T hwiMode, //硬中断模式 共享和非共享 HWI_PROC_FUNC hwiHandler,//硬中断处理函数 HwiIrqParam *irqParam) //硬中断处理函数参数 { UINT32 ret; (VOID)hwiPrio; if (hwiHandler == NULL) {//中断处理函数不能为NULL return OS_ERRNO_HWI_PROC_FUNC_NULL; } if ((hwiNum > OS_USER_HWI_MAX) || ((INT32)hwiNum < OS_USER_HWI_MIN)) {//中断数区间限制 [32,96] return OS_ERRNO_HWI_NUM_INVALID; } #ifdef LOSCFG_NO_SHARED_IRQ //不支持共享中断 ret = OsHwiCreateNoShared(hwiNum, hwiMode, hwiHandler, irqParam); #else ret = OsHwiCreateShared(hwiNum, hwiMode, hwiHandler, irqParam); #endif return ret; } //创建一个共享硬件中断,共享中断就是一个中断能触发多个响应函数 STATIC UINT32 OsHwiCreateShared(HWI_HANDLE_T hwiNum, HWI_MODE_T hwiMode, HWI_PROC_FUNC hwiHandler, const HwiIrqParam *irqParam) { UINT32 intSave; HwiHandleForm *hwiFormNode = NULL; HwiHandleForm *hwiForm = NULL; HwiIrqParam *hwiParam = NULL; HWI_MODE_T modeResult = hwiMode & IRQF_SHARED; if (modeResult && ((irqParam == NULL) || (irqParam->pDevId == NULL))) { return OS_ERRNO_HWI_SHARED_ERROR; } HWI_LOCK(intSave);//中断自旋锁 hwiForm = &g_hwiForm[hwiNum];//获取中断处理项 if ((hwiForm->pstNext != NULL) && ((modeResult == 0) || (!(hwiForm->uwParam & IRQF_SHARED)))) { HWI_UNLOCK(intSave); return OS_ERRNO_HWI_SHARED_ERROR; } while (hwiForm->pstNext != NULL) {//pstNext指向 共享中断的各处理函数节点,此处一直撸到最后一个 hwiForm = hwiForm->pstNext;//找下一个中断 hwiParam = (HwiIrqParam *)(hwiForm->uwParam);//获取中断参数,用于检测该设备ID是否已经有中断处理函数 if (hwiParam->pDevId == irqParam->pDevId) {//设备ID一致时,说明设备对应的中断处理函数已经存在了. HWI_UNLOCK(intSave); return OS_ERRNO_HWI_ALREADY_CREATED; } } hwiFormNode = (HwiHandleForm *)LOS_MemAlloc(m_aucSysMem0, sizeof(HwiHandleForm));//创建一个中断处理节点 if (hwiFormNode == NULL) { HWI_UNLOCK(intSave); return OS_ERRNO_HWI_NO_MEMORY; } hwiFormNode->uwParam = OsHwiCpIrqParam(irqParam);//获取中断处理函数的参数 if (hwiFormNode->uwParam == LOS_NOK) { HWI_UNLOCK(intSave); (VOID)LOS_MemFree(m_aucSysMem0, hwiFormNode); return OS_ERRNO_HWI_NO_MEMORY; } hwiFormNode->pfnHook = hwiHandler;//绑定中断处理函数 hwiFormNode->pstNext = (struct tagHwiHandleForm *)NULL;//指定下一个中断为NULL,用于后续遍历找到最后一个中断项(见于以上 while (hwiForm->pstNext != NULL)处) hwiForm->pstNext = hwiFormNode;//共享中断 if ((irqParam != NULL) && (irqParam->pName != NULL)) { g_hwiFormName[hwiNum] = (CHAR *)irqParam->pName; } g_hwiForm[hwiNum].uwParam = modeResult; HWI_UNLOCK(intSave); return LOS_OK; } 解读 内核将硬中断进行编号,如: #define NUM_HAL_INTERRUPT_TIMER0 33 #define NUM_HAL_INTERRUPT_TIMER1 33 #define NUM_HAL_INTERRUPT_TIMER2 34 #define NUM_HAL_INTERRUPT_TIMER3 34 #define NUM_HAL_INTERRUPT_TIMER4 35 #define NUM_HAL_INTERRUPT_TIMER5 35 #define NUM_HAL_INTERRUPT_TIMER6 36 #define NUM_HAL_INTERRUPT_TIMER7 36 #define NUM_HAL_INTERRUPT_DMAC 60 #define NUM_HAL_INTERRUPT_UART0 38 #define NUM_HAL_INTERRUPT_UART1 39 #define NUM_HAL_INTERRUPT_UART2 40 #define NUM_HAL_INTERRUPT_UART3 41 #define NUM_HAL_INTERRUPT_UART4 42 #define NUM_HAL_INTERRUPT_TIMER NUM_HAL_INTERRUPT_TIMER4 例如:时钟节拍处理函数 OsTickHandler 就是在 HalClockInit中注册的 //硬时钟初始化 VOID HalClockInit(VOID) { // ... (void)LOS_HwiCreate(NUM_HAL_INTERRUPT_TIMER, 0xa0, 0, OsTickHandler, 0);//注册OsTickHandler到中断向量表 } //节拍中断处理函数 ,鸿蒙默认10ms触发一次 LITE_OS_SEC_TEXT VOID OsTickHandler(VOID) { UINT32 intSave; TICK_LOCK(intSave);//tick自旋锁 g_tickCount[ArchCurrCpuid()]++;// 累加当前CPU核tick数 TICK_UNLOCK(intSave); OsTimesliceCheck();//时间片检查 OsTaskScan(); /* task timeout scan *///扫描超时任务 例如:delay(300) #if (LOSCFG_BASE_CORE_SWTMR == YES) OsSwtmrScan();//扫描定时器,查看是否有超时定时器,加入队列 #endif } 鸿蒙是支持中断共享的,在OsHwiCreateShared中,将函数注册到g_hwiForm中.中断向量完成注册后,就是如何触发和回调的问题.触发在 鸿蒙内核源码分析(总目录)中断切换篇中已经讲清楚,触发是从底层汇编向上调用,调用的C函数就是HalIrqHandler 中断怎么触发的? 分两种情况: 通过硬件触发,比如按键,USB的插拔这些中断源向中断控制器发送电信号(高低电平触发或是上升/下降沿触发),中断控制器经过过滤后将信号发给对应的CPU处理,通过硬件改变PC和CPSR寄存值,直接跳转到中断向量(固定地址)执行. b reset_vector @开机代码 b _osExceptUndefInstrHdl @异常处理之CPU碰到不认识的指令 b _osExceptSwiHdl @异常处理之:软中断 b _osExceptPrefetchAbortHdl @异常处理之:取指异常 b _osExceptDataAbortHdl @异常处理之:数据异常 b _osExceptAddrAbortHdl @异常处理之:地址异常 b OsIrqHandler @异常处理之:硬中断 b _osExceptFiqHdl @异常处理之:快中断 通过软件触发,常见于核间中断的情况, 核间中断指的是几个CPU之间相互通讯的过程.以下为某一个CPU向其他CPU(可以是多个)发起让这些CPU重新调度LOS_MpSchedule的中断请求信号.最终是写了中断控制器的GICD_SGIR寄存器,这是一个由软件触发中断的寄存器.中断控制器会将请求分发给对应的CPU处理中断,即触发了OsIrqHandler. //给参数CPU发送调度信号 VOID LOS_MpSchedule(UINT32 target)//target每位对应CPU core { UINT32 cpuid = ArchCurrCpuid(); target &= ~(1U << cpuid);//获取除了自身之外的其他CPU HalIrqSendIpi(target, LOS_MP_IPI_SCHEDULE);//向目标CPU发送调度信号,核间中断(Inter-Processor Interrupts),IPI } //SGI软件触发中断(Software Generated Interrupt)通常用于多核间通讯 STATIC VOID GicWriteSgi(UINT32 vector, UINT32 cpuMask, UINT32 filter) { UINT32 val = ((filter & 0x3) << 24) | ((cpuMask & 0xFF) << 16) | (vector & 0xF); GIC_REG_32(GICD_SGIR) = val;//写SGI寄存器 } //向指定核发送核间中断 VOID HalIrqSendIpi(UINT32 target, UINT32 ipi) { GicWriteSgi(ipi, target, 0); } 中断统一处理入口函数 HalIrqHandler //硬中断统一处理函数,这里由硬件触发,调用见于 ..\arch\arm\arm\src\los_dispatch.S VOID HalIrqHandler(VOID) { UINT32 iar = GIC_REG_32(GICC_IAR);//从中断确认寄存器获取中断ID号 UINT32 vector = iar & 0x3FFU;//计算中断向量号 /* * invalid irq number, mainly the spurious interrupts 0x3ff, * gicv2 valid irq ranges from 0~1019, we use OS_HWI_MAX_NUM * to do the checking. */ if (vector >= OS_HWI_MAX_NUM) { return; } g_curIrqNum = vector;//记录当前中断ID号 OsInterrupt(vector);//调用上层中断处理函数 /* use orignal iar to do the EOI */ GIC_REG_32(GICC_EOIR) = iar;//更新中断结束寄存器 } VOID OsInterrupt(UINT32 intNum)//中断实际处理函数 { HwiHandleForm *hwiForm = NULL; UINT32 *intCnt = NULL; intCnt = &g_intCount[ArchCurrCpuid()];//当前CPU的中断总数量 ++ *intCnt = *intCnt + 1;//@note_why 这里没看明白为什么要 +1 #ifdef LOSCFG_CPUP_INCLUDE_IRQ //开启查询系统CPU的占用率的中断 OsCpupIrqStart();//记录本次中断处理开始时间 #endif #ifdef LOSCFG_KERNEL_TICKLESS OsTicklessUpdate(intNum); #endif hwiForm = (&g_hwiForm[intNum]);//获取对应中断的实体 #ifndef LOSCFG_NO_SHARED_IRQ //如果没有定义不共享中断 ,意思就是如果是共享中断 while (hwiForm->pstNext != NULL) { //一直撸到最后 hwiForm = hwiForm->pstNext;//下一个继续撸 #endif if (hwiForm->uwParam) {//有参数的情况 HWI_PROC_FUNC2 func = (HWI_PROC_FUNC2)hwiForm->pfnHook;//获取回调函数 if (func != NULL) { UINTPTR *param = (UINTPTR *)(hwiForm->uwParam); func((INT32)(*param), (VOID *)(*(param + 1)));//运行带参数的回调函数 } } else {//木有参数的情况 HWI_PROC_FUNC0 func = (HWI_PROC_FUNC0)hwiForm->pfnHook;//获取回调函数 if (func != NULL) { func();//运行回调函数 } } #ifndef LOSCFG_NO_SHARED_IRQ } #endif ++g_hwiFormCnt[intNum];//中断号计数器总数累加 *intCnt = *intCnt - 1; //@note_why 这里没看明白为什么要 -1 #ifdef LOSCFG_CPUP_INCLUDE_IRQ //开启查询系统CPU的占用率的中断 OsCpupIrqEnd(intNum);//记录中断处理时间完成时间 #endif } 解读 统一中断处理函数是一个通过一个中断号去找到注册函数的过程,分四步走: 第一步:取号,这号是由中断控制器的 GICC_IAR寄存器提供的,这是一个专门保存当前中断号的寄存器. 第二步:从注册表g_hwiForm中查询注册函数,同时取出参数. 第三步:执行函数,也就是回调注册函数,分有参和无参两种情况 func(...),在中断共享的情况下,注册函数会指向下一个注册函数pstNext,依次执行回调函数,这是中断共享的实现细节. typedef struct tagHwiHandleForm { HWI_PROC_FUNC pfnHook; //中断处理函数 HWI_ARG_T uwParam; //中断处理函数参数 struct tagHwiHandleForm *pstNext; //节点,指向下一个中断,用于共享中断的情况 } HwiHandleForm; 第四步:销号,本次中断完成了就需要消除记录,中断控制器也有专门的销号寄存器GICC_EOIR 另外的是一些统一数据,每次中断号处理内核都会记录次数,和耗时,以便定位/跟踪/诊断问题. 参与贡献 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" .欢迎转载,请注明出处. 进入 >> 百万汉字注解 百篇博客分析 精读鸿蒙源码 深挖地基工程 < gitee | csdn | oschina >

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

鸿蒙内核源码分析(时间管理篇) | Tick是操作系统的基本时间单位 | 中文注解HarmonyOS源码 | v35.01

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony > 本篇说清楚时间概念 读本篇之前建议先读鸿蒙内核源码分析(总目录)其他篇. 时间概念太重要了,在鸿蒙内核又是如何管理和使用时间的呢? 时间管理以系统时钟 g_sysClock 为基础,给应用程序提供所有和时间有关的服务。 用户以秒、毫秒为单位计时. 操作系统以Tick为单位计时,这个认识很重要. 每秒的tick大小很大程度上决定了内核调度的次数多少. 当用户需要对系统进行操作时,例如任务挂起、延时等,此时需要时间管理模块对Tick和秒/毫秒进行转换。 熟悉两个概念: Cycle(周期):系统最小的计时单位。Cycle的时长由系统主时钟频率决定,系统主时钟频率就是每秒钟的Cycle数。 Tick(节拍):Tick是操作系统的基本时间单位,由用户配置的每秒Tick数决定,可大可小. 怎么去理解他们之间的关系呢?看几个宏定义就清楚了. #ifndef OS_SYS_CLOCK //HZ:是每秒中的周期性变动重复次数的计量 #define OS_SYS_CLOCK (get_bus_clk()) //系统主时钟频率 例如:50000000 即50微秒 #endif #ifndef LOSCFG_BASE_CORE_TICK_PER_SECOND #define LOSCFG_BASE_CORE_TICK_PER_SECOND 100 //每秒Tick数,意味着正常情况下每秒调度100次 #endif #define OS_CYCLE_PER_TICK (g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND) //每个tick多少机器周期 时钟周期(振荡周期) 在鸿蒙g_sysClock表示时钟周期,是CPU的赫兹,也就是上面说的Cycle,这是固定不变的,由硬件晶振的频率决定的. OsMain是内核运行的第一个C函数,首个子函数就是 osRegister,完成对g_sysClock的赋值 LITE_OS_SEC_TEXT_INIT VOID osRegister(VOID) { g_sysClock = OS_SYS_CLOCK; //获取CPU HZ g_tickPerSecond = LOSCFG_BASE_CORE_TICK_PER_SECOND;//每秒节拍数 默认100 即一个tick = 10ms return; } CPU周期也叫(机器周期) 在鸿蒙宏OS_CYCLE_PER_TICK表示机器周期,Tick由用户根据实际情况配置. 例如:主频为1G的CPU,其振荡周期为: 1吉赫 (GHz 109 Hz) = 1 000 000 000 Hz 当Tick为100时,则1 000 000 000/100 = 10000000 ,即一秒内可产生1千万个CPU周期.CPU就是用这1千万个周期去执行指令的. 指令周期 指令周期是执行一条指令所需要的时间,一般由若干个机器周期组成。指令不同,所需的机器周期数也不同。 对于一些简单的的单字节指令,在取指令周期中,指令取出到指令寄存器后,立即译码执行,不再需要其它的机器周期。 对于一些比较复杂的指令,例如转移指令、乘法指令,则需要两个或者两个以上的机器周期。 通常含一个机器周期的指令称为单周期指令,包含两个机器周期的指令称为双周期指令。 Tick硬中断函数 LITE_OS_SEC_BSS volatile UINT64 g_tickCount[LOSCFG_KERNEL_CORE_NUM] = {0};//tick计数器,系统一旦启动,一直在++, 为防止溢出,这是一个 UINT64 的变量 LITE_OS_SEC_DATA_INIT UINT32 g_sysClock;//系统时钟,是绝大部分部件工作的时钟源,也是其他所有外设的时钟的来源 LITE_OS_SEC_DATA_INIT UINT32 g_tickPerSecond;//每秒Tick数,鸿蒙默认是每秒100次,即:10ms LITE_OS_SEC_BSS DOUBLE g_cycle2NsScale; //周期转纳秒级 /* spinlock for task module */ LITE_OS_SEC_BSS SPIN_LOCK_INIT(g_tickSpin); //节拍器自旋锁 #define TICK_LOCK(state) LOS_SpinLockSave(&g_tickSpin, &(state)) /* * Description : Tick interruption handler *///节拍中断处理函数 ,鸿蒙默认10ms触发一次 LITE_OS_SEC_TEXT VOID OsTickHandler(VOID) { UINT32 intSave; TICK_LOCK(intSave); g_tickCount[ArchCurrCpuid()]++;//当前CPU核计数器 TICK_UNLOCK(intSave); #ifdef LOSCFG_KERNEL_VDSO OsUpdateVdsoTimeval(); #endif #ifdef LOSCFG_KERNEL_TICKLESS OsTickIrqFlagSet(OsTicklessFlagGet()); #endif #if (LOSCFG_BASE_CORE_TICK_HW_TIME == YES) HalClockIrqClear(); /* diff from every platform */ #endif OsTimesliceCheck();//时间片检查 OsTaskScan(); /* task timeout scan *///任务扫描 #if (LOSCFG_BASE_CORE_SWTMR == YES) OsSwtmrScan();//定时器扫描,看是否有超时的定时器 #endif } #ifdef __cplusplus #if __cplusplus } 解读 g_tickCount记录每个CPU核tick的数组,每次硬中断都触发 OsTickHandler,每个CPU核单独计数. OsTickHandler是内核调度的动力,其中会检查任务时间片是否用完,定时器是否超时.主动delay的任务是否需要被唤醒,其本质是个硬中断,在HalClockInit硬时钟初始化时创建的,具体在硬中断篇中会详细讲解. TICK_LOCK是tick操作的自旋锁,宏原型LOS_SpinLockSave在自旋锁篇中已详细介绍. 功能函数 #define OS_SYS_MS_PER_SECOND 1000 //一秒多少毫秒 //获取自系统启动以来的Tick数 LITE_OS_SEC_TEXT_MINOR UINT64 LOS_TickCountGet(VOID) { UINT32 intSave; UINT64 tick; /* * use core0's tick as system's timeline, * the tick needs to be atomic. */ TICK_LOCK(intSave); tick = g_tickCount[0];//使用CPU core0作为系统的 tick数 TICK_UNLOCK(intSave); return tick; } //每个Tick多少Cycle数 LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID) { return g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND; } //毫秒转换成Tick LITE_OS_SEC_TEXT_MINOR UINT32 LOS_MS2Tick(UINT32 millisec) { if (millisec == OS_MAX_VALUE) { return OS_MAX_VALUE; } return ((UINT64)millisec * LOSCFG_BASE_CORE_TICK_PER_SECOND) / OS_SYS_MS_PER_SECOND; } //Tick转化为毫秒 LITE_OS_SEC_TEXT_MINOR UINT32 LOS_Tick2MS(UINT32 tick) { return ((UINT64)tick * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND; } 说明 在CPU篇中讲过,0号CPU核默认为主核,默认获取自系统启动以来的Tick数使用的是g_tickCount[0] 因每个CPU核的tick是独立计数的,所以g_tickCount中各值是不一样的. 系统的Tick数在关中断的情况下不进行计数,因为OsTickHandler本质是由硬中断触发的,屏蔽硬中断的情况下就不会触发OsTickHandler,自然也就不会有g_tickCount[ArchCurrCpuid()]++的计数,所以系统Tick数不能作为准确时间使用. 追问下,什么情况下硬中断会被屏蔽? 编程示例 前提条件: 使用每秒的Tick数LOSCFG_BASE_CORE_TICK_PER_SECOND的默认值100。 配好OS_SYS_CLOCK系统主时钟频率。 时间转换 VOID Example_TransformTime(VOID) { UINT32 ms; UINT32 tick; tick = LOS_MS2Tick(10000); // 10000ms转换为tick dprintf("tick = %d \n",tick); ms = LOS_Tick2MS(100); // 100tick转换为ms dprintf("ms = %d \n",ms); } 时间转换结果 tick = 1000 ms = 1000 时间统计和时间延迟 LITE_OS_SEC_TEXT UINT32 LOS_TaskDelay(UINT32 tick); VOID Example_GetTime(VOID) { UINT32 cyclePerTick; UINT64 tickCount; cyclePerTick = LOS_CyclePerTickGet(); if(0 != cyclePerTick) { dprintf("LOS_CyclePerTickGet = %d \n", cyclePerTick); } tickCount = LOS_TickCountGet(); if(0 != tickCount) { dprintf("LOS_TickCountGet = %d \n", (UINT32)tickCount); } LOS_TaskDelay(200);//延迟200个tick tickCount = LOS_TickCountGet(); if(0 != tickCount) { dprintf("LOS_TickCountGet after delay = %d \n", (UINT32)tickCount); } } 时间统计和时间延迟结果 LOS_CyclePerTickGet = 495000 //取决于CPU的频率 LOS_TickCountGet = 1 //实际情况不一定是1的 LOS_TickCountGet after delay = 201 //实际情况不一定是201,但二者的差距会是200 喜欢就大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >

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

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

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony > 本篇说清楚定时器的实现 读本篇之前建议先读鸿蒙内核源码分析(总目录)其余篇. 运作机制 软件定时器,是基于系统Tick时钟中断且由软件来模拟的定时器。当经过设定的Tick数后,会触发用户自定义的回调函数。 软件定时器是系统资源,在模块初始化的时候已经分配了一块连续内存。 软件定时器使用了系统的一个队列和一个任务资源,软件定时器的触发遵循队列规则,先进先出。定时时间短的定时器总是比定时时间长的靠近队列头,满足优先触发的准则。 软件定时器以Tick为基本计时单位,当创建并启动一个软件定时器时,鸿蒙会根据当前系统Tick时间及设置的定时时长确定该定时器的到期Tick时间,并将该定时器控制结构挂入计时全局链表。 当Tick中断到来时,在Tick中断处理函数中扫描软件定时器的计时全局链表,检查是否有定时器超时, 若有则将超时的定时器记录下来。Tick中断处理函数结束后,软件定时器任务(优先级为最高)被唤醒,在该任务中调用已经记录下来的定时器的回调函数。 定时器长什么样? typedef VOID (*SWTMR_PROC_FUNC)(UINTPTR arg);//函数指针, 赋值给 SWTMR_CTRL_S->pfnHandler,回调处理 typedef struct tagSwTmrCtrl {//软件定时器控制块 SortLinkList stSortList;//通过它挂到对应CPU核定时器链表上 UINT8 ucState; /**< Software timer state *///软件定时器的状态 UINT8 ucMode; /**< Software timer mode *///软件定时器的模式 UINT8 ucOverrun; /**< Times that a software timer repeats timing *///软件定时器重复计时的次数 UINT16 usTimerID; /**< Software timer ID *///软件定时器ID,唯一标识,由软件计时器池分配 UINT32 uwCount; /**< Times that a software timer works *///软件定时器工作的时间 UINT32 uwInterval; /**< Timeout interval of a periodic software timer *///周期性软件定时器的超时间隔 UINT32 uwExpiry; /**< Timeout interval of an one-off software timer *///一次性软件定时器的超时间隔 #if (LOSCFG_KERNEL_SMP == YES) UINT32 uwCpuid; /**< The cpu where the timer running on *///多核情况下,定时器运行的cpu #endif UINTPTR uwArg; /**< Parameter passed in when the callback function that handles software timer timeout is called *///回调函数的参数 SWTMR_PROC_FUNC pfnHandler; /**< Callback function that handles software timer timeout */ //处理软件计时器超时的回调函数 UINT32 uwOwnerPid; /** Owner of this software timer *///软件定时器所属进程ID号 } SWTMR_CTRL_S;//变量前缀 uc:UINT8 us:UINT16 uw:UINT32 解读 在多CPU核情况下,定时器是跟着CPU走的,每个CPU核都维护着独立的定时任务链表,上面挂的都是CPU核要处理的定时器. stSortList的背后是双向链表,这对钩子在定时器创建的那一刻会钩到CPU的swtmrSortLink上去. pfnHandler定时器时间到了的执行函数,由外界指定.uwArg为回调函数的参数 ucMode 为定时器模式,软件定时器提供了三类模式 单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动删除。 周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动停止定时器,否则将永远持续执行下去。 单次触发定时器,但这类定时器超时触发后不会自动删除,需要调用定时器删除接口删除定时器。 ucState 定时器状态. OS_SWTMR_STATUS_UNUSED(定时器未使用) 系统在定时器模块初始化时,会将系统中所有定时器资源初始化成该状态。 OS_SWTMR_STATUS_TICKING(定时器处于计数状态) 在定时器创建后调用LOS_SwtmrStart接口启动,定时器将变成该状态,是定时器运行时的状态。 OS_SWTMR_STATUS_CREATED(定时器创建后未启动,或已停止) 定时器创建后,不处于计数状态时,定时器将变成该状态。 定时器怎么管理? LITE_OS_SEC_BSS SWTMR_CTRL_S *g_swtmrCBArray = NULL; /* First address in Timer memory space *///定时器池 LITE_OS_SEC_BSS UINT8 *g_swtmrHandlerPool = NULL; /* Pool of Swtmr Handler *///用于注册软时钟的回调函数 LITE_OS_SEC_BSS LOS_DL_LIST g_swtmrFreeList; /* Free list of Software Timer *///空闲定时器链表 typedef struct {//处理软件定时器超时的回调函数的结构体 SWTMR_PROC_FUNC handler; /**< Callback function that handles software timer timeout */ //处理软件定时器超时的回调函数 UINTPTR arg; /**< Parameter passed in when the callback function that handles software timer timeout is called */ //调用处理软件计时器超时的回调函数时传入的参数 } SwtmrHandlerItem; 解读 三个全局变量可知,定时器是通过池来管理,在初始化阶段赋值. g_swtmrCBArray 定时器池,初始化中一次性创建1024个定时器控制块供使用 g_swtmrHandlerPool 回调函数池,回调函数也是统一管理的,申请了静态内存保存. 池中放的是 SwtmrHandlerItem 回调函数描述符. g_swtmrFreeList 空闲可供分配的定时器链表,鸿蒙的进程池,任务池,事件池都是这么处理的,没有印象的自行去 鸿蒙内核源码分析(总目录)< OSCHINA | CSDN > 翻看. g_swtmrFreeList上挂的是一个个的 SWTMR_CTRL_S 要搞明白 SWTMR_CTRL_S 和 SwtmrHandlerItem的关系,前者是一个定时器,后者是定时器时间到了去哪里干活. 初始化 -> OsSwtmrInit #define LOSCFG_BASE_CORE_SWTMR_LIMIT 1024 // 最大支持的软件定时器数 LITE_OS_SEC_TEXT_INIT UINT32 OsSwtmrInit(VOID) { UINT32 size; UINT16 index; UINT32 ret; SWTMR_CTRL_S *swtmr = NULL; UINT32 swtmrHandlePoolSize; UINT32 cpuid = ArchCurrCpuid(); if (cpuid == 0) {//确保以下代码块由一个CPU执行,g_swtmrCBArray和g_swtmrHandlerPool 是所有CPU共用的 size = sizeof(SWTMR_CTRL_S) * LOSCFG_BASE_CORE_SWTMR_LIMIT;//申请软时钟内存大小 swtmr = (SWTMR_CTRL_S *)LOS_MemAlloc(m_aucSysMem0, size); /* system resident resource */ //常驻内存 if (swtmr == NULL) { return LOS_ERRNO_SWTMR_NO_MEMORY; } (VOID)memset_s(swtmr, size, 0, size);//清0 g_swtmrCBArray = swtmr;//软时钟 LOS_ListInit(&g_swtmrFreeList);//初始化空闲链表 for (index = 0; index < LOSCFG_BASE_CORE_SWTMR_LIMIT; index++, swtmr++) { swtmr->usTimerID = index;//按顺序赋值 LOS_ListTailInsert(&g_swtmrFreeList, &swtmr->stSortList.sortLinkNode);//通过sortLinkNode将节点挂到空闲链表 } //想要用静态内存池管理,就必须要使用LOS_MEMBOX_SIZE来计算申请的内存大小,因为需要点前缀内存承载头部信息. swtmrHandlePoolSize = LOS_MEMBOX_SIZE(sizeof(SwtmrHandlerItem), OS_SWTMR_HANDLE_QUEUE_SIZE);//计算所有注册函数内存大小 //规划一片内存区域作为软时钟处理函数的静态内存池。 g_swtmrHandlerPool = (UINT8 *)LOS_MemAlloc(m_aucSysMem1, swtmrHandlePoolSize); /* system resident resource *///常驻内存 if (g_swtmrHandlerPool == NULL) { return LOS_ERRNO_SWTMR_NO_MEMORY; } ret = LOS_MemboxInit(g_swtmrHandlerPool, swtmrHandlePoolSize, sizeof(SwtmrHandlerItem));//初始化软时钟注册池 if (ret != LOS_OK) { return LOS_ERRNO_SWTMR_HANDLER_POOL_NO_MEM; } } //每个CPU都会创建一个属于自己的 OS_SWTMR_HANDLE_QUEUE_SIZE 的队列 ret = LOS_QueueCreate(NULL, OS_SWTMR_HANDLE_QUEUE_SIZE, &g_percpu[cpuid].swtmrHandlerQueue, 0, sizeof(CHAR *));//为当前CPU core 创建软时钟队列 maxMsgSize:sizeof(CHAR *) if (ret != LOS_OK) { return LOS_ERRNO_SWTMR_QUEUE_CREATE_FAILED; } ret = OsSwtmrTaskCreate();//每个CPU独自创建属于自己的软时钟任务,统一处理队列 if (ret != LOS_OK) { return LOS_ERRNO_SWTMR_TASK_CREATE_FAILED; } ret = OsSortLinkInit(&g_percpu[cpuid].swtmrSortLink);//每个CPU独自对自己软时钟链表排序初始化,为啥要排序因为每个定时器的时间不一样,鸿蒙把用时短的排在前面 if (ret != LOS_OK) { return LOS_ERRNO_SWTMR_SORTLINK_CREATE_FAILED; } return LOS_OK; } 代码解读: 每个CPU核都是独立处理定时器任务的,所以需要独自管理.OsSwtmrInit是负责初始化各CPU核定时模块功能的,注意在多CPU核时,OsSwtmrInit会被多次调用. cpuid == 0代表主CPU核, 它最早执行这个函数,所以g_swtmrCBArray和g_swtmrHandlerPool是共用的,系统默认最多支持 1024 个定时器和回调函数. 每个CPU核都创建了自己独立的 LOS_QueueCreate队列和任务OsSwtmrTaskCreate,并初始化了swtmrSortLink链表,关于链表排序可前往系列篇总目录 排序链表篇查看. 定时任务 -> 最高优先级 LITE_OS_SEC_TEXT_INIT UINT32 OsSwtmrTaskCreate(VOID) { UINT32 ret, swtmrTaskID; TSK_INIT_PARAM_S swtmrTask; UINT32 cpuid = ArchCurrCpuid();//获取当前CPU id (VOID)memset_s(&swtmrTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));//清0 swtmrTask.pfnTaskEntry = (TSK_ENTRY_FUNC)OsSwtmrTask;//入口函数 swtmrTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//16K默认内核任务栈 swtmrTask.pcName = "Swt_Task";//任务名称 swtmrTask.usTaskPrio = 0;//哇塞! 逮到一个最高优先级的任务 @note_thinking 这里应该用 OS_TASK_PRIORITY_HIGHEST 表示 swtmrTask.uwResved = LOS_TASK_STATUS_DETACHED;//分离模式 #if (LOSCFG_KERNEL_SMP == YES) swtmrTask.usCpuAffiMask = CPUID_TO_AFFI_MASK(cpuid);//交给当前CPU执行这个任务 #endif ret = LOS_TaskCreate(&swtmrTaskID, &swtmrTask);//创建任务并申请调度 if (ret == LOS_OK) { g_percpu[cpuid].swtmrTaskID = swtmrTaskID;//全局变量记录 软时钟任务ID OS_TCB_FROM_TID(swtmrTaskID)->taskStatus |= OS_TASK_FLAG_SYSTEM_TASK;//告知这是一个系统任务 } return ret; } 代码解读: 内核为每个CPU处理单独创建任务来处理定时器, 任务即线程, 外界可理解为内核开设了一个线程跑定时器. 注意看任务的优先级 swtmrTask.usTaskPrio = 0; 0是最高优先级! 这并不多见! 内核会在第一时间响应软时钟任务. 系列篇CPU篇中讲过每个CPU都有自己的任务链表和定时器任务,g_percpu[cpuid].swtmrTaskID = swtmrTaskID; 表示创建的任务和CPU具体核进行了捆绑.从此swtmrTaskID负责这个CPU的定时器处理. 定时任务是一个系统任务,除此之外还有哪些是系统任务? 任务入口函数OsSwtmrTask ,是任务的执行体,类似于[Java 线程中的run()函数] usCpuAffiMask代表这个任务只能由这个CPU核来跑 队列消费者 -> OsSwtmrTask //软时钟的入口函数,拥有任务的最高优先级 0 级! LITE_OS_SEC_TEXT VOID OsSwtmrTask(VOID) { SwtmrHandlerItemPtr swtmrHandlePtr = NULL; SwtmrHandlerItem swtmrHandle; UINT32 ret, swtmrHandlerQueue; swtmrHandlerQueue = OsPercpuGet()->swtmrHandlerQueue;//获取定时器超时队列 for (;;) {//死循环获取队列item,一直读干净为止 ret = LOS_QueueRead(swtmrHandlerQueue, &swtmrHandlePtr, sizeof(CHAR *), LOS_WAIT_FOREVER);//一个一个读队列 if ((ret == LOS_OK) && (swtmrHandlePtr != NULL)) { swtmrHandle.handler = swtmrHandlePtr->handler;//超时中断处理函数,也称回调函数 swtmrHandle.arg = swtmrHandlePtr->arg;//回调函数的参数 (VOID)LOS_MemboxFree(g_swtmrHandlerPool, swtmrHandlePtr);//静态释放内存,注意在鸿蒙内核只有软时钟注册用到了静态内存 if (swtmrHandle.handler != NULL) { swtmrHandle.handler(swtmrHandle.arg);//回调函数处理函数 } } } } 代码解读: OsSwtmrTask是任务的执行体,只做一件事,消费定时器回调函数队列. 任务在跑一个死循环,不断在读队列.关于队列的具体操作不在此处细说,系列篇中已有专门的文章讲解,可前往查看. 每个CPU核都有属于自己的定时器回调函数队列,里面存放的是时间到了回调函数. 但队列的数据怎么来呢? OsSwtmrTask只是在不断的消费队列,那生产者在哪里呢? 就是 OsSwtmrScan 队列生产者 -> OsSwtmrScan LITE_OS_SEC_TEXT VOID OsSwtmrScan(VOID)//扫描定时器,如果碰到超时的,就放入超时队列 { SortLinkList *sortList = NULL; SWTMR_CTRL_S *swtmr = NULL; SwtmrHandlerItemPtr swtmrHandler = NULL; LOS_DL_LIST *listObject = NULL; SortLinkAttribute* swtmrSortLink = &OsPercpuGet()->swtmrSortLink;//拿到当前CPU的定时器链表 swtmrSortLink->cursor = (swtmrSortLink->cursor + 1) & OS_TSK_SORTLINK_MASK; listObject = swtmrSortLink->sortLink + swtmrSortLink->cursor; //由于swtmr是在特定的sortlink中,所以需要很小心的处理它,但其他CPU Core仍然有机会处理它,比如停止计时器 /* * it needs to be carefully coped with, since the swtmr is in specific sortlink * while other cores still has the chance to process it, like stop the timer. */ LOS_SpinLock(&g_swtmrSpin); if (LOS_ListEmpty(listObject)) { LOS_SpinUnlock(&g_swtmrSpin); return; } sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode); ROLLNUM_DEC(sortList->idxRollNum); while (ROLLNUM(sortList->idxRollNum) == 0) { sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode); LOS_ListDelete(&sortList->sortLinkNode); swtmr = LOS_DL_LIST_ENTRY(sortList, SWTMR_CTRL_S, stSortList); swtmrHandler = (SwtmrHandlerItemPtr)LOS_MemboxAlloc(g_swtmrHandlerPool);//取出一个可用的软时钟处理项 if (swtmrHandler != NULL) { swtmrHandler->handler = swtmr->pfnHandler; swtmrHandler->arg = swtmr->uwArg; if (LOS_QueueWrite(OsPercpuGet()->swtmrHandlerQueue, swtmrHandler, sizeof(CHAR *), LOS_NO_WAIT)) { (VOID)LOS_MemboxFree(g_swtmrHandlerPool, swtmrHandler); } } if (swtmr->ucMode == LOS_SWTMR_MODE_ONCE) { OsSwtmrDelete(swtmr); if (swtmr->usTimerID < (OS_SWTMR_MAX_TIMERID - LOSCFG_BASE_CORE_SWTMR_LIMIT)) { swtmr->usTimerID += LOSCFG_BASE_CORE_SWTMR_LIMIT; } else { swtmr->usTimerID %= LOSCFG_BASE_CORE_SWTMR_LIMIT; } } else if (swtmr->ucMode == LOS_SWTMR_MODE_NO_SELFDELETE) { swtmr->ucState = OS_SWTMR_STATUS_CREATED; } else { swtmr->ucOverrun++; OsSwtmrStart(swtmr); } if (LOS_ListEmpty(listObject)) { break; } sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode); } LOS_SpinUnlock(&g_swtmrSpin); } 代码解读: OsSwtmrScan 函数是在系统时钟处理函数 OsTickHandler 中调用的,它就干一件事,不停的比较定时器是否超时 一旦超时就把定时器的回调函数扔到队列中,让 OsSwtmrTask去消费. 总结 定时器池 g_swtmrCBArray 存储内核所有的定时器,默认1024个,各CPU共享这个池 定时器响应函数池g_swtmrHandlerPool 存储内核所有的定时器响应函数,默认1024个,各CPU也共享这个池 每个CPU核都有独立的任务(线程)来处理定时器, 这个任务叫定时任务 每个CPU核都有独立的响应函数队列swtmrHandlerQueue,队列中存放该核时间到了的响应函数SwtmrHandlerItem 定时任务的优先级最高,循环读取队列swtmrHandlerQueue, swtmrHandlerQueue中存放是定时器时间到了的响应函数.并一一回调这些响应函数. OsSwtmrScan负责扫描定时器的时间是否到了,到了就往队列swtmrHandlerQueue中扔. 定时器有多种模式,包括单次,循环.所以循环类定时器的响应函数会多次出现在swtmrHandlerQueue中. 喜欢就请收藏吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >

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

鸿蒙内核源码分析(构建工具篇) | 顺瓜摸藤调试鸿蒙构建过程 | 百篇博客分析HarmonyOS源码 | v59.01

OpenHarmony | 鸿蒙研究站 | WeHarmony < 国内 | 国外 > 百篇博客系列篇.本篇为: 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 构建的必要性 前端开发有构建工具:Grunt、Gulp、Webpack 后台开发有构建工具: Maven、Ant、Gradle 构建工具重要性不言而喻,它描述了整个工程的如何编译、连接,打包等规则,其中包括:工程中的哪些源文件需要编译以及如何编译、需要创建那些库文件以及如何创建这些库文件、如何最后输出我们想要的文件。 鸿蒙轻内核(L1/liteos)的编译构建工具是hb,hb是ohos-build的简称, 而ohos又是openharmony os的简称. hb | ohos-build hb通过以下命令安装,是用 python写的一个构建工具. python3 -m pip install --user ohos-build 其源码在 ./build/lite 目录下,含义如下: build/lite ├── components # 组件描述文件 ├── figures # readme中的图片 ├── hb # hb pip安装包源码 │ ├── build # hb build 命令实现 │ ├── clean # hb clean 命令实现 │ ├── common # 通用类, 提供 Device,Config,Product,utils 类 │ ├── cts # hb cts 命令实现 │ ├── deps # hb deps 命令实现 │ ├── env # hb env 命令实现 │ ├── set # hb set 命令实现 ├── make_rootfs # 文件系统镜像制作脚本 ├── config # 编译配置项 │ ├── component # 组件相关的模板定义 │ ├── kernel # 内核相关的编译配置 │ └── subsystem # 子系统编译配置 ├── platform # ld脚本 ├── testfwk # 测试编译框架 └── toolchain # 编译工具链配置,包括:编译器路径、编译选项、链接选项等 构建组成 鸿蒙构建系统由 python, gn, ninja, makefile几个部分组成,每个部分都有自己的使命,干自己最擅长的活. python : 胶水语言,最擅长的是对参数,环境变量,文件操作,它任务是做好编译前的准备工作和为gn收集命令参数.不用python直接用gn行不行? 也行,但很麻烦.比如:直接使用下面的命令行也可以生成 .ninja文件,最后的效果是一样的.但相比只使用 hb build 哪个更香, hb build也会生成下面这一坨坨, 但怎么来是python的强项. /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' 图为绕过hb python部分直接执行gn gen 的结果: gn : 类似构建界的高级语言,gn和ninja的关系有点像C和汇编语言的关系,与它对标的是cmake,它的作用是生成.ninja文件,不用gn直接用ninja行不行? 也行,但更麻烦.就跟全用汇编写鸿蒙系统一样,理论上可行,可谁会这么去干呢. ninja:类似构建界的汇编语言,与它对标的是make,由它完成对编译器clang,链接器ld的使用. makefile:鸿蒙有些模块用的还是make编译, 听说后面会统一使用ninja,是不是以后就看不到make文件了,目前是还有大量的make存在. 如何调试 hb 推荐使用vscode来调试,在调试面板点击 create a launch.json file创建调试文件,复制以下内容就可以调试hb了. { "version": "0.2.0", "configurations": [ {// hb set "name": "hb set", "type": "python", "request": "launch", "program": "./build/lite/hb/__main__.py", "console": "integratedTerminal", "args": ["set"], "stopOnEntry": true }, {//hb build "name": "hb build debug", "type": "python", "request": "launch", "program": "./build/lite/hb/__main__.py", "console": "integratedTerminal", "args": ["build"], "stopOnEntry": true }, {//hb clean "name": "hb clean", "type": "python", "request": "launch", "program": "./build/lite/hb/__main__.py", "console": "integratedTerminal", "args": ["clean"], "stopOnEntry": true }, ] } 构建流程 编译构建流程图所示,主要分设置和编译两步: 本篇调试图中的 hb set | hb build 两个命令 hb set | 选择项目 源码见于: ./build/lite/hb/set/set.py hb set执行的大致流程是这样的: 你可以在任何目录下执行hb set, 它尝试读取当前目录下的 ohos_config.json配置文件,如果没有会让你输入代码的路径 [OHOS INFO] Input code path: 也就是源码根目录, 生成ohos_config.json配置文件,配置内容项是固定的,由Config类管理. 可以在以下位置打上断点调试 set命令,跟踪整个过程. def exec_command(args): if args.root_path is not None: return set_root_path(root_path=args.root_path) if args.product: return set_product() return set_root_path() == 0 and set_product() == 0 图为断点调试现场 最后生成的配置文件如下: { "root_path": "/home/openharmony/code-v1.1.1-LTS", "board": "hispark_aries", "kernel": "liteos_a", "product": "ipcamera_hispark_aries", "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", "patch_cache": null } 有了这些路径就为后续 hb build 铺好了路. hb build | 编译项目 源码见于: ./build/lite/hb/build/*.py 建议大家去调试下源码,非常有意思,能看清楚所有的细节.本篇将编译工具中重要代码都加上了注解. 也可以前往 weharmony | 注解鸿蒙编译工具 查看对其的代码注释工程. 总体步骤是分两步: 调用 gn_build 使用 gn gen生成 *.ninja 文件 调用 ninja_build 使用 ninja -w dupbuild=warn -C 生成 *.o *.so *.bin 等最后的文件 gn_build 关于gn的资料可以前往 GN参考手册查看. 具体gn是如何生成.ninja文件的,后续有篇幅详细介绍其语法及在鸿蒙中的使用. #执行gn编译 def gn_build(self, cmd_args): # Clean out path remove_path(self.config.out_path) #先删除out目录 makedirs(self.config.out_path) #创建out目录 # Gn cmd init and execute ,生成 build.ninja, args.gn gn_path = self.config.gn_path gn_args = cmd_args.get('gn', []) gn_cmd = [gn_path,#gn的安装路径 ~/gn 'gen', self.config.out_path, #/home/openharmony/out/hispark_aries/ipcamera_hispark_aries '--root={}'.format(self.config.root_path), #项目的根例如:/home/openharmony '--dotfile={}/.gn'.format(self.config.build_path),#/home/openharmony/build/lite/.gn -> root = "//build/lite" f'--script-executable={sys.executable}',#python3 '--args={}'.format(" ".join(self._args_list))] + gn_args # ohos_build_type="debug" # ohos_build_compiler_specified="clang" # ohos_build_compiler_dir="/root/llvm" # product_path="/home/openharmony/vendor/hisilicon/hispark_aries" # device_path="/home/openharmony/device/hisilicon/hispark_aries/sdk_liteos" # ohos_kernel_type="liteos_a" # enable_ohos_appexecfwk_feature_ability = false # ohos_full_compile=true' # 这些参数也将在exec_command后保存在 args.gn文件中 exec_command(gn_cmd, log_path=self.config.log_path)#执行 gn gen .. 命令,在./out/hispark_aries/ipcamera_hispark_aries目录下生成如下文件 # obj 子编译项生成的 ninja文件目录,例如:obj/base/global/resmgr_lite/frameworks/resmgr_lite/global_resmgr.ninja # args.gn 各种参数, ohos_build_type="debug" ... # build.ninja 子编译项 例如: build aa: phony dev_tools/bin/aa # build.ninja.d 由那些模块产生的子编译项 例如:../../../base/global/resmgr_lite/frameworks/resmgr_lite/BUILD.gn # toolchain.ninja 工具链 放置了各种编译/链接规则 rule cxx rule alink ninja_build 关于ninja 的资料可以前往 ninja 参考手册查看. 具体ninja是如何运行的,后续有篇幅详细介绍其语法及在鸿蒙中的使用. # ninja 编译过程 def ninja_build(self, cmd_args): ninja_path = self.config.ninja_path ninja_args = cmd_args.get('ninja', []) ninja_cmd = [ninja_path, '-w', 'dupbuild=warn', '-C', self.config.out_path] + ninja_args # ninja -w dupbuild=warn -C /home/openharmony/out/hispark_aries/ipcamera_hispark_aries # 将读取gn生成的文件,完成编译的第二步,最终编译成 .o .bin 文件 exec_command(ninja_cmd, log_path=self.config.log_path, log_filter=True) #生成以下部分文件 #NOTICE_FILE OHOS_Image.bin bin build.ninja config etc libs obj server.map test_info userfs #OHOS_Image OHOS_Image.map bm_tool.map build.ninja.d data foundation.map liteos.bin rootfs.tar suites toggleButtonTest.map userfs_jffs2.img #OHOS_Image.asm args.gn build.log bundle_daemon_tool.map dev_tools gen media_server.map rootfs_jffs2.img test toolchain.ninja vendor exec_command | utils.py gn_build 和 ninja_build 最后都会调用 exec_command来执行命令,exec_command是个通用方法,见于 build/lite/hb/common/utils.py,调试时建议在这里打断点,顺瓜摸藤,跟踪相关函数的实现细节. def exec_command(cmd, log_path='out/build.log', **kwargs): useful_info_pattern = re.compile(r'\[\d+/\d+\].+') is_log_filter = kwargs.pop('log_filter', False) with open(log_path, 'at', encoding='utf-8') as log_file: process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', **kwargs) for line in iter(process.stdout.readline, ''): if is_log_filter: info = re.findall(useful_info_pattern, line) if len(info): hb_info(info[0]) else: hb_info(line) log_file.write(line) process.wait() ret_code = process.returncode if ret_code != 0: with open(log_path, 'at', encoding='utf-8') as log_file: for line in iter(process.stderr.readline, ''): if 'ninja: warning' in line: log_file.write(line) continue hb_error(line) log_file.write(line) if is_log_filter: get_failed_log(log_path) hb_error('you can check build log in {}'.format(log_path)) if isinstance(cmd, list): cmd = ' '.join(cmd) raise Exception("{} failed, return code is {}".format(cmd, ret_code)) 图为断点调试现场 百篇博客.往期回顾 在加注过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 :P 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,.xx代表修改的次数,精雕细琢,言简意赅,力求打造精品内容。 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 原创不易,欢迎转载,但麻烦请注明出处.

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

鸿蒙内核源码分析(ELF反汇编篇) | 程序的入口函数并不是main | 百篇博客分析HarmonyOS源码 | v51.03

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< osc | 51cto | csdn | harmony > 阅读之前的说明 先说明,本篇很长,也很枯燥,若不是绝对的技术偏执狂是看不下去的.将通过一段简单代码去跟踪编译成ELF格式后的内容.看看ELF究竟长了怎样的一副花花肠子,用readelf命令去窥视ELF的全貌,最后用objdump命令反汇编ELF.找到了大家熟悉main函数. 开始之前先说结论: ELF有两个重要概念 Segment(段) and 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)接口的一部分,也是Linux的主要可执行文件格式。 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, ELF头部,定义全局性信息,即readelf -h app看到的内容 Program Header Table, 描述段(Segment)信息的数组,每个元素对应一个段;通常包含在可执行文件中,可重定文件中可选(通常不包含) 即readelf -l app看到的前半部分内容 Segment and Section,段(Segment)由若干区(Section)组成;段在运行时被加载到进程地址空间中,包含在可执行文件中;区是段的组成单元,包含在可执行文件和可重定位文件中 即readelf -l app后半部分内容,并readelf -x ** app查看区具体内容. Section Header Table,描述区(Section)信息的数组,每个元素对应一个区;通常包含在可重定位文件中,可执行文件中为可选(通常包含) 即readelf -S app看到的内容 从图中可以看出 Segment:Section(M:N)是多对多的包含关系.Segment是由多个Section组成,Section也能属于多个段.系列篇统一将Segment注解为段, 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类型,可重定位文件/可执行文件/共享库文件,显然这里是一个可执行文件;e_machine表示执行的机器平台,这里是x86_64; UINT16 elfMachine; /* Architecture *///表示cpu架构 UINT32 elfVersion; /* Object file version *///表示文件版本号,这里的1表示初始版本号; UINT32 elfEntry; /* Entry point virtual address *///对应`Entry point address`,程序入口函数地址,通过进程虚拟地址空间地址(0x400440)表达; UINT32 elfPhoff; /* Program header table file offset *///对应`Start of program headers`,表示program header table在文件内的偏移位置,这里是从第64号字节(假设初始为0号字节)开始; UINT32 elfShoff; /* Section header table file offset *///对应`Start of section headers`,表示section header table在文件内的偏移位置,这里是从第4472号字节开始,靠近文件尾部; UINT32 elfFlags; /* Processor-specific flags *///表示与CPU处理器架构相关的信息,这里为零; UINT16 elfHeadSize; /* ELF header size in bytes *///对应`Size of this header`,表示本ELF header自身的长度,这里为64个字节,回看前面的e_phoff为64,说明ELF header后紧跟着program header table; UINT16 elfPhEntSize; /* Program header table entry size *///对应`Size of program headers`,表示program header table中每个元素的大小,这里为56个字节; UINT16 elfPhNum; /* Program header table entry count *///对应`Number of program headers`,表示program header table中元素个数,这里为9个; UINT16 elfShEntSize; /* Section header table entry size *///对应`Size of section headers`,表示section header table中每个元素的大小,这里为64个字节; UINT16 elfShNum; /* Section header table entry count *///对应`Number of section headers`,表示section header table中元素的个数,这里为30个; 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个段,仔细看下这些段的开始地址和大小,发现有些段是重叠的.那是因为一个区可以被多个段所拥有.例如:0x2db8 对应的 .init_array区就被第四LOAD 和 GNU_RELRO两段所共有. Number of program headers: 13 //段数量 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; 通过readelf -S命令看看示例程序中 section header table的内容,如下图所示。示例程序共生成31个区.其实在头文件中也已经告诉我们了 Number of section headers: 31 //区数量 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读出下标7区的详细内容观察: 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中的e_shstrndx, 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函数的入口地址.我们看到了激动人心的调用1149位置的say_hello指令 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 | 51cto | osc > v50.xx (编译环境篇) | 编译鸿蒙看这篇或许真的够了 < csdn | 51cto | osc > v49.xx (信号消费篇) | 两次切换用户栈和内核栈 < csdn | 51cto | osc > v48.xx (信号生产篇) | 如何安装和发送异步信号 < csdn | 51cto | osc > v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | osc > v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | osc > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 < csdn | 51cto | osc > v44.xx (中断管理篇) | 硬中断的实现像观察者模式 < csdn | 51cto | osc > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | osc > v42.xx (中断切换篇) | 中断切换又在切换什么 < csdn | 51cto | osc > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | osc > v40.xx (汇编汇总篇) | 所有的汇编代码都在呢 < csdn | 51cto | osc > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | osc > v38.xx (寄存器篇) | 看完再也不对寄存器怕怕 < csdn | 51cto | osc > v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | osc > v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆 < csdn | 51cto | osc > v35.xx (时间管理篇) | tick是操作系统的基本时间单位 < csdn | 51cto | osc > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航 < csdn | 51cto | osc > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 < csdn | 51cto | osc > v32.xx (cpu篇) | 整个内核就是一个死循环 < csdn | 51cto | osc > v31.xx (定时器篇) | 内核最高优先级任务是谁 < csdn | 51cto | osc > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | osc > v29.xx (信号量篇) | 信号量解决任务的什么问题 < csdn | 51cto | osc > v28.xx (进程通讯篇) | 九种进程间通讯方式速揽 < csdn | 51cto | osc > v27.xx (互斥锁篇) | 比自旋锁丰满的互斥锁 < csdn | 51cto | osc > v26.xx (自旋锁篇) | 自旋锁当立贞节牌坊 < csdn | 51cto | osc > v25.xx (并发并行篇) | 两个字永远记住二者区别 < csdn | 51cto | osc > v24.xx (进程概念篇) | 进程在管理哪些资源 < csdn | 51cto | osc > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数 < csdn | 51cto | osc > v22.xx (汇编基础篇) | cpu在哪里打卡上班 < csdn | 51cto | osc > v21.xx (线程概念篇) | 是谁在不断的折腾cpu < csdn | 51cto | osc > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | osc > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个 < csdn | 51cto | osc > v18.xx (源码结构篇) | 梳理内核源文件的作用和含义 < csdn | 51cto | osc > v17.xx (物理内存篇) | 怎么管理物理内存 < csdn | 51cto | osc > v16.xx (内存规则篇) | 内存管理到底在管什么 < csdn | 51cto | osc > v15.xx (内存映射篇) | 虚拟内存虚在哪里 < csdn | 51cto | osc > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础 < csdn | 51cto | osc > v13.xx (源码注释篇) | 鸿蒙必须成功,也必然成功 < csdn | 51cto | osc > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的 < csdn | 51cto | osc > v11.xx (内存分配篇) | 内存有哪些分配方式 < csdn | 51cto | osc > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处 < csdn | 51cto | osc > v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | 51cto | osc > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | osc > v07.xx (调度机制篇) | 任务是如何被调度执行的 < csdn | 51cto | osc > v06.xx (调度队列篇) | 内核有多少个调度队列 < csdn | 51cto | osc > v05.xx (任务管理篇) | 任务池是如何管理的 < csdn | 51cto | osc > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | osc > v03.xx (时钟任务篇) | 谁是触发调度的最大动力 < csdn | 51cto | osc > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | osc > v01.xx (双向链表篇) | 谁是内核最重要结构体 < csdn | 51cto | osc > 进入 >> osc | csdn | 51cto | 掘金 | 公众号 | 头条号 | gitee | github 鸿蒙内核源码分析系列篇 . 原创不易,欢迎转载,但请注明出处. 热爱是所有的理由和答案 - turing 不经过深度思考的人生不值得一过 - turing

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

鸿蒙内核源码分析(ELF反汇编篇) | 程序的入口函数并不是main | 百篇博客分析HarmonyOS源码 | v51.02

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< osc | 51cto | csdn | harmony > 阅读之前的说明 先说明,本篇很长,也很枯燥,若不是绝对的技术偏执狂是看不下去的.将通过一段简单代码去跟踪编译成ELF格式后的内容.看看ELF究竟长了怎样的一副花花肠子并用了 readelf命令去窥视ELF的全貌,最后用objdump命令反汇编ELF.找到了大家熟悉画面. 示例代码 在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)接口的一部分,也是Linux的主要可执行文件格式。 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, ELF头部,定义全局性信息,即readelf -h app看到的内容 Program Header Table, 描述段(Segment)信息的数组,每个元素对应一个段;通常包含在可执行文件中,可重定文件中可选(通常不包含) 即readelf -l app看到的前半部分内容 Segment and Section,段(Segment)由若干区(Section)组成;段在运行时被加载到进程地址空间中,包含在可执行文件中;区是段的组成单元,包含在可执行文件和可重定位文件中 即readelf -l app后半部分内容,并readelf -x ** app查看区具体内容. Section Header Table,描述区(Section)信息的数组,每个元素对应一个区;通常包含在可重定位文件中,可执行文件中为可选(通常包含) 即readelf -S app看到的内容 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类型,可重定位文件/可执行文件/共享库文件,显然这里是一个可执行文件;e_machine表示执行的机器平台,这里是x86_64; UINT16 elfMachine; /* Architecture *///表示cpu架构 UINT32 elfVersion; /* Object file version *///表示文件版本号,这里的1表示初始版本号; UINT32 elfEntry; /* Entry point virtual address *///对应”Entry point address”,程序入口函数地址,通过进程虚拟地址空间地址(0x400440)表达; UINT32 elfPhoff; /* Program header table file offset *///对应“Start of program headers”,表示program header table在文件内的偏移位置,这里是从第64号字节(假设初始为0号字节)开始; UINT32 elfShoff; /* Section header table file offset *///对应”Start of section headers”,表示section header table在文件内的偏移位置,这里是从第4472号字节开始,靠近文件尾部; UINT32 elfFlags; /* Processor-specific flags *///表示与CPU处理器架构相关的信息,这里为零; UINT16 elfHeadSize; /* ELF header size in bytes *///对应”Size of this header”,表示本ELF header自身的长度,这里为64个字节,回看前面的e_phoff为64,说明ELF header后紧跟着program header table; UINT16 elfPhEntSize; /* Program header table entry size *///对应“Size of program headers”,表示program header table中每个元素的大小,这里为56个字节; UINT16 elfPhNum; /* Program header table entry count *///对应”Number of program headers”,表示program header table中元素个数,这里为9个; UINT16 elfShEntSize; /* Section header table entry size *///e_shentsize 对应”Size of section headers”,表示section header table中每个元素的大小,这里为64个字节; UINT16 elfShNum; /* Section header table entry count *///e_shnum 对应”Number of section headers”,表示section header table中元素的个数,这里为30个; UINT16 elfShStrIndex; /* Section header string table index *///e_shstrndx 对应”Section header string table index”,表示描述各section字符名称的string table在section header table中的下标,详见后文对string 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 //段数量 PHDR,此类型header元素描述了program header table自身的信息。从这里的内容看出,示例程序的program header table在文件中的偏移(Offset)为0x40,即64号字节处;该段映射到进程空间的虚拟地址(VirtAddr)为0x40;PhysAddr暂时不用,其保持和VirtAddr一致;该段占用的文件大小FileSiz为0x2d8;运行时占用进程空间内存大小MemSiz也为0x2d8;Flags标记表示该段的读写权限,这里”R E”表示可读可执行,说明本段属于代码段;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;可见该段和上一个数据段是有重叠的。 再看命令返回内容的后半部分-段区映射关系 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 段和区是一对多的关系,一个段可包含多个区 INTERP段只包含了.interp区 代码段包含.interp、.plt、.text等区 数据段包含.dynamic、.data、.bss等区 DYNAMIC`段包含”.dynamic”区。从这里可以看出,有些区被包含在多个段中。 区表 段(Segment)信息对应鸿蒙源码结构体为 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; 通过readelf -S命令看看示例程序中section header table的内容,如下图所示。示例程序共生成31个区.其实在头文件中也已经告诉我们了 Number of section headers: 31 //区数量 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读出下标7区的详细内容观察: 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中的e_shstrndx, 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函数的入口地址.我们看到了激动人心的调用1149位置的say_hello指令 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 | 51cto | osc > v50.xx (编译环境篇) | 编译鸿蒙看这篇或许真的够了 < csdn | 51cto | osc > v49.xx (信号消费篇) | 两次切换用户栈和内核栈 < csdn | 51cto | osc > v48.xx (信号生产篇) | 如何安装和发送异步信号 < csdn | 51cto | osc > v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | osc > v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | osc > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 < csdn | 51cto | osc > v44.xx (中断管理篇) | 硬中断的实现像观察者模式 < csdn | 51cto | osc > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | osc > v42.xx (中断切换篇) | 中断切换又在切换什么 < csdn | 51cto | osc > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | osc > v40.xx (汇编汇总篇) | 所有的汇编代码都在呢 < csdn | 51cto | osc > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | osc > v38.xx (寄存器篇) | 看完再也不对寄存器怕怕 < csdn | 51cto | osc > v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | osc > v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆 < csdn | 51cto | osc > v35.xx (时间管理篇) | tick是操作系统的基本时间单位 < csdn | 51cto | osc > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航 < csdn | 51cto | osc > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 < csdn | 51cto | osc > v32.xx (cpu篇) | 整个内核就是一个死循环 < csdn | 51cto | osc > v31.xx (定时器篇) | 内核最高优先级任务是谁 < csdn | 51cto | osc > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | osc > v29.xx (信号量篇) | 信号量解决任务的什么问题 < csdn | 51cto | osc > v28.xx (进程通讯篇) | 九种进程间通讯方式速揽 < csdn | 51cto | osc > v27.xx (互斥锁篇) | 比自旋锁丰满的互斥锁 < csdn | 51cto | osc > v26.xx (自旋锁篇) | 自旋锁当立贞节牌坊 < csdn | 51cto | osc > v25.xx (并发并行篇) | 两个字永远记住二者区别 < csdn | 51cto | osc > v24.xx (进程概念篇) | 进程在管理哪些资源 < csdn | 51cto | osc > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数 < csdn | 51cto | osc > v22.xx (汇编基础篇) | cpu在哪里打卡上班 < csdn | 51cto | osc > v21.xx (线程概念篇) | 是谁在不断的折腾cpu < csdn | 51cto | osc > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | osc > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个 < csdn | 51cto | osc > v18.xx (源码结构篇) | 梳理内核源文件的作用和含义 < csdn | 51cto | osc > v17.xx (物理内存篇) | 怎么管理物理内存 < csdn | 51cto | osc > v16.xx (内存规则篇) | 内存管理到底在管什么 < csdn | 51cto | osc > v15.xx (内存映射篇) | 虚拟内存虚在哪里 < csdn | 51cto | osc > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础 < csdn | 51cto | osc > v13.xx (源码注释篇) | 鸿蒙必须成功,也必然成功 < csdn | 51cto | osc > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的 < csdn | 51cto | osc > v11.xx (内存分配篇) | 内存有哪些分配方式 < csdn | 51cto | osc > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处 < csdn | 51cto | osc > v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | 51cto | osc > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | osc > v07.xx (调度机制篇) | 任务是如何被调度执行的 < csdn | 51cto | osc > v06.xx (调度队列篇) | 内核有多少个调度队列 < csdn | 51cto | osc > v05.xx (任务管理篇) | 任务池是如何管理的 < csdn | 51cto | osc > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | osc > v03.xx (时钟任务篇) | 谁是触发调度的最大动力 < csdn | 51cto | osc > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | osc > v01.xx (双向链表篇) | 谁是内核最重要结构体 < csdn | 51cto | osc > 进入 >> osc | csdn | 51cto | 掘金 | 公众号 | 头条号 | gitee | github 各大站点搜 "鸿蒙内核源码分析" .原创不易,欢迎转载,但请注明出处. 热爱是所有的理由和答案 - turing

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

鸿蒙内核源码分析(信号消费篇) | 谁让CPU连换四次栈运行 | 百篇博客分析HarmonyOS源码 | v49.04

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< osc | 51cto | csdn | harmony > 信号消费 本篇为信号消费篇,读之前建议先阅读信号生产篇,信号部分姊妹篇如下: v48.xx (信号生产篇) | 信号它老人家已经五十多岁了 < csdn | 51cto | harmony > v49.xx (信号消费篇) | 用户栈到内核栈的两次切换 < csdn | 51cto | harmony > 本篇有相当的难度,涉及用户栈和内核栈的两次切换,寄存器改值,将围绕下图来说明. 解读 从图中可以看出信号消费让CPU四次换栈运行. 为本篇理解方便,把图做简化标签说明: user:用户空间 kernel:内核空间 source(...):源函数 sighandle(...):信号处理函数, syscall(...):系统调用,参数为系统调用号,如sigreturn,N(表任意) user.source():表示在用户空间运行的源函数 系列篇已多次说过,用户态的任务有两个运行栈,一个是用户栈,一个是内核栈.栈空间分别来自用户空间和内核空间.两种空间是有严格的地址划分的,通过虚拟地址的大小就能判断出是用户空间还是内核空间.系统调用本质上是软中断,它使CPU执行指令的场地由用户栈变成内核栈.怎么变的并不复杂,就是改变(sp和cpsr寄存器的值).sp指向哪个栈就代表在哪个栈运行, 当cpu在用户栈运行时是不能访问内核空间的,但内核态任务可以访问整个空间,而且内核态任务没有用户栈. 理解了上面的说明,再来说下正常系统调用流程是这样的: user.source() -> kernel.syscall(N) - > user.source() ,想要回到user.source()继续运行,就必须保存用户栈现场各寄存器的值.这些值保存在内核栈中,恢复也是从内核栈恢复. 信号消费的过程的上图可简化表示为: user.source() -> kernel.syscall(N) ->user.sighandle() ->kernel.syscall(sigreturn) -> user.source() 在原本要回到user.source()的中间插入了信号处理函数的调用. 这正是本篇要通过代码来说清楚的核心问题. 顺着这个思路可以推到以下几点,实际也是这么做的: kernel.syscall(N) 中必须要再次保存user.source()的上下文sig_switch_context,为何已经保存了一次还要再保存一次? 因为第一次是保存在内核栈中,而内核栈这部分数据会因回到用户态user.sighandle()运行而被恢复现场出栈了.保存现场/恢复现场是成双出队的好基友,注意有些文章说会把整个内核栈清空,这是不对的. 第二次保存在任务结构体中,任务来源于任务池,是内核全局变量,常驻内存的.两次保存的都是user.source()运行时现场信息,再回顾下相关的结构体.关键是sig_switch_context typedef struct { // ... sig_cb sig;//信号控制块,用于异步通信 } LosTaskCB; typedef struct {//信号控制块(描述符) sigset_t sigFlag; //不屏蔽的信号集 sigset_t sigPendFlag; //信号阻塞标签集,记录那些信号来过,任务依然阻塞的集合.即:这些信号不能唤醒任务 sigset_t sigprocmask; /* Signals that are blocked */ //任务屏蔽了哪些信号 sq_queue_t sigactionq; //信号捕捉队列 LOS_DL_LIST waitList; //等待链表,上面挂的是等待信号到来的任务, 请查找 OsTaskWait(&sigcb->waitList, timeout, TRUE) 理解 sigset_t sigwaitmask; /* Waiting for pending signals */ //任务在等待哪些信号的到来 siginfo_t sigunbinfo; /* Signal info when task unblocked */ //任务解锁时的信号信息 sig_switch_context context; //信号切换上下文, 用于保存切换现场, 比如发生系统调用时的返回,涉及同一个任务的两个栈进行切换 } sig_cb; 还必须要改变原有PC/R0/R1寄存器的值.想要执行user.sighandle(),PC寄存器就必须指向它,而R0,R1就是它的参数. 信号处理完成后须回到内核态,怎么再次陷入内核态? 答案是:__NR_sigreturn,这也是个系统调用.回来后还原sig_switch_context,即还原user.source()被打断时SP/PC等寄存器的值,使其跳回到用户栈从user.source()的被打断处继续执行. 有了这三个推论,再理解下面的代码就是吹灰之力了,涉及三个关键函数 OsArmA32SyscallHandle,OsSaveSignalContext,OsRestorSignalContext本篇一一解读,彻底挖透.先看信号上下文结构体sig_switch_context. sig_switch_context //任务中断上下文 #define TASK_IRQ_CONTEXT \ unsigned int R0; \ unsigned int R1; \ unsigned int R2; \ unsigned int R3; \ unsigned int R12; \ unsigned int USP; \ unsigned int ULR; \ unsigned int CPSR; \ unsigned int PC; typedef struct {//信号切换上下文 TASK_IRQ_CONTEXT unsigned int R7; //存放系统调用的ID unsigned int count; //记录是否保存了信号上下文 } sig_switch_context; 保存user.source()现场的结构体,USP,ULR代表用户栈指针和返回地址. CPSR寄存器用于设置CPU的工作模式,CPU有7种工作模式,具体可前往翻看 v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆? < csdn | 51cto | harmony > 谈论的用户态(usr普通用户)和内核态(sys超级用户)对应的只是其中的两种.二者都共用相同的寄存器.还原它就是告诉CPU内核已切到普通用户模式运行. 其他寄存器没有保存的原因是系统调用不会用到它们,所以不需要保存. R7是在系统调用发生时用于记录系统调用号,在信号处理过程中,R0将获得信号编号,作为user.sighandle()的第一个参数. count记录是否保存了信号上下文 OsArmA32SyscallHandle 系统调用总入口 /* The SYSCALL ID is in R7 on entry. Parameters follow in R0..R6 */ /****************************************************************** 由汇编调用,见于 los_hw_exc.s / BLX OsArmA32SyscallHandle SYSCALL是产生系统调用时触发的信号,R7寄存器存放具体的系统调用ID,也叫系统调用号 regs:参数就是所有寄存器 注意:本函数在用户态和内核态下都可能被调用到 //MOV R0, SP @获取SP值,R0将作为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; } //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn 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://如何是两个参数的系统调用,这里传三个参数也没有问题,因被调用函数不会去取用R2值 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);//如果有信号要处理,将改写pc,r0,r1寄存器,改变返回正常用户态路径,而先去执行信号处理程序. /* Return the last value of curent_regs. This supports context switches on return from the exception. * That capability is only used with the SYS_context_switch system call. */ return regs;//返回寄存器的值 } 解读 这是系统调用的总入口,所有的系统调用都要跑这里要统一处理.通过系统号(保存在R7),找到注册函数并回调.完成系统调用过程. 关于系统调用可查看 v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | harmony > 本篇不详细说系统调用过程,只说跟信号相关的部分. OsArmA32SyscallHandle总体理解起来是被信号的保存和还原两个函数给包夹了.注意要在运行过程中去理解调用两个函数的过程,对于同一个任务来说,一定是先执行OsSaveSignalContext,第二次进入OsArmA32SyscallHandle后再执行OsRestorSignalContext. 看OsSaveSignalContext,由它负责保存user.source() 的上下文,其中改变了sp,r0/r1寄存器值,切到信号处理函数user.sighandle()运行. 在函数的开头,碰到系统调用号__NR_sigreturn,直接恢复信号上下文就退出了,因为这是要切回user.source()继续运行的操作. //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn if (cmd == __NR_sigreturn) { OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行. return regs; } OsSaveSignalContext 保存信号上下文 有了上面的铺垫,就不难理解这个函数的作用. /********************************************** 产生系统调用时,也就是软中断时,保存用户栈寄存器现场信息 改写PC寄存器的值 **********************************************/ void OsSaveSignalContext(unsigned int *sp) { UINTPTR sigHandler; UINT32 intSave; LosTaskCB *task = NULL; LosProcessCB *process = NULL; sig_cb *sigcb = NULL; unsigned long cpsr; OS_RETURN_IF_VOID(sp == NULL); cpsr = OS_SYSCALL_GET_CPSR(sp);//获取系统调用时的 CPSR值 OS_RETURN_IF_VOID(((cpsr & CPSR_MASK_MODE) != CPSR_USER_MODE));//必须工作在CPU的用户模式下,注意CPSR_USER_MODE(cpu层面)和OS_USER_MODE(系统层面)是两码事. SCHEDULER_LOCK(intSave);//如有不明白前往 https://my.oschina.net/weharmony 翻看工作模式/信号分发/信号处理篇 task = OsCurrTaskGet(); process = OsCurrProcessGet(); sigcb = &task->sig;//获取任务的信号控制块 //1.未保存任务上下文任务 //2.任何的信号标签集不为空或者进程有信号要处理 if ((sigcb->context.count == 0) && ((sigcb->sigFlag != 0) || (process->sigShare != 0))) { sigHandler = OsGetSigHandler();//获取信号处理函数 if (sigHandler == 0) {//信号没有注册 sigcb->sigFlag = 0; process->sigShare = 0; SCHEDULER_UNLOCK(intSave); PRINT_ERR("The signal processing function for the current process pid =%d is NULL!\n", task->processID); return; } /* One pthread do the share signal */ sigcb->sigFlag |= process->sigShare;//扩展任务的信号标签集 unsigned int signo = (unsigned int)FindFirstSetedBit(sigcb->sigFlag) + 1; OsProcessExitCodeSignalSet(process, signo);//设置进程退出信号 sigcb->context.CPSR = cpsr; //保存状态寄存器 sigcb->context.PC = sp[REG_PC]; //获取被打断现场寄存器的值 sigcb->context.USP = sp[REG_SP];//用户栈顶位置,以便能从内核栈切回用户栈 sigcb->context.ULR = sp[REG_LR];//用户栈返回地址 sigcb->context.R0 = sp[REG_R0]; //系统调用的返回值 sigcb->context.R1 = sp[REG_R1]; sigcb->context.R2 = sp[REG_R2]; sigcb->context.R3 = sp[REG_R3]; sigcb->context.R7 = sp[REG_R7];//为何参数不用传R7,是因为系统调用发生时 R7始终保存的是系统调用号. sigcb->context.R12 = sp[REG_R12];//详见 https://my.oschina.net/weharmony/blog/4967613 sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数. sp[REG_R0] = signo; //参数1,信号ID sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2 /* sig No bits 00000100 present sig No 3, but 1<< 3 = 00001000, so signo needs minus 1 */ sigcb->sigFlag ^= 1ULL << (signo - 1); sigcb->context.count++; //代表已保存 } SCHEDULER_UNLOCK(intSave); } 解读 先是判断执行条件,确实是有信号需要处理,有处理函数.自定义处理函数是由用户进程安装进来的,所有进程旗下的任务都共用,参数就是信号signo,注意可不是系统调用号,有区别的.信号编号长这样. #define SIGHUP 1 //终端挂起或者控制进程终止 #define SIGINT 2 //键盘中断(ctrl + c) #define SIGQUIT 3 //键盘的退出键被按下 #define SIGILL 4 //非法指令 #define SIGTRAP 5 //跟踪陷阱(trace trap),启动进程,跟踪代码的执行 #define SIGABRT 6 //由abort(3)发出的退出指令 #define SIGIOT SIGABRT //abort发出的信号 #define SIGBUS 7 //总线错误 #define SIGFPE 8 //浮点异常 #define SIGKILL 9 //常用的命令 kill 9 123 | 不能被忽略、处理和阻塞 系统调用号长这样,是不是看到一些很熟悉的函数. #define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 #define __NR_time 13 #define __NR_mknod 14 #define __NR_chmod 15 #define __NR_lchown 16 #define __NR_break 17 最后是最最最关键的代码,改变pc寄存器的值,此值一变,在_osExceptSwiHdl中恢复上下文后,cpu跳到用户空间的代码段 user.sighandle(R0,R1) 开始执行,即执行信号处理函数. sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数. sp[REG_R0] = signo; //参数1,信号ID sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2 OsRestorSignalContext 恢复信号上下文 /**************************************************** 恢复信号上下文,由系统调用之__NR_sigreturn产生,这是一个内部产生的系统调用. 为什么要恢复呢? 因为系统调用的执行由任务内核态完成,使用的栈也是内核栈,CPU相关寄存器记录的都是内核栈的内容, 而系统调用完成后,需返回任务的用户栈执行,这时需将CPU各寄存器回到用户态现场 所以函数的功能就变成了还原寄存器的值 ****************************************************/ void OsRestorSignalContext(unsigned int *sp) { LosTaskCB *task = NULL; /* Do not adjust this statement */ LosProcessCB *process = NULL; sig_cb *sigcb = NULL; UINT32 intSave; SCHEDULER_LOCK(intSave); task = OsCurrTaskGet(); sigcb = &task->sig;//获取当前任务信号控制块 if (sigcb->context.count != 1) {//必须之前保存过,才能被恢复 SCHEDULER_UNLOCK(intSave); PRINT_ERR("sig error count : %d\n", sigcb->context.count); return; } process = OsCurrProcessGet();//获取当前进程 sp[REG_PC] = sigcb->context.PC;//指令寄存器 OS_SYSCALL_SET_CPSR(sp, sigcb->context.CPSR);//重置程序状态寄存器 sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行 sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置 sp[REG_R0] = sigcb->context.R0; sp[REG_R1] = sigcb->context.R1; sp[REG_R2] = sigcb->context.R2; sp[REG_R3] = sigcb->context.R3; sp[REG_R7] = sigcb->context.R7; sp[REG_R12] = sigcb->context.R12; sigcb->context.count--; //信号上下文的数量回到减少 process->sigShare = 0; //回到用户态,信号共享清0 OsProcessExitCodeSignalClear(process);//清空进程退出码 SCHEDULER_UNLOCK(intSave); } 解读 在信号处理函数完成之后,内核会触发一个__NR_sigreturn的系统调用,又陷入内核态,回到了OsArmA32SyscallHandle. 恢复的过程很简单,把之前保存的信号上下文恢复到内核栈sp开始位置,数据在栈中的保存顺序可查看 用栈方式篇 ,最重要的看这几句. sp[REG_PC] = sigcb->context.PC;//指令寄存器 sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行 sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置 注意这里还不是真正的切换上下文,只是改变内核栈中现有的数据.这些数据将还原给寄存器.USP和ULR指向的是用户栈的位置.一旦PC,USP,ULR从栈中弹出赋给寄存器.才真正完成了内核栈到用户栈的切换.回到了user.source()继续运行. 真正的切换汇编代码如下,都已添加注释,在保存和恢复上下文中夹着OsArmA32SyscallHandle @ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理,注意此时已在内核栈运行 @保存任务上下文(TaskContext) 开始... 一定要对照TaskContext来理解 SUB SP, SP, #(4 * 16) @先申请16个栈空间单元用于处理本次软中断 STMIA SP, {R0-R12} @TaskContext.R[GEN_REGS_NUM] STMIA从左到右执行,先放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值 => TaskContext.regPSR ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 => TaskContext.PC STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 从右向左 保存 => TaskContext.LR和SP SUB SP, SP, #4 @ => TaskContext.resved PUSH_FPU_REGS R1 @保存中断模式(用户模式) @保存任务上下文(TaskContext) 结束 MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用,参数为R0,指向TaskContext的开始位置*/ CPSID I @执行后续指令前必须先关中断 @恢复任务上下文(TaskContext) 开始 POP_FPU_REGS R1 @弹出FPU值给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 切回用户模式运行 @恢复任务上下文(TaskContext) 结束 具体也可看这两篇: v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | 51cto | harmony > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | harmony > 鸿蒙源码百篇博客 往期回顾 在给 鸿蒙内核源码加中文注释 过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感.百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.:P 写文章比写代码累多了,越深入研究,越觉得没写好,所以文章和注解会反复修正, .xx代表修改的次数, 将持续完善源码注解和文档内容,精雕细琢,言简意赅, 尽全力打磨精品内容. v51.xx (ELF反汇编篇) | 程序的入口函数并不是main < csdn | 51cto | osc > v50.xx (编译环境篇) | 编译鸿蒙看这篇或许真的够了 < csdn | 51cto | osc > v49.xx (信号消费篇) | 谁让CPU连换四次栈运行 < csdn | 51cto | osc > v48.xx (信号生产篇) | 信号它老人家已经五十多岁了 < csdn | 51cto | osc > v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | osc > v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | osc > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 < csdn | 51cto | osc > v44.xx (中断管理篇) | 硬中断的实现像观察者模式 < csdn | 51cto | osc > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | osc > v42.xx (中断切换篇) | 中断切换又在切换什么 < csdn | 51cto | osc > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | osc > v40.xx (汇编汇总篇) | 所有的汇编代码都在呢 < csdn | 51cto | osc > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | osc > v38.xx (寄存器篇) | 看完再也不对寄存器怕怕 < csdn | 51cto | osc > v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | osc > v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆 < csdn | 51cto | osc > v35.xx (时间管理篇) | tick是操作系统的基本时间单位 < csdn | 51cto | osc > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航 < csdn | 51cto | osc > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 < csdn | 51cto | osc > v32.xx (cpu篇) | 整个内核就是一个死循环 < csdn | 51cto | osc > v31.xx (定时器篇) | 内核最高优先级任务是谁 < csdn | 51cto | osc > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | osc > v29.xx (信号量篇) | 信号量解决任务的什么问题 < csdn | 51cto | osc > v28.xx (进程通讯篇) | 九种进程间通讯方式速揽 < csdn | 51cto | osc > v27.xx (互斥锁篇) | 比自旋锁丰满的互斥锁 < csdn | 51cto | osc > v26.xx (自旋锁篇) | 自旋锁当立贞节牌坊 < csdn | 51cto | osc > v25.xx (并发并行篇) | 两个字永远记住二者区别 < csdn | 51cto | osc > v24.xx (进程概念篇) | 进程在管理哪些资源 < csdn | 51cto | osc > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数 < csdn | 51cto | osc > v22.xx (汇编基础篇) | cpu在哪里打卡上班 < csdn | 51cto | osc > v21.xx (线程概念篇) | 是谁在不断的折腾cpu < csdn | 51cto | osc > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | osc > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个 < csdn | 51cto | osc > v18.xx (源码结构篇) | 梳理内核源文件的作用和含义 < csdn | 51cto | osc > v17.xx (物理内存篇) | 怎么管理物理内存 < csdn | 51cto | osc > v16.xx (内存规则篇) | 内存管理到底在管什么 < csdn | 51cto | osc > v15.xx (内存映射篇) | 虚拟内存虚在哪里 < csdn | 51cto | osc > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础 < csdn | 51cto | osc > v13.xx (源码注释篇) | 鸿蒙必须成功,也必然成功 < csdn | 51cto | osc > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的 < csdn | 51cto | osc > v11.xx (内存分配篇) | 内存有哪些分配方式 < csdn | 51cto | osc > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处 < csdn | 51cto | osc > v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | 51cto | osc > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | osc > v07.xx (调度机制篇) | 任务是如何被调度执行的 < csdn | 51cto | osc > v06.xx (调度队列篇) | 内核有多少个调度队列 < csdn | 51cto | osc > v05.xx (任务管理篇) | 任务池是如何管理的 < csdn | 51cto | osc > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | osc > v03.xx (时钟任务篇) | 谁是触发调度的最大动力 < csdn | 51cto | osc > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | osc > v01.xx (双向链表篇) | 谁是内核最重要结构体 < csdn | 51cto | osc > 进入 >> osc | csdn | 51cto | 掘金 | 公众号 | 头条号 | gitee | github 鸿蒙内核源码分析系列篇.原创不易,欢迎转载,但请注明出处. 热爱是所有的理由和答案 - turing 不经过深度思考的人生不值得一过 - turing

资源下载

更多资源
优质分享App

优质分享App

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

腾讯云软件源

腾讯云软件源

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

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

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