深入浅出内存管理--内存管理概述

内存管理我的理解是分为两个部分,一个是物理内存的管理,另一个部分是物理内存地址到虚拟地址的转换。

物理内存管理

内核中实现了很多机制和算法来进行物理内存的管理,比如大名鼎鼎的伙伴系统,以及slab分配器等等。我们知道随着Linux系统的运行,内存是不断的趋于碎片化的,内存碎片分为两种类型,一种为外碎片,所谓外碎片就是以页为单位的内存之间的碎片化,另一种为内碎片,内碎片是指同一个页面内的碎片化,那么如果来优化这种内存碎片问题呢?

  • 伙伴系统
    伙伴系统可以用来减少外碎片的,通过更加合理的分配以页为单位的内存,可以减少外碎片的产生,以尽量保持系统内存的连续性。

  • slab分配器
    slab是用于优化内碎片问题的,通过把小块内存以对象的方式管理起来,并且创建slab缓存,方便同种类型对象的分配和释放,减少了内碎片的产生,同时这些小块内存会尽可能的保持在硬件cache中,所以极大提升了访问效率。

物理内存管理这块比较复杂,本文仅仅做一个简述,关于这两者的内核API以及实现,将在后续文章中再做介绍。

物理地址到虚拟地址的转换

本文只介绍内核中访问所有物理内存的方式,当前我们面对的问题是:如何物理内存映射到内核空间(3G-4G)这一段区域内?对于用户空间访问物理内存的话题,我们后续再开专门的文章进行介绍。
内核中把物理内存的低端区域作为直接映射区,高地址区域定义为高端内存,通过一个变量high_memory来界定他们的分界线。high_memory是一个虚拟地址,定义了高端内存被允许映射到内核的起始地址。
它在arm平台上的定义如下:

void * high_memory;

EXPORT_SYMBOL(high_memory);


arm_lowmem_limit = lowmem_limit;

high_memory = __va(arm_lowmem_limit - 1) + 1;

if (!memblock_limit)
    memblock_limit = arm_lowmem_limit;

以我的测试板子为例:

Memory: 1030548K/1048576K available (5078K kernel code, 221K rwdata, 1624K rodata, 1584K init, 179K bss, 18028K reserved, 0K cma-reserved, 270336K highmem)
Virtual kernel memory layout:
    vector  : 0xffff0000 - 0xffff1000   (   4 kB)
    fixmap  : 0xffc00000 - 0xfff00000   (3072 kB)
    vmalloc : 0xf0000000 - 0xff000000   ( 240 MB)
    lowmem  : 0xc0000000 - 0xef800000   ( 760 MB)
    pkmap   : 0xbfe00000 - 0xc0000000   (   2 MB)
    modules : 0xbf000000 - 0xbfe00000   (  14 MB)
      .text : 0xc0008000 - 0xc0693dd8   (6704 kB)
      .init : 0xc0694000 - 0xc0820000   (1584 kB)
      .data : 0xc0820000 - 0xc0857708   ( 222 kB)
       .bss : 0xc0857708 - 0xc0884700   ( 180 kB)

它的虚拟内存分布如上所示。这块信息的实现代码如下:

     pr_notice("Virtual kernel memory layout:\n"
             "    vector  : 0x%08lx - 0x%08lx   (%4ld kB)\n"
 #ifdef CONFIG_HAVE_TCM
             "    DTCM    : 0x%08lx - 0x%08lx   (%4ld kB)\n"
             "    ITCM    : 0x%08lx - 0x%08lx   (%4ld kB)\n"
 #endif
             "    fixmap  : 0x%08lx - 0x%08lx   (%4ld kB)\n",
             MLK(UL(CONFIG_VECTORS_BASE), UL(CONFIG_VECTORS_BASE) +
                 (PAGE_SIZE)),
 #ifdef CONFIG_HAVE_TCM
             MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
             MLK(ITCM_OFFSET, (unsigned long) itcm_end),
 #endif
             MLK(FIXADDR_START, FIXADDR_END));
 #ifdef CONFIG_ENABLE_VMALLOC_SAVING
     print_vmalloc_lowmem_info();
 #else
     printk(KERN_NOTICE
            "    vmalloc : 0x%08lx - 0x%08lx   (%4ld MB)\n"
            "    lowmem  : 0x%08lx - 0x%08lx   (%4ld MB)\n",
             MLM(VMALLOC_START, VMALLOC_END),
             MLM(PAGE_OFFSET, (unsigned long)high_memory));
 #endif
     printk(KERN_NOTICE
 #ifdef CONFIG_HIGHMEM
            "    pkmap   : 0x%08lx - 0x%08lx   (%4ld MB)\n"
 #endif
 #ifdef CONFIG_MODULES
            "    modules : 0x%08lx - 0x%08lx   (%4ld MB)\n"
 #endif
            "      .text : 0x%p" " - 0x%p" "   (%4d kB)\n"
            "      .init : 0x%p" " - 0x%p" "   (%4d kB)\n"
            "      .data : 0x%p" " - 0x%p" "   (%4d kB)\n"
            "       .bss : 0x%p" " - 0x%p" "   (%4d kB)\n",
 #ifdef CONFIG_HIGHMEM
             MLM(PKMAP_BASE, (PKMAP_BASE) + (LAST_PKMAP) *
                 (PAGE_SIZE)),
 #endif
 #ifdef CONFIG_MODULES
             MLM(MODULES_VADDR, MODULES_END),
 #endif
 
             MLK_ROUNDUP(_text, _etext),
             MLK_ROUNDUP(__init_begin, __init_end),
             MLK_ROUNDUP(_sdata, _edata),
             MLK_ROUNDUP(__bss_start, __bss_stop));
 

