• 深入浅出内存管理--内存管理概述【转】


    转自:https://blog.csdn.net/rikeyone/article/details/84976442?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.add_param_isCf

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

    物理内存管理

    内核中实现了很多机制和算法来进行物理内存的管理,比如大名鼎鼎的伙伴系统,以及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:
    "
                 "    vector  : 0x%08lx - 0x%08lx   (%4ld kB)
    "
     #ifdef CONFIG_HAVE_TCM
                 "    DTCM    : 0x%08lx - 0x%08lx   (%4ld kB)
    "
                 "    ITCM    : 0x%08lx - 0x%08lx   (%4ld kB)
    "
     #endif
                 "    fixmap  : 0x%08lx - 0x%08lx   (%4ld kB)
    ",
                 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)
    "
                "    lowmem  : 0x%08lx - 0x%08lx   (%4ld MB)
    ",
                 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)
    "
     #endif
     #ifdef CONFIG_MODULES
                "    modules : 0x%08lx - 0x%08lx   (%4ld MB)
    "
     #endif
                "      .text : 0x%p" " - 0x%p" "   (%4d kB)
    "
                "      .init : 0x%p" " - 0x%p" "   (%4d kB)
    "
                "      .data : 0x%p" " - 0x%p" "   (%4d kB)
    "
                "       .bss : 0x%p" " - 0x%p" "   (%4d kB)
    ",
     #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.
    
    
    • 1
    • 2
    • 3
    • 4

    这里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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    低端内存映射

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

     lowmem  : 0xc0000000 - 0xef800000   ( 760 MB)
    
    • 1

    对应的解释如下:

     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.
    
    • 1
    • 2
    • 3

    高端内存映射

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

    • pkmap

    测试平台上的数据如下:

     pkmap   : 0xbfe00000 - 0xc0000000   (   2 MB)
    
    • 1

    说明:

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

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

     void *kmap(struct page *page);
    
    • 1

    传入的是一个物理内存页对应的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)
    
    • 1

    说明:

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

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

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

    测试平台上的数据如下:

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

    说明:

     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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    void *vmalloc(unsigned long size);
    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    923c C. Perfect Security
    hdu ACM Steps Section 1 花式A+B 输入输出格式
    我回来了
    POJ–2104 K-th Number
    bzoj1009: [HNOI2008]GT考试
    bzoj1875: [SDOI2009]HH去散步
    1898: [Zjoi2005]Swamp 沼泽鳄鱼
    Hadoop
    程序员的自我修养
    Effective C++笔记
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/13669986.html
Copyright © 2020-2023  润新知