《深入Linux内核架构》第3章 内存管理(4)

目录

3.4 初始化内存管理

3.4.1 建立数据结构

3.4.2 特定于体系架构的设置

内核在内存中的布局

初始化步骤

分页机制的初始化

3.4.3 启动期间的内存管理

数据结构

初始化

与内核的接口

停用bootmem分配器

释放初始化数据


3.4 初始化内存管理

包括:

        显式设置内存模型。

        确认NUMA中内存总数量。

        初始化内存节点。

3.4.1 建立数据结构

只有1个节点时(如UMA中),nid是伪参数。

        #define NODE_DATA(nid) (&contig_page_data)

setup_arch()中内存相关有:

        内存布局初始化:设置地址空间范围。

        MMU配置:包括设置页表、页目录。

        物理内存探测:解析启动参数或BIOS信息,获取内存总量和分布信息。

        启用bootmem分配器,用于启动过程内存分配。

setup_per_cpu_areas:

        bootmem分配器给每个CPU分配一块存储per-cpu变量的区域。

        per-cpu变量保存在ELF文件单独的段中,如.data..percpu。

        并加载到物理内存.data..percpu段中。

build_all_zonelists:

        为每个CPU构建zonelist,其中zone按优先级排序。

        可根据当前CPU的zonelis构快速找到最优的zone,以完成内存分配。

                优点:优化内存访问,降低内存碎片、提高内存利用率。

mm_init:

        初始化内核的页表。

        停用bootmem分配器,迁移到伙伴系统。

        初始化SLAB 分配器(后续章节讲)

        预留内存页。

        初始化内存统计和信息

setup_per_cpu_pageset:

        给每个CPU设置缓存页的结构。

        优点:避免使用伙伴系统分配,引发zone->lock锁竞争。

zonelist构建流程:

        遍历所有内存节点ABCD,每个节点都建立各自zonelist。

如下图是节点C的zonelist,表示内存不足时尝试分配的优先次序。

C2表示:内存结点C的高端内存域。

D1表示:内存结点D的普通内存域

如想要从结点C分配一个高端内存域的内存,查看上图的2,优先从C2中分配,C2无法满足分配需求,依次尝试C1 C0 D2 ....

3.4.2 特定于体系架构的设置

本节所讲不同体系架构实现有差异。

2.6.24内核开始将IA32和AMD64统一为x86。

        体系代码都迁移到目录arch/x86下。

内核在内存中的布局

先讲两个重要宏:

1. PHYS_OFFSET:内存在系统内的起始物理地址。

        该值与平台相关,如项目中:

                #define PHYS_OFFSET UL(CONFIG_DRAM_BASE)

                        CONFIG_DRAM_BASE=0x80000000

2. PAGE_OFFSET:内核空间的起始虚拟地址,划分了内核与用户空间。

        32位系统为例:

                若值为0xC0000000,即用户空间3GB,内核空间1GB。

                若值为0x80000000,即用户空间和内核空间均为2GB。

cat /proc/iomem中可查看:

        1. 系统RAM的起始物理地址。

        2. 内核代码段和数据段的物理地址。

ZRELADDR:内核解压的运行物理地址。

我的项目中ZRELADDR地址为:

        zreladdr-y := $(CONFIG_DRAM_BASE)+0x8000

        CONFIG_DRAM_BASE是内存的起始物理地址,项目中值为0x80000000。

所以内核解压后运行地址是:内存起始偏移0x8000处,即0x80008000。

# cat /proc/iomem 可验证

        80000000-83ffffff : System RAM

                80008000-804cbb83 : Kernel code 内核的物理地址0x80008000

                804ee000-8063b10f : Kernel data

内核编译链接时,vmlinux.ld.S确定内核的_text段,__edata等段,以及符号函数的虚拟地址,并存储在system.map文件。

一个内核符号的物理地址计算方法:

        Physical Address = (Virtual Address - PAGE_OFFSET) + PHYS_OFFSET

                PAGE_OFFSET:内核虚拟地址空间的的起始位置。

                PHYS_OFFSET:内存的其实物理地址。

查看系统物理地址空间分布,包括所有硬件设备,如PCIE,RAM,SPI,PHY,RTC,flash等。

# cat /proc/iomem

        00000000-00000000 : gdm-pmic

        13200000-1320003f : gdm-spi

        13380000-13381fff : gdm-rgmii

        40000000-401fffff : gdm-sflash

        80000000-83ffffff : System RAM

                80008000-804628a7 : Kernel code

                80484000-805daf4b : Kernel data

初始化步骤

Intel IA-32架构为例:

        setup_arch中关于内存管理的部分如下:

parse_cmdline_early:解析cmdline中内存参数,如

        mem=xxx[KMG]:

                控制内核可用内存大小,例如只使用部分内存。

        reserve=<start>,<size>

                预留一块物理内存不使用。

        cma=<start>,<size>

                启动时预留一块连续的物理内存,例如给DMA设备或者图形硬件使用。