我们通过上面机器的启动log打印出来的memory layout可以知道,在3G以下的区域也是被内核数据所占用了,可是上面不是说用户空间是0-3G吗?这里不会被用户所占用导致冲突吗?
实际上,用户空间的映射区定义如下:

00001000    TASK_SIZE-1 User space mappings
                Per-thread mappings are placed here via
                the mmap() system call.

这里TASK_SIZE实际上并不是PAGE_OFFSET-1,而是中间间隔了一段区域(16M):

/*
 * TASK_SIZE - the maximum size of a user space task.
 * TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area
 */
#define TASK_SIZE       (UL(CONFIG_PAGE_OFFSET) - UL(SZ_16M))
#define TASK_UNMAPPED_BASE  ALIGN(TASK_SIZE / 3, SZ_16M)

低端内存映射

内核空间1G的虚拟空间,其中有一部分用于直接映射,线性映射区,在arm32平台上,物理地址[0:760M]这部分内存被线性的映射到[3G:3G+760M]的虚拟地址上,剩余的264M虚拟地址做什么呢?
是保留给高端内存映射使用的,这部分是能够动态分配和释放的,因为平台上实际的物理内存可能会超过1G,那么内核必须要具有能够寻址到整个物理内存的能力。线性映射区在启动时就完成了页表的创建,没有必要再过多介绍。
测试平台上的线性映射区域:

 lowmem  : 0xc0000000 - 0xef800000   ( 760 MB)

对应的解释如下:

 PAGE_OFFSET high_memory-1   Kernel direct-mapped RAM region.
                 This maps the platforms RAM, and typically
                 maps all platform RAM in a 1:1 relationship.

高端内存映射

针对高端内存的映射,内核又划分了多个区域,因为需要在264M有限的区域内去访问除了760M之外的所有物理内存,所以这部分相比线性映射区将变得更加复杂。内核有三种方式用于将高端内存映射到内核空间,分别是pkmap、fixmap和vmalloc。

  • pkmap

测试平台上的数据如下:

 pkmap   : 0xbfe00000 - 0xc0000000   (   2 MB)

说明:

 PKMAP_BASE  PAGE_OFFSET-1   Permanent kernel mappings
                 One way of mapping HIGHMEM pages into kernel
                 space.

永久内核映射区,映射高端内存到内核空间的一种方式。pkmap在用于映射高端物理内存的,当我们从伙伴系统中分配到高端内存后,是无法直接操作的,必须要经过map操作,此时就可以使用pkmap,它对应的
内核API为

 void *kmap(struct page *page);

传入的是一个物理内存页对应的struct page结构体,返回一个虚拟地址。使用方法如下:
使用alloc_pages()在高端存储器区得到struct page结构,然后调用kmap(struct *page)在内核地址空间[PKMAP_BASE : PAGE_OFFSET-1]中建立永久映射,如果page结构对应的是低端物理内存的页,该函数仅仅返回该页对应的虚拟地址。
另外需要注意kmap()可能引起睡眠,所以不能用在中断和持有锁的代码中使用。从使用方法上我们知道,它是针对struct page来进行的操作,所以至少会映射一个page。

  • fixmap
    测试平台上的数据如下:
fixmap  : 0xffc00000 - 0xfff00000   (3072 kB)

说明:

ffc00000    ffefffff    Fixmap mapping region.  Addresses provided
                by fix_to_virt() will be located here.

fixmap也叫临时映射区,他是一个固定的一块虚拟空间用于映射不同的物理地址,并且是在申请时使用,不用时释放。它于pkmap的区别在于这块地址的映射不会引起睡眠,是可以在中断和持有锁的代码中运行的,它的内核API如下:

void *kmap_atomic(struct page *page);
  • vmalloc

测试平台上的数据如下:

 vmalloc : 0xf0000000 - 0xff000000   ( 240 MB)
 lowmem  : 0xc0000000 - 0xef800000   ( 760 MB)

说明:

 VMALLOC_START   VMALLOC_END-1   vmalloc() / ioremap() space.
                 Memory returned by vmalloc/ioremap will
                 be dynamically placed in this region.
                 Machine specific static mappings are also
                 located here through iotable_init().
                 VMALLOC_START is based upon the value
                 of the high_memory variable, and VMALLOC_END
                 is equal to 0xff800000.

从平台打印的数据来看,vmalloc和lowmem线性映射区并没有完全紧靠着,而是中间有一个hole空洞(8M),这个8M的空间是为了捕获越界访问的。
vmalloc会分配非连续物理内存,这里的非连续指的是物理内存不连续,虚拟地址是连续的,优先使用高端内存来分配物理页,如果分配失败,才会从Normal zone分配。这个接口和上面的都不同,它会自动分配物理内存,然后完成映射后直接返回虚拟地址,而上面两个都是只进行映射。

void *vmalloc(unsigned long size);

你可能感兴趣的:(内核笔记,深入浅出内存管理)