Linux下内核空间和用户空间(32/64位)内存映射图快速掌握

一、简介

本文主要介绍内核和用户空间的物理地址到虚拟地址的映射,通过详细图解方便读者快速掌握。

二、内存空间定义

内核空间定义
内核空间是操作系统内核运行的区域,它包括了操作系统内核代码、数据结构和设备驱动程序等。内核空间通常是操作系统中的一块保护内存区域,只有操作系统内核才能够访问这个区域。
用户空间定义
用户空间是指用户应用程序运行的区域,包括用户应用程序代码、数据和堆栈等。
内核空间、用户空间的具体划分:针对 Linux 操作系统而言,最高的 1G 字节由内核使用,称为内核空间。而较低的 3G 字节由各个进程使用,称为用户空间。

以32位机器为例:
最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF);
较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF)。

三、内存权限

1、内存访问权限不同
内核空间的内存访问权限比用户空间更高,因为内核需要访问整个系统的物理资源,例如设备驱动、中断处理程序等。
2、虚拟地址映射方式不同
用户空间的虚拟地址空间是由操作系统分配和管理的,它们通过页表映射到物理地址空间。而内核空间的虚拟地址空间是由内核自己管理的,它们不需要通过页表进行映射,而是直接映射到物理地址空间。
3、运行环境不同
内核空间是操作系统内核的运行环境,用户空间是应用程序的运行环境。

四、内存空间映射图

4.1 32位系统

3G用户态,1G内核态
在这里插入图片描述

4.2 64位系统

1.用户空间:0x0000 0000 0000 0000到0x0000 ffff ffff ffff,一共有256TB。
2.非规范区域。
3.内核空间:0xffff 0000 0000 0000到Oxffff ffff ffff ffff。一共有256TB.

在这里插入图片描述

4.3 映射空间解析

(1)线性映射区域的范围是[PAGE OFFSET,264-1],起始位置是PAGE OFFSET=(0xFFFFFFFF FFFF FFFF <<(VA BITS-1)),长度是内核虛拟地址空间的一半。称为线性映射区域的原因是虚拟地址和物理地址是线性关系:
虚拟地址=((物理地址-PHYS OFFSET)+PAGE OFFSET),:其中PHYS OFFSET是内存的起始物理地址。
内核空间又由线性映射、非线性映射区(包含vmaloc区、动态映射区、固定映射区)组成,我们kmalloc()/get free_page()分配内存就是从normal直接映射区的分配一片内核空间,这片空间的内存地址便是内核态虚拟地址,与物理内存构成线性偏移关系。表面是从直接映射区内核空间分配走一片内核虚拟空间,实际在读写这片内存时,读写对应的是构成映射关系的物理内存。

(2)固定映射区域的范围是[FIXADDR_START,FIXADDR_TOP],长度是FIXADDR_SIZE,结束地址是FIXADDR_TOP=(PCI I/O START-2MB)。
固定地址是编译时的特殊虚拟地址,编译的时候是一个常量,在内核初始化的时候映射到物理地址。

(3)vmalloc区域的范围是[VMALLOC_START - VMALLOC_END),起始地址是VMALLOC_START,等于内核模块区域的结束地址,结束地址是VMALLOC_END=(PAGE_OFFSET - PUD SIZE - VMEMMAP SIZE - 64KB),其中PUD SIZE是页上级目录表项映射的地址空间的长度。
vmalloc区域是函数vmalloc使用的虚拟地址空间内核使用vmalloc分配虚拟地址连续但物理地址不连续的内存。
内核镜像在vmalloc区域,起始虚拟地址是(KIMAGE_VADDR + TEX_ OFFSET),其中

内核镜像在vmalloc区域,起始虚拟地址是(KIMAGE VADDR + TEXT_OFFSET),其中KIMAGE VADDR是内核镜像的虚拟地址的基准值,等于内核模块区域的结束地址MODULES_END;
TEXT_OFFSET是内存中的内核镜像相对内存起始位置的偏移。

内核镜像的组成:
引导加载程序(Boot Loader):负责初始化硬件、加载内核镜像等工作,是内核启动的第一步。比如常见的 GRUB(Grand Unified Bootloader),它可以从磁盘等存储设备中读取内核镜像并将其加载到内存中合适的位置。
内核代码段(Text Segment):包含了内核的可执行代码,是内核功能的核心部分,如进程调度、内存管理、设备驱动等功能的代码都在这个段中。
内核数据段(Data Segment):用于存储内核运行时需要使用的数据,像全局变量、静态变量等。例如,内核中用于记录系统状态的一些变量就存储在此。
内核 BSS 段(BSS Segment):主要用于存储未初始化的全局变量和静态变量,在系统启动时,内核会将这部分区域清零。

(4)内核模块区域的范围是[MODULES_VADDR - MODULES_END],长度是128MB,起始地址是MODULES_VADDR=(内核虚拟地址空间的起始地址 +KASAN影子区域的长度)。
内核模块区域是内核模块使用的虚拟地址空间

(5)KASAN影子区域的起始地址是内核虚拟地址空间的起始地址,长度是内核虚拟地址空间长度的1/8。
内核地址消毒剂(KernelAddress SANitizer,KASAN)是一个动态的内存错误检查工具。它为发现释放后使用和越界访问这两类缺陷提供了快速和综合的解决方案。

