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流程分析:
-->1)struct 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()
-->2)if (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
-->3)if (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
-->1)arch_get_unmapped_area()
默认使用mm_struct结构中的get_unmapped_area接口
-->1)if (flags & MAP_FIXED) return addr;
若是固定映射,则直接返回addr作为vm_start进行操作
-->1)vm_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)根据不同的映射类型进行不同的操作
-->1)if (file)
vma->vm_file = get_file(file)
call_mmap(file, vma)
若是file映射,则填充vma->vm_file成员,并调用vm_file->f_ops->mmap()接口填充vma->vm_ops成员为具体文件系统的ops接口
-->2)if (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结构
-->2)alloc_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成员
-->3)vma_set_anonymous(vma)
vma->vm_ops = NULL
若是匿名私有映射,则设置vma->vm_ops=NULL
-->3)vma_link(mm, vma, prev, rb_link, rb_parent)
将vma添加到vma的链表和红黑树上,并将mm->map_count++