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

目录

内存映射原理

系统调用

mmap内存映射原理三个阶段

sys_mmap系统调用

munmap系统调用


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

1)文件映射:文件支持的内存映射,把文件的一个区间映射到进程的虚拟地址空间,数据源是存储设备上的文件。

2)匿名映射:没有文件支持的内存映射,把物理内存映射到进程的虚拟地址空间,没有数据源。


内存映射原理

创建内存映射时,在进程的用户虚拟地址空间中分配一个虚拟内存区域。内核采用延迟分配物理内存的策略,在进程第一次访问虚拟页的时候,产生缺页异常

  • 如果是文件映射,那么分配物理页,把文件指定区间的数据读到物理页中,然后在页表中把虚拟页映射到物理页;
  • 如果是匿名映射,就分配物理页,然后在页表中把虚拟页映射到物理页 ;

系统调用

应用程序 malloc或mmap申请内存,glibc库的内存分配器ptmalloc使用brk或mmap向内核以页为单位申请虚拟内存,然后把页划分成小内存块给应用程序。

默认阈值是128kb,如果应用程序申请的内存长度小于阈值,ptmalloc使用brk,否则使用mmap

注:应用层的mmap和内核中的mmap参数不一样

库函数

#include <sys/mman.h>
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

内核

int mmap(struct file *filp, struct vm_area_struct *vma)

mmap内存映射原理三个阶段

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


sys_mmap系统调用

mmap 创建 " 内存映射 " 的调用mmap 和 mmap2 ;

mmap 偏移单位是 " 字节 " ,mmap2 偏移单位是 " " ,

在 arm 64 体系架构中 , 没有实现 mmap2 , 只实现了 mmap 系统调用 ;

do_mmap实现创建内存映射的主要工作:

1)调用函数get_unmmaped_area 从进程的虚拟地址空间分配一个虚拟地址范围

  • 如果是创建文件映射或匿名映射,那么调用file->f_op->get_unmapped_area以分配虚拟地址范围
  • 如果创建的是共享匿名映射,那么调用shmem_get_unmapped_area以分配虚拟地址范围
  • 如果是创建私有的匿名映射,那么调用mm->get_unmapped_area以分配虚拟地址范围
调用mmap_region 创建虚拟内存区域

1、调用函数may_expand_vm以检查进程申请的虚拟内存是否超过限制

检查进程的虚拟内存总数+申请的页数 是否超过地址空间限制:

mm->total_vm + npages > rlimit(RLIMIT_AS)>>PAGE_SHIFT

如果是私有映射,并且不是栈,检查进程的虚拟内存总数+申请的页数是否超过最大长度

mm->total_vm + npages > rlimit(RLIMIT_DATA)>>PAGE_SHIFT

2、如果是固定映射,强制指定虚拟地址范围,可能和旧的虚拟内存区域重叠,需要从旧的虚拟内存区域删除重叠的部分。

3、如果是私有的可写映射,检查所有进程申请的虚拟内存的总和是否超过物理内存的容量。

4、如果可以和已有的虚拟内存区域合并,调用vma_merage

5、如果不能合并

  •  创建新的虚拟内存区域
  • 如果是文件映射,调用文件操作集合中的mmap方法(file->f_op->mmap)其主要功能是设置虚拟内存区域的内存操作集合(vm_area_struct.vm_ops),其中fault方法:第一次访问虚拟页的时候,触发页错误异常,把文件数据读到内存;
  • 如果是共享的匿名映射,在文件系统tmpfs中创建名为/dev/zero的文件,并且创建一个打开实例file,虚拟内存区域的成员vm_file指向这个打开实例。
  • 调用函数vma_link,把虚拟内存区域添加到链表和红黑树中;
  • 调用vma_set_page_prot,根据虚拟内存标志计算页保护位,如果共享的可写映射想要把页标记为只读,目的是跟踪写事件那么从页保护位删除即可。

6、虚拟内存过量提交策略__vm_enough_memory函数判断内存是否足够

  • OVERCOMMIT_GUESS(0):猜测
  • OVERCOMMIT_ALWAYS(1)允许
  • OVERCOMMIT_NERVER不允许


munmap系统调用

        系统调用munmap用来删除内存映射,它有两个参数:起始地址和长度即可。它的主要工作委托给内核源码文件处理mm/mmap.c

步骤解析

  1. vma = find_vma(mm,start);//根据起始地址找到要删除的第一个虚拟内存区域vma
  2. 如果只删除虚拟内存区域vam的部分,那么分裂虚拟内存区域vma
  3. 根据结束地址找到删除的最后一个虚拟内存区域vma
  4. 如果只删除虚拟内存区域last的一部分,那么分裂虚拟内存区域vma
  5. 针对所有删除目标,如果虚拟内存区域被锁定在内存中,调用函数解锁
  6. 把所有目标从进程虚拟内存区域链表和树链表中删除,组成一条临时链表
  7. 针对所有删除目标,在进程的页表中删除映射,并且从处理器的页缓存中删除映射
  8. 执行处理器架构特定的处理操作
  9. 删除所有目标

源码如下:

//找到要删除的 第一个 虚拟内存区域 vm_area_struct 结构体实例 
int __do_munmap(struct mm_struct *mm, unsigned long start, size_t len,
		struct list_head *uf, bool downgrade)
{
	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);
	end = start + len;
	if (len == 0)
		return -EINVAL;

	/*
	 * arch_unmap() might do unmaps itself.  It must be called
	 * and finish any rbtree manipulation before this code
	 * runs and also starts to manipulate the rbtree.
	 */
// 该处理器架构 对应的 删除内存映射的处理操作
	arch_unmap(mm, start, end);

	/* 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.. */
	if (vma->vm_start >= end)
		return 0;

	/*
	 * 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;
        //不是删除整个 vam 内存区域,指向分裂的区域
		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;

	if (unlikely(uf)) {
		/*
		 * If userfaultfd_unmap_prep returns an error the vmas
		 * will remain splitted, but userland will get a
		 * highly unexpected error anyway. This is no
		 * different than the case where the first of the two
		 * __split_vma fails, but we don't undo the first
		 * split, despite we could. This is unlikely enough
		 * failure that it's not worth optimizing it for.
		 */
		int error = userfaultfd_unmap_prep(vma, start, end, uf);
		if (error)
			return error;
	}

	/*
	 * 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;
		}
	}

	/* Detach vmas from rbtree */
// 虚拟内存区域 " 从 进程的 虚拟内存区域 链表 和 红黑树 数据结构中删除
	detach_vmas_to_be_unmapped(mm, vma, prev, end);

	if (downgrade)
		downgrade_write(&mm->mmap_sem);

//被删除内存区域 对应的 映射 " 删除 , 从处理器页表缓存中也删除对应映射
	unmap_region(mm, vma, prev, start, end);

	/* Fix up all other VM information */
//删除所有的虚拟内存区域
	remove_vma_list(mm, vma);

	return downgrade ? 1 : 0;
}

int do_munmap(struct mm_struct *mm, unsigned long start, size_t len,
	      struct list_head *uf)
{
	return __do_munmap(mm, start, len, uf, false);
}

int vm_munmap(unsigned long addr, size_t len)
{
	struct mm_struct *mm = current->mm;
	int ret;

	down_write(&mm->mmap_sem);
	ret = do_munmap(mm, addr, len, NULL);
	up_write(&mm->mmap_sem);
	return ret;
}


参考:
《深入理解linux内核》
《Linux内核深度解析》

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为了维护世界和平_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值