2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > linux用户态进程地址空间管理

linux用户态进程地址空间管理

时间:2021-02-18 10:06:52

相关推荐

linux用户态进程地址空间管理

在linux操作系统下,每个进程或者线程都用一个task_struct来描述,这其中有一个mm_struct结构体来管理用户态进程的内存,我们称之为内存描述符,该结构体包含了用户态进程地址空间相关的全部信息。task_struct 中还有一个很重要的元素为 active_mm (上一个被调用的用户态进程的mm指针),主要用于内核线程访问内核页全局目录,事实上,内核线程并没有相关的内存描述符,对应的task_struct中的mm指针为空。

include/linux/mm_types.hstruct mm_struct {struct {struct vm_area_struct *mmap;/* list of VMAs */struct rb_root mm_rb; /*VMA形成的红黑树*/u64 vmacache_seqnum; /* per-thread vmacache */#ifdef CONFIG_MMUunsigned long (*get_unmapped_area) (struct file *filp,unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags);#endifunsigned long mmap_base;/* base of mmap area */unsigned long mmap_legacy_base;/* base of mmap area in bottom-up allocations */......unsigned long task_size;/* size of task vm space */unsigned long highest_vm_end;/* highest vma end address */pgd_t * pgd;/*页全局目录*/......#ifdef CONFIG_MMUatomic_long_t pgtables_bytes;/* PTE page table pages */#endifint map_count;/* number of VMAs */......unsigned long total_vm; /* Total pages mapped */unsigned long locked_vm; /* Pages that have PG_mlocked set */unsigned long pinned_vm; /* Refcount permanently increased */unsigned long data_vm; /* VM_WRITE & ~VM_SHARED & ~VM_STACK */unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE & ~VM_STACK */unsigned long stack_vm; /* VM_STACK */unsigned long def_flags;spinlock_t arg_lock; /* protect the below fields */unsigned long start_code, end_code, start_data, end_data; /*text段(可执行代码)的开始和结束地址,data段(已初始化数据)的开始和结束地址*/unsigned long start_brk, brk, start_stack; /*heap的起始地址和当前的结束地址,stack的起始地址*/unsigned long arg_start, arg_end, env_start, env_end;/*命令行参数的开始和结束地址,环境变量的开始和结束地址,都位于stack中最高地址的地方*/unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv *//** Special counters, in some configurations protected by the* page_table_lock, in other configurations by being atomic.*/struct mm_rss_stat rss_stat;struct linux_binfmt *binfmt;/* Architecture-specific MM context */mm_context_t context;。。。。。。};

我们知道,用户态进程的虚拟地址空间很大,不可能都有真实的物理内存对应,mm_struct结构体里total_vm是总共映射的页的数量。data_vm是存放数据的页的数量,exec_vm是存放可执行文件的页的数量,stack_vm是栈占用的页的数量。

start_code, end_code, start_data, end_data分别为text段的开始和结束地址,data段的开始和结束地址。start_brk, brk, start_stack,mmap_base分别为heap的起始地址和当前的结束地址,stack的起始地址,虚拟空间中用于内存映射的起始地址。除了这些代码段的位置信息,mm_struct里还有 struct vm_area_struct 的单链表mmap以及红黑树mm_rb方便快速查找一个内存区域,vm_area_struct 描述了一段连续的具有相同属性的虚拟空间,是进程虚拟地址空间管理的基本单元,最终的内存映射图形如下。

根据此图,我们再来看各个代码段的位置信息,start_code和end_code是由elf文件传入的,通过exec运行一个二进制程序的时候,除了解析ELF格式还会建立内存映射,调用顺序为 __do_execve_file->exec_binprm->search_binary_handler->load_binary load_binary是一个hook函数,最终调用load_elf_binary,这个函数完成的事情主要如下

static int load_elf_binary(struct linux_binprm *bprm){....................................setup_new_exec(bprm); //设置内存映射区mmap_base....................................retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),executable_stack); //设置栈的vm_area_struct....................................error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,elf_prot, elf_flags, total_size); //将ELF文件中的代码部分映射到内存中....................................retval = set_brk(elf_bss, elf_brk, bss_prot); //设置heap的vm_area_struct....................................if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) { //bss段清零retval = -EFAULT; /* Nobody gets to see this, but.. */goto out_free_dentry;}....................................elf_entry = load_elf_interp(&loc->interp_elf_ex,interpreter,&interp_map_addr,load_bias, interp_elf_phdata); //将动态.so文件映射到内存映射区域....................................current->mm->end_code = end_code;current->mm->start_code = start_code;current->mm->start_data = start_data;current->mm->end_data = end_data;current->mm->start_stack = bprm->p;....................................start_thread(regs, elf_entry, bprm->p); //设置应用程序的入口,当内核操作结束,返回用户态的时候,接下来执行的就是该程序了。....................................}

至此, start_stack, mmap_base, brk, start_brk, end_data, start_data, end_code, start_code 都设置好了,bss段也已经清零,最后调用start_thread设置程序返回用户态时的入口。动态库ld.so,libc.so等会映射到Memory Mapping Segment,并且设置vm_area_struct结构体变量vm_flags为VM_READ | VM_EXEC表示只读可执行。

这些映射好的段地址空间什么时候会改变呢?从图中箭头可以看出,stack区,memory mapping区,heap区会沿着箭头方向扩展,随着函数的调用,函数栈的栈顶指针会改变,当我们要进行文件映射或者匿名映射的时候memory mapping区会改变,用户态调用libc接口malloc申请一块内存空间时,如果申请的内存小于128KB会按页对齐将brk往上推,如果申请的内存较大则调用mmap。

上面说了运行elf文件产生的进程,那么用fork创建的进程是如何配置进程空间的呢?fork ->_do_fork ->copy_process ->copy_mm -> dup_mm这个调用流程里dump_mm函数会创建一个新的内存描述符,将父进程的mm结构体整个copy过去,mm_init初始化内存相关的模块(复制内核空间的页表),copy_page_range复制页表空间。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。