Linux内存管理

参考:深入分析LINUX内核源码

深入分析Linux内核源码 (kerneltravel.net)

存储器是一种必须仔细管理的重要资源。在理想的情况下,每个程序员都喜欢无穷大、 快速并且内容不易变(即掉电后内容不会丢失)的存储器,同时又希望它是廉价的。但不幸的是,当前技术没有能够提供这样的存储器,因此大部分的计算机都有一个存储器层次结构, 即少量、快速、昂贵、易变的高速缓存(cache);若干兆字节的中等速度、中等价格、易变的主存储器(RAM);数百兆或数千兆的低速、廉价、不易变的磁盘。如图 6.1 所示,这些资源的合理使用与否 ,直接关系着系统的效率。

Linux 内存管理是内核最复杂的任务之一。主要是因为它用到许多 CPU 提供的功能,而且和这些功能密切相关。正因为如此,我们在第二章较详细地介绍了 Intel 386 的段机制和页机制,可以说这是进行内存管理分析的物质基础 。这一章用大量的篇幅描述了 Linux 内存管理所涉及到的各种机制,如内存的初始化机制、地址映射机制、请页机制、交换机制、内存分配和回收机制、缓存和刷新机制以及内存共享机制,最后还分析了程序的创建和执行。

Linux 的内存管理概述

Linux 是为多用户多任务设计的操作系统,所以存储资源要被多个进程有效共享;且由于程序规模的不断膨胀,要求的内存空间比从前大得多。 Linux 内存管理的设计充分利用了 计算机系统所提供的虚拟存储技术,真正实现了虚拟存储器管理。

第二章介绍的 Intel 386 的段机制和页机制是 Linux 实现虚拟存储管理的一种硬件平台。实际上, Linux 2.0 以上的版本不仅仅可以运行在 Intel 系列个人计算机上,还可以运行在 Apple 系列、DEC Alpha 系列、MIPS 和 Motorola 68k 等系列上, 这些平台都支持虚拟存储器管理,我们之所以选择 Intel 386,是因为它具有代表性和普遍性。

Linux 的内存管理主要体现在对虚拟内存的管理。我们可以把 Linux 虚拟内存管理功能概括为以下几点:

• 大地址空间;

• 进程保护;

• 内存映射;

• 公平的物理内存分配;

• 共享虚拟内存。

关于这些功能的实现,我们将会陆续介绍。

Linux 虚拟内存的实现结构 我们先从整体结构上了解 Linux 对虚拟内存的实现结构,如图 6.2 所示。

从图 6.2 中可看到实现虚拟内存的组成模块。其实现的源代码大部分放在/mm 目录下。

(1)内存映射模块(mmap):负责把磁盘文件的逻辑地址映射到虚拟地址,以及把虚拟地址映射到物理地址。

(2)交换模块(swap):负责控制内存内容的换入和换出,它通过交换机制,使得在物 理内存的页面(RAM 页)中保留有效的页 ,即从主存中淘汰最近没被访问的页,保存近来访问过的页。

(3)核心内存管理模块(core):负责核心内存管理功能,即对页的分配、回收、释放 及请页处理等,这些功能将被别的内核子系统(如文件系统)使用。

(4)结构特定的模块:负责给各种硬件平台提供通用接口,这个模块通过执行命令来改变硬件 MMU 的虚拟地址映射,并在发生页错误时,提供了公用的方法来通知别的内核子系统。 这个模块是实现虚拟内存的物理基础。

内核空间和用户空间

从第二章我们知道,Linux 简化了分段机制,使得虚拟地址与线性地址总是一致,因此, Linux 的虚拟地址空间也为 0~4G 字节。Linux 内核将这 4G 字节的空间分为两部分。将最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF),供各个进程使用,称为“用户空间”。因为每个进程可以通过系统调用进入内核,因此,Linux 内核由系统内的所有进程共享。 于是,从具体进程的角度来看,每个进程可以拥有 4G 字节的虚拟空间。图 6.3 给出了进程虚拟空间示意图。

Linux 使用两级保护机制:0 级供内核使用,3 级供用户程序使用。从图 6.3 中可以看出, 每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的。最高的 1G 字节虚拟内核空间则为所有进程以及内核所共享。

1.虚拟内核空间到物理空间的映射

内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。读者会问,系统启动时,内核的代码和数据不是被装入到物理内存吗?它们为什么也处于虚拟内存中呢?这和编译程序有关,后面我们通过具体讨论就会明白这一点。

