一文搞定 mmap 内存映射原理

news/2024/7/20 13:32:59 标签: linux, mmap, 内存映射, 内存管理

1. mmap介绍

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享

2. Linux内存管理介绍

结构体定义

在Linux操作系统中,一切皆文件,所有概念都被通过文件描述符进行抽象建模来进行标识,内存管理也是如此,如下是Linux中进程、内存管理的结构描述。

进程结构体(task_struct)

在task_struct结构体中,进程地址空间通过以下成员变量指向mm_struct即内存空间,每个进程空间都有自身的内存空间。

    struct mm_struct *mm, *active_mm;

内存结构体(mm_struct)

在mm_struct内存结构体中,通过两种数据结构来表示vm_area_struct内存空间,当内存空间较少时采用链表进行管理,当内存空间较多时会采用红黑树进行管理。下面是mm_struct的类结构,内容较多,为了保证完整性没有做删减聚焦。

struct mm_struct {

    //指向线性区对象的链表头
    struct vm_area_struct * mmap;       /* list of VMAs */
    //指向线性区对象的红黑树
    struct rb_root mm_rb;
    //指向最近找到的虚拟区间
    struct vm_area_struct * mmap_cache; /* last find_vma result */

    //用来在进程地址空间中搜索有效的进程地址空间的函数
    unsigned long (*get_unmapped_area) (struct file *filp,
                unsigned long addr, unsigned long len,
                unsigned long pgoff, unsigned long flags);

       unsigned long (*get_unmapped_exec_area) (struct file *filp,
                unsigned long addr, unsigned long len,
                unsigned long pgoff, unsigned long flags);

    //释放线性区时调用的方法,          
    void (*unmap_area) (struct mm_struct *mm, unsigned long addr);

    //标识第一个分配文件内存映射的线性地址
    unsigned long mmap_base;        /* base of mmap area */


    unsigned long task_size;        /* size of task vm space */
    /*
     * RHEL6 special for bug 790921: this same variable can mean
     * two different things. If sysctl_unmap_area_factor is zero,
     * this means the largest hole below free_area_cache. If the
     * sysctl is set to a positive value, this variable is used
     * to count how much memory has been munmapped from this process
     * since the last time free_area_cache was reset back to mmap_base.
     * This is ugly, but necessary to preserve kABI.
     */
    unsigned long cached_hole_size;

    //内核进程搜索进程地址空间中线性地址的空间空间
    unsigned long free_area_cache;      /* first hole of size cached_hole_size or larger */

    //指向页表的目录
    pgd_t * pgd;

    //共享进程时的个数
    atomic_t mm_users;          /* How many users with user space? */

    //内存描述符的主使用计数器,采用引用计数的原理,当为0时代表无用户再次使用
    atomic_t mm_count;          /* How many references to "struct mm_struct" (users count as 1) */

    //线性区的个数
    int map_count;              /* number of VMAs */

    struct rw_semaphore mmap_sem;

    //保护任务页表和引用计数的锁
    spinlock_t page_table_lock;     /* Protects page tables and some counters */

    //mm_struct结构,第一个成员就是初始化的mm_struct结构,
    struct list_head mmlist;        /* List of maybe swapped mm's.  These are globally strung
                         * together off init_mm.mmlist, and are protected
                         * by mmlist_lock
                         */

    /* Special counters, in some configurations protected by the
     * page_table_lock, in other configurations by being atomic.
     */

    mm_counter_t _file_rss;
    mm_counter_t _anon_rss;
    mm_counter_t _swap_usage;

    //进程拥有的最大页表数目
    unsigned long hiwater_rss;  /* High-watermark of RSS usage */、
    //进程线性区的最大页表数目
    unsigned long hiwater_vm;   /* High-water virtual memory usage */

    //进程地址空间的大小,锁住无法换页的个数,共享文件内存映射的页数,可执行内存映射中的页数
    unsigned long total_vm, locked_vm, shared_vm, exec_vm;
    //用户态堆栈的页数,
    unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
    //维护代码段和数据段
    unsigned long start_code, end_code, start_data, end_data;
    //维护堆和栈
    unsigned long start_brk, brk, start_stack;
    //维护命令行参数,命令行参数的起始地址和最后地址,以及环境变量的起始地址和最后地址
    unsigned long arg_start, arg_end, env_start, env_end;

    unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

    struct linux_binfmt *binfmt;

    cpumask_t cpu_vm_mask;

    /* Architecture-specific MM context */
    mm_context_t context;

    /* Swap token stuff */
    /*
     * Last value of global fault stamp as seen by this process.
     * In other words, this value gives an indication of how long
     * it has been since this task got the token.
     * Look at mm/thrash.c
     */
    unsigned int faultstamp;
    unsigned int token_priority;
    unsigned int last_interval;

    //线性区的默认访问标志
    unsigned long flags; /* Must use atomic bitops to access the bits */

    struct core_state *core_state; /* coredumping support */
#ifdef CONFIG_AIO
    spinlock_t      ioctx_lock;
    struct hlist_head   ioctx_list;
#endif
#ifdef CONFIG_MM_OWNER
    /*
     * "owner" points to a task that is regarded as the canonical
     * user/owner of this mm. All of the following must be true in
     * order for it to be changed:
     *
     * current == mm->owner
     * current->mm != mm
     * new_owner->mm == mm
     * new_owner->alloc_lock is held
     */
    struct task_struct *owner;
#endif

#ifdef CONFIG_PROC_FS
    /* store ref to file /proc/<pid>/exe symlink points to */
    struct file *exe_file;
    unsigned long num_exe_file_vmas;
#endif
#ifdef CONFIG_MMU_NOTIFIER
    struct mmu_notifier_mm *mmu_notifier_mm;
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
    pgtable_t pmd_huge_pte; /* protected by page_table_lock */
#endif
    /* reserved for Red Hat */
#ifdef __GENKSYMS__
    unsigned long rh_reserved[2];
#else
    /* How many tasks sharing this mm are OOM_DISABLE */
    union {
        unsigned long rh_reserved_aux;
        atomic_t oom_disable_count;
    };

    /* base of lib map area (ASCII armour) */
    unsigned long shlib_base;
#endif
};

虚拟内存区域结构体(vm_area_struct)

struct vm_area_struct { 
 /*公共的, 与vma类型无关的 */ 
 struct mm_struct * vm_mm; 
 //标记内存区域开始位置
 unsigned long vm_start; 
    //标记内存区域结束位置
 unsigned long vm_end; 
 //标记下一个vm_area_struct内存区域的引用
 struct vm_area_struct *vm_next; 
 //内存页区域保护方式
 pgprot_t vm_page_prot; 
 //内存页区域特性标记
 unsigned long vm_flags; 
 
 //实现红黑树存储
 short vm_avl_height; 
 struct vm_area_struct * vm_avl_left; 
 struct vm_area_struct * vm_avl_right; 
 struct vm_area_struct *vm_next_share; 
 struct vm_area_struct **vm_pprev_share; 
 
 /* 与类型相关的 */ 
 struct vm_operations_struct * vm_ops; 
 unsigned long vm_pgoff; 
 struct file * vm_file; 
 unsigned long vm_raend; 
 void * vm_private_data;
};

vm_area_struct可以说是虚拟内存的最小结构单元,主要通过vm_start、vm_end表示内存空间的起止位置,通过vm_prot表示内存区域保护方式,通过vm_next表示下一块内存空间引用,通过vm_flags表示内存空间区域的特性,等等。Linux提供的mmap函数方法会影响它们。

相关视频推荐

2024,彻底搞懂计算机的底层原理,linux内核源码分析教程,六大模块全面分析(内存管理、进程管理、设备驱动、网络协议栈、文件系统、中断管理及基础)icon-default.png?t=N7T8https://www.bilibili.com/video/BV1GT4y1t7Hs/

免费学习地址:Linux C/C++开发(后端/音视频/游戏/嵌入式/高性能网络/存储/基础架构/安全)

需要C/C++ Linux服务器架构师学习资料加qun579733396获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

页表管理

Linux Kernel 使用内存管理的时候,采取的是页式的管理方式,应用程序给出的内存地址是虚拟地址,是经过若干层的页表的转换才能得到真正的物理地址,所以相对来说,进程的地址空间是一份虚拟的地址空间,每一个地址通过页表的转换映射到所谓的物理地址空间上。