(6)vmemmap区域是与内存管理紧密相关的一个重要部分,以下是对其作用的详细解析:
管理物理内存描述信息:
跟踪内存状态:
vmemmap区域主要用于存储物理内存的描述信息,每个物理页面都有对应的结构体来描述其状态。例如,通过这些结构体可以记录页面是空闲的、已分配的还是正在被使用等状态信息,内核可以根据这些信息来进行内存的分配和回收操作。
记录内存属性:除了页面状态,还能记录物理内存页面的其他属性,如页面的访问权限、是否为可缓存页面等。这些属性对于内核在进行内存访问和管理时非常重要,比如在进行页面换入换出操作时,需要根据页面的属性来决定是否需要对页面进行特殊处理。

协助内存分配与回收:
伙伴系统管理:Linux 内核中的伙伴系统是用于管理物理内存分配的重要机制,vmemmap区域为伙伴系统提供了关键的数据支持。它帮助伙伴系统跟踪不同大小的内存块的分配情况,通过记录每个内存块的状态,伙伴系统可以快速找到合适大小的空闲内存块进行分配,以及在内存块释放时进行合并操作,以提高内存的利用率。
页面分配与释放:在进行页面分配时,内核会从vmemmap中查找合适的空闲页面,并将其分配给需要的进程或内核模块。当进程或内核模块使用完页面后,会将页面释放回vmemmap管理的区域,内核再根据页面的状态和相关算法,决定是否将其与相邻的空闲页面进行合并等操作,以便更好地管理内存资源。

支持内存热插拔:
检测与识别:在支持内存热插拔的系统中,vmemmap区域能够协助内核检测和识别新插入的内存或移除的内存。当有新的内存插入时,内核可以通过vmemmap来记录新内存的相关信息,并将其纳入到系统的内存管理体系中;当内存被移除时,内核也能通过vmemmap来更新内存状态,确保系统不会访问已经移除的内存区域。
资源调整:vmemmap有助于内核在内存热插拔过程中对内存资源进行调整和重新分配。例如,当插入新内存时,内核可以根据系统的负载和内存使用情况,决定是否将某些进程或数据迁移到新的内存区域,以实现更合理的内存布局和资源利用;当内存被移除时,内核需要确保正在使用该内存的进程或内核模块能够得到妥善处理,可能会进行页面换出等操作,以保证系统的正常运行。

参与内核调试与性能分析:
提供内存信息:在调试内核问题或进行性能分析时,vmemmap区域存储的信息非常有价值。开发人员可以通过查看vmemmap中的数据,了解内存的使用情况、页面的分配和回收历史等,从而帮助定位内存相关的问题,如内存泄漏、内存碎片等。
性能优化依据:vmemmap中的数据还可以作为性能优化的依据。通过分析vmemmap中记录的内存访问模式、页面换入换出频率等信息,开发人员可以对内核的内存管理算法和策略进行优化,以提高系统的整体性能。

4.2 用户空间

按功能区域划分
代码段(Text Segment)
原则:用于存储程序的可执行代码,通常是只读的,以防止程序在运行过程中意外修改自身代码。代码段在内存中是共享的,多个运行相同程序的进程可以共享同一个代码段,这样可以节省内存空间。
示例:对于一个简单的 C 语言程序,经过编译链接后生成的可执行文件中的机器码就存储在代码段中,程序运行时,这部分代码被加载到内存的代码段区域。
数据段(Data Segment)
原则:用于存储程序中已初始化的全局变量和静态变量。数据段分为初始化数据段和未初始化数据段(BSS 段),初始化数据段存储了有初始值的全局变量和静态变量,未初始化数据段则用于存储未初始化的全局变量和静态变量,在程序启动时,系统会自动将 BSS 段中的变量初始化为 0。
示例:在 C 语言中,int global_var = 10;这样的全局变量会被存储在初始化数据段,而int uninitialized_global_var;这样未初始化的全局变量会被放在 BSS 段。
堆(Heap)
原则:用于动态内存分配,由程序员通过malloc、new等函数在运行时申请和释放内存。堆的大小是可以动态增长和收缩的,它从低地址向高地址生长。堆内存的分配相对灵活,但需要程序员手动管理内存的分配和释放,否则容易出现内存泄漏等问题。
示例:在 C 语言中,使用malloc函数分配内存,如int *ptr = (int *)malloc(sizeof(int));,分配的内存就在堆上。
栈(Stack)
原则:主要用于函数调用和局部变量的存储。每当一个函数被调用时,系统会在栈顶为该函数的参数、局部变量等分配空间,函数执行结束后,这些空间会自动释放。栈从高地址向低地址生长,其大小通常是有限的,一般在几 MB 到几十 MB 之间。
示例:在函数内部定义的局部变量,如void func() { int local_var = 5; },local_var就存储在栈上。
文件映射区域(Memory-Mapped Region)
原则:通过mmap系统调用,可以将文件或设备等映射到进程的虚拟地址空间中,使得进程可以像访问内存一样访问文件或设备的数据。这种方式可以提高文件 I/O 的效率,并且支持多个进程共享文件数据。
示例:可以使用mmap函数将一个文件映射到内存中,然后直接对映射后的内存区域进行读写操作,就像操作普通内存一样

五、其他相关链接

1、关于linux下内存管理内容总结

2、Linux内核中kzalloc分配内存时用的参数GFP_KERNEL详解

3、Linux下stream内存带宽测试参数和示例详解附源码总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值