Linux进程地址空间

系统中每个用户空间进程所看到的内存

进程地址空间由可寻址的虚拟内存组成。

线程共用地址空间

一个进程的地址空间与另一个进程的地址空间即使有相同的内存地址,也是互不相干的,我们称之为线程。

进程可以访问的合法空间称为内存区域,通过内核,进程可以申请动态地添加或者减少内存区域。
如果一个进程访问了不属于自己的区域,那么就会报段错误。

内存区域的按逻辑段(用途)划分

  • 可执行文件的代码的内存映射,代码段
  • 可执行文件的已初始化全局变量的内存映射,数据段(data section)
  • 未初始化的全局变量的内存映射,bss段(block started by symbol)
  • 栈的内存映射(进程的内核栈由内核维护,就是一张页)
  • 堆的内存映射
    进程包含的c库或者动态链接的共享库的数据段,代码段和bss也会载入进程的地址空间。

内存描述符

struct mm_struct {
    // 进程的虚拟地址空间链表的头指针
    struct vm_area_struct *mmap;

    // 进程的虚拟地址空间红黑树
    struct rb_root mm_rb;

    // 进程的用户空间的基地址
    unsigned long mmap_base;

    // 进程的用户空间大小
    unsigned long task_size;

    // 代码段的起始和结束地址
    unsigned long start_code, end_code;

    // 数据段的起始和结束地址
    unsigned long start_data, end_data;

    // 堆的结束地址,表示动态内存分配的边界
    unsigned long brk;

    // 进程栈的底部地址
    unsigned long start_stack;

    // 进程命令行参数的起始和结束地址
    unsigned long arg_start, arg_end;

    // 进程环境变量的起始和结束地址
    unsigned long env_start, env_end;

    // 进程的常驻集大小,表示进程当前使用的物理内存的大小
    unsigned long rss;

    // 匿名页的常驻集大小,表示进程使用的匿名内存的物理页数量
    unsigned long anon_rss;

    // 进程虚拟内存的总大小
    unsigned long total_vm;

    // 用户和引用计数,用于管理 struct mm_struct 的生命周期
    atomic_t mm_users;
    atomic_t mm_count;

    // 并发访问 mmap 链表的信号量
    struct rw_semaphore mmap_sem;

    // 其他字段和操作省略...
};

mmap和mm_rb都存放的是全部虚拟内存区域,前者是链表,后者是红黑树。
在内存描述符(task_struct结构体中的,mm域就存放着mm_struct…
fork()函数利用copy_mm函数复制父进程的内存描述符。

撤销内存描述符

当进程退出时,内核会减少mm_struct的用户计量,如果用户计量为0,再减少mm_count使用计数,如果使用计数也为0,则说明这块内存没有使用者了,会将这个mm_struct结构体归还给slab缓存中。

mm_struct与内核线程

内核线程没有进程地址空间,所以内核线程对应的进程描述符中mm域为空。
事实上,这也是内核线程的真实含义,它们没有用户上下文,它们在用户空间没有页,在需要使用页时,他们会使用前一个进程的页表。

当一个进程被调度时,该进程的mm域指向的地址空间被装载到内存

虚拟内存区域

内存区域由vm_area_struct描述。
其描述了指定地址空间内连续区间上的一个独立内存范围。
内核将每个内存区域作为一个单独的内存对象管理(其按照逻辑划分,比如内存映射文件,进程用户空间栈)。
每个进程的虚拟地址空间被划分为多个不同的区域,每个区域都由一个 struct vm_area_struct 描述。

struct vm_area_struct {
    struct mm_struct *vm_mm;       // 指向拥有这个 VMA 的进程的 mm_struct
    unsigned long vm_start;         // VMA 的起始地址
    unsigned long vm_end;           // VMA 的结束地址(不包含在 VMA 内)
    pgprot_t vm_page_prot;          // VMA 的页面保护标志
    unsigned long vm_flags;         // VMA 的标志(页可读,可写,区域向下增长,页面被锁。。)

    struct vm_operations_struct *vm_ops;  // 指向 VMA 操作的结构
    unsigned long vm_pgoff;          // 偏移量,表示 VMA 的页偏移
    struct file *vm_file;            // 映射的文件
    void *vm_private_data;           // 私有数据指针,用于一些特殊映射
    。。。
};

vm_start区间首地址到vm_end区间的尾地址。
如果两个线程共享相同的地址空间,那么它们共享相同的vm_zrea_struct,因为相同的mm_strcut,有相同的vm_area_struct。
mm_struct的mmap和mm_rb包含完全相同的vm_area_struct,只是组织方式不同。

共享内存vm_area_struct

如果一片内存范围是共享的或者不可写的,那么这片内存用来共享读就是安全的。
所以c库在内存中仅仅需要占用1212KB空间,所有进程都只会来访问c库代码空间,节约了大量内存。

页表

应用程序使用的是虚拟内存,但是处理器操作的却是物理内存,这就需要地址转换,需要使用页表。
地址转换需要将虚拟地址分段,使每段虚拟地址都作为一个索引指向页表,而页表指向下一级页表
(多级页表)或者直接指向最终的物理页面。
每个进程都有自己的页表,其存在于mm_struct的pgd_t。

你可能感兴趣的:(Linux,linux,运维,服务器)