Linux内存映射原理

Posted 打工人打工魂打工人上人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内存映射原理相关的知识,希望对你有一定的参考价值。

内存映射原理

物理地址空间

  1. 处理器在系统总线上看到的地址。

  2. 使用RISC(Reduced Instruction Set Computer RISC 精简指令集)的处理器通常只实现一个物理地址空间,外围设备和物理内存使用统一的物理地址空间。有些处理器架构把分配给外围设备的物理地址区域称为设备内存。

  3. 处理器通过外围设备控制器的寄存器访问外围设备,寄存器分为控制寄存器、状态寄存器和数据寄存器三大类。外围设备寄存器通常被连续编址,分为两种方式:I/O映射方式(I/O-mapped),内存映射方式(memory-mapped)。

  4. 应用程序只能通过虚拟地址访问外设寄存器,内核提供API函数把外设寄存器的物理地址映射到虚拟地址空间。

  5. ARM64架构(物理地址宽度最大支持48位)分为两种类型:

    • 正常内存(Normal Memory):包括物理内存和只读存储器(ROM)
    • 设备内存(Device Memory):分配给外围设备寄存器的物理地址区域。

    设备内存共享属性总是外部共享,缓存属性总是不可缓存(必须绕过处理器的缓存)。

内存映射原理

内存映射即在进程的虚拟地址空间中创建一个映射,分为两种:

  1. 文件映射:文件支持的内存映射,把文件的一个区间映射到进程的虚拟地址空间,数据源是存储设备上的文件。
  2. 匿名映射:没有文件支持的内存映射,把物理内存映射到进程的虚拟地址空间,没有数据源。

两个进程可以使用共享的文件映射实现共享内存。匿名映射通常是私有映射,共享的匿名映射只可能出现在父进程和子进程之间。

在进程的虚拟地址空间中,代码段和数据段是私有的文件映射,未初始化的数据段、堆栈是私有的匿名映射。

修改过的脏页面不会立即更新到文件中,可以调用msync强制同步写入文件。

数据结构

系统调用

应用程序通常使用malloc()申请内存。glibc库的内存分配器ptmalloc使用brk或mmap向内核以页为单位申请虚拟内存,然后把页划分成小内存块分配给应用程序。默认阈值是128Kb,如果应用程序申请的内存长度小于阈值,ptmalloc分配器使用brk向内核申请虚拟内存,否则ptmalloc分配器使用mmap向内核申请虚拟内存。

应用程序可以直接使用mmap向内核申请虚拟内存。

mmap内存映射原理三个阶段:

  1. 进程启动映射过程,并且在虚拟地址空间中为映射创建虚拟映射区域;
  2. 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟的一一映射关系;
  3. 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存的拷贝)。

测试代码

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

typedef struct 
    char name[6];
    int age;
people;

int main(int argc, char *argv[]) 

    int fd, i;
    people *p_map;
    char temp;
    fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 00777);
    lseek(fd, sizeof(people) * 5 - 1, SEEK_SET);
    write(fd, "", 1);
    p_map = (people *)mmap(NULL, sizeof(people) * 10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (p_map == (void *) - 1) 
        fprintf(stderr, "mmap : %s \\n", strerror(errno));
        return -1;
    
    close(fd);
    temp = \'A\';
    for (i = 0; i < 10; i++) 
        temp = temp + 1;
        (*(p_map + i)).name[1] = \'\\0\';
        memcpy((*(p_map + i)).name, &temp, 1);
        (*(p_map + i)).age = 30 + i;
    

    printf("Initialize.\\n");
    sleep(15);
    munmap(p_map, sizeof(people) * 10);
    printf("UMA OK.\\n");
    return 0;

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


typedef struct 
    char name[6];
    int age;
people;

int main(int argc, char *argv[]) 

    int fd, i;
    people *p_map;
    char temp;
    fd = open(argv[1], O_CREAT|O_RDWR, 00777);
    lseek(fd, sizeof(people) * 5 - 1, SEEK_SET);
    write(fd, "", 1);
    p_map = (people *)mmap(NULL, sizeof(people) * 10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (p_map == (void *) - 1) 
        fprintf(stderr, "mmap : %s \\n", strerror(errno));
        return -1;
    
    close(fd);

    for (i = 0; i < 10; i++) 
        printf("name:%s age:%d\\n", (*(p_map + i)).name, (*(p_map + i)).age);
    

    munmap(p_map, sizeof(people) * 10);
    printf("UMA OK.\\n");
    return 0;


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

#define handle_error(msg) do perror(msg); exit(EXIT_FAILURE);while(0)

static char *buffer;

static void handler(int sig,siginfo_t *si,void *unused)

    printf("Get SIGSEGV at address : %p\\n",si->si_addr);
    exit(EXIT_FAILURE);


int main(int argc,char *argv[])

    int pageSize;
    struct sigaction sa;

    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa. sa_mask);
    sa.sa_sigaction = handler;

    if(sigaction(SIGSEGV,&sa,NULL) == -1)
        handle_error("siaction");

    pageSize = sysconf(_SC_PAGE_SIZE);
    if(pageSize == -1)
        handle_error("sysconf");

    buffer = memalign(pageSize, 4 * pageSize);
    if(buffer == NULL)
        handle_error("memalign");

    printf("start of region : %p\\n" ,buffer);

    if(mprotect(buffer + pageSize * 2, pageSize, PROT_READ) == -1)
        handle_error("mprotect");

    for(char *p = buffer;;)
    *(p++) = \'A\';

    printf("for completed.\\n");
    exit(EXIT_SUCCESS);

    return 0;

总结

本文主要介绍了物理地址空间,内存映射原理,文件映射到虚拟地址所用数据结构示意图,系统调用,三个函数分别给出demo。

技术参考

本文技术点出处:Linux内核源码视频系列:https://ke.qq.com/course/3294666

以上是关于Linux内存映射原理的主要内容,如果未能解决你的问题,请参考以下文章

linux内核源码分析之虚拟内存映射

Linux 内核 内存管理内存映射原理 ① ( 物理地址空间 | 外围设备寄存器 | 外围设备寄存器的物理地址 映射到 虚拟地址空间 )

共享内存实现原理

Linux 内核 内存管理内存管理系统调用 ① ( mmap 创建内存映射 | munmap 删除内存映射 | mprotect 设置虚拟内存区域访问权限 )

Linux之共享内存shm和内存映射mmap

Linux mmap原理