3. mmap内存映射剖析

内存&文件映射结构

如上介绍Linux内存管理方式,进程、进程内存、mmap的关系图如上

  • 进程(Process) 通过task_struct进行表示,会包含一个内存结构体mm_struct

  • 内存(Memory) 通过mm_struct进行标识,会包含一个内存映射mmap和栈、堆、bss数据段、初始化数据段、text数据段等内存空间组成,这些组成体都是通过vm_area_struct这个最小的内存管理组成单元进行构建和表示的

  • 内存空间区域(Virtual Memory Area) 内存空间区域即组成内存管理的最小单元,里面包含vm_start、vm_end、vm_next等变量进行内存页结构的表示与维护

内存&文件映射过程

mmap内存映射的实现过程,总的来说可以分为三个阶段:

  • 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

  • 进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

  • 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址

  • 为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化

  • 将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中

  • 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系

  • 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。

  • 通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。

  • 内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。

  • 通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

  • 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

  • 进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。

  • 缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。

  • 调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。

  • 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

值得注意的是:

  • 前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。

  • 修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用 msync() 来强制同步, 这样所写的内容就能立即保存到文件里了。

4. mmap函数

语法

void mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length);

功能

  • 将普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,用内存读写取代I/O读写,以获得较高的性能

  • 将特殊文件进行匿名内存映射,为关联进程提供共享内存空间

  • 为无关联的进程间的Posix共享内存(SystemV的共享内存操作是shmget/shmat)

参数

  • 参数addr指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。

  • 参数length代表将文件中多大的部分映射到内存。

  • 参数prot映射区域的保护方式。可以为以下几种方式的组合:

参数

含义

PROT_EXEC

映射区域可被执行

PROT_READ

映射区域可被读取

PROT_WRITE

映射区域可被写入

PROT_NONE

映射区域不能存取

  • 参数flags影响映射区域的各种特性,在调用mmap()时必须要指定。

参数

含义

MAP_FIXED

如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此

MAP_SHARED

对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。

MAP_PRIVATE

对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。

MAP_ANONYMOUS

建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。

MAP_DENYWRITE

只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。

MAP_LOCKED

将映射区域锁定住,这表示该区域不会被置换(swap)。

  • 参数fd要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1

  • 参数offset文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍

mmap函数只是完成建立mmap虚拟内存空间与文件的地址映射,并没有进行数据拷贝

mumap函数

解除虚拟内存与文件映射关系

mprotect函数

对内存空间数据进行保护设置

msync函数

把映射区域的修改回写到后备存储中.因为munmap时并不保证页面回写,如果不调用msync,那么有可能在munmap后丢失对映射区的修改.其中flags可以是MS_SYNC, MS_ASYNC, MS_INVALIDATE, MS_SYNC要求回写完成后才返回, MS_ASYNC发出回写请求后立即返回, MS_INVALIDATE使用回写的内容更新该文件的其它映射.该系统调用是通过调用映射文件的sync函数来完成工作的.

brk函数

将进程的数据段扩展到end_data_segement指定的地址,该系统调用和mmap的实现方式十分相似,同样是产生一个vma,然后指定其属性.不过在此之前需要做一些合法性检查,比如该地址是否大于mm->end_code, end_data_segement和mm->brk之间是否还存在其它vma等等.通过brk产生的vma映射的文件为空,这和匿名映射产生的vma相似,关于匿名映射不做进一步介绍.库函数malloc就是通过brk实现的

5. mmap交互过程跟踪

在JDK中,DirectByteBuffer 就是利用mmap机制实现的IO高性能操作。下面先简单编写一个实现DEMO如下:

/**
 * created by guanjian on 2021/1/11 9:53
 */
public class ByteBufferTest {
    public static void main(String[] args) throws InterruptedException {
        ByteBuffer bb = ByteBuffer.allocateDirect(10);
  //保持运行状态,使Linux的proc进程文件存在便于查看
        Thread.sleep(500000);
    }
}

编译DEMO后放置在Linux系统上执行,通过strace命令来查看服务进程与操作系统之间的交互情况