setup_memory

        确定每个节点可用的物理内存页数目

        初始化bootmem

        分配各个内存区

paging_init

        初始化内核页表。

        根据编译选项,是否开启PAE特性。

zone_sizes_init

        初始化所有内存结点的pgdata_t实例。

分页机制的初始化

先看几个地址空间分布图,以32位系统为例。

1. 内核虚拟地址空间分布:

2. 内核空间与物理内存映射:

直接映射区:即线性映射区。

        范围:内核空间3G ~ 3G+896M,映射到物理内存DMA和NORMAL两个ZONE

直接映射区的虚拟地址转换为物理地址:

        #define __pa(x) ((unsigned long) (x) - PAGE_OFFSET)

内存DMA和NORMAL的物理地址转换为虚拟地址:

        #define __va(x) ((void *) ((unsigned long) (x) + PAGE_OFFSET))

注意:这两个函数只适用于直接映射区。

        而高端内存区不是简单的线性映射,不适用。

内核空间最后128M,会映射到物理内存ZONE_HIGHMEM。有三种用途:

1. 固定映射: Fixed Mapping

        编译时已映射到特定物理地址。

        无需动态页表,快速访问的物理内存地址

        用于:

                映射硬件寄存器区和I/O。

        起始处:FIXADDR_START

2. 永久/映射映射: pkmap:Persistent Kernel Mapping

        将高端页帧长期映射到内核地址空间中,直到手动解除映射。

        如kmap, kunmap函数。

3. vmalloc区:即动态内存映射区。

        范围:VMALLOC_START ~ VMALLOC_END

        vmalloc函数作用:

                分配较大的连续虚拟内存,对应物理内存不连续。

paging_init:

        划分内核空间与用户空间。

pagetable_init:

        初始化系统PGD PUD PMD PTE页表。

        将物理内存映射到虚拟地址PAGE_OFFSET处。

        初始化固定映射。

load_cr3

        将PGD页表(内核变量swapper_pg_dir)加载到CR3寄存器。

__flush_all_tlb:

        刷出TLB,即清空页表缓存。

zone_pcp_init:

        初始化PCP冷热页,包括初始化per_cpu_pageset实例。

        计算batch值。

                batch值作用:如果pcp没有空闲页,每次从zone中批量分配batch个页面。

                batch大小:考虑让热缓存页有可能放置到CPU L2缓存中

3.4.3 启动期间的内存管理

内核启动过程中,内存管理子系统还未初始化,此时使用bootmem分配器进行内存分配。

bootmem:使用bitmap表示页空闲、已使用的页

        分配时,遍历位图,找到所需连续空闲页。

        缺点:每次分配都遍历位图,不高效。

数据结构

系统每个节点都有一个bootmem_data实例,在编译时分配。

struct bootmem_data {

        unsigned long node_min_pfn;

                起始物理页框号

        unsigned long node_low_pfn;

                可管理的物理内存最后一页编号,即ZONE_NORMAL最后一页

        void *node_bootmem_map;

                位图指针,表示页是否使用。

        unsigned long last_end_off;

                上次分配页的页内偏移,用于分配小于整页的内存

        unsigned long hint_idx;

                提供一个分配起点或最有位置的提示

        struct list_head list;

                链接所有结点的bootmem_data

} bootmem_data_t;

UMA系统只需一个sbootmem_data_t实例

        contig_bootmem_data //contig,即contigous连续内存模型

初始化

ARM:setup_arch-> paging_init -> bootmem_init

而IA-32:set_memory 中进行bootmem初始化

bootmem:不使用内存的高端内存区域。

        使用内存的normal区域,因为可通过固定的线性映射访问。访问简单。

bootmem初始化包括:

        初始化bootmem_data结构体。

        扫描内存,构建位图,表示对应内存页是否使用。

        为设备驱动、DMA区域等预留内存区域。

与内核的接口

启动期间分配内存:

        alloc_bootmem()/alloc_bootmem_pages()

                从ZONE_NORMAL域内分配内存

       

        alloc_bootmem_low()/alloc_bootmem_low_pages()

                从ZONE_DMA域分配内存

        alloc_bootmem_node()

                NUMA中从指定节点的bootmme分配内存.

上述函数都调用alloc_bootmem_core(unsigned long size,

                                unsigned long align,

                                unsigned long goal,

                                unsigned long limit)

        size:所需内存大小。

        align:对齐方式,如按page对齐。

        goal:目标地址,建议分配的内存地址

        limit:确保不会分配超过这个地址的内存。

释放内存

free_bootmem(unsigned long addr, unsigned long size)

void __init free_bootmem_node(pg_data_t *pgdat, unsigned long physaddr, unsigned long size)

        这两个函数使用较少,大部分分配的内存后续会一直使用,不用释放。

停用bootmem分配器

系统初始化到伙伴系统后,伙伴系统将负责内存分配工作,此时需停用bootmem分配器。

