Linux的内存管理(一)

Linux的内存管理(一)

1.存储器基本知识

内存,是一种物理存储器,这里指的是随机存取存储器,即RAM,现在普遍使用的是DDR SDRAM。
它是CPU能直接寻址的存储空间,用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据
首先我们要区分物理存储器和地址空间。
存储地址空间是指对存储器编码(编码地址)的范围。
CPU通过总线来访问各种存储器(高速缓冲存储器,内存,外存,外设等),由于现代CPU地址总线多为32位,因此地址空间可达2的32次方,即4GB。
 
从上图(通用PC)中可以看到,各类存储器通过总线与CPU相连,所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器,每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。
CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据

对于外设,CPU通过读写设备上的寄存器来管理和使用,外设寄存器也称为"I/O端口".
IO端口有两种编址方式:独立编址和统一编制。
统一编址:外设接口中的IO寄存器(即IO端口)与主存单元一样看待,每个端口占用一个存储单元的地址,将主存的一部分划出来用作IO地址空间,统一编址也称为“I/O内存”方式,外设寄存器位于“内存空间”
独立编址:也称单独编址,即IO地址与存储地址分开独立编址,I/0端口地址不占用存储空间的地址范围,这样,在系统中就存在了另一种与存储地址无关的IO地址,CPU也必须具有专用与输入输出操作的IO指令(IN、OUT等)和控制逻辑。
x86 CPU采用的是独立编址,存在IO空间的概念; 而大多数嵌入式微控制器如ARM、PowerPC等并不提供I/O空间,仅有内存空间,可直接用地址、指针访问。
Linux为了兼容不同的CPU,于是它采用一种新的方法,将基于I/O映射方式的或内存映射方式的I/O端口通称为“I/O区域”(I/O region)。
不论你采用哪种方式,都要先申请IO区域:request_resource(),结束时释放 它:release_resource()。

对外设的访问
1、访问I/O内存的流程是:request_mem_region() -> ioremap() -> ioread8()/iowrite8() -> iounmap() -> release_mem_region() 。
2、访问I/O端口有2种途径:I/O映射方式(I/O-mapped)、内存映射方式(Memory-mapped)。前一种途径不映射到内存空间,直接使用 intb()/outb()之类的函数来读写IO端口;后一种MMIO是先把IO端口映射到IO内存(“内存空间”),再使用访问IO内存的函数来访问 IO端口。

2.MMU和Cache

现代处理器中通常包含的MMU(Memory Management Unit,内存管理单元)和Cache(缓存)来协助处理器进行存储空间的访问,从而满足现代操作系统多任务的需求。
 
MMU的作用在于将CPU发出的虚拟地址(VA)翻译成物理地址(PA),即进行地址转换;同时提供硬件机制的内存访问权限检查。
Cache的出现是由于CPU与内存的速度不匹配,为了进可能的发挥CPU的高速度。

不同处理器实现的MMU和Cache差异较大,比较经典的是x86系统和RISC系列处理器,两者不同之处在于是否分段。
- x86系列处理器
x86架构处理器的MMU通常有分段和分页,于是有了三种概念, 逻辑地址,线性地址,物理地址
逻辑地址: 机器语言指令仍用这种地址指定一个操作数的地址或一条指令的地址。这种寻址方式把程序分为若干段。每个逻辑地址都由一个段和偏移量组成。
线性地址: 线性地址是一个32位的无符号整数,可以表达高达232(4GB)的地址。通常用16进制表示线性地址,其取值范围为0x00000000~0xffffffff。
物理地址: 也就是内存单元的实际地址,用于芯片级内存单元寻址。物理地址也由32位无符号整数表示。
- RISC系列处理器 
与x86系列不同,
RISC系列处理器对分段的支持并不好,一般只存在分页的概念

操作系统对页表的使用:
1. 操作系统在初始化或分配、释放内存时会执行一些指令在物理内存中填写页表,然后用指令设置MMU,告诉MMU页表在物理内存中的什么位置。
2. 设置好之后,CPU每次执行访问内存的指令都会自动引发MMU做查表和地址转换操作,地址转换操作由硬件自动完成,不需要用指令控制MMU去做。

3.NUMA

习惯上认为计算机内存是一种均匀,共享的资源。在忽略硬件高速缓存作用的情况下,期望不管内存单元和cpu处于何处,cpu对内存单元的访问都需要相同的时间,即统一内存架构(Unified Memory Architecture简称UMA)
然后,在一些体系结构(MIPS等)和SMP中,UMA并不成立,于是引入NUMA(Linux的NUMA技术).

Non Uniform Memory Access Achitecture,即非一致性内存访问,在这种模型中,给定CPU对不同内存单元访问的时间可能不一样,系统的物理内存被划分为几个节点(node)。
在一个单独的node中,任一给定CPU访问内存单元所需的时间都是相同的;然后,对不同的CPU,这个时间可能不同。

4.分页机制