strace -o log.txt -T -tt -e trace=memory java com/github/java/learning/nio/ByteBufferTest

运行后log.txt日志输出的交互情况如下:

15:47:00.969923 brk(NULL) = 0x4010a000 <0.000009> 15:47:00.970083 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8ae8eab000 <0.000007> 15:47:00.970374 mmap(NULL, 37494, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f8ae8ea1000 <0.000008> 15:47:00.970456 mmap(NULL, 2208904, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f8ae8a6f000 <0.000009> 15:47:00.970499 mprotect(0x7f8ae8a86000, 2093056, PROT_NONE) = 0 <0.000010> 15:47:00.970530 mmap(0x7f8ae8c85000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16000) = 0x7f8ae8c85000 <0.000012> 15:47:00.970567 mmap(0x7f8ae8c87000, 13448, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f8ae8c87000 <0.000009> 15:47:00.970654 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8ae8ea0000 <0.000008> 15:47:00.970687 mmap(NULL, 1089200, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f8ae8d96000 <0.000008> 15:47:00.970713 mprotect(0x7f8ae8d9d000, 1052672, PROT_NONE) = 0 <0.000010> 15:47:00.970739 mmap(0x7f8ae8e9e000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8000) = 0x7f8ae8e9e000 <0.000009> 15:47:00.970833 mmap(NULL, 2109744, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f8ae886b000 <0.000010> 15:47:00.970862 mprotect(0x7f8ae886d000, 2097152, PROT_NONE) = 0 <0.000011> 15:47:00.970889 mmap(0x7f8ae8a6d000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f8ae8a6d000 <0.000009> 15:47:00.970991 mmap(NULL, 3940800, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f8ae84a8000 <0.000008> 15:47:00.971021 mprotect(0x7f8ae8660000, 2097152, PROT_NONE) = 0 <0.000016> 15:47:00.971054 mmap(0x7f8ae8860000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b8000) = 0x7f8ae8860000 <0.000011> 15:47:00.971088 mmap(0x7f8ae8866000, 16832, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f8ae8866000 <0.000008> 15:47:00.971145 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8ae8d95000 <0.000007> 15:47:00.971182 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8ae8d93000 <0.000007> 15:47:00.971301 mprotect(0x7f8ae8860000, 16384, PROT_READ) = 0 <0.000009> 15:47:00.971341 mprotect(0x7f8ae8a6d000, 4096, PROT_READ) = 0 <0.000008> 15:47:00.971383 mprotect(0x7f8ae8c85000, 4096, PROT_READ) = 0 <0.000009> 15:47:00.971416 mprotect(0x7f8ae8eac000, 4096, PROT_READ) = 0 <0.000008> 15:47:00.971441 munmap(0x7f8ae8ea1000, 37494) = 0 <0.000024> 15:47:00.971634 brk(NULL) = 0x4010a000 <0.000006> 15:47:00.971661 brk(0x4012b000) = 0x4012b000 <0.000007> 15:47:00.971683 brk(NULL) = 0x4012b000 <0.000005> 15:47:00.971830 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8ae8eaa000 <0.000009> 15:47:00.971905 munmap(0x7f8ae8eaa000, 4096) = 0 <0.000011> 15:47:00.972194 brk(NULL) = 0x4010a000 <0.000007> 15:47:00.972252 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d1aa2e000 <0.000008> 15:47:00.972780 mmap(NULL, 37494, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f4d1aa24000 <0.000008> 15:47:00.972863 mmap(NULL, 2208904, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4d1a5f2000 <0.000009> 15:47:00.972891 mprotect(0x7f4d1a609000, 2093056, PROT_NONE) = 0 <0.000010> 15:47:00.972917 mmap(0x7f4d1a808000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16000) = 0x7f4d1a808000 <0.000011> 15:47:00.972952 mmap(0x7f4d1a80a000, 13448, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f4d1a80a000 <0.000009> 15:47:00.973043 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d1aa23000 <0.000008> 15:47:00.973079 mmap(NULL, 1089200, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4d1a919000 <0.000007> 15:47:00.973105 mprotect(0x7f4d1a920000, 1052672, PROT_NONE) = 0 <0.000009> 15:47:00.973130 mmap(0x7f4d1aa21000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8000) = 0x7f4d1aa21000 <0.000009> 15:47:00.973252 mmap(NULL, 2109744, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4d1a3ee000 <0.000008> 15:47:00.973281 mprotect(0x7f4d1a3f0000, 2097152, PROT_NONE) = 0 <0.000011> 15:47:00.973310 mmap(0x7f4d1a5f0000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f4d1a5f0000 <0.000009> 15:47:00.973435 mmap(NULL, 3940800, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4d1a02b000 <0.000009> 15:47:00.973478 mprotect(0x7f4d1a1e3000, 2097152, PROT_NONE) = 0 <0.000013> 15:47:00.973510 mmap(0x7f4d1a3e3000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b8000) = 0x7f4d1a3e3000 <0.000011> 15:47:00.973543 mmap(0x7f4d1a3e9000, 16832, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f4d1a3e9000 <0.000008> 15:47:00.973599 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d1a918000 <0.000008> 15:47:00.973635 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d1a916000 <0.000007> 15:47:00.973743 mprotect(0x7f4d1a3e3000, 16384, PROT_READ) = 0 <0.000009> 15:47:00.973782 mprotect(0x7f4d1a5f0000, 4096, PROT_READ) = 0 <0.000008> 15:47:00.973821 mprotect(0x7f4d1a808000, 4096, PROT_READ) = 0 <0.000008> 15:47:00.973854 mprotect(0x7f4d1aa2f000, 4096, PROT_READ) = 0 <0.000008> 15:47:00.973879 munmap(0x7f4d1aa24000, 37494) = 0 <0.000012> 15:47:00.974056 brk(NULL) = 0x4010a000 <0.000006> 15:47:00.974083 brk(0x4012b000) = 0x4012b000 <0.000007> 15:47:00.974105 brk(NULL) = 0x4012b000 <0.000005> 15:47:00.974247 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d1aa2d000 <0.000009> 15:47:00.974322 munmap(0x7f4d1aa2d000, 4096) = 0 <0.000012> 15:47:00.974496 mmap(NULL, 12625408, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4d19420000 <0.000009> 15:47:00.974530 mprotect(0x7f4d19d3a000, 1056768, PROT_NONE) = 0 <0.000008> 15:47:00.974555 mmap(0x7f4d19e3c000, 1789952, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x91c000) = 0x7f4d19e3c000 <0.000010> 15:47:00.974589 mmap(0x7f4d19ff1000, 235008, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f4d19ff1000 <0.000010> 15:47:00.974704 mmap(NULL, 37494, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f4d1aa24000 <0.000008> 15:47:00.974790 mmap(NULL, 3150136, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4d1911e000 <0.000009> 15:47:00.974818 mprotect(0x7f4d1921f000, 2093056, PROT_NONE) = 0 <0.000011> 15:47:00.974845 mmap(0x7f4d1941e000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x100000) = 0x7f4d1941e000 <0.000010> 15:47:00.974964 mprotect(0x7f4d1941e000, 4096, PROT_READ) = 0 <0.000009> 15:47:00.976963 munmap(0x7f4d1aa24000, 37494) = 0 <0.000013> 15:47:00.977029 mmap(NULL, 1052672, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f4d1901d000 <0.000009> 15:47:00.977076 mprotect(0x7f4d1901d000, 4096, PROT_NONE) = 0 <0.000008>

梳理日志文本,可以看到主要调用的函数为mmap、mprotect、brk、mumap,下面通过man命令来查看函数的使用介绍梳理如下:

  • mmap 它把文件内容映射到一段内存上(准确说是虚拟内存上),通过对这段内存的读取和修改,实现对文件的读取和修改

  • mprotect 可以用来修改一段指定内存区域的保护属性

  • brk 改变数据段所用内存的数量

  • mumap 将文件内容与虚拟内存映射关系解除

下面通过ps -ef |grep java命令找到运行的java进程来查看

root 23866 23864 0 15:46 pts/1 00:00:00 java com/github/java/learning/nio/ByteBufferTest

此时进程ID是23866,通过命令 ll /proc/23866/fd/ ,查看文件描述符如下:

lrwx------ 1 root root 64 Jan 18 16:08 0 -> /dev/pts/1 lrwx------ 1 root root 64 Jan 18 16:08 1 -> /dev/pts/1 lrwx------ 1 root root 64 Jan 18 16:07 2 -> /dev/pts/1 lr-x------ 1 root root 64 Jan 18 16:08 3 -> /export/servers/jdk1.6.0_25/jre/lib/rt.jar

6. mmap与常规IO区别

操作

交互路径

IO传输次数

常规IO

用户空间 <–io流–> 内核空间 <–io流–> 存储介质

读取: 用户空间 <–io流–> 内核空间 (1) 内核空间 <–io流–> 存储介质 (1) 写入: 用户空间 <–io流–> 内核空间 (1) 内核空间 <–io流–> 存储介质 (1)

mmap

用户空间 <–映射地址–> 存储介质

读取: 用户空间 <–映射地址–> 存储介质(1) 写入: 用户空间 <–映射地址–>存储介质 (1)

常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。

7. mmap作用及优点

  • 将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能

  • 将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间

  • 为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中


http://www.niftyadmin.cn/n/5479954.html

相关文章

C++笔记:STL容器库的使用

前置&#xff1a; 对于stl容器库&#xff0c;我只做了一些常用的笔记&#xff0c;关于更详细的使用可以参考:https://cppreference.com/https://cppreference.com/ 一.string--字符串 对于C中string字符串会比C语言的字符数组使用起来会顺手许多。 命名空间&#xff1a;std 关于…

Few-Shot目标检测数据集 | Few-Shot目标检测数据集_已经整理成MS-COCO数据格式_含60000+张图_可直接用于目标检测算法训练

项目应用场景 面向 Few-Shot 目标检测场景&#xff0c;项目提供 6000 张图&#xff0c;已经整理成 MS-COCO 数据格式&#xff0c;可用于 Few-Shot 目标检测的训练数据集&#xff0c;或作为 Few-Shot 目标检测数据集的补充。 数据集展示 数据集下载 > 具体参见项目 README.m…

项目实战 | 使用python分析Excel销售数据(用groupby)

项目实战 | 使用python分析Excel销售数据 本文目录&#xff1a; 零、00时光宝盒 一、提出问题 二、理解数据 2.1、安装python读取excel文件的库 2.2、查看excel表的字段名和前几行记录 2.3、查看excel表结构 2.4、查看索引 2.5、查看每一列的列表头内容 2.6、查看每一…

SpringBoot整合RabbitMQ,三种交换机类型示例

SpringBoot整合RabbitMQ&#xff0c;三种交换机类型示例 1、流程概括 2、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>3、配置RabbitMQ连接 在a…

leetcode每日一题(1702. 修改后的最大二进制字符串)

题目描述 题解 这道题贪心的思想&#xff0c;我们只需要尽可能多的把0变成1&#xff0c;而且进行操作1才能使其变大。观察发现以下几点&#xff1a; 不论原字符串有多少个0&#xff0c;最后都会剩余1个0。 假设原字符串只有一个0&#xff0c;不能进行任何操作&#xff0c;显然…

配置Spring Boot WebSocket 客户端与前端vue实现多人通信聊天

后台引入 <!-- websocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency> 然后配置类配置下 Configuration public class WebSocketConfig {/…

【零基础SRC】成为漏洞赏金猎人的第一课:加入玲珑安全漏洞挖掘班

我们是谁 你是否对漏洞挖掘充满好奇&#xff1f;零基础或有基础但想更进一步&#xff1f;想赚取可观的漏洞赏金让自己有更大的自由度&#xff1f; 不妨了解下《玲珑安全团队》。 玲珑安全团队&#xff0c;拥有多名实力讲师&#xff0c;均就职于互联网头部公司以及国内国有企…

【HTML】简单制作一个分形动画

目录 前言 开始 HTML部分 效果图 ​编辑​编辑​编辑​编辑总结 前言 无需多言&#xff0c;本文将详细介绍一段代码&#xff0c;具体内容如下&#xff1a; 开始 首先新建文件夹&#xff0c;创建一个文本文档&#xff0c;其中HTML的文件名改为[index.html]&a…