(二)Linux物理内存初始化
背景
Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基
说明:
- Kernel版本:4.14
- ARM64处理器,Contex-A53,双核
- 使用工具:Source Insight 3.5, Visio
1. 介绍
让我们思考几个朴素的问题?
- 系统是怎么知道物理内存的?
- 在内存管理真正初始化之前,内核的代码执行需要分配内存该怎么处理?
我们先来尝试回答第一个问题,看过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 image
和dtb
拷贝到内存中,并且将dtb物理地址
告知kernel
,kernel
需要从该物理地址上读取到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) */
......
}
关键点:
FIXADDR_START
,定义了Fixed map
区域的起始地址,位于arch/arm64/include/asm/fixmap.h
中;pgd_offset_k(addr)
,获取addr
地址对应pgd全局页表中的entry
,而这个pgd全局页表正是swapper_pg_dir
全局页表;- 将
bm_pud
的物理地址写到pgd全局页目录表中; - 将
bm_pmd
的物理地址写到pud页目录表中; - 将
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
操作,最终对具有相同flag
的region
进行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物理内存初始化相关推荐
- Linux物理内存初始化
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...
- Linux内核初始化阶段内存管理的几种阶段
本系列旨在讲述从引导到完全建立内存管理体系过程中,内核对内存管理所经历的几种状态.阅读本系列前,建议先阅读memblock的相关文章. 一些讲在前面的话 在很久很久以前,linux内核还是支持直接从磁 ...
- 操作系统实验二:物理内存管理系统
操作系统实验二:物理内存管理系统 一. 实验目的 二. 实验内容 三. 实验准备 [实验概述] [关键数据结构] [执行流程] 四. 实验步骤 (一) 练习0:填写已有实验 (二) 练习1:实现 fi ...
- Linux 物理内存管理涉及的三大结构体之struct zone
从Linux 物理内存管理涉及的三大结构体之struct page 中,大概知道了UMA和NUMA概念,同时也知道在每个node对应的内存范围内,都会将其分成不同的内存管理区域zone.之所以分成几类 ...
- linux usb初始化
linux usb 初始化 谨以此文纪念过往的岁月 一.前言 对于usb的普通驱动,我们了解了不少,但是对于usb的真正核心还是不是太理解.该文中对于usb的初始化进行一定的学习,如有不对之处,请各位 ...
- linux内存管理的主要概念是虚拟内存,有关linux内存管理机制的相关内容,linux物理内存和虚拟内存,深入了解Linux内存运行 ......
在linux中空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然. 这是Linux内存管理的一个优秀特性,区别于Windows的内存管理. 主要特点: 无论物理内存有多大,L ...
- linux物理内存地址与iomem,一种Linux系统物理内存镜像文件分析方法_4
模块信息,如图7所示,给出了本发明的实施例中 模块结构关系图,modules变量指向某一个已加载模块结构体module地址,所有已加载模 块其module形成一个双向链表,如图7所示,据此可以获取到所 ...
- Linux系统启动初始化
文章目录 一.BIOS 加载启动引导程序 二.MBR 主引导扇区 三.GRUB引导内核 3.1运行 boot.img 3.2加载 core.img 3.3切换到保护模式 3.4kernel.img 引 ...
- LINUX系统初始化
说明BIOS是位于位于主板flash rom(掉电不丢失0)中的程序,操作系统Boot Loader位于硬盘MBR中.BIOS在完成 硬件检测和资源分配后.将硬盘MBR中的Boot Loader读到 ...
最新文章
- 现代密码学1.4--现代密码的三大原则
- JAVA——Unicode编码格式工具类
- react循环setstate_[React] 8 - React 自身或工程性能优化点?
- 处理SAP gateway service使用过程中遇到的400 error - Malformed URI literal syntax
- JS实现动态显示当前时间(12/24小时制)(转载Mr.Think)
- Windows下Git客户端的安装及配置
- cannot convert 'this' pointer from 'const class A' to 'class A '
- 【转载】如何知道自己适合做什么
- 北大主场夺金ACM-ICPC全球总决赛,总教练罗国杰分享背后“秘笈”
- Linux的使用_尚硅谷视频学习笔记
- R语言单因素分析案例
- 只读(Readonly)与禁用(Disable)的区别与使用
- 批量缩小多张图片尺寸,保持图片清晰无损
- Visual Assist 使用小结
- android 定制手机刷机,怎么定制安卓刷机包
- 同向放大器、反向放大器的区别
- 电影网站 php asp,大站长电影网址大全 ASP版 v20180507
- Python chicken (2)
- 如何开一家盈利的健身房?我用1年回本的经验告诉你,别谈恋爱
- 看新闻的时候,你们手机都用什么软件?
热门文章
- 2019-1-19 object祖宗类的equals重写
- 美国一男子从移动电话基站跳伞死亡
- 专访重庆光博士才俊明
- 使用存储过程,分页用户控件,jQuery进行Ajax分页!
- DPDK 报文调度/保序 终极解决方案 Event Dev 简介(硬件加速也很可能是鸿蒙微内核IPC性能的钥匙)...
- wordpress子比zibll主题V6.4.1免授权博客自媒体付费下载主题
- python小数乘法_python小数类型
- 关于TI账号注册卡在机器人验证那一步
- 基于Python的汉语多音字注音研究
- Shark恒【逆向笔记】