linux do_mmap流程简析

mmap --> sys_mmap --> ksys_mmap_pgoff --> vm_mmap_pgoff --> do_mmap_pgoff --> do_mmap

1 mmap接口介绍

void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset)
参数: 
start :映射vma的起始地址,一般设置为NULL,表示由系统分配
length:映射vma的长度
prot  :映射vma的读写权限,PROT_READ PROT_WRITE PROT_EXEC PROT_NONE
flags :映射vma的标志,MAP_SHARED MAP_PRIVATE MAP_ANONYMOUS MAP_FIXED MAP_PUPOLATE
        -- MAP_SHARED   :共享映射,多个进程的vma映射到关联同一个文件file
		-- MAP_PRIVATE  :私有映射
		-- MAP_ANONYMOUS:匿名映射
fd    :映射vma关联的文件
        -- 文件映射:将一个普通文件的全部或部分映射到进程的vma,进程可通过vma来操作文件
		-- 匿名映射:fd为NULL,没有文件,映射后vma会初始化为0
offset:映射文件的偏移

返回值:返回vma起始地址vm_start

2 结构介绍

1)vm_area_struct结构

struct vm_area_struct {

	unsigned long          vm_start;		 // vma起始地址
	unsigned long          vm_end;		     // vma结束地址
	struct vm_area_struct *vm_next;          // vma链表节点,按地址排序
	struct vm_area_struct *vm_prev;
	struct rb_node         vm_rb;            // 插入到mm->mm_rb红黑树的节点
	unsigned long          rb_subtree_gap;   // 以当前vma为根,左子树中最大可用虚拟内存区域的大小,有助于get_unmapped_area接口获得合适的空闲vma
	struct mm_struct      *vm_mm;            // vma所属的mm结构地址空间
	pgprot_t               vm_page_prot;     // 此vma的访问权限
	unsigned long          vm_flags;		 // 标志位

	union {
		struct {
			struct rb_node rb;
			unsigned long rb_subtree_last;
		} shared;
		const char __user *anon_name; 
	};

	const struct vm_operations_struct *vm_ops;      // vma操作接口
	
	struct list_head          anon_vma_chain;       // 由mmap_sem和* page_table_lock序列化
	struct anon_vma          *anon_vma;	            // 由page_table_lock序列化
	
	struct file              *vm_file;	            // 映射的文件,匿名映射为NULL	
	unsigned long             vm_pgoff;		        // 在vm_file中以PAGE_SIZE为单位的偏移量
	
	void                     *vm_private_data;		// 私有数据
	atomic_long_t             swap_readahead_info;  // 是vm_pte(共享内存)
	pid_t                     pid;                  //
	
	struct vm_region         *vm_region;            // NOMMU映射区域
	struct mempolicy         *vm_policy;	        // 针对vma的NUMA策略
	
} __randomize_layout;
2)mm_struct结构

struct mm_struct {
	struct {
		unsigned long (*get_unmapped_area) ()      // 获取一段空间vma的回调接口

		struct vm_area_struct  *mmap;		       // 指向vma链表头
		struct rb_root          mm_rb;             // 挂载vma的红黑树根节点        
		int                     map_count;		   // 此mm中已映射的vma个数
		
		unsigned long mmap_base;	    // 第一个已映射vma的基地址
		unsigned long mmap_legacy_base;	// 自底向上分配的vma基地址

		unsigned long task_size;	    // task虚拟地址空间vm总大小
		unsigned long highest_vm_end;	// 指向最大的vma结束地址
		pgd_t        *pgd;              // 指向task的全局页目录基址

		atomic_t      mm_count;         // 主计数器,使用此mm_struct的引用计数值
		atomic_t      mm_users;         // 次计数器,存放共享此mm_struct数据结构的task个数		
		
		struct rw_semaphore mmap_sem;            // 读写信号量锁,用于共享此mm_struct的多个task之间访问的锁
		spinlock_t          page_table_lock;     // 保护vma和页表的自旋锁
		
		atomic_long_t       pgtables_bytes;	     // PTE页表表示的page大小,一般为4K
		struct list_head    mmlist;              // 挂载所有task的mm_struct结构的链表节点

		unsigned long hiwater_rss; // 进程所拥有的最大page个数
		unsigned long hiwater_vm;  // 进程线性区中最大page个数

		unsigned long total_vm;	   // 进程地址空间大小(page个数)
		unsigned long locked_vm;   // 使用PG_mlocked标志锁住而不能换出的page个数
		unsigned long pinned_vm;   /* Refcount permanently increased */
		unsigned long data_vm;	   // data段映射的page个数
		unsigned long exec_vm;	   // 可执行内存映射的page个数
		unsigned long stack_vm;	   // stack段已映射的page个数
		unsigned long def_flags;   // 线性区默认访问标志

		unsigned long start_code, end_code;         // 此task的text段起始结束地址
		unsigned long start_data, end_data;         // 此task的data段起始结束地址
		unsigned long start_brk, brk;               // 此task的堆空间brk起始结束地址
		unsigned long start_stack;		            // 此task的栈空间起始地址
		unsigned long arg_start, arg_end;           // 此task的args参数段的起始结束地址
		unsigned long env_start, env_end;           // 此task的env环境参数段的起始结束地址
		unsigned long saved_auxv[AT_VECTOR_SIZE]; 

