进程地址空间


进程地址空间

15.1 地址空间

15.2 内存描述符

内核用内存描述符,表示进程的地址空间,描述了与进程地址有关的所有信息

mm_struct结构体,定义在<linux/sched.h>中

struct mm_struct&#123;
    struct vm_area_struct     *mmap;        /*内存区域链表*/
    struct rb_root            mm_rb;        /*VMA形成的红黑树*/
    ...
    pgd_t                    *pgd;
    struct list_head        mmlist;        /*所有mm_struct形成的链表*/
    ...
    atomic_t                mm_users;    /*使用地址空间的用户数*/
    atomic_t                mm_count;    /*主使用计数器*/
&#125;

mm_users:如果两个线程共享地址空间,则等于2, mm_count为增加量,则为1

​ 9个线程共享,mm_count仍为1

只有mm_users减为0,mm_count才为0,结构体撤销

mmap和mm_rb描述的对象相同,地址空间中所有的内存区域

前者以链表形式,后者以红黑树

冗余的意义:链表适合便利,mm_rb适合搜索

mm_struct:通过 mmlist域连接在双向链表,首元素是init_mm内存描述符,代表init进程的地址空间

操作时使用mmlist_lock,定义在kernel/fork.c

15.2.1 分配内存描述符

进程描述符task_struct中,mm域存放进程使用的内存描述符,current->mm

fork()函数利用copy_mm复制父进程的内存描述符

mm_struct实际由fork.c中的allocate_mm()宏,从mm_cachep slab缓存中分配得到‘

通常,每个进程有唯一的mm_struct,即唯一进程地址空间

如果父进程希望和子进程共享地址空间,调用clone()时设置CLONE_VM标志,这样的进程为线程

CLONE_VM仅需要在调用copy_mm()函数将mm域指向父进程的mm_struct

if(clone_flags & CLONE_VM)&#123;
    /*
    *    current是父进程而tsk在fork()执行期间是子进程
    */
    atomic_inc(&current->mm->mm_users);
    tsk->mm = current->mm;
&#125;

15.2.2 撤销内存描述符

进程退出时,内核调用定义在kernel/exit.c中的exit_mm()函数

更新统计量,调用mmput()减少mm_users计数

调用mmdrop()减少mm_count计数

调用free_mm()宏,归还到mm_cachep slab缓存

15.2.3 mm_struct域内核线程

内核线程没用进程地址空间,没有内存描述符,所以进程描述符中mm为空

内核线程不需要访问任何用户空间的内存

不需要自己的内存描述符和页表

为了避免切换地址空间,内核线程直接使用前一个进程的内存描述符

当进程调度时,mm域指向的地址空间被装载到内存,进程描述符中的active_mm域被更新

内核mm域为空,当内核线程被调度时,会保留前一个进程的地址空间,内核更新进程描述符中的active_mm为前一个进程的mm

需要时,内核线程可使用前一个进程的页表,仅使用地址空间中和内核相关的信息(存在可以访问用户空间信息的可能)

15.3 虚拟内存区域

内存区域由vm_area_struct结构体描述,定义在<linux/mm_types.h>

虚拟内存区域 virtual memeoryAreas, VMAs

vm_area_struct描述了连续区间的独立内存范围

每个VMA可代表不同的内存区域:内存映射文件,进程用户空间栈

struct vm_area_struct{
    struct mm_struct        *vm_mm;
    unsigned long             vm_start;
    unsigned long             vm_end;
    struct vm_area_struct    *vm_next;
    pgprog_t                vm_page_prot;
    unsigned long            vm_flags;
    ...
    struct vm_operations_struct    &vm_ops;
    ...
}

每个VMA对其相关的mm_struct都是唯一的

15.3.1 VMA标志

<linux/mm.h>

包含在vm_flags域内

反映了内核处理页面需要遵守的行为准则,而不是硬件要求

标志 对VMA以及页面的影响
VM_READ,VM_WRITE,VM_EXEC
VM_SHARED 页面可共享
VM_MAYREAD,… READ,WRITE,EXEC可设置
VM_GROWSDOWN 可向下增长
VM_SHM 可做共享内存
VM_IO 页面映射设备IO空间
VM_SEQ_READ 可能被连续访问
VM_DONTCOPY 不能在fork时被拷贝
VM_RESERVED 区域不能被换出

页面的权限还要和访问权限配合

VM_IO常在驱动设备执行的mmap()函数中设置,同时表示内存区域不能被包含在任何进程的core dump中

VM_SEQ_READ,VM_RAND_READ决定内核是否有必要预读取文件

15.3.2 VMA操作

struct vm_operations_struct{
    //内存区域被加入地址空间
    void(*open)(struct vm_area_struct *);
    //内存区域被删除
    void(*close)(struct vm_area_struct *);
    //没有出现在物理内存的页面
    int(*fault)(struct vm_area_struct *, struct vm_fault *);
    //只读被写
    int(*page_mkwrite)(struct vm_area_struct *, struct vm_fault *);
    //get_user_pages()调用失败,access_process_vm()调用
    int(*access)(struct vm_area_struct *,unsigned long, void*, int, int);
}

15.3.3 内存区域的树形和链表结构

15.3.4 实际使用中的内存区域

cat /proc/1426/maps可查看内存区域

不可写的内存区域可以只保留一份映射,所有进程的libc库在物理内存中实际只需要一份

这样的方式能节约大量的内存

没有映射文件的内存区域设备标志为00:00,索引节点标志也为0,这就是零页——零页映射的内容全0

15.4 操作内存区域

15.4.1 find_vma()

内存地址属于哪个区域

先检查mmap_cache,再搜索红黑树

15.4.2 find_vma_prev()

返回第一个小于addr的VMA

15.4.3 find_vma_intersection()

返回第一个和指定区间相交的VMA

15.5 mmap()和do_mmap():创建地址区间

do_mmap:创建一个新的线性地址区间

unsigned long dommap(struct file*file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long offset)

file取NULL,offset=0,这次映射没有文件相关:匿名映射,否则文件映射

addr:可选

用户空间通过mmap()系统调用获取do_mmap()功能

15.6 mummap()和do_mummap()

删除地址空间

15.7 页表

内核正确设置页表的前提下,硬件才能操作他们

每个进程字节的页表,内存描述符的pgd域指向页全局目录,然后三级页表

页表对应的结构体依赖于具体的体系结构,asm/page.h

页表的管理时内核的关键部分,不断改进,2.6:

  1. 高端内存分配页表
  2. 写时拷贝共享页表,(不改就不分配新的)

文章作者: N1co5in3
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 N1co5in3 !
  目录