当前位置:   article > 正文

03_007linux内存管理架构以虚拟内存空间布局架构(用户地址空间+内核地址空间)_linux内存管理子系统

linux内存管理子系统

前言

介绍一下内存管理子系统架构
因为cpu访问的地址都是虚拟地址 ,发送虚拟地址给到 mmu mmu经过操作把虚拟地址映射为物理地址继续完成cpu的一些操作
所以同时介绍一下 虚拟地址空间的布局 虚拟地址空间分为
用户地址空间+内核地址空间 同时也介绍这两个空间的分布
在这里插入图片描述

内存管理子系统架构

内存管理子系统架构可以分为:用户空间、内核空间及硬件部分3个层面,具体结构如
下图所示:
1、用户空间:应用程序使用malloc()申请内存资源/free()释放内存资源。
2、内核空间:内核总是驻留在内存中,是操作系统的一部分。内核空间为内核保留,不允许应用程序读写该区域的内容或直接调用内核代码定义的函数。
3、硬件:处理器包含一个内存管理单元 (Memory Management Uint,MMU)的部件,负责把虚拟地址转换为物理地址。
下面的这个架构图 但是就为了说明 中间有个系统调用在kernel和app的c库之间 在内核层
在这里插入图片描述

内核空间模块分布图

简单把内核氛围下面的模块
Process shedule 进程管理调度运行
Mem 内存管理调度 负责内存资源
Ipc 系统中进程间通信
VFS 虚拟文件系统,把磁盘硬盘进行抽象,通过统一的文件接口进行操作
Net 网络子系统 管理网络设备 各种网络标准
Dev 驱动
app通过各种系统调用在用户空间操作到内核空间
在这里插入图片描述

用内存开辟举个例子,看看app怎么通过系统调用操作到内核

比如应用程序app需要开辟空间 需要通过c的一些标准函数 malloc new free等 操作到各种库提供的函数
这些函数使用系统调用 sys_brk来继续进行内存的分配
到虚拟内存管理 页错误异常处理 页表管理 内存控制 内存碎片整理 页回收 内存耗尽
分配器 都是这些内核里面的模块

在这里插入图片描述
1、用户空间
相当于应用程序使用malloc()申请内存,通过free0释放内存。malloc()/free()是 glibc库的内存分配器ptmalloc提供的接口,
ptmalloc使用系统调用brk或mmap向内核以页为单位申请内存,然后进行分成很小内存块分配给对应应用程序。
2、内核空间
虚拟内存管理负责从进程的虚拟地址空间分配虚拟页, sys_brk来扩大或收缩堆,sys mmap用来在内存映射区域分配虚拟页,
sys
munmap用来释放虚拟页。页分配器负责分配物理页,使用分配器是伙伴分配器
内核空间扩展功能,不连续页分配器提供分配内存的接口vmalloc和释放内存接口vfree。在内存碎片化的时候,申请连续物理页的成功
比较低,可以申请不连续的物理页,映射到连续的虚拟页,即虚拟地址连续而物理地址不连续。
内存控制组用来控制进程占用的内存资源。当内存碎片化的时候,找不到连续的物理页,内存碎片整理通过迁移方式得到连续的物理
页。在内存不足的时候,页回收负责回收物理页。
3、 硬件
MMU包含一个页表缓存, 保存最近使用过的页表映射,避免每次把虚拟地址转换为物理地址都需要查询内存中的页表。解决处理器执行速
度和内存速度不匹配问题,中间增加一个缓存。一级缓存分为数据缓存和指令缓存。 二级作用协调一级缓存和内存之间的工作效率。

代码详解

所以代码在系统调用中怎么传递的
在这里插入图片描述

