内存是什么
- 内存又称为主存,是CPU能直接寻址的存储空间,由半导体器件制成。
- 内存的特点是存取速率快。
内存的作用
- 暂时存放CPU的运算数据
- 硬盘等外部存储器交换的数据。
- 保障CPU计算的稳定性和高效性。
CPU如何访问内存?
首先CPU,在访问内存的时候需要通过MMU把虚拟地址转化为物理地址,然后通过总线访问内存。MMU开启后CPU看到的所有的地址都是虚拟地址,CPU把这个虚拟地址发给MMU后,MMU会通过页表在页表里查出这个虚拟地址的物理地址是什么,从而去访问DDR(内存条)
物理地址空间布局
Linux系统在初始化时,会根据实际物理内存的大小,为每个物理页面创建一个page对象,所有的page对象构成一个mem_map数组。进一步,针对不同的用途,Linux内核将所有的物理页面划分到3类内存管理区,分别为ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。
- ZONE_DMA的范围是0-16M,该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的 物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。
- ZONE_NORMAL的范围是16M-896M,该区域的物理页面是内核能够直接使用的。
- ZONE_HIGHMEM的范围是896M---结束,该区域即为高端内存,内核不能直接使用。
Linux物理地址和虚拟地址的关系
Linux将4G的线性地址分为2部分,0—3G为user space,3G—4G为kernel space。
由于开启了分页机制,内核想要访问物理地址空间的话,必须先建立映射关系,然后通过虚拟地址来访问。为了能够访问所有的物理地址空间,就要将所有的物理空间映射到1G的内核空间,这显然不可能。内核将0—896M的物理空间一对一映射到自己的线性地址空间中,这样可以随时访问ZONE_DMA和ZONE_NORMAL里的物理页面;此时剩下的128M线性地址空间不足以完全映射所有的ZONE_HIGHMEM,Linux采取了动态映射的方法,即按需的将ZONE_HIGHMEM里的物理页面映射到kernel space的最后128M地址空间里,使用完之后释放映射关系,以供其他物理页面映射。
内存地址------用户态&内核态
用户态切换到内核态的3种方式:系统调用 异常 外设中断
区别:每个进程都有完全属于自己的,独立的,不被干扰的内存空间;用户态的程序就不能随意操作内核态地址空间,具有一定的安全保护作用;内核态线程共享内核地址空间。
内存地址-------MMU地址转换
- MMU是一种硬件电路,它包含两个部件,一个是分段部件,一个是分页部件
- 分段机制把一个逻辑地址换换为线性地址
- 分页机制把一个线性地址转换为物理地址
内存地址-----分段机制
- 段选择符
为了方便快速检索中段选择符,处理器提供了6个分段寄存器来缓存段选择符,他们是CS,SS,DS,ES,FS和GS。
段的基地址:在线性空间中段的起始地址。
段的界限:在虚拟地址空间中,段内可以使用的最大偏移量。
- 分段实现
逻辑地址的段寄存器中值提供段描述符,然后从段描述符中段基址和段界限,然后加上逻辑地址的偏移量,就得到了线性地址。
内存机制----------分页机制(32位)
分页机制是在分段机制后进行的,他进一步将线性地址转换为物理地址。
10位页3目录,10位页表项,12位页偏移地址
单页的大小为4KB
用户地址空间
- TEXT:代码段可执行代码,字符串字面值,只读变量
- DATA:数据段,映射程序中已经初始化的全局变量
- BSS段:存放程序中未初始化的全局变量
- HEAP:运行时的堆,在程序运行中使用malloc申请的内存区域
- MMAP:共享库及匿名文件的映射区域
- STACK:用户进程栈
Linux用户空间虚拟地址分布
用户进程的代码区一般是从虚拟地址空间的0X08048000开始,这是为了便于检查空指针。
内核地址空间
- 直接映射区:线性空间中从3G开始最大896M的区间,为直接内存映射区
- 动态内存映射区:该区域可访问高端内存
- 固定映射区:该区域和4G的顶端只有4K的隔离带,其每个地址项都服务于特定的用途。如:ACPI_BASE等。
进程内存空间
- 用户进程通常情况只能访问用户空间的虚拟地址,不能访问内核空间的虚拟地址
- 内核空间是由内核负责映射,不会跟着进程变化;内核空间地址有自己对应的页表,用户进程各自有不同的页表。
Linux内存分配算法
- 内存碎片
- 基本原理
- 产生原因:内存分配较少,并且分配的这些小的内存生存周期又较长,反复申请后将产生内存碎片的出现。
- 优点:提高分配速度,便于内存管理,防止内存泄漏
- 缺点:大量的内存碎片会使系统缓慢,内存使用率低,浪费大
- 如何避免内存碎片
- 少用动态内存分配的函数,尽量使用栈空间
- 分配内存和释放内存尽量在同一个函数
- 尽量一次申请较大的内存,而 不要反复申请小内存
- 尽可能申请大块的2的指数幂大小的内存空间
- 外部碎片避免------伙伴系统算法
- 内部碎片避免----slab算法
- 自己进行内存管理工作,设计内存池
伙伴系统算法-------组织结构
- 概念
- 为内核提供了一种用于分配一组连续的页而建立的一种高效的分配策略,并有效的解决了外部碎片问题
- 分配的内存区是以页框为基本单位的
- 外部碎片
外部碎片是指还没有被分配出去不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域
- 组织结构
把所有的空闲页分组为11个块链表,每个链表分别包含大小为1,2,4,8,16,32,64,128,256,1024个连续页框的页块。最大可以申请1024个连续页,对应4MB的大小的连续内存。
伙伴系统算法----申请和回收
- 申请算法
- 申请2^i个页块存储空间,如果2^i对应的块链表有空闲页块,则分配给应用
- 如果没有空闲页块,则查找2^(i+1)对应的块链表是否有空闲页块,如果有,则分配2^i块链表节点给应用,另外2^i块链表插入到2^i对应的链表中
- 如果2^(i+1)块链表中没有空闲页块,则重复上边步骤,直到找到有空闲页块的块链表
- 如果仍然没有,则返回内存分配失败
- 回收算法
- 释放2^i个页块存储空间,查找2^i个页块对应的块链表,是否有与其物理地址是连续的页块,如果没有,则无需合并
- 如果有,则合并成 2^(i+1)的页块,以此类推,继续查找下一级块链表,直到不能合并为止
如何分配4M以上内存?
- 为何限制大块内存分配
分配的内存越大,失败的可能性越大
大块内存使用场景少
- 内核中获取4M以上大的内存的方法
- 修改MAX_ORDER,重新编译内核
- 内核启动选型传递“mean =”参数,如“mean=80M”,预留部分内存;然后通过
- 在start_kernel中mean_init函数之前调用alloc_boot_mem函数预分配大块内存,需要重新编译内核
- Vmalloc函数,内核代码使用它来分配在虚拟内存中连续但在物理内存中不一定连续的内存
伙伴系统-----反碎片机制
- 不可移动页
这些页在内存中有固定的位置,不能够移动,也不可回收
内核代码段,数据段,内核kmalloc()出来的内存,内核线程占用的内存等
- 可回收页
这些页不可移动,但可以删除。内核在回收页占据了太多的内存时或者内存短缺时进行页面回收
- 可移动页
这些页可以任意移动,用户空间应用程序使用的页都属于该类别。他们是通过页表映射的
当他们移动到新的位置,页表项也会相应的更新
Slab算法-------基本原理
- 基本概念
- Linux所使用的slab分配器的基础是Jeff Bonwick为SunOS操作系统首席引入的一种算法
- 他的基本思想是将内核中经常使用的对象放到高速缓存中,并且由系统保持为初始的可利用状态。比如进程描述符,内核中会频繁的对此数据进行申请和释放
- 内部碎片
已经被分配出去的空间大于请求所需的内存空间
- 基本目标
减少伙伴算法在分配小块连续内存时所产生的内部碎片
将频繁使用的对象缓存起来,减少分配、初始化和释放对象的时间开销
通过着色技术调整对象以更好的使用硬件高速缓存
博客里边的图都是来自公众号人人都是极客