赞
踩
主要参考了《深入linux内核》和《Linux内核深度解析》,另外简单浅析了一下相关内容
虚拟内存区域使用起始地址和结束地址描述,链表按起始地址递增排序。两系统调用区别: mmap指定的偏移的单位是字节,而mmap2指定的偏移的单位是页。ARM64架构实现系统调用mmap。
系统调用mmap用来创建内存映射,把创建内存映射主要工作委托给do_mmap函数,内核源码文件处理:
mm/mmap.c
#define SYSCALL_DEFINE1(name, …) SYSCALL_DEFINEx(1, _##name, VA_ARGS)
#define SYSCALL_DEFINE2(name, …) SYSCALL_DEFINEx(2, _##name, VA_ARGS)
#define SYSCALL_DEFINE3(name, …) SYSCALL_DEFINEx(3, _##name, VA_ARGS)
#define SYSCALL_DEFINE4(name, …) SYSCALL_DEFINEx(4, _##name, VA_ARGS)
#define SYSCALL_DEFINE5(name, …) SYSCALL_DEFINEx(5, _##name, VA_ARGS)
#define SYSCALL_DEFINE6(name, …) SYSCALL_DEFINEx(6, _##name, VA_ARGS)
#define SYSCALL_DEFINEx(x, sname, …) \
SYSCALL_METADATA(sname, x, VA_ARGS) \
__SYSCALL_DEFINEx(x, sname, VA_ARGS)
sys_mmap
SYSCALL_DEFINE1(old_mmap, struct mmap_arg_struct __user *, arg)
{
struct mmap_arg_struct a;
if (copy_from_user(&a, arg, sizeof(a)))
return -EFAULT;
if (offset_in_page(a.offset)) // 检查是不是整数倍
return -EINVAL;
return sys_mmap_pgoff(a.addr, a.len, a.prot, a.flags, a.fd,
a.offset >> PAGE_SHIFT);
}
sys_mmap_pgoff
SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len, unsigned long, prot, unsigned long, flags, unsigned long, fd, unsigned long, pgoff) { struct file *file = NULL; unsigned long retval; if (!(flags & MAP_ANONYMOUS)) { audit_mmap_fd(fd, flags); file = fget(fd); if (!file) return -EBADF; if (is_file_hugepages(file)) len = ALIGN(len, huge_page_size(hstate_file(file))); retval = -EINVAL; if (unlikely(flags & MAP_HUGETLB && !is_file_hugepages(file))) goto out_fput; } else if (flags & MAP_HUGETLB) { struct user_struct *user = NULL; struct hstate *hs; hs = hstate_sizelog((flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK); if (!hs) return -EINVAL; len = ALIGN(len, huge_page_size(hs)); /* * VM_NORESERVE is used because the reservations will be * taken when vm_ops->mmap() is called * A dummy user value is used because we are not locking * memory so no accounting is necessary */ file = hugetlb_file_setup(HUGETLB_ANON_FILE, len, VM_NORESERVE, &user, HUGETLB_ANONHUGE_INODE, (flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK); if (IS_ERR(file)) return PTR_ERR(file); } flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff); out_fput: if (file) fput(file); return retval; }
系统调用munmap用来删除 内存映射,它有两个参数:起始地址 和长度即可。它的主要工作委托给内 核源码文件处理“mm/mmap.c”当 中的函数do_munmap。
如上图的9个项
1、vma = find_vma(mm, start); //根据起始地址找到要删除的第一个虚拟内存区域vma
2、如果只删除虚拟内存区域vma的部分,那么分裂虚拟内存区域vma
3、根据结束地址找到要删除的最后一个虚拟内存区域vma
4、如果只删除虚拟内存区域last的一部分,那么分裂虚拟内存区域vma
5、针对所有删除目标,如果虚拟内存区域被锁定在内存中(不允许换出到交换区 (swap) ),同用函数解除锁定。
6、调用此函数,把所有删除目标从进程虚拟内存区域链表和树中删除,单独组成一条临时的链表。
7、调用此函数,针对所有删除目标,在进程的页表中删除映射,并且从处理器的页表缓存(tlb_gather_mmu)中删除映射。
8、调用此函数执行处理器架构特定的处理操作。
9、调用此函数,删除所有目标。
/* Munmap is split into 2 main parts -- this part which finds * what needs doing, and the areas themselves, which do the * work. This now handles partial unmappings. * Jeremy Fitzhardinge <jeremy@goop.org> */ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len, struct list_head *uf) { unsigned long end; struct vm_area_struct *vma, *prev, *last; if ((offset_in_page(start)) || start > TASK_SIZE || len > TASK_SIZE-start) return -EINVAL; len = PAGE_ALIGN(len); if (len == 0) return -EINVAL; /* Find the first overlapping VMA */ vma = find_vma(mm, start); if (!vma) return 0; prev = vma->vm_prev; /* we have start < vma->vm_end */ /* if it doesn't overlap, we have nothing.. */ end = start + len; if (vma->vm_start >= end) return 0; if (uf) { int error = userfaultfd_unmap_prep(vma, start, end, uf); if (error) return error; } /* * If we need to split any vma, do it now to save pain later. * * Note: mremap's move_vma VM_ACCOUNT handling assumes a partially * unmapped vm_area_struct will remain in use: so lower split_vma * places tmp vma above, and higher split_vma places tmp vma below. */ if (start > vma->vm_start) { int error; /* * Make sure that map_count on return from munmap() will * not exceed its limit; but let map_count go just above * its limit temporarily, to help free resources as expected. */ if (end < vma->vm_end && mm->map_count >= sysctl_max_map_count) return -ENOMEM; error = __split_vma(mm, vma, start, 0); if (error) return error; prev = vma; } /* Does it split the last one? */ last = find_vma(mm, end); if (last && end > last->vm_start) { int error = __split_vma(mm, last, end, 1); if (error) return error; } vma = prev ? prev->vm_next : mm->mmap; /* * unlock any mlock()ed ranges before detaching vmas */ if (mm->locked_vm) { struct vm_area_struct *tmp = vma; while (tmp && tmp->vm_start < end) { if (tmp->vm_flags & VM_LOCKED) { mm->locked_vm -= vma_pages(tmp); munlock_vma_pages_all(tmp); } tmp = tmp->vm_next; } } /* * Remove the vma's, and unmap the actual pages */ detach_vmas_to_be_unmapped(mm, vma, prev, end); unmap_region(mm, vma, prev, start, end); arch_unmap(mm, vma, start, end); /* Fix up all other VM information */ remove_vma_list(mm, vma); return 0; }
/* * Get rid of page table information in the indicated region. * * Called with the mm semaphore held. */ static void unmap_region(struct mm_struct *mm, struct vm_area_struct *vma, struct vm_area_struct *prev, unsigned long start, unsigned long end) { struct vm_area_struct *next = prev ? prev->vm_next : mm->mmap; struct mmu_gather tlb; lru_add_drain(); tlb_gather_mmu(&tlb, mm, start, end); update_hiwater_rss(mm); unmap_vmas(&tlb, vma, start, end); free_pgtables(&tlb, vma, prev ? prev->vm_end : FIRST_USER_ADDRESS, next ? next->vm_start : USER_PGTABLES_CEILING); tlb_finish_mmu(&tlb, start, end); }
目前多处理器系统有两种体系结构:
1)非一致内存访问(Non-Unit Memory Access,NUMA):指内存被划分成多个内存节点的多处理器系统。访问一个内存节点花费的时间取决于处理器和内存节点的距离。
NUMA系统 每个CPU都有本地内存,可支持特别快速的访问。各个CPU之间通过总结连接起 来,以支持对其他CPU的本地内存访问,当然比访问本地内存要慢一点。
2)对称多处理器(Symmetric Multi-Processor,SMP):即一致内存访问(Uniform Memory Access,UMA),所有处理器访问内存花费的时间是相同。
内存模型是从处理器角度看到的物理内存分布,内核管理不同内存模型的方式存差异。
内存管理子系统支持3种内存模型:
1)平坦内存(Flat Memory):内存的物理地址空间是连续的,没有空洞。
2)不连续内存(Discontiguous Memory):内存的物理地址空间存在空洞,这种模型可以高效地处理空洞。
3)稀疏内存(Space Memory):内存的物理地址空间存在空洞,如果要支持内存热插拔,只能选择稀疏内存模型。
什么情况会出现内存的物理地址空间存在空洞?
- 系统包含多块物理内存,两块内存的物理地址空间之间存在空洞。一块内存的物理地址空间也可能存在空洞,可以查看处理器的参考手册获取分配给内存的物理地址空间。
如果内存的物理地址空间是连续的,不连续内存模型会产生额外的开销,降低性能,所以平坦内存模型是更好的选择。如果内存的物理地址空间存在空洞,应该选择哪种内存模型?
- 平坦内存模型会为空洞分配page结构体,浪费内存;而不连续内存模型对空洞做了优化处理,不会为空洞分配page结构体。和平坦内存模型相比,不连续内存模型是更好的选择。
稀疏内存模型是实验性的,尽量不要选择稀疏内存模型,除非内存的物理地址空间很稀疏,或者要支持内存热插拔。其他情况应该选择不连续内存模型。
内存管理子系统使用节点(node)、区域(zone)和页(page)三级结构描述物理内存。
一个物理内存分为很多个node,每个node存在多个zone,每个zone细分为page大小。
内存节点分为两种情况:
(1)NUMA体系的内存节点,根据处理器和内存的距离划分内存节点;
(2)在具有不连续内存的UMA系统中,表示比区域的级别更高的内存区域,根据物理地址是否连续划分,每块物理地址连续的内存是一个内存节点。
include/linux/mmzone.h
typedef struct pglist_data { struct zone node_zones[MAX_NR_ZONES]; // 内存区域数组 struct zonelist node_zonelists[MAX_ZONELISTS]; // 备用区域列表 int nr_zones; // 该节点包含的内存区域数量 #ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */ struct page *node_mem_map; // 页描述符数组 #ifdef CONFIG_PAGE_EXTENSION struct page_ext *node_page_ext; // 页的扩展属性 #endif #endif ... unsigned long node_start_pfn; // 该节点的起始物理页号 unsigned long node_present_pages; /// 物理页的总数 unsigned long node_spanned_pages; // 物理页的总长度,包括空洞 int node_id; // 节点标识符 ... } pg_data_t;
内存节点被划分为内存区域
include/linux/mmzone.h
内核定义的区域类型
enum zone_type { #ifdef CONFIG_ZONE_DMA ZONE_DMA, #endif #ifdef CONFIG_ZONE_DMA32 ZONE_DMA32, #endif ZONE_NORMAL, #ifdef CONFIG_HIGHMEM ZONE_HIGHMEM, #endif ZONE_MOVABLE, #ifdef CONFIG_ZONE_DEVICE ZONE_DEVICE, #endif __MAX_NR_ZONES };
每个内存区域使用一个zone结构体描述,如下为主要成员
struct zone { /* Read-mostly fields */ /* zone watermarks, access with *_wmark_pages(zone) macros */ unsigned long watermark[NR_WMARK]; // 页分配器使用的水线 unsigned long nr_reserved_highatomic; long lowmem_reserve[MAX_NR_ZONES]; // 页分配器使用,当前区域保留多少页不能借给高的区域类型 #ifdef CONFIG_NUMA int node; #endif struct pglist_data *zone_pgdat; // 指向内存节点的pglist_data实例 struct per_cpu_pageset __percpu *pageset; // 每个处理器页集合 #ifndef CONFIG_SPARSEMEM /* * Flags for a pageblock_nr_pages block. See pageblock-flags.h. * In SPARSEMEM, this map is stored in struct mem_section */ unsigned long *pageblock_flags; // 当前区域的起始物理页号 #endif /* CONFIG_SPARSEMEM */ /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */ unsigned long zone_start_pfn; unsigned long managed_pages; // 伙伴分配器管理的物理页的数量 unsigned long spanned_pages; // 当前区域跨越的总页数,包括空洞 unsigned long present_pages; // 当前区域存在的物理页的数量,不包括空洞 const char *name; // 区域名称 #ifdef CONFIG_MEMORY_ISOLATION /* * Number of isolated pageblock. It is used to solve incorrect * freepage counting problem due to racy retrieving migratetype * of pageblock. Protected by zone->lock. */ unsigned long nr_isolate_pageblock; #endif #ifdef CONFIG_MEMORY_HOTPLUG /* see spanned/present_pages for more description */ seqlock_t span_seqlock; #endif int initialized; /* Write-intensive fields used from the page allocator */ ZONE_PADDING(_pad1_) /* free areas of different sizes */ struct free_area free_area[MAX_ORDER]; // 不同长度的空闲区域 ...
每个物理页对应一个page结构体,称为页描述符,内存节点的pglist_data实例的成员node_mem_map指向该内存节点包含的所有物理页的页描述符组成的数组。
页是内存管理当中的最小单位,页面中的内存其物理地址是连续的,每个物理页由struct page描述。为了节省内存,struct page 是个联合体(共用体)。
页,又称为页帧,在内核当中,内存管理单元MMU(负责虚拟地址和物理地址转换的硬件)是把物理页page作为内存管理的基本单位。体系结构不同,支持的页大小也相同。
32位体系结构支持4kb的页
64位体系结构支持8kb的页
MIPS64架构体系支持16kb的页
include/linux/mm_types.h
struct page {
/* First double word block */
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously */
结构体page的成员flags的布局如下
因为物理页的数量很大,所以在page结构体中增加1个成员,可能导致所有page实例占用的内存大幅增加。为了减少内存消耗,内核努力使page结构体尽可能小,对于不会同时生效的成员,使用联合体,这种做法带来的负面影响是page结构体的可读性差。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。