Linux内核内存布局与堆管理

一、Linux内核内存布局

        64位Linux一般使用48位来表示虚拟地址空间,45位表示物理地址。通过命令: cat /proc/cpuinfo。查看Linux内核位数和proc文件系统输出系统软硬件信息如下:

Linux内核内存布局与堆管理_第1张图片

        x86架构体系内核分布情况:

Linux内核内存布局与堆管理_第2张图片

一、物理内存相关

  1. MemTotal
    系统物理内存总量(单位:KB),包含已使用和未使用的所有内存。

  2. MemFree
    未被使用的物理内存,即当前完全空闲的内存空间。

  3. MemAvailable
    系统可实际使用的内存,不仅包含 MemFree,还计算了可回收的缓存(如 CachedSReclaimable 等),更贴近程序实际可用内存的真实情况。

  4. Buffers
    块设备(如硬盘、SSD)的缓冲区内存,用于临时存储磁盘写入数据。

  5. Cached
    文件缓存内存,用于缓存从磁盘读取的文件数据,加速后续读取操作。

二、交换空间(Swap)相关

  1. SwapTotal
    交换空间(虚拟内存)的总大小。

  2. SwapFree
    剩余可用的交换空间。

  3. SwapCached
    被交换空间缓存的内存页,即曾换出到 Swap 又被重新访问、暂时缓存的数据。

三、内存活动状态分类

  1. Active
    活跃内存页,近期被频繁访问的内存(如正在运行程序的内存)。

  2. Inactive
    非活跃内存页,近期未被频繁访问的内存,可能被系统回收。

  3. Active(anon) / Inactive(anon)
    “匿名内存”(如进程堆、栈)的活跃 / 非活跃状态。

  4. Active(file) / Inactive(file)
    与文件相关的内存(如文件缓存)的活跃 / 非活跃状态。

四、其他内存统计

  1. Unevictable
    不可回收的内存页,如被锁定的内存。

  2. Mlocked
    通过 mlock() 系统调用锁定、禁止换出到 Swap 的内存。

  3. Zswap
    压缩交换空间使用的内存(若启用 Zswap 压缩技术)。

  4. Dirty
    等待写入磁盘的脏数据占用的内存。

  5. Writeback
    正在写入磁盘的回写数据占用的内存。

  6. AnonPages
    匿名内存页(如进程堆、栈、未映射文件的内存)。

  7. Mapped
    映射文件(如通过 mmap() 映射的文件)占用的内存。

  8. Shmem
    共享内存(如 tmpfs 挂载的临时文件系统、进程间共享内存)。

  9. Slab
    内核缓存对象(如目录项、索引节点)占用的内存,包含可回收和不可回收部分。

  10. SReclaimable
    Slab 中可回收的内存(如缓存的目录项、索引节点)。

  11. SUnreclaim
    Slab 中不可回收的内存(如内核固定使用的结构)。

         Linux内核动态内存分配通过系统接口实现:

                alloc_pages/__get_free_page:以页为单位分配 vmalloc:以字节为单位分配虚拟地址连续的内存块 kmalloc:以字节为单位分配物理地址连续的内存块,它是 以 slab 为中心。

        我们可以通过vmalloc分配的内存将它统计输出,具体如下:

Linux内核内存布局与堆管理_第3张图片

1. 地址范围

每行开头的 0xffff... 是虚拟地址区间,格式为 起始地址 - 结束地址,表示 vmalloc 分配的虚拟内存范围。例如 0xffffface34000000-0xffffface34001000,代表一段连续的虚拟地址空间。

2. 数字标识(如 8192、20480 等)

这些数字是 页数量,Linux 中每页默认大小为 4KB。通过页数量可计算分配的内存大小。例如:

 
  • 8192 pages:对应 8192 × 4KB = 32MB
  • 20480 pages:对应 20480 × 4KB = 80MB

3. 分配用途描述

如 bpf_prog_alloc_no_statsgen_pool_adddup_task_struct 等,标识该内存分配的内核组件或功能场景。例如:

 
  • bpf_prog_alloc_no_stats:与 eBPF(扩展 Berkeley 包过滤)程序分配相关;
  • dup_task_struct:与进程任务结构体复制相关。

4. pages=

明确标注分配的页数量,与前面的数字含义一致,用于直观展示内存分配的页单位数量。

5. vmalloc NO=

分配序号,用于唯一标识每一次 vmalloc 分配操作,方便内核追踪和管理不同的内存分配请求。        

    

    ARM64 架构采用 48 位物理寻址方式,最大可寻找 256TB 的物理地址空间。对于目前应用完全足够,不需要扩展到 64 位。虚拟地址也同样最大支持 48 位寻址。Linux 内核在大多数体系结构上将地址空间划为:用户空间和内核空间。

        用户空间(User space):0x0000_0000_0000_0000 至 0x0000_FFFF_FFFF_FFFF。
        内核空间(Kernel space):0xFFFF_0000_0000_0000 至 0xFFFF_FFFF_FFFF_FFFF。

