背景

  • Read the fucking source code! --By 鲁迅
  • A picture is worth a thousand words. --By 高尔基

说明:

  1. Kernel版本:4.14
  2. ARM64处理器,Contex-A53,双核
  3. 使用工具:Source Insight 3.5, Visio

1. 介绍

让我们思考几个朴素的问题?

  1. 系统是怎么知道物理内存的?
  2. 在内存管理真正初始化之前,内核的代码执行需要分配内存该怎么处理?

我们先来尝试回答第一个问题,看过dts文件的同学应该见过memory的节点,以arch/arm64/boot/dts/freescale/fsl-ls208xa.dtsi为例:

 memory@80000000 {device_type = "memory";reg = <0x00000000 0x80000000 0 0x80000000>;/* DRAM space - 1, size : 2 GB DRAM */};

这个节点描述了内存的起始地址及大小,事实上内核在解析dtb文件时会去读取该memory节点的内容,从而将检测到的内存注册进系统。

那么新的问题又来了?Uboot会将kernel imagedtb拷贝到内存中,并且将dtb物理地址告知kernelkernel需要从该物理地址上读取到dtb文件并解析,才能得到最终的内存信息,dtb的物理地址需要映射到虚拟地址上才能访问,但是这个时候paging_init还没有调用,也就是说物理地址的映射还没有完成,那该怎么办呢?没错,Fixed map机制出现了。

第二个问题答案:当所有物理内存添加进系统后,在mm_init之前,系统会使用memblock模块来对内存进行管理。

开启探索之旅吧!

2. early_fixmap_init

简单来说,Fixed map指的是虚拟地址中的一段区域,在该区域中所有的线性地址是在编译阶段就确定好的,这些虚拟地址需要在boot阶段去映射到物理地址上。
来张图片看看虚拟地址空间:

图中fixed: 0xffffffbefe7fd000 - 0xffffffbefec00000,描述的就是Fixed map的区域。

那么这段区域中的详细一点的布局是怎样呢?看看arch/arm64/include/asm/fixmap.h中的enum fixed_address结构就清晰了,图来了:

从图中可以看出,如果要访问DTB所在的物理地址,那么需要将该物理地址映射到Fixed map中的区域,然后访问该区域中的虚拟地址即可。访问IO空间也是一样的道理,下文也会讲述到。

那么来看看early_fixmap_init函数的关键代码吧:

void __init early_fixmap_init(void)
{pgd_t *pgd;pud_t *pud;pmd_t *pmd;unsigned long addr = FIXADDR_START;              /* (1) */pgd = pgd_offset_k(addr);           /* (2) */if (CONFIG_PGTABLE_LEVELS > 3 &&!(pgd_none(*pgd) || pgd_page_paddr(*pgd) == __pa_symbol(bm_pud))) {/** We only end up here if the kernel mapping and the fixmap* share the top level pgd entry, which should only happen on* 16k/4 levels configurations.*/BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));pud = pud_offset_kimg(pgd, addr);} else {if (pgd_none(*pgd))__pgd_populate(pgd, __pa_symbol(bm_pud), PUD_TYPE_TABLE);          /* (3) */pud = fixmap_pud(addr);}if (pud_none(*pud))__pud_populate(pud, __pa_symbol(bm_pmd), PMD_TYPE_TABLE);    /* (4) */pmd = fixmap_pmd(addr);__pmd_populate(pmd, __pa_symbol(bm_pte), PMD_TYPE_TABLE);        /* (5) */
......
}

关键点:

  1. FIXADDR_START,定义了Fixed map区域的起始地址,位于arch/arm64/include/asm/fixmap.h中;
  2. pgd_offset_k(addr),获取addr地址对应pgd全局页表中的entry,而这个pgd全局页表正是swapper_pg_dir全局页表;
  3. bm_pud的物理地址写到pgd全局页目录表中;
  4. bm_pmd的物理地址写到pud页目录表中;
  5. bm_pte的物理地址写到pmd页表目录表中;

bm_pud/bm_pmd/bm_pte是三个全局数组,相当于是中间的页表,存放各级页表的entry,定义如下:

static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss;
static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;
static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused;

事实上,early_fixmap_init只是建立了一个映射的框架,具体的物理地址和虚拟地址的映射没有去填充,这个是由使用者具体在使用时再去填充对应的pte entry。比如像fixmap_remap_fdt()函数,就是典型的填充pte entry的过程,完成最后的一步映射,然后才能读取dtb文件。

