title: 解析dtb为节点

date: 2019/4/26 14:02:18

toc: true

kernel解析dtb为节点

head.s入口传递

回顾

看以前的笔记 kernel(二)源码浅析

先来回顾下以前uboot是怎么传递参数的?

R0 一般设置为0
R1 machine id (设备树不使用)
R2 ATAGS(设备树使用为DTB地址)

kernel的入口点是``arch\arm\kernel\head.S,以前的流程是根据这个machine id去匹配到具体的单板,然后使用ATAGS`构造相应的启动参数

使用机器id可以找到类似如下的结构体

  1. #define MACHINE_START(_type,_name) \
  2. static const struct machine_desc __mach_desc_##_type \
  3. __used \
  4. __attribute__((__section__(".arch.info.init"))) = { \
  5. .nr = MACH_TYPE_##_type, \
  6. .name = _name,
  7. #define MACHINE_END \
  8. };
  9. MACHINE_START(S3C2440, "SMDK2440")
  10. /* Maintainer: Ben Dooks <ben@fluff.org> */
  11. .phys_io = S3C2410_PA_UART,
  12. .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
  13. .boot_params = S3C2410_SDRAM_PA + 0x100,
  14. .init_irq = s3c24xx_init_irq,
  15. .map_io = smdk2440_map_io,
  16. .init_machine = smdk2440_machine_init,
  17. .timer = &s3c24xx_timer,
  18. MACHINE_END

具体这个结构如下

  1. struct machine_desc {
  2. /*
  3. * Note! The first four elements are used
  4. * by assembler code in head-armv.S
  5. */
  6. unsigned int nr; /* architecture number */
  7. unsigned int phys_io; /* start of physical io */
  8. unsigned int io_pg_offst; /* byte offset for io
  9. * page tabe entry */
  10. const char *name; /* architecture name */
  11. unsigned long boot_params; /* tagged list */
  12. unsigned int video_start; /* start of video RAM */
  13. unsigned int video_end; /* end of video RAM */
  14. unsigned int reserve_lp0 :1; /* never has lp0 */
  15. unsigned int reserve_lp1 :1; /* never has lp1 */
  16. unsigned int reserve_lp2 :1; /* never has lp2 */
  17. unsigned int soft_reboot :1; /* soft reboot */
  18. void (*fixup)(struct machine_desc *,
  19. struct tag *, char **,
  20. struct meminfo *);
  21. void (*map_io)(void);/* IO mapping function */
  22. void (*init_irq)(void);
  23. struct sys_timer *timer; /* system tick timer */
  24. void (*init_machine)(void);
  25. };

设备树启动浅析

head.s会调用head-common.S

  1. a. __lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
  2. b. __vet_atags : 判断是否存在可用的ATAGSDTB
  3. c. __create_page_tables : 创建页表, 即创建虚拟地址和物理地址的映射关系
  4. d. __enable_mmu : 使能MMU, 以后就要使用虚拟地址了
  5. e. __mmap_switched : 上述函数里将会调用__mmap_switched
  6. .long __bss_start @ r0
  7. .long __bss_stop @ r1
  8. .long init_thread_union + THREAD_START_SP @ sp
  9. .long processor_id @ r0
  10. .long __machine_arch_type @ r1
  11. .long __atags_pointer @ r2
  12. f. bootloader传入的r2参数, 保存到变量__atags_pointer
  13. g. 调用C函数start_kernel

start_kernel

先记住这个

  1. .long processor_id @ r0
  2. .long __machine_arch_type @ r1
  3. .long __atags_pointer @ r2

大概的流程是这样的

  1. mdesc = setup_machine_fdt(__atags_pointer);
  2. // 头部检查
  3. early_init_dt_verify
  4. //找到最匹配的machine_desc
  5. of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
  6. if (!mdesc) //按照以前使用atag的方式
  7. mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
  8. // 找到匹配的machine_desc,后处理了
  9. machine_desc = mdesc;
  10. machine_name = mdesc->name;
  11. dump_stack_set_arch_desc("%s", mdesc->name);
  12. // 把命令行启动参数存起来
  13. /* populate cmd_line too for later use, preserving boot_command_line */
  14. strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
  15. *cmdline_p = cmd_line;
  16. // 保留dtb 本身的内存 以及指定的 reserve的内存
  17. arm_memblock_init(mdesc);

启动参数以及内存解析

  • /chosen节点中bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来,存在boot_command_line

  • /memory中的reg属性指定了不同板子内存的大小和起始地址,调用memblock_add

  • 根节点的#address-cells和#size-cells属性指定属性参数的位数

代码浅析

  1. setup_machine_fdt
  2. mdesc_best = &__mach_desc_GENERIC_DT; 这个应该是机器描述符段的起始地址
  3. early_init_dt_verify
  4. // 头部校验
  5. fdt_check_header
  6. // 把这个 地址又存一遍 initial_boot_params
  7. initial_boot_params = params;
  8. //计算crc到 of_fdt_crc32
  9. of_fdt_crc32=crc32_be(...)
  10. mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
  11. // arch_get_next_mach 获取下一个机器描述
  12. // 寻找下一个机器描述的id
  13. // get_next_compat=arch_get_next_mach
  14. // 最终找到最匹配的机器描述
  15. while ((data = get_next_compat(&compat)))
  16. {
  17. score = of_flat_dt_match(dt_root, compat);
  18. // initial_boot_params 就是上面存档的dtb地址
  19. of_fdt_match(initial_boot_params, node, compat);
  20. of_fdt_is_compatible
  21. // 寻找 compatible 属性来匹配 单板
  22. fdt_getprop(blob, node, "compatible", &cplen);
  23. // 找到最匹配的
  24. while (cplen > 0)
  25. of_compat_cmp
  26. if (score > 0 && score < best_score)
  27. {
  28. best_data = data;
  29. best_score = score;
  30. }
  31. }
  32. early_init_dt_scan_nodes
  33. // 找到启动命令参数
  34. /* Retrieve various information from the /chosen node */
  35. of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
  36. early_init_dt_scan_chosen // 寻找到 chosen 的 bootargs
  37. p = of_get_flat_dt_prop(node, "bootargs", &l);
  38. // 找到reg的描述
  39. /* Initialize {size,address}-cells info */
  40. of_scan_flat_dt(early_init_dt_scan_root, NULL);
  41. prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
  42. prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
  43. // 内存相关设置
  44. /* Setup memory, calling early_init_dt_add_memory_arch */
  45. of_scan_flat_dt(early_init_dt_scan_memory, NULL);
  46. early_init_dt_scan_memory
  47. if( ! of_get_flat_dt_prop(node, "linux,usable-memory", &l);)
  48. else of_get_flat_dt_prop(node, "reg", &l);
  49. ....
  50. //
  51. of_get_flat_dt_prop(node, "hotpluggable", NULL);

内存保留

uboot把设备树DTB文件随便放到内存的某一个地方就可以使用,内核不会去覆盖DTB所占用的那块内存呢.在设备树文件中,可以使用/memreserve/指定一块内存,这块内存就是保留的内存,内核不会占用它。即使你没有指定这块内存,当我们内核启动时,他也会把设备树所占用的区域保留下来.

  1. setup_arch
  2. // dtb 判断,机器id查找
  3. setup_machine_fdt
  4. // 内存保留
  5. arm_memblock_init(mdesc);
  6. // dtb 自身的内存空间
  7. early_init_fdt_reserve_self();
  8. early_init_dt_reserve_memory_arch(
  9. __pa(initial_boot_params), //initial_boot_params 是以前存储的dtb地址
  10. fdt_totalsize(initial_boot_params), //头部指示的大小
  11. 0);
  12. memblock_reserve(....)
  13. // dtb 描述中的 reserve 内存
  14. early_init_fdt_scan_reserved_mem();
  15. fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
  16. early_init_dt_reserve_memory_arch
  17. memblock_reserve(....)

小结

综上,我们到此为止解析了如下

chosen/bootargs 启动命令行boot_command_line
memory 保留内存
dtb本身内存
initial_boot_params dtb地址,这个是个全局变量

节点的解析

这段代码的入口是这里

  1. setup_arch
  2. // 找到匹配的机器描述
  3. setup_machine_fdt
  4. // 内存保留
  5. arm_memblock_init(mdesc);
  6. // 节点解析
  7. unflatten_device_tree();
  8. unflatten_device_tree()
  9. {
  10. // 第一次解析,这里最后一个参数是false,不会分配内存,会计算所需的内存大小
  11. __unflatten_device_tree(initial_boot_params, NULL, &of_root,
  12. early_init_dt_alloc_memory_arch, false);
  13. /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
  14. of_alias_scan(early_init_dt_alloc_memory_arch);
  15. // 这里会真正分配内存,存放结构
  16. unittest_unflatten_overlay_base();
  17. }

这段代码还是比较复杂了,暂时不去分析了这里没有使用递归,按我的理解应该是类似这样的

  1. for(next_node(xxx))-----这里的next_node 会记录树的深度,也就是应该会有父兄的记录
  2. {
  3. // 解析status
  4. // 解析属性
  5. if(is_not_属性)
  6. continue;
  7. }

数据结构

节点描述

  1. struct device_node {
  2. const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL"
  3. const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL"
  4. phandle phandle;
  5. const char *full_name; // 节点的名字, node-name[@unit-address]
  6. struct fwnode_handle fwnode;
  7. struct property *properties; // 节点的属性
  8. struct property *deadprops; /* removed properties */
  9. struct device_node *parent; // 节点的父亲
  10. struct device_node *child; // 节点的孩子(子节点)
  11. struct device_node *sibling; // 节点的兄弟(同级节点)
  12. #if defined(CONFIG_OF_KOBJ)
  13. struct kobject kobj;
  14. #endif
  15. unsigned long _flags;
  16. void *data;
  17. #if defined(CONFIG_SPARC)
  18. const char *path_component_name;
  19. unsigned int unique_id;
  20. struct of_irq_controller *irq_trans;
  21. #endif
  22. };

属性描述

  1. struct property {
  2. char *name; // 属性名字, 指向dtb文件中的字符串
  3. int length; // 属性值的长度
  4. void *value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储
  5. struct property *next;
  6. #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
  7. unsigned long _flags;
  8. #endif
  9. #if defined(CONFIG_OF_PROMTREE)
  10. unsigned int unique_id;
  11. #endif
  12. #if defined(CONFIG_OF_KOBJ)
  13. struct bin_attribute attr;
  14. #endif
  15. };

具体的描述看下老师的图,很容易理解,就是一个比较大的链表,注意其中节点名字指向节点本身最后的内存

kernel解析dtb为节点的更多相关文章

  1. Fabric1.4源码解析:Peer节点加入通道

          又开始新的阅读了,这次看的是Peer节点加入通道的过程.其实每次看源码都会有好多没有看懂的地方,不过相信只要坚持下去,保持记录,还是有很多收获的.       对于Peer节点加入通道这一 ...

  2. laravel kernel解析过程

    laravel kernel解析过程 前面的两篇laravel文章过后,可以在bootstrap/app.php中拿到$app这个实例, app.php中 接下来通过singleton方法绑定了三个闭 ...

  3. dubbo源码解析-zookeeper创建节点

    前言 在之前dubbo源码解析-本地暴露中的前言部分提到了两道高频的面试题,其中一道dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗?在上周的dubbo源码 ...

  4. 解析xml(当节点中有多个子节点)

    概要:解析一个xml,当一个节点中又包含多个子节点如何解析,对比一个节点中不包括其他节点的情况. 一,xml样例 <cisReports batNo="查询批次号" unit ...

  5. Fabric1.4源码解析:Peer节点背书提案过程

    以前从来没有写过博客,从这段时间开始才开始写一些自己的博客,之前总觉得写一篇博客要耗费大量的时间,而且写的还是自己已经学会的,觉得没什么必要.但是当开始用博客记录下来的时候,才发现有些学会的地方只是自 ...

  6. Fabric1.4源码解析:Peer节点启动过程

    看一下Peer节点的启动过程,通常在Fabric网络中,Peer节点的启动方式有两种,通过Docker容器启动,或者是通过执行命令直接启动. 一般情况下,我们都是执行docker-compose -f ...

  7. Spring mybatis源码篇章-NodeHandler实现类具体解析保存Dynamic sql节点信息

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource SqlNode接口类 publi ...

  8. mybatis源码-Mapper解析之SQL 语句节点解析(一条语句对应一个MappedStatement)

    目录 一起学 mybatis 0 <sql> 节点解析 1 解析流程 2 节点解析 2.1 解析流程 2.2 <include> 节点的解析 2.3 Node.ELEMENT_ ...

  9. linux kernel的cmdline参数解析原理分析【转】

    转自:https://blog.csdn.net/skyflying2012/article/details/41142801 版权声明:本文为博主kerneler辛苦原创,未经允许不得转载. htt ...

随机推荐

  1. 直接从硬盘安装centos7网址整理

    1.https://blog.csdn.net/happy_joker/article/details/52822025 注意:(1)第3步-->Linux引导安装-->软件选择--> ...

  2. SpringCloud系列一:SpringCloud的简介和架构

    一.SpringCloud简介 SpringCloud就是一套分布式服务治理的框架,既然它是一套服务治理的框架,那么它本身不会提供具体功能性的操作,更专注于服务之间的通讯.熔断.监控等.因此就需要很多 ...

  3. python大法好——Python2.x与3​​.x版本区别

    python大法好——Python2.x与3​​.x版本区别 Python的3​​.0版本,常被称为Python 3000,或简称Py3k.相对于Python的早期版本,这是一个较大的升级. 为了不带 ...

  4. leetcode64

    class Solution { public: int minPathSum(vector<vector<int>>& grid) { int row=grid.si ...

  5. vmware 里MAC 鼠标能移动 无法单击

    vmware 里MAC  鼠标能移动 无法单击 移动有效果,能看到鼠标移动的光标,鼠标放到mac的图标上还有提示,就是无法单击. 键盘正常. 重启mac,重启vmware 20次好了,2小时.

  6. ios unicode

    转义字符,反斜扛\ \u 后跟4位16进制数 \U 后跟8位16进制数

  7. php 处理上百万条的数据库如何提高处理查询速度

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...

  8. 我的hadoop学习之路

    Hadoop实现了一个分布式文件系统(Hadoop Distributed File System),简称HDFS.HDFS有高容错性的特点,并且设计用来部署在低廉的(low-cost)硬件上. Ha ...

  9. Web Worker模拟抢票

    web worker工作原理图: 抢票系统思维导图: 思路:五个人(5个div窗口模拟)同时进行抢票,有百分之十的几率可以抢到票,抢到票后对应的窗口(即随机生成的数大于等于0小于9的情况)会编程天蓝色 ...

  10. 解决在jupyter notebook中遇到的ImportError: matplotlib is required for plotting问题

    昨天学习pandas和matplotlib的过程中, 在jupyter notebook遇到ImportError: matplotlib is required for plotting错误, 以下 ...