首页 > 系统相关 >内存管理相关——malloc,mmap,mlock与unevictable列表

内存管理相关——malloc,mmap,mlock与unevictable列表

时间:2024-10-31 20:50:52浏览次数:3  
标签:malloc unevictable mmap 内存 include shm mlock

一、背景

之前的内核内存子系统的章节里已经介绍了内存回收有关的MIGRATE_TYPE的概念并做了不少的相关实验。详细见我之前写的博客 内存回收相关内核基础概念——MIGRATE_TYPE_kreclaimable 没有回收-CSDN博客

锁内存相关的常用函数有四个,SetPageReserved/mlock/get_user_pages/pin_user_pages。这四个函数里除了mlock是用户态的函数,其他三个都是内核态的函数。这篇博客主要讲的是锁内存常用函数里的前两个,主要讲的是mlock系统调用及其内核的实现逻辑,在讲mlock之前,会先讲到与mlock有关的一个unevictable列表。讲到unevictable列表时,也会讲到有哪些情形会加入到这个unevictable列表。在做实验时也会做一些MIGRATE_TYPE相关的实验,主要是为了确定其内存分配用的是那种MIGRATE_TYPE类型。

为什么要把这些锁内存有关的函数先拿出来来讲,一方面,这些函数在我们底层内核态开发的过程中比较常用(mlock则是用户态里与内存处理相关的常用的函数),相比基本不会去动到的内存回收逻辑,这些函数的使用还是平时容易接触到的,另外,这些函数的底层逻辑也影响了内核的内存回收逻辑该如何处理这些特殊标记的内存,而内存回收逻辑也是内核内存子系统里的避不掉的大课题,也是这个内存子系统这个系列博客的终极课题。

本文对于锁内存的函数只会介绍SetPageReserved/mlock这两个函数,关于get_user_pages/pin_user_pages请关注后面的博客。

本文在介绍mlock前还先介绍了malloc的内核部分的实现,介绍mlock的同时也介绍malloc,可以把相关的实现细节联系起来,更方便理解和记忆。

在介绍malloc时,不可避免会涉及到brk系统调用和mmap系统调用,博客里会展开他们俩的实现,另外,对于mmap会展开描述得详细一些,也会涉及一些与之相关的例子和优化项。这里面还有关联的细节内容并没有全部塞在这一篇博客里,会在后面的内核内存子系统章节里继续涉及。

二、unevictable列表

unevictable是不可驱逐的意思,unevictable相关的功能添加了一个额外的LRU列表,用于跟踪不可驱逐的页,并将这些页隐藏在vmscan之外。

需要强调的是unevictable不是一个用于LRU排序的列表,更不是一个MIGRATE_TYPE类型,而是与LRU排序的列表(匿名/文件,活动/非活动)相配合。

每个node都有一个unevictable列表,以及一个相关的unevictable页标志位PG_unevictable,用于进行不可驱逐页的管理。

2.1 为什么要有unevictable列表

举个例子,一个x86_64平台,拥有128GB的主内存,单个node上有3200万个4k页,如果这些页因为某种原因变成不可驱逐的话,那么vmscan会花费大量时间扫描lru列表。我们需要这个unevictable列表把这些mlock等的页抽出来。关于mlock的细节见第三章。

2.1.1 温故一下MIGRATE_TYPE的概念,以及"不可以回收但可以移动"类型

我们再来理一下概念,MIGRATE_TYPE是free_area的pages的属性,在内存已经分配出来以后就确定了,后面在内存回收时会根据MIGRATE_TYPE的类型决定能不能做回收或者移动,UNMOVABLE是既不能移动也不能回收,RECLAIMABLE是不能移动但可以回收,MOVABLE是既可以移动也可以回收。可以发现MIGRATE_TYPE里并没有不可以回收但可以移动的这个类型,而mlock恰好满足了这一点,事实上,mlock如下面的内核文档描述,就是可以确保不可以被回收(不会被交换到swap分区或者像RECLAIMABLE一样直接回收),但是可以被移动的。这个逻辑借助了PG_mlocked属性,注意,这个PG_mlocked要和PG_locked区分,PG_locked是页锁,PG_mlocked才是标志mlock的page。而mlock一般就是对于申请MOVABLE类型的内存来进行锁定,除了标志PG_mlocked,如上面描述,内核还增加了刚才说的unevictable列表为了回收管理时的高效避免遍历时遇到过多的PG_mlocked的页面。

