mmap内存映射原理
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函数方法会影响它们。
页表管理
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读写,以获得较高的性能
- 将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间
- 为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中
参考
https://blog.csdn.net/Caoyuan_001/article/details/84543119
https://baike.baidu.com/item/mmap/1322217
https://blog.csdn.net/bbzhaohui/article/details/81665370
https://www.cnblogs.com/huxiao-tee/p/4657851.html
https://blog.csdn.net/qq_26768741/article/details/54375524