精华内容
下载资源
问答
  • 前言:  我们都知道32位的操作系统可以寻找4g的内存空间。... 要回答这个问题,我们首先要搞清楚什么是物理内存?大家常说的物理内存就是指安装在主板上的内存条,其实不然,在计算机的系统中

    前言:

        我们都知道32位的操作系统可以寻找4g的内存空间。因此我们安装一个32位系统再配置4g的内存条,看起来应该是一个完美的方案。可是,当我们装好系统配好内存,打开任务管理器后,确发现我们的物理内存只有3g左右,这是怎么一回事呢?

    预备知识:

        要回答这个问题,我们首先要搞清楚什么是物理内存?大家常说的物理内存就是指安装在主板上的内存条,其实不然,在计算机的系统中,物理内存不仅包括装在主板上的内存条(RAM),还应该包括主板BIOS芯片的ROM,显卡上的显存(RAM)和BIOS(ROM),以及各种PCI、PCI-E设备上的RAM和ROM。

    解释:

        所以答案就显而易见了。其实32位系统寻址的4g物理内存,这里的物理内存就是上面提到的物理内存的概念。即,寻址的这4g内存空间,一部分来自于我们安装的物理内存条,剩下的部分来自BIOS(ROM),显存等等。所以我们装的4g内存条只用到了其中的一部分。
        详细点来说,虽然可以安装4GB内存条,但这4GB的内存空间不能全部纷配给内存,因为从4GB空间的顶端地址(FFFF_FFFFh)开始向下要有400MB-1GB的地址空间要分配给主板上的其他物理内存。我们可以看到4GB的地址空间可以分为两大部分,0MB~物理内存顶端的地址分配给主板上安装的物理内存,4GB到物理内存顶端的地址分配给BIOS(ROM)和PCI/PCI-E设备的存储器。由于这些存储器基本上是用于系统的输入和输出,所以Intel又把这段地址空间称之为“MMIO”(Memory-Mapped I/O—I/O存储器映射)。当系统安装3GB以下的内存时,MMIO区域不会与物理内存条的地址空间相重叠,操作系统可以访问几乎全部的物理内存,而操作系统属性里显示的物理内存基本接近实际内存的容量。而当系统安装上4GB内存时,问题出现了。由于位于4GB下面的部分地址空间要优先分配给MMIO,内存条上对应的这段区间就得不到编址,所以操作系统就不能使用。

    参考:http://blog.csdn.net/jxhui23/article/details/8374017

        
    展开全文
  • 2、不要用金斯顿的U盘,很容易出现识别不了(表现Flash Bios灯常亮,主板故障灯CPU不亮,主板不启动)。 3、正常情况下刷bios上U盘后只需按一下flash bios buttom键即可,按键灯闪亮后主板启动,主板故障灯CPU...

    2021.2.27

    1、首先强调U盘格式必须是FAT32,rom文件名字是MSI.ROM。

    2、不要用金斯顿的U盘,很容易出现识别不了(表现为Flash Bios灯常亮,主板故障灯CPU不亮,主板不启动)。

    3、正常情况下刷bios插上U盘后只需按一下flash bios buttom键即可,按键灯闪亮后主板启动,主板故障灯CPU常亮,四五分钟后重启。 

    4、可强行关闭电源,插上内存条和显卡启动。若无问题即可正常启动。

    ==================================================================================================================

    2021.3.28

    我就是属于刷新Bios后DRAM故障灯依然常亮的!!!问题没有解决,也就是DRAM故障灯亮并不是Bios的问题

    1、故障排除  

    开机的时候亮DRAM也就是内存灯的话,一般是内存的报错的,两根内存的话微星四槽主板优先插在从CPU开始向右数的第2个和第4个插槽哦,单内存的话则是第二个插槽,插槽其他的插槽可能会报错或者点不亮的。

    如果插槽位置正确,但是依然亮DRAM灯的话,可以考虑断电后,重新插拔一下内存,或者只保留一根内存在第二个插槽看能否点亮。(断电拔掉电源插头 取下主板纽扣电池10分钟在装上插电开机)

    两个方法都不行的话插拔一下CPU 看下CPU针脚是否弯曲。还有可能是硅脂沾到cpu针脚了。

    当我完成以上所有操作时,我发现,只要插双通道,必然无法通过自检。单插1/2卡槽可以正常开机,单插3/4卡槽无法正常开机。查看CPU针脚并没有问题(轻微一点点弯曲,我觉得并不会影响使用),再加上这几天刚好重新涂了硅脂,于是我自信的认为一定是硅脂沾到针脚了,用湿纸巾一波擦拭之后,还是无法双通道开机。。。。。。。还有什么散热器压太紧的。都试了。。。折腾了一整天了。还是不行,绝望。

    2、联系主板厂商,请求更换主板。(三年包换,换好不换新,算不算被坑了,毕竟新主板没回来才一个月)  后续待更新。。。。如果不是主板问题,那还得更换CPU了,非常浪费时间!

     

     

    展开全文
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-08-31 ... Linux内存管理 1 前景回顾前面我们讲到服务器体系(SMP, NUMA, MPP)与共享存储器架构(UMA和NUMA)1.1 UMA和NUMA两种模型共享存储型多处理机有两种模型
    日期 内核版本 架构 作者 GitHub CSDN
    2016-08-31 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理

    2017-11-05 最新更新
    更新了 struct zone 结构体的字段描述, 更详细, 更清晰

    1 前景回顾


    前面我们讲到服务器体系(SMP, NUMA, MPP)与共享存储器架构(UMA和NUMA)

    1.1 UMA和NUMA两种模型


    共享存储型多处理机有两种模型

    • 均匀存储器存取(Uniform-Memory-Access,简称UMA)模型

    • 非均匀存储器存取(Nonuniform-Memory-Access,简称NUMA)模型

    UMA模型

    物理存储器被所有处理机均匀共享。所有处理机对所有存储字具有相同的存取时间,这就是为什么称它为均匀存储器存取的原因。每台处理机可以有私用高速缓存,外围设备也以一定形式共享。

    NUMA模型

    NUMA模式下,处理器被划分成多个”节点”(node), 每个节点被分配有的本地存储器空间。 所有节点中的处理器都可以访问全部的系统物理存储器,但是访问本节点内的存储器所需要的时间,比访问某些远程节点内的存储器所花的时间要少得多。

    1.2 (N)UMA模型中linux内存的机构


    非一致存储器访问(NUMA)模式下

    • 处理器被划分成多个”节点”(node), 每个节点被分配有的本地存储器空间. 所有节点中的处理器都可以访问全部的系统物理存储器,但是访问本节点内的存储器所需要的时间,比访问某些远程节点内的存储器所花的时间要少得多

    • 内存被分割成多个区域(BANK,也叫”簇”),依据簇与处理器的”距离”不同, 访问不同簇的代码也会不同. 比如,可能把内存的一个簇指派给每个处理器,或则某个簇和设备卡很近,很适合DMA,那么就指派给该设备。因此当前的多数系统会把内存系统分割成2块区域,一块是专门给CPU去访问,一块是给外围设备板卡的DMA去访问

    在UMA系统中, 内存就相当于一个只使用一个NUMA节点来管理整个系统的内存. 而内存管理的其他地方则认为他们就是在处理一个(伪)NUMA系统.

    1.3 Linux如何描述物理内存


    Linux把物理内存划分为三个层次来管理

    层次 描述
    存储节点(Node) CPU被划分为多个节点(node), 内存则被分簇, 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存簇bank,即每个内存簇被认为是一个节点
    管理区(Zone) 每个物理内存节点node被划分为多个内存管理区域, 用于表示不同范围的内存, 内核可以使用不同的映射方式映射物理内存
    页面(Page) 内存被细分为多个页面帧, 页面是最基本的页面分配的单位 |

    1.4 用pd_data_t描述内存节点node


    CPU被划分为多个节点(node), 内存则被分簇, 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存簇bank,即每个内存簇被认为是一个节点

    系统的物理内存被划分为几个节点(node), 一个node对应一个内存簇bank,即每个内存簇被认为是一个节点

    • 首先, 内存被划分为结点. 每个节点关联到系统中的一个处理器, 内核中表示为pg_data_t的实例. 系统中每个节点被链接到一个以NULL结尾的pgdat_list链表中<而其中的每个节点利用pg_data_tnode_next字段链接到下一节.而对于PC这种UMA结构的机器来说, 只使用了一个成为contig_page_data的静态pg_data_t结构.

    内存中的每个节点都是由pg_data_t描述,而pg_data_t由struct pglist_data定义而来, 该数据结构定义在include/linux/mmzone.h, line 615

    在分配一个页面时, Linux采用节点局部分配的策略, 从最靠近运行中的CPU的节点分配内存, 由于进程往往是在同一个CPU上运行, 因此从当前节点得到的内存很可能被用到

    1.5 今日内容(内存管理域zone)


    为了支持NUMA模型,也即CPU对不同内存单元的访问时间可能不同,此时系统的物理内存被划分为几个节点(node), 一个node对应一个内存簇bank,即每个内存簇被认为是一个节点

    • 首先, 内存被划分为结点. 每个节点关联到系统中的一个处理器, 内核中表示为pg_data_t的实例. 系统中每个节点被链接到一个以NULL结尾的pgdat_list链表中<而其中的每个节点利用pg_data_tnode_next字段链接到下一节.而对于PC这种UMA结构的机器来说, 只使用了一个成为contig_page_data的静态pg_data_t结构.

    • 接着各个节点又被划分为内存管理区域, 一个管理区域通过struct zone_struct描述, 其被定义为zone_t, 用以表示内存的某个范围, 低端范围的16MB被描述为ZONE_DMA, 某些工业标准体系结构中的(ISA)设备需要用到它, 然后是可直接映射到内核的普通内存域ZONE_NORMAL,最后是超出了内核段的物理地址域ZONE_HIGHMEM, 被称为高端内存. 是系统中预留的可用内存空间, 不能被内核直接映射.

    下面我们就来详解讲讲内存管理域的内容zone

    2 为什么要将内存node分成不同的区域zone


    NUMA结构下, 每个处理器CPU与一个本地内存直接相连, 而不同处理器之前则通过总线进行进一步的连接, 因此相对于任何一个CPU访问本地内存的速度比访问远程内存的速度要快, 而Linux为了兼容NUMAJ结构, 把物理内存相依照CPU的不同node分成簇, 一个CPU-node对应一个本地内存pgdata_t.

    这样已经很好的表示物理内存了, 在一个理想的计算机系统中, 一个页框就是一个内存的分配单元, 可用于任何事情:存放内核数据, 用户数据和缓冲磁盘数据等等. 任何种类的数据页都可以存放在任页框中, 没有任何限制.


    但是Linux内核又把各个物理内存节点分成个不同的管理区域zone, 这是为什么呢?

    因为实际的计算机体系结构有硬件的诸多限制, 这限制了页框可以使用的方式. 尤其是, Linux内核必须处理80x86体系结构的两种硬件约束.

    • ISA总线的直接内存存储DMA处理器有一个严格的限制 : 他们只能对RAM的前16MB进行寻址

    • 在具有大容量RAM的现代32位计算机中, CPU不能直接访问所有的物理地址, 因为线性地址空间太小, 内核不可能直接映射所有物理内存到线性地址空间, 我们会在后面典型架构(x86)上内存区域划分详细讲解x86_32上的内存区域划分

    因此Linux内核对不同区域的内存需要采用不同的管理方式和映射方式, 因此内核将物理地址或者成用zone_t表示的不同地址区域

    3 内存管理区类型zone_type


    前面我们说了由于硬件的一些约束, 低端的一些地址被用于DMA, 而在实际内存大小超过了内核所能使用的现行地址的时候, 一些高地址处的物理地址不能简单持久的直接映射到内核空间. 因此内核将内存的节点node分成了不同的内存区域方便管理和映射.

    Linux使用enum zone_type来标记内核所支持的所有内存区域

    3.1 内存区域类型zone_type


    zone_type结构定义在include/linux/mmzone.h, 其基本信息如下所示

    enum zone_type
    {
    #ifdef CONFIG_ZONE_DMA
        ZONE_DMA,
    #endif
    
    #ifdef CONFIG_ZONE_DMA32
    
        ZONE_DMA32,
    #endif
    
        ZONE_NORMAL,
    
    #ifdef CONFIG_HIGHMEM
        ZONE_HIGHMEM,
    #endif
        ZONE_MOVABLE,
    #ifdef CONFIG_ZONE_DEVICE
        ZONE_DEVICE,
    #endif
        __MAX_NR_ZONES
    
    };
    

    不同的管理区的用途是不一样的,ZONE_DMA类型的内存区域在物理内存的低端,主要是ISA设备只能用低端的地址做DMA操作。ZONE_NORMAL类型的内存区域直接被内核映射到线性地址空间上面的区域(line address space),ZONE_HIGHMEM将保留给系统使用,是系统中预留的可用内存空间,不能被内核直接映射。

    3.2 不同的内存区域的作用


    在内存中,每个簇所对应的node又被分成的称为管理区(zone)的块,它们各自描述在内存中的范围。一个管理区(zone)由struct zone结构体来描述,在linux-2.4.37之前的内核中是用typedef struct zone_struct zone_t数据结构来描述)

    管理区的类型用zone_type表示, 有如下几种

    管理内存域 描述
    ZONE_DMA 标记了适合DMA的内存域. 该区域的长度依赖于处理器类型. 这是由于古老的ISA设备强加的边界. 但是为了兼容性, 现代的计算机也可能受此影响
    ZONE_DMA32 标记了使用32位地址字可寻址, 适合DMA的内存域. 显然, 只有在53位系统中ZONE_DMA32才和ZONE_DMA有区别, 在32位系统中, 本区域是空的, 即长度为0MB, 在Alpha和AMD64系统上, 该内存的长度可能是从0到4GB
    ZONE_NORMAL 标记了可直接映射到内存段的普通内存域. 这是在所有体系结构上保证会存在的唯一内存区域, 但无法保证该地址范围对应了实际的物理地址. 例如, 如果AMD64系统只有两2G内存, 那么所有的内存都属于ZONE_DMA32范围, 而ZONE_NORMAL则为空
    ZONE_HIGHMEM 标记了超出内核虚拟地址空间的物理内存段, 因此这段地址不能被内核直接映射
    ZONE_MOVABLE 内核定义了一个伪内存域ZONE_MOVABLE, 在防止物理内存碎片的机制memory migration中需要使用该内存域. 供防止物理内存碎片的极致使用
    ZONE_DEVICE 为支持热插拔设备而分配的Non Volatile Memory非易失性内存
    MAX_NR_ZONES 充当结束标记, 在内核中想要迭代系统中所有内存域, 会用到该常亮

    根据编译时候的配置, 可能无需考虑某些内存域. 例如在64位系统中, 并不需要高端内存, 因为AM64的linux采用4级页表,支持的最大物理内存为64TB, 对于虚拟地址空间的划分,将0x0000,0000,0000,0000 – 0x0000,7fff,ffff,f000这128T地址用于用户空间;而0xffff,8000,0000,0000以上的128T为系统空间地址, 这远大于当前我们系统中的内存空间, 因此所有的物理地址都可以直接映射到内核中, 不需要高端内存的特殊映射. 可以参见Documentation/x86/x86_64/mm.txt
    `

    ZONE_MOVABLE和ZONE_DEVICE其实是和其他的ZONE的用途有异,

    • ZONE_MOVABLE在防止物理内存碎片的机制中需要使用该内存区域,

    • ZONE_DEVICE笔者也第一次知道了,理解有错的话欢迎大家批评指正, 这个应该是为支持热插拔设备而分配的Non Volatile Memory非易失性内存,

    关于ZONE_DEVICE, 具体的信息可以参见[ATCH v2 3/9] mm: ZONE_DEVICE for “device memory”](https://lkml.org/lkml/2015/8/25/844)

    While pmem is usable as a block device or via DAX mappings to userspace
    there are several usage scenarios that can not target pmem due to its
    lack of struct page coverage. In preparation for “hot plugging” pmem
    into the vmemmap add ZONE_DEVICE as a new zone to tag these pages
    separately from the ones that are subject to standard page allocations.
    Importantly “device memory” can be removed at will by userspace
    unbinding the driver of the device.

    3.3 典型架构(x86)上内存区域划分


    对于x86机器,管理区(内存区域)类型如下分布

    类型 区域
    ZONE_DMA 0~15MB
    ZONE_NORMAL 16MB~895MB
    ZONE_HIGHMEM 896MB~物理内存结束

    而由于32位系统中, Linux内核虚拟地址空间只有1G, 而0~895M这个986MB被用于DMA和直接映射, 剩余的物理内存被成为高端内存. 那内核是如何借助剩余128MB高端内存地址空间是如何实现访问可以所有物理内存?

    当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,实现了使用有限的地址空间,访问所有所有物理内存

    关于高端内存的内容, 我们后面会专门抽出一章进行讲解

    因此, 传统和X86_32位系统中, 前16M划分给ZONE_DMA, 该区域包含的页框可以由老式的基于ISAS的设备通过DMA使用”直接内存访问(DMA)”, ZONE_DMA和ZONE_NORMAL区域包含了内存的常规页框, 通过把他们线性的映射到现行地址的第4个GB, 内核就可以直接进行访问, 相反ZONE_HIGHME包含的内存页不能由内核直接访问, 尽管他们也线性地映射到了现行地址空间的第4个GB. 在64位体系结构中, 线性地址空间的大小远远好过了系统的实际物理地址, 内核可知直接将所有的物理内存映射到线性地址空间, 因此64位体系结构上ZONE_HIGHMEM区域总是空的.

    4 管理区结构zone_t


    一个管理区(zone)由struct zone结构体来描述(linux-3.8~目前linux4.5),而在linux-2.4.37之前的内核中是用struct zone_struct数据结构来描述), 他们都通过typedef被重定义为zone_t类型

    zone对象用于跟踪诸如页面使用情况的统计数, 空闲区域信息和锁信息

    里面保存着内存使用状态信息,如page使用统计, 未使用的内存区域,互斥访问的锁(LOCKS)等.

    4.1 struct zone管理域数据结构


    struct zonelinux/mmzone.h中定义, 在linux-4.7的内核中可以使用include/linux/mmzone.h来查看其定义

    struct zone
    {
        /* Read-mostly fields */
    
        /* zone watermarks, access with *_wmark_pages(zone) macros */
        unsigned long watermark[NR_WMARK];
    
        unsigned long nr_reserved_highatomic;
    
        /*
         * We don't know if the memory that we're going to allocate will be
         * freeable or/and it will be released eventually, so to avoid totally
         * wasting several GB of ram we must reserve some of the lower zone
         * memory (otherwise we risk to run OOM on the lower zones despite
         * there being tons of freeable ram on the higher zones).  This array is
         * recalculated at runtime if the sysctl_lowmem_reserve_ratio sysctl
         * changes.
         * 分别为各种内存域指定了若干页
         * 用于一些无论如何都不能失败的关键性内存分配。
         */
        long lowmem_reserve[MAX_NR_ZONES];
    
    #ifdef CONFIG_NUMA
        int node;
    #endif
    
        /*
         * The target ratio of ACTIVE_ANON to INACTIVE_ANON pages on
         * this zone's LRU.  Maintained by the pageout code.
         * 不活动页的比例,
         * 接着是一些很少使用或者大部分情况下是只读的字段:
         * wait_table wait_table_hash_nr_entries wait_table_bits
         * 形成等待列队,可以等待某一页可供进程使用  */
        unsigned int inactive_ratio;
    
        /*  指向这个zone所在的pglist_data对象  */
        struct pglist_data      *zone_pgdat;
        /*/这个数组用于实现每个CPU的热/冷页帧列表。内核使用这些列表来保存可用于满足实现的“新鲜”页。但冷热页帧对应的高速缓存状态不同:有些页帧很可能在高速缓存中,因此可以快速访问,故称之为热的;未缓存的页帧与此相对,称之为冷的。*/
        struct per_cpu_pageset __percpu *pageset;
    
        /*
         * This is a per-zone reserve of pages that are not available
         * to userspace allocations.
         * 每个区域保留的不能被用户空间分配的页面数目
         */
        unsigned long       totalreserve_pages;
    
    #ifndef CONFIG_SPARSEMEM
        /*
         * Flags for a pageblock_nr_pages block. See pageblock-flags.h.
         * In SPARSEMEM, this map is stored in struct mem_section
         */
        unsigned long       *pageblock_flags;
    #endif /* CONFIG_SPARSEMEM */
    
    #ifdef CONFIG_NUMA
        /*
         * zone reclaim becomes active if more unmapped pages exist.
         */
        unsigned long       min_unmapped_pages;
        unsigned long       min_slab_pages;
    #endif /* CONFIG_NUMA */
    
        /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT
         * 只内存域的第一个页帧 */
        unsigned long       zone_start_pfn;
    
        /*
         * spanned_pages is the total pages spanned by the zone, including
         * holes, which is calculated as:
         *      spanned_pages = zone_end_pfn - zone_start_pfn;
         *
         * present_pages is physical pages existing within the zone, which
         * is calculated as:
         *      present_pages = spanned_pages - absent_pages(pages in holes);
         *
         * managed_pages is present pages managed by the buddy system, which
         * is calculated as (reserved_pages includes pages allocated by the
         * bootmem allocator):
         *      managed_pages = present_pages - reserved_pages;
         *
         * So present_pages may be used by memory hotplug or memory power
         * management logic to figure out unmanaged pages by checking
         * (present_pages - managed_pages). And managed_pages should be used
         * by page allocator and vm scanner to calculate all kinds of watermarks
         * and thresholds.
         *
         * Locking rules:
         *
         * zone_start_pfn and spanned_pages are protected by span_seqlock.
         * It is a seqlock because it has to be read outside of zone->lock,
         * and it is done in the main allocator path.  But, it is written
         * quite infrequently.
         *
         * The span_seq lock is declared along with zone->lock because it is
         * frequently read in proximity to zone->lock.  It's good to
         * give them a chance of being in the same cacheline.
         *
         * Write access to present_pages at runtime should be protected by
         * mem_hotplug_begin/end(). Any reader who can't tolerant drift of
         * present_pages should get_online_mems() to get a stable value.
         *
         * Read access to managed_pages should be safe because it's unsigned
         * long. Write access to zone->managed_pages and totalram_pages are
         * protected by managed_page_count_lock at runtime. Idealy only
         * adjust_managed_page_count() should be used instead of directly
         * touching zone->managed_pages and totalram_pages.
         */
        unsigned long       managed_pages;
        unsigned long       spanned_pages;             /*  总页数,包含空洞  */
        unsigned long       present_pages;              /*  可用页数,不包哈空洞  */
    
        /*  指向管理区的传统名字, "DMA", "NROMAL"或"HIGHMEM" */
        const char          *name;
    
    #ifdef CONFIG_MEMORY_ISOLATION
        /*
         * Number of isolated pageblock. It is used to solve incorrect
         * freepage counting problem due to racy retrieving migratetype
         * of pageblock. Protected by zone->lock.
         */
        unsigned long       nr_isolate_pageblock;
    #endif
    
    #ifdef CONFIG_MEMORY_HOTPLUG
        /* see spanned/present_pages for more description */
        seqlock_t           span_seqlock;
    #endif
    
        /*
         * wait_table       -- the array holding the hash table
         * wait_table_hash_nr_entries   -- the size of the hash table array
         * wait_table_bits      -- wait_table_size == (1 << wait_table_bits)
         *
         * The purpose of all these is to keep track of the people
         * waiting for a page to become available and make them
         * runnable again when possible. The trouble is that this
         * consumes a lot of space, especially when so few things
         * wait on pages at a given time. So instead of using
         * per-page waitqueues, we use a waitqueue hash table.
         *
         * The bucket discipline is to sleep on the same queue when
         * colliding and wake all in that wait queue when removing.
         * When something wakes, it must check to be sure its page is
         * truly available, a la thundering herd. The cost of a
         * collision is great, but given the expected load of the
         * table, they should be so rare as to be outweighed by the
         * benefits from the saved space.
         *
         * __wait_on_page_locked() and unlock_page() in mm/filemap.c, are the
         * primary users of these fields, and in mm/page_alloc.c
         * free_area_init_core() performs the initialization of them.
         */
        /*  进程等待队列的散列表, 这些进程正在等待管理区中的某页  */
        wait_queue_head_t       *wait_table;
        /*  等待队列散列表中的调度实体数目  */
        unsigned long       wait_table_hash_nr_entries;
        /*  等待队列散列表数组大小, 值为2^order  */
        unsigned long       wait_table_bits;
    
        ZONE_PADDING(_pad1_)
    
        /* free areas of different sizes
           页面使用状态的信息,以每个bit标识对应的page是否可以分配
           是用于伙伴系统的,每个数组元素指向对应阶也表的数组开头
           以下是供页帧回收扫描器(page reclaim scanner)访问的字段
           scanner会跟据页帧的活动情况对内存域中使用的页进行编目
           如果页帧被频繁访问,则是活动的,相反则是不活动的,
           在需要换出页帧时,这样的信息是很重要的:   */
        struct free_area    free_area[MAX_ORDER];
    
        /* zone flags, see below 描述当前内存的状态, 参见下面的enum zone_flags结构 */
        unsigned long       flags;
    
        /* Write-intensive fields used from the page allocator, 保存该描述符的自旋锁  */
        spinlock_t          lock;
    
        ZONE_PADDING(_pad2_)
    
        /* Write-intensive fields used by page reclaim */
    
        /* Fields commonly accessed by the page reclaim scanner */
        spinlock_t          lru_lock;   /* LRU(最近最少使用算法)活动以及非活动链表使用的自旋锁  */
        struct lruvec       lruvec;
    
        /*
         * When free pages are below this point, additional steps are taken
         * when reading the number of free pages to avoid per-cpu counter
         * drift allowing watermarks to be breached
         * 在空闲页的数目少于这个点percpu_drift_mark的时候
         * 当读取和空闲页数一样的内存页时,系统会采取额外的工作,
         * 防止单CPU页数漂移,从而导致水印被破坏。
         */
        unsigned long percpu_drift_mark;
    
    #if defined CONFIG_COMPACTION || defined CONFIG_CMA
        /* pfn where compaction free scanner should start */
        unsigned long       compact_cached_free_pfn;
        /* pfn where async and sync compaction migration scanner should start */
        unsigned long       compact_cached_migrate_pfn[2];
    #endif
    
    #ifdef CONFIG_COMPACTION
        /*
         * On compaction failure, 1<<compact_defer_shift compactions
         * are skipped before trying again. The number attempted since
         * last failure is tracked with compact_considered.
         */
        unsigned int        compact_considered;
        unsigned int        compact_defer_shift;
        int                       compact_order_failed;
    #endif
    
    #if defined CONFIG_COMPACTION || defined CONFIG_CMA
        /* Set to true when the PG_migrate_skip bits should be cleared */
        bool            compact_blockskip_flush;
    #endif
    
        bool            contiguous;
    
        ZONE_PADDING(_pad3_)
        /* Zone statistics 内存域的统计信息, 参见后面的enum zone_stat_item结构 */
        atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];
    } ____cacheline_internodealigned_in_smp;
    字段 描述
    watermark 每个 zone 在系统启动时会计算出 3 个水位值, 分别为 WMAKR_MIN, WMARK_LOW, WMARK_HIGH 水位, 这在页面分配器和 kswapd 页面回收中会用到
    lowmem_reserve[MAX_NR_ZONES] zone 中预留的内存, 为了防止一些代码必须运行在低地址区域,所以事先保留一些低地址区域的内存
    pageset page管理的数据结构对象,内部有一个page的列表(list)来管理。每个CPU维护一个page list,避免自旋锁的冲突。这个数组的大小和NR_CPUS(CPU的数量)有关,这个值是编译的时候确定的
    lock 对zone并发访问的保护的自旋锁
    free_area[MAX_ORDER] 页面使用状态的信息,以每个bit标识对应的page是否可以分配
    lru_lock LRU(最近最少使用算法)的自旋锁
    wait_table 待一个page释放的等待队列哈希表。它会被wait_on_page(),unlock_page()函数使用. 用哈希表,而不用一个等待队列的原因,防止进程长期等待资源
    wait_table_hash_nr_entries 哈希表中的等待队列的数量
    zone_pgdat 指向这个zone所在的pglist_data对象
    zone_start_pfn 和node_start_pfn的含义一样。这个成员是用于表示zone中的开始那个page在物理内存中的位置的present_pages, spanned_pages: 和node中的类似的成员含义一样
    name zone的名字,字符串表示: “DMA”,”Normal” 和”HighMem”
    totalreserve_pages 每个区域保留的不能被用户空间分配的页面数目
    ZONE_PADDING 由于自旋锁频繁的被使用,因此为了性能上的考虑,将某些成员对齐到cache line中,有助于提高执行的性能。使用这个宏,可以确定zone->lock,zone->lru_lock,zone->pageset这些成员使用不同的cache line.
    managed_pages zone 中被伙伴系统管理的页面数量
    spanned_pages zone 中包含的页面数量
    present_pages zone 中实际管理的页面数量. 对一些体系结构来说, 其值和 spanned_pages 相等
    lruvec LRU 链表集合
    vm_stat zone 计数

    4.2 ZONE_PADDING将数据保存在高速缓冲行


    该结构比较特殊的地方是它由ZONE_PADDING分隔的几个部分. 这是因为堆zone结构的访问非常频繁. 在多处理器系统中, 通常会有不同的CPU试图同时访问结构成员. 因此使用锁可以防止他们彼此干扰, 避免错误和不一致的问题. 由于内核堆该结构的访问非常频繁, 因此会经常性地获取该结构的两个自旋锁zone->lock和zone->lru_lock

    由于 struct zone 结构经常被访问到, 因此这个数据结构要求以 L1 Cache 对齐. 另外, 这里的 ZONE_PADDING( )zone->lockzone_lru_lock 这两个很热门的锁可以分布在不同的 Cahe Line 中. 一个内存 node 节点最多也就几个 zone, 因此 zone 数据结构不需要像 struct page 一样关心数据结构的大小, 因此这里的 ZONE_PADDING( ) 可以理解为用空间换取时间(性能). 在内存管理开发过程中, 内核开发者逐渐发现有一些自选锁竞争会非常厉害, 很难获取. 像 zone->lockzone->lru_lock 这两个锁有时需要同时获取锁. 因此保证他们使用不同的 Cache Line 是内核常用的一种优化技巧.

    那么数据保存在CPU高速缓存中, 那么会处理得更快速. 高速缓冲分为行, 每一行负责不同的内存区. 内核使用ZONE_PADDING宏生成”填充”字段添加到结构中, 以确保每个自旋锁处于自身的缓存行中

    ZONE_PADDING宏定义在nclude/linux/mmzone.h?v4.7, line 105

    /*
     * zone->lock and zone->lru_lock are two of the hottest locks in the kernel.
     * So add a wild amount of padding here to ensure that they fall into separate
     * cachelines.  There are very few zone structures in the machine, so space
     * consumption is not a concern here.
         */
    #if defined(CONFIG_SMP)
        struct zone_padding
        {
                char x[0];
        } ____cacheline_internodealigned_in_smp;
        #define ZONE_PADDING(name)      struct zone_padding name;
    
    #else
        #define ZONE_PADDING(name)
     #endif

    内核还用了____cacheline_internodealigned_in_smp,来实现最优的高速缓存行对其方式.

    该宏定义在include/linux/cache.h

    #if !defined(____cacheline_internodealigned_in_smp)
        #if defined(CONFIG_SMP)
            #define ____cacheline_internodealigned_in_smp \
            __attribute__((__aligned__(1 << (INTERNODE_CACHE_SHIFT))))
        #else
            #define ____cacheline_internodealigned_in_smp
        #endif
    #endif

    4.3 水印watermark[NR_WMARK]与kswapd内核线程


    Zone的管理调度的一些参数watermarks水印, 水存量很小(MIN)进水量,水存量达到一个标准(LOW)减小进水量,当快要满(HIGH)的时候,可能就关闭了进水口

    WMARK_LOW, WMARK_LOW, WMARK_HIGH就是这个标准

    enum zone_watermarks
    {
            WMARK_MIN,
            WMARK_LOW,
            WMARK_HIGH,
            NR_WMARK
    };
    
    
    #define min_wmark_pages(z) (z->watermark[WMARK_MIN])
    #define low_wmark_pages(z) (z->watermark[WMARK_LOW])
    #define high_wmark_pages(z) (z->watermark[WMARK_HIGH])

    在linux-2.4中, zone结构中使用如下方式表示水印, 参照include/linux/mmzone.h?v=2.4.37, line 171

    typedef struct zone_watermarks_s
    {
        unsigned long min, low, high;
    } zone_watermarks_t;
    
    
    typedef struct zone_struct {
        zone_watermarks_t       watermarks[MAX_NR_ZONES];

    在Linux-2.6.x中标准是直接通过成员pages_min, pages_low and pages_high定义在zone结构体中的, 参照include/linux/mmzone.h?v=2.6.24, line 214

    当系统中可用内存很少的时候,系统进程kswapd被唤醒, 开始回收释放page, 水印这些参数(WMARK_MIN, WMARK_LOW, WMARK_HIGH)影响着这个代码的行为

    每个zone有三个水平标准:watermark[WMARK_MIN], watermark[WMARK_LOW], watermark[WMARK_HIGH],帮助确定zone中内存分配使用的压力状态

    标准 描述
    watermark[WMARK_MIN] 当空闲页面的数量达到page_min所标定的数量的时候, 说明页面数非常紧张, 分配页面的动作和kswapd线程同步运行.
    WMARK_MIN所表示的page的数量值,是在内存初始化的过程中调用free_area_init_core中计算的。这个数值是根据zone中的page的数量除以一个>1的系数来确定的。通常是这样初始化的ZoneSizeInPages/12
    watermark[WMARK_LOW] 当空闲页面的数量达到WMARK_LOW所标定的数量的时候,说明页面刚开始紧张, 则kswapd线程将被唤醒,并开始释放回收页面
    watermark[WMARK_HIGH] 当空闲页面的数量达到page_high所标定的数量的时候, 说明内存页面数充足, 不需要回收, kswapd线程将重新休眠,通常这个数值是page_min的3倍

    * 如果空闲页多于pages_high = watermark[WMARK_HIGH], 则说明内存页面充足, 内存域的状态是理想的.

    • 如果空闲页的数目低于pages_low = watermark[WMARK_LOW], 则说明内存页面开始紧张, 内核开始将页患处到硬盘.

    • 如果空闲页的数目低于pages_min = watermark[WMARK_MIN], 则内存页面非常紧张, 页回收工作的压力就比较大

    4.3 内存域标志


    内存管理域zone_t结构中的flags字段描述了内存域的当前状态

    //  http://lxr.free-electrons.com/source/include/linux/mmzone.h#L475
    struct zone
    {
        /* zone flags, see below */
        unsigned long           flags;
    }

    它允许使用的标识用enum zone_flags标识, 该枚举标识定义在include/linux/mmzone.h?v4.7, line 525, 如下所示

    enum zone_flags
    {
        ZONE_RECLAIM_LOCKED,         /* prevents concurrent reclaim */
        ZONE_OOM_LOCKED,               /* zone is in OOM killer zonelist 内存域可被回收*/
        ZONE_CONGESTED,                 /* zone has many dirty pages backed by
                                                        * a congested BDI
                                                        */
        ZONE_DIRTY,                           /* reclaim scanning has recently found
                                                       * many dirty file pages at the tail
                                                       * of the LRU.
                                                       */
        ZONE_WRITEBACK,                 /* reclaim scanning has recently found
                                                       * many pages under writeback
                                                       */
        ZONE_FAIR_DEPLETED,           /* fair zone policy batch depleted */
    };
    flag标识 描述
    ZONE_RECLAIM_LOCKED 防止并发回收, 在SMP上系统, 多个CPU可能试图并发的回收亿i个内存域. ZONE_RECLAIM_LCOKED标志可防止这种情况: 如果一个CPU在回收某个内存域, 则设置该标识. 这防止了其他CPU的尝试
    ZONE_OOM_LOCKED 用于某种不走运的情况: 如果进程消耗了大量的内存, 致使必要的操作都无法完成, 那么内核会使徒杀死消耗内存最多的进程, 以获取更多的空闲页, 该标志可以放置多个CPU同时进行这种操作
    ZONE_CONGESTED 标识当前区域中有很多脏页
    ZONE_DIRTY 用于标识最近的一次页面扫描中, LRU算法发现了很多脏的页面
    ZONE_WRITEBACK 最近的回收扫描发现有很多页在写回
    ZONE_FAIR_DEPLETED 公平区策略耗尽(没懂)

    4.4 内存域统计信息vm_stat


    内存域struct zone的vm_stat维护了大量有关该内存域的统计信息. 由于其中维护的大部分信息曲面没有多大意义

    //  http://lxr.free-electrons.com/source/include/linux/mmzone.h#L522
    struct zone
    {
          atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
    }

    vm_stat的统计信息由enum zone_stat_item枚举变量标识, 定义在include/linux/mmzone.h?v=4.7, line 110

    enum zone_stat_item
    {
        /* First 128 byte cacheline (assuming 64 bit words) */
        NR_FREE_PAGES,
        NR_ALLOC_BATCH,
        NR_LRU_BASE,
        NR_INACTIVE_ANON = NR_LRU_BASE, /* must match order of LRU_[IN]ACTIVE */
        NR_ACTIVE_ANON,         /*  "     "     "   "       "         */
        NR_INACTIVE_FILE,       /*  "     "     "   "       "         */
        NR_ACTIVE_FILE,         /*  "     "     "   "       "         */
        NR_UNEVICTABLE,         /*  "     "     "   "       "         */
        NR_MLOCK,               /* mlock()ed pages found and moved off LRU */
        NR_ANON_PAGES,  /* Mapped anonymous pages */
        NR_FILE_MAPPED, /* pagecache pages mapped into pagetables.
                           only modified from process context */
        NR_FILE_PAGES,
        NR_FILE_DIRTY,
        NR_WRITEBACK,
        NR_SLAB_RECLAIMABLE,
        NR_SLAB_UNRECLAIMABLE,
        NR_PAGETABLE,           /* used for pagetables */
        NR_KERNEL_STACK,
        /* Second 128 byte cacheline */
        NR_UNSTABLE_NFS,        /* NFS unstable pages */
        NR_BOUNCE,
        NR_VMSCAN_WRITE,
        NR_VMSCAN_IMMEDIATE,    /* Prioritise for reclaim when writeback ends */
        NR_WRITEBACK_TEMP,      /* Writeback using temporary buffers */
        NR_ISOLATED_ANON,       /* Temporary isolated pages from anon lru */
        NR_ISOLATED_FILE,       /* Temporary isolated pages from file lru */
        NR_SHMEM,               /* shmem pages (included tmpfs/GEM pages) */
        NR_DIRTIED,             /* page dirtyings since bootup */
        NR_WRITTEN,             /* page writings since bootup */
        NR_PAGES_SCANNED,       /* pages scanned since last reclaim */
    #ifdef CONFIG_NUMA
        NUMA_HIT,               /* allocated in intended node */
        NUMA_MISS,              /* allocated in non intended node */
        NUMA_FOREIGN,           /* was intended here, hit elsewhere */
        NUMA_INTERLEAVE_HIT,    /* interleaver preferred this zone */
        NUMA_LOCAL,             /* allocation from local node */
        NUMA_OTHER,             /* allocation from other node */
    #endif
        WORKINGSET_REFAULT,
        WORKINGSET_ACTIVATE,
        WORKINGSET_NODERECLAIM,
        NR_ANON_TRANSPARENT_HUGEPAGES,
        NR_FREE_CMA_PAGES,
        NR_VM_ZONE_STAT_ITEMS
    };

    内核提供了很多方式来获取当前内存域的状态信息, 这些函数大多定义在include/linux/vmstat.h?v=4.7

    4.5 Zone等待队列表(zone wait queue table)


    struct zone中实现了一个等待队列, 可用于等待某一页的进程, 内核将进程排成一个列队, 等待某些条件. 在条件变成真时, 内核会通知进程恢复工作.

    struct zone
    {
        wait_queue_head_t       *wait_table;
        unsigned long               wait_table_hash_nr_entries;
        unsigned long               wait_table_bits;
    }
    字段 描述
    wait_table 待一个page释放的等待队列哈希表。它会被wait_on_page(),unlock_page()函数使用. 用哈希表,而不用一个等待队列的原因,防止进程长期等待资源
    wait_table_hash_nr_entries 哈希表中的等待队列的数量
    wait_table_bits 等待队列散列表数组大小, wait_table_size == (1 << wait_table_bits)

    当对一个page做I/O操作的时候,I/O操作需要被锁住,防止不正确的数据被访问。进程在访问page前,wait_on_page_locked函数,使进程加入一个等待队列

    访问完成后,UnlockPage函数解锁其他进程对page的访问。其他正在等待队列中的进程被唤醒。每个page都可以有一个等待队列,但是太多的分离的等待队列使得花费太多的内存访问周期。替代的解决方法,就是将所有的队列放在struct zone数据结构中

    也可以有一种可能,就是struct zone中只有一个队列,但是这就意味着,当一个page unlock的时候,访问这个zone里内存page的所有休眠的进程将都被唤醒,这样就会出现拥堵(thundering herd)的问题。建立一个哈希表管理多个等待队列,能解决这个问题,zone->wait_table就是这个哈希表。哈希表的方法可能还是会造成一些进程不必要的唤醒。但是这种事情发生的机率不是很频繁的。下面这个图就是进程及等待队列的运行关系:

    等待队列的哈希表的分配和建立在free_area_init_core函数中进行。哈希表的表项的数量在wait_table_size() 函数中计算,并且保持在zone->wait_table_size成员中。最大4096个等待队列。最小是NoPages / PAGES_PER_WAITQUEUE的2次方,NoPages是zone管理的page的数量,PAGES_PER_WAITQUEUE被定义256

    zone->wait_table_bits用于计算:根据page 地址得到需要使用的等待队列在哈希表中的索引的算法因子. page_waitqueue()函数负责返回zone中page所对应等待队列。它用一个基于struct page虚拟地址的简单的乘法哈希算法来确定等待队列的.

    page_waitqueue()函数用GOLDEN_RATIO_PRIME的地址和“右移zone→wait_table_bits一个索引值”的一个乘积来确定等待队列在哈希表中的索引的。

    Zone的初始化, 在kernel page table通过paging_init()函数完全建立起z来以后,zone被初始化。下面章节将描述这个。当然不同的体系结构这个过程肯定也是不一样的,但它们的目的却是相同的:确定什么参数需要传递给free_area_init()函数(对于UMA体系结构)或者free_area_init_node()函数(对于NUMA体系结构)。这里省略掉NUMA体系结构的说明。
    free_area_init()函数的参数:
    unsigned long *zones_sizes: 系统中每个zone所管理的page的数量的数组。这个时候,还没能确定zone中那些page是可以分配使用的(free)。这个信息知道boot memory allocator完成之前还无法知道。
    来源: http://www.uml.org.cn/embeded/201208071.asp

    4.6 冷热页与Per-CPU上的页面高速缓存


    内核经常请求和释放单个页框. 为了提升性能, 每个内存管理区都定义了一个每CPU(Per-CPU)的页面高速缓存. 所有”每CPU高速缓存”包含一些预先分配的页框, 他们被定义满足本地CPU发出的单一内存请求.

    struct zone的pageset成员用于实现冷热分配器(hot-n-cold allocator)

    struct zone
    {
        struct per_cpu_pageset __percpu *pageset;
    };

    内核说页面是热的, 意味着页面已经加载到CPU的高速缓存, 与在内存中的页相比, 其数据访问速度更快. 相反, 冷页则不再高速缓存中. 在多处理器系统上每个CPU都有一个或者多个告诉缓存. 各个CPU的管理必须是独立的.

    尽管内存域可能属于一个特定的NUMA结点, 因而关联到某个特定的CPU。 但其他CPU的告诉缓存仍然可以包含该内存域中的页面. 最终的效果是, 每个处理器都可以访问系统中的所有页, 尽管速度不同. 因而, 特定于内存域的数据结构不仅要考虑到所属NUMA结点相关的CPU, 还必须照顾到系统中其他的CPU.

    pageset是一个指针, 其容量与系统能够容纳的CPU的数目的最大值相同.

    数组元素类型为per_cpu_pageset, 定义在include/linux/mmzone.h?v4.7, line 254, 如下所示

    struct per_cpu_pageset {
           struct per_cpu_pages pcp;
    #ifdef CONFIG_NUMA
           s8 expire;
    #endif
    #ifdef CONFIG_SMP
           s8 stat_threshold;
           s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS];
    #endif
    };

    该结构由一个per_cpu_pages pcp变量组成, 该数据结构定义如下, 位于include/linux/mmzone.h?v4.7, line 245

    struct per_cpu_pages {
        int count;              /* number of pages in the list 列表中的页数  */
        int high;               /* high watermark, emptying needed 页数上限水印, 在需要的情况清空列表  */
        int batch;              /* chunk size for buddy add/remove,  添加/删除多页块的时候, 块的大小  */
    
        /* Lists of pages, one per migrate type stored on the pcp-lists 页的链表*/
           struct list_head lists[MIGRATE_PCPTYPES];
    };
    字段 描述
    count 记录了与该列表相关的页的数目
    high 是一个水印. 如果count的值超过了high, 则表明列表中的页太多了
    batch 如果可能, CPU的高速缓存不是用单个页来填充的, 而是欧诺个多个页组成的块, batch作为每次添加/删除页时多个页组成的块大小的一个参考值
    list 一个双链表, 保存了当前CPU的冷页或热页, 可使用内核的标准方法处理

    在内核中只有一个子系统会积极的尝试为任何对象维护per-cpu上的list链表, 这个子系统就是slab分配器.

    • struct per_cpu_pageset具有一个字段, 该字段

    • struct per_cpu_pages则维护了链表中目前已有的一系列页面, 高极值和低极值决定了何时填充该集合或者释放一批页面, 变量决定了一个块中应该分配多少个页面, 并最后决定在页面前的实际链表中分配多少各页面

    4.7 内存域的第一个页帧zone_start_pfn


    struct zone中通过zone_start_pfn成员标记了内存管理区的页面地址.

    然后内核也通过一些全局变量标记了物理内存所在页面的偏移, 这些变量定义在mm/nobootmem.c?v4.7, line 31

    unsigned long max_low_pfn;
    unsigned long min_low_pfn;
    unsigned long max_pfn;
    unsigned long long max_possible_pfn;

    PFN是物理内存以Page为单位的偏移量

    变量 描述
    max_low_pfn x86中,max_low_pfn变量是由find_max_low_pfn函数计算并且初始化的,它被初始化成ZONE_NORMAL的最后一个page的位置。这个位置是kernel直接访问的物理内存, 也是关系到kernel/userspace通过“PAGE_OFFSET宏”把线性地址内存空间分开的内存地址位置
    min_low_pfn 系统可用的第一个pfn是min_low_pfn变量, 开始与_end标号的后面, 也就是kernel结束的地方.在文件mm/bootmem.c中对这个变量作初始化
    max_pfn 系统可用的最后一个PFN是max_pfn变量, 这个变量的初始化完全依赖与硬件的体系结构.
    max_possible_pfn

    x86的系统中, find_max_pfn函数通过读取e820表获得最高的page frame的数值, 同样在文件mm/bootmem.c中对这个变量作初始化。e820表是由BIOS创建的

    This is the physical memory directly accessible by the kernel and is related to the kernel/userspace split in the linear address space marked by PAGE OFFSET.

    我理解为这段地址kernel可以直接访问,可以通过PAGE_OFFSET宏直接将kernel所用的虚拟地址转换成物理地址的区段。在文件mm/bootmem.c中对这个变量作初始化。在内存比较小的系统中max_pfn和max_low_pfn的值相同
    min_low_pfn, max_pfn和max_low_pfn这3个值,也要用于对高端内存(high memory)的起止位置的计算。在arch/i386/mm/init.c文件中会对类似的highstart_pfn和highend_pfn变量作初始化。这些变量用于对高端内存页面的分配。后面将描述。

    5 管理区表zone_table与管理区节点的映射


    内核在初始化内存管理区时, 首先建立管理区表zone_table. 参见mm/page_alloc.c?v=2.4.37, line 38

    /*
     *
     * The zone_table array is used to look up the address of the
     * struct zone corresponding to a given zone number (ZONE_DMA,
     * ZONE_NORMAL, or ZONE_HIGHMEM).
     */
    zone_t *zone_table[MAX_NR_ZONES*MAX_NR_NODES];
    EXPORT_SYMBOL(zone_table);

    MAX_NR_ZONES是一个节点中所能包容纳的管理区的最大数, 如3个, 定义在include/linux/mmzone.h?v=2.4.37, line 25, 与zone区域的类型(ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM)定义在一起. 当然这时候我们这些标识都是通过宏的方式来实现的, 而不是如今的枚举类型

    MAX_NR_NODES是可以存在的节点的最大数.

    函数EXPORT_SYMBOL使得内核的变量或者函数可以被载入的模块(比如我们的驱动模块)所访问.

    该表处理起来就像一个多维数组, 在函数free_area_init_core中, 一个节点的所有页面都会被初始化.

    6 zonelist内存域存储层次


    6.1 内存域之间的层级结构


    当前结点与系统中其他结点的内存域之前存在一种等级次序

    我们考虑一个例子, 其中内核想要分配高端内存.

    1. 它首先企图在当前结点的高端内存域找到一个大小适当的空闲段. 如果失败, 则查看该结点的普通内存域. 如果还失败, 则试图在该结点的DMA内存域执行分配.

    2. 如果在3个本地内存域都无法找到空闲内存, 则查看其他结点. 在这种情况下, 备
      选结点应该尽可能靠近主结点, 以最小化由于访问非本地内存引起的性能损失.

    内核定义了内存的一个层次结构, 首先试图分配”廉价的”内存. 如果失败, 则根据访问速度和容量, 逐渐尝试分配”更昂贵的”内存.

    高端内存是最廉价的, 因为内核没有任何部份依赖于从该内存域分配的内存. 如果高端内存域用尽, 对内核没有任何副作用, 这也是优先分配高端内存的原因.

    其次是普通内存域, 这种情况有所不同. 许多内核数据结构必须保存在该内存域, 而不能放置到高端内存域.

    因此如果普通内存完全用尽, 那么内核会面临紧急情况. 所以只要高端内存域的内存没有用尽, 都不会从普通内存域分配内存.

    最昂贵的是DMA内存域, 因为它用于外设和系统之间的数据传输. 因此从该内存域分配内存是最后一招.

    6.2 zonelist结构


    内核还针对当前内存结点的备选结点, 定义了一个等级次序. 这有助于在当前结点所有内存域的内存都用尽时, 确定一个备选结点

    内核使用pg_data_t中的zonelist数组, 来表示所描述的层次结构.

    typedef struct pglist_data {
        struct zonelist node_zonelists[MAX_ZONELISTS];
        /*  ......  */
    }pg_data_t;

    关于该结构zonelist的所有相关信息定义include/linux/mmzone.h?v=4.7, line 568, 我们下面慢慢来讲.

    node_zonelists数组对每种可能的内存域类型, 都配置了一个独立的数组项.

    该数组项的大小MAX_ZONELISTS用一个匿名的枚举常量定义, 定义在include/linux/mmzone.h?v=4.7, line 571

    enum
    {
        ZONELIST_FALLBACK,      /* zonelist with fallback */
    #ifdef CONFIG_NUMA
        /*
         * The NUMA zonelists are doubled because we need zonelists that
         * restrict the allocations to a single node for __GFP_THISNODE.
         */
        ZONELIST_NOFALLBACK,    /* zonelist without fallback (__GFP_THISNODE) */
    #endif
        MAX_ZONELISTS
    };

    我们会发现在UMA结构下, 数组大小MAX_ZONELISTS = 1, 因为只有一个内存结点, zonelist中只会存储一个ZONELIST_FALLBACK类型的结构, 但是NUMA下需要多余的ZONELIST_NOFALLBACK用以表示当前结点的信息

    pg_data_t->node_zonelists数组项用struct zonelis结构体定义, 该结构包含了类型为struct zoneref的一个备用列表由于该备用列表必须包括所有结点的所有内存域,因此由MAX_NUMNODES * MAX_NZ_ZONES项组成,外加一个用于标记列表结束的空指针

    struct zonelist结构的定义在include/linux/mmzone.h?v=4.7, line 606

    /*
     * One allocation request operates on a zonelist. A zonelist
     * is a list of zones, the first one is the 'goal' of the
     * allocation, the other zones are fallback zones, in decreasing
     * priority.
     *
     * To speed the reading of the zonelist, the zonerefs contain the zone index
     * of the entry being read. Helper functions to access information given
     * a struct zoneref are
     *
     * zonelist_zone()      - Return the struct zone * for an entry in _zonerefs
     * zonelist_zone_idx()  - Return the index of the zone for an entry
     * zonelist_node_idx()  - Return the index of the node for an entry
     */
    struct zonelist {
        struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
    };

    而struct zoneref结构的定义如下include/linux/mmzone.h?v=4.7, line 583

    /*
     * This struct contains information about a zone in a zonelist. It is stored
     * here to avoid dereferences into large structures and lookups of tables
     */
    struct zoneref {
        struct zone *zone;      /* Pointer to actual zone */
        int zone_idx;       /* zone_idx(zoneref->zone) */
    };

    6.3 内存域的排列方式


    那么我们内核是如何组织在zonelist中组织内存域的呢?

    NUMA系统中存在多个节点, 每个节点对应一个struct pglist_data结构, 每个结点中可以包含多个zone, 如: ZONE_DMA, ZONE_NORMAL, 这样就产生几种排列顺序, 以2个节点2个zone为例(zone从高到低排列, ZONE_DMA0表示节点0的ZONE_DMA,其它类似).

    • Legacy方式, 每个节点只排列自己的zone;

    Legacy方式

    • Node方式, 按节点顺序依次排列,先排列本地节点的所有zone,再排列其它节点的所有zone。

    Node方式

    • Zone方式, 按zone类型从高到低依次排列各节点的同相类型zone

    Zone方式

    可通过启动参数”numa_zonelist_order”来配置zonelist order,内核定义了3种配置, 这些顺序定义在mm/page_alloc.c?v=4.7, line 4551

    // http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4551
    #define ZONELIST_ORDER_DEFAULT  0 /* 智能选择Node或Zone方式 */
    
    #define ZONELIST_ORDER_NODE     1 /* 对应Node方式 */
    
    #define ZONELIST_ORDER_ZONE     2 /* 对应Zone方式 */

    注意

    在非NUMA系统中(比如UMA), 由于只有一个内存结点, 因此ZONELIST_ORDER_ZONE和ZONELIST_ORDER_NODE选项会配置相同的内存域排列方式, 因此, 只有NUMA可以配置这几个参数

    全局的current_zonelist_order变量标识了系统中的当前使用的内存域排列方式, 默认配置为ZONELIST_ORDER_DEFAULT, 参见mm/page_alloc.c?v=4.7, line 4564

    //  http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4564
    /* zonelist order in the kernel.
     * set_zonelist_order() will set this to NODE or ZONE.
     */
    static int current_zonelist_order = ZONELIST_ORDER_DEFAULT;
    static char zonelist_order_name[3][8] = {"Default", "Node", "Zone"};

    而zonelist_order_name方式分别对应了Legacy方式, Node方式和Zone方式. 其zonelist_order_name[current_zonelist_order]就标识了当前系统中所使用的内存域排列方式的名称”Default”, “Node”, “Zone”.

    zonelist_order_name 排列方式 描述
    ZONELIST_ORDER_DEFAULT Default 由系统智能选择Node或Zone方式
    ZONELIST_ORDER_NODE Node Node方式 按节点顺序依次排列,先排列本地节点的所有zone,再排列其它节点的所有zone
    ZONELIST_ORDER_ZONE Zone Zone方式 按zone类型从高到低依次排列各节点的同相类型zone

    内核就通过通过set_zonelist_order函数设置当前系统的内存域排列方式current_zonelist_order, 其定义依据系统的NUMA结构还是UMA结构有很大的不同. 该函数定义在mm/page_alloc.c?v=4.7, line 4571

    6.4 build_all_zonelists初始化内存节点


    内核通过build_all_zonelists初始化了内存结点的zonelists域

    • 首先内核通过set_zonelist_order函数设置了zonelist_order,如下所示, 参见mm/page_alloc.c?v=4.7, line 5031

    • 建立备用层次结构的任务委托给build_zonelists, 该函数为每个NUMA结点都创建了相应的数据结构. 它需要指向相关的pg_data_t实例的指针作为参数

    7 总结


    在linux中,内核也不是对所有物理内存都一视同仁,内核而是把页分为不同的区, 使用区来对具有相似特性的页进行分组.

    Linux必须处理如下两种硬件存在缺陷而引起的内存寻址问题:

    1. 一些硬件只能用某些特定的内存地址来执行DMA

    2. 一些体系结构其内存的物理寻址范围比虚拟寻址范围大的多。这样,就有一些内存不能永久地映射在内核空间上。

    为了解决这些制约条件,Linux使用了三种区:

    1. ZONE_DMA : 这个区包含的页用来执行DMA操作。

    2. ZONE_NOMAL : 这个区包含的都是能正常映射的页。

    3. ZONE_HIGHEM : 这个区包”高端内存”,其中的页能不永久地映射到内核地址空间

    而为了兼容一些设备的热插拔支持以及内存碎片化的处理, 内核也引入一些逻辑上的内存区.

    1. ZONE_MOVABLE : 内核定义了一个伪内存域ZONE_MOVABLE, 在防止物理内存碎片的机制memory migration中需要使用该内存域. 供防止物理内存碎片的极致使用

    2. ZONE_DEVICE : 为支持热插拔设备而分配的Non Volatile Memory非易失性内存

    区的实际使用与体系结构是相关的。linux把系统的内存结点划分区, 一个区包含了若干个内存页面, 形成不同的内存池,这样就可以根据用途进行分配了

    需要说明的是,区的划分没有任何物理意义, 只不过是内核为了管理页而采取的一种逻辑上的分组. 尽管某些分配可能需要从特定的区中获得页, 但这并不是说, 某种用途的内存一定要从对应的区来获取,如果这种可供分配的资源不够用了,内核就会占用其他可用去的内存.

    下表给出每个区及其在X86上所占的列表

    每个区及其在X86上所占的列表

    展开全文
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 ...在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换
    日期 内核版本 架构 作者 GitHub CSDN
    2016-09-01 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理

    在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后奇偶内核才能检测到可用内存和寄存器.

    1 前景回顾


    1.1 Linux内存管理的层次结构


    Linux把物理内存划分为三个层次来管理

    层次 描述
    存储节点(Node) CPU被划分为多个节点(node), 内存则被分簇, 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存簇bank,即每个内存簇被认为是一个节点
    管理区(Zone) 每个物理内存节点node被划分为多个内存管理区域, 用于表示不同范围的内存, 内核可以使用不同的映射方式映射物理内存
    页面(Page) 内存被细分为多个页面帧, 页面是最基本的页面分配的单位 |

    为了支持NUMA模型,也即CPU对不同内存单元的访问时间可能不同,此时系统的物理内存被划分为几个节点(node), 一个node对应一个内存簇bank,即每个内存簇被认为是一个节点

    • 首先, 内存被划分为结点. 每个节点关联到系统中的一个处理器, 内核中表示为pg_data_t的实例. 系统中每个节点被链接到一个以NULL结尾的pgdat_list链表中<而其中的每个节点利用pg_data_tnode_next字段链接到下一节.而对于PC这种UMA结构的机器来说, 只使用了一个成为contig_page_data的静态pg_data_t结构.

    • 接着各个节点又被划分为内存管理区域, 一个管理区域通过struct zone_struct描述, 其被定义为zone_t, 用以表示内存的某个范围, 低端范围的16MB被描述为ZONE_DMA, 某些工业标准体系结构中的(ISA)设备需要用到它, 然后是可直接映射到内核的普通内存域ZONE_NORMAL,最后是超出了内核段的物理地址域ZONE_HIGHMEM, 被称为高端内存. 是系统中预留的可用内存空间, 不能被内核直接映射.

    • 最后页帧(page frame)代表了系统内存的最小单位, 堆内存中的每个页都会创建一个struct page的一个实例. 传统上,把内存视为连续的字节,即内存为字节数组,内存单元的编号(地址)可作为字节数组的索引. 分页管理时,将若干字节视为一页,比如4K byte. 此时,内存变成了连续的页,即内存为页数组,每一页物理内存叫页帧,以页为单位对内存进行编号,该编号可作为页数组的索引,又称为页帧号.

    1.2 今日内容(启动过程中的内存初始化)


    在初始化过程中, 还必须建立内存管理的数据结构, 以及很多事务. 因为内核在内存管理完全初始化之前就需要使用内存. 在系统启动过程期间, 使用了额外的简化悉尼股市的内存管理模块, 然后在初始化完成后, 将旧的模块丢弃掉.

    因此我们可以把linux内核的内存管理分三个阶段。

    阶段 起点 终点 描述
    第一阶段 系统启动 bootmem或者memblock初始化完成 此阶段只能使用memblock_reserve函数分配内存, 早期内核中使用init_bootmem_done = 1标识此阶段结束
    第二阶段 bootmem或者memblock初始化完 buddy完成前 引导内存分配器bootmem或者memblock接受内存的管理工作, 早期内核中使用mem_init_done = 1标记此阶段的结束
    第三阶段 buddy初始化完成 系统停止运行 可以用cache和buddy分配内存

    1.3 start_kernel系统启动阶段的内存初始化过程


    首先我们来看看start_kernel是如何初始化系统的, start_kerne定义在init/main.c?v=4.7, line 479

    其代码很复杂, 我们只截取出其中与内存管理初始化相关的部分, 如下所示

    asmlinkage __visible void __init start_kernel(void)
    {
    
        setup_arch(&command_line);
        mm_init_cpumask(&init_mm);
    
        setup_per_cpu_areas();
    
    
        build_all_zonelists(NULL, NULL);
        page_alloc_init();
    
    
        /*
         * These use large bootmem allocations and must precede
         * mem_init();
         * kmem_cache_init();
         */
        mm_init();
    
        kmem_cache_init_late();
    
        kmemleak_init();
        setup_per_cpu_pageset();
    
        rest_init();
    }
    函数 功能
    setup_arch 是一个特定于体系结构的设置函数, 其中一项任务是负责初始化自举分配器
    mm_init_cpumask 初始化CPU屏蔽字
    setup_per_cpu_areas 函数(查看定义)给每个CPU分配内存,并拷贝.data.percpu段的数据. 为系统中的每个CPU的per_cpu变量申请空间.
    在SMP系统中, setup_per_cpu_areas初始化源代码中(使用per_cpu宏)定义的静态per-cpu变量, 这种变量对系统中每个CPU都有一个独立的副本.
    此类变量保存在内核二进制影像的一个独立的段中, setup_per_cpu_areas的目的就是为系统中各个CPU分别创建一份这些数据的副本
    在非SMP系统中这是一个空操作
    build_all_zonelists 建立并初始化结点和内存域的数据结构
    mm_init 建立了内核的内存分配器,
    其中通过mem_init停用bootmem分配器并迁移到实际的内存管理器(比如伙伴系统)
    然后调用kmem_cache_init函数初始化内核内部用于小块内存区的分配器
    kmem_cache_init_late 在kmem_cache_init之后, 完善分配器的缓存机制, 当前3个可用的内核内存分配器slab, slob, slub都会定义此函数
    kmemleak_init Kmemleak工作于内核态,Kmemleak 提供了一种可选的内核泄漏检测,其方法类似于跟踪内存收集器。当独立的对象没有被释放时,其报告记录在 /sys/kernel/debug/kmemleak中, Kmemcheck能够帮助定位大多数内存错误的上下文
    setup_per_cpu_pageset 初始化CPU高速缓存行, 为pagesets的第一个数组元素分配内存, 换句话说, 其实就是第一个系统处理器分配
    由于在分页情况下,每次存储器访问都要存取多级页表,这就大大降低了访问速度。所以,为了提高速度,在CPU中设置一个最近存取页面的高速缓存硬件机制,当进行存储器访问时,先检查要访问的页面是否在高速缓存中.

    1.4 setup_arch函数初始化内存流程


    前面我们的内核从start_kernel开始, 进入setup_arch(), 并完成了早期内存分配器的初始化和设置工作.

    void __init setup_arch(char **cmdline_p)
    {
        /*  初始化memblock  */
        arm64_memblock_init( );
    
        /*  分页机制初始化  */
        paging_init();
    
        bootmem_init();
    }
    流程 描述
    arm64_memblock_init 初始化memblock内存分配器
    paging_init 初始化分页机制
    bootmem_init 初始化内存管理

    该函数主要执行了如下操作

    1. 使用arm64_memblock_init来完成memblock机制的初始化工作, 至此memblock分配器接受系统中系统中内存的分配工作

    2. 调用paging_init来完成系统分页机制的初始化工作, 建立页表, 从而内核可以完成虚拟内存的映射和转换工作

    3. 最后调用bootmem_init来完成实现buddy内存管理所需要的工作

    1.5 (第一阶段)启动过程中的内存分配器


    在初始化过程中, 还必须建立内存管理的数据结构, 以及很多事务. 因为内核在内存管理完全初始化之前就需要使用内存. 在系统启动过程期间, 使用了额外的简化悉尼股市的内存管理模块, 然后在初始化完成后, 将旧的模块丢弃掉.

    这个阶段的内存分配其实很简单, 因此我们往往称之为内存分配器(而不是内存管理器), 早期的内核中内存分配器使用的bootmem引导分配器, 它基于一个内存位图bitmap, 使用最优适配算法来查找内存, 但是这个分配器有很大的缺陷, 最严重的就是内存碎片的问题, 因此在后来的内核中将其舍弃《而使用了新的memblock机制. memblock机制的初始化在arm64上是通过arm64_memblock_init函数来实现的

    ```cpp
    start_kernel()
        |---->page_address_init()
        |     考虑支持高端内存
        |     业务:初始化page_address_pool链表;
        |          将page_address_maps数组元素按索引降序插入
        |          page_address_pool链表; 
        |          初始化page_address_htable数组.
        | 
        |---->setup_arch(&command_line);
        |     初始化特定体系结构的内容
              |
              |---->arm64_memblock_init( );
              |     初始化引导阶段的内存分配器memblock
              |
              |---->paging_init();
              |     分页机制初始化
              |
              |---->bootmem_init();
              |     始化内存数据结构包括内存节点, 内存域和页帧page
                    |
                    |---->arm64_numa_init();
                    |     支持numa架构
                    |
                    |---->zone_sizes_init(min, max);
                        来初始化节点和管理区的一些数据项
                        |
                        |---->free_area_init_node
                        |   初始化内存节点
                        |
                            |---->free_area_init_core
                                |   初始化zone
                                |
                                |---->memmap_init
                                |   初始化page页面
                    |
                    |---->memblock_dump_all();
                    |   初始化完成, 显示memblock的保留的所有内存信息
                   |
        |---->build_all_zonelist()      [当前位置]
        |     为系统中的zone建立后备zone的列表.
        |     所有zone的后备列表都在
        |     pglist_data->node_zonelists[0]中;
        |
        |     期间也对per-CPU变量boot_pageset做了初始化. 
        |

    1.6 今日内容(第二阶段(二)–初始化备用内存域列表zonelists)


    我们之前讲了在memblock完成之后, 内存初始化开始进入第二阶段, 第二阶段是一个漫长的过程, 它执行了一系列复杂的操作, 从体系结构相关信息的初始化慢慢向上层展开, 其主要执行了如下操作

    特定于体系结构的设置

    在完成了基础的内存结点和内存域的初始化工作以后, 我们必须克服一些硬件的特殊设置

    • 在初始化内存的结点和内存区域之前, 内核先通过pagging_init初始化了内核的分页机制, 这样我们的虚拟运行空间就初步建立, 并可以完成物理地址到虚拟地址空间的映射工作.

    在arm64架构下, 内核在start_kernel()->setup_arch()中通过arm64_memblock_init( )完成了memblock的初始化之后, 接着通过setup_arch()->paging_init()开始初始化分页机制

    paging_init负责建立只能用于内核的页表, 用户空间是无法访问的. 这对管理普通应用程序和内核访问内存的方式,有深远的影响

    • 在分页机制完成后, 内核通过setup_arch()->bootmem_init开始进行内存基本数据结构(内存结点pg_data_t, 内存域zone和页帧)的初始化工作, 就是在这个函数中, 内核开始从体系结构相关的部分逐渐展开到体系结构无关的部分, 在zone_sizes_init->free_area_init_node中开始, 内核开始进行内存基本数据结构的初始化, 也不再依赖于特定体系结构无关的层次
    bootmem_init
    始化内存数据结构包括内存节点, 内存域和页帧page
    |
    |---->arm64_numa_init();
    |     支持numa架构
    |
    |---->zone_sizes_init(min, max);
        来初始化节点和管理区的一些数据项
        |
        |---->free_area_init_node
        |   初始化内存节点
        |
            |---->free_area_init_core
                |   初始化zone
                |
                |---->memmap_init
                |   初始化page页面
    |
    |---->memblock_dump_all();
    |   初始化完成, 显示memblock的保留的所有内存信息

    至此,bootmem_init已经完成了节点和管理区的关键数据已完成初始化, 内核在后面为内存管理做得一个准备工作就是将所有节点的管理区都链入到zonelist中,便于后面内存分配工作的进行.

    内核在start_kernel()–>build_all_zonelist()中完成zonelist的初始化

    2 后备内存域列表zonelists


    内核setup_arch的最后通过bootmem_init中完成了内存数据结构的初始化(包括内存结点pg_data_t, 内存管理域zone和页面信息page), 数据结构已经基本准备好了, 在后面为内存管理做得一个准备工作就是将所有节点的管理区都链入到zonelist中, 便于后面内存分配工作的进行.

    2.1 回到start_kernel函数(已经完成的工作)


    前面我们分析了start_kernel()->setup_arch()函数, 已经完成了memblock内存分配器的创建和初始化工作, 然后paging_init也完成分页机制的初始化, 然后bootmem_init也完成了内存结点和内存管理域的初始化工作. setup_arch函数已经执行完了, 现在我们回到start_kernel

    asmlinkage __visible void __init start_kernel(void)
    {
    
        setup_arch(&command_line);
    
    
        build_all_zonelists(NULL, NULL);
        page_alloc_init();
    
    
        /*
         * These use large bootmem allocations and must precede
         * mem_init();
         * kmem_cache_init();
         */
        mm_init();
    
        kmem_cache_init_late();
    
        kmemleak_init();
        setup_per_cpu_pageset();
    
        rest_init();
    }

    下面内核开始通过start_kernel()->build_all_zonelists来设计内存的组织形式

    2.2 后备内存域列表zonelist


    内存节点pg_data_t中将内存节点中的内存区域zone按照某种组织层次存储在一个zonelist中, 即pglist_data->node_zonelists成员信息

    //  http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L626
    typedef struct pglist_data
    {
        struct zone node_zones[MAX_NR_ZONES];
        struct zonelist node_zonelists[MAX_ZONELISTS];
    }

    内核定义了内存的一个层次结构关系, 首先试图分配廉价的内存,如果失败,则根据访问速度和容量,逐渐尝试分配更昂贵的内存.

    高端内存最廉价, 因为内核没有任何部分依赖于从该内存域分配的内存, 如果高端内存用尽, 对内核没有副作用, 所以优先分配高端内存

    普通内存域的情况有所不同, 许多内核数据结构必须保存在该内存域, 而不能放置到高端内存域, 因此如果普通内存域用尽, 那么内核会面临内存紧张的情况

    DMA内存域最昂贵,因为它用于外设和系统之间的数据传输。
    举例来讲,如果内核指定想要分配高端内存域。它首先在当前结点的高端内存域寻找适当的空闲内存段,如果失败,则查看该结点的普通内存域,如果还失败,则试图在该结点的DMA内存域分配。如果在3个本地内存域都无法找到空闲内存,则查看其他结点。这种情况下,备选结点应该尽可能靠近主结点,以最小化访问非本地内存引起的性能损失。

    2.3 build_all_zonelists初始化zonelists


    内核在start_kernel中通过build_all_zonelists完成了内存结点及其管理内存域的初始化工作, 调用如下

      build_all_zonelists(NULL, NULL);

    build_all_zonelists建立内存管理结点及其内存域的组织形式, 将描述内存的数据结构(结点, 管理域, 页帧)通过一定的算法组织在一起, 方便以后内存管理工作的进行. 该函数定义在mm/page_alloc.c?v4.7, line 5029

    2.4 build_all_zonelists函数


    /*
     * Called with zonelists_mutex held always
     * unless system_state == SYSTEM_BOOTING.
     *
     * __ref due to (1) call of __meminit annotated setup_zone_pageset
     * [we're only called with non-NULL zone through __meminit paths] and
     * (2) call of __init annotated helper build_all_zonelists_init
     * [protected by SYSTEM_BOOTING].
     */
    void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
    {
        /*  设置zonelist中节点和内存域的组织形式
         *  current_zonelist_order变量标识了当前系统的内存组织形式
         *  zonelist_order_name以字符串存储了系统中内存组织形式的名称  */
        set_zonelist_order();
    
        if (system_state == SYSTEM_BOOTING) {
            build_all_zonelists_init();
        } else {
    #ifdef CONFIG_MEMORY_HOTPLUG
            if (zone)
                setup_zone_pageset(zone);
    #endif
            /* we have to stop all cpus to guarantee there is no user
               of zonelist */
            stop_machine(__build_all_zonelists, pgdat, NULL);
            /* cpuset refresh routine should be here */
        }
        vm_total_pages = nr_free_pagecache_pages();
        /*
         * Disable grouping by mobility if the number of pages in the
         * system is too low to allow the mechanism to work. It would be
         * more accurate, but expensive to check per-zone. This check is
         * made on memory-hotadd so a system can start with mobility
         * disabled and enable it later
         */
        if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES))
            page_group_by_mobility_disabled = 1;
        else
            page_group_by_mobility_disabled = 0;
    
        pr_info("Built %i zonelists in %s order, mobility grouping %s.  Total pages: %ld\n",
            nr_online_nodes,
            zonelist_order_name[current_zonelist_order],
            page_group_by_mobility_disabled ? "off" : "on",
            vm_total_pages);
    #ifdef CONFIG_NUMA
        pr_info("Policy zone: %s\n", zone_names[policy_zone]);
    #endif
    }

    3 设置结点初始化顺序


    在build_all_zonelists开始, 首先内核通过set_zonelist_order函数设置了zonelist_order,如下所示, 参见mm/page_alloc.c?v=4.7, line 5031

    void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
    {
        set_zonelist_order();
        /* .......  */
    }

    3.1 zonelist


    前面我们讲解内存管理域时候讲解到, 系统中的所有管理域都存储在一个多维的数组zone_table. 内核在初始化内存管理区时, 必须要建立管理区表zone_table. 参见mm/page_alloc.c?v=2.4.37, line 38

    /*
     *
     * The zone_table array is used to look up the address of the
     * struct zone corresponding to a given zone number (ZONE_DMA,
     * ZONE_NORMAL, or ZONE_HIGHMEM).
     */
    zone_t *zone_table[MAX_NR_ZONES*MAX_NR_NODES];
    EXPORT_SYMBOL(zone_table);
    • MAX_NR_NODES为系统中内存结点的数目

    • MAX_NR_ZONES为系统中单个内存结点所拥有的最大内存区域数目

    3.2 内存域初始化顺序zonelist_order


    NUMA系统中存在多个节点, 每个节点对应一个struct pglist_data结构, 每个结点中可以包含多个zone, 如: ZONE_DMA, ZONE_NORMAL, 这样就产生几种排列顺序, 以2个节点2个zone为例(zone从高到低排列, ZONE_DMA0表示节点0的ZONE_DMA,其它类似).

    • Legacy方式, 每个节点只排列自己的zone;

    Legacy方式

    • Node方式, 按节点顺序依次排列,先排列本地节点的所有zone,再排列其它节点的所有zone。

    Node方式

    • Zone方式, 按zone类型从高到低依次排列各节点的同相类型zone

    Zone方式

    可通过启动参数”numa_zonelist_order”来配置zonelist order,内核定义了3种配置, 这些顺序定义在mm/page_alloc.c?v=4.7, line 4551

    // http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4551
    /*
     *  zonelist_order:
     *  0 = automatic detection of better ordering.
     *  1 = order by ([node] distance, -zonetype)
     *  2 = order by (-zonetype, [node] distance)
     *
     *  If not NUMA, ZONELIST_ORDER_ZONE and ZONELIST_ORDER_NODE will create
     *  the same zonelist. So only NUMA can configure this param.
     */
    #define ZONELIST_ORDER_DEFAULT  0 /* 智能选择Node或Zone方式 */
    
    #define ZONELIST_ORDER_NODE     1 /* 对应Node方式 */
    
    #define ZONELIST_ORDER_ZONE     2 /* 对应Zone方式 */

    注意

    在非NUMA系统中(比如UMA), 由于只有一个内存结点, 因此ZONELIST_ORDER_ZONE和ZONELIST_ORDER_NODE选项会配置相同的内存域排列方式, 因此, 只有NUMA可以配置这几个参数

    全局的current_zonelist_order变量标识了系统中的当前使用的内存域排列方式, 默认配置为ZONELIST_ORDER_DEFAULT, 参见mm/page_alloc.c?v=4.7, line 4564

    //  http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4564
    /* zonelist order in the kernel.
     * set_zonelist_order() will set this to NODE or ZONE.
     */
    static int current_zonelist_order = ZONELIST_ORDER_DEFAULT;
    static char zonelist_order_name[3][8] = {"Default", "Node", "Zone"};

    而zonelist_order_name方式分别对应了Legacy方式, Node方式和Zone方式. 其zonelist_order_name[current_zonelist_order]就标识了当前系统中所使用的内存域排列方式的名称”Default”, “Node”, “Zone”.

    zonelist_order_name 排列方式 描述
    ZONELIST_ORDER_DEFAULT Default 由系统智能选择Node或Zone方式
    ZONELIST_ORDER_NODE Node Node方式 按节点顺序依次排列,先排列本地节点的所有zone,再排列其它节点的所有zone
    ZONELIST_ORDER_ZONE Zone Zone方式 按zone类型从高到低依次排列各节点的同相类型zone

    3.3 set_zonelist_order设置排列方式


    内核就通过通过set_zonelist_order函数设置当前系统的内存域排列方式current_zonelist_order, 其定义依据系统的NUMA结构还是UMA结构有很大的不同.

    // http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4571
    #ifdef CONFIG_NUMA
    /* The value user specified ....changed by config */
    static int user_zonelist_order = ZONELIST_ORDER_DEFAULT;
    /* string for sysctl */
    #define NUMA_ZONELIST_ORDER_LEN 16
    char numa_zonelist_order[16] = "default";
    
    
    //  http://lxr.free-electrons.com/source/mm/page_alloc.c#L4571
    static void set_zonelist_order(void)
    {
        if (user_zonelist_order == ZONELIST_ORDER_DEFAULT)
            current_zonelist_order = default_zonelist_order();
        else
            current_zonelist_order = user_zonelist_order;
    }
    
    
    #else   /* CONFIG_NUMA */
    
    //  http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4892
    static void set_zonelist_order(void)
    {
        current_zonelist_order = ZONELIST_ORDER_ZONE;
    }

    其设置的基本流程如下

    • 如果系统当前系统是非NUMA结构的, 则系统中只有一个结点, 配置ZONELIST_ORDER_NODE和ZONELIST_ORDER_ZONE结果相同. 那么set_zonelist_order函数被定义为直接配置当前系统的内存域排列方式current_zonelist_order为ZONE方式(与NODE效果相同)

    • 如果系统是NUMA结构, 则设置为系统指定的方式即可

      1. 当前的排列方式为ZONELIST_ORDER_DEFAULT, 即系统默认方式, 则current_zonelist_order则由内核交给default_zonelist_order采用一定的算法选择一个最优的分配策略, 目前的系统中如果是32位则配置为ZONE方式, 而如果是64位系统则设置为NODE方式

      2. 当前的排列方式不是默认方式, 则设置为user_zonelist_order指定的内存域排列方式

    3.4 default_zonelist_order函数选择最优的配置


    在UMA结构下, 内存域使用NODE和ZONE两个排列方式会产生相同的效果, 因此系统不用特殊指定, 直接通过set_zonelist_order函数, 将当前系统的内存域排列方式current_zonelist_order配置为为ZONE方式(与NODE效果相同)即可

    但是NUMA结构下, 默认情况下(当配置了ZONELIST_ORDER_DEFAULT), 系统需要根据系统自身的环境信息选择一个最优的配置(NODE或者ZONE方式), 这个工作就由default_zonelist_order函数了来完成. 其定义在mm/page_alloc.c?v=4.7, line 4789

    #if defined(CONFIG_64BIT)
    /*
     * Devices that require DMA32/DMA are relatively rare and do not justify a
     * penalty to every machine in case the specialised case applies. Default
     * to Node-ordering on 64-bit NUMA machines
     */
    static int default_zonelist_order(void)
    {
        return ZONELIST_ORDER_NODE;
    }
    #else
    /*
     * On 32-bit, the Normal zone needs to be preserved for allocations accessible
     * by the kernel. If processes running on node 0 deplete the low memory zone
     * then reclaim will occur more frequency increasing stalls and potentially
     * be easier to OOM if a large percentage of the zone is under writeback or
     * dirty. The problem is significantly worse if CONFIG_HIGHPTE is not set.
     * Hence, default to zone ordering on 32-bit.
     */
    static int default_zonelist_order(void)
    {
        return ZONELIST_ORDER_ZONE;
    }
    #endif /* CONFIG_64BIT */

    3.5 user_zonelist_order用户指定排列方式


    在NUMA结构下, 系统支持用户指定内存域的排列方式, 用户以字符串的形式操作numa_zonelist_order(default, node和zone), 最终被内核转换为user_zonelist_order, 这个变量被指定为字符串numa_zonelist_order指定的排列方式, 他们定义在mm/page_alloc.c?v4.7, line 4573, 注意只有在NUMA结构中才需要这个配置信息.

    #ifdef CONFIG_NUMA
    /* The value user specified ....changed by config */
    static int user_zonelist_order = ZONELIST_ORDER_DEFAULT;
    /* string for sysctl */
    #define NUMA_ZONELIST_ORDER_LEN 16
    char numa_zonelist_order[16] = "default";
    
    #else
    /* ......*/
    #endif

    而接受和处理用户配置的工作, 自然是交给我们强大的proc文件系统来完成的, 可以通过/proc/sys/vm/numa_zonelist_order动态改变zonelist order的分配方式。

    /proc/sys/vm/numa_zonelist_order`

    内核通过setup_numa_zonelist_order读取并处理用户写入的配置信息

    • 接收到用户的信息后用__parse_numa_zonelist_order处理接收的参数

    • 如果前面用__parse_numa_zonelist_order处理的信息串成功, 则将对用的设置信息写入到字符串numa_zonelist_order中

    参见mm/page_alloc.c?v=4.7, line 4578

    /*
     * interface for configure zonelist ordering.
     * command line option "numa_zonelist_order"
     *      = "[dD]efault   - default, automatic configuration.
     *      = "[nN]ode      - order by node locality, then by zone within node
     *      = "[zZ]one      - order by zone, then by locality within zone
     */
    
    static int __parse_numa_zonelist_order(char *s)
    {
        if (*s == 'd' || *s == 'D') {
            user_zonelist_order = ZONELIST_ORDER_DEFAULT;
        } else if (*s == 'n' || *s == 'N') {
            user_zonelist_order = ZONELIST_ORDER_NODE;
        } else if (*s == 'z' || *s == 'Z') {
            user_zonelist_order = ZONELIST_ORDER_ZONE;
        } else {
            pr_warn("Ignoring invalid numa_zonelist_order value:  %s\n", s);
            return -EINVAL;
        }
        return 0;
    }
    
    static __init int setup_numa_zonelist_order(char *s)
    {
        int ret;
    
        if (!s)
            return 0;
    
        ret = __parse_numa_zonelist_order(s);
        if (ret == 0)
            strlcpy(numa_zonelist_order, s, NUMA_ZONELIST_ORDER_LEN);
    
        return ret;
    }
    early_param("numa_zonelist_order", setup_numa_zonelist_order);

    4 build_all_zonelists_init完成内存域zonelists的初始化


    build_all_zonelists函数在通过set_zonelist_order设置了zonelists中结点的组织顺序后, 首先检查了ssytem_state标识. 如果当前系统处于boot阶段(SYSTEM_BOOTING), 就开始通过build_all_zonelists_init函数初始化zonelist

    build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
    {
        /*  设置zonelist中节点和内存域的组织形式
         *  current_zonelist_order变量标识了当前系统的内存组织形式
         *  zonelist_order_name以字符串存储了系统中内存组织形式的名称  */
        set_zonelist_order();
    
        if (system_state == SYSTEM_BOOTING) {
            build_all_zonelists_init();

    4.1 system_state系统状态标识


    其中system_state变量是一个系统全局定义的用来表示系统当前运行状态的枚举变量, 其定义在include/linux/kernel.h?v=4.7, line 487

    /* Values used for system_state */
    extern enum system_states
    {
        SYSTEM_BOOTING,
        SYSTEM_RUNNING,
        SYSTEM_HALT,
        SYSTEM_POWER_OFF,
        SYSTEM_RESTART,
    } system_state;
    • 如果系统system_state是SYSTEM_BOOTING, 则调用build_all_zonelists_init初始化所有的内存结点

    • 否则的话如果定义了冷热页CONFIG_MEMORY_HOTPLUG且参数zone(待初始化的内存管理域zone)不为NULL, 则调用setup_zone_pageset设置冷热页

    if (system_state == SYSTEM_BOOTING)
    {
        build_all_zonelists_init();
    }
    else
    {
    #ifdef CONFIG_MEMORY_HOTPLUG
        if (zone)
            setup_zone_pageset(zone);
    #endif

    4.2 build_all_zonelists_init函数

    build_all_zonelists函数在如果当前系统处于boot阶段(system_state == SYSTEM_BOOTING), 就开始通过build_all_zonelists_init函数初始化zonelist

    build_all_zonelists_init函数定义在mm/page_alloc.c?v=4.7, line 5013

    static noinline void __init
    build_all_zonelists_init(void)
    {
        __build_all_zonelists(NULL);
        mminit_verify_zonelist();
        cpuset_init_current_mems_allowed();
    }

    build_all_zonelists_init将将所有工作都委托给__build_all_zonelists完成了zonelists的初始化工作, 后者又对系统中的各个NUMA结点分别调用build_zonelists.

    函数__build_all_zonelists定义在mm/page_alloc.c?v=4.7, line 4959

    /* return values int ....just for stop_machine() */
    static int __build_all_zonelists(void *data)
    {
        int nid;
        int cpu;
        pg_data_t *self = data;
    
        /*  ......  */
    
        for_each_online_node(nid) {
            pg_data_t *pgdat = NODE_DATA(nid);
    
            build_zonelists(pgdat);
        }
        /*  ......  */
    }

    for_each_online_node遍历了系统中所有的活动结点.

    由于UMA系统只有一个结点,build_zonelists只调用了一次, 就对所有的内存创建了内存域列表.

    NUMA系统调用该函数的次数等同于结点的数目. 每次调用对一个不同结点生成内存域数据

    4.3 build_zonelists初始化每个内存结点的zonelists


    build_zonelists(pg_data_t *pgdat)完成了节点pgdat上zonelists的初始化工作, 它建立了备用层次结构zonelists. 由于UMA和NUMA架构下结点的层次结构有很大的区别, 因此内核分别提供了两套不同的接口.

    如下所示

    // http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7
    4571 #ifdef CONFIG_NUMA
    
    4586 static int __parse_numa_zonelist_order(char *s)
    
    4601 static __init int setup_numa_zonelist_order(char *s)
    
    4619 int numa_zonelist_order_handler(struct ctl_table *table, int write,
    4620                 void __user *buffer, size_t *length,
    
    4678 static int find_next_best_node(int node, nodemask_t *used_node_mask)
    
    4730 static void build_zonelists_in_node_order(pg_data_t *pgdat, int node)
    
    4746 static void build_thisnode_zonelists(pg_data_t *pgdat)
    
    4765 static void build_zonelists_in_zone_order(pg_data_t *pgdat, int nr_nodes)
    
    4789 #if defined(CONFIG_64BIT)
    
    4795 static int default_zonelist_order(void)
    4799 #else
    4808 static int default_zonelist_order(void)
    4812 #endif /* CONFIG_64BIT */
    
    4822 static void build_zonelists(pg_data_t *pgdat)
    
    4872 #ifdef CONFIG_HAVE_MEMORYLESS_NODES
    4879 int local_memory_node(int node)
    4888 #endif
    
    4890 #else   /* CONFIG_NUMA */
    
    4897 static void build_zonelists(pg_data_t *pgdat)
    
    4892 static void set_zonelist_order(void)
    
    4931 #endif  /* CONFIG_NUMA */
    函数 NUMA UMA
    build_zonelists build_zonelists -=> mm/page_alloc.c?v=4.7, line 4822 build_zonelists -=> mm/page_alloc.c?v=4.7, line 4897

    build_zonelists_node -=> mm/page_alloc.c?v=4.7, line 4531

    我们以UMA结构下的build_zonelists为例, 来讲讲内核是怎么初始化备用内存域层次结构的, UMA结构下的build_zonelists函数定义在mm/page_alloc.c?v=4.7, line 4897, 如下所示

    node_zonelists的数组元素通过指针操作寻址, 这在C语言中是完全合法的惯例。实际工作则委托给build_zonelist_node。在调用时,它首先生成本地结点内分配内存时的备用次

    内核在build_zonelists中按分配代价从昂贵到低廉的次序, 迭代了结点中所有的内存域. 而在build_zonelists_node中, 则按照分配代价从低廉到昂贵的次序, 迭代了分配代价不低于当前内存域的内存域.

    首先我们来看看build_zonelists_node函数, 该函数定义在mm/page_alloc.c?v=4.7, line 4531

    /*
     * Builds allocation fallback zone lists.
     *
     * Add all populated zones of a node to the zonelist.
     */
    static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist, int nr_zones)
    {
        struct zone *zone;
        enum zone_type zone_type = MAX_NR_ZONES;
    
        do {
            zone_type--;
            zone = pgdat->node_zones + zone_type;
            if (populated_zone(zone)) {
                zoneref_set_zone(zone,
                    &zonelist->_zonerefs[nr_zones++]);
                check_highest_zone(zone_type);
            }
        } while (zone_type);
    
        return nr_zones;
    }

    备用列表zonelists的各项是借助于zone_type参数排序的, 该参数指定了最优先选择哪个内存域, 该参数的初始值是外层循环的控制变量i.

    我们知道其值可能是ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA或ZONE_DMA32之一.

    nr_zones表示从备用列表中的哪个位置开始填充新项. 由于列表中尚没有项, 因此调用者传递了0.

    内核在build_zonelists中按分配代价从昂贵到低廉的次序, 迭代了结点中所有的内存域. 而在build_zonelists_node中, 则按照分配代价从低廉到昂贵的次序, 迭代了分配代价不低于当前内存域的内存域.

    在build_zonelists_node的每一步中, 都对所选的内存域调用populated_zone, 确认zone->present_pages大于0, 即确认内存域中确实有页存在. 倘若如此, 则将指向zone实例的指针添加到zonelist->zones中的当前位置. 后备列表的当前位置保存在nr_zones.

    在每一步结束时, 都将内存域类型zone_type减1.换句话说, 设置为一个更昂贵的内存域类型. 例如, 如果开始的内存域是ZONE_HIGHMEM, 减1后下一个内存域类型是ZONE_NORMAL.

    考虑一个系统, 有内存域ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA。在第一次运行build_zonelists_node时, 实际上会执行下列赋值

    zonelist->zones[0] = ZONE_HIGHMEM;
    zonelist->zones[1] = ZONE_NORMAL;
    zonelist->zones[2] = ZONE_DMA;

    我们以某个系统为例, 图中示范了一个备用列表在多次循环中不断填充的过程. 系统中共有四个结点

    连续填充备用列表

    其中
    A=(NUMA)结点0 0=DMA内存域
    B=(NUMA)结点1 1=普通内存域
    C=(NUMA)结点2 2=高端内存域
    D=(NUMA)结点3

    第一步之后, 列表中的分配目标是高端内存, 接下来是第二个结点的普通和DMA内存域.

    内核接下来必须确立次序, 以便将系统中其他结点的内存域按照次序加入到备用列表.

    现在我们回到build_zonelists函数, UMA架构下该函数定义在mm/page_alloc.c?v=4.7, line 4897, 如下所示

    static void build_zonelists(pg_data_t *pgdat)
    {
        int node, local_node;
        enum zone_type j;
        struct zonelist *zonelist;
    
        /*  ......  */
    
        for (node = local_node + 1; node < MAX_NUMNODES; node++) {
            if (!node_online(node))
                continue;
            j = build_zonelists_node(NODE_DATA(node), zonelist, j);
        }
        for (node = 0; node < local_node; node++) {
            if (!node_online(node))
                continue;
            j = build_zonelists_node(NODE_DATA(node), zonelist, j);
        }
    
        zonelist->_zonerefs[j].zone = NULL;
        zonelist->_zonerefs[j].zone_idx = 0;
    }

    第一个循环依次迭代大于当前结点编号的所有结点. 在我们的例子中,有4个结点编号副本为0、1、2、3,此时只剩下结点3。新的项通过build_zonelists_node被加到备用列表。此时j的作用就体现出来了。在本地结点的备用目标找到之后,该变量的值是3。该值用作新项的起始位置。如果结点3也由3个内存域组成,备用列表在第二个循环之后的情况如图3-9的第二步所示

    第二个for循环接下来对所有编号小于当前结点的结点生成备用列表项。在我们的例子中,这些结点的编号为0和1。 如果这些结点也有3个内存域,则循环完毕之后备用列表的情况如下图下半部分所示

    完成的备用列表

    备用列表中项的数目一般无法准确知道,因为系统中不同结点的内存域配置可能并不相同。因此
    列表的最后一项赋值为空指针,显式标记列表结束。
    对总数N个结点中的结点m来说,内核生成备用列表时,选择备用结点的顺序总是:m、m+1、
    m+2、…、N1、0、1、…、m1。这确保了不过度使用任何结点。例如,对照情况是:使用一个独立
    于m、不变的备用列表。

    4.4 setup_pageset初始化per_cpu缓存


    前面讲解内存管理域zone的时候, 提到了per-CPU缓存, 即冷热页. 在组织每个节点的zonelist的过程中, setup_pageset初始化了per-CPU缓存(冷热页面)

    static void setup_pageset(struct per_cpu_pageset *p, unsigned long batch)
    {
        pageset_init(p);
        pageset_set_batch(p, batch);
    }

    在此之前free_area_init_node初始化内存结点的时候, 内核就输出了冷热页的一些信息, 该工作由zone_pcp_init完成, 该函数定义在mm/page_alloc.c?v=4.7, line 5029

    static __meminit void zone_pcp_init(struct zone *zone)
    {
        /*
         * per cpu subsystem is not up at this point. The following code
         * relies on the ability of the linker to provide the
         * offset of a (static) per cpu variable into the per cpu area.
         */
        zone->pageset = &boot_pageset;
    
        if (populated_zone(zone))
            printk(KERN_DEBUG "  %s zone: %lu pages, LIFO batch:%u\n",
                zone->name, zone->present_pages,
                         zone_batchsize(zone));
    }

    5 总结


    5.1 start_kernel启动流程


    start_kernel()
        |---->page_address_init()
        |     考虑支持高端内存
        |     业务:初始化page_address_pool链表;
        |          将page_address_maps数组元素按索引降序插入
        |          page_address_pool链表; 
        |          初始化page_address_htable数组.
        | 
        |---->setup_arch(&command_line);
        |
        |---->setup_per_cpu_areas();
        |     为per-CPU变量分配空间
        |
        |---->build_all_zonelist()
        |     为系统中的zone建立后备zone的列表.
        |     所有zone的后备列表都在
        |     pglist_data->node_zonelists[0]中;
        |
        |     期间也对per-CPU变量boot_pageset做了初始化. 
        |
        |---->page_alloc_init()
             |---->hotcpu_notifier(page_alloc_cpu_notifier, 0);
             |     不考虑热插拔CPU 
             |
        |---->pidhash_init()
        |     详见下文.
        |     根据低端内存页数和散列度,分配hash空间,并赋予pid_hash
        |
        |---->vfs_caches_init_early()
              |---->dcache_init_early()
              |     dentry_hashtable空间,d_hash_shift, h_hash_mask赋值;
              |     同pidhash_init();
              |     区别:
              |         散列度变化了(13 - PAGE_SHIFT);
              |         传入alloc_large_system_hash的最后参数值为0;
              |
              |---->inode_init_early()
              |     inode_hashtable空间,i_hash_shift, i_hash_mask赋值;
              |     同pidhash_init();
              |     区别:
              |         散列度变化了(14 - PAGE_SHIFT);
              |         传入alloc_large_system_hash的最后参数值为0;
              |

    5.2 pidhash_init配置高端内存


    void pidhash_init(void)
        |---->pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 
        |         0, 18, HASH_EARLY|HASH_SMALL, &pidhash_shift, NULL, 4096);
        |     根据nr_kernel_pages(低端内存的页数),分配哈希数组,以及各个哈希
        |     数组元素下的哈希链表的空间,原理如下:
        |     number = nr_kernel_pages; 
        |     number >= (18 - PAGE_SHIFT) 根据散列度获得数组元素个数
        |     number = roundup_pow_of_two(number);
        |     pidhash_shift = max{x | 2**x <= number}
        |     size = number * sizeof(*pid_hash);
        |     使用位图分配器分配size空间,将返回值付给pid_hash;
        |
        |---->pidhash_size = 1 << pidhash_shift;
        |
        |---->for(i = 0; i < pidhash_size; i++)
        |         INIT_HLIST_HEAD(&pid_hash[i]);

    5.3 build_all_zonelists初始化每个内存节点的zonelists


    void build_all_zonelists(void)
        |---->set_zonelist_order()
             |---->current_zonelist_order = ZONELIST_ORDER_ZONE;
        |
        |---->__build_all_zonelists(NULL);
        |    Memory不支持热插拔, 为每个zone建立后备的zone,
        |    每个zone及自己后备的zone,形成zonelist
            |
            |---->pg_data_t *pgdat = NULL;
            |     pgdat = &contig_page_data;(单node)
            |
            |---->build_zonelists(pgdat);
            |     为每个zone建立后备zone的列表
                |
                |---->struct zonelist *zonelist = NULL;
                |     enum zone_type j;
                |     zonelist = &pgdat->node_zonelists[0];
                |
                |---->j = build_zonelists_node(pddat, zonelist, 0, MAX_NR_ZONES - 1);
                |     为pgdat->node_zones[0]建立后备的zone,node_zones[0]后备的zone
                |     存储在node_zonelist[0]内,对于node_zone[0]的后备zone,其后备的zone
                |     链表如下(只考虑UMA体系,而且不考虑ZONE_DMA):
                |     node_zonelist[0]._zonerefs[0].zone = &node_zones[2];
                |     node_zonelist[0]._zonerefs[0].zone_idx = 2;
                |     node_zonelist[0]._zonerefs[1].zone = &node_zones[1];
                |     node_zonelist[0]._zonerefs[1].zone_idx = 1;
                |     node_zonelist[0]._zonerefs[2].zone = &node_zones[0];
                |     node_zonelist[0]._zonerefs[2].zone_idx = 0;
                |
                |     zonelist->_zonerefs[3].zone = NULL;
                |     zonelist->_zonerefs[3].zone_idx = 0;
            |
            |---->build_zonelist_cache(pgdat);
                  |---->pdat->node_zonelists[0].zlcache_ptr = NULL;
                  |     UMA体系结构
                  |
            |---->for_each_possible_cpu(cpu)
            |     setup_pageset(&per_cpu(boot_pageset, cpu), 0);
                  |详见下文
        |---->vm_total_pages = nr_free_pagecache_pages();
        |    业务:获得所有zone中的present_pages总和.
        |
        |---->page_group_by_mobility_disabled = 0;
        |     对于代码中的判断条件一般不会成立,因为页数会最够多(内存较大)
    展开全文
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 ...在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换
  • 分装库下载:https://github.com/facebook/FBMemoryProfiler FBMemoryProfiler类库使用教程:... 手机设备的内存是一个共享资源。应用程序可能会不当的耗尽内存、崩溃,或者遭遇大幅度的性能降低。 Facebook iO
  • linux内存管理--内存管理机制综述

    千次阅读 2013-09-25 22:27:15
    整个系统的性能取决于如何优先地管理动态内存。从两个角度介绍:连续物理内存处理、非连续内存区的处理。 一、页框管理  Linux采用4KB页框大小作为标准的内存分配单元。内核把物理页作为内存管理的基本单位。内存...
  • 因为买内存条前没有注意看,以为自己是1条16的。...(说明书有写优先使用插槽2) 16GA2 8+8B1、B2 能点亮电脑,cpu-z显示双通道,总共大小是32 但是没写双通道是16+16还是8+8 请指点?
  • 申请内存时可以指定堆(socket),或者任意堆,当指定任意堆时,会优先使用本地堆(本地socket)。 结合前面说的dpdk内存初始化,每个堆的原始块是相同socket_id的所有memseg。每个memseg段就是一个地址连续的块...
  • Android 内存缓存:手把手教你学会LrhCache算法

    千次阅读 多人点赞 2018-09-06 08:52:26
    内存缓存知识在Android开发中实现重要 本文将全面介绍内存缓存的所有相关知识(含LrhCache算法、其原理等),希望您们会喜欢 目录 1. 简介 下面,将详细介绍 LrhCache算法 2. LrhCache算法 ...
  • 计算机内存体系与Java 内存模型

    千次阅读 2019-05-26 15:39:41
    计算机内存相关硬件介绍与缓存一致性: 让计算机并发执行若干个任务与更充分的利用计算机处理器的效能之间的因果关系,非常的复杂,其中一个重要的复杂性来源是绝大多数的运算任务都不可能只靠处理器计算就能完成的...
  • 程序员需要了解的硬核知识之内存

    千次阅读 多人点赞 2019-10-28 13:03:02
    我们都知道,计算机是...什么内存 内存(Memory)是计算机中最重要的部件之一,它是程序与CPU进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的,因此内存对计算机的影响非常大,内存又被称为主存,其...
  • iOS 【在iOS中自动检测内存泄漏】

    千次阅读 2016-07-17 01:14:55
    iOS中如何自动检测内存泄漏
  • 程序的内存分配之堆和栈的区别

    万次阅读 多人点赞 2015-10-01 09:38:52
    堆栈概述  在计算机领域,堆栈是一个不容忽视的概念,堆栈是两种数据结构。堆栈都是一种数据项按序排列的数据...堆,优先队列(priority queue);普通的队列是一种先进先出的数据结构(FIFO—First-In/First-Out),元
  • 问题解决_系统识别不了内存

    千次阅读 2020-09-23 15:05:59
    电脑原来没有任何问题,新加内存后,操作系统法识别新内存,原来的部分内存条也识别不了了,表现为内存不增反降。 具体情况: 微星X299pro主板 bios能识别内存,但是系统不识别内存 内存插好了 内存没有坏 问题解决...
  • 内存屏障

    千次阅读 2018-03-20 00:38:13
    1. 什么内存屏障 它是一条CPU指令: a)确保一些特定操作执行的顺序; b)影响一些数据的可见性(可能是某些指令执行后的结果)。 2. 内存屏障与处理器重排序 现代的处理器使用写缓冲区来临时保存向内存写入的...
  • 优先队列

    千次阅读 2019-03-13 22:54:29
    根据老师在课上所讲的优先队列还有在csdn上做的几道优先队列的题,我在网上查了很多优先队列的资料,大体上了解了优先队列的内容,下面结合上课所讲还有网上的资料来总结一下优先队列我自己的简介。 优先队列书上...
  • 由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲:将运算需要使用到的数据复制...
  • 主要参数 -b -k -m:分别以字节(KB、MB)单位显示内存使用情况。-s delay:显示每隔多少秒数来显示一次内存使用情况。-t:显示内存总和列。-o:不显示缓冲区调节列。d.应用实例free命令是用来查看内...
  • 二叉树遍历——深度优先遍历、广度优先遍历

    万次阅读 多人点赞 2018-10-23 19:41:03
    要特别注意的是,二叉树的深度优先遍历比较特殊,可以细分先序遍历、中序遍历、后序遍历。具体说明如下: 先序遍历:对任一子树,先访问根,然后遍历其左子树,最后遍历其右子树。 中序遍历:对任一子树,先遍历...
  • Java内存模型

    千次阅读 2016-09-02 09:08:52
    概述  衡量一个服务性能的高低好坏,每秒事务处理数... Java内存模型(Java Memory Model,JMM)用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。J
  • Java与C++之间有- -堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。 2.1 概述 对于从事C、C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力的“皇帝”...
  • Q:在Java中怎么可以产生内存泄露? A:Java中,造成内存泄露的原因有很多种。典型的例子是一个没有实现hasCode和equals方法的Key类在HashMap中保存的情况。最后会生成很多重复的对象。所有的内存泄露最后都会抛出...
  • JVM内存结构和Java内存模型别再傻傻分不清了

    万次阅读 多人点赞 2020-03-04 20:53:30
    JVM内存结构和Java内存模型都是面试的热点问题,名字看感觉都差不多,网上有些博客也都把这两个概念混着用,实际上他们之间差别还是挺大的。 通俗点说,JVM内存结构是与JVM的内部存储结构相关,而Java内存模型是与多...
  • 关于启动过程内存管理见《内存管理-之启动》 如果需要,内存管理三篇文章整理成pdf了,下载地址http://download.csdn.net/detail/shichaog/8662135 第四章 物理内存管理 图2.2中(见内存管理-之启动)已经展示了伙伴...
  • 优先优先调度算法(java实现)

    千次阅读 2019-04-14 17:33:39
    目录 1、算法思想 ...(1)当该算法用于作业调度时,系统从后备作业队列中选择若干个优先级最高的,且系统能满足资源要求的作业装入内存运行。 (2)当该算法用于进程调度时,将把处理机分配给...
  • Java内存模型与线程

    千次阅读 2018-08-07 11:28:07
    Java内存模型与线程 Java内存模型与线程 Start 硬件的效率与一致性 Java内存模型 主内存与工作内存 内存间交互操作 对于volatile型变量的特殊规则 可见性 禁止指令重排序优化 对于long和double型变量的特殊...
  • 邻接表介绍邻接矩阵是不错的一种图存储结构,但是我们也发现,对于边数相对顶点较少的图...关于邻接矩阵的详细介绍请看:图:图的邻接矩阵创建、深度优先遍历和广度优先遍历详解 。邻接表创建图我们创建边表的单链...
  • 内存不能只读问题的解决办法

    千次阅读 2013-01-18 14:34:13
    内存不能Read只读问题的解决办法 出现这个现象有方面的,一是硬件,即内存方面有问题,二是软件,这就有多方面的问题了。  一:先说说硬件:  一般来说,电脑硬件是很不容易坏的。内存出现问题的可能性...
  • MySQL的内存

    千次阅读 2015-07-30 17:10:32
    我们在做网站时,偶然遇到一个问题:网站工作时,有时需要一些不变的资源表,需要根据不同的人的信息,去查表寻找对应需要的数据。由于每个人都需要查表...内存表的特性所谓“内存表”,即数据保存在内存中的MySQL表

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 94,023
精华内容 37,609
关键字:

为什么内存优先插24