Linux内核内存布局与堆管理_第4张图片

KASAN(影子区):它是一个动态检测内存错误的工具,原理利用额外的内存标记可用内存的状态(将 1/8 内存作为影 子区)

modules:内核模块使用的虚拟地址空间

vmalloc:vmalooc 函数使用的虚拟地址空间

.text:代码段

.init:模块初始化数据

.data:数据段

.bss:静态内存分配段

fixed:固定映射区域

PCI I/O:PCI 设备的 I/O 地址空间

vmemmap:内存的物理地址如果不连续的话,就会存在内 存空洞(稀疏内存)

vmemmap 就用来存放稀疏内存的 page 结构体的数据的虚拟地址空间

memory:线性映射区域 

 

二、堆管理

        堆是进程中主要用于动态分配变量和数据的内存区域,堆的管理对应程序员不是直接可见的。malloc 和内核之间的经典接口是 brk 系统调用,负责扩展 / 收缩堆。
堆是一个连续的内存区域,在扩展时自下至上增长。其中 mm_struct 结构,包含堆在虚拟地址空间中的起始和当前结束地址 (start_brk 和 brk)。

Linux内核内存布局与堆管理_第5张图片

 Linux中有两个方法可以创建堆:

  • brk () 是系统调用,实际是设置进程数据段的结束地址,将数据段的结束地址向高地址移动。
#include   
#include   
#include   

int main() {  
    // 获取当前 brk 地址  
    void* original_brk = sbrk(0);  
    if (original_brk == (void*)-1) {  
        perror("sbrk");  
        return 1;  
    }  

    // 扩展堆,分配 1024 字节  
    void* alloc_addr = sbrk(1024);  
    if (alloc_addr == (void*)-1) {  
        perror("brk");  
        return 1;  
    }  
    printf("Allocated with brk: %p\n", alloc_addr);  

    // (演示收缩,实际场景中较少手动收缩堆)  
    if (brk(original_brk) == -1) {  
        perror("brk shrink");  
        return 1;  
    }  
    return 0;  
}  
  • mmap () 向操作系统申请一段虚拟地址空间(使用映射到某个文件)。当不用此空间来映射到某个文件时,这块空间称为匿名空间,可以用来作为堆空间。
#include   
#include   
#include   

int main() {  
    // 使用 mmap 分配 1024 字节匿名空间  
    void* mmap_addr = mmap(  
        NULL,           // 让内核决定映射地址  
        1024,           // 映射大小(字节)  
        PROT_READ | PROT_WRITE,  // 读写权限  
        MAP_PRIVATE | MAP_ANONYMOUS,  // 匿名、私有映射  
        -1,             // 不关联文件描述符  
        0               // 偏移量  
    );  
    if (mmap_addr == MAP_FAILED) {  
        perror("mmap");  
        return 1;  
    }  
    printf("Allocated with mmap: %p\n", mmap_addr);  

    // 使用完后释放内存  
    if (munmap(mmap_addr, 1024) == -1) {  
        perror("munmap");  
        return 1;  
    }  
    return 0;  
}  

per-CPU计数器,引入它来加速SMP系统上的计数器操作,Linux内核源码如下:

Linux内核内存布局与堆管理_第6张图片

1. per - CPU 计数器作用

在对称多处理(SMP)系统中,多个 CPU 核心可同时执行任务,若多个 CPU 核心同时对同一计数器进行操作(如递增、递减等),会因竞争同一资源引发锁争用问题,导致性能下降。per - CPU 计数器为每个 CPU 核心分配独立计数器实例,各 CPU 核心操作自己的计数器实例,减少锁争用。比如统计系统中网络接收数据包数量,每个 CPU 核心可记录自己处理的数据包数,最后汇总得到总数,加快计数操作速度,提升系统性能。

2. 使用方法

  • 定义与初始化:先定义percpu_counter结构体变量。通常在内核模块初始化代码或相关子系统初始化时,调用percpu_counter_init函数初始化计数器,指定初始计数值等参数 。示例代码:
#include 
struct percpu_counter my_counter;
void my_module_init(void)
{
    percpu_counter_init(&my_counter, 0); // 初始值设为0
}
 
  • 计数操作:在需要计数的地方,如某个事件发生需递增计数时,使用percpu_counter_inc等函数。每个 CPU 核心操作自己的本地计数器副本。示例:
void some_event_occurred(void)
{
    percpu_counter_inc(&my_counter);
}
 
  • 获取计数值:要获取计数器总值时,调用percpu_counter_read函数,它会汇总所有 CPU 核心的本地计数器值。示例:
s64 total_count = percpu_counter_read(&my_counter);
 
  • 销毁计数器:在内核模块退出或相关子系统清理时,调用percpu_counter_destroy函数释放计数器占用资源。示例:
void my_module_exit(void)
{
    percpu_counter_destroy(&my_counter);
}

 https://github.com/0voice

你可能感兴趣的:(linux)