嵌入式Linux系统中内存分配详解

简介: 嵌入式Linux系统中内存分配详解

Linux中内存管理

内存管理的主要工作就是对物理内存进行组织,然后对物理内存的分配和回收。但是Linux引入了虚拟地址的概念。

虚拟地址的作用

如果用户进程直接操作物理地址会有以下的坏处:

1、 用户进程可以直接操作内核对应的内存,破坏内核运行。

2、 用户进程也会破坏其他进程的运行

CPU 中寄存器中存储的是逻辑地址,需要进行映射才能转化为对应的物理地址,然后获取对应的内存。

通过引入逻辑地址,每个进程都拥有单独的逻辑地址范围。

当进程申请内存的时候,会为其分配逻辑地址和物理地址,并将逻辑地址和物理地址做一个映射。

所以,Linux内存管理涉及到了以下三个部分:

1、物理内存

物理内存的组织

Linux 中内存分为 3 个级别,从下到上依次为:

1>、Page: 一个 page 的大小为 4k, Page 是内存的一个最基本的单位。

2>、Zone: Zone 中提供了多个队列来管理 page。

Zone分为 3 种

2.1、 ZONE_DMA:用来存放 DMA 读取 IO 设备的数据,内核专用

2.2、 ZONE_NORMAL:用来存放内核的相关数据,内核专用

2.3、 ZONE_HIGHMEM:高端内存,用来存放用户进程数据

3>、Node 节点,一个 CPU 对应着一个 Node,一个 Node 包括一个 Zone_DMA、 ZONE_NORMAL、ZONE_HIGHMEM。

同时当一个 CPU 对应的内存用光后,可以申请其他 CPU 对应的内存。

物理内存的分配

Linux将内存分配分为两种:

1>、大内存

大内存 利用伙伴系统 分配。

伙伴系统的做法是将 ZONE 中的 Page 分组,然后组装为多个链表。链表中存放的是 页块 的集合。页块对应着有不同的大小,分别为 1、2、4、8 … 1024个页。

当请求 (2i-1 ,2i] 大小的 page 的时候,会直接请求 2i 个页, 如果对应的链表中有对应的页块,就直接分配。如果对应的链表没有,就往上找 2i+1,如果 2i+1 存在,就将其分为 2 个 2i 页块,将其中 1 个 2i 加入到对应的链表中,将另外一个分配出去。

例如,要请求一个 128 个页的页块时,先检查 128 个页的页块链表是否有空闲块。如果没有,则查 256 个

的页块链表;如果有空闲块的话,则将 256 个页的页块分成两份,一份使用,一份插入 128 个页的页块链表中。如果还是没有,就查 512 个页的页块链表;如果有的话,就分裂为 128、128、256 三个页块,一个 128 的使用,剩余两个插入对应页块链表。

2>、小内存分配

小内存分配利用 slub 分配,比如对象等数据 slub 就是 将几个页单独拎出来作为缓存,里面维护了链表。每次直接从链表中获取对应的内存,用完之后也不用清空,就直接挂到链表上,然后等待下次利用。

2、如何组织虚拟地址

虚拟地址对应的是虚拟空间,虚拟空间只不过是一个虚拟地址的集合,用来映射物理内存。

虚拟空间分为 用户态内核态

32位系统中 将虚拟空间按照 1:3 的比例分配给 内核态用户态。

64位系统中 分别给 内核态 和 用户态 分配了 128T。

用户态结构

每个进程 都会 对应一个 用户态虚拟空间, 里面存放了 Text(代码)的内存虚拟地址范围、 Data(数据)的内存虚拟地址范围、BSS(全局变量)的内存虚拟地址范围、堆的虚拟地址范围、栈的虚拟地址范围,以及mmap 内存映射区。

其中 mmap 用于申请动态内存的时候的映射,堆和栈都是动态变化的。