2.内核映像

在下面的描述中,我们把内核的代码和数据就叫内核映像(Kernel Image)。当系统启动时,Linux 内核映像被安装在物理地址 0x00100000 开始的地方,即 1MB 开始的区间(第 1M 留作它用)。然而,在正常运行时, 整个内核映像应该在虚拟内核空间中,因此,连接程序在连接内核映像时,在所有的符号地址上加一个偏移量 PAGE_OFFSET,这样,内核映像在内核空间的起始地址就为 0xC0100000。 例如,进程的页目录 PGD(属于内核数据结构)就处于内核空间中。在进程切换时,要将寄存器 CR3 设置成指向新进程的页目录 PGD,而该目录的起始地址在内核空间中是虚地址, 但 CR3 所需要的是物理地址,这时候就要用__pa()进行地址转换。在 mm_context.h 中就有这 么一行语句:

asm volatile(“movl %0,%%cr3”: :”r” (__pa(next->pgd));

这是一行嵌入式汇编代码,其含义是将下一个进程的页目录起始地址 next_pgd,通过 __pa()转换成物理地址,存放在某个寄存器中,然后用 mov 指令将其写入 CR3 寄存器中。经过这行语句的处理,CR3 就指向新进程 next 的页目录表 PGD 了。

内容太多,具体参考开头连接里的pdf。

暂略

KASan(Kernel Address Sanitizer)是一个强大的内存错误检测工具,以下是关于它的详细介绍:

  1. 功能

    1. 核心功能:主要用于检测 Linux 内核中的内存越界访问和使用已释放内存等问题。通过额外的 shadow memory(影子内存)标记内存状态,在运行时对内存操作进行监控,及时发现并报告内存错误。例如,当程序试图访问未分配的内存区域或已经释放的内存时,KASan 能够准确识别并发出警报。

    2. 支持的架构:支持多种架构,包括 x86_64 和 arm64 等。不同的架构在具体的实现细节上可能会有所差异,但基本原理都是利用影子内存技术来检测内存错误。

  2. 原理

    1. 影子内存机制:将一部分物理内存(通常为总内存的 1/8)作为影子内存,每个字节对应实际内存中的若干字节。使用特殊的 “magic num” 填充影子内存,在每一次内存的 load/store 操作时,同时检查对应的影子内存来确定操作是否合法。例如,连续 8 bytes 内存(8 bytes align)使用 1 byte 影子内存标记,根据影子内存中的值来判断这 8 个字节的内存访问是否有效。

    2. 编译器插入代码:在编译时,编译器会在内存访问指令前插入相应的检查函数调用,如 __asan_load##size()__asan_store##size() 函数。这些函数会检查影子内存的状态,以确定内存访问是否合法。如果发现不合法的内存访问,就会触发错误报告机制。

  3. 使用方法

    1. 配置与编译:需要在编译内核时进行配置和编译。首先要确保选择的内核代码版本高于 4.0(因为 KASan 在 Linux 内核 4.0 版本被引入)。然后使用 make menuconfigmake nconfig 命令配置内核,选择启用 KASan 的相关选项,如 “KASAN: runtime memory debugger” 和 “KASAN: inline instrumentation (EXPERIMENTAL)” 等。最后进行内核编译和安装。

    2. 启动参数:在启动内核时,需要添加启动参数 kasan=1 来启用 KASan。

    3. 测试与验证:Linux 内核的源码中包含了针对 KASan 的测试代码(位置在 linux/lib/test_kasan.c),可以编译内核或者单独编译该模块来生成测试文件,然后通过插入测试模块等方式进行测试和验证。

  4. 优缺点

    1. 优点:能够有效地检测出内存错误,帮助开发者快速定位和修复问题,提高内核的稳定性和安全性;对于不同类型的内存错误都有较好的检测能力,包括显式和隐式的越界访问、使用已释放的内存等;提供了详细的错误报告,包含堆栈信息、错误类型和相关内存地址等,便于调试。

    2. 缺点:会增加一定的内存和 CPU 开销,特别是在大规模的内核代码或高频率的内存访问场景下可能会有显著的性能影响;目前只支持部分架构,对于其他架构的内核无法使用;虽然功能较为强大,但对于一些复杂的内存错误情况,可能还需要结合其他工具进行进一步的分析和诊断。

综上所述,KASan工具是一种功能强大且有效的动态内存错误检测工具,它通过影子内存技术来监控Linux内核中的内存访问行为,从而快速准确地发现并报告潜在的内存错误。尽管存在一些性能开销和架构限制,但其优点仍然使其成为提升内核稳定性和安全性的重要手段之一。

Linux内核自带MMU功能吗?

Linux内核自带MMU(内存管理单元)功能,但需要硬件支持才能发挥作用。以下是具体介绍:

  1. 内核中的MMU机制

    1. 虚拟地址到物理地址的转换:Linux内核通过MMU来实现虚拟地址到物理地址的转换。当应用程序请求内存时,它使用的是虚拟地址,而MMU负责将这些虚拟地址转换为实际的物理地址,以便CPU能够正确地访问内存中的数据。这一过程是通过页表来实现的,页表中存储了虚拟地址与物理地址之间的映射关系。

    2. 内存保护:MMU还提供了内存保护的功能。它可以设置不同内存区域的访问权限,例如只读、可读写等,以防止一个进程对另一个进程的内存进行非法访问或修改,从而提高系统的安全性和稳定性。

    3. 实现虚拟内存:虚拟内存是Linux内核的一个重要特性,它允许系统使用硬盘空间来模拟额外的RAM,从而扩展了可用的内存空间。MMU在实现虚拟内存的过程中起着关键作用,它将磁盘上的页面与内存中的页面进行映射,使得程序可以使用比实际物理内存更大的地址空间。

  2. 硬件对MMU的支持

    1. 处理器的支持:并非所有的处理器都具有MMU功能。一些简单的嵌入式处理器可能没有MMU,因此在这些处理器上运行的Linux内核无法完全利用MMU的功能,可能需要采用其他方式来实现内存管理。而大多数常见的处理器,如x86系列、ARM系列等,都具备MMU功能,可以很好地支持Linux内核的内存管理机制。

    2. 开启MMU功能:对于具有MMU功能的处理器,通常还需要在BIOS或系统启动配置中启用MMU功能。在Linux系统启动过程中,内核会检测处理器是否支持MMU,并根据配置决定是否启用MMU相关的功能。

综上所述,Linux内核确实集成了MMU功能,但这一功能的发挥高度依赖于底层硬件的支持。没有硬件MMU的配合,Linux内核虽仍能运行,但将无法充分利用其内存管理的优势,特别是在需要高效内存管理和隔离的场景下。

如果芯片不支持MMU,跑Linux的时候,还能进行虚拟地址的管理吗?

如果芯片不支持MMU,Linux内核仍然可以在一定程度上管理虚拟地址,但会采用一些替代方法来实现内存管理。以下是具体介绍:

  1. 使用软件模拟

    1. 页表管理:在没有MMU的情况下,操作系统可以在软件层面维护一个页表或类似的数据结构来跟踪虚拟地址到物理地址的映射关系。当进程访问虚拟地址时,操作系统通过查找页表来确定对应的物理地址,并进行相应的地址转换。不过,这种方式完全由软件实现,会导致地址转换的速度较慢,因为每次地址转换都需要在页表中进行查找和计算。

    2. 内存保护:虽然MMU通常负责内存保护,但在没有MMU的情况下,操作系统可以通过在软件层面检查进程对内存的访问权限来实现一定的内存保护。例如,在进程访问内存之前,先检查该进程是否有权限访问目标内存区域,如果没有权限则阻止访问,并产生相应的错误或异常。

  2. 限制功能

    1. 简化内存管理模型:没有MMU的支持,Linux内核可能无法实现复杂的内存管理特性,如页式存储管理、虚拟内存等。此时,内核可能会采用更简单的内存管理模型,例如将整个物理内存划分为多个固定大小的区域,每个进程分配一个或多个这样的区域来使用。这种简化的模型可能会导致内存利用率较低,并且难以实现进程之间的内存隔离和共享。

    2. 减少多任务支持:由于缺乏有效的内存管理和保护机制,支持多任务处理的能力也会受到限制。在没有MMU的情况下,同时运行多个进程可能会导致它们之间的内存冲突和干扰,影响系统的稳定性和可靠性。因此,系统可能只能同时运行有限的几个任务,或者需要对任务的运行进行严格的控制和管理。

综上所述,尽管没有MMU的支持会增加系统的复杂性和降低性能,但通过软件层面的优化和调整,仍然可以实现一定程度的虚拟地址管理和内存保护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值