MMU的存在使得对内存管理的方式多种多样,在操作系统的课程中,我们知道有分区式管理、页式管理、段式管理和段页式管理等方式,现代操作系统在实现时多采用分段和分页的机制,Linux在为了简化对内存的管理,同时兼容不同处理器架构,采用分页机制来实现内存管理。

一个内存单元的可见长度是8bits,即一个字节。
分页机制管理的对象是固定大小的存储块,称之为页(page),page的大小可以由MMU相关寄存器进行设置,但通常为4K字节大小,并在4K字节的边界上对齐,即每一页的起始地址都能被4K整除。
分页机制把整个线性地址空间及整个物理地址空间都看成由页组成,在线性地址空间中的任何一页,可以映射为物理地址空间中的任何一页,我们将物理空间中的一页叫做一个页面或页框(page frame)。

为了保存从页到物理页框的映射,引入了页表(Page Table),即内核需要设置好页表并提供给MMU。
一个32位逻辑地址空间的计算机系统,页大小为4KB,那么页表有一百万条目。假设每个条目占4B,则需要4MB物理地址空间来存储页表本身。于是引入多级页表,利用多级页表,可以减少页表所占用的空间。
页表中的内容即页表项,保存了物理页面的基地址和权限及是否允许cache的标志

由于页表存储在主存储器中,查询页表所付出的代价很大,由此产生了TLB(Translation Lookaside Buffer)。
TLB是一块高速缓存,它保存了N(不同处理器该指不同,mips和x86为32)个处理器最近使用的页表。
当CPU进行存储器访问时,先检查要访问的页面是否在TLB中,如果在,就不必经过多级访问; 如果不在,再进行多级访问。

有的CPU采用二级页表结构(如MIPS与i386),但有些CPU采用三级,甚至四级结构。
Linux为了兼容这些CPU,采用四级页表结构:
- PGD : Page Global Directory,页全局目录,是顶级页表。
- PUD : Page Upper Directory,页上级目录,是第二级页表
- PMD : Page Middle Derectory,页中间目录,是第三级页表
- PTE : Page Table Entry,页表,最后一级页表。指向物理页面
See struct mm_walk

从页表项获得物理地址:
以x86处理器二级页表为例,
逻辑地址格式如下:
 
0 -11位:页内偏移OFFSET
12-21位:页面表偏移PT
22-31位:页面目录偏移PGD
寻址过程如下:
1)操作系统从寄存器CR3获得当前页面目录指针(基地址);
2)基地址+页面目录偏移 ->页面表指针(基地址);
3)页面表指针+页面表偏移 –> 内存页基址;
4)内存页基址+页内偏移 –> 具体物理内存单元。
显然,12位的页内偏移可以寻址4K,所以一张内存页为4K;而总共可寻内存为4G=2^10 * 2^10 * 2^12;因此在32位机器上内存上限一般为4G。

 

5.Linux内存管理和布局

Linux 内核支持三种memory model
Flat Memory 平坦内存模式。                   CONFIG_FLATMEM,这是最常见的模式
Discontiguous Memory 非接触式内存模式。 CONFIG_DISCONTIGMEM , 为了支持NUMA
Sparse Memory 稀疏内存模式。                CONFIG_SPARSEMEM ,主要用来支持内存热插拔。

Linux内核在管理内存时将物理内存从逻辑上划分为节点(node),内存管理区(zone),页框(frame page)三级结构。
物理内存先被划分为node,每个node关联一个CPU,各个node又被划分几个zone,在一个zone中则是页框。
frame page是内存管理的基本单位。 
 
内存管理区通常有DMA、Normal和HighMem这三种( See enum zone_type)。

- 空闲内存区域(free area):是内存页区内连续2^order页空闲内存组成的内存区域。

- 节点            用pg_data_t结构体来描述      /* mmzone.h */
- 内存管理区    用zone结构体来描述              /* mmzone.h */
- 页框            用page结构体来描述             /* mm_type.h */

页框(struct page)描述了物理页的状态信息,所有的页框用一个数据
每个内存介质维护了一个mem_map,为介质中的每一个物理页面建立了一个page结构与之对应,以便管理物理内存。
每个zone记录着它在mem_map上的起始位置。并且通过free_area串连着这个zone上空闲的page。物理内存的分配就是从这里来的,从 free_area上把page摘下,就算是分配了。(内核的内存分配与用户进程不同,用户使用内存会被内核监督,使用不当就"段错误";而内核则无人监督,只能靠自觉,不是自己从free_area摘下的page就不要乱用。)

前面我们知道,针对不同的用途
Linux将内存管理区分为DMA、Normal和HighMem这三种

  • DMA:  该区域的物理页面专门供I/O设备的DMA使用。因为某些硬件设备不使用内存映射机制(MMU), 而是需要直接访问内存,故从物理地址空间专门划分一段区域用于DMA(对于X86, 0~16MB)。
  • NORMAL:  该区域的物理页面是内核能够直接使用的(对于X86, 16~896MB)
  • HIGHMEM:  该区域即为高端内存,该部分包含的页框不能经由同样的线性映射被内核直接访问。注意: HIGHMEM 可能不存在的, 比如物理内存空间没有达到HIGHMEM的大小(对于X86为>896MB,而对于MIPS则为>512M [HIGHMEM_START]),同时在64位机器上也是没有高端内存的.

