首页 文章 精选 留言 我的

精选列表

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

鸿蒙内核源码分析(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 >

资源下载

更多资源
Mario

Mario

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

腾讯云软件源

腾讯云软件源

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

Sublime Text

Sublime Text

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

WebStorm

WebStorm

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

用户登录
用户注册