来一张图片就懂了,是透彻的懂了:

3. early_ioremap_init

如果在boot早期需要操作IO设备的话,那么ioremap就用上场了,由于跟实际的内存管理关系不太大,不再太深入的分析。

简单来说,ioremap的空间为7 * 256K的区域,保存在slot_vir[]数组中,当需要进行IO操作的时候,最终会调用到__early_ioremap函数,在该函数中去填充对应的pte entry,从而完成最终的虚拟地址和物理地址的映射。

4. memblock

上文讲的内容都只是铺垫,为了能正确访问DTB文件并且解析得到物理地址信息。从入口到最终添加的调用过程如下图:

所以,这个章节的重点就是memblock模块,这个是早期的内存分配管理器,我不禁想起了之前在Nuttx中的内存池实现了,细节已然不太清晰了,但是框架性的思维都大同小异。

4.1 结构体

总共由三个数据结构来描述:

  • struct memblock定义了一个全局变量,用来维护所有的物理内存;
  • struct memblock_type代表系统中的内存类型,包括实际使用的内存和保留的内存;
  • struct memblock_region用来描述具体的内存区域,包含在struct memblock_type中的regions数组中,最多可以存放128个。

直接上个代码吧:

static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS] __initdata_memblock;
#endifstruct memblock memblock __initdata_memblock = {.memory.regions      = memblock_memory_init_regions,.memory.cnt     = 1,   /* empty dummy entry */.memory.max      = INIT_MEMBLOCK_REGIONS,.memory.name       = "memory",.reserved.regions = memblock_reserved_init_regions,.reserved.cnt     = 1,   /* empty dummy entry */.reserved.max        = INIT_MEMBLOCK_REGIONS,.reserved.name     = "reserved",#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP.physmem.regions    = memblock_physmem_init_regions,.physmem.cnt       = 1,   /* empty dummy entry */.physmem.max     = INIT_PHYSMEM_REGIONS,.physmem.name       = "physmem",
#endif.bottom_up        = false,.current_limit     = MEMBLOCK_ALLOC_ANYWHERE,
};

定义的memblock为全局变量,在定义的时候就进行了初始化。初始化的时候,regions指向的也是静态全局的数组,其中数组的大小为INIT_MEMBLOCK_REGIONS,也就是128个,限制了这些内存块的个数了,实际在代码中可以看到,当超过这个数值时,数组会以2倍的速度动态扩大。

初始化完了后,大体是这个样子的:

4.2 memblock_add/memblock_remove

memblock子模块,基本的逻辑都是围绕内存的添加和移除操作来展开,最终是通过调用memblock_add_range/memblock_remove_range来实现的。

  • memblock_add_range

图中的左侧是函数的执行流程图,执行效果是右侧部分。右侧部分画的是一个典型的情况,实际的情况可能有多种,但是核心的逻辑都是对插入的region进行判断,如果出现了物理地址范围重叠的部分,那就进行split操作,最终对具有相同flagregion进行merge操作。

  • memblock_remove_range
    该函数执行的一个典型case效果如下图所示:

假如现在需要移除掉一片区域,而该区域跨越了多个region,则会先调用memblock_isolate_range来对这片区域进行切分,最后再调用memblock_remove_region对区域范围内的region进行移除操作。

当调用memblock_alloc函数进行地址分配时,最后也是调用memblock_add_range来实现的,申请的这部分内存最终会添加到reserved类型中,毕竟已经分配出去了,其他人也不应该使用了。

5. arm64_memblock_init

当物理内存都添加进系统之后,arm64_memblock_init会对整个物理内存进行整理,主要的工作就是将一些特殊的区域添加进reserved内存中。函数执行完后,如下图所示:

  • 其中浅绿色的框表示的都是保留的内存区域, 剩下的部分就是可以实际去使用的内存了。

物理内存大体面貌就有了,后续就需要进行内存的页表映射,完成实际的物理地址到虚拟地址的映射了。

那就待续吧。