对于DMA和NORMAL部分的内存,我们可以统称为低端内存
低端内存和高端内存出现的原因:
在采用MMU的系统中,Linux使用线性地址来访问内存空间,对于32位的CPU,最大能访问的地址空间为4GB,即为线性地址空间。
而Linux又将这4G的线性地址空间分为用户空间和内核空间,在二者的上下文中使用同样的映射。
    X86中将1GB~4GB分配给用户空间,0~1GB分配给内核空间
    MIPS中将0~2GB分配给用户空间,2GB~4GB(实际可用的是2GB~2.5GB)分配内核空间
Linux内核空间采用线性映射来通过虚拟地址来访问物理地址,即 物理地址 = 虚拟地址 - PAGE_OFFSET
物理地址与内核的虚拟地址只差一个偏移量。所以,当物理内存大于内核可用范围时(X86为1G, MIPS为512M),物理内存无法全部映射到内核线性地址空间

虚拟存储空间布局:
MMU实现虚拟存储空间到物理存储空间映射功能,而虚拟存储空间与物理存储空间的映射关系则是由Linux内核来管理的。

说明:

1)线性地址空间:是指Linux系统中从0x00000000到0xFFFFFFFF整个4GB虚拟存储空间。

2)内核空间:内核空间表示运行在处理器最高级别的超级用户模式(supervisor mode)下的代码或数据

3)用户空间:每个进程都有一个独立用户空间,所以用户空间由每个进程独有

4)内核逻辑地址空间:是指从PAGE_OFFSET到high_memory之间的线性地址空间,是系统物理内存映射区,它映射了全部或部分(如果系统包含高端内存)物理内存。内核逻辑地址空间中的地址与RAM内存物理地址空间中对应的地址只差一个固定偏移量(3G),如果RAM内存物理地址空间从0x00000000地址编址,那么这个偏移量就是PAGE_OFFSET。

5)低端内存:内核逻辑地址空间所映射物理内存就是低端内存,低端内存在Linux线性地址空间中始终有永久的一一对应的内核逻辑地址,系统初始化过程中将低端内存永久映射到了内核逻辑地址空间,为低端内存建立了虚拟映射页表。
低端内存内物理内存的物理地址与线性地址之间的转换可以通过__pa(x)和__va(x)两个宏来进行,
__pa(x)将内核逻辑地址空间的地址x转换成对应的物理地址,相当于__virt_to_phys((unsigned long)(x)),
__va(x)则相反,把低端物理内存空间的地址转换成对应的内核逻辑地址,相当于((void *)__phys_to_virt((unsigned long)(x)))。

6)高端内存:高端内存在Linux线性地址空间中没有没有固定的一一对应的内核逻辑地址,系统初始化过程中不会为这些内存建立映射页表将其固定映射到Linux线性地址空间,而是需要使用高端内存的时候才为分配的高端物理内存建立映射页表,使其能够被内核使用,否则不能被使用。高端内存的物理地址于现行地址之间的转换不能使用上面的__pa(x)和__va(x)宏。
对于高端内存无法采用简单的线性映射来访问,Linux提供三种不同的映射机制将页框映射到高端内存:
   1、非连续内存分配: 通过 vmalloc()在"内核动态映射空间"(VMALLOC_START~VMALLOC_END)申请内存的时候,就可能从该区域获得页面
   2、永久内核映射:  内核专门为此留出一块线性空间,从PKMAP_BASE~FIXADDR_START作为永久内核。通过 kmap()/kunmap(),可以把一个page映射/释放到这个空间
   3、临时内核映射: 通过 kmap_atomic()/kunmap_atomic 可建立/取消临时映射 

6)地址映射:内核在分配page、建立地址映射的过程中,使用的是虚拟地址。内核代码所访问的地址都是虚拟地址,因为CPU指令接收的就是虚拟地址(地址映射对于CPU指令是透明的)。但是,建立地址映射时,内核在页表里面填写的内容却是物理地址,因为地址映射的目标就是要得到物理地址。那么,内核怎么得到这个物理地址呢?其实,上面也提到了,mem_map中的page就是根据物理内存来建立的,每一个page就对应了一个物理页。于是我们可以说,虚拟地址的映射是靠这里page结构来完成的,是它们给出了最终的物理地址。然而,page结构显然是通过虚拟地址来管理的(前面已经说过,CPU指令接收的就是虚拟地址)。那么,page结构实现了别人的虚拟地址映射,谁又来实现page结构自己的虚拟地址映射呢?解决这个问题的办法是内核空间的页表项是写死的。在内核初始化时,内核的地址空间就已经把地址映射写死了。page结构显然存在于内核空间,所以它的地址映射问题已经通过“写死”解决了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值