在执行结果看看内存分配的对应地址
使用cat /proc/进程号/maps 找到对应进程的一个内存分布图
下面的heap(堆)中的内存分布和代码中创建的一样

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
	// 使用 sbrk 系统调用申请 堆内存
    int *p = sbrk(0);

	// 记录该堆内存地址
    int *p_old = p;

	// 继续为 申请的堆内存, 申请 1024 字节内存
    p = sbrk(1024);

	// 打印进程 ID , PID 
    printf("pid : %d\n", getpid());
    
	// 打印 申请的 堆内存地址 , 发现地址没有变化
    printf("p_old : %p \np : %p \n", p_old, p);

	// 申请新的 堆内存
    int *p_new = sbrk(0);

	// 打印新的 堆内存地址
    printf("p_new : %p\n", p_new);

	// 此处死循环阻塞, 方便查看 /proc/pid/maps 中的信息
	// 进程退出后 , 进程相关内存信息也会同时销毁
    while (1);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

sbrk 返回的指针 p , 在第二次申请内存时 , 指针始终没有改变 , 一直都是 0x0000 557f fee6 6000 地址 ;
如果使用新的指针 p_new 接收 sbrk 系统调用返回的堆内存指针 , 则分配的是新的地址 ;
在这里插入图片描述

虚拟空间的布局架构

因为目前应用程序没有那么大的内存需求,所以ARM64处理器不支持完全的64位虚拟
地址。
在ARM64架构的Iinux内核中,内核虚拟地址和用户虚拟地址的宽度相同。
所有进程共享内核虚拟地址空间,每个进程有独立的用户虚拟地址空间,同一个线程
组的用户线程共享用户虛拟地址空间,内核线程没有用户虚拟地址空间。
在这里插入图片描述
因为分成了三个区域一个个讲讲
先说说上面地址划分的定义在哪

进程的用户虚拟空间的起始地址是0,长度是TASK_ SIZE,由每种处理器架构定义自己
的宏TASK_ SIZE。ARM64架构定义的宏TASK_ SIZE如下:
32位用户空间程序: TASK_ SIZE的值是TASK_ SIZE_ 32,即0x100000000, 等4GB。
64位用户空间程序: TASK_ SIZE的值是TASK_ SIZE _64,即2^VA BITS字节。
在这里插入图片描述
代码的详细定义如下
在这里插入图片描述
根据上面的宏把cpu访问的虚拟地址划分为了 用户虚拟地址空间和内核虚拟地址空间 下面进一步的查看不同空间的区域作用

进程用户虚拟地址空间划分

每一个进程的用户虚拟地址空间包含区域:
代码段、数据段、未初始化数据段;
动态库的代码段、数据段和末初始化数据段;
存放动态生成的数据的堆;
存放局部变量和实现函数调用的栈;
把文件区间映射到虚拟地址空间的内存映射区域;
存放在栈底部的环境变量和参数字符串。
task_struck是之前说的进程结构体 这次说的是mm_struct 进程虚拟地址空间中的用户虚拟地址结构体
在这里插入图片描述

在代码中的表示

mm_struct 如下表示 可以参数mm_struch 结构体 有各种数据段 代码段 映射区等起始地址和结束地址
linux下当前进程的用户空间所有的信息都在这里 是我们讲的内存描述符的主要成员
Tack_stucl -> mm_struct ->页表(pagetabel)->物理内存

