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

news2024/11/15 1:42:58

目录

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.coloradmin.cn/o/1549309.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数字图像处理matlab系列】数组索引

【数字图像处理matlab系列】数组索引 【先赞后看养成习惯】【求点赞+关注+收藏】 MATLAB 支持大量功能强大的索引方案,这些索引方案不仅简化了数组操作,而且提高了程序的运行效率。 1. 向量索引 维数为1xN的数组称为行向量。行向量中元素的存取是使用一维索引进行的。因此…

STM32收发HEX数据包

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

Anaconda虚拟环境的介绍与使用

Anaconda虚拟环境介绍 虚拟环境是一种工具&#xff0c;可以让您在同一台计算机上创建多个独立的Python环境&#xff0c;每个环境可以拥有自己的Python解释器版本、包和依赖项。使用虚拟环境有以下几个优点&#xff1a; 隔离项目环境&#xff1a; 在开发中&#xff0c;不同的项…

使用LangChain LCEL生成RAG应用、使用LangChain TruLens对抗RAG幻觉

# 导入LangChain的库 from langchain import *# 加载数据源 loader WebBaseLoader() doc loader.load("https://xxx.html")# 分割文档对象 splitter RecursiveCharacterTextSplitter(max_length512) docs splitter.split(doc)# 转换文档对象为嵌入&#xff0c;并…

3d放上模型为什么渲染不出来---模大狮模型网

如果在3D软件中放置模型后无法正确渲染出来&#xff0c;可能有几个常见的原因导致这种情况发生&#xff1a; 材质设置问题&#xff1a;确保所放置的模型具有正确的材质和纹理&#xff0c;并且材质设置正确。如果材质设置有误&#xff0c;可能会导致模型无法正确显示。 光照设置…

BaseDao封装JavaWeb的增删改查

目录 什么是BaseDao&#xff1f; 为什么需要BaseDao&#xff1f; BaseDao的实现逻辑 什么是BaseDao&#xff1f; Basedao 是一种基于数据访问对象&#xff08;Data Access Object&#xff09;模式的设计方法。它是一个用于处理数据库操作的基础类&#xff0c;负责封装数据库…

大模型论文阅读:ADAPTIVE BUDGET ALLOCATION FOR PARAMETEREFFICIENT FINE-TUNING

大模型论文阅读:ADAPTIVE BUDGET ALLOCATION FOR PARAMETEREFFICIENT FINE-TUNING 论文链接:https://arxiv.org/pdf/2303.10512v1.pdf 当存在大量下游任务时,微调所有预训练模型的参数变得不可行。因此,为了以参数高效的方式学习预训练权重的增量更新,提出了许多微调方法,…

【Linux】进程状态(R运行状态、S睡眠状态、D磁盘休眠状态、T停止状态、X死亡状态)

目录 01.运行状态 02.睡眠状态 03.磁盘睡眠状态 04.停止状态 05.死亡状态 进程的状态会随着操作系统的调度和外部事件的发生而不断地发生转换。例如&#xff0c;一个新创建的进程经过初始化后会进入就绪态&#xff0c;等待被调度执行&#xff1b;当调度器分配处理器资源给…

AI+云平台|全闪云底座迎战

AI融万物之势席卷而来 人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 行业特点 AI场景中80%以上是小文件&#xff0c;以非结构化数据为…

maya导入导出bvh 自动 脚本

目录 maya打开脚本编辑器 运行打开bvh脚本 maya导出bvh脚本 maya打开脚本编辑器 打开Maya软件,点击右下角 “脚本编辑器” 运行打开bvh脚本<

一款比 K8S 更好用的编排工具——Nomod 单机部署

上下文 最近公司需要调研类似 EMCHub 这样支持算力共享的服务。第一直觉是使用 K8S 或 K3S&#xff0c;作为 CNCF 孵化的顶级项目&#xff0c;同时也是当前云原生生态使用最广的编排系统。但是在学习 EMC Hub 源码过程中&#xff0c;偶然发现它是基于 Nomad 做的集群管理。 相…

如何清理释放群晖客户端缓存?

任正菲说&#xff1a;企业最大的浪费&#xff0c;是经验的浪费&#xff01; 而一个一个的经验&#xff0c;又都来自企业的每一个工作者。 因此当我们在工作过程中遇到一些问题时&#xff0c;我们就应该下意识的把解决问题的经验沉淀下来&#xff0c;从而可以与大家进行分享。…

耳目一新的滑块版登录注册界面~

又到了毕业季&#xff0c;大家做毕设的时候总会参考已有的案例&#xff0c;不过大多产品的样式非常单一雷同。本帖博主给大家分享一个比较别树一帜的登录界面&#xff0c;如下&#xff1a; 如果没有账号&#xff0c;点击“去注册”&#xff0c;则会产生如下的效果&#xff1a; …

django orm DateTimeField 6位小数精度问题

from django.db.backends.mysql.base import DatabaseWrapperDatabaseWrapper.data_types[DateTimeField] "datetime"意思就是重写源码里面的DateTimeField字段

如何在家中使用手机平板电脑 公司iStoreOS软路由实现远程桌面

文章目录 简介一、配置远程桌面公网地址二、家中使用永久固定地址 访问公司电脑**具体操作方法是&#xff1a;** 简介 软路由是PC的硬件加上路由系统来实现路由器的功能&#xff0c;也可以说是使用软件达成路由功能的路由器。 使用软路由控制局域网内计算机的好处&#xff1a…

C++ 控制语句(一)

一 顺序结构 程序的基本结构有三种&#xff1a; 顺序结构、分支结构、循环结构 大量的实际问题需要通过各种控制流程来解决。 1.1 顺序结构 1.2 简单语句和复合语句 二 循环 2.1 for循环 语句流程图 注意&#xff1a;使用for语句的灵活性 三 while语句 四 do while语句

线程 和 进程详解——以Java为例

一、概念 线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运作单位。 简单理解&#xff1a;应用软件中互相独立&#xff0c;可以同时运行的功能 进程 进程是程序的基本执行实体。 多线程中的两个概念&#xff1a;并发和并行…

鸿蒙OS开发实例:【工具类封装-emitter组件间通信】

import Emitter from ohos.events.emitter; import pasteboard from ohos.pasteboard; MyEmitterUtil 是一个针对 HarmonyOS 的事件驱动编程封装类&#xff0c;主要用于组件间的通信和数据传递。 使用要求&#xff1a; DevEco Studio 3.1.1 Release 或更高版本API 版本&…

vue3+threejs新手从零开发卡牌游戏(十六):初始化对方手牌

添加对方手牌区时注意位置调整&#xff0c;以及手牌应该是背面朝上&#xff0c;加个rotateX翻转即可&#xff0c;其他代码和p1.vue代码一致&#xff0c;game/hand/p2.vue代码如下&#xff1a; <template><div></div> </template><script setup lan…

C++:关键字(4)

在c中的关键字就是我们各个写的各种代码 这些就是关键字&#xff0c;这些东西是无法当参数的&#xff0c;比如在给变量名设置为int那就不行 这就是个错的 在写其他的参数时候&#xff0c;不可以使用关键词作为参数