精华内容
下载资源
问答
  • 日期 内核版本 架构 作者 GitHub ... Linux内存管理在内存管理的上下文中, 初始(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 今日内容(第二阶段(一)–初始化内存管理数据结构)


    我们之前讲了在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的保留的所有内存信息

    建立内存管理的数据结构

    对相关数据结构的初始化是从全局启动函数start_kernel中开始的, 该函数在加载内核并激活各个子系统之后执行. 由于内存管理是内核一个非常重要的部分, 因此在特定体系结构的设置步骤中检测并确定系统中内存的分配情况后, 会立即执行内存管理的初始化.

    移交早期的分配器到内存管理器

    最后我们的内存管理器已经初始化并设置完成, 可以投入运行了, 因此内核将内存管理的工作从早期的内存分配器(bootmem或者memblock)移交到我们的buddy伙伴系统.

    2 初始化前的准备工作


    2.1 回到setup_arch函数(当前已经完成的工作)


    现在我们回到start_kernel()->setup_arch()函数

    void __init setup_arch(char **cmdline_p)
    {
        /*  初始化memblock  */
        arm64_memblock_init( );
    
        /*  分页机制初始化  */
        paging_init();
    
        bootmem_init();
    }

    到目前位置我们已经完成了如下工作

    • memblock已经通过arm64_memblock_init完成了初始化, 至此系统中的内存可以通过memblock分配了

    • paging_init完成了分页机制的初始化, 至此内核已经布局了一套完整的虚拟内存空间

    至此我们所有的内存都可以通过memblock机制来分配和释放, 尽管它实现的笨拙而简易, 但是已经足够我们初始化阶段使用了, 反正内核页不可能指着它过一辈子, 而我们也通过pagging_init创建了页表, 为内核提供了一套可供内核和进程运行的虚拟运行空间, 我们可以安全的进行内存的分配了

    因此该是时候初始化我们强大的buddy系统了.

    内核接着setup_arch()->bootmem_init()函数开始执行

    体系结构相关的代码需要在启动期间建立如下信息

    • 系统中各个内存域的页帧边界,保存在max_zone_pfn数组

    早期的内核还需记录各结点页帧的分配情况,保存在全局变量early_node_map中

    zone_sizes_init函数

    内核提供了一个通用的框架, 用于将上述信息转换为伙伴系统预期的节点和内存域数据结构, 但是在此之前各个体系结构必须自行建立相关结构.

    2.2 bootmem_init函数初始化内存结点和管理域


    arm64架构下, 在setup_arch中通过paging_init函数初始化内核分页机制之后, 内核通过bootmem_init()开始完成内存结点和内存区域的初始化工作, 该函数定义在arch/arm64/mm/init.c, line 306

    void __init bootmem_init(void)
    {
        unsigned long min, max;
    
        min = PFN_UP(memblock_start_of_DRAM());
        max = PFN_DOWN(memblock_end_of_DRAM());
    
        early_memtest(min << PAGE_SHIFT, max << PAGE_SHIFT);
    
        max_pfn = max_low_pfn = max;
    
        arm64_numa_init();
        /*
         * Sparsemem tries to allocate bootmem in memory_present(), so must be
         * done after the fixed reservations.
         */
        arm64_memory_present();
    
        sparse_init();
        zone_sizes_init(min, max);
    
        high_memory = __va((max << PAGE_SHIFT) - 1) + 1;
        memblock_dump_all();
    }

    2.3 zone_sizes_init函数


    在初始化内存结点和内存域之前, 内核首先通过setup_arch()–>bootmem_init()–>zone_sizes_init()来初始化节点和管理区的一些数据项, 其中关键的是初始化了系统中各个内存域的页帧边界,保存在max_zone_pfn数组.

    zone_sizes_init函数定义在arch/arm64/mm/init.c?v=4.7, line 92, 由于arm64支持NUMA和UMA两种存储器架构, 因此该函数依照NUMA和UMA, 有两种不同的实现.

    #ifdef CONFIG_NUMA
    
    static void __init zone_sizes_init(unsigned long min, unsigned long max)
    {
        unsigned long max_zone_pfns[MAX_NR_ZONES]  = {0};
    
        if (IS_ENABLED(CONFIG_ZONE_DMA))
            max_zone_pfns[ZONE_DMA] = PFN_DOWN(max_zone_dma_phys());
        max_zone_pfns[ZONE_NORMAL] = max;
    
        free_area_init_nodes(max_zone_pfns);
    }
    
    #else
    
    static void __init zone_sizes_init(unsigned long min, unsigned long max)
    {
        struct memblock_region *reg;
        unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
        unsigned long max_dma = min;
    
        memset(zone_size, 0, sizeof(zone_size));
    
        /* 4GB maximum for 32-bit only capable devices */
    #ifdef CONFIG_ZONE_DMA
        max_dma = PFN_DOWN(arm64_dma_phys_limit);
        zone_size[ZONE_DMA] = max_dma - min;
    #endif
        zone_size[ZONE_NORMAL] = max - max_dma;
    
        memcpy(zhole_size, zone_size, sizeof(zhole_size));
    
        for_each_memblock(memory, reg) {
            unsigned long start = memblock_region_memory_base_pfn(reg);
            unsigned long end = memblock_region_memory_end_pfn(reg);
    
            if (start >= max)
                continue;
    
    #ifdef CONFIG_ZONE_DMA
            if (start < max_dma) {
                unsigned long dma_end = min(end, max_dma);
                zhole_size[ZONE_DMA] -= dma_end - start;
            }
    #endif
            if (end > max_dma) {
                unsigned long normal_end = min(end, max);
                unsigned long normal_start = max(start, max_dma);
                zhole_size[ZONE_NORMAL] -= normal_end - normal_start;
            }
        }
    
        free_area_init_node(0, zone_size, min, zhole_size);
    }
    
    #endif /* CONFIG_NUMA */

    在获取了三个管理区的页面数后, NUMA架构下通过free_area_init_nodes()来完成后续工作, 其中核心函数为free_area_init_node(),用来针对特定的节点进行初始化, 由于UMA架构下只有一个内存结点, 因此直接通过free_area_init_node来完成内存结点的初始化

    截至到目前为止, 体系结构相关的部分已经结束了, 各个体系结构已经自行建立了自己所需的一些底层数据结构, 这些结构建立好以后, 内核将繁重的内存数据结构创建和初始化的工作交给free_area_init_node(s)函数来完成,

    3 free_area_init_nodes初始化NUMA管理数据结构


    注意

    此部分内容参照

    Linux内存管理伙伴算法

    linux 内存管理 - paging_init 函数

    free_area_init_nodes初始化了NUMA系统中所有结点的pg_data_t和zone、page的数据, 并打印了管理区信息, 该函数定义在mm/page_alloc.c?v=4.7, line 6460

    3.1 代码注释


    //  初始化各个节点的所有pg_data_t和zone、page的数据
    void __init free_area_init_nodes(unsigned long *max_zone_pfn)
    {
        unsigned long start_pfn, end_pfn;
        int i, nid;
    
        /* Record where the zone boundaries are
         * 全局数组arch_zone_lowest_possible_pfn
         * 用来存储各个内存域可使用的最低内存页帧编号   */
        memset(arch_zone_lowest_possible_pfn, 0,
                    sizeof(arch_zone_lowest_possible_pfn));
    
        /* 全局数组arch_zone_highest_possible_pfn
         * 用来存储各个内存域可使用的最高内存页帧编号   */
        memset(arch_zone_highest_possible_pfn, 0,
                    sizeof(arch_zone_highest_possible_pfn));
    
        /* 辅助函数find_min_pfn_with_active_regions
         * 用于找到注册的最低内存域中可用的编号最小的页帧 */
        arch_zone_lowest_possible_pfn[0] = find_min_pfn_with_active_regions();
    
        /*  max_zone_pfn记录了各个内存域包含的最大页帧号  */
        arch_zone_highest_possible_pfn[0] = max_zone_pfn[0];
    
        /*  依次遍历,确定各个内存域的边界    */
        for (i = 1; i < MAX_NR_ZONES; i++) {
            /*  由于ZONE_MOVABLE是一个虚拟内存域
             *  不与真正的硬件内存域关联
             *  该内存域的边界总是设置为0 */
            if (i == ZONE_MOVABLE)
                continue;
            /*  第n个内存域的最小页帧
             *  即前一个(第n-1个)内存域的最大页帧  */
            arch_zone_lowest_possible_pfn[i] =
                arch_zone_highest_possible_pfn[i-1];
            /*  不出意外,当前内存域的最大页帧
             *  由max_zone_pfn给出  */
            arch_zone_highest_possible_pfn[i] =
                max(max_zone_pfn[i], arch_zone_lowest_possible_pfn[i]);
        }
        arch_zone_lowest_possible_pfn[ZONE_MOVABLE] = 0;
        arch_zone_highest_possible_pfn[ZONE_MOVABLE] = 0;
    
        /* Find the PFNs that ZONE_MOVABLE begins at in each node */
        memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
        /*  用于计算进入ZONE_MOVABLE的内存数量  */
        find_zone_movable_pfns_for_nodes();
    
        /* Print out the zone ranges
         * 将各个内存域的最大、最小页帧号显示出来  */
        pr_info("Zone ranges:\n");
        for (i = 0; i < MAX_NR_ZONES; i++) {
            if (i == ZONE_MOVABLE)
                continue;
            pr_info("  %-8s ", zone_names[i]);
            if (arch_zone_lowest_possible_pfn[i] ==
                    arch_zone_highest_possible_pfn[i])
                pr_cont("empty\n");
            else
                pr_cont("[mem %#018Lx-%#018Lx]\n",
                    (u64)arch_zone_lowest_possible_pfn[i]
                        << PAGE_SHIFT,
                    ((u64)arch_zone_highest_possible_pfn[i]
                        << PAGE_SHIFT) - 1);
        }
    
        /* Print out the PFNs ZONE_MOVABLE begins at in each node */
        pr_info("Movable zone start for each node\n");
        for (i = 0; i < MAX_NUMNODES; i++) {
            /*  对每个结点来说,zone_movable_pfn[node_id]
             *  表示ZONE_MOVABLE在movable_zone内存域中所取得内存的起始地址
             *  内核确保这些页将用于满足符合ZONE_MOVABLE职责的内存分配 */
            if (zone_movable_pfn[i])
            {
                /*  显示各个内存域的分配情况  */
                pr_info("  Node %d: %#018Lx\n", i,
                       (u64)zone_movable_pfn[i] << PAGE_SHIFT);
            }
        }
    
        /* Print out the early node map */
        pr_info("Early memory node ranges\n");
        for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid)
            pr_info("  node %3d: [mem %#018Lx-%#018Lx]\n", nid,
                (u64)start_pfn << PAGE_SHIFT,
                ((u64)end_pfn << PAGE_SHIFT) - 1);
    
        /* Initialise every node */
        mminit_verify_pageflags_layout();
        setup_nr_node_ids();
    
        /*  代码遍历所有的活动结点,
         *  并分别对各个结点调用free_area_init_node建立数据结构,
         *  该函数需要结点第一个可用的页帧作为一个参数,
         *  而find_min_pfn_for_node则从early_node_map数组提取该信息   */
        for_each_online_node(nid) {
            pg_data_t *pgdat = NODE_DATA(nid);
            free_area_init_node(nid, NULL,
                    find_min_pfn_for_node(nid), NULL);
    
            /* Any memory on that node
             * 根据node_present_pages字段判断结点具有内存
             * 则在结点位图中设置N_HIGH_MEMORY标志
             * 该标志只表示结点上存在普通或高端内存
             * 因此check_for_regular_memory
             * 进一步检查低于ZONE_HIGHMEM的内存域中是否有内存
             * 并据此在结点位图中相应地设置N_NORMAL_MEMORY   */
            if (pgdat->node_present_pages)
                node_set_state(nid, N_MEMORY);
            check_for_memory(pgdat, nid);
        }
    }

    free_area_init_nodes函数中通过循环遍历各个节点,循环中调用了free_area_init_node函数初始化该节点对应的pg_data_t和zone、page的数据.

    3.2 设置可使用的页帧编号


    free_area_init_nodes首先必须分析并改写特定于体系结构的代码提供的信息。其中,需要对照在zone_max_pfn和zone_min_pfn中指定的内存域的边界,计算各个内存域可使用的最低和最高的页帧编号。使用了两个全局数组来存储这些信息:

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

    static unsigned long __meminitdata arch_zone_lowest_possible_pfn[MAX_NR_ZONES];
    
    static unsigned long __meminitdata arch_zone_highest_possible_pfn[MAX_NR_ZONES];

    通过max_zone_pfn传递给free_area_init_nodes的信息记录了各个内存域包含的最大页帧号。
    free_area_init_nodes将该信息转换为一种更方便的表示形式,即以[low, high]形式描述各个内
    存域的页帧区间,存储在前述的全局变量中(我省去了对这些变量填充字节0的初始化过程):

    void __init free_area_init_nodes(unsigned long *max_zone_pfn)
    {
        /*  ......  */
        arch_zone_lowest_possible_pfn[ZONE_MOVABLE] = 0;
        arch_zone_highest_possible_pfn[ZONE_MOVABLE] = 0;
    
        /* Find the PFNs that ZONE_MOVABLE begins at in each node */
        memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
        /*  用于计算进入ZONE_MOVABLE的内存数量  */
        find_zone_movable_pfns_for_nodes();
        /*  依次遍历,确定各个内存域的边界    */
        for (i = 1; i < MAX_NR_ZONES; i++) {
            /*  由于ZONE_MOVABLE是一个虚拟内存域
             *  不与真正的硬件内存域关联
             *  该内存域的边界总是设置为0 */
            if (i == ZONE_MOVABLE)
                continue;
            /*  第n个内存域的最小页帧
             *  即前一个(第n-1个)内存域的最大页帧  */
            arch_zone_lowest_possible_pfn[i] =
                arch_zone_highest_possible_pfn[i-1];
            /*  不出意外,当前内存域的最大页帧
             *  由max_zone_pfn给出  */
            arch_zone_highest_possible_pfn[i] =
                max(max_zone_pfn[i], arch_zone_lowest_possible_pfn[i]);
        }
    
        /*  ......  */
    }

    辅助函数find_min_pfn_with_active_regions用于找到注册的最低内存域中可用的编号最小的页帧。该内存域不必一定是ZONE_DMA,例如,在计算机不需要DMA内存的情况下也可以是ZONE_NORMAL。最低内存域的最大页帧号可以从max_zone_pfn提供的信息直接获得。

    3.3 构建其他内存域的页帧区间


    接下来构建其他内存域的页帧区间,方法很直接:第n个内存域的最小页帧,即前一个(第n-1个)内存域的最大页帧。当前内存域的最大页帧由max_zone_pfn给出

    void __init free_area_init_nodes(unsigned long *max_zone_pfn)
    {
        /*  ......  */
    
        arch_zone_lowest_possible_pfn[ZONE_MOVABLE] = 0;
        arch_zone_highest_possible_pfn[ZONE_MOVABLE] = 0;
    
        /* Find the PFNs that ZONE_MOVABLE begins at in each node */
        memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
        /*  用于计算进入ZONE_MOVABLE的内存数量  */
        find_zone_movable_pfns_for_nodes();
    
        /*  ......  */
    }

    由于ZONE_MOVABLE是一个虚拟内存域,不与真正的硬件内存域关联,该内存域的边界总是设置为0。回忆前文,可知只有在指定了内核命令行参数kernelcore或movablecore之一时,该内存域才会存在.
    该内存域一般开始于各个结点的某个特定内存域的某一页帧号。相应的编号在find_zone_movable_pfns_for_nodes里计算。

    现在可以向用户提供一些有关已确定的页帧区间的信息。举例来说,其中可能包括下列内容(输出取自AMD64系统,有4 GiB物理内存):

    > dmesg
    
    Zone PFN ranges:
    DMA 0 0 -> 4096
    DMA32 4096 -> 1048576
    Normal 1048576 -> 1245184

    3.4 建立结点数据结构


    free_area_init_nodes剩余的部分遍历所有结点,分别建立其数据结构

    void __init free_area_init_nodes(unsigned long *max_zone_pfn)
    {
        /*  输出有关内存域的信息  */
        /*  ......  */
    
        /*  代码遍历所有的活动结点,
         *  并分别对各个结点调用free_area_init_node建立数据结构,
         *  该函数需要结点第一个可用的页帧作为一个参数,
         *  而find_min_pfn_for_node则从early_node_map数组提取该信息   */
        for_each_online_node(nid) {
            pg_data_t *pgdat = NODE_DATA(nid);
            free_area_init_node(nid, NULL,
                    find_min_pfn_for_node(nid), NULL);
    
            /* Any memory on that node
             * 根据node_present_pages字段判断结点具有内存
             * 则在结点位图中设置N_HIGH_MEMORY标志
             * 该标志只表示结点上存在普通或高端内存
             * 因此check_for_regular_memory
             * 进一步检查低于ZONE_HIGHMEM的内存域中是否有内存
             * 并据此在结点位图中相应地设置N_NORMAL_MEMORY   */
            if (pgdat->node_present_pages)
                node_set_state(nid, N_MEMORY);
            check_for_memory(pgdat, nid);
        }
    
        /*  ......  */
    }

    代码遍历所有活动结点,并分别对各个结点调用free_area_init_node建立数据结构。该函数需要结点第一个可用的页帧作为一个参数,而find_min_pfn_for_node则从early_node_map数组提取该信息。

    如果根据node_present_pages字段判断结点具有内存,则在结点位图中设置N_HIGH_MEMORY标志。我们知道该标志只表示结点上存在普通或高端内存,因此check_for_regular_memory进一步检查低于ZONE_HIGHMEM的内存域中是否有内存,并据此在结点位图中相应地设置N_NORMAL_MEMORY标志

    4 free_area_init_node初始化UMA内存结点


    free_area_init_nodes函数初始化所有结点的pg_data_t和zone、page的数据,并打印了管理区信息.

    4.1 free_area_init_node函数注释


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

    void __paginginit free_area_init_node(int nid, unsigned long *zones_size,
            unsigned long node_start_pfn, unsigned long *zholes_size)
    {
        pg_data_t *pgdat = NODE_DATA(nid);
        unsigned long start_pfn = 0;
        unsigned long end_pfn = 0;
    
        /* pg_data_t should be reset to zero when it's allocated */
        WARN_ON(pgdat->nr_zones || pgdat->classzone_idx);
    
        reset_deferred_meminit(pgdat);
        pgdat->node_id = nid;
        pgdat->node_start_pfn = node_start_pfn;
    #ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
        get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);
        pr_info("Initmem setup node %d [mem %#018Lx-%#018Lx]\n", nid,
            (u64)start_pfn << PAGE_SHIFT,
            end_pfn ? ((u64)end_pfn << PAGE_SHIFT) - 1 : 0);
    #else
        start_pfn = node_start_pfn;
    #endif
        /*  首先累计各个内存域的页数
         *  计算结点中页的总数
         *  对连续内存模型而言
         *  这可以通过zone_sizes_init完成
         *  但calculate_node_totalpages还考虑了内存空洞 */
        calculate_node_totalpages(pgdat, start_pfn, end_pfn,
                      zones_size, zholes_size);
        /*  分配了该节点的页面描述符数组
         *  [pgdat->node_mem_map数组的内存分配  */
        alloc_node_mem_map(pgdat);
    #ifdef CONFIG_FLAT_NODE_MEM_MAP
        printk(KERN_DEBUG "free_area_init_node: node %d, pgdat %08lx, node_mem_map %08lx\n",
            nid, (unsigned long)pgdat,
            (unsigned long)pgdat->node_mem_map);
    #endif
    
        /*  对该节点的每个区[DMA,NORMAL,HIGH]的的结构进行初始化  */
        free_area_init_core(pgdat);
    }

    4.2 流程分析


    • calculate_node_totalpages函数累计各个内存域的页数,计算结点中页的总数。对连续内存模型而言,这可以通过zone_sizes_init完成,但calculate_node_totalpages还考虑了内存空洞,该函数定义在mm/page_alloc.c, line 5789

      以下例子取自一个UMA系统, 具有512 MiB物理内存。

    > dmesg
    ...
    On node 0 totalpages: 131056
    • alloc_node_mem_map(pgdat)函数分配了该节点的页面描述符数组[pgdat->node_mem_map数组的内存分配.

    • 继续调用free_area_init_core函数,继续初始化该节点的pg_data_t结构,初始化zone以及page结构 ,##2.6 free_area_init_core函数是初始化zone的核心

    4.3 alloc_node_mem_map函数


    alloc_node_mem_map负责初始化一个简单但非常重要的数据结构。如上所述,系统中的各个物理内存页,都对应着一个struct page实例。该结构的初始化由alloc_node_mem_map执行

    static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat)
    {
        unsigned long __maybe_unused start = 0;
        unsigned long __maybe_unused offset = 0;
    
        /* Skip empty nodes */
        if (!pgdat->node_spanned_pages)
            return;
    
    #ifdef CONFIG_FLAT_NODE_MEM_MAP
        start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1);
        offset = pgdat->node_start_pfn - start;
        /* ia64 gets its own node_mem_map, before this, without bootmem */
        if (!pgdat->node_mem_map) {
            unsigned long size, end;
            struct page *map;
    
            /*
             * The zone's endpoints aren't required to be MAX_ORDER
             * aligned but the node_mem_map endpoints must be in order
             * for the buddy allocator to function correctly.
             */
            end = pgdat_end_pfn(pgdat);
            end = ALIGN(end, MAX_ORDER_NR_PAGES);
            size =  (end - start) * sizeof(struct page);
            map = alloc_remap(pgdat->node_id, size);
            if (!map)
                map = memblock_virt_alloc_node_nopanic(size,
                                       pgdat->node_id);
            pgdat->node_mem_map = map + offset;
        }
    #ifndef CONFIG_NEED_MULTIPLE_NODES
        /*
         * With no DISCONTIG, the global mem_map is just set as node 0's
         */
        if (pgdat == NODE_DATA(0)) {
            mem_map = NODE_DATA(0)->node_mem_map;
    #if defined(CONFIG_HAVE_MEMBLOCK_NODE_MAP) || defined(CONFIG_FLATMEM)
            if (page_to_pfn(mem_map) != pgdat->node_start_pfn)
                mem_map -= offset;
    #endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */
        }
    #endif
    #endif /* CONFIG_FLAT_NODE_MEM_MAP */
    }

    没有页的空结点显然可以跳过。如果特定于体系结构的代码尚未建立内存映射(这是可能的,例如,在IA-64系统上),则必须分配与该结点关联的所有struct page实例所需的内存。各个体系结构可以为此提供一个特定的函数。但目前只有在IA-32系统上使用不连续内存配置时是这样。在所有其他的配置上,则使用普通的自举内存分配器进行分配。请注意,代码将内存映射对齐到伙伴系统的最大分配阶,因为要使所有的计算都工作正常,这是必需的。

    指向该空间的指针不仅保存在pglist_data实例中,还保存在全局变量mem_map中,前提是当前考察的结点是系统的第0个结点(如果系统只有一个内存结点,则总是这样)。mem_map是一个全局数组,在讲解内存管理时,我们会经常遇到, 定义在mm/memory.c?v=4.7, line 85

    struct page *mem_map;

    然后在free_area_init_node函数的最后, 通过free_area_init_core来完成内存域zone的初始化

    5 free_area_init_core初始化内存域zone


    初始化内存域数据结构涉及的繁重工作由free_area_init_core执行,它会依次遍历结点的所有内存域, 该函数定义在mm/page_alloc.c?v=4.7, line 5932

    5.1 free_area_init_core函数代码注释


    /*
     * Set up the zone data structures:
     *   - mark all pages reserved
     *   - mark all memory queues empty
     *   - clear the memory bitmaps
     *
     * NOTE: pgdat should get zeroed by caller.
     */
    static void __paginginit free_area_init_core(struct pglist_data *pgdat)
    {
        enum zone_type j;
        int nid = pgdat->node_id;
        int ret;
    
        /*  初始化pgdat->node_size_lock自旋锁  */
        pgdat_resize_init(pgdat);
    #ifdef CONFIG_NUMA_BALANCING
        spin_lock_init(&pgdat->numabalancing_migrate_lock);
        pgdat->numabalancing_migrate_nr_pages = 0;
        pgdat->numabalancing_migrate_next_window = jiffies;
    #endif
    #ifdef CONFIG_TRANSPARENT_HUGEPAGE
        spin_lock_init(&pgdat->split_queue_lock);
        INIT_LIST_HEAD(&pgdat->split_queue);
        pgdat->split_queue_len = 0;
    #endif
    
        /*  初始化pgdat->kswapd_wait等待队列  */
        init_waitqueue_head(&pgdat->kswapd_wait);
        /*  初始化页换出守护进程创建空闲块的大小
         *  为2^kswapd_max_order  */
        init_waitqueue_head(&pgdat->pfmemalloc_wait);
    #ifdef CONFIG_COMPACTION
        init_waitqueue_head(&pgdat->kcompactd_wait);
    #endif
        pgdat_page_ext_init(pgdat);
    
        /* 遍历每个管理区 */
        for (j = 0; j < MAX_NR_ZONES; j++) {
            struct zone *zone = pgdat->node_zones + j;
            unsigned long size, realsize, freesize, memmap_pages;
            unsigned long zone_start_pfn = zone->zone_start_pfn;
    
            /*  size为该管理区中的页框数,包括洞 */
            size = zone->spanned_pages;
             /* realsize为管理区中的页框数,不包括洞  /
            realsize = freesize = zone->present_pages;
    
            /*
             * Adjust freesize so that it accounts for how much memory
             * is used by this zone for memmap. This affects the watermark
             * and per-cpu initialisations
             * 调整realsize的大小,即减去page结构体占用的内存大小  */
            /*  memmap_pags为包括洞的所有页框的page结构体所占的大小  */
            memmap_pages = calc_memmap_size(size, realsize);
            if (!is_highmem_idx(j)) {
                if (freesize >= memmap_pages) {
                    freesize -= memmap_pages;
                    if (memmap_pages)
                        printk(KERN_DEBUG
                               "  %s zone: %lu pages used for memmap\n",
                               zone_names[j], memmap_pages);
                } else  /*  内存不够存放page结构体  */
                    pr_warn("  %s zone: %lu pages exceeds freesize %lu\n",
                        zone_names[j], memmap_pages, freesize);
            }
    
            /* Account for reserved pages
             * 调整realsize的大小,即减去DMA保留页的大小  */
            if (j == 0 && freesize > dma_reserve) {
                freesize -= dma_reserve;
                printk(KERN_DEBUG "  %s zone: %lu pages reserved\n",
                        zone_names[0], dma_reserve);
            }
    
            if (!is_highmem_idx(j))
                nr_kernel_pages += freesize;
            /* Charge for highmem memmap if there are enough kernel pages */
            else if (nr_kernel_pages > memmap_pages * 2)
                nr_kernel_pages -= memmap_pages;
            nr_all_pages += freesize;
    
            /*
             * Set an approximate value for lowmem here, it will be adjusted
             * when the bootmem allocator frees pages into the buddy system.
             * And all highmem pages will be managed by the buddy system.
             */
            /* 设置zone->spanned_pages为包括洞的页框数  */
            zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;
    #ifdef CONFIG_NUMA
            /* 设置zone中的节点标识符 */
            zone->node = nid;
            /* 设置可回收页面比率 */
            zone->min_unmapped_pages = (freesize*sysctl_min_unmapped_ratio)
                            / 100;
            /* 设置slab回收缓存页的比率 */
            zone->min_slab_pages = (freesize * sysctl_min_slab_ratio) / 100;
    #endif
            /*  设置zone的名称  */
            zone->name = zone_names[j];
    
            /* 初始化各种锁 */
            spin_lock_init(&zone->lock);
            spin_lock_init(&zone->lru_lock);
            zone_seqlock_init(zone);
            /* 设置管理区属于的节点对应的pg_data_t结构 */
            zone->zone_pgdat = pgdat;
            /* 初始化cpu的页面缓存 */
            zone_pcp_init(zone);
    
            /* For bootup, initialized properly in watermark setup */
            mod_zone_page_state(zone, NR_ALLOC_BATCH, zone->managed_pages);
    
            /* 初始化lru相关成员 */
            lruvec_init(&zone->lruvec);
            if (!size)
                continue;
    
            set_pageblock_order();
            /* 定义了CONFIG_SPARSEMEM该函数为空 */
            setup_usemap(pgdat, zone, zone_start_pfn, size);
            /* 设置pgdat->nr_zones和zone->zone_start_pfn成员
             * 初始化zone->free_area成员
             * 初始化zone->wait_table相关成员
             */
             ret = init_currently_empty_zone(zone, zone_start_pfn, size);
            BUG_ON(ret);
            /* 初始化该zone对应的page结构 */
            memmap_init(size, nid, j, zone_start_pfn);
        }
        /*  ......  */
    }

    5.2 流程讲解


    初始化内存域数据结构涉及的繁重工作由free_area_init_core执行,它会依次遍历结点的所有内存域

    static void __paginginit free_area_init_core(struct pglist_data *pgdat)
    {
        enum zone_type j;
        int nid = pgdat->node_id;
        int ret;
    
        /*  ......  */
        /* 遍历每个管理区 */
        for (j = 0; j < MAX_NR_ZONES; j++) {
            struct zone *zone = pgdat->node_zones + j;
            unsigned long size, realsize, freesize, memmap_pages;
            unsigned long zone_start_pfn = zone->zone_start_pfn;
    
            /*  size为该管理区中的页框数,包括洞 */
            size = zone->spanned_pages;
             /* realsize为管理区中的页框数,不包括洞  /
            realsize = freesize = zone->present_pages;
    
            /*  ......  */
    }

    内存域的真实长度,可通过跨越的页数减去空洞覆盖的页数而得到。这两个值是通过两个辅助函数计算的,我不会更详细地讨论了。其复杂性实质上取决于内存模型和所选定的配置选项,但所有变体最终都没有什么意外之处

    static void __paginginit free_area_init_core(struct pglist_data *pgdat)
    {
            /*  ......  */
            if (!is_highmem_idx(j))
                nr_kernel_pages += freesize;
            /* Charge for highmem memmap if there are enough kernel pages */
            else if (nr_kernel_pages > memmap_pages * 2)
                nr_kernel_pages -= memmap_pages;
            nr_all_pages += freesize;
    
            /*
             * Set an approximate value for lowmem here, it will be adjusted
             * when the bootmem allocator frees pages into the buddy system.
             * And all highmem pages will be managed by the buddy system.
             */
            /* 设置zone->spanned_pages为包括洞的页框数  */
            zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;
    #ifdef CONFIG_NUMA
            /* 设置zone中的节点标识符 */
            zone->node = nid;
            /* 设置可回收页面比率 */
            zone->min_unmapped_pages = (freesize*sysctl_min_unmapped_ratio)
                            / 100;
            /* 设置slab回收缓存页的比率 */
            zone->min_slab_pages = (freesize * sysctl_min_slab_ratio) / 100;
    #endif
            /*  设置zone的名称  */
            zone->name = zone_names[j];
    
            /* 初始化各种锁 */
            spin_lock_init(&zone->lock);
            spin_lock_init(&zone->lru_lock);
            zone_seqlock_init(zone);
            /* 设置管理区属于的节点对应的pg_data_t结构 */
            zone->zone_pgdat = pgdat;
            /*  ......  */
    }

    内核使用两个全局变量跟踪系统中的页数。nr_kernel_pages统计所有一致映射的页,而nr_all_pages还包括高端内存页在内free_area_init_core始化为0

    我们比较感兴趣的是调用的两个辅助函数

    • zone_pcp_init尝试初始化该内存域的per-CPU缓存, 定义在mm/page_alloc.c?v=4.7, line 5443

    • init_currently_empty_zone初始化free_area列表,并将属于该内存域的所有page实例都设置为初始默认值。正如前文的讨论,调用了memmap_init_zone来初始化内存域的页, 定义在mm/page_alloc.c?v=4.7, line 5458

    我们还可以回想前文提到的,所有页属性起初都设置MIGRATE_MOVABLE。
    此外,空闲列表是在zone_init_free_lists中初始化的

    static void __paginginit free_area_init_core(struct pglist_data *pgdat)
    {
        /*  ......  */
        {
            /* 初始化cpu的页面缓存 */
            zone_pcp_init(zone);
    
            /* 设置pgdat->nr_zones和zone->zone_start_pfn成员
             * 初始化zone->free_area成员
             * 初始化zone->wait_table相关成员
             */
             ret = init_currently_empty_zone(zone, zone_start_pfn, size);
            BUG_ON(ret);
            /* 初始化该zone对应的page结构 */
            memmap_init(size, nid, j, zone_start_pfn);
        }
        /*  ......  */
    }

    6 memmap_init初始化page页面


    在free_area_init_core初始化内存管理区zone的过程中, 通过memmap_init函数对每个内存管理区zone的page内存进行了初始化

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

    #ifndef __HAVE_ARCH_MEMMAP_INIT
    #define memmap_init(size, nid, zone, start_pfn) \
        memmap_init_zone((size), (nid), (zone), (start_pfn), MEMMAP_EARLY)
    #endif

    memmap_init_zone函数完成了page的初始化工作, 该函数定义在mm/page_alloc.c?v=4.7, line 5139

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

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

    7 总结


    7.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;
              |

    7.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]);

    7.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;
        |     对于代码中的判断条件一般不会成立,因为页数会最够多(内存较大)
    展开全文
  • Java内存管理:在前面的一些文章了解到javac编译的大体过程、Class文件结构、以及JVM字节码指令,下面我们详细了解Java内存区域:先说明JVM规范定义的JVM运行时分配的数据区有哪些,然后分别介绍它们的特点,并指出...

    Java内存管理:Java内存区域 JVM运行时数据区

           在前面的一些文章了解到javac编译的大体过程、Class文件结构、以及JVM字节码指令。

           下面我们详细了解Java内存区域:先说明JVM规范定义的JVM运行时分配的数据区有哪些,然后分别介绍它们的特点,并指出给出一些HotSpot虚拟机实现的不同点和调整参数。

    1、Java内存区域概述

    1-2、C/C++与Java程序开发的内存管理

           在内存管理领域,C/C++程序开发与Java程序开发有着完全不同的理念:

    1、C/C++程序开发

           自己管理内存是一项基础的工作;

           自已分配内存,但也得自己来及时回收;

           比较自由,但多了些工作量,且容易出现内存泄露和内存溢出等问题;

    2、Java程序开发

           JVM管理内存,不需要自己手动分配内存和释放内存;

           不容易出现内存泄露和内存溢出;

           一旦出现问题不容易排查,所以得了解JVM是怎么使用内存;

    1-2、Java内存区域与JVM运行时数据区

           如上图, Java虚拟机规范定义了字节码执行期间使用的各种运行时数据区,即JVM在执行Java程序的过程中,会把它管理的内存划分为若干个不同的数据区域,包括:

          程序计数器、java虚拟机栈、本地方法栈、java堆、方法区、运行时常量池;

           从线程共享角度来说,可以分为两类:

    1、所有线程共享的数据区

           方法区、运行时常量池、java堆;

           这些数据区域是在Java虚拟机启动时创建的,只有当Java虚拟机退出时才会被销毁;

    2、线程间隔离的数据区

           程序计数器、java虚拟机栈、本地方法栈、

           这些数据区域是每个线程的"私有"数据区,每个线程都有自己的,不与其他线程共享;

           每个线程的数据区在创建线程时创建,并在线程退出时被销毁;

    3、另外,还一种特殊的数据区

          直接内存--使用Native函数库直接分配的堆外内存;

           Java内存区域 = JVM运行时数据区 +直接内存。

    2、Java各内存区域说明

           上面图片展示的是JVM规范定义的运行时数据概念模型,实际上JVM的实现可能有所差别,下面在介绍各内存数据区时会给出一些HotSpot虚拟机实现的不同点和调整参数

    2-1、程序计数器

           程序计数器(Program Counter Register),简称PC计数器;

    1、生存特点

           每个线程都需要一个独立的PC计数器,生命周期与所属线程相同,各线程的计数器互不影响;

    2、作用

          JVM字节码解释器通过改变这个计数器的值来选取线程的下一条执行指令;

    3、存储内容

           JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的;

           在任意时刻,一个线程只会执行一个方法的代码(称为该线程的当前方法(Current Method));        

    (A)、如果这个方法是Java方法,那PC计数器就保存JVM正在执行的字节码指令的地址;

    (B)、如果该方法是native的,那PC计数器的值是空(undefined)

    4、内存分配特点

           PC计数器占用较小的内存空间;

           容量至少应当能保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值;

    5、异常情况

           唯一一个JVM规范中没有规定会抛出OutOfMemoryError情况的区域;

    2-2、Java虚拟机栈

           Java虚拟机栈(Java Virtual Machine Stack,JVM Stack),指常说的栈内存(Stack);

           和Java堆指的堆内存(Heap),都是需要重点关注的内存区域

    1、生存特点

           每个线程都有一个私有的,生命周期与所属线程相同;

    2、作用

           描述的是Java方法执行的内存模型,与传统语言中(如C/C++)的栈类似;

           在方法调用和返回中也扮演了很重要的角色;

    3、存储内容

           用于保存方法的栈帧(Stack Frame)

           每个方法从调用到执行结束,对应其栈帧在JVM栈上的入栈到出栈的过程;

               栈帧:

           每个方法执行时都会创建一个栈帧,随着方法调用而创建(入栈),随着方法结束而销毁(出栈)

           栈帧是方法运行时的基础结构;

           栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息;

    (A)、局部变量表

           局部变量表(Local Variables Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量

           这些都是在编译期可知的数据,所以一个方法调用时,在JVM栈中分配给该方法的局部变量空间是完全确定的,运行中不改变;

           一个方法分配局部变量表的最大容量由Class文件中该方法的Code属性的max_locals数据项确定;

    (B)、操作数栈

           操作数栈(Operand Stack)简称操作栈,它是一个后进先出(Last-In-First-Out,LIFO)栈;

           在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容(任意类型的值),也就是入栈/出栈操作;

           在方法调用的时候,操作数栈也用来准备调用方法的参数以及接收方法返回结果;

           一个方法的操作数栈长度由Class文件中该方法的Code属性的max_stacks数据项确定;

    (C)、动态链接

           每一个栈帧内部都包含一个指向运行时常量池的引用,来支持当前方法的执行过程中实现动态链接 (Dynamic Linking);

           在 Class 文件里面,描述一个方法调用了其他方法,或者访问其成员变量是通过符号引用(Symbolic Reference)来表示的;

          动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用(除了在类加载阶段解析的一部分符号);

    4、内存分配特点

           因为除了栈帧的出栈和入栈之外,JVM栈从来不被直接操作,所以栈帧可以在堆中分配;

           JVM栈所使用的内存不需要保证是连续的;

           JVM规范允许JVM栈被实现成固定大小的或者是根据计算动态扩展和收缩的:

    (A)、固定大小

           如果JVM栈是固定大小的,则当创建新线程的栈时,可以独立地选择每个JVM栈的大小;

    (B)、动态扩展或收缩

           在动态扩展或收缩JVM栈的情况下,JVM实现应该提供调节JVM栈最大和最小内存空间的手段;

    两种情况下,JVM实现都应当提供调节JVM栈初始内存空间大小的手段;

          HotSpot VM通过"-Xss"参数设置JVM栈内存空间大小;

    5、异常情况

           JVM规范中对该区域,规定了两种可能的异常状况:

    (A)、StackOverflowError

           如果线程请求分配的栈深度超过JVM栈允许的最大深度时,JVM将会抛出一个StackOverflowError异常;

    (B)、 OutOfMemoryError

           如果JVM栈可以动态扩展,当然扩展的动作目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那JVM将会抛出一个OutOfMemoryError异常;

    该区域与方法执行的JVM字节码指令密切相关,这里篇幅有限,以后有时间会分析方法的调用与执行过程,再来详细介绍该区域。

    2-3、本地方法栈

           本地方法栈(Native Method Stack)与 Java虚拟机栈类似

     1、与Java虚拟机栈的区别

           Java虚拟机栈为JVM执行Java方法(也就是字节码)服务;

           本地方法栈则为Native方法(指使用Java以外的其他语言编写的方法)服务

    2、HotSpot VM实现方式

           JVM规范中没有规定本地方法栈中方法使用的语言、方式和数据结构,JVM可以自由实现;

          HotSpot VM直接把本地方法栈和Java虚拟机栈合并为一个;

    2-4、Java堆

           Java堆(Java Heap)指常说的堆内存(Heap);

    1、生存特点

           所有线程共享;

           生命周期与JVM相同;

    2、作用

          为"new"创建的实例对象提供存储空间

           里面存储的这些对象实例都是通过垃圾收集器(Garbage Collector)进行自动管理,所以Java堆也称"GC堆"(Garbage Collected Heap);

    对GC堆以及GC的参数设置调整,就是JVM调优的主要内容

    3、存储内容

          用于存放几乎所有对象实例;

           (随JIT编译技术和逃逸分析技术发展,少量对象实例可能在栈上分配,详见后面介绍JIT编译的文章)

    4、内存分配特点

    (A)、Java堆划分

           为更好回收内存,或更快分配内存,需要对Java堆进行划分:

    (I)、从垃圾收集器的角度来看

           JVM规范没有规定JVM如何实现垃圾收集器;

           由于很多JVM采用分代收集算法,所以Java堆还可以细分为:新生代、老年代和永久代

    (II)、从内存分配角度来看

           为解决分配内存线程不安全问题,需要同步处理;

           Java堆可能划分出每个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),减少线程同步;

    HotSpot VM通过"-XX:+/-UseTLAB"指定是否使用TLAB;

    (B)、分配调整

           和JVM栈一样,Java堆所使用的物理内存不需要保证是连续的,逻辑连续即可;

           JVM规范允许Java堆被实现成固定大小的或者是根据计算动态扩展和收缩的:

           两种情况下,JVM实现都应当提供调节JJava堆初始内存空间大小的手段;

           在动态扩展或收缩的情况下,还应该提供调节最大和最小内存空间的手段;

    (C)、HotSpot VM相关调整

           目前主流的JVM都把Java堆实现成动态扩展的,如HotSpot VM:

    (1)、初始空间大小

           通过"-Xms"或"-XX:InitialHeapSize"参数指定Java堆初始空间大小;

           默认为1/64的物理内存空间;

    (2)、最大空间大小

           通过"-Xmx"或"-XX:MaxHeapSize"参数指定ava堆内存分配池的最大空间大小;

           默认为1/4的物理内存空间;

           Parallel垃圾收集器默认的最大堆大小是当小于等于192MB物理内存时,为物理内存的一半,否则为物理内存的四分之一;

    (3)、各年代内存的占用空间与可用空间的比例

           通过"-XX:MinHeapFreeRatio"和"-XX:MaxHeapFreeRatio"参数设置堆中各年代内存的占用空间与可用空间的比例保持在特定范围内;

           默认:

           "-XX:MinHeapFreeRatio=40":即一个年代(新生代或老年代)内存空余小于40%时,JVM会从未分配的堆内存中分配给该年代,以保持该年代40%的空余内存,直到分配完"-Xmx"指定的堆内存最大限制;

           "-XX:MaxHeapFreeRatio=70":即一个年代(新生代或老年代)内存空余大于70%时,JVM会缩减该年代内存,以保持该年代70%的空余内存,直到缩减到"-Xms"指定的堆内存最小限制;

          这两个参数不适用于Parallel垃圾收集器(通过“-XX:YoungGenerationSizeIncrement”、“-XX:TenuredGenerationSizeIncrement ”能及“-XX:AdaptiveSizeDecrementScaleFactor”调节);

    (4)、年轻代与老年代的大小比例

           通过"-XX:NewRatio":控制年轻代与老年代的大小比例;

           默认设置"-XX:NewRatio=2"表新生代和老年代之间的比例为1:2;

           换句话说,eden和survivor空间组合的年轻代大小将是总堆大小的三分之一;

    (5)、年轻代空间大小

           通过"-Xmn"参数指定年轻代(nursery)的堆的初始和最大大小

           或通过"-XX:NewSize"和"-XX:MaxNewSize"限制年轻代的最小大小和最大大小

    (6)、定永久代空间大小

           通过"-XX:MaxPermSize(JDK7)"或"-XX:MaxMetaspaceSize(JDK8)"参数指定永久代的最大内存大小

           通过"-XX:PermSize(JDK7)"或"-XX:MetaspaceSize(JDK8)"参数指定永久代的内存阈值--超过将触发垃圾回收

           注:JDK8中永久代已被删除,类元数据存储空间在本地内存中分配

           详情请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref62

    (D)、调整策略

          关于这些参数的调整需要垃圾收集的一些知识(以后文章会介绍),先来简单了解:

          当使用某种并行垃圾收集器时,应该指定期望的具体行为而不是指定堆的大小;

          让垃圾收集器自动地、动态的调整堆的大小来满足期望的行为;

          调整的一般规则:

          除非你的应用程序无法接受长时间的暂停,否则你可以将堆调的尽可能大一些;

          除非你发现问题的原因在于老年代的垃圾收集或应用程序暂停次数过多,否则你应该将堆的较大部分分给年轻代;

    关于HotSpot虚拟机堆内存分代说明以及空间大小说明请参考:

    http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/generations.html#sthref16

    http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/sizing.html#sizing_generations

    5、异常情况

          如果实际所需的堆超过了垃圾收集器能提供的最大容量,那Java虚拟机将会抛出一个OutOfMemoryError异常;

    该部分的内存如何分配、垃圾如何收集,上面这些参数如何调整,将在以后的文章详细说明。

    2-5、方法区

          方法区(Method Area)是堆的逻辑组成部分,但有一个别名"Non-Heap"(非堆)用以区分;

    1、生存特点

          所有线程共享;

          生命周期与JVM相同;

    2、作用

          为类加载器加载Class文件并解析后的类结构信息提供存储空间

          以及提供JVM运行时常量存储的空间;

    3、存储内容

          用于存储JVM加载的每一个类的结构信息,主要包括:

    (A)、运行时常量池(Runtime Constant Pool)、字段和方法数据;

    (B)、构造函数、普通方法的字节码内容以及JIT编译后的代码;

    (C)、还包括一些在类、实例、接口初始化时用到的特殊方法;

    4、内存分配特点

    (A)、分配调整

          和Java堆一样,所使用的物理内存不需要保证是连续的;

          或以实现成固定大小的或者是根据计算动态扩展和收缩的;

    (B)、方法区的实现与垃圾回收

          JVM规范规定:

          虽然方法区是堆的逻辑组成部分,但不限定实现方法区的内存位置;

          甚至简单的虚拟机实现可以选择在这个区域不实现垃圾收集;

          因为垃圾收集主要针对常量池和类型卸载,效果不佳;

          但方法区实现垃圾回收是必要的,否则容易引起内存溢出问题;

    (C)、HotSpot VM相关调整

    (I)、在JDK7中

          使用永久代(Permanent Generation)实现方法区,这样就可以不用专门实现方法区的内存管理,但这容易引起内存溢出问题;

          有规划放弃永久代而改用Native Memory来实现方法区;

          不再在Java堆的永久代中生成中分配字符串常量池,而是在Java堆其他的主要部分(年轻代和老年代)中分配;

          更多请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/enhancements-7.html

    (II)、在JDK8中

          永久代已被删除,类元数据(Class Metadata)存储空间在本地内存中分配,并用显式管理元数据的空间:

          从OS请求空间,然后分成块;

          类加载器从它的块中分配元数据的空间(一个块被绑定到一个特定的类加载器);

          当为类加载器卸载类时,它的块被回收再使用或返回到操作系统;

          元数据使用由mmap分配的空间,而不是由malloc分配的空间;

          通过"-XX:MaxMetaspaceSize" (JDK8)参数指定类元数据区的最大内存大小

          通过"-XX:MetaspaceSize" (JDK8)参数指定类元数据区的内存阈值--超过将触发垃圾回收;

          详情请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref62

    5、异常情况

          如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常;

    2-6、运行常量池

          运行常量池(Runtime Constant Pool)是方法区的一部分

    1、存储内容

          是每一个类或接口的常量池(Constant_Pool)的运行时表示形式;

          包括了若干种不同的常量:

          (A)、从编译期可知的字面量和符号引用,也即Class文件结构中的常量池

          (B)、必须运行期解析后才能获得的方法或字段的直接引用

          (C)、还包括运行时可能创建的新常量(如JDK1.6中的String类intern()方法)

    2-7、直接内存

          直接内存(Direct Memory)不是JVM运行时数据区,也不是JVM规范中定义的内存区域;

    1、特点

          是使用Native函数库直接分配的堆外内存;

          被频繁使用,且容易出现OutOfMemoryError异常

    2、作用

          因为避免了在Java堆中来回复制数据,能在一些场景中显著提高性能;

    3、实现方式

          JDK1.4中新加入NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式;

          它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java椎中的DirectByteBuffer对象作为这块内存的引用进行操作;

    4、HotSpot VM相关调整

          可以通过"-XX:MaxDirectMemorySize"参数指定直接内存最大空间

          不会受到Java堆大小的限制,即"-Xmx"参数限制的空间不包括直接内存

          容易导致各个内存区域总和大于物理内存限制,出现OutOfMemoryError异常;

     

          到这里,我们大体了解Java各内存区域是什么,有些什么特点了,但方法执行的JVM字节码指令如何在Java虚拟栈中运作的,以及Java堆内存如何分配、垃圾如何收集,如何进行JVM调优,将在以后的文章详细说明。

          后面我们将分别去了解:方法的调用与执行、JIT编译--在运行时把Class文件字节码编译成本地机器码的过程、以及JVM垃圾收集相关内容……

     

    【参考资料】

    1、《The Java Virtual Machine Specification》Java SE 8 Edition:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

    2、《Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide》:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html

    3、《Memory Management in the Java HotSpot™ Virtual Machine》:http://www.oracle.com/technetwork/java/javase/tech/memorymanagement-whitepaper-1-150020.pdf

    4、HotSpot虚拟机参数官方说明:http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

    5、《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版 第2章

    6、Java Class文件结构解析 及 实例分析验证

    展开全文
  • linux内存管理初始

    千次阅读 2014-12-28 00:21:08
    内存管理子系统是linux内核最核心最重要的一部分...本文详细描述了从bootloader跳转到linux内核内存管理子系统初始期间所做的操作,从而来加深对内存管理子系统知识的理解和掌握。内核的入口是stext,这是在arch/arm/

    内存管理子系统是linux内核最核心最重要的一部分,内核的其他部分都需要在内存管理子系统的基础上运行。而对其初始化是了解整个内存管理子系统的基础。对相关数据结构的初始化是从全局启动例程start_kernel开始的。本文详细描述了从bootloader跳转到linux内核内存管理子系统初始化期间所做的操作,从而来加深对内存管理子系统知识的理解和掌握。

    内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中指定的。而符号stext是在arch/arm/kernel/head.S中定义的。整个初始化分为两个阶段,首先是在head.S中用汇编代码执行一些平台相关的初始化,完成后跳转到start_kernel函数用C语言代码执行剩余的通用初始化部分。整个初始化流程图如下图所示:


    一、     启动条件
    通常从系统上电到运行到linux kenel这部分的任务是由boot loader来完成。Boot loader在跳转到kernel之前要完成一些限制条件:

    1、CPU必须处于SVC(supervisor)模式,并且IRQFIQ中断必须是禁止的;

    2、MMU(内存管理单元)必须是关闭的此时虚拟地址对应物理地址;

    3、数据cache(Data cache)必须是关闭的;

    4、指令cache(Instruction cache)没有强制要求;

    5、CPU通用寄存器0(r0)必须是0;

    6、CPU通用寄存器1(r1)必须是ARM Linux machine type

    7、CPU通用寄存器2(r2)必须是kernel parameter list的物理地址;

    二、     汇编代码初始化部分

    汇编代码部分主要完成的工作如下所示,下文只是概述head.S中各个函数完成的主要

    功能,具体的技术细节,本文不再赘述。

    ?  确定processor type

    ?  确定machine type

    ?  创建页表

    ?  调用平台特定的__cpu_flush函数

    ?  开启mmu

    ?  切换数据

    ?  最终跳转到start_kernel

    三、     C语言代码初始化部分

    C语言代码初始化部分主要负责建立结点和内存域的数据结构、初始化页表、初始化用

    于内存管理的伙伴系统。在启动过程中,尽管内存管理模块尚未初始化完成,但内核仍然需要分配内存以创建各种数据结构。bootmem分配器用于在启动阶段分配内存。所有涉及的实现都是在start_kernel函数中实现的,其中涉及到内存初始化的函数如下:

    1、 build_all_zonelist用于结点和内存域的初始化。

    在linux系统中,内存划分结点,每个结点关联到系统中的一个处理器。各个结点又划分内存域,主要包括ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM。大部分系统只有一个内存结点,下文只针对此类系统。此函数用于初始化内存的结点和内存域。内核在mm/page/page_alloc.c中定义了一个pglist_data的内存结点实例(contig_page_data)用于管理所有系统的内存。

    build_all_zonelist用于初始化结点和内存域。该函数首先调用__build_all_zonelists,此函数遍历系统的每个内存结点,针对每个内存结点调用build_zonelists(),该函数的任务是在当前处理的结点和系统中其他结点的内存域之间建立一种等级次序。接下来,依据这种次序分配内存,如果在期望的结点内存域中,没有空闲内存,就去查找相邻结点的内存域。内核定义了内存的一个层次结构关系,首先试图分配廉价的内存,如果失败,则根据访问速度和容量,逐渐尝试分配更昂贵的内存。

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

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

    DMA内存域最昂贵,因为它用于外设和系统之间的数据传输。

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

    该函数接下来计算所有剩余的内存页,存放到全局变量vm_total_pages中。接下来如果空闲内存页太少,则关闭页的可迁移性,即page_group_by_mobility_disabled置位。页的可迁移性是内核为了避免内存碎片而在linux2.6.24中加入的新特性。该特性把内存页分为不可移动内存页、可回收页和可移动页三种类型来避免内存碎片。

    2、 mem_init用于停用bootmem分配器并迁移到伙伴系统。

    在系统初始化进行到伙伴系统分配器能够承担内存管理的责任后,必须停用bootmem

    配器。该函数遍历所有的内存域结点,对每个结点分别调用free_all_bootmem_node函数,停用bootmem分配器。该函数调用free_all_bootmem_core,首先扫描bootmem分配器位图,释放每个未用的页。到伙伴系统的接口是__free_pages_bootmem函数,该函数对每个空闲内存页调用,该函数内部依赖于标准函数__free_page。它使得这些页并入到伙伴系统的数据结构,在其中作为空闲页,用于分配管理。在页位图已经完全扫描后,它占据的内存空间也必须释放掉,此后,只有伙伴系统可用于内存分配。

    3、 初始化slab分配器。

    kmem_cache_init初始化内核用于小块内存区的分配器(slab分配器)。他在内核初始化阶段、伙伴系统启用之后调用。

    kmem_cache_init创建系统中的第一个slab缓存,以便为kmem_cache的实例提供内存,为此,内核使用一个在编译时创建的静态数据(cache_cache)。该函数接下来初始化一般性的缓存,用作kmalloc内存的来源。为此,针对所需的各个缓存长度,分别调用kmem_cache_create函数。

    4、 初始化页表,初始化bootmem分配器。

    setup_arch是特定于体系结构的,用于初始化页表,初始化bootmem分配器。该函数的执行流程图如下:


    4.1、setup_processorsetup_machine用来确定处理器类型和机器类型。

    4.2、parse_targs用来解析从uboot传递过来的tag值。在ubootdo_bootm_linux函数中,会创建传递给内核的各个tagtag的地址是由内核和uboot约定的,在uboot中是在函数board_init中将该约定好的地址保存在gd->bd->bi_boot_params中,如下所示:

    gd->bd->bi_boot_params = CFG_BOOT_PARAMS;

    在内核中,MACHINE_START定义了struct machine_desc数据结构,各成员函数在linux启动的不同时期被调用。该结构体的boot_params成员存放的就是uboot传递给内核的tag的起始地址。

    MACHINE_START(hi3520v100, "hi3520v100")

        .phys_io    = IO_SPACE_PHYS_START,

        .io_pg_offst    = (IO_ADDRESS(IO_SPACE_PHYS_START) >> 18) & 0xfffc,

        .boot_params    = PHYS_OFFSET + 0x100,

        .map_io     = hisilicon_map_io,

        .init_irq   = hisilicon_init_irq,

        .timer      = &hisilicon_timer,

        .init_machine   = hisilicon_init_machine,

    MACHINE_END

        在uboot中通过setup_memory_tags来建立内存的tag,把物理内存的起始地址和大小记录在tag里,tag头用ATAG_MEM标识。内核中有一个tagtable的表,把各种表头的标识和解析函数关联起来,关于内存的如下所示:

    __tagtable(ATAG_MEM, parse_tag_mem32);

    parse_targs找到以ATAG_MEM标识的tag后,调用parse_tag_mem32,把在uboot中填充到tag里的内存起始地址和大小填充到全局变量meminfo数组中。

    4.3、parse_cmdline解析命令行参数。

        此函数解析由uboot传进来的命令行参数。在内核中,各个命令行参数的头都与相应的解析函数一一对应,关于内存,有如下对应:

    __early_param("mem=", early_mem);

    在内核中解析命令行中以"mem="开头的命令行,找到此命令行后,调用early_mem函数解析命令行。如果内核需要管理几段不同的内存,可以在ubootbootarg环境变量中分别指定对应的内存段的起始地址和长度,如下所示:

    mem=72M@0xe2000000 mem=128M@0xe8000000

    表示内核需要管理两段不连续的内存,第一段起始地址为0xe2000000,大小为72M,另外一段起始地址为0xe8000000,大小为128M,内核通过early_mem函数分别把这两段内存写入到meminfo的两个bank中。

    4.4、paging_init函数用来初始化页表项,启用bootmem分配器。


    4.4.1、build_mem_type_table用来建立各种类型的页表选项。该函数是为了给mem_types数组中的各种类型的页表参数添加上我们的要求,主要是一级页表,二级页表,访问权限控制等标志位。

    4.4.2、prepare_page_table函数清除一级页表中无效的页表,只保留物理内存在虚拟地址空间中的映射。

    4.4.3、devicemaps_init函数用来清除VMALLOC_END之后的一级页表,分配vector page,调用mdesc->map_io来映射设备,mdesc->map_io即上文中提到的用MACHINE_START定义的结构体中的成员,即hisilicon_map_io

    4.4.4、bootmem_init函数在做一些必要的操作后,调用bootmem_init_node来初始化一级页表、启用bootmem分配器。

        Linux内核的段页表项将4GB的地址空间分成40961MB的段(section),每个段页表项是一个unsigned long型变量,占用4字节,因此段页表项占用4096*4=16K的内存空间。而全局页表项的大小为2M,如下

        #define PGDIR_SHIFT     21

    #define PGDIR_SIZE      (1UL << PGDIR_SHIFT)

    而全局页表项的数据结构pgd_t的定义如下

    typedef struct { unsigned long pgd[2]; } pgd_t;

    所以一个全局页表对应两个段页表,正好符合上述定义。

    该函数首先根据meminfo数组中的bank数目,分别调用map_memory_bank来映射各个物理内存区的段页表项。该函数调用create_mapping来执行具体的映射工作。

    建立完物理内存的页表映射后,内核会把物理内存的起始地址的页帧号放入start_pfn,物理内存的结束地址的页帧号放入end_pfn。这里要注意如果有多个内存bank时,start_pfnend_pfn之间可能有空洞,拿上文提到的例子来讲,start_pfnend_pfn之间是有内存空洞的,具体如下:

    mem=72M@0xe2000000 mem=128M@0xe8000000

    start_pfn = 0xe2000000 >> PAGE_SHIFT;

    end_pfn = (0xe8000000 + 8000000) >> PAGE_SHIFT;

    在内核启动期间,由于基于物理内存的伙伴系统尚未初始化完成,但内核仍然需要分配内存以创建各种数据结构,bootmem分配器用于在启动阶段早期分配内存。bootmem分配器是一个最先适配分配器,该分配器使用一个位图来管理内存页,位图比特位的数目与系统中物理内存页的数目相同,比特位为1表示已用页,比特位为0表示空闲页。在需要分配内存时,分配器逐位扫描位图,直至找到一个能提供足够连续内存页的位置,即所谓的最先适配位置。bootmem分配器也必须管理一些数据,内核为系统中的每个结点都提供了一个bootmem_data结构的实例,用于该用途。当然,该结构不能动态分配,只能在编译时分配给内核,在UMA系统上,只有一个bootmem_data_t实例,即contig_bootmem_data

    typedef struct bootmem_data {

        unsigned long node_boot_start;

        unsigned long node_low_pfn;

        void *node_bootmem_map;

        unsigned long last_offset;

        unsigned long last_pos;

        unsigned long last_success;

        struct list_head list;

    } bootmem_data_t;

    node_boot_start保存了系统中物理内存的第一个页帧的编号。

    node_low_pfn是系统物理内存最后一页的页帧编号。

    node_bootmem_map 是指向bootmem分配器位图所在地址的指针。

    last_pos是上一次分配的页帧编号。如果没有请求分配整个页帧,last_offset用作该页内部的偏移量,这使得bootmem分配器可以分配小于一整页的内存区。

    last_success指定位图中上一次成功分配内存的位置,新的分配由此开始。

    list:所有注册的bootmem分配器保存在一个链表中,表头是全局变量bdata_list

    内核接下来调用bootmem_bootmap_pages来计算bootmem分配器位图的大小(需要按页对齐),然后调用find_bootmap_pfn在物理内存中找到一块大小合适的内存,优先考虑内核的数据段的结尾处。接下来调用init_bootmem_node初始化bootmem分配器,该函数调用init_bootmem_core来填充bootmem_data_t结构中各个成员。

    接下来调用free_bootmem_node进而调用free_bootmem_core把整个位图清零,把所有内存页标记为空闲页。然后在位图中分别把已用的内存页标记为1,已用的内存页包括位图占用的内存页、initrd占用的内存页、内核的代码段和数据段占用的内存,到此bootmem分配器正式建立起来了。

    最后,free_area_init_node用来填充内存结点的数据结构pglist_data中的各个成员变量,其中zones_size表示物理内存的大小,包含内存空洞,zholes_size是内存空洞的页数。pgdat是内存结点的数据结构,node_start_pfn是物理内存的起始页帧编号。该函数首先调用calculate_node_totalpages用来计算总的物理内存页帧数,包括内存空洞,保存在内存结点数据结构的node_spanned_pages成员中,然后计算去掉内存空洞后的实际物理内存页帧数,保存在node_present_pages成员中。

    由于每个内存页都需要有一个struct page的数据结构来管理,所以该函数接下来调用

    alloc_node_mem_map来创建所有物理内存页的struct page实例,指向该空间的指针不仅保存在pglist_data结构的node_mem_map中,还保存在全局变量mem_map中。

        最后,free_area_init_node函数调用free_area_init_core来初始化pglist_data实例的各个内存域。该函数负责初始化各个zone结构中的成员。在ARM Linux中,没有ZONE_HIGHMEMZONE_NORMAL初始化成0,所有可用的物理内存被放置在ZONE_DMA。主要涉及两个函数:zone_pcp_init用来初始化冷热缓存页,init_currently_empty_zone用来初始化伙伴系统的free_area列表,并将属于该内存域的所有page实例都设置成默认值。

        zone_pcp_init负责初始化冷热缓存页。struct zonepageset成员用于实现冷热页分配器。内核说页是热的,意味着页已经加载到了CPU高速缓存,与在内存中的页相比,其数据能够更快的访问。相反,冷页则不在高速缓存中。在多处理器系统上每个CPU都有一个或多个高速缓存,各个CPU的管理必须是独立的。pageset是一个数组,其容量与系统能够容纳的CPU数目的最大值相同。

        struct zone{

            ………

            Struct per_cpu_pageset pageset[NR_CPUS];

            ………

    }

    NR_CPUS是一个可以在编译时配置的宏常数。在单处理器上其值总是1,针对SMP系统编译的内核中,其值可能是232之间。数组元素的类型为per_cpu_pageset,定义如下

    struct per_cpu_pageset {

        struct per_cpu_pages pcp[2];    /* 0: hot.  1: cold */

    } ____cacheline_aligned_in_smp;

    该结构有两个数组项,第一项管理热页,第二项管理冷页。per_cpu_pageset结构如下:

    struct per_cpu_pages {

        int count;

        int high;

        int batch;

        struct list_head list;

    };

    count记录了与该列表相关的页的数目。

    high是页数上限的水印值,在需要的情况下清空列表。

    batch是每次添加页数的值。

    zone_pcp_init函数首先用zone_batchsize计算出批量添加页的大小,保存在batch中,然后遍历系统中所有CPU,同时调用setup_pageset填充每个per_cpu_pageset实例的常量。根据计算得到的batch大约相当于内存域中页数的0.25‰。

    init_currently_empty_zone用来初始化与伙伴系统相关的free_aera列表。该函数首先调用memmap_init_zone初始化上文分配好的保存在全局变量mem_map中的物理内存的struct page实例。然后调用zone_init_free_lists初始化free_aera空闲列表。空闲页的数目free_area.nr_free当前仍然规定为0,直至停用bootmem分配器、普通的伙伴系统分配器生效时,才会设置正确的数值。

    整个内核地址空间的划分请参见下图:


    图中PAGE_OFFSET=0xc0000000TEXT_OFFSET=0x00008000arch/arm/makefile中指定,swapper_pg_dir=0x00004000head.S中指定。

        地址空间的第一段用于将系统的所有物理内存页映射到内核的虚拟地址空间中。由于内核地址空间从偏移量0xc0000000开始,即3GiB,所以每个虚拟地址x都对应于物理地址x-0xc0000000,因此这是一个简单的线性偏移。

        直接映射区:从PAGE_OFFSET(0xc0000000)开始到high_memory的地址空间,在内核调用bootmem_init函数中,把high_memory的值设置为实际物理内存的结束地址对应的虚拟地址,最大不超过896M。当实际物理内存大于896M时,超出的部分映射为高端内存区。物理内存的起始地址的16K未用,从swapper_pg_dir0xc000800016K存放段地址的页表项内容,前文已经描述过了。从0xc0008000开始存放内核的代码段、初始化的数据段和未初始化的数据段。紧接着内核的代码段和数据段的是bootmem分配器的位图区域,上文中页提到过。

        VMALLOC区:虚拟内存中连续、但物理内存中不连续的内存区,可以在vmalloc区域分配。该机制通常用于用户过程,内核自身会试图尽力避免非连续的物理内存。但在已经运行了很长时间的系统上,在内核需要物理内存时,可能出现可用内存不连续的情况。此类情况,主要出现在动态加载模块时。vmalloc区域在何处结束取决于是否启用了高端内存支持。如果没有启用,那么就不需要持久映射区,因为整个物理内存都是可以直接映射的。因此根据配置的不同,该区域结束于持久内核映射或固定映射区域的起始处,中间总会留下两页,作为vmalloc区与这两个区域之间的保护措施。

        持久映射区:用于将高端内存域中的非持久页映射到内核。

        固定映射区:是与物理地址空间中的固定页关联的虚拟地址空间页,但具体关联的页帧可以自由选择。它通过固定公式与物理内存关联的直接映射页相反,虚拟固定映射地址与物理内存位置之间的关联可以自行定义。

        在直接映射区和vmalloc区域之间有一个8M的缺口,这个缺口可用作针对任何内核故障的保护措施。如果访问越界地址,则访问失败并生成一个异常,报告该错误。如果vmalloc区域紧接着直接映射,那么访问将成功而不会注意到错误。

    到此,整个linux的内存管理子系统初始化完成了,具体的细节部分请参考代码阅读体会。


    from:http://sunjiangang.blog.chinaunix.net/uid-9543173-id-3568385.html

    展开全文
  • DNS学习笔记之6 - DNS区域管理

    千次阅读 2012-01-06 11:29:57
    DNS学习笔记之6 - DNS区域管理   DNS学习笔记之6 - DNS区域管理一内容简介:这一篇是DNS笔记的最后一节,建立区域是很简单的,但是我们要分辨出区域类型之间的区别。我这里对区域管理做说明。 管理正向区域 ...

    DNS学习笔记之6 - DNS区域管理

     
    DNS学习笔记之6 - DNS区域管理一内容简介:这一篇是DNS笔记的最后一节,建立区域是很简单的,但是我们要分辨出区域类型之间的区别。我这里对区域的管理做说明。 管理正向区域 根据区域类型和区域存储方式的不同,管理DNS区域的方式也不同,在此我根据区域类型

    这一篇是DNS笔记的最后一节,建立区域是很简单的,但是我们要分辨出区域类型之间的区别。我这里对区域的管理做说明。

    管理正向区域

    根据区域类型和区域存储方式的不同,管理DNS区域的方式也不同,在此我根据区域类型来进行介绍:

    1、主要区域

    活动目录集成主要区域和标准主要区域相比,常规选项不同,并且具有安全标签。

    在活动目录集成主要区域的常规标签,你可以暂停和开始区域的运行,并且可以修改区域类型、复制方式和动态更新方式;

    DNS 学习笔记之6- DNS区域的管理①

    而在标准主要区域的常规标签,你可以暂停和开始区域的运行,并且可以修改区域类型、区域数据存储的文件名和动态更新方式,但是不支持安全动态更新。

    DNS 学习笔记之6- DNS区域的管理①

    点击老化按钮→区域老化/清理属性设置,此设置必须和DNS服务器的老化/清理设置共同 使用方可生效。

    当启用老化时,对于每个动态更新记录,会基于当前的DNS服务器时间创建一个时间戳,当DHCP客户端服务或者DHCP服务器为此区域中的A记录进行动态更新时,会刷新时间 戳。手动创建的资源记录会分配一个为0的时间戳记录,代表它们将不会老化。

    •    无刷新间隔:无刷新间隔是在上次时间戳刷新后,DNS服务器拒绝再次进行刷新的时间周期,这阻止DNS服务器进行没有必要的刷新和减少了没有必要的区域传输流量。默认情况下,无刷新间隔为7天;

    •    刷新间隔:刷新间隔是在无刷新间隔后的时候,在这段时间周期内允许DNS客户端刷新资源记录的时间戳,并且资源记录不会被DNS服务器清理。 当无刷新间隔和刷新间隔之后,如果资源记录没有被DNS客户端进行刷新,则此资源记录将会被DNS服务器清除掉。默认情况下刷新间隔是7天,这意味着默认情况下动态注册的 资源记录将会在14天后被清理掉。

    如果你需要修改这两个参数,请记住以下原则:刷新间隔应该大于或等于无刷新间隔。

    DNS 学习笔记之6- DNS区域的管理① 

    起始授权机构(SOA)

    起始授权机构(SOA)标签允许你配置此DNS区域的SOA记录。当DNS服务器加载DNS区域时,它首先通过SOA记录来决定此DNS区域的基本信息和主服务器,如下图所示:

    DNS 学习笔记之6- DNS区域的管理①

    * 序列号:序列号代表了此区域文件的修订号。当区域中任何资源记录被修改或者点击了增量按钮时,此序列号会自动增加。 在配置了区域复制时,辅助DNS服务器会间歇的查询主服务器上DNS区域的序列号,如果主服务器上DNS区域的序列号大于自己的序列号,则辅助DNS服务器向主服务器发起区域 复制。

    * 主服务器:主服务器包含了此DNS区域的主DNS服务器的FQDN,此名字必须使用“.”结尾。

    * 负责人:指定了管理此DNS区域的负责人的邮箱,你可以修改为在DNS区域中定义的其他RP(负责人)资源记录,此名字必须使用 “.”结尾。

    * 刷新间隔: 此参数定义了辅助DNS服务器查询主服务器以进行区域更新前等待的时间。当刷新时间到期时,辅助DNS服务器从主服务器上获取主 DNS区域的SOA记录,然后和本地辅助DNS区域的SOA记录相比较,如果值不相同则进行区域传输。默认情况下,刷新间隔为15分钟。

    * 重试间隔:此参数定义了当区域复制失败时,辅助DNS服务器进行重试前需要等待的时间间隔,默认情况下为10分钟。

    * 过期时间:此参数定义了当辅助DNS服务器无法联系主服务器时,还可以使用此辅助DNS区域答复DNS客户端请求的时间,当到达此时间限制时,辅 助DNS服务器会认为此辅助DNS区域不可信。默认情况下为1天。

    * 最小(默认)TTL:此参数定义了应用到此DNS区域中所有资源记录的生存时间(TTL),默认情况下为1小时。此TTL只是和资源记录在非权威的 DNS服务器上进行缓存时的生存时间,当TTL过期时,缓存此资源记录的DNS服务器将丢弃此记录的缓存。

    注意:增大TTL可以减少网络中DNS解析请求的流量,但是可能会导致修改资源记录后DNS解析时延的问题。一般情况下无需对默认参数进行修改。

    * 此记录的TTL:此参数用于设置此SOA记录的TTL值,这个参数将覆盖最小(默认)TTL中设置的值。

    名称服务器

    名字服务器标签允许你配置DNS区域的NS资源记录,NS记录用于指定此DNS区域中的权威DNS服务器,默认情况下会包含此DNS区域的主服务器,并且一个区域 至少必须具有一个NS资源记录。

    和SOA记录一样,你只能在区域属性中对NS记录进行修改,你不能创建NS记录。

    DNS 学习笔记之6- DNS区域的管理①

    WINS

    你可以在WINS标签配置DNS服务器使用WINS查找,此时,当DNS服务器无法解析某个FQDN时,将会使用配置的WINS服务器来查询此FQDN的主机名;对于正向 区域是查询WINS服务器的正向记录,对于反向区域是查询WINS服务器的反向记录;如果在WINS服务器上查询到对应的记录,则DNS服务器会将此记录复制到此区域中,你可以勾选不复制此记录来让DNS服务器不复制从WINS服务器获得的记录。

    DNS 学习笔记之6- DNS区域的管理① 

    区域复制

    你可以在区域复制标签中配置是否允许此区域进行区域复制,以及区域复制到的对象,它们之间的区别在于:

    * 到所有服务器:所有服务器都可以从此DNS服务器获取此区域的区域数据;

    * 只有在“名称服务器”选项卡中列出的服务器:只有在名称服务器标签中列出的DNS服务器才能从此DNS服务器获取区域数据;

    * 只允许到下列服务器:只允许你在下面列表中指定的DNS服务器从此DNS服务器获取区域数据;

    在windows 2000中,默认情况下是允许区域复制到所有服务器,这个选项具有安全隐患,所以在Windows Server 2003中,对于标准主要区域,默认情况下只是允许区域复制到名称服务器中所定义的DNS服务中,而对于活动目录集成主要区域,由于通过活动目录进行复制,默认情况下是不允许区域复制。

    DNS 学习笔记之6- DNS区域的管理①

    DNS 学习笔记之6- DNS区域的管理①

    你可以点击通知按钮来配置通知辅助DNS服务器接收区域更新,默认情况下此DNS区域更新时,主服务器会通知名称服务器标签中的所有 DNS服务器。

    DNS 学习笔记之6- DNS区域的管理①

    当某个标准区域产生以下事件时,将进行通知或初始化区域复制:

    * 主DNS区域的SOA记录的刷新间隔过期;

    * 辅助DNS服务器启动;此时辅助DNS服务器会联系主服务器获取SOA记录,然后比较本地的SOA记录来决定是否需要区域复制;

    * 主服务器上对区域数据进行了修改,则主服务器按照配置来通知辅助DNS服务器。

    当初始化区域复制时,辅助DNS服务器可以从主服务器执行增量区域传输(IXFR)或者完全区域传输(AXFR),运行在Windows Server 2003上的DNS服务器 支持IXFR和AXFR。默认情况下,运行在Windows 2000服务器和Windows Server 2003系统上的DNS服务器从主服务器进行区域复制时执行IXFR,此时,只有更新数据才会进行 传输;Windows NT服务器不支持IXFR,只能执行AXFR,此时,将会对所有区域数据进行传输。

    其他的就不说了,关于如何添加资源记录,网上教程很多,这里只说一个MX记录的添加的(关于MX、PTR我在exchange哪里还会仔细说明的):

    新建邮件交换器(MX):

    在创建邮件交换器记录之前,必须已经为此MX记录所对应的邮件服务器创建了A记录;在主机或子域中输入邮件域名,如果不输入则代表此DNS区域;

    你可以针对相同的DNS域配置多个MX记录,但是邮件服务器优先级数值越低的MX记录具有越高的优先级。

    DNS 学习笔记之6- DNS区域的管理①

    注意:主机或子域内容--是不需要填写的

    管理任务

    主要区域的管理任务如下图所示:

    DNS 学习笔记之6- DNS区域的管理①

    更新服务器数据文件:同DNS服务器的更新服务器数据文件管理任务;

    重新加载:重新从本地的区域文件或者活动目录中加载此DNS区域;

    辅助区域和主要区域的属性基本相同,我在此着重介绍不同之处:

    在常规标签,你可以暂停和开始区域的运行,并且可以修改区域类型和用于复制区域数据的主服务器地址。主服务器上必须允许了区域复制到此辅助 DNS服务器。

    辅助区域不能和活动目录集成,因此复制选项不可用;并且也不支持动态更新,因为辅助区域是只读的。

    DNS 学习笔记之6- DNS区域的管理②  

    起始授权机构(SOA)

    对于辅助区域而言,起始授权机构(SOA)记录是只读的,因此你不能在起始授权机构(SOA)标签进行任何配置,如下图所示:

    DNS 学习笔记之6- DNS区域的管理②

    名称服务器

    和起始授权机构(SOA)记录一样,NS记录是只读的,因此你不能在辅助DNS服务器上修改名字服务器,如下图所示:

    DNS 学习笔记之6- DNS区域的管理②

    WINS

    你可以在WINS标签配置辅助DNS服务器使用WINS查找,但是由于辅助DNS区域是只读的,所以对于从WINS服务器获得的记录,你只能缓存在本地,而不能将其复制到DNS区域中。

    DNS 学习笔记之6- DNS区域的管理②

    区域复制

    你可以在区域复制标签配置将此辅助DNS区域复制到其他辅助DNS服务器,但是默认情况下是不会配置辅助区域的区域复制。

    DNS 学习笔记之6- DNS区域的管理②

    当在域控制器上安装DNS服务器时,辅助DNS区域的属性中会具有安全标签,用于控制用户对于此DNS区域及所属子对象的权限。

    和主要区域相比,辅助区域的管理任务只有三项,如下图所示:

    DNS 学习笔记之6- DNS区域的管理②

     

    展开全文
  • 重大危险源可视GIS管理系统

    千次阅读 2019-04-13 14:32:33
    随着安全生产形势的日益严峻和国家对安全生产的高度重视,进一步深化信息建设,提高安全生产监管工作的办事效率和工作透明度,构建高效、透明、快捷、安全的“电子政务”已势在必行。如何利用先进的计算机技术软...
  • 【一句话】区域售前管理工作思路

    千次阅读 2019-07-20 23:09:00
    对于业务、客户分散在全国各区域的公司来说,本地化区域化建设是非常重要的一个课题。因为涉及到售前响应速度、交付支持力度等。 5. 区域售前工作与总部售前的各自特点 理论上来说,总部售前的技术能力以及提升...
  • 二、管理正向区域 根据区域类型和区域存储方式的不同,管理DNS区域的方式也不同,在此我根据区域类型来进行介绍: 1、主要区域 活动目录集成主要区域和标准主要区域相比,常规选项不同,并且具有安全标签。 常规...
  • 信息化管理系统在企业的应用

    千次阅读 2013-08-09 14:50:21
    思多雅注:信息化管理系统在企业的应用是个比较大的题目,涉及的范畴广,而具体到每个企业的实际情况又有所不同,笔者尝试以国内企业发展的共性,小论信息化对企业抵御金融的作用,以抛砖引玉,共同探讨。
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-08-31 ... Linux内存管理 1 前景回顾前面我们讲到服务器体系(SMP, NUMA, MPP)与共享存储器架构(UMA和NUMA)1.1 UMA和NUMA两种模型共享存储型多处理机有两种模型
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 ...在内存管理的上下文中, 初始(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换
  • Oracle 10g SGA 的自动化管理

    万次阅读 2010-04-27 16:02:00
    --==============================--Oracle 10g SGA 的自动化管理--==============================/* 在SGA中每一个单独的组件究竟需要多少内存呢?在Oracle 10g 中可以自动化管理大多数SGA参数。 一、什么是SGA...
  • 日期 内核版本 架构 作者 GitHub ... Linux内存管理在内存管理的上下文中, 初始(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到
  • 供应链管理,阿米巴管理,能源化工行业四大业务特点,六大管理现状,管理经营数据五大问题,能源化工行业数据四大特点,基于能源行业业务、管理、数据特点的数据决策管理支持方案(PC端集成、移动办公、微信集成、...
  • “前几天有居民反映小区井盖没有了,通过网格化管理系统进行联动,马上就把井盖补上了,这在以前是难以做到的。” “社区网格群里,有几个居民反映噪音扰民的问题,网格员根据大家提供的信息,对多家小门店进行了...
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 ...在内存管理的上下文中, 初始(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换
  • JVM:自动内存管理之Java内存区域与内存溢出

    千次阅读 多人点赞 2021-01-07 12:03:22
    一、Java内存区域与内存溢出异常 1、运行时数据区域 运行时数据分为七大块 先来看看JVM内存分布图 1、程序计数器 程序计数器是一个记录着当前线程所执行的字节码的行号指示器。 Java虚拟机中每条线程都有...
  • TensorBoard是TensorFlow自带的可视结构管理和调试优化网络的工具。在我们学习深度学习网络框架时,我们需要更直观的看到各层网络结构和参数,也可以更好的进行调试优化网络。TensorBoard可以实现网络结构的显示,...
  • 数字城市及网络化管理技术

    千次阅读 2012-10-04 16:02:21
    数字城管的起源: 1998年1月,美国副总统戈尔在加利福尼亚科学中心举行的开放地理信息系统协会上,发表了题为“数字地球:21世纪认识地球的方式”的报告。...“数字城市”是一个集数字、网络和信息等多种
  • sugarnms网络管理智能

    千次阅读 2013-11-29 16:30:01
    进入21世纪,网络技术飞速发展,企业网络的规模不断扩大,这不仅让企业办公环境得到了根本性变化,也让管理这些网络的IT部门逐渐开始体验到了一种“成长的烦恼”。目前的企业在成立之初就开始规划企业网络,在网络规模...
  • 数字冰雹应急管理大屏可视决策系统,面向应急指挥中心大屏环境,具备优秀的大数据显示性能以及多机协同管理机制,支持大屏、多屏、超大分辨率等显示情景。 支持整合应急管理部门现有信息系统的数据资源,覆盖日常...
  • nand flash 扇区的管理以及初始

    千次阅读 2014-05-29 11:48:19
    U盘量产工具其实就是一种集坏区域扫描和Flash管理系统装载于一身的工具。常规U盘主控的扫描是以块为单位,扫描即往每一个块里写入数据,然后将读出的数据与写入的数据比较,如果数据有误则把该块标为“坏块”。扫描...
  • 矿产资源规划管理信息建设研究

    千次阅读 2011-05-11 22:27:00
    2006-11-17 | 作者: 任效颖 | 来源: 国土资源信息 | 【大 中 小】【打印】【关闭】 一、 引 言 国务院于2001年4月批复的《全国矿产资源规划》下发实施后,全国31个省(区、市)的省级矿产资源规划...
  • VisualNet采用C/S+B/S架构(见图1),为用户提供一个虚拟现实的网络资源综合性管理...②采用由总到分、由粗到细的层次管理模式,便于管理复杂的大型网络; ③每个图形对象均可被查询、定位和管理,并与各种第三方
  • 湘潭大学信息体系中涉及的人员复杂,除了高校本单位维护的老师外,还有外包团队人员。湘潭大学已经意识到需要对IT资产(硬件、应用、人员)实施有效管理,保证网络安全、可靠和畅通。 高校行业特性 (一)网络...
  •  在内存子系统初始以前,即boot阶段也需要进行内存管理,启动内存分配器是专为此而设计的。linux启动内存分配器是在伙伴系统、slab机制实现之前,为满足内核中内存的分配而建立的。本身的机制比较简单,使用位图...
  • linux物理内存管理区初始

    千次阅读 2012-01-01 20:50:57
    Linux物理内存管理区在start_kernel函数中进行初始,此时启动分配器已经建立,所以可以从bootmem中分配需要的内存。 一、全局变量初始 max_pfn:最大物理页面帧号 start_kernel()->setup_arch()->e820_end_of...
  • 我们知道随着Linux系统的运行,内存是不断的趋于碎片的,内存碎片分为两种类型,一种为外碎片,所谓外碎片就是以页为单位的内存之间的碎片,另一种为内碎片,内碎片是指同一个页面内的碎片,那么...
  • 由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分。在讨论JVM内存区域划分之前,先来看一下Java程序具体执行的过程:    如上图所示,首先Java源代码文件(.java后缀)...
  • 通过持续交付的流水线,我们能够清晰的定义出软件从代码提交到上线发布之前所需要经过的每个环节,协助开发者发现工作流程中存在的瓶颈,并促使团队提升端到端的自动程度,缩短独立功能上线的周期。  那么容器...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 248,737
精华内容 99,494
关键字:

区域化管理的意义