关于MIGRATE_TYPE在内存管理里的层级,之前的博客也有一张图,这张图的展示更加清晰一点(暂且可以忽略ZONE_HIGHMEM和ZONE_MOVABLE这两个zone类型):

2.2 四种情形会加入到unevictable列表

包括mlock在内,以下几种情况会被加入到unevictable列表:

2.2.1 ramfs拥有的页会被加入到unevictable列表

下图是在ramfs里新增了一个文件

2.2.2 noswap方式mount的tmpfs会被加入到unevictable列表

2.2.3 "shm的shmlock_example进行shmctl SHM_LOCK的锁定"会被加入到unevictable列表

与shmctl SHM_LOCK相关的这套shm的api比较老,这套api创建和使用的shm,可以用ipcs -m和ipcrm -m <shmid>来查看当前系统上的shm有哪些和进行删除

ipcs -m

下图种看到的shmid是全局id

ipcrm -m <shmid>可以删除<shmid>全局id对应的shm

shmlock_example.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>

#define SIZE 409600

int main() {
#if 1
    //key_t key = ftok("shmfile1", 66); // 创建唯一的键 执行这一步会导致shmget的size不能超过4096,具体原因还>没查
    int shmid = shmget(0, SIZE, 0777 | IPC_CREAT); // 创建共享内存
    //printf("errno=%d, errmsg=%s\n", errno, strerror(errno));
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    printf("shmid = %d\n", shmid);
#endif
    //int shmid = 6;
    void *ptr = shmat(shmid, NULL, 0); // 将共享内存附加到进程

    if (ptr == (void *)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    // 锁定共享内存
    if (shmctl(shmid, SHM_LOCK, NULL) == -1) {
        perror("shmctl SHM_LOCK");
        exit(EXIT_FAILURE);
    }

    // 写入数据到共享内存
    sprintf(ptr, "Hello, Shared Memory with shmctl!");

    // 读取数据
    printf("Data from shared memory: %s\n", (char *)ptr);

    getchar();

    int i;
    void *ptraddone = ptr + 1;
    printf("ptraddone - ptr = %llx\n", (unsigned long long)ptraddone - (unsigned long long)ptr);
    for (i = 0; i < SIZE; i++) {
        *((char*)(ptr + i)) = 0;
    }

    getchar();

    // 解锁共享内存
    if (shmctl(shmid, SHM_UNLOCK, NULL) == -1) {
        perror("shmctl SHM_UNLOCK");
        exit(EXIT_FAILURE);
    }

    // 解除共享内存映射
        if (shmdt(ptr) == -1) {
        perror("shmdt");
        exit(EXIT_FAILURE);
    }

    // 删除共享内存
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl IPC_RMID");
        exit(EXIT_FAILURE);
    }

    return 0;
}

这里例子里有两个getchar打断

第一个getchar前是创建共享内存并进行shmctl的SHM_LOCK

第二个getchar是对所有内存数据进行操作来触发缺页中断

最后return会把相关资源全部释放