(二)Linux物理内存初始化相关推荐

  1. Linux物理内存初始化

    背景 Read the fucking source code!  --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...

  2. Linux内核初始化阶段内存管理的几种阶段

    本系列旨在讲述从引导到完全建立内存管理体系过程中,内核对内存管理所经历的几种状态.阅读本系列前,建议先阅读memblock的相关文章. 一些讲在前面的话 在很久很久以前,linux内核还是支持直接从磁 ...

  3. 操作系统实验二:物理内存管理系统

    操作系统实验二:物理内存管理系统 一. 实验目的 二. 实验内容 三. 实验准备 [实验概述] [关键数据结构] [执行流程] 四. 实验步骤 (一) 练习0:填写已有实验 (二) 练习1:实现 fi ...

  4. Linux 物理内存管理涉及的三大结构体之struct zone

    从Linux 物理内存管理涉及的三大结构体之struct page 中,大概知道了UMA和NUMA概念,同时也知道在每个node对应的内存范围内,都会将其分成不同的内存管理区域zone.之所以分成几类 ...

  5. linux usb初始化

    linux usb 初始化 谨以此文纪念过往的岁月 一.前言 对于usb的普通驱动,我们了解了不少,但是对于usb的真正核心还是不是太理解.该文中对于usb的初始化进行一定的学习,如有不对之处,请各位 ...

  6. linux内存管理的主要概念是虚拟内存,有关linux内存管理机制的相关内容,linux物理内存和虚拟内存,深入了解Linux内存运行 ......

    在linux中空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然. 这是Linux内存管理的一个优秀特性,区别于Windows的内存管理. 主要特点: 无论物理内存有多大,L ...

  7. linux物理内存地址与iomem,一种Linux系统物理内存镜像文件分析方法_4

    模块信息,如图7所示,给出了本发明的实施例中 模块结构关系图,modules变量指向某一个已加载模块结构体module地址,所有已加载模 块其module形成一个双向链表,如图7所示,据此可以获取到所 ...

  8. Linux系统启动初始化

    文章目录 一.BIOS 加载启动引导程序 二.MBR 主引导扇区 三.GRUB引导内核 3.1运行 boot.img 3.2加载 core.img 3.3切换到保护模式 3.4kernel.img 引 ...

  9. LINUX系统初始化

     说明BIOS是位于位于主板flash rom(掉电不丢失0)中的程序,操作系统Boot Loader位于硬盘MBR中.BIOS在完成 硬件检测和资源分配后.将硬盘MBR中的Boot Loader读到 ...

最新文章

  1. 现代密码学1.4--现代密码的三大原则
  2. JAVA——Unicode编码格式工具类
  3. react循环setstate_[React] 8 - React 自身或工程性能优化点?
  4. 处理SAP gateway service使用过程中遇到的400 error - Malformed URI literal syntax
  5. JS实现动态显示当前时间(12/24小时制)(转载Mr.Think)
  6. Windows下Git客户端的安装及配置
  7. cannot convert 'this' pointer from 'const class A' to 'class A '
  8. 【转载】如何知道自己适合做什么
  9. 北大主场夺金ACM-ICPC全球总决赛,总教练罗国杰分享背后“秘笈”
  10. Linux的使用_尚硅谷视频学习笔记
  11. R语言单因素分析案例
  12. 只读(Readonly)与禁用(Disable)的区别与使用
  13. 批量缩小多张图片尺寸,保持图片清晰无损
  14. Visual Assist 使用小结
  15. android 定制手机刷机,怎么定制安卓刷机包
  16. 同向放大器、反向放大器的区别
  17. 电影网站 php asp,大站长电影网址大全 ASP版 v20180507
  18. Python chicken (2)
  19. 如何开一家盈利的健身房?我用1年回本的经验告诉你,别谈恋爱
  20. 看新闻的时候,你们手机都用什么软件?

热门文章

  1. 2019-1-19 object祖宗类的equals重写
  2. 美国一男子从移动电话基站跳伞死亡
  3. 专访重庆光博士才俊明
  4. 使用存储过程,分页用户控件,jQuery进行Ajax分页!
  5. DPDK 报文调度/保序 终极解决方案 Event Dev 简介(硬件加速也很可能是鸿蒙微内核IPC性能的钥匙)...
  6. wordpress子比zibll主题V6.4.1免授权博客自媒体付费下载主题
  7. python小数乘法_python小数类型
  8. 关于TI账号注册卡在机器人验证那一步
  9. 基于Python的汉语多音字注音研究
  10. Shark恒【逆向笔记】