停用bootmem函数:

        free_all_bootmem(void)

                扫描bootmem位图,释放未使用页到伙伴系统。

释放初始化数据

__init和__initdata是GNU C编译器语句,编译后会将对应数据和函数放在内核镜像的特定段。

启动结束,可从内存完全删除对应数据和函数。

其实现为:

#define __init __attribute__(__section(.init.text)) __cold

#define __initdata __attribute__(__section(.init.data))

        __attribute__:GCC关键字

        __section:编译器将数据放入二进制文件.init.text和.inint.data中

        __cold:该函数不会被经常调用

命令readelf可查看内核镜像的各个段,如:

        readelf - sections vmlinux

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/572399.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IHO S-100系列产品标准

1 什么是S-100? S-100《通用海道测量数据模型》是国际海道测量组织(IHO)推出的新一代海上空间地理信息国际标准,旨在克服传统S-57数字海道测量数据传输标准的局限。这一标准不仅兼容了更为丰富的数据类型,如影像与栅格数据、时变数据等,还摒弃了固定的编码格式要求,采用…

2024/03/27(C++·day3)

一、思维导图 二、完成下面类 代码 #include <cstring> #include <iostream>using namespace std;class myString { private:char *str; // 记录C风格的字符串int size; // 记录字符串的实际长度public:// 无参构造函数myString() : size(10){str new char[si…

MySQL数据库高级语句

文章目录 MySQL高级语句older by 排序区间判断查询或与且&#xff08;or 与and&#xff09;嵌套查询&#xff08;多条件&#xff09;查询不重复记录distinctcount 计数限制结果条目limit别名as常用通配符嵌套查询&#xff08;子查询&#xff09;同表不同表嵌套查询还能用于删除…

家用洗地机哪个型号质量好,性价比高?全方位盘点与推荐!

洗地机一推一拉就能轻松把污渍清理干净&#xff0c;比用传统清理工具打扫卫生方便多了&#xff0c;更加省时省力&#xff0c;但是该说不说&#xff0c;洗地机这种东西买对了是提升幸福感&#xff0c;买错了那可是要多糟心有多糟心&#xff0c;所以在买之前一定要做好攻略。 洗…

代码随想录——搜索插入位置(Leetcode35)

题目链接 class Solution {public int searchInsert(int[] nums, int target) {int len nums.length;int left 0;int right len - 1;int index -1;while(left < len / 2){if(nums[left] target || target < nums[left]){index left;break;}else{left;}if(nums[ri…

STM32收发HEX数据包

在实际应用中&#xff0c;STM32的串口通信都是以数据包格式进行收发&#xff0c;这个数据包一般都包含包头和包尾&#xff0c;表示一个数据包。源代码在文末给出 数据包格式&#xff1a; 固定长度&#xff0c;含包头包尾 可变包长&#xff0c;含包头包尾 问题1&#xff1a;当…

递推入门||数字三角形

题目&#xff1a; 如果只能往左下或右下走&#xff0c;找到下面三角形的最大值 代码&#xff1a; #include <bits/stdc.h> using namespace std; int a[105][105];int main() {int n,i,j;cin>>n;for(i1;i<n;i){for(j1;j<i;j){cin>>a[i][j];}}for(in-1…

服务运营 | 印第安纳大学翟成成:改变生活的水井选址

编者按&#xff1a; 作者于2023年4月在“Production and Operations Management”上发表的“Improving drinking water access and equity in rural Sub-Saharan Africa”探讨了欠发达地区水资源供应中的可达性和公平性问题。作者于2020年1月去往非洲埃塞俄比亚提格雷地区进行…

上位机图像处理和嵌入式模块部署(qmacvisual图像拼接)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 qmacvisual本身提供了图像拼接的功能。功能本身比较有意思的。大家如果拍过毕业照&#xff0c;特别是那种几百人、上千人的合照&#xff0c;应该就…

Maven的pom.xml中resources标签的用法

spring-boot-starter-parent-2.4.1.pom文件中resources标签内容如下&#xff1a; <build><resources><resource><directory>${basedir}/src/main/resources</directory><filtering>true</filtering><includes><include>…

数字化运维:引领IT运维管理的新潮流

目录 前言 数字化运维与传统运维 数字化运维是什么 数字化运维的关键技术 书本推荐 主要内容 读者对象 总结 前言 数字化转型已经成为大势所趋&#xff0c;各行各业正朝着数字化方向转型&#xff0c;利用数字化转型方法论和前沿科学技术实现降本、提质、增效&#xf…

【前端学习——js篇】6.事件模型

具体见&#xff1a;https://github.com/febobo/web-interview 6.事件模型 ①事件与事件流 事件(Events) 事件是指页面中发生的交互行为&#xff0c;比如用户点击按钮、键盘输入、鼠标移动等。在js中&#xff0c;可以通过事件来触发相应的操作&#xff0c;例如执行函数、改变…