这套接口和下面3.2里的shm_open的创建在/dev/shm/**下的shm的接口对比而言,

相同点是:

这两套api都需要主动释放,否则会占用物理内存

不同点是:

这套api在shmctl SHM_LOCK相比mlock而言,它不会像mlock那样主动触发缺页中断把物理内存都分配出来并锁住,shmctl SHM_LOCK它不会把物理内存分配出来

2.2.4 映射到VM_LOCKED的VMAs会被加入到unevictable列表

这包含了所有调用mlock的场景,包含shm里使用mlock的场景

2.2.5 两个相关疑问

关于上面提到的加入到unevictable的四种情况有关的,有两个相关疑问:

2.2.5.1 tmpfs和ramfs的关系

tmpfs是ramfs的改进版本,不设置noswap不会的话不会强制加入unevictable,普通用户虽然和ramfs一样不能mount,但是可以回写tmpfs上的文件。

2.2.5.2 为什么mlock的操作内核把相关page移到了unevictable里去,而PageReserved的页没有移到unevictable呢?

我的理解是PageReserved的行为相对较少,使用的大小总量也相对较少,一些与DMA相关或者与硬件设备相关的buffer需要映射到用户态及其他驱动用相关的逻辑才会用到该PageReserved。而mlock的行为是用户态可以控制的,而且容易出现大块的mlock,因为lock的page量可能会很大,所以有必要为其建一个unevictable列表来管理

2.3 四种会加入unevictable列表的情形里分别用的是哪种MIGRATE_TYPE

2.3.1 ramfs和tmpfs(默认或者noswap)用的是哪种MIGRATE_TYPE

首先需要明确一下所谓unevictable并不是一个MIGRATE_TYPE,只是有一个unevictable的lru list,在需要加入unevictable里去的时候,需要把page从lru链表里挪出到unevictable列表

做了实验以后,结果如下总结:

2.3.1.1 ramfs用的是unmovable的free page

dd写文件前:

dd写文件后:

2.3.1.2 tmpfs(默认的)用的是movable的free page

dd写文件前:

dd写文件后:

2.3.1.3 tmpfs(noswap方式)和tmpfs(默认的)一样,用的是movable的free page

dd写文件前:

dd写文件后:

2.3.1.4 shm的使用和tmpfs一样,用的是movable的free page

测试程序:

下面测试程序有两个getchar,为了的是方便对比看情况,第一个getchar前,只是open了一个共享内存,mmap出来,但是没有使用内存,所以测试可以发现它这时候并不会占用物理内存,第一个getchar之后,到第二个getchar等待按键输入时是可以看到,物理内存因为调用了mlock,主动触发了缺页中断,导致物理内存这时候就分配出来了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>           // For O_* constants
#include <sys/mman.h>       // For shm_open, mmap
#include <unistd.h>         // For close
#include <sys/stat.h>       // For mode constants
#include <sys/types.h>
#include <errno.h>

int main() {
    const char *name = "/my_shm"; // 共享内存的名称
    const size_t size = 1024*1024*1024;      // 共享内存的大小
    int shm_fd;                    // 共享内存文件描述符
    void *ptr;                     // 指向共享内存的指针

    // 创建共享内存对象
    shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        exit(EXIT_FAILURE);
    }

    // 调整共享内存的大小
    if (ftruncate(shm_fd, size) == -1) {
        perror("ftruncate");
        exit(EXIT_FAILURE);
    }

    // 将共享内存映射到进程的地址空间
    ptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    getchar();

    // 锁定共享内存区域中的页
    if (mlock(ptr, size) != 0) {
        perror("mlock");
        exit(EXIT_FAILURE);
    }

    getchar();

    // 写入数据到共享内存
    sprintf((char*)ptr, "Hello, Shared Memory!");

    // 读取数据
    printf("Data from shared memory: %s\n", (char *)ptr);

    // 解锁共享内存区域
    if (munlock(ptr, size) != 0) {
        perror("munlock");
        exit(EXIT_FAILURE);
    }

    // 解除映射
    if (munmap(ptr, size) == -1) {
        perror("munmap");
        exit(EXIT_FAILURE);
    }

    // 关闭共享内存对象
    if (close(shm_fd) == -1) {
        perror("close");
        exit(EXIT_FAILURE);
    }

    // 删除共享内存对象
    if (shm_unlink(name) == -1) {
        perror("shm_unlink");
        exit(EXIT_FAILURE);
    }

    return 0;
}

在按第一个按键前,可以看到movable的free还有很多

按了一次按键以后,也就是执行了mlock之后,可以看到movable的free少了非常多

说明是用MOVABLE的migrate_type来申请的

2.3.2 tmpfs会占用free -h里的shared,也会占用free -h里的buff/cache

现在大部分的share memory的使用最终都是用的tmpfs文件系统,这个实验是为了搞清楚,常规的share memory的内存的使用,会被统计到哪些系统观测的数据里去

我们发现tmpfs会占用free -h里的shared,也会占用free -h里的buff/cache:

在/dev/shm下,在dd文件前:

在/dev/shm下,在dd 1g的文件后:

所以,我们大体上可以通过buff/cache减去shared来得到有对应硬盘文件的page cache的占用

另外,需要注意的是:

echo 3 > /proc/sys/vm/drop_caches

对上图中shared的统计没有变化(无论是有swap分区和没有swap分区我都测过),当然也是很好理解的,因为shm的内存实际上是在内存上的,不像pagecache那样有地方落盘和存储,shm的内存是在DDR上的,没有落盘的地方,自然就腾不出内存来

另外,该shared项的部分,在cat /proc/meminfo里的Shmem里会有统计,但是在Kxx和Sxx部分都不计算在内,只有一些因为shm新增内容而导致的管理变量占了一些空间算在了Kxx和Sxx里,如下图:

在/dev/shm下dd前:

在/dev/shm下dd后:

三、malloc的内核实现

在介绍mlock前,我们先介绍另外一个用户态的常用函数malloc的内核部分实现,这部分内核实现不少细节和mlock里的实现是相关联的,另外,对malloc的实现进行拆解可以帮助我们理解内核内存分配的机制和原理。

malloc在用户态时,会按照小块内存和大块内存两种情况,走的不同的分叉,调用了不同的系统调用。小块内存分配调用的是brk系统调用,大块内存分配调用的是mmap系统调用,我们分别来阐述。

下面的阐述基于的内核版本是5.19.17

3.1 malloc的brk场景

malloc小块内存触发的brk系统调用:

SYSCALL_DEFINE1(brk, unsigned long, brk)

    如果新边界地址小于旧边界地址,那么表示进程请求释放空间,调用do_munmap来释放这一部分内存

    find_vma查找当前用户进程中是否已经有一块VMA和start_brk重叠

    如果没有找到现成的vma,则用do_brk_flags继续分配vma,do_brk_flags函数如下:

        调用get_unmapped_area传入flags参数是MAP_FIXED表示使用指定的虚拟地址对应的空间

        调用munmap_vma_range,实际上是调用了find_vma_links函数遍历用户进程红黑树中的VMA,根据start地址来查找最合适插入红黑树的节点,最终rb_link指针指向最合适节点的rb_left或rb_right指针地址;范围0表示查找到了最合适插入的节点;若范围-ENOMEM表示和现有的VMA重叠,这时候需要调用do_munmap来释放这段重叠的空间

        调用vma_merge来检查有没有办法合并addr附近的VMA。如果没有办法合并,那么只能新创建一个VMA

        新建一个VMA并填充内容包括使用vm_get_page_prot这个架构相关的函数来根据VM_READ|VM_WRITE|VM_EXEC|VM_SHARED的组合标志位通过protection_map转换成与架构相关的标志位参数

            这些标志位参数目前还不会设置到硬件页表中去,在缺页异常处理函数里分配物理内存并设置PTE和PTE的属性

        调用vma_link把VMA添加到进程的红黑树中

    如果应用程序调用过mlockall把进程中全部进程虚拟地址空间加锁,那么这个mm->def_flags就包含了VM_LOCKED,这时候就需要用mm_populate来立刻分配物理内存(这块逻辑和下面要说的mlock逻辑部分重叠)

3.2 malloc的mmap场景

再来看一下malloc的调用触发的mmap的场景:

当使用fd=-1,且flags=MAP_ANONYMOUS | MAP_PRIVATE时,创建的mmap是私有匿名映射。

(私有匿名映射通用用于内存分配,私有文件映射通常用于加载动态库)

glibc分配大内存块时,大于MMAP_THREASHOLD 128kb,glibc会默认使用mmap代替brk来分配内存

mmap/mmap2

    do_mmap2

        ksys_mmap_pgoff

            vm_mmap_pgoff

                do_mmap

                    根据MAP_XXX属性转换成VM_XXX属性,比如根据MAP_SHARED转换成VM_SHARED

                        转换函数还有calc_vm_flag_bits VM_LOCKED和MAP_LOCKED的转换

在计算要传入mmap_region的vm_flags的参数时也或上了mm->def_flags

                     mmap_region

                         检查是否与已映射的部分重叠,如果有则进行munmap——在3.2.2一节里会举例

                         调用vma_merge尝试与附近的VMA合并

                         如果可以合并直接返回地址

                         如果不可以合并

                             则分配一个新的VMA

                                 如果有传入file,则进行文件映射 这里的文件并不一定对应于磁盘上的文件

                                     比如DMA-BUF接口也有传fd给到上层做mmap,也会传入文件,但是需要定义这个文件对应的ops包括.mmap这个ops;另外,shm_open再mmap的方式也是传入file的

                                 如果没有传入file,则判断是否是共享映射,判断方式就是vm_flags & VM_SHARED

                                     使用shmem_zero_setup建立一个共享匿名映射(父子进程共享)

                                 剩下的,那就是匿名私有映射 vma_set_anonymous(vma->vm_ops=NULL)

                           使用vma_link把VMA插入mm系统中

                           在返回地址前,做一些统计,如果标记了VM_LOCKED,那也要记录locked_vm的统计

                       在do_mmap返回地址前,判断vm_flags和flags,如果需要锁定,则设置*populate=len

                   根据do_mmap返回的populate变量,如果true则调用mm_populate来立刻分配物理内存

3.2.1 关于mmap如果带上MAP_POPULATE/MAP_LOCKED所会调用到的__get_user_pages的一些细节

mmap带MAP_POPULATE/MAP_LOCKED,以及mlock以及内核代码里直接调用get_user_pages/pin_user_pages都时会最终调用到__get_user_pages里去的,但是他们调用进去时的传参是不一样的,get_user_pages会带上FOLL_GET,pin_user_pages会带上FOLL_PIN。

但是mmap带MAP_POPULATE/MAP_LOCKED,以及mlock在调用__get_user_pages时并不会传入FOLL_GET或FOLL_PIN,这就会让__get_user_pages里的逻辑并不会增加引用计数

get_user_pages/pin_user_pages虽然没有像mlock调用,把lru移到unevictable列表里,但是它比mlock锁定得更加强大

mlock只是能确保内存不会被swap掉,但是不能确保内存不会被迁移,也就是说,mlock后,相应的物理内存还可以是发生变化的,它保证的是有对应物理内存,并不保证对应物理内存不发生变更。而get_user_pages/pin_user_pages则能保证既不会被swap掉,也不会发生变更。

有关get_user_pages/pin_user_pages如何让系统不会将它swap掉或者migrate掉,以及更多的一些细节,关注我后面内核内存子系统相关的博文

3.2.2 mmap_region会监测要映射的地址是否与已有地址重叠,如果重叠会先进行munmap

在3.2里,有阐述mmap_region这个关键函数,这个函数会检查待映射的地址是否与已有地址重叠,如果重叠会先进行munmap

例子:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    // 第一次调用 mmap,映射 819200 字节
    void *addr1 = mmap((void *)0x20000000, 819200, PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
    if (addr1 == MAP_FAILED) {
        perror("mmap failed");
        exit(EXIT_FAILURE);
    }
    printf("第一次 mmap 返回地址: %p\n", addr1);

    // 对映射的内存进行写入
    memset(addr1, 'A', 819200);

    // 第二次调用 mmap,映射 4096 字节到相同的地址
    void *addr2 = mmap((void *)0x20000000, 4096, PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
    if (addr2 == MAP_FAILED) {
        perror("第二次 mmap 失败");
        exit(EXIT_FAILURE);
    }
    printf("第二次 mmap 返回地址: %p\n", addr2);

    // 对第二次映射的内存进行写入
    memset(addr2, 'B', 4096);

    // 验证第一次映射的内容
    printf("第一次 mmap 的内容: %c\n", *((char *)addr1));

    // 释放内存
    munmap(addr1, 819200);
    munmap(addr2, 4096);

    return 0;
}

运行后发现,第二次mmap并没有失败:

这是因为mmap_region会进行如下调用

find_vma_links里会检查要映射的区域是否和已有的VMA有重叠,如果有重叠则返回-ENOMEM,于是在munmap_vma_range里会进行do_munmap

3.2.3 mmap之后优化预读和顺序读

调用madvise(add, len, MADV_WILLNEED | MADV_SEQUENTIAL)可能会对文件内容进行预读和顺序读,这有利于提高磁盘读性能。

这两个都需要指定addr和len,MADV_WILLNEED 会立刻启动磁盘I/O进行预读,这个MADV_WILLNEED比较适合内核很预测就要预读哪些内容,如随机读场景,这不太适合流媒体播放场景,MADV_SEQUENTIAL则比较适合这个场景,另外通过修改内核默认预读窗口也可以做到,如下操作:

blockdev --getra /dev/sda

四、mlock

mlock是一个系统调用,上面的章节也提到了,mlock一旦锁住内存,lru会被迁移到unevictable列表,另外,mlock还会把相关的物理内存主动触发一遍缺页异常从而把传入mlock的addr开始的size的这段虚拟地址对应的物理内存都分配出来。

下图就是一个mlock_example.c的代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define PAGE_SIZE 4096 // 定义页面大小

int main() {
    // 分配一页内存
    void *ptr = malloc(PAGE_SIZE);
    if (ptr == NULL) {
        perror("malloc");
        return EXIT_FAILURE;
    }

    // 初始化内存
    memset(ptr, 0, PAGE_SIZE);

    // 锁定内存
    if (mlock(ptr, PAGE_SIZE) != 0) {
        perror("mlock");
        free(ptr);
        return EXIT_FAILURE;
    }

    printf("Memory locked. Press Enter to unlock...\n");
    getchar(); // 等待用户输入

    // 解锁内存
    if (munlock(ptr, PAGE_SIZE) != 0) {
        perror("munlock");
        free(ptr);
        return EXIT_FAILURE;
    }

    printf("Memory unlocked.\n");

    // 释放内存
    free(ptr);
    return EXIT_SUCCESS;
}

用的malloc申请,申请了4k,mlock以后,会锁住两个page

说明一旦跨越多个page,mlock调用会都锁住

另外,从上图中可以看到,mlock的page会被纳入到unevictable列表的统计

3.1 mlock系统调用的内核实现

mlock的主要代码处理流程如下:

SYSCALL_DEFINE2(mlock, unsigned long, start, size_t, len)

    do_mlock(start, len, VM_LOCKED)

        apply_vma_lock_flags

            根据start和len找到所属的vm_area_struct

            把传入的flag或上原有的vma_area_struct的vm_flags成员变量

        __mm_populate 填充物理页 这往下都是在mm/gup.c里,gup是get user page的意思

            populate_vma_page_range

                __get_user_pages 这个函数和2.2.3要讲的部分相关,这个函数是一个循环

                    follow_page_mask 查看传入的vma中的虚拟页面是否已经分配了物理内存,同时也会根据传入的FOLL_XX的标志来增加引用计数,也就是get_user_pages和pin_user_pages所增加的引用计数

                     如果没有找到,则调用faultin_page函数

                         继而调用handle_mm_fault来人为触发一个缺页异常

                     执行flush_anon_page和flush_dcache_page来进行page的高速缓存刷新

                         这两个函数和arch实现有关

                     next_page标签,每次操作一个page,做下一个page的操作的准备

  

另外mlock针对VMA添加VMA_LOCKED属性,针对VMA映射的page添加PG_mlocked属性,注意,这个PG_mlocked户型要和PG_locked进行区分,PG_locked是页锁。lock_page函数用于申请页锁,如果页锁被其他进程占用了,那么它会睡眠等待。

本文已经有比较多的内容了,为了不搅浑在一起,所以把follow_page_mask函数和缺页异常有关的handle_mm_fault函数放到后续的内核内存子系统的博文里去展开

 四、SetPageReserved

SetPageReserved函数可以讲的内容并不多,就放到这篇博客里和mlock一起讲了。

一般驱动编写内核代码,涉及到DMA或者泛mmap操作时,都会调用SetPageReserved来“预留”内存。

在之前的博客里,我们已经做了实验确认了kmalloc和alloc_pages这两种常用的分配方式如果用的GFP_KERNEL的最常用的这个标志来分配,分配出来的内存是MIGRATE_UNMOVABLE类型的,所以,在系统做内存回收时,是不会动到他的,另外,kmalloc和alloc_pages是立马分配出物理内存的,也不涉及滞后的缺页异常才占用物理内存的情况,所以理应用GFP_KERNEL进行的kmalloc和alloc_pages分配出来的内存都不用SetPageReserved来进行内存保留(当然以往万一,去设一下,只要在释放时记得ClearPageReserved的话,也未尝不可)

对于vmalloc分配出来的内存的场景,在64位系统上(没有所谓高端内存的系统),如果是默认的GFP_KERNEL分配的话,也是会直接分配出物理内存,理论上也不需要再调用SetPageReserved来预留,当然如果不是默认的GFP_KERNEL所标志的UNMOVABLE内存,这样的预留还是有必要的。

当然,很多代码都应该是要兼容32位的,在32位系统上,没有做相关占用实验,在代码里可以搜到大量的ed主要是针对泛vmalloc分配出来的内存,用于泛mmap或DMA时所使用,如下图,做了SetPageReserved的保留:

3.1 SetPageReserved的设置不会挪动lru到unevictable列表

意思就是,设置SetPageReserved并不会把分配内存时所在的类别

SetPageReserved的设置既不会挪动lru到unevictable列表(下图是insmod一个alloc_pages然后SetPageReserved的ko):

标签:malloc,unevictable,mmap,内存,include,shm,mlock
From: https://blog.csdn.net/weixin_42766184/article/details/143365311

相关文章

  • c语言:动态内存管理中的malloc和free,calloc和realloc
    为什么要有动态内存分配?通过之前的学习,我们已经掌握的内存开辟方式有:inta=20;//在栈空间上开辟四个字节chararr[10]={0};//在栈空间上开辟10个字节的连续空间上述空间的开辟的大小是固定的数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小不能进行调整。......
  • C++ 内存管理 堆和栈、内存泄漏、内存分配、指针与内存、智能指针、malloc和free、new
    1.堆和栈的区别1.**管理方式**:-**栈**:自动管理。当函数调用时,局部变量会自动分配在栈上。函数执行完毕后,这些变量会自动释放。-**堆**:手动管理。程序员需要使用`new`来在堆上分配内存,并在不再需要时使用`delete`来释放。2.**使用方式和寿命**:-**栈**:用......
  • 谈一谈 Netty 的内存管理 —— 且看 Netty 如何实现 Java 版的 Jemalloc
    本文基于Netty4.1.112.Final版本进行讨论在之前的Netty系列中,笔者是以4.1.56.Final版本为基础和大家讨论的,那么从本文开始,笔者将用最新版本4.1.112.Final对Netty的相关设计展开解析,之所以这么做的原因是Netty的内存池设计一直在不断地演进优化。在4.1.52.Final......
  • EnumMap
    EnumMap是一种基于枚举类型的Map实现,它具有非常高的性能和可读性。EnumMap的定义如下所示:public class EnumMap<K extends Enum<K>,V> extends AbstractMap<K,V>implements java.io.Serializable, CloneableEnumMap的主要特点如下:EnumMap是一种基于枚举类型的Map实......
  • 用malloc申请空间的开辟和free空间的释放
    malloc的头文件是#include<stdlib.h>malloc开辟的空间虽然和数组一样可以调用,并且都是连续存放的,但是他们所在的位置不一样malloc开辟的空间位置在堆区;局部数组是把数据存在栈区;动态内存空间的大小可以调整;如果申请成功,会返回开辟好的空间的起始地址;如果开辟失败,会返回NU......
  • 使用mmap()创建内存映射
    系统调用(mmap和munmap)mmap内存映射类型mmap()系统调用用于在调用进程的虚拟地址空间中创建内存映射,主要分为两种类型:文件映射:将文件的一部分直接映射到虚拟内存中,允许通过内存访问文件内容,映射的分页会在需要时自动加载匿名映射:没有对应文件,分页初始化为0,可以视为一......
  • malloc底层实现以及和new的比较
    背景:前几天去面试,被问到了一个问题:“malloc的底层实现是怎样的?怎样防止内存碎片?”当时答的不够好,现在再整理一下。(本文档通过收集整理网上博客而来。先挖个坑,等有时间了去看一下《深入理解操作系统》的第九章虚拟内存,再重新整理一篇)内存布局Linux中每个进程都有自己的虚拟地......
  • 43 C 程序动态内存分配:内存区域划分、void 指针、内存分配相关函数(malloc、calloc、re
    目录1 C程序内存区域划分1.1代码区(CodeSection)1.2全局/静态区(Global/StaticSection)1.3栈区(StackSection)1.4 堆区(HeapSection)1.5动态内存分配2void指针(无类型指针)2.1void指针介绍2.2void指针的作用2.3void指针的特点2.4 void指针类......
  • redis安装致命错误jemalloc/jemalloc.h
    安装报错在安装redis的时候,执行 make&&makeinstall发生以下错误解决方案 其实可以读一下redis里的Readme.md文件,我截图了其中的部分,使用makeMALLOC=libc&makeinstall Redis指令记录 DBSIZECONFIGGETdatabases ......
  • C语言-动态内存管理(malloc、calloc、realloc)
    目录1.内存布局2.动态内存函数2.1malloc2.1.1malloc是什么2.1.2如何用​编辑2.2free2.2.1free是什么2.2.2如何用2.3calloc2.3.1calloc是什么2.4realloc2.4.1realloc是什么2.4.2realloc如何使用2.4.3realloc可以实现与malloc同样的功能3.常见的动态......