struct mm_struct {
    struct vm_ area_ struct *mmap; //虚拟内存区域链表 !!!!!  一个用户虚拟地址空间由很多个 vm_area组成
    struct rb_ root mm_ rb; // 虚拟内存区域红黑树
    u32 vmacache_ seqnum;
    /* per-thread vmacache */
    #ifdef CONFIG_ MMU //在内存映射区域找到一-个没有映射的区域
        unsigned long (*get_ _unmapped_ area) (struct file *filp,
        unsigned long addr, unsigned long len,
        unsigned long pgoff, unsigned long flags) ;
    #endif
    unsigned long mmap_ base;
    //内存映射区域的起始地址
    unsigned long mmap_ legacy_ base;
    /* base of mmap area in bottom-up allocations */
    #ifdef CONFIG HAVE ARCH_ COMPAT_ MMAP_ BASES
        /* Base adresses for compatible mmap() */
        unsigned long mmap_ compat_ base;
        unsigned long mmap_ compat_ legacy_ base;
    #endif
    unsigned long task_ size;
    //用户虛拟地址空间的长度
    unsigned long highest_ vm_ end;
    /* highest vma end address */
    pgd_ t * pgd; //指向页全局目录,即第- -级页表
    /*mm_ _users: The number of users including userspace.
    Use mmget () /mmget_ not_ zero() /mmput() to modify. When this drops
    to 0 (i.e. when the task exits and there are no other temporary
    reference holders) ,we also release a reference on @mm_ count
    ★(which may then free the &struct mm_ struct if @mm_ count also
    ★dropsto0)。
    */
    atomic__tmm_users;//共享一个用户虚拟地址空间的进程的数量,也就是线程组包含的进程的数量
    atomic_ t mm_ count; //内存描述符的引用计数
    atomic_ 1ong_ t nr_ ptes;
    /* PTE page table pages */
    #if CONFIG_ PGTABLE LEVELS > 2
    atomic_ 1ong_ t nr_ pmds;
    /* PMD page table pages */
    #endif
    int map_ count;
    /* number of VMAsS */
    spinlock_ t page_ table_ lock;
    /* Protects page tables and some counters */
    struct rw semaphore mmap sem;
    struct list_ head mmlist;
    /* List of maybe swapped mm's. These are globally strung
    together off init_ mm.mmlist, and are protected
    py mmlist_ lock
    |*/
    unsigned long hiwater_ rss; // 进程所拥有的最大页框数
    unsigned long hiwater_ _vm; 1/ 进程线性区中最大页数
    unsigned long total_ vm; 
    //进程地址空间的大小(页数)
    unsigned long locked_ _vm; // 锁住而不能换出的页的个数
    unsigned long pinned_ _vm;
    /* Refcount pe rmanently 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;
    //代码段的起始地址和结束地址,数据段的起始地址和结束地址
    unsigned long start_ code, end_ code, start_ data, end_ data;
    //堆的起始地址和结束地址,栈的起始地址
    unsigned long start_ brk, brk, start_ stack;
    //参数字符串的起始地址和结束地址,环境变量的起始地址和结束地址
    unsigned long arg_ start, arg_ end, env_ start, env_ end;
    unsigned long saved_ auxv [AT_ VECTOR_ SIZE] ; /* for /proc/PID/auxv */
    /* Architecture-specific MM context */
    mmn_ context_ t context; //处理器架构特定的内存管理上下文
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

上面的mm_strcut是当前进程中完整的用户空间描述结构体 但是完整的用户空间是由一个个区间 stuct vm_area组成
所以一个进程的虚拟地址空间主要由两个数据结构进行描述。-个是最高层次的mm_ struct,较高层次的
vm_ area_ struct。 最高层次mm_ struct结构描述一个进程整 个虚拟地址空间。较高层次结构描述虚拟地址空
间的一个区间(称为虚拟区)。每个进程只有一个mm_ struct结构, 在每个进程的task_ struct结构中,有一
个专门]用来指向该进程的结构。mm_ struct结构是对整个用户空间的描述。
在这里插入图片描述

内核虚拟地址地址空间布局

再次说一遍 在ARM64架构的Iinux内核中,内核虚拟地址和用户虚拟地址的宽度相同。
所有进程共享内核虚拟地址空间,每个进程有独立的用户虚拟地址空间,同一个线程
组的用户线程共享用户虛拟地址空间,内核线程没有用户虚拟地址空间。
在这里插入图片描述
影子区域的起始地址是内核空间的起始地址长度是内核虚拟空间地址的8/1

内存布局在代码中的表述如下

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/704738
推荐阅读
相关标签
  

闽ICP备14008679号