		struct mm_rss_stat        rss_stat;         //
		struct linux_binfmt      *binfmt;           //
		mm_context_t              context;          // 体系结构相关的mm上下文
		unsigned long             flags;            // Must use atomic bitops to access 
		struct core_state        *core_state;       // coredumping support 
		struct task_struct __rcu *owner;            //
		struct user_namespace    *user_ns;          //
		struct file __rcu        *exe_file;         //
		struct mmu_notifier_mm   *mmu_notifier_mm;  //
		pgtable_t                 pmd_huge_pte;     // protected by page_table_lock
	} __randomize_layout;

	unsigned long cpu_bitmap[];                     // 大小是动态改变的,所以放在mm_struct结构的结尾
};

mm_struct中分配vma策略:默认是从mmap_base --> task_size自底向上分配vma

3 linux do_mmap流程

mmap系统调用流程:
sys_mmap()
--> do_mmap_pgoff()
    --> get_unmmaped_area()
    --> mmap_region()
        --> generic_file_mmap()

get_unmmaped_area接口作用:
获取一段未映射的虚拟地址空间的addr、len。
mmap_region接口作用:
1)创建并填充一个vma结构;
2)调用f_ops->mmap()回调接口填充vma_ops->fault()接口;
3)将新的vma添加到进程的vma链表中。

缺页异常调用流程:
do_page_fault()
-->handle_mm_fault()
   -->handle_pte_fault()
      -->do_linear_fault()
         -->__do_dault()

__do_fault接口作用:
1)vma->vm_ops->fault()
调用fault()回调接口获取要映射的物理页page;
2)entry = mk_pte(page, vma->vm_page_prot)
根据物理页page生成页表项entry
3)set_pte_at(mm, address, page_table, vma)
将虚拟内存映射address到物理页page(即将进程的页表项设置为物理页page的页表项值)

3.1 do_mmap流程图

在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/59b068854f0743cebc41137f50f158c4.png
在这里插入图片描述

3.2 do_mmap代码分析

do_mmap流程分析:

-->1struct mm_struct *mm = current->mm
      获取当前进程的mm_struct结构

-->2)addr = get_unmapped_area(file, addr, len, pgoff, flags)
      查找一个未映射的vma,并返回其起始地址vm_start

	  -->1)针对不同类型的vma获取对应的get_area接口
	  
            -->1)get_area = current->mm->get_unmapped_area
	              默认使用mm_struct结构中的get_unmapped_area接口,即arch_get_unmapped_area()
	  
	        -->2if (file) 
	                  get_area = file->f_op->get_unmapped_area;
				  若是file映射,则使用f_ops中的get_unmapped_area接口,最终还是调用mm_struct结构中的get_unmapped_area接口
				  例:ext4文件系统,ext4_file_operations --> __thp_get_unmapped_area --> current->mm->get_unmapped_area
				  	 
	        -->3if (flags & MAP_SHARED)
	                  get_area = shmem_get_unmapped_area
				  若是匿名共享映射,则使用shmem_get_unmapped_area接口
					  
	  -->2)addr = get_area(file, addr, len, pgoff, flags)
	        执行get_area接口获取一段空闲的vma,并返回起始地址addr

            -->1arch_get_unmapped_area()
			      默认使用mm_struct结构中的get_unmapped_area接口
				  
				  -->1if (flags & MAP_FIXED) return addr;
				        若是固定映射,则直接返回addr作为vm_start进行操作
				   
				  -->1vm_unmapped_area(&info)
				        一般addr为NULL,调用此接口在mm->mmap_base和TASK_SIZE之间查找一个长度为len的空闲vma,并返回其vm_start地址				  		  

-->3)mmap_region
      创建并填充一个新的vma结构,将其与file进行映射并添加到vma链表上

      -->1)创建并填充一个新的vma结构 
	        vma = vm_area_alloc(mm)
			vma->vm_start     = addr;                        // vma起始地址
			vma->vm_end       = addr + len;                  // vma结束地址
			vma->vm_flags     = vm_flags;                    // vma映射标志
			vma->vm_page_prot = vm_get_page_prot(vm_flags);  // vma访问权限
			vma->vm_pgoff     = pgoff;		                 // vma在vm_file中的page_size单位偏移量	

	  -->2)根据不同的映射类型进行不同的操作

			-->1if (file)
	                  vma->vm_file = get_file(file)
				      call_mmap(file, vma)
				  若是file映射,则填充vma->vm_file成员,并调用vm_file->f_ops->mmap()接口填充vma->vm_ops成员为具体文件系统的ops接口

	        -->2if (vm_flags & VM_SHARED)
	                  shmem_zero_setup(vma)
				  若是匿名共享映射,则在shm_mnt虚拟文件系统中创建一个inode和file结构,
				  重新填充vma->vm_file和vma->vm_ops成员

					  -->1)file = shmem_kernel_file_setup("dev/zero", size, vma->vm_flags)
					        在shm_mnt虚拟文件系统中创建一个新的inode和file结构

                            -->1)inode = shmem_get_inode(mnt->mnt_sb, NULL, S_IFREG | S_IRWXUGO, 0, flags)
							      创建inode结构
							
                            -->2alloc_file_pseudo(inode, mnt, name, O_RDWR, &shmem_file_operations)
							      创建file结构,填充shmem_file_operations
							
					  -->2)vma->vm_file = file
					        填充vma->vm_file成员

					  -->3)vma->vm_ops = &shmem_vm_ops
                            填充vma->vm_ops成员

	        -->3vma_set_anonymous(vma)
			          vma->vm_ops = NULL
				  若是匿名私有映射,则设置vma->vm_ops=NULL

	  -->3vma_link(mm, vma, prev, rb_link, rb_parent)
	        将vma添加到vma的链表和红黑树上,并将mm->map_count++
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值