linux内存的管理主要分为两部分,地址管理和存储设备管理。下面针对这两部分介绍一下我对内存管理的理解。
硬件地址的基本概念
- DRAM域地址:是DRAM控制器所能访问的地址空间集合。
- PCI总线域地址:是PCI设备所能直接访问的地址空间集合。
- 存储器域地址:是CPU所能访问的地址空间集合。
结合下图对上面概念进行解释:
CPU访问DRAM域或PCI总线域地址空间时,都需要进行地址转换(将存储器域地址转换为相应域的地址)。例如:CPU访问DRAM域时,需要进行存储器域地址空间到DRAM域地址空间的转换(由DRAM控制器完成);CPU访问PCI总线域时,需要进行存储器域地址空间到PCI总线域地址空间的转换(由HOST主桥完成)。
在x86处理器系统中,会将DRAM域和PCI总线域映射到存储器域空间中,并且其大多数DRAM域中的地址与存储器域中的地址一一对应而且相等,而存储器域的PCI地址与PCI总线域的地址也一一对应而且相等。它们在存储器域空间的映射彼此独立,互不冲突,映射关系由BIOS提供(e820地址映射表)。
PCI设备访问DRAM域地址空间时,首先要经过HOST主桥将PCI总线域地址转换为存储器域地址,然后再由DRAM控制器将存储器域地址转换为DRAM域地址。
软件地址的基本概念
-
逻辑地址,是一个32位长的地址。所有进程地址都使用逻辑地址。
-
线性地址(也称为虚拟地址),是一个32位长地址,可以用来表示高达4G的地址,值的范围从0x00000000到0xffffffff。它是通过分段单元的硬件电路将逻辑地址转换为线性地址,即将逻辑地址加上一个段起始地址就得到了线性地址。在linux中,所有的段都从0x00000000开始,所以在linux下逻辑地址等于线性地址,即进程地址也是线性地址。后面我们就不再讨论逻辑地址,而都使用线性地址代表进程所使用的地址。
-
物理地址,是通过分页单元的硬件电路将线性地址转换为物理地址。该地址就是上面所说的存储器域地址,是CPU所能访问的地址空间的集合。
下面用图对上面概念进行解释:
在X86系统中,所有进程都使用的是逻辑地址,它要通过分段单元硬件电路转换为线性地址,再通过分页单元硬件电路转换为物理地址(即存储器域地址)。
分段单元的逻辑如下图所示(将逻辑地址转换为线性地址)
分页单元的逻辑如下图所示(将线性地址转换为物理地址)
地址管理
linux将线性地址空间分为user和kernel两个空间
-
USER线性地址空间,指的是可被应用层访问的线性地址空间。其大小(32位处理器) = 4G - KERNEL线性地址空间大小。
-
KERNEL线性地址空间,指的是可被内核使用的线性地址空间。其大小 = LOWMEM线性地址空间大小 + VMALLOC线性地址空间大小。
* LOWMEM线性地址空间,它占用了KERNEL线性地址空间中的一部分。其大小 = KERNEL线性地址空间大小 - VMALLOC线性地址空间大小 。 * LOWMEM功能,这段地址空间在启动时已做好地址映射,其映射的物理内存是连续的,并与物理内存地址一一对应。 * VMALLOC线性地址空间,它占用了KERNEL线性地址空间中的另一部分。其大小 = (KERNEL线性地址空间大小 - 物理内存大小) > __VMALLOC_RESERVE ?KERNEL线性地址空间 - 物理内存大小 : __VMALLOC_RESERVE * VMALLOC功能,用于在内核中分配线性地址连续,但物理地址不需连续的大内存区。这段地址空间初始化时未做任何地址映射。
linux内核线性地址空间变量的含义
-
PAGE_OFFSET = __PAGE_OFFSET = CONFIG_PAGE_OFFSET:这个值是用于划分USER线性地址和KERNEL线性地址的分界点。
-
__VMALLOC_RESERVE:表示为VMALLOC线性地址空间最小应保留的空间大小,默认是128M。这个值可在系统启动时通过内核参数vmalloc来改变。
-
VMALLOC_END:VMALLOC线性地址空间的结束地址。其值 = 最大线性地址 – 其它ROM内存空间大小
-
VMALLOC_START:VMALLOC线性地址空间的起始地址。其值 = VMALLOC_END - VMALLOC线性大小(最小为VMALLOC_RESERVE)
-
VMALLOC_OFFSET:固定大小为8M,用于再LOWMEM线性地址空间和VMALLOC线性地址空间之间建立一个隔离带,防止它们互相影响。
-
MAXMEM:其值为(
VMALLOC_END - PAGE_OFFSET - __VMALLOC_RESERVE
),表示内核能够直接映射的最大RAM容量,这个值也是相对固定的(与物理内存实际的大小没有关系),因为VMALLOC_END、PAGE_OFFSET、 __VMALLOC_RESERVE
是相对固定的。 -
MAXMEM_PFN:其值为PFN_DOWN(MAXMEM),表示内核能够直接管理的内存页个数。
-
MAX_ARCH_PFN:表示CPU最大可访问的内存页数。对应32位CPU,如果其开启了PAE,则MAX_ARCH_PFN=(1ULL<<(36-PAGE_SHIFT))。如果未开启PAE,则MAX_ARCH_PFN=(1ULL<<(32-PAGE_SHIFT))
-
max_pfn:是从bios发现的最大的能管理的内存页个数, 与实际内存大小有关,但它的大小不能超过MAX_ARCH_PFN。 当max_pfn <= MAXMEM_PFN时说明实际的物理内存比内核能够线性映射的内存数小,此时就不需要Highmem来管理了。此时就算开启了HIGHMEM,ZONE_HIGHMEM区能够管理的内存大小也是0。 当max_pfn > MAXMEM_PFN时说明实际的物理内存比内核能够线性映射的内存数大,这样多余的那部分物理内存就不能进行线性映射了,必须通过页表映射(即vmalloc来管理), 该物理内存应该由ZONE_HIGHMEM区来管理。而如果没有开启HIGHMEM选项,则这部分内存就丢失了,不再被管理。
-
max_low_pfn:低端内存(相对于highmem)的最大页数,这个就是ZONE_NORMAL区能管理的最大页数。 如果实际物理内存比内核能够线性映射的内存数小,max_low_pfn = max_pfn,即所有的内存都归低端内存区管理。 如果实际物理内存比内核能够线性映射的内存数大,max_low_pfn = MAXMEM_PFN,即低端内存区能够管理的最大值就是内核能够线性映射的最大值。
-
highmem_pages:高端内存的最大页数,这个是就是ZONE_HIGHMEM区管理的最大页数。其值为(max_fn – max_low_pfn)。
存储设备管理(即可使用物理内存空间的管理)
linux内核将物理内存空间进行功能区划分(划分时要参考地址管理中线性地址的分配),并通过page结构进行管理
-
ZONE_DMA物理内存空间,在x86体系结构上它是0~16M的内存范围。由page结构进行管理,这个区包含的页能被老设备执行DMA操作。
-
ZONE_NORMAL物理内存空间,在x86体系上它是16M~KERNEL->LOWMEM线性地址对应的内存范围。由page结构进行管理。 (内核在启动时,就已经将上面这两个区的内存空间映射到KERNEL线性地址空间中了。所以内核不需做任何操作,就可以直接访问这些物理内存。但为了保证所访问内存页没有被其它功能使用,因此在访问内存之前,要使用get_free_page()等函数获取空闲页,并将该页标记为使用。返回的物理页不需要做任何地址映射,仅仅是将物理地址加上内核空间偏移量就获得了其线性地址)
-
ZONE_HIGHMEM物理内存空间,其大小 = 实际物理内存大小 – KERNEL->LOWMEM线性地址空间大小。由page结构进行管理,用于管理物理内存大小超过KERNEL->LOWMEM线性地址空间大小以外的物理内存。这段内存空间没有被映射到内核线性地址空间,在使用时,要通过alloc_page()分配一个page页,然后再将该页映射到线性地址空间,这样才能通过线性地址访问。内核(通常用vmalloc()函数)将HIGHMEM物理内存页映射到VMALLOC线性地址空间,因此内核可使用的HIGHMEM物理内存大小与VMALLOC线性地址空间大小有关。另外,HIGHMEM物理内存页通常被映射到USER线性地址空间。
-
ZONE_NORMAL 与ZONE_HIGHMEM主要的区别是ZONE_NORMAL中的物理内存不需要做页表映射直接用就行了,其线性地址的最大值是MAXMEM。而ZONE_HIGHMEM中的物理内存需要做页表映射才能使用,其线性地址的范围是VMALLOC_START~VMALLOC_END。所以不能做线性映射的内存都归ZONE_HIGHMEM来管理了。如果开启的HIGHMEM选项,则ZONE_HIGHMEM的大小就是剩下的物理内存,如果没开起,则这部分物理内存就无法管理了。
来源:oschina
链接:https://my.oschina.net/u/2329777/blog/401352