一个进程对应的用户态中的 各个方面的虚拟地址信息都通过一个 struct  来存储在内存中,当创建进程的时候会为其分配内存存储对应的虚拟地址信息。

内核态结构

Linux 的内核程序共用一个内核态虚拟空间。其中分为了以下几部分:

1、直接映射区

896M,内核空间直接映射到对应的ZONE_DMA和ZONE_NORMAL中。为什么叫做直接映射呢?逻辑地址 直接 减去对应的差值就可以得到对应的物理地址。固定死了。

2、动态映射

为什么要引入动态映射呢?因为所有物理内存的分配都需要内核程序进行申请,用户进程没有这个权限。所以内核空间一定要能映射到所有的物理内存地址。

那么如果都采用直接映射的话,1G大小逻辑地址的内核空间只能映射1G大小的物理内存。

所以引入了动态映射,动态映射就是 内核空间的逻辑地址可以映射到 物理内存中的ZONE_HIGHMEM(高端内存)中的任何一个地址,并且在对应的物理内存使用完之后,可以再映射其他物理内存地址。

动态映射分为三种:

1>、动态内存映射: 使用完对应的物理内存后,就可以映射其他物理内存了。

2>、永久内存映射: 一个虚拟地址只能映射一个物理地址。如果需要映射其他物理地址,需要解绑。

3>、固定内存映射: 只能被某些特定的函数来调用引用物理地址。

动态内存映射和直接映射的区别

动态映射和直接映射的区别就是逻辑地址到物理地址的转化规则。

直接映射

直接映射的规则是死的,一个逻辑地址对应的物理地址是固定的。通过逻辑地址加或者减去一个数,就可以得到对应的物理地址。

动态映射

动态映射是动态的绑定,每个逻辑地址对应的物理地址是动态的,通过页表进行查询。

用户空间映射:

用户空间采用动态映射,每个虚拟地址可以被映射到一个物理地址,映射到ZONE_HIGHMEM。

为什么用户空间不采用直接映射呢?

因为物理内存是多个进程所有的,每个进程都有一个用户空间。如果采用直接映射的话,对应的物理地址是会冲突的。其用户空间的逻辑地址大小都为 3G,所以存在逻辑地址相同,但是对应的物理地址不同。需要通过页表来转化,一个进程会对应一个页表。

3、如何将虚拟地址映射到物理内存

虚拟地址通过 页表虚拟地址 转化为 物理地址,每个进程都对应着一个页表,内核只有一个页表。

虚拟空间 和 物理内存 都按照 4k 来分页,一个虚拟空间中的页 和 物理内存中页 是 一一对应的。

页表映射

如上图所示,将虚拟地址中的页号 通过页表转化为 对应的物理页号,然后通过页内偏移量 就可以得到对应的 物理地址了。

但是 1 个进程就需要一个页表,一个 4G 的内存条,就需要 1M 个页表记录来描述,假如 1 个 页表记录需要 4个字节,那么就需要 4MB。而且页表记录是通过下标来对应的,通过虚拟页号来乘以对应的页表项大小来计算得到对应的地址的。

所以 Linux 将 4M 分为 1K 个 4K, 一个 4K 对应着一个 page,用来存储对应的真正的页表记录。将 1K 个 page 分开存放,就不要求连续的 4M 了。

如果将 4M 分成 1K 个离散的 page 的话,怎么虚拟地址对应的页表号呢?

利用指针,存储 1K 个地址,分别指向这 1K 个 page, 地址的大小为 4 个字节,也就是32位,完全可以表示整个内存的地址范围。

1K * 4个字节,正好是一个 page 4k,所以 也就是利用 1 个 page来存储对应的页表记录索引。

所以 我们的虚拟地址寻找过程如下:

1>、找到对应的页表记录索引位置,因为有 1K 个索引,所以用 10 位就可以表示了

2>、通过索引可以找到对应的真正的页表地址,对应的有 1K 个页表记录,所以用 10 位就可以表示了

3>、1个页有 4K,通过 12 位就可以表示其页内偏移量了。

所以虚拟地址被分为了三部分:

1>、10位 表示索引偏移

2>、10位 表示页表记录偏移

3>、 12位 表示页内偏移

虽然这种方式增加了索引项,进一步增加了内存,但是减少了连续内存的使用,通过离散的内存就可以存储页表。

这是对于32位系统,而 64 位系统采用了5级页表。

映射流程图

用户态申请内存时,只会申请对应的虚拟地址,不会直接为其分配物理内存,而是等到真正访问内存的时候,产生缺页中断,然后内核才会为其分配,然后为其建立映射,也就是建立对应的页表项。

TLB

TLB 就是一个缓存,放在 CPU 中。用来将虚拟地址和对应的物理地址进行缓存。当查询对应的物理地址的时候,首先查询 TLB,如果TLB中存在对应的记录,就直接返回。如果不存在,就再去查询页表。

虚拟内存

虚拟内存 指的是 将硬盘中划出一段 swap 分区 当作 虚拟的内存,用来存放内存中暂时用不到的内存页,等到需要的时候再从 swap 分区中 将对应的内存页调入到 内存中。硬盘此时相当于一个虚拟的内存。

从逻辑上能够运行更大内存的程序,因为程序运行的时候并不需要把所有数据都加载到内存中,只需要将当前运行必要的相关程序和数据加载到内存中就可以了,当需要其他数据和程序的时候,再将其调入。

相较于真正的内存加载,虚拟内存需要将数据在内存和磁盘中不断切换,这是一个耗时的操作,所以速度比不上真正的内存加载。

总结

虚拟空间 和 物理内存 都分为 内核空间 和 用户空间。

虚拟地址需要通过页表转化为物理地址,然后才能访问。

用户虚拟空间 只能映射 物理内存中的用户内存,无法映射到物理内存中的内核内存,也就是说,用户进程只能操作用户内存。

内核空间 只能被 内核 申请使用,用户进程只能操作用户空间的物理内存和虚拟空间。

当用户进程 调用系统调用的时候,会将其对应的代码和数据运行在内核空间中。

所以当调用 内核空间 读取文件或者网络数据的时候,首先会将数据拷贝到内存空间,然后在将数据从内核空间拷贝到用户空间。因为 用户进程不能访问内核空间。

目录
相关文章
|
3天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
18 3
|
3天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
16 2
|
3天前
|
安全 网络协议 Linux
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。通过掌握 ping 命令,读者可以轻松测试网络连通性、诊断网络问题并提升网络管理能力。
18 3
|
6天前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
23 6
|
6天前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
30 6
|
7天前
|
机器学习/深度学习 自然语言处理 Linux
Linux 中的机器学习:Whisper——自动语音识别系统
本文介绍了先进的自动语音识别系统 Whisper 在 Linux 环境中的应用。Whisper 基于深度学习和神经网络技术,支持多语言识别,具有高准确性和实时处理能力。文章详细讲解了在 Linux 中安装、配置和使用 Whisper 的步骤,以及其在语音助手、语音识别软件等领域的应用场景。
33 5
|
7天前
|
监控 网络协议 算法
Linux内核优化:提升系统性能与稳定性的策略####
本文深入探讨了Linux操作系统内核的优化策略,旨在通过一系列技术手段和最佳实践,显著提升系统的性能、响应速度及稳定性。文章首先概述了Linux内核的核心组件及其在系统中的作用,随后详细阐述了内存管理、进程调度、文件系统优化、网络栈调整及并发控制等关键领域的优化方法。通过实际案例分析,展示了这些优化措施如何有效减少延迟、提高吞吐量,并增强系统的整体健壮性。最终,文章强调了持续监控、定期更新及合理配置对于维持Linux系统长期高效运行的重要性。 ####
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
366 0
|
21天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
44 1
|
26天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。