request_standard_resources()

<kernel v5.0>

ARM32용 표준 리소스 등록

2 개의 루트 리소스에 관련 표준 리소스를 등록한다.

  • 전역 iomem_resource에 System RAM을 등록하고 커널 코드와 커널 데이터 영역이 System RAM 영역에 포함되는 경우 System RAM의 child 리소스로 추가한다.
    • XIP 커널의 경우 커널 커드는 ROM에 포함되므로 System RAM의 child 리소스로 추가되지 않는다.
  • 전역 ioport_resource에는 lp0~lp2가 머신에 등록되어 있는 경우 리소스로 추가한다.

 

request_standard_resources-1

 

request_standard_resources() – ARM32

arch/arm/kernel/setup.c

static void __init request_standard_resources(const struct machine_desc *mdesc)
{
        struct memblock_region *region;
        struct resource *res;

        kernel_code.start   = virt_to_phys(_text);
        kernel_code.end     = virt_to_phys(__init_begin - 1);
        kernel_data.start   = virt_to_phys(_sdata);
        kernel_data.end     = virt_to_phys(_end - 1);

        for_each_memblock(memory, region) {
                phys_addr_t start = __pfn_to_phys(memblock_region_memory_base_pfn(region));
                phys_addr_t end = __pfn_to_phys(memblock_region_memory_end_pfn(region)) - 1;
                unsigned long boot_alias_start;

                /*
                 * Some systems have a special memory alias which is only
                 * used for booting.  We need to advertise this region to
                 * kexec-tools so they know where bootable RAM is located.
                 */
                boot_alias_start = phys_to_idmap(start);
                if (arm_has_idmap_alias() && boot_alias_start != IDMAP_INVALID_ADDR) {
                        res = memblock_alloc(sizeof(*res), SMP_CACHE_BYTES);
                        res->name = "System RAM (boot alias)";
                        res->start = boot_alias_start;
                        res->end = phys_to_idmap(end);
                        res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;
                        request_resource(&iomem_resource, res);
                }

                res = memblock_alloc(sizeof(*res), SMP_CACHE_BYTES);
                res->name  = "System RAM";
                res->start = start;
                res->end = end;
                res->flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;

                request_resource(&iomem_resource, res);

                if (kernel_code.start >= res->start &&
                    kernel_code.end <= res->end)
                        request_resource(res, &kernel_code);
                if (kernel_data.start >= res->start &&
                    kernel_data.end <= res->end)
                        request_resource(res, &kernel_data);
        }

        if (mdesc->video_start) {
                video_ram.start = mdesc->video_start;
                video_ram.end   = mdesc->video_end;
                request_resource(&iomem_resource, &video_ram);
        }

        /*
         * Some machines don't have the possibility of ever
         * possessing lp0, lp1 or lp2
         */
        if (mdesc->reserve_lp0)
                request_resource(&ioport_resource, &lp0);
        if (mdesc->reserve_lp1)
                request_resource(&ioport_resource, &lp1);
        if (mdesc->reserve_lp2)
                request_resource(&ioport_resource, &lp2);
}

표준 리소스를 등록 요청한다.

  • 코드 라인 6~9에서 커널 코드와 커널 데이터 물리 주소를 구한다.
  • 코드 라인 11~29에서 memory memblock을 순회하며 특정 시스템에서 boot용 RAM이 있는 경우 resource 구조체를 할당하고 부팅용 시스템 RAM으로 iomem 리소스로 등록 요청한다.
  • 코드 라인 31~37에서 resource 구조체를 할당하고 시스템 RAM으로 iomem 리소스로 등록 요청한다.
  • 코드 라인 39~41에서 커널 코드영역이 System RAM 영역에 포함된 경우 커널 코드 영역을 System RAM의 child로 추가한다.
  • 코드 라인 42~44에서 커널 데이터영역이 System RAM 영역에 포함된 경우 커널 데이터 영역을 System RAM의 child로 추가한다.
  • 코드 라인 47~51에서 머신에 비디오 정보가 있는 경우 전역 iomem_resource에 비디오 램 리소스 정보를 추가한다.
  • 코드 라인 57~58에서 머신에 reserve_lp0 정보가 있는 경우 전역 ioport_resource에 lp0 리소스를 추가한다.
  • 코드 라인 59~60에서 머신에 reserve_lp1 정보가 있는 경우 전역 ioport_resource에 lp1 리소스를 추가한다.
  • 코드 라인 61~62에서 머신에 reserve_lp2 정보가 있는 경우 전역 ioport_resource에 lp2 리소스를 추가한다.

 

아래 그림은 루트 리소스 iomem_resource에 등록된 System RAM, Video RAM 등을 보여준다. System RAM 내부  Kernel code와 Kernel data도 존재한다. 만일 XIP 커널이라면 Kernel code는 존재하지 않을 것이다.

request_standard_resources-2a

아래 그림은 루트 리소스 ioport_resource에 등록된 lp0~lp2 리소스 등을 보여준다.

request_standard_resources-3a

 


ARM64용 표준 리소스 등록

request_standard_resources() – ARM64

arch/arm64/kernel/setup.c

static void __init request_standard_resources(void)
{
        struct memblock_region *region;
        struct resource *res;
        unsigned long i = 0;

        kernel_code.start   = __pa_symbol(_text);
        kernel_code.end     = __pa_symbol(__init_begin - 1);
        kernel_data.start   = __pa_symbol(_sdata);
        kernel_data.end     = __pa_symbol(_end - 1);

        num_standard_resources = memblock.memory.cnt;
        standard_resources = memblock_alloc_low(num_standard_resources *
                                                sizeof(*standard_resources),
                                                SMP_CACHE_BYTES);

        for_each_memblock(memory, region) {
                res = &standard_resources[i++];
                if (memblock_is_nomap(region)) {
                        res->name  = "reserved";
                        res->flags = IORESOURCE_MEM;
                } else {
                        res->name  = "System RAM";
                        res->flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;
                }
                res->start = __pfn_to_phys(memblock_region_memory_base_pfn(region));
                res->end = __pfn_to_phys(memblock_region_memory_end_pfn(region)) - 1;

                request_resource(&iomem_resource, res);

                if (kernel_code.start >= res->start &&
                    kernel_code.end <= res->end)
                        request_resource(res, &kernel_code);
                if (kernel_data.start >= res->start &&
                    kernel_data.end <= res->end)
                        request_resource(res, &kernel_data);
#ifdef CONFIG_KEXEC_CORE
                /* Userspace will find "Crash kernel" region in /proc/iomem. */
                if (crashk_res.end && crashk_res.start >= res->start &&
                    crashk_res.end <= res->end)
                        request_resource(res, &crashk_res);
#endif
        }
}

표준 리소스를 등록 요청한다.

  • 코드 라인 7~10에서 커널 코드와 커널 데이터 물리 주소를 구한다.
  • 코드 라인 12~15에서 memory memblock 수 만큼 리소스 배열을 할당받는다.
  • 코드 라인 17~29에서 memory memblock을 순회하며 nomap 플래그가 설정된 영역은 reserved 영역으로, 그 외의 경우는 시스템 RAM으로  iomem 리소스에 등록 요청한다.
  • 코드 라인 31~33에서 커널 코드영역이 System RAM 영역에 포함된 경우 커널 코드 영역을 System RAM의 child로 추가한다.
  • 코드 라인 34~36에서 커널 데이터영역이 System RAM 영역에 포함된 경우 커널 데이터 영역을 System RAM의 child로 추가한다.
  • 코드 라인 37~42에서 크래시 커널 영역이 설정된 경우 이 영역을 시스템 RAM의 child로 추가한다.

 


Standard Resource

kernel/resource.c

struct resource ioport_resource = {
        .name   = "PCI IO",
        .start  = 0,
        .end    = IO_SPACE_LIMIT,
        .flags  = IORESOURCE_IO,
};
EXPORT_SYMBOL(ioport_resource);

struct resource iomem_resource = {
        .name   = "PCI mem",
        .start  = 0,
        .end    = -1,
        .flags  = IORESOURCE_MEM,
};
EXPORT_SYMBOL(iomem_resource);
  • ioport_resource와 iomem_resource 루트 리소스이다.

 

ARM32용 리소스

arch/arm/kernel/setup.c – ARM32

/*
 * Standard memory resources
 */
static struct resource mem_res[] = {
        {
                .name = "Video RAM",
                .start = 0,
                .end = 0,
                .flags = IORESOURCE_MEM
        },
        {
                .name = "Kernel code",
                .start = 0,
                .end = 0,
                .flags = IORESOURCE_MEM
        },
        {
                .name = "Kernel data",
                .start = 0,
                .end = 0,
                .flags = IORESOURCE_MEM
        }
};

#define video_ram   mem_res[0]
#define kernel_code mem_res[1]
#define kernel_data mem_res[2]
  • 루트 리소스 iomem_resource에 등록될 Video RAM, Kernel code, Kernel data 리소스의 초기데이터이다.

 

arch/arm/kernel/setup.c – ARM32

static struct resource io_res[] = {
        {
                .name = "reserved",
                .start = 0x3bc,
                .end = 0x3be,
                .flags = IORESOURCE_IO | IORESOURCE_BUSY
        },
        {
                .name = "reserved",
                .start = 0x378,
                .end = 0x37f,
                .flags = IORESOURCE_IO | IORESOURCE_BUSY
        },
        {
                .name = "reserved",
                .start = 0x278,
                .end = 0x27f,
                .flags = IORESOURCE_IO | IORESOURCE_BUSY
        }
};

#define lp0 io_res[0]
#define lp1 io_res[1]
#define lp2 io_res[2]
  • 루트 리소스 ioport_resource 루트 리소스에 등록될 lp0~lp2 리소스의 초기데이터이다.

 

ARM64용 리소스

arch/arm64/kernel/setup.c – ARM64

/*
 * Standard memory resources
 */
static struct resource mem_res[] = {
        {
                .name = "Kernel code",
                .start = 0,
                .end = 0,
                .flags = IORESOURCE_SYSTEM_RAM
        },
        {
                .name = "Kernel data",
                .start = 0,
                .end = 0,
                .flags = IORESOURCE_SYSTEM_RAM
        }
};

 

include/linux/ioport.h

/* I/O resource extended types */
#define IORESOURCE_SYSTEM_RAM           (IORESOURCE_MEM|IORESOURCE_SYSRAM)

 

다음은 rock960 보드의 iomem 리소스를 보여준다.

$ cat /proc/iomem
00200000-f7ffffff : System RAM
  02080000-0303ffff : Kernel code
  03160000-033b6fff : Kernel data
f8000000-f9ffffff : axi-base
fa000000-fbdfffff : MEM
  fa000000-fa0fffff : PCI Bus 0000:01
    fa000000-fa003fff : 0000:01:00.0
      fa000000-fa003fff : nvme
fd000000-fdffffff : apb-base
fe310000-fe313fff : /dwmmc@fe310000
fe320000-fe323fff : /dwmmc@fe320000
fe330000-fe33ffff : mmc1
fe380000-fe39ffff : /usb@fe380000
fe3a0000-fe3bffff : /usb@fe3a0000
fe3c0000-fe3dffff : /usb@fe3c0000
fe3e0000-fe3fffff : /usb@fe3e0000
fe800000-fe807fff : /usb@fe800000/dwc3@fe800000
  fe800000-fe807fff : /usb@fe800000/dwc3@fe800000
fe80c100-fe8fffff : /usb@fe800000/dwc3@fe800000
fe900000-fe907fff : /usb@fe900000/dwc3@fe900000
  fe900000-fe907fff : /usb@fe900000/dwc3@fe900000
fe90c100-fe9fffff : /usb@fe900000/dwc3@fe900000
ff100000-ff1000ff : /saradc@ff100000
ff110000-ff110fff : /i2c@ff110000
ff120000-ff120fff : /i2c@ff120000
ff150000-ff150fff : /i2c@ff150000
ff180000-ff18001f : serial
ff1b0000-ff1b001f : serial
ff1c0000-ff1c0fff : /spi@ff1c0000
ff260000-ff2600ff : /tsadc@ff260000
ff370000-ff37001f : serial
ff3c0000-ff3c0fff : /i2c@ff3c0000
ff3d0000-ff3d0fff : /i2c@ff3d0000
ff650000-ff6507ff : /vpu_service@ff650000
ff650800-ff65083f : /iommu@ff650800
ff660000-ff6603ff : /rkvdec@ff660000
ff660480-ff6604bf : /iommu@ff660480
ff6604c0-ff6604ff : /iommu@ff660480
ff690000-ff69007f : /efuse@ff690000
ff6d0000-ff6d3fff : /amba/dma-controller@ff6d0000
  ff6d0000-ff6d3fff : /amba/dma-controller@ff6d0000
ff6e0000-ff6e3fff : /amba/dma-controller@ff6e0000
  ff6e0000-ff6e3fff : /amba/dma-controller@ff6e0000
ff720000-ff7200ff : /pinctrl/gpio0@ff720000
ff730000-ff7300ff : /pinctrl/gpio1@ff730000
ff780000-ff7800ff : /pinctrl/gpio2@ff780000
ff788000-ff7880ff : /pinctrl/gpio3@ff788000
ff790000-ff7900ff : /pinctrl/gpio4@ff790000
ff7c0000-ff7fffff : /phy@ff7c0000
ff800000-ff83ffff : /phy@ff800000
ff848000-ff8480ff : /watchdog@ff848000
ff870000-ff870fff : /spdif@ff870000
ff880000-ff880fff : /i2s@ff880000
ff8a0000-ff8a0fff : /i2s@ff8a0000
ff8f0000-ff8f05ff : regs
ff8f1c00-ff8f1dff : cabc_lut
ff8f2000-ff8f23ff : gamma_lut
ff8f3f00-ff8f3fff : /iommu@ff8f3f00
ff900000-ff9005ff : regs
ff901c00-ff901dff : cabc_lut
ff902000-ff902fff : gamma_lut
ff903f00-ff903fff : /iommu@ff903f00
ff914000-ff9140ff : /iommu@ff914000
ff915000-ff9150ff : /iommu@ff914000
ff924000-ff9240ff : /iommu@ff924000
ff925000-ff9250ff : /iommu@ff924000
ff940000-ff95ffff : /hdmi@ff940000
ff9a0000-ff9affff : ff9a0000.gpu

 

$ cat /proc/ioports
00000000-000fffff : I/O

 

다음은 qemu 에뮬레이션 상태의 iomem 리소스를 보여준다.

$ cat /proc/iomem
09000000-09000fff : pl011@9000000
  09000000-09000fff : pl011@9000000
09010000-09010fff : pl031@9010000
  09010000-09010fff : rtc-pl031
09030000-09030fff : pl061@9030000
  09030000-09030fff : pl061@9030000
0a003c00-0a003dff : a003c00.virtio_mmio
0a003e00-0a003fff : a003e00.virtio_mmio
10000000-3efeffff : pcie@10000000
3f000000-3fffffff : PCI ECAM
40000000-7fffffff : System RAM
  40080000-4103ffff : Kernel code
  41040000-4119ffff : reserved
  411a0000-413dcfff : Kernel data
  48000000-48008fff : reserved
  7ca00000-7cbfffff : reserved
  7cdef000-7dbfffff : reserved
  7ddc0000-7ddc0fff : reserved
  7ddc1000-7ddeefff : reserved
  7ddf1000-7ddf1fff : reserved
  7ddf2000-7ddf6fff : reserved
  7ddf7000-7ddfefff : reserved
  7ddff000-7fffffff : reserved
8000000000-ffffffffff : pcie@10000000

 

$ cat /proc/ioports
00000000-0000ffff : pcie@10000000

 

참고

__flush_dcache_page()

page 구조체가 가리키는 메모리 영역에 대한 d-cache를 flush 한다.

 

__flush_dcache_page-1

 

__flush_dcache_page()

arch/arm/mm/flush.c

void __flush_dcache_page(struct address_space *mapping, struct page *page)
{
        /*  
         * Writeback any data associated with the kernel mapping of this
         * page.  This ensures that data in the physical page is mutually
         * coherent with the kernels mapping.
         */
        if (!PageHighMem(page)) {
                size_t page_size = PAGE_SIZE << compound_order(page);
                __cpuc_flush_dcache_area(page_address(page), page_size);
        } else {
                unsigned long i;
                if (cache_is_vipt_nonaliasing()) {
                        for (i = 0; i < (1 << compound_order(page)); i++) {
                                void *addr = kmap_atomic(page + i); 
                                __cpuc_flush_dcache_area(addr, PAGE_SIZE);
                                kunmap_atomic(addr);
                        }   
                } else {
                        for (i = 0; i < (1 << compound_order(page)); i++) {
                                void *addr = kmap_high_get(page + i); 
                                if (addr) {
                                        __cpuc_flush_dcache_area(addr, PAGE_SIZE);
                                        kunmap_high(page + i); 
                                }   
                        }   
                }   
        }   

        /*
         * If this is a page cache page, and we have an aliasing VIPT cache,
         * we only need to do one flush - which would be at the relevant
         * userspace colour, which is congruent with page->index.
         */
        if (mapping && cache_is_vipt_aliasing())
                flush_pfn_alias(page_to_pfn(page),
                                page->index << PAGE_CACHE_SHIFT);
}

페이지가 highmem 영역이 아닌 경우 해당 페이지들 영역에 대한 d-cache를 flush 한다.

  • if (!PageHighMem(page)) {
    • 주어진 페이지가 highmem 영역이 아니면
  • size_t page_size = PAGE_SIZE << compound_order(page);
    • size는 PAGE_SIZE x 2^compund_order로 한다.
    • 예) PAGE_SIZE=4K, compound_order=9
      • size=2M
  • __cpuc_flush_dcache_area(page_address(page), page_size);
    • flush_dcache_area에 대한 각 아키텍처 종속 코드 함수를 호출하여 특정 영역 크기에 해당하는 d-cache를 flush 한다.
    • rpi2: v7_flush_kern_dcache_area() 함수를 호출한다.

페이지가 highmem 영역이면서 cache가 vipt non-aliasing 타입인 경우 해당 페이지들 영역을 루프를 돌며 각 페이지에 대해 fixmap 매핑한 후 d-cache를 flush 하고 다시 fixmap 매핑을 해지한다.

  •  if (cache_is_vipt_nonaliasing()) {
  • for (i = 0; i < (1 << compound_order(page)); i++) {
    • 2^compound_order 페이지 만큼 루프를 돈다.
  • void *addr = kmap_atomic(page + i);
    • fixmap 영역에 highmem 메모리 한 페이지를 매핑 한다.
    • 참고: Fixmap | 문c
  • __cpuc_flush_dcache_area(addr, PAGE_SIZE);
    • 주어진 1개 가상 주소 페이지 하나에 대해 d-cache flush를 수행한다.
  • kunmap_atomic(addr);
    • fixmap 영역에 매핑된 highmem 페이지를 해지한다.

페이지가 highmem 영역이면서 cache가 vipt non-aliasing 타입이 아닌 경우 해당 페이지들 영역을 루프를 돌며 각 페이지에 대해 이미 kmap 매핑된 가상 주소를 찾아 d-cache를 flush 하고 그 kmap 매핑을 해지한다.

  • for (i = 0; i < (1 << compound_order(page)); i++) {
    • 2^compound_order 페이지 만큼 루프를 돈다.
  • void *addr = kmap_high_get(page + i);
    • kmap 영역에 매핑된 highmem 메모리를 찾는다.
    • 참고: Kmap(Pkmap) | 문c
  • __cpuc_flush_dcache_area(addr, PAGE_SIZE);
    • 찾은 1개 가상 주소 페이지 하나에 대해 d-cache flush를 수행한다.
  • kunmap_high(page + i);
    • kmap 영역에 매핑된 highmem 페이지를 해지한다.

인수로 요청한 매핑이 있는 경우이면서 캐시 타입이 vipt aliasing인 경우 TLB를 flush 한다.

  • if (mapping && cache_is_vipt_aliasing())
    • mapping과 캐시 타입이 vipt aliasing인 경우
  • flush_pfn_alias(page_to_pfn(page), page->index << PAGE_CACHE_SHIFT);
    • 요청 페이지에 대한 TLB를 flush 한다.

 

v7_flush_kern_dcache_area()

arch/arm/mm/cache-v7.S

/*
 *      v7_flush_kern_dcache_area(void *addr, size_t size)
 *
 *      Ensure that the data held in the page kaddr is written back
 *      to the page in question.
 *
 *      - addr  - kernel address
 *      - size  - region size
 */
ENTRY(v7_flush_kern_dcache_area)
        dcache_line_size r2, r3
        add     r1, r0, r1
        sub     r3, r2, #1
        bic     r0, r0, r3
#ifdef CONFIG_ARM_ERRATA_764369
        ALT_SMP(W(dsb))
        ALT_UP(W(nop))
#endif
1:
        mcr     p15, 0, r0, c7, c14, 1          @ clean & invalidate D line / unified line
        add     r0, r0, r2
        cmp     r0, r1
        blo     1b  
        dsb     st  
        ret     lr  
ENDPROC(v7_flush_kern_dcache_area)
  • start(r0) ~ end(r1) 까지 캐시 라인 길이(바이트) 만큼 증가시키면서루프를 돌며 DCCIMVAC를 호출하여 해당 캐시 라인을 비운다.
    • DCCIMVAC(Data Cache Clean & Invalidate MVA to PoC)

 

dcache_line_size()

arch/arm/mm/proc-macros.S

/*
 * dcache_line_size - get the minimum D-cache line size from the CTR register
 * on ARMv7.
 */
        .macro  dcache_line_size, reg, tmp 
        mrc     p15, 0, \tmp, c0, c0, 1         @ read ctr 
        lsr     \tmp, \tmp, #16 
        and     \tmp, \tmp, #0xf                @ cache line size encoding
        mov     \reg, #4                        @ bytes per word
        mov     \reg, \reg, lsl \tmp            @ actual cache line size
        .endm

캐시 라인 워드 사이즈를 읽어온 후 4를 곱하여 reg 레지스터로 리턴한다.

  • CTR.DminLine: 캐시 라인 사이즈로 word 단위이다.

 

flush_pfn_alias()

arch/arm/mm/flush.c

static void flush_pfn_alias(unsigned long pfn, unsigned long vaddr)
{
        unsigned long to = FLUSH_ALIAS_START + (CACHE_COLOUR(vaddr) << PAGE_SHIFT);
        const int zero = 0;

        set_top_pte(to, pfn_pte(pfn, PAGE_KERNEL));

        asm(    "mcrr   p15, 0, %1, %0, c14\n"
        "       mcr     p15, 0, %2, c7, c10, 4"
            :   
            : "r" (to), "r" (to + PAGE_SIZE - 1), "r" (zero)
            : "cc");
}
  • unsigned long va = FLUSH_ALIAS_START + (CACHE_COLOUR(vaddr) << PAGE_SHIFT);
    • ARMv6 VIPT 캐시를 사용하는 경우 CACHE_COLOUR() 값은 0~3이 리턴된다.
      • 실제 캐시 way 단면 사이즈가 4K를 초과하는 경우 페이지 크기와 달라지면서 이에 대한 교정이 필요하다. 따라서 4K를 초과하는 2개의 비트에 대해 페이지별로 coloring을 하기 위해 사용한다.
      • 참고: Cache – VIPT 캐시 컬러링 | 문c
    • FLUSH_ALIAS_START
      • 0xffff_4000
    • va
      • coloring이 존재하지 않는 경우
        • 0xffff_4000
      • coloring이 존재하는 경우
        • 0xffff_4000, 0xffff_5000, 0xffff_6000, 0xffff_7000 중 하나
  • set_top_pte(to, pfn_pte(pfn, PAGE_KERNEL));
    • va 주소가 가리키는 hw pte 엔트리 값의 속성을 x로 설정한다.
  • asm( “mcrr p15, 0, %1, %0, c14\n”
    • block operation으로 %0 ~ %1까지 주소에 Clean & Invalidate Data Cache range 명령을 수행한다.
    • 참고: c7, Cache Operations Register -> Table 3.78
  • ”       mcr     p15, 0, %2, c7, c10, 4″
    • DSB

 

set_top_pte()

arch/arm/mm/mm.h

static inline void set_top_pte(unsigned long va, pte_t pte)
{
        pte_t *ptep = pte_offset_kernel(top_pmd, va);
        set_pte_ext(ptep, pte, 0); 
        local_flush_tlb_kernel_page(va);
}
  • pte_t *ptep = pte_offset_kernel(top_pmd, va);
    • 가장 최상단에 위치한 pmd 테이블에서 va 주소와 매치되는 pmd 엔트리를 통해 찾은 pte 엔트리 주소
  • set_pte_ext(ptep, pte, 0);
  • local_flush_tlb_kernel_page(va);
    • 요청 가상 주소 페이지에 대한 TLB flush를 수행한다.

 

참고

 

Memory Model -4- (APIs)

Memory Model -4- (APIs)

 

for_each_migratetype_order()

include/linux/mmzone.h

#define for_each_migratetype_order(order, type) \
        for (order = 0; order < MAX_ORDER; order++) \
                for (type = 0; type < MIGRATE_TYPES; type++)
  • buddy 메모리 할당자가 사용하는 MAX_ORDER(11)  수 만큼 루프를 돈다.
  • 메모리 hotplug에 대한 이주 플래그 관리를 담당하는 MIGRATE_TYPES 만큼 루프를 돈다.

 


존 및 노드 관련

is_highmem_idx()

include/linux/mmzone.h

static inline int is_highmem_idx(enum zone_type idx)
{
#ifdef CONFIG_HIGHMEM
        return (idx == ZONE_HIGHMEM ||
                (idx == ZONE_MOVABLE && movable_zone == ZONE_HIGHMEM)); 
#else
        return 0;
#endif
}

 

zone_idx()

include/linux/mmzone.h

/*
 * zone_idx() returns 0 for the ZONE_DMA zone, 1 for the ZONE_NORMAL zone, etc.
 */
#define zone_idx(zone)          ((zone) - (zone)->zone_pgdat->node_zones)
  • zone 인덱스 번호를 리턴한다.
    • 예) ZONE_DMA, ZONE_NORMAL을 사용하는 경우 0과 1이 리턴된다.
    • 예) ZONE_NORMAL만 사용하는 경우 0이 리턴된다.

 

set_page_links()

include/linux/mm.h

static inline void set_page_links(struct page *page, enum zone_type zone,
        unsigned long node, unsigned long pfn) 
{
        set_page_zone(page, zone);
        set_page_node(page, node);
#ifdef SECTION_IN_PAGE_FLAGS
        set_page_section(page, pfn_to_section_nr(pfn));
#endif
}
  • page->flags에 zone, node 및 section 정보를 설정한다.

 

set_page_zone()

include/linux/mm.h

static inline void set_page_zone(struct page *page, enum zone_type zone)
{
        page->flags &= ~(ZONES_MASK << ZONES_PGSHIFT);
        page->flags |= (zone & ZONES_MASK) << ZONES_PGSHIFT;
}
  • page->flags에 zone 정보를 설정한다.

 

set_page_node()

include/linux/mm.h

static inline void set_page_node(struct page *page, unsigned long node)
{
        page->flags &= ~(NODES_MASK << NODES_PGSHIFT);
        page->flags |= (node & NODES_MASK) << NODES_PGSHIFT;
}
  • page->flags에 노드 정보를 설정한다.

 

page_zone_id()

include/linux/mm.h

/*
 * The identification function is mainly used by the buddy allocator for
 * determining if two pages could be buddies. We are not really identifying
 * the zone since we could be using the section number id if we do not have
 * node id available in page flags.
 * We only guarantee that it will return the same value for two combinable
 * pages in a zone.
 */
static inline int page_zone_id(struct page *page)
{
        return (page->flags >> ZONEID_PGSHIFT) & ZONEID_MASK;
}

페이지에서 zone id를 추출하여 반환한다.

 


Sprsemem 섹션 관련

set_page_section()

include/linux/mm.h

#ifdef SECTION_IN_PAGE_FLAGS
static inline void set_page_section(struct page *page, unsigned long section)
{
        page->flags &= ~(SECTIONS_MASK << SECTIONS_PGSHIFT);
        page->flags |= (section & SECTIONS_MASK) << SECTIONS_PGSHIFT;
}
#endif
  • page->flags에 섹션 정보를 설정한다.

 

__pfn_to_section()

include/linux/mmzone.h

static inline struct mem_section *__pfn_to_section(unsigned long pfn) 
{
        return __nr_to_section(pfn_to_section_nr(pfn));
}

pfn 값에 대응하는 mem_section 구조체 정보를 리턴한다.

  •  pfn_to_section_nr()
    • pfn 값으로 섹션 번호를 알아온다.
  • __nr_to_section()
    • 섹션 번호로 mem_section 구조체 정보를 리턴한다.

 

pfn_to_section_nr()

include/linux/mmzone.h

#define pfn_to_section_nr(pfn) ((pfn) >> PFN_SECTION_SHIFT)
  • pfn의 섹션(Sparse) 인덱스를 리턴한다.
    • 예) Realview-PBX
      • 섹션 사이즈가 256M 단위(PFN_SECTION_SHIFT=16)이므로 섹션 번호는 0~15까지의 결과

 

__nr_to_section()

include/linux/mmzone.h

static inline struct mem_section *__nr_to_section(unsigned long nr)
{
        if (!mem_section[SECTION_NR_TO_ROOT(nr)])
                return NULL;
        return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
}
  • 섹션 번호로 mem_section 구조체 정보를 리턴한다.

 

SECTION_NR_TO_ROOT()

include/linux/mmzone.h

#define SECTION_NR_TO_ROOT(sec) ((sec) / SECTIONS_PER_ROOT)
  • 섹션 번호로 ROOT 번호를 리턴한다.

 

#ifdef CONFIG_SPARSEMEM_EXTREME
#define SECTIONS_PER_ROOT       (PAGE_SIZE / sizeof (struct mem_section))
#else
#define SECTIONS_PER_ROOT       1
#endif
  • ROOT 하나 당 섹션 수
    • PAGE_SIZE(4K)에 mem_section 구조체가 들어갈 수 있는 수

 

present_section_nr()

include/linux/mmzone.h

static inline int present_section_nr(unsigned long nr)
{
        return present_section(__nr_to_section(nr));
}

섹션 번호에 해당하는 mem_section이 준비되어 있는지 확인한다. 준비되어 있지 않은 경우 해당 섹션은 hole을 의미한다.

  • __nr_to_section()
    • 섹션 번호로 mem_section 구조체 정보를 알아온다.
  • present_section()
    • mem_section 구조체 정보에 섹션이 존재하는지 확인한다.

 

present_section()

include/linux/mmzone.h

static inline int present_section(struct mem_section *section)
{
        return (section && (section->section_mem_map & SECTION_MARKED_PRESENT));
}

mem_section 구조체 정보에 섹션이 존재하는지 확인한다.

  • SECTION_MARKED_PRESENT 식별 비트가 설정되어 있는지 확인한다.

 

__section_mem_map_addr()

include/linux/mmzone.h

static inline struct page *__section_mem_map_addr(struct mem_section *section)
{
        unsigned long map = section->section_mem_map;
        map &= SECTION_MAP_MASK;
        return (struct page *)map;
}

헤당 Sparse memory 섹션에 대한 mem_map 주소를 반환한다.

include/linux/mmzone.h

/*
 * We use the lower bits of the mem_map pointer to store
 * a little bit of information.  There should be at least
 * 3 bits here due to 32-bit alignment.
 */
#define SECTION_MARKED_PRESENT  (1UL<<0)
#define SECTION_HAS_MEM_MAP     (1UL<<1)
#define SECTION_MAP_LAST_BIT    (1UL<<2)
#define SECTION_MAP_MASK        (~(SECTION_MAP_LAST_BIT-1))
#define SECTION_NID_SHIFT       2

 


페이지의 참조 사용 및 사용 해제

get_page()

include/linux/mm.h

static inline void get_page(struct page *page)
{
        page = compound_head(page);
        /*
         * Getting a normal page or the head of a compound page
         * requires to already have an elevated page->_refcount.
         */
        VM_BUG_ON_PAGE(page_ref_count(page) <= 0, page);
        page_ref_inc(page);
}

참조 카운터를 1 증가시킨다.

 

get_page_unless_zero()

include/linux/mm.h

/*
 * Try to grab a ref unless the page has a refcount of zero, return false if
 * that is the case.
 * This can be called when MMU is off so it must not access
 * any of the virtual mappings.
 */
static inline int get_page_unless_zero(struct page *page)
{
        return page_ref_add_unless(page, 1, 0);
}

참조 카운터(p->_refcount)를 읽은 후 0 값과 다른 경우에 한해 증가시킨다. 결과 값이 0이 아니면 true를 반환한다.

 

put_page()

include/linux/mm.h

static inline void put_page(struct page *page)
{
        page = compound_head(page);

        /*
         * For devmap managed pages we need to catch refcount transition from
         * 2 to 1, when refcount reach one it means the page is free and we
         * need to inform the device driver through callback. See
         * include/linux/memremap.h and HMM for details.
         */
        if (put_devmap_managed_page(page))
                return;

        if (put_page_testzero(page))
                __put_page(page);
}

참조 카운터를 1 감소 시킨다. 만일 0이되면 페이지의 회수를 진행한다.

 

put_page_testzero()

include/linux/mm.h

/*
 * Methods to modify the page usage count.
 *
 * What counts for a page usage:
 * - cache mapping   (page->mapping)
 * - private data    (page->private)
 * - page mapped in a task's page tables, each mapping
 *   is counted separately
 *
 * Also, many kernel routines increase the page count before a critical
 * routine so they can be sure the page doesn't go away from under them.
 */

/*
 * Drop a ref, return true if the refcount fell to zero (the page has no users)
 */
static inline int put_page_testzero(struct page *page)
{
        VM_BUG_ON_PAGE(page_ref_count(page) == 0, page);
        return page_ref_dec_and_test(page);
}

페이지의 참조카운터를 감소시키고 0(사용완료)인지 확인하여 사용완료 여부를 반환한다.

  • 0=사용중, 1=사용완료(참조 _count가 0이된 경우)

 


page vs pfn 변환

PFN과 page 구조체 포인터와의 변환은 다음 2개의 API를 사용한다.

include/asm-generic/memory_model.h

#define page_to_pfn __page_to_pfn
#define pfn_to_page __pfn_to_page
  • page_to_pfn()
    • page 구조체 포인터로 pfn 값을 알아온다.
  • pfn_to_page()
    • pfn 값으로 page 구조체 포인터를 알아온다.

 

다음과 같이 flat 및 sparse물리 메모리 모델에 따라 변환 방법이 달라지며, sparse 물리 모델의 경우 vmemmap 사용여부에 따라 다시 2 가지로 나뉜다.

CONFIG_FLATMEM
#define __pfn_to_page(pfn)      (mem_map + ((pfn) - ARCH_PFN_OFFSET))
#define __page_to_pfn(page)     ((unsigned long)((page) - mem_map) + \
                                 ARCH_PFN_OFFSET)
  • __pfn_to_page()
    • ARCH_PFN_OFFSET은 물리 DRAM의 시작 PFN 값을 가리킨다.
    • mem_map[@pfn – 물리 DRAM 시작 PFN]

 

CONFIG_SPARSEMEM
/*
 * Note: section's mem_map is encoded to reflect its start_pfn.
 * section[i].section_mem_map == mem_map's address - start_pfn;
 */
#define __page_to_pfn(pg)                                       \
({      const struct page *__pg = (pg);                         \
        int __sec = page_to_section(__pg);                      \
        (unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec))); \
})      

#define __pfn_to_page(pfn)                              \
({      unsigned long __pfn = (pfn);                    \
        struct mem_section *__sec = __pfn_to_section(__pfn);    \
        __section_mem_map_addr(__sec) + __pfn;          \
})
  • __pfn_to_page()
    • pfn을 섹션 단위로 바꾼 후 mem_section[][]에 접근하여 섹션에 대한 mam_map[@pfn] 주소를 반환한다.

 

CONFIG_SPARSEMEM & CONFIG_SPARSEMEM_VMEMMAP
/* memmap is virtually contiguous.  */
#define __pfn_to_page(pfn)      (vmemmap + (pfn))
#define __page_to_pfn(page)     (unsigned long)((page) - vmemmap)
  • __pfn_to_page()
    • = mem_map[@pfn]
    • vmemmap = mem_map[0]의 가상 주소가 저장되어 있다.

 


페이지 플래그

include/linux/page-flags.h

/*
 * Various page->flags bits:
 *
 * PG_reserved is set for special pages. The "struct page" of such a page
 * should in general not be touched (e.g. set dirty) except by its owner.
 * Pages marked as PG_reserved include:
 * - Pages part of the kernel image (including vDSO) and similar (e.g. BIOS,
 *   initrd, HW tables)
 * - Pages reserved or allocated early during boot (before the page allocator
 *   was initialized). This includes (depending on the architecture) the
 *   initial vmemmap, initial page tables, crashkernel, elfcorehdr, and much
 *   much more. Once (if ever) freed, PG_reserved is cleared and they will
 *   be given to the page allocator.
 * - Pages falling into physical memory gaps - not IORESOURCE_SYSRAM. Trying
 *   to read/write these pages might end badly. Don't touch!
 * - The zero page(s)
 * - Pages not added to the page allocator when onlining a section because
 *   they were excluded via the online_page_callback() or because they are
 *   PG_hwpoison.
 * - Pages allocated in the context of kexec/kdump (loaded kernel image,
 *   control pages, vmcoreinfo)
 * - MMIO/DMA pages. Some architectures don't allow to ioremap pages that are
 *   not marked PG_reserved (as they might be in use by somebody else who does
 *   not respect the caching strategy).
 * - Pages part of an offline section (struct pages of offline sections should
 *   not be trusted as they will be initialized when first onlined).
 * - MCA pages on ia64
 * - Pages holding CPU notes for POWER Firmware Assisted Dump
 * - Device memory (e.g. PMEM, DAX, HMM)
 * Some PG_reserved pages will be excluded from the hibernation image.
 * PG_reserved does in general not hinder anybody from dumping or swapping
 * and is no longer required for remap_pfn_range(). ioremap might require it.
 * Consequently, PG_reserved for a page mapped into user space can indicate
 * the zero page, the vDSO, MMIO pages or device memory.
 *
 * The PG_private bitflag is set on pagecache pages if they contain filesystem
 * specific data (which is normally at page->private). It can be used by
 * private allocations for its own usage.
 *
 * During initiation of disk I/O, PG_locked is set. This bit is set before I/O
 * and cleared when writeback _starts_ or when read _completes_. PG_writeback
 * is set before writeback starts and cleared when it finishes.
 *
 * PG_locked also pins a page in pagecache, and blocks truncation of the file
 * while it is held.
 *
 * page_waitqueue(page) is a wait queue of all tasks waiting for the page
 * to become unlocked.
 *
 * PG_swapbacked is set when a page uses swap as a backing storage.  This are
 * usually PageAnon or shmem pages but please note that even anonymous pages
 * might lose their PG_swapbacked flag when they simply can be dropped (e.g. as
 * a result of MADV_FREE).
 *
 * PG_uptodate tells whether the page's contents is valid.  When a read
 * completes, the page becomes uptodate, unless a disk I/O error happened.
 *
 * PG_referenced, PG_reclaim are used for page reclaim for anonymous and
 * file-backed pagecache (see mm/vmscan.c).
 *
 * PG_error is set to indicate that an I/O error occurred on this page.
 *
 * PG_arch_1 is an architecture specific page state bit.  The generic code
 * guarantees that this bit is cleared for a page when it first is entered into
 * the page cache.
 *
 * PG_hwpoison indicates that a page got corrupted in hardware and contains
 * data with incorrect ECC bits that triggered a machine check. Accessing is
 * not safe since it may cause another machine check. Don't touch!
 */
/*
 * Don't use the pageflags directly.  Use the PageFoo macros.
 *
 * The page flags field is split into two parts, the main flags area
 * which extends from the low bits upwards, and the fields area which
 * extends from the high bits downwards.
 *
 *  | FIELD | ... | FLAGS |
 *  N-1           ^       0
 *               (NR_PAGEFLAGS)
 *
 * The fields area is reserved for fields mapping zone, node (for NUMA) and
 * SPARSEMEM section (for variants of SPARSEMEM that require section ids like
 * SPARSEMEM_EXTREME with !SPARSEMEM_VMEMMAP).
enum pageflags {
        PG_locked,              /* Page is locked. Don't touch. */
        PG_referenced,
        PG_uptodate,
        PG_dirty,
        PG_lru,
        PG_active,
        PG_workingset,
        PG_waiters,             /* Page has waiters, check its waitqueue. Must be bit #7 and in the same byte as "PG_locked" */
        PG_error,
        PG_slab,
        PG_owner_priv_1,        /* Owner use. If pagecache, fs may use*/
        PG_arch_1,
        PG_reserved,
        PG_private,             /* If pagecache, has fs-private data */
        PG_private_2,           /* If pagecache, has fs aux data */
        PG_writeback,           /* Page is under writeback */
        PG_head,                /* A head page */
        PG_mappedtodisk,        /* Has blocks allocated on-disk */
        PG_reclaim,             /* To be reclaimed asap */
        PG_swapbacked,          /* Page is backed by RAM/swap */
        PG_unevictable,         /* Page is "unevictable"  */
#ifdef CONFIG_MMU
        PG_mlocked,             /* Page is vma mlocked */
#endif
#ifdef CONFIG_ARCH_USES_PG_UNCACHED
        PG_uncached,            /* Page has been mapped as uncached */
#endif
#ifdef CONFIG_MEMORY_FAILURE
        PG_hwpoison,            /* hardware poisoned page. Don't touch */
#endif
#if defined(CONFIG_PAGE_IDLE_FLAG) && defined(CONFIG_64BIT)
        PG_young,
        PG_idle,
#endif
#ifdef CONFIG_64BIT
        PG_arch_2,
#endif
#ifdef CONFIG_KASAN_HW_TAGS
        PG_skip_kasan_poison,
#endif
        __NR_PAGEFLAGS,

        /* Filesystems */
        PG_checked = PG_owner_priv_1,

        /* SwapBacked */
        PG_swapcache = PG_owner_priv_1, /* Swap page: swp_entry_t in private */

        /* Two page bits are conscripted by FS-Cache to maintain local caching
         * state.  These bits are set on pages belonging to the netfs's inodes
         * when those inodes are being locally cached.
         */
        PG_fscache = PG_private_2,      /* page backed by cache */

        /* XEN */
        /* Pinned in Xen as a read-only pagetable page. */
        PG_pinned = PG_owner_priv_1,
        /* Pinned as part of domain save (see xen_mm_pin_all()). */
        PG_savepinned = PG_dirty,
        /* Has a grant mapping of another (foreign) domain's page. */
        PG_foreign = PG_owner_priv_1,
        /* Remapped by swiotlb-xen. */
        PG_xen_remapped = PG_owner_priv_1,

        /* SLOB */
        PG_slob_free = PG_private,

        /* Compound pages. Stored in first tail page's flags */
        PG_double_map = PG_workingset,

#ifdef CONFIG_MEMORY_FAILURE
        /*
         * Compound pages. Stored in first tail page's flags.
         * Indicates that at least one subpage is hwpoisoned in the
         * THP.
         */
        PG_has_hwpoisoned = PG_mappedtodisk,
#endif

        /* non-lru isolated movable page */
        PG_isolated = PG_reclaim,

        /* Only valid for buddy pages. Used to track pages that are reported */
        PG_reported = PG_uptodate,
};

 

page->flags에 기록되는 추가 정보

linux/page-flags-layout.h

/*
 * page->flags layout:
 *
 * There are five possibilities for how page->flags get laid out.  The first
 * pair is for the normal case without sparsemem. The second pair is for
 * sparsemem when there is plenty of space for node and section information.
 * The last is when there is insufficient space in page->flags and a separate
 * lookup is necessary.
 *
 * No sparsemem or sparsemem vmemmap: |       NODE     | ZONE |             ... | FLAGS |
 *      " plus space for last_cpupid: |       NODE     | ZONE | LAST_CPUPID ... | FLAGS |
 * classic sparse with space for node:| SECTION | NODE | ZONE |             ... | FLAGS |
 *      " plus space for last_cpupid: | SECTION | NODE | ZONE | LAST_CPUPID ... | FLAGS |
 * classic sparse no space for node:  | SECTION |     ZONE    | ... | FLAGS |
 */

커널 설정에 따라 page->flags에 플래그들 이외에 SECTION, NODE, ZONE 및 LAST_CPUPID 정보 등이 기록된다.

 


Reserved 플래그(예)

PageReserved(), SetPageReserved(), ClearPageReserved(), __ClearPageReserved()

include/linux/page-flags.h

PAGEFLAG(Reserved, reserved) __CLEARPAGEFLAG(Reserved, reserved)
  • PageReserved(), SetPageReserved(), ClearPageReserved() 및 __ClearPageReserved() static inline 함수가 만들어진다.

 

#define PAGEFLAG(uname, lname) TESTPAGEFLAG(uname, lname)               \
        SETPAGEFLAG(uname, lname) CLEARPAGEFLAG(uname, lname)
  • 아래 매크로를 사용하여 PageXXX(), SetPageXXX() 및 ClearPageXXX() static inline 함수가 만들어진다.

 

/*
 * Macros to create function definitions for page flags
 */
#define TESTPAGEFLAG(uname, lname)                                      \
static inline int Page##uname(const struct page *page)                  \
                        { return test_bit(PG_##lname, &page->flags); }

#define SETPAGEFLAG(uname, lname)                                       \
static inline void SetPage##uname(struct page *page)                    \
                        { set_bit(PG_##lname, &page->flags); }

#define CLEARPAGEFLAG(uname, lname)                                     \
static inline void ClearPage##uname(struct page *page)                  \
                        { clear_bit(PG_##lname, &page->flags); }

 

#define __CLEARPAGEFLAG(uname, lname)                                   \
static inline void __ClearPage##uname(struct page *page)                \
                        { __clear_bit(PG_##lname, &page->flags); }
  • test_bit()
    • &page->flags의 PG_xxxxx 번호 비트가 set되었는지 여부를 알아온다.
  • set_bit()
    • &page->flags의 PG_xxxxx 번호 비트를 atomic하게 set 한다.
  • clear_bit()
    • &page->flags의 PG_xxxxx 번호 비트를 atomic하게 clear 한다.
  • __clear_bit()
    • &page->flags의 PG_xxxxx 번호 비트를 clear 한다. (non-atomic)

 


일부 플래그의 재편성 (p->page_type)

아래 4개의 PG_buddy, PG_ballon, PG_kmemcg, PG_table 플래그는 p->_mapcount와 같이 사용하는 것으로 바뀌었고, 다시 유니언 선언하여 공유된 p->page_type을 사용한다.

  • 최초 p->flags에서 관리되던 플래그들이 p->_mapcount로 분리되었었다.
  •  후 새 커널에서는 p->_mapcount 대신 유니온으로 공유된 p->page_type를 사용한다. 단  p->_mapcount의 초기 값이 -1(0xffff_ffff)이므로 비트의 설정과 해제는 반대로 사용한다.
    • 예) Set Buddy
      • old 커널: p->_mapcount = PAGE_BUDDY_MAPCOUNT_VALUE(-128)
      • new 커널: p->page_type &= ~0x80
    • 예) Clear Buddy
      • p->_mapcount = PAGE_BUDDY_MAPCOUNT_VALUE(-1)
      • new 커널: p->page_type |= 0x80
    • 참고: mm: split page_type out from _mapcount

 

include/linux/page-flags.h

/*
 * PageBuddy() indicates that the page is free and in the buddy system
 * (see mm/page_alloc.c).
 */
PAGE_TYPE_OPS(Buddy, buddy)

/*
 * PageBalloon() is true for pages that are on the balloon page list
 * (see mm/balloon_compaction.c).
 */
PAGE_TYPE_OPS(Balloon, balloon)

/*
 * If kmemcg is enabled, the buddy allocator will set PageKmemcg() on
 * pages allocated with __GFP_ACCOUNT. It gets cleared on page free.
 */
PAGE_TYPE_OPS(Kmemcg, kmemcg)

/*
 * Marks pages in use as page tables.
 */
PAGE_TYPE_OPS(Table, table)

 

/*
 * For pages that are never mapped to userspace (and aren't PageSlab),
 * page_type may be used.  Because it is initialised to -1, we invert the
 * sense of the bit, so __SetPageFoo *clears* the bit used for PageFoo, and
 * __ClearPageFoo *sets* the bit used for PageFoo.  We reserve a few high and
 * low bits so that an underflow or overflow of page_mapcount() won't be
 * mistaken for a page type value.
 */
#define PAGE_TYPE_BASE  0xf0000000
/* Reserve              0x0000007f to catch underflows of page_mapcount */
#define PAGE_MAPCOUNT_RESERVE   -128
#define PG_buddy        0x00000080
#define PG_balloon      0x00000100
#define PG_kmemcg       0x00000200
#define PG_table        0x00000400

#define PageType(page, flag)                                            \
        ((page->page_type & (PAGE_TYPE_BASE | flag)) == PAGE_TYPE_BASE)

static inline int page_has_type(struct page *page)
{
        return (int)page->page_type < PAGE_MAPCOUNT_RESERVE;
}

#define PAGE_TYPE_OPS(uname, lname)                                     \
static __always_inline int Page##uname(struct page *page)               \
{                                                                       \
        return PageType(page, PG_##lname);                              \
}                                                                       \
static __always_inline void __SetPage##uname(struct page *page)         \
{                                                                       \
        VM_BUG_ON_PAGE(!PageType(page, 0), page);                       \
        page->page_type &= ~PG_##lname;                                 \
}                                                                       \
static __always_inline void __ClearPage##uname(struct page *page)       \
{                                                                       \
        VM_BUG_ON_PAGE(!Page##uname(page), page);                       \
        page->page_type |= PG_##lname;                                  \
}

위의 매크로를 통해 PageBuddy(), __SetPageBuddy(), __ClearPageBuddy() 등의 인라인 함수가 생성된다.

 

 


페이지 블럭 관련

set_pageblock_flags_group()

linux/pageblock-flags.h

#define set_pageblock_flags_group(page, flags, start_bitidx, end_bitidx) \
        set_pfnblock_flags_mask(page, flags, page_to_pfn(page),         \
                        end_bitidx,                                     \
                        (1 << (end_bitidx - start_bitidx + 1)) - 1)

 

set_pfnblock_flags_mask()

mm/page_alloc.c

/**
 * set_pfnblock_flags_mask - Set the requested group of flags for a pageblock_nr_pages block of pages
 * @page: The page within the block of interest
 * @flags: The flags to set
 * @pfn: The target page frame number
 * @end_bitidx: The last bit of interest
 * @mask: mask of bits that the caller is interested in
 */
void set_pfnblock_flags_mask(struct page *page, unsigned long flags,
                                        unsigned long pfn,
                                        unsigned long end_bitidx,
                                        unsigned long mask)
{
        struct zone *zone;      
        unsigned long *bitmap;
        unsigned long bitidx, word_bitidx;
        unsigned long old_word, word;

        BUILD_BUG_ON(NR_PAGEBLOCK_BITS != 4);

        zone = page_zone(page);
        bitmap = get_pageblock_bitmap(zone, pfn);
        bitidx = pfn_to_bitidx(zone, pfn);
        word_bitidx = bitidx / BITS_PER_LONG;
        bitidx &= (BITS_PER_LONG-1);

        VM_BUG_ON_PAGE(!zone_spans_pfn(zone, pfn), page);

        bitidx += end_bitidx;
        mask <<= (BITS_PER_LONG - bitidx - 1);
        flags <<= (BITS_PER_LONG - bitidx - 1); 

        word = ACCESS_ONCE(bitmap[word_bitidx]);
        for (;;) {
                old_word = cmpxchg(&bitmap[word_bitidx], word, (word & ~mask) | flags);
                if (word == old_word)
                        break;
                word = old_word;
        }            
}

 

get_pfnblock_flags_mask()

mm/page_alloc.c

/**
 * get_pfnblock_flags_mask - Return the requested group of flags for the pageblock_nr_pages block of pages              
 * @page: The page within the block of interest
 * @pfn: The target page frame number
 * @end_bitidx: The last bit of interest to retrieve
 * @mask: mask of bits that the caller is interested in
 *              
 * Return: pageblock_bits flags
 */
unsigned long get_pfnblock_flags_mask(struct page *page, unsigned long pfn,
                                        unsigned long end_bitidx,
                                        unsigned long mask)
{
        struct zone *zone;
        unsigned long *bitmap;
        unsigned long bitidx, word_bitidx;
        unsigned long word;

        zone = page_zone(page);
        bitmap = get_pageblock_bitmap(zone, pfn);
        bitidx = pfn_to_bitidx(zone, pfn);
        word_bitidx = bitidx / BITS_PER_LONG;
        bitidx &= (BITS_PER_LONG-1);

        word = bitmap[word_bitidx];
        bitidx += end_bitidx;
        return (word >> (BITS_PER_LONG - bitidx - 1)) & mask;
}

 

get_pageblock_bitmap()

mm/page_alloc.c

/* Return a pointer to the bitmap storing bits affecting a block of pages */
static inline unsigned long *get_pageblock_bitmap(struct zone *zone,
                                                        unsigned long pfn)
{
#ifdef CONFIG_SPARSEMEM
        return __pfn_to_section(pfn)->pageblock_flags;
#else
        return zone->pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
}

@pfn이 포함된 페이지 블럭 비트맵을 반환한다. (usemap)

  • usemap에는 4비트로 표현된 mobility 플래그들이 저장된다.

 

pfn_to_bitidx()

mm/page_alloc.c

static inline int pfn_to_bitidx(struct zone *zone, unsigned long pfn)
{
#ifdef CONFIG_SPARSEMEM
        pfn &= (PAGES_PER_SECTION-1);
        return (pfn >> pageblock_order) * NR_PAGEBLOCK_BITS;
#else
        pfn = pfn - round_down(zone->zone_start_pfn, pageblock_nr_pages);
        return (pfn >> pageblock_order) * NR_PAGEBLOCK_BITS;
#endif /* CONFIG_SPARSEMEM */
}

pfn에 대한 pageblock에서 비트 인덱스를 반환한다.

 

SECTION_BLOCKFLAGS_BITS

include/linux/mmzone.h

#define SECTION_BLOCKFLAGS_BITS \
        ((1UL << (PFN_SECTION_SHIFT - pageblock_order)) * NR_PAGEBLOCK_BITS)

섹션 당 pageblock 비트 수

  • NR_PAGEBLOCK_BITS
    • pageblock에 대해 필요한 비트 수=4
  • PFN_SECTION_SHIFT
    • 섹션 길이 표현에 필요한 비트 수 – 페이지 길이 표현에 필요한 비트 수를 뺀 값
      • arm64: 섹션 길이=27(128M 표현) bits – 12(4KB 표현) bits = 15
  • 예) arm64에서 섹션 크기=128M, pageblock_order=9인 경우
    • SECTION_BLOCKFLAGS_BITS  = 2^(15-9) * 4 bits = 256 bits

 

참고

 

Memory Model -3- (Sparse Memory)

<kernel v5.15>

Sparse memory 모델은 섹션 메모리 단위로 mem_section 구조체를 통해 다음과 같은 자료를 관리한다.

  • mem_map
  • usemap (페이지블럭)

 

섹션

Sparse memory 모델에서 섹션은 메모리의 online/offlline(hotplug memory)을 관리하는 최소 메모리 크기 단위이다. 전체 메모리를 섹션들로 나누어 사용하는데 적절한 섹션 사이즈로 나누어 사용하는데 아키텍처 마다 다르다.

  • 보통 섹션 크기는 수십MB~수GB를 사용한다.
    • arm64의 경우 디폴트 값으로 1G를 사용다가 최근 128M로 변경하였다.
    • 주의: 본문 그림 등에서는 섹션 크기를 1G로 사용한 예를 보여주고 있음.
    • 주의: 매핑 테이블에서 사용하는 섹션(2M) 용어와 다름.

 

섹션 배열 2 가지 관리 방법

섹션에서 mem_map을 관리하는 다음 두 가지 방법을 알아본다.

  • static
    • 컴파일 타임에 1단계 mem_section[] 배열을 결정하여 사용하는 방법이다.
    • 주로 32bit 시스템에서 섹션 수가 적을 때 사용한다.
  • extream
    • 런타임에 1단계 **mem_section[] 포인터 배열을 할당하고, 2 단계는 필요 시마다 mem_section[] 배열을 할당하여 사용하는 방법으로 메모리 낭비를 막는 방법이다.
    • 주로 64bit 시스템에서 섹션 수가 많을 때 사용한다.

 

CONFIG_SPARSEMEM_STATIC

32bit ARM에서 Sparse Memory를 사용하는 Realview-PBX 보드가 섹션당 256MB 크기로 구성된 사례를 사용한다.

  • Realview-PBX
    • 3개의 메모리
      • 256MB @ 0x00000000 -> PAGE_OFFSET
      • 512MB @ 0x20000000 -> PAGE_OFFSET + 0x10000000
      • 256MB @ 0x80000000 -> PAGE_OFFSET + 0x30000000
    • MAX_PHYSMEM_BITS=32 (4G 메모리 크기)
    • SECTION_SIZE_BITS=28 (256MB 섹션 크기)
    • PFN_SECTION_SHIFT=(SECTION_SIZE_BITS – PAGE_SHIFT)=16
    • SECTIONS_PER_ROOT=1
    • SECTIONS_SHIFT=(MAX_PHYSMEM_BITS – SECTION_SIZE_BITS)=4
    • NR_MEM_SECTIONS=2^SECTIONS_SHIFT=16
    • PAGES_PER_SECTION=2^PFN_SECTION_SHIFT=64K
    • PAGE_SECTION_MASK=(~(PAGES_PER_SECTION-1))=0xffff_0000

 

CONFIG_SPARSEMEM_EXTREME

64bit ARM에서 Sparse Memory를 사용하는 경우의 사례로 1GB 크기로 구성된 사례를 사용한다.

  • arm64
    • 2개의 메모리
      • 2GB @ 0x0_8000_0000
      • 2GB @ 0x8_0000_0000
  • MAX_PHYSMEM_BITS=48 (256 TB 메모리 크기)
  • SECTION_SIZE_BITS=30 (1GB 섹션 크기)
  • PFN_SECTION_SHIFT=(SECTION_SIZE_BITS – PAGE_SHIFT)=18
  • SECTIONS_PER_ROOT=(PAGE_SIZE / sizeof (struct mem_section))=256
  • SECTIONS_SHIFT=(MAX_PHYSMEM_BITS – SECTION_SIZE_BITS)=18
  • NR_MEM_SECTIONS=2^SECTIONS_SHIFT=256K
  • PAGES_PER_SECTION=2^PFN_SECTION_SHIFT=256K
  • PAGE_SECTION_MASK=(~(PAGES_PER_SECTION-1))=0xffff_ffff_fffc_0000

 

usemap

usemap은 메모리 회수 매커니즘 중 하나인 compaction에서 전체 메모리를 스캔할 때 사용되는 페이지 블럭 당 4비트로 구성된다.

  • compaction에서 빠른 스캔을 위해 블럭 단위(arm64 디폴트=2M)로 묶어 사용한다.
  • 페이지 블럭 당 사용되는 4비트는 3비트의 mobility(migratype) 속성과 1 비트의 skip 비트로 구성된다.

 

VMEMMAP

32bit arm 에서는 사용하지 않는다. CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용할 수 있는 arm64를 포함하는 일부 64비트 아키텍처에서 Flat Memory 모델처럼 빠르게 운용할 수 있다.

  • 64비트 시스템에서 vmemmap 매핑 공간이 별도로 구성되어 있어 그 영역에 section_mem_map으로 구성된 mem_map들을 매핑한다.
  • vmemmap을 사용하는 경우 노드별 메모리에 분산되어 있는 페이지 descriptor로 serial하게 접근할 수 있어 page_to_pfn() 또는 pfn_to_page() 함수의 성능을 빠르게 얻을 수 있다.

 

다음 그림은 vmemmap이 VMEMMAP_START 아래의 PFN #0 위치에 대응하는 page 구조체의 위치를 가리키는 예를 보여준다.

  • VMEMMAP_START는 실제 DRAM이 위치한 PFN에 대한 page 구조체를 가리키고,
  • vmemmap은 PFN#0에 대한 page 구조체를 가리킨다.
    • 주의: DRAM 시작 주소가 0이 아니면 vmemmap은 VMEMMAP_START 아래에 위치하게 된다. 이 때 아래에 위치한 vmemmap의 주소는 실제 mem_map이 매핑된 공간이 아니므로 커널이 이 공간을 액세스하면 fault 발생하여 시스템이 멈춘다.)

 


Sparse 메모리 모델 초기화

다음 순서도는 Sparse 메모리 초기화에 대한 로직으로 다음과 같은 일들을 한다.

  • 노드별 usemap을 할당하고 임시로 사용되는 usemap_map에 연결한다.
  • CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용하면 mem_map을 vmemmap에 매핑하여 사용한다.
  • CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용하지 않는 경우 노드마다 활성화된 섹션들의 mem_map을 가능하면 한꺼번에 할당하다. (이전 버전의 x86 커널에 적용했었던 CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 커널 옵션과 유사)

 

다음 그림은 sparse_init() 에서 만들어지는 mem_map, usemap, mem_section[] 들을 보여준다.

 

sparse_init()

mm/sparse.c

/*
 * Allocate the accumulated non-linear sections, allocate a mem_map
 * for each and record the physical to section mapping.
 */
void __init sparse_init(void)
{
        unsigned long pnum_end, pnum_begin, map_count = 1;
        int nid_begin;

        memblocks_present();

        pnum_begin = first_present_section_nr();
        nid_begin = sparse_early_nid(__nr_to_section(pnum_begin));

        /* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */
        set_pageblock_order();

        for_each_present_section_nr(pnum_begin + 1, pnum_end) {
                int nid = sparse_early_nid(__nr_to_section(pnum_end));

                if (nid == nid_begin) {
                        map_count++;
                        continue;
                }
                /* Init node with sections in range [pnum_begin, pnum_end) */
                sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
                nid_begin = nid;
                pnum_begin = pnum_end;
                map_count = 1;
        }
        /* cover the last node */
        sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
        vmemmap_populate_print_last();
}

sparse 물리 메모리 모델을 사용하기 위해 페이지 디스크립터들이 집합되어 있는 mem_map들을 섹션 단위로 할당하고 관리하기 위해 초기화하는 과정을 알아본다.
분산된 메모리를 섹션 단위로 관리하기 위해 각 섹션이 mem_map과 usemap을 할당하여 관리할 수 있도록 한다. usemap은 페이지 블록 단위를 결정한 후 페이지 블록당 4비트의 비트맵으로 mobility 특성을 관리한다.

  • 코드 라인 6에서 memblock에 등록된 메모리들을 모두 mem_section 연결하고, 섹션별로 활성화시킨다.
  • 코드 라인 8~9에서 시작 섹션 번호와 이에 대한 nid를 알아온다.
  • 코드 라인 12에서 전체 메모리에 대해 페이지 블록 크기 단위로 4비트의 mobility 특성을 기록하고 관리하기 위해 먼저 전역 변수 pageblock_order를 설정한다.
    • ARM64 시스템의 디폴트 설정은 pageblock_order 값에 9을 사용한다.
  • 코드 라인 14~20에서 present 섹션을 순회하며 동일 노드인 경우 map_count를 증가시켜 mem_map등을 한꺼번에 같은 노드에서 할당 받게 한다.
    • present 섹션을 순회 중일 때 pnum_end에는 순회중인 present 섹션 번호가 담긴다.
  • 코드 라인 22~25에서 바뀐 노드인 경우 해당 present  범위의 섹션들을 초기화한다.
  • 코드 라인 28에서 위의 루프에서 처리하지 않은 마지막 남은 노드에 대해서도 해당 present 범위의 섹션들을 초기화한다.
  • 코드 라인 29에서 arm과 arm64에서는 구현되지 않아 출력하는 메시지가 없다.

 


메모리 활성화

memblocks_present()

mm/sparse.c

/*
 * Mark all memblocks as present using memory_present().
 * This is a convenience function that is useful to mark all of the systems
 * memory as present during initialization.
 */
static void __init memblocks_present(void)
{
        unsigned long start, end;
        int i, nid;

        for_each_mem_pfn_range(i, MAX_NUMNODES, &start, &end, &nid)
                memory_present(nid, start, end);
}

memblock에 등록된 메모리들을 순회하며 mem_section 연결하고, 섹션별로 활성화시킨다.

 

memory_present()

mm/sparse.c

/* Record a memory area against a node. */
static void __init memory_present(int nid, unsigned long start, unsigned long end)
{
        unsigned long pfn;

#ifdef CONFIG_SPARSEMEM_EXTREME
        if (unlikely(!mem_section)) {
                unsigned long size, align;

                size = sizeof(struct mem_section*) * NR_SECTION_ROOTS;
                align = 1 << (INTERNODE_CACHE_SHIFT);
                mem_section = memblock_alloc(size, align);
                if (!mem_section)
                        panic("%s: Failed to allocate %lu bytes align=0x%lx\n",
                              __func__, size, align);
        }
#endif

        start &= PAGE_SECTION_MASK;
        mminit_validate_memmodel_limits(&start, &end);
        for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) {
                unsigned long section = pfn_to_section_nr(pfn);
                struct mem_section *ms;

                sparse_index_init(section, nid);
                set_section_nid(section, nid);

                ms = __nr_to_section(section);
                if (!ms->section_mem_map) {
                        ms->section_mem_map = sparse_encode_early_nid(nid) |
                                                        SECTION_IS_ONLINE;
                        __section_mark_present(ms, section);
                }
        }
}

memory_present() 함수는 CONFIG_HAVE_MEMORY_PRESENT 커널 옵션을 사용하는 경우에만 동작하며 각 섹션에 노드 id를 기록하는데, 자세한 내용은 코드를 보고 알아보기로 한다.

  • 코드 라인 6~15에서 CONFIG_SPARSEMEM_EXTREME 커널 옵션을 사용하는 경우 처음 mem_section이 초기화되지 않은 경우 mem_section[] 배열을 생성한다.
    • arm64 예) 4K 페이지, sizeof(struct mem_section)=16
      • NR_SECTION_ROOTS=1024
  • 코드 라인 18~19에서 요청한 범위의 시작 pfn을 섹션 단위로 내림 정렬한 주소로 변환한 후 해당 범위의 pfn이 실제로 지정할 수 있는 물리 메모리 범위에 포함되는지 검증하고 초과 시 그 주소를 강제로 제한한다.
  • 코드 라인 20~21에서 시작 pfn부터 섹션 단위로 증가시키며 이 값으로 section 번호를 구한다.
  • 코드 라인 24에서 CONFIG_SPARSEMEM_EXTREME 커널 옵션을 사용하는 경우에만 동작하며, 이 함수에서 해당 섹션에 대한 mem_section[]을 동적으로(dynamic) 할당받는다. CONFIG_SPARSEMEM_STATIC 커널 옵션을 사용하는 경우에는 이미 정적(static) 배열이 준비되어 있으므로 아무런 동작도 요구되지 않는다.
  • 코드 라인 25에서 NODE_NOT_IN_PAGE_FLAGS가 정의된 경우 별도의 전역 section_to_node_table[] 배열에 해당 섹션을 인덱스로 해당 노드 id를 가리키게 한다.
    • NODE_NOT_IN_PAGE_FLAGS 커널 옵션은 page 구조체의 flags 필드에 노드 번호를 저장할 비트가 부족한 32비트 아키텍처에서 사용되는 옵션이다.
  • 코드 라인 27~32에서 해당 섹션의 mem_section 구조체내의 section_mem_map에 노드 id와 online 및 present 플래그를 설정한다.

 

아래 그림은 memory_present() 함수가 호출되는 과정에서 mem_section[] 배열이 할당되는 것을 나타낸다.

  • 붉은 색 박스는 sparse_index_alloc() 함수에 의해 dynamic 하게 메모리 할당을 받는 것을 의미한다.
  • 푸른 색 박스는 컴파일 타임에 static하게 배열이 할당됨을 의미한다.

memory_present-1

 

mminit_validate_memmodel_limits()

mm/sparse.c

/* Validate the physical addressing limitations of the model */
void __meminit mminit_validate_memmodel_limits(unsigned long *start_pfn,
                                                unsigned long *end_pfn)
{
        unsigned long max_sparsemem_pfn = 1UL << (MAX_PHYSMEM_BITS-PAGE_SHIFT);

        /*
         * Sanity checks - do not allow an architecture to pass
         * in larger pfns than the maximum scope of sparsemem:
         */
        if (*start_pfn > max_sparsemem_pfn) {
                mminit_dprintk(MMINIT_WARNING, "pfnvalidation",
                        "Start of range %lu -> %lu exceeds SPARSEMEM max %lu\n",
                        *start_pfn, *end_pfn, max_sparsemem_pfn);
                WARN_ON_ONCE(1);
                *start_pfn = max_sparsemem_pfn;
                *end_pfn = max_sparsemem_pfn;
        } else if (*end_pfn > max_sparsemem_pfn) {
                mminit_dprintk(MMINIT_WARNING, "pfnvalidation",
                        "End of range %lu -> %lu exceeds SPARSEMEM max %lu\n",
                        *start_pfn, *end_pfn, max_sparsemem_pfn);
                WARN_ON_ONCE(1);
                *end_pfn = max_sparsemem_pfn;
        }   
}

인자로 사용된 시작 pfn, 끝 pfn 값이 물리 메모리 주소 최대 pfn 값을 초과하지 않도록 제한한다.

  • 코드 라인 10~16에서 시작 pfn이 max_sparsemem_pfn보다 크면 경고를 출력하고, start_pfn과 end_pfn에 max_sparsemem_pfn을 설정한다.
  • 코드 라인 17~23에서 끝 pfn이 max_sparsemem_pfn보다 크면 경고를 출력하고, end_pfn에 max_sparsemem_pfn을 설정한다.

 

섹션 인덱스 초기화

sparse_index_init()

mm/sparse.c

#ifdef CONFIG_SPARSEMEM_EXTREME
static int __meminit sparse_index_init(unsigned long section_nr, int nid)
{
        unsigned long root = SECTION_NR_TO_ROOT(section_nr);
        struct mem_section *section;

        /*
         * An existing section is possible in the sub-section hotplug
         * case. First hot-add instantiates, follow-on hot-add reuses
         * the existing section.
         *
         * The mem_hotplug_lock resolves the apparent race below.
         */
        if (mem_section[root])
                return 0;

        section = sparse_index_alloc(nid);
        if (!section)
                return -ENOMEM;

        mem_section[root] = section;

        return 0;
}
#endif

CONFIG_SPARSEMEM_EXTREME 커널 옵션을 사용하는 경우 dynamic하게 mem_section 테이블을 할당 받아 구성한다.

  • 코드 라인 4에서 섹션 번호로 루트 번호를 구한다.
  • 코드 라인 14~15에서 해당 루트 인덱스의 루트 섹션에 값이 존재하는 경우 이미 mem_section[] 테이블이 구성되었으므로 함수를 빠져나간다.
  • 코드 라인 17~19에서 해당 노드에서 2 단계용 섹션 테이블을 할당받아 구성한다. 핫플러그 메모리를 위해 각 mem_section[] 테이블은 해당 노드에 위치해야 한다.
  • 코드 라인 21에서 루트 번호에 해당하는 1단계 mem_section[] 포인터 배열에 새로 할당받은 2 단계 mem_section[] 테이블의 시작 주소를 설정한다.
  • 코드 라인 23에서 정상 결과 0을 반환한다.

 

sparse_index_alloc()

mm/sparse.c

#ifdef CONFIG_SPARSEMEM_EXTREME
static noinline struct mem_section __ref *sparse_index_alloc(int nid)
{
        struct mem_section *section = NULL;
        unsigned long array_size = SECTIONS_PER_ROOT *
                                   sizeof(struct mem_section);

        if (slab_is_available()) {
                        section = kzalloc_node(array_size, GFP_KERNEL, nid);
        } else {
                section = memblock_alloc_node(array_size, SMP_CACHE_BYTES,
                                              nid);
                if (!section)
                        panic("%s: Failed to allocate %lu bytes nid=%d\n",
                              __func__, array_size, nid);
        }

        return section;
}
#endif

CONFIG_SPARSEMEM_EXTREME 커널 옵션을 사용하는 경우 mem_section[] 테이블용 메모리를 할당 받는다.

  • 코드 라인 5~6에서 루트 엔트리는 1개의 페이지로 구성되며, 가득 구성될 mem_section 구조체 배열의 크기를 구한다. SECTIONS_PER_ROOT는 루트 엔트리당 mem_section 수를 의미한다. 이 상수는 다음과 같이 정의되어 있다.
    • #define SECTIONS_PER_ROOT (PAGE_SIZE / sizeof(struct mem_section))
      • 예) arm64: 4K / 16 bytes = 256개
  • 코드 라인 8~9에서 슬랩 메모리 할당자가 동작하는 경우 kzalloc( ) 함수를 통해 메모리를 할당한다.
  • 코드 라인 10~16에서 슬랩 메모리 할당자가 동작하지 않는 경우 해당 노드의 memblock에 할당한다
  • 코드 라인 18에서 할당 받은 mem_section 배열을 반환한다.

 

set_section_nid()

mm/sparse.c

#ifdef NODE_NOT_IN_PAGE_FLAGS
static void set_section_nid(unsigned long section_nr, int nid)
{
        section_to_node_table[section_nr] = nid;
}
#else /* !NODE_NOT_IN_PAGE_FLAGS */
static inline void set_section_nid(unsigned long section_nr, int nid)
{
}
#endif

NODE_NOT_IN_PAGE_FLAGS  옵션을 사용하는 경우 section_to_node_table[]에 섹션 번호에 1:1로 대응하는 노드 번호를 저장한다.

  • page 구조체 멤버 변수 flags에 노드 번호를 저장할 비트가 부족한 32비트 아키텍처에서 사용되는 옵션이다.

 

아래 그림은 set_section_nid() 함수를 통해 주어진 섹션 번호에 노드 번호를 저장한다.

set_section_nid-1

 

__nr_to_section()

mm/sparse.c

static inline struct mem_section *__nr_to_section(unsigned long nr)
{
#ifdef CONFIG_SPARSEMEM_EXTREME
        if (!mem_section)
                return NULL;
#endif
        if (!mem_section[SECTION_NR_TO_ROOT(nr)])
                return NULL;
        return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
}

섹션 번호에 해당하는 mem_section 구조체 정보를 알아온다.

 

아래 그림은 __nr_to_section() 함수를 사용하여 섹션 번호로 mem_section 구조체 정보를 알아오는 단계를 2 가지 예로 나타내었다.

__nr_to_section-1

 

__section_mark_present()

mm/sparse.c

/*
 * There are a number of times that we loop over NR_MEM_SECTIONS,
 * looking for section_present() on each.  But, when we have very
 * large physical address spaces, NR_MEM_SECTIONS can also be
 * very large which makes the loops quite long.
 *
 * Keeping track of this gives us an easy way to break out of
 * those loops early.
 */
unsigned long __highest_present_section_nr;
static void __section_mark_present(struct mem_section *ms,
                unsigned long section_nr)
{
        if (section_nr > __highest_present_section_nr)
                __highest_present_section_nr = section_nr;

        ms->section_mem_map |= SECTION_MARKED_PRESENT;
}

섹션 메모리가 존재함을 표시한다.

  • 코드 라인 5~6에서 전역 변수 __highest_present_section_nr를 가장 높은 섹션 번호로 갱신한다.
  • 코드 라인 8에서 mem_section을 가리키는 ms->section_mem_map에 SECTION_MARKED_PRESENT 플래그 비트를 추가한다.

 

for_each_present_section_nr()

mm/sparse.c

#define for_each_present_section_nr(start, section_nr)          \
        for (section_nr = next_present_section_nr(start-1);     \
             ((section_nr >= 0) &&                              \
              (section_nr <= __highest_present_section_nr));    \
             section_nr = next_present_section_nr(section_nr))

@start 섹션을 포함하여 마지막 present 섹션까지 present 섹션마다 순회한다. @section_nr는 출력 인자로 present 섹션 번호이다.

 

next_present_section_nr()

mm/sparse.c

static inline int next_present_section_nr(int section_nr)
{
        do {
                section_nr++;
                if (present_section_nr(section_nr))
                        return section_nr;
        } while ((section_nr <= __highest_present_section_nr));

        return -1;
}

요청한 @section_nr 다음 섹션번호부터 마지막 섹션까지 순회하며 present 섹션을 발견하면 해당 섹션 번호를 반환한다.

 


pageblock order 설정

set_pageblock_order()

mm/page_alloc.c

#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE

/* Initialise the number of pages represented by NR_PAGEBLOCK_BITS */
void __init set_pageblock_order(void)
{
        unsigned int order;

        /* Check that pageblock_nr_pages has not already been setup */
        if (pageblock_order)
                return;

        if (HPAGE_SHIFT > PAGE_SHIFT)
                order = HUGETLB_PAGE_ORDER;
        else
                order = MAX_ORDER - 1;

        /*
         * Assume the largest contiguous order of interest is a huge page.
         * This value may be variable depending on boot parameters on IA64 and
         * powerpc.
         */
        pageblock_order = order;
}
#else /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

/*
 * When CONFIG_HUGETLB_PAGE_SIZE_VARIABLE is not set, set_pageblock_order()
 * is unused as pageblock_order is set at compile-time. See
 * include/linux/pageblock-flags.h for the values of pageblock_order based on
 * the kernel config
 */
void __init set_pageblock_order(void)
{
}

#endif /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

CONFIG_HUGETLB_PAGE_SIZE_VARIABLE 커널 옵션을 사용하는 경우에만 런타임에 pageblock_order를 설정한다.

 

pageblock_order

include/linux/pageblock-flags.h

#ifdef CONFIG_HUGETLB_PAGE

#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE

/* Huge page sizes are variable */
extern unsigned int pageblock_order;

#else /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

/* Huge pages are a constant size */
#define pageblock_order         HUGETLB_PAGE_ORDER

#endif /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

#else /* CONFIG_HUGETLB_PAGE */

/* If huge pages are not used, group by MAX_ORDER_NR_PAGES */
#define pageblock_order         (MAX_ORDER-1)

#endif /* CONFIG_HUGETLB_PAGE */

페이지 블럭 오더 크기는 CONFIG_HUGETLB_PAGE 및 CONFIG_HUGETLB_PAGE_SIZE_VARIABLE 커널 옵션에 사용 유무에 따라 결정된다.

  • CONFIG_HUGETLB_PAGE 사용 시
    • CONFIG_HUGETLB_PAGE_SIZE_VARIABLE  커널 옵션 사용 시
      • pageblock_order를 런타임에 set_pageblock_order() 함수에서 결정한다.
    • CONFIG_HUGETLB_PAGE_SIZE_VARIABLE  커널 옵션 미 사용 시
      • 컴파일 타임에 HUGETLB_PAGE_ORDER 값으로 결정된다.
        • =HPAGE_SHIFT(21) – PAGE_SHIFT(12)=9 (arm64 디폴트 설정 사용 시)
  • CONFIG_HUGETLB_PAGE 커널 옵션을 사용하지 않을 경우
    • 컴파일 타임에 MAX_ORDER-1 값으로 결정된다.

 


노드별 sparse 초기화

sparse_init_nid()

mm/sparse.c

/*
 * Initialize sparse on a specific node. The node spans [pnum_begin, pnum_end)
 * And number of present sections in this node is map_count.
 */
static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
                                   unsigned long pnum_end,
                                   unsigned long map_count)
{
        struct mem_section_usage *usage;
        unsigned long pnum;
        struct page *map;

        usage = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nid),
                        mem_section_usage_size() * map_count);
        if (!usage) {
                pr_err("%s: node[%d] usemap allocation failed", __func__, nid);
                goto failed;
        }
        sparse_buffer_init(map_count * section_map_size(), nid);
        for_each_present_section_nr(pnum_begin, pnum) {
                unsigned long pfn = section_nr_to_pfn(pnum);

                if (pnum >= pnum_end)
                        break;

                map = __populate_section_memmap(pfn, PAGES_PER_SECTION,
                                nid, NULL);
                if (!map) {
                        pr_err("%s: node[%d] memory map backing failed. Some memory will not be available.",
                               __func__, nid);
                        pnum_begin = pnum;
                        sparse_buffer_fini();
                        goto failed;
                }
                check_usemap_section_nr(nid, usage);
                sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,
                                SECTION_IS_EARLY);
                usage = (void *) usage + mem_section_usage_size();
        }
        sparse_buffer_fini();
        return;
failed:
        /* We failed to allocate, mark all the following pnums as not present */
        for_each_present_section_nr(pnum_begin, pnum) {
                struct mem_section *ms;

                if (pnum >= pnum_end)
                        break;
                ms = __nr_to_section(pnum);
                ms->section_mem_map = 0;
        }
}

@nid 노드에 해당하는 @pnum_begin ~ @pnum_end 미만 까지의 섹션 범위들에 대해 @map_count 만큼의 present 섹션과 관련된 mem_map 및 usemap을 할당하고 초기화한다.

  • 코드 라인 9~14에서 @map_count 섹션 수 만큼의 usemap[] 배열을 노드 @nid에서 한꺼번에 할당한다.
  • 코드 라인 15에서 @map_count 섹션 수 만큼의 sparse 버퍼를 노드 @nid에서 한꺼번에 할당하여 준비해 놓는다.
  • 코드 라인 16~30에서 @pnum_begin 섹션 부터 끝 present 섹션까지 순회하며 해당 섹션의 mem_map을 할당받는다.
    • 가능하면 sparse 버퍼에서 할당받는다.
  • 코드 라인 31에서 할당받은 usemap과 노드 정보(pgdat)가 기록된 섹션과 같지 않으면 경고 메시지를 출력한다. usemap 정보가 같은 노드 공간에 구성되지 않으면 메모리 핫리무브(hot remove) 동작 시 노드별로 메모리를 제거해야 할 때 circular dependancy 문제가 발생할 수 있다.
  • 코드 라인 32~33에서 해당 섹션 하나를 초기화한다.
  • 코드 라인 36에서 할당하고 남은 sparse 버퍼를 할당 해제한다.

브라켓 표현법

begin이 1이고 end가 5라고 할 때

  • [begin, end]
    • begin 이상 ~ end 이하
    • 1~5
  • (begin, end)
    • bigin 초과 ~ end 미만
    • 2~4
  • [begin, end)
    • begin 이상 ~ end 미만
    • 1~4
  • (begin, end]
    • begin 초과, end 이하
    • 2~5

 

usemap 할당

sparse_early_usemaps_alloc_pgdat_section()

mm/sparse.c

#ifdef CONFIG_MEMORY_HOTREMOVE
static unsigned long * __init
sparse_early_usemaps_alloc_pgdat_section(struct pglist_data *pgdat,
                                         unsigned long size)
{
        struct mem_section_usage *usage;
        unsigned long goal, limit;
        int nid;
        /*
         * A page may contain usemaps for other sections preventing the
         * page being freed and making a section unremovable while
         * other sections referencing the usemap remain active. Similarly,
         * a pgdat can prevent a section being removed. If section A
         * contains a pgdat and section B contains the usemap, both
         * sections become inter-dependent. This allocates usemaps
         * from the same section as the pgdat where possible to avoid
         * this problem.
         */
        goal = pgdat_to_phys(pgdat) & (PAGE_SECTION_MASK << PAGE_SHIFT);
        limit = goal + (1UL << PA_SECTION_SHIFT);
        nid = early_pfn_to_nid(goal >> PAGE_SHIFT);
again:
        usage = memblock_alloc_try_nid(size, SMP_CACHE_BYTES, goal, limit, nid);
        if (!usage && limit) {
                limit = 0;
                goto again;
        }
        return usage;
}
#endif

CONFIG_MEMORY_HOTREMOVE 커널 옵션을 사용하는 경우 usemap을 할당한다. 가능하면 노드 정보(pgdat)가 담겨 있는 섹션에 usemap이 들어갈 수 있도록 메모리 할당을 시도한다. 실패한 경우 위치에 상관없이 다시 한번 할당을 시도한다.

  • 코드 라인 19~20에서 goal에는 노드에서의 섹션 시작 물리 주소를 구하고 limit를 1개의 섹션 크기로 제한한다.
  • 코드 라인 21에서 기존에 mem_map에 저장해놓은 섹션별 노드 정보를 가져온다.
  • 코드 라인 22~23에서 again: 레이블이다. 지정된 노드의 goal~limit 범위, 즉 노드 정보가 담겨 있는 섹션 영역 내에서 SMP_CACHE_BYTES align으로 size만큼의 memblock 공간 할당을 요청한다.
  • 코드 라인 24~27에서 한 번 시도해서 할당이 안 되면 limit를 0으로 만들어 다시 한번 시도한다.
  • 코드 라인 28에서 할당된 usemap을 반환한다.

 

mem_map용 Sparse 버퍼 할당/해제

노드에 해당하는 전체 present 섹션 메모리에 대한 mem_map[] 배열을 한꺼번에 할당을 시도한다. 이렇게 시도하여 할당이 된 경우 이 sparse 버퍼 메모리를 mem_map[] 배열로 사용한다. 만일 할당이 실패하는 경우 fallback되어 그냥 섹션별로 할당한다.

 

sparse_buffer_init()

mm/sparse.c

static void __init sparse_buffer_init(unsigned long size, int nid)
{
        WARN_ON(sparsemap_buf); /* forgot to call sparse_buffer_fini()? */
        sparsemap_buf =
                memblock_alloc_try_nid_raw(size, PAGE_SIZE,
                                                __pa(MAX_DMA_ADDRESS),
                                                MEMBLOCK_ALLOC_ACCESSIBLE, nid);
        sparsemap_buf_end = sparsemap_buf + size;
}

요청한 @size만큼 노드 @nid에서 페이지 단위로 sparse 버퍼를 할당한다.

 

sparse_buffer_alloc()

mm/sparse.c

void * __meminit sparse_buffer_alloc(unsigned long size)
{
        void *ptr = NULL;

        if (sparsemap_buf) {
                ptr = PTR_ALIGN(sparsemap_buf, size);
                if (ptr + size > sparsemap_buf_end)
                        ptr = NULL;
                else
                        sparsemap_buf = ptr + size;
        }
        return ptr;
}

Sparse 버퍼에서 @size 만큼의 메모리를 할당한다. (for mem_map)

 

sparse_buffer_fini()

mm/sparse.c

static void __init sparse_buffer_fini(void)
{
        unsigned long size = sparsemap_buf_end - sparsemap_buf;

        if (sparsemap_buf && size > 0)
                memblock_free_early(__pa(sparsemap_buf), size);
        sparsemap_buf = NULL;
}

사용하고 남은 sparse 버퍼를 할당 해제한다.

 

usemap 할당 섹션 또는 노드 체크

check_usemap_section_nr()

mm/sparse.c

#ifdef CONFIG_MEMORY_HOTREMOVE
static void __init check_usemap_section_nr(int nid,
                struct mem_section_usage *usage)
{
        unsigned long usemap_snr, pgdat_snr;
        static unsigned long old_usemap_snr;
        static unsigned long old_pgdat_snr;
        struct pglist_data *pgdat = NODE_DATA(nid);
        int usemap_nid;

        /* First call */
        if (!old_usemap_snr) {
                old_usemap_snr = NR_MEM_SECTIONS;
                old_pgdat_snr = NR_MEM_SECTIONS;
        }

        usemap_snr = pfn_to_section_nr(__pa(usage) >> PAGE_SHIFT);
        pgdat_snr = pfn_to_section_nr(pgdat_to_phys(pgdat) >> PAGE_SHIFT);
        if (usemap_snr == pgdat_snr)
                return;

        if (old_usemap_snr == usemap_snr && old_pgdat_snr == pgdat_snr)
                /* skip redundant message */
                return;

        old_usemap_snr = usemap_snr;
        old_pgdat_snr = pgdat_snr;

        usemap_nid = sparse_early_nid(__nr_to_section(usemap_snr));
        if (usemap_nid != nid) {
                pr_info("node %d must be removed before remove section %ld\n",
                        nid, usemap_snr);
                return;
        }
        /*
         * There is a circular dependency.
         * Some platforms allow un-removable section because they will just
         * gather other removable sections for dynamic partitioning.
         * Just notify un-removable section's number here.
         */
        pr_info("Section %ld and %ld (node %d) have a circular dependency on usemap and pgdat allocations\n",
                usemap_snr, pgdat_snr, nid);
}
#endif

CONFIG_MEMORY_HOTREMOVE 커널 옵션을 사용하는 경우 usemap 섹션은 pgdat가 위치한 섹션에 있거나 그렇지 않다면 다른 섹션이 모두 삭제될 때까지 usemap이 위치한 섹션은 삭제되면 안 된다. 따라서 이에 대한 관계를 메시지로 알아보기 위한 루틴이다. 할당받은 usemap과 노드 정보(pgdat)가 기록된 섹션과 같지 않으면 정보를 출력한다. usemap 정보가 같은 노드 공간에 구성되지 않으면 메모리 핫리무브(hot remove) 동작 시 노드별로 메모리를 제거해야 할 때 circular dependancy 문제가 발생할 수 있다.

 

  • 코드 라인 12~15에서 처음 호출 시 초깃값으로 진행 중인 섹션 번호를 담는다.
  • 코드 라인 17~20에서 usemap이 할당된 섹션과 노드 정보가 기록된 섹션이 같은 경우 정상이므로 함수를 빠져나간다.
  • 코드 라인 22~24에서 이미 한 번 진행하였던 섹션인 경우 skip 하기 위해 함수를 빠져나간다.
  • 코드 라인 26~27에서 같은 섹션 번호로 다시 한 번 진행하는 경우 skip 하기 위해 섹션 번호들을 기억해둔다.
  • 코드 라인 29~34에서 요청한 @nid에 usemap 노드가 없는 경우 use_map이 있는 섹션 정보를 먼저 hot remove 하여야 한다는 정보를 출력한다.
  • 코드 라인 41~42에서 요청한 @nid에 usemap이 있는 경우 메모리 핫리무브(hot remove) 동작 시 노드별로 메모리를 제거해야 할 때 circular dependancy 문제가 발생할 수 있다는 정보를 출력한다.

 

usemap_size()

mm/sparse.c

unsigned long usemap_size(void)
{
        return BITS_TO_LONGS(SECTION_BLOCKFLAGS_BITS) * sizeof(unsigned long);
}

usemap 사이즈를 리턴한다.

  •  SECTION_BLOCKFLAGS_BITS
    • 섹션당 pageblock 비트 수 (pageblock_order=9일 때)
      • arm64=2048
  • 예) arm64
    • 2048 / 8=256(byte)

 

sparse_mem_map_populate()

vmemmap을 사용하지 않을 때의 함수이다. arm64 디폴트 설정에서는 vmemmap을 사용하므로 이 함수를 사용하지 않는다.

mm/sparse.c

#ifndef CONFIG_SPARSEMEM_VMEMMAP
struct page __init *sparse_mem_map_populate(unsigned long pnum, int nid,
                struct vmem_altmap *altmap)
{
        unsigned long size = section_map_size();
        struct page *map = sparse_buffer_alloc(size);

        if (map)
                return map;

        map = memblock_alloc_try_nid(size,
                                          PAGE_SIZE, __pa(MAX_DMA_ADDRESS),
                                          MEMBLOCK_ALLOC_ACCESSIBLE, nid);
        return map;
}
#endif /* !CONFIG_SPARSEMEM_VMEMMAP */

CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용하지 않는 시스템에서 미리 할당한 sparse 버퍼를 사용하여 mem_map[]을 할당해준다. 만일 실패하는 경우 별도로 섹션마다 mem_map[] 배열을 memblock 할당 요청한다.

 

sparse_init_one_section()

mm/sparse.c

static void __meminit sparse_init_one_section(struct mem_section *ms,
                unsigned long pnum, struct page *mem_map,
                struct mem_section_usage *usage, unsigned long flags)
{
        ms->section_mem_map &= ~SECTION_MAP_MASK;
        ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum)
                | SECTION_HAS_MEM_MAP | flags;
        ms->usage = usage;
}

mem_map 영역을 엔코딩하여 mem_section에 연결하고 usemap 영역도 연결한다.

 


mem_map 주소의 엔코딩/디코딩

각 섹션을 관리하는 mem_section 구조체의 section_mem_map 멤버는 다음과 같이 2가지 정보를 담아사용한다. 단 동시에 사용되지는 않는다.

  • 부트 타임에 잠시 섹션에 대한 early 노드 번호를 담아 사용한다.
    • sparse_encode_early_nid() 및 sparse_early_nid() 함수를 사용한다.
    • mem_map이 활성화된 이후 노드 번호는 page->flags에 저장하여 사용된다.
  • 각 섹션이 관리하는 mem_map – 섹션에 대한 pfn 값에 몇 개의 플래그들을 엔코딩하여 사용한다.
    • sparse_encode_mem_map() 및 sparse_decode_mem_map() 함수를 사용한다.
    • 섹션에 대한 pfn 값을 빼서 저장하면 vmemmap을 사용하지 않는 시스템에서 pfn_to_page() 함수를 사용할 때 한 번의 산술연산이 절감되는 효과가 있다.
    • 예) 0x8000_0000 ~ 0xC000_0000 (1G)을 관리하는 섹션에 대한 mem_map 주소 – 0x80000(섹션 pfn) + 플래그 값으로 엔코딩한다.

 

early 노드 번호 엔코딩/디코딩

sparse_encode_early_nid()

mm/sparse.c

/*
 * During early boot, before section_mem_map is used for an actual
 * mem_map, we use section_mem_map to store the section's NUMA
 * node.  This keeps us from having to use another data structure.  The
 * node information is cleared just before we store the real mem_map.
 */
static inline unsigned long sparse_encode_early_nid(int nid)
{
        return (nid << SECTION_NID_SHIFT);
}

노드 번호는 SECTION_NID_SHIFT(6) 비트부터 사용되므로 그만큼 좌측으로 쉬프트한다.

 

sparse_early_nid()

mm/sparse.c

static inline int sparse_early_nid(struct mem_section *section)
{
        return (section->section_mem_map >> SECTION_NID_SHIFT);
}

mem_section 구조체 멤버 변수인 section_mem_map에서 노드 정보를 추출하여 리턴한다.

 

정규 mem_map 엔코딩/디코딩

sparse_encode_mem_map()

mm/sparse.c

/*
 * Subtle, we encode the real pfn into the mem_map such that
 * the identity pfn - section_mem_map will return the actual
 * physical page frame number.
 */
static unsigned long sparse_encode_mem_map(struct page *mem_map, unsigned long pnum)
{
        unsigned long coded_mem_map =
                (unsigned long)(mem_map - (section_nr_to_pfn(pnum)));
        BUILD_BUG_ON(SECTION_MAP_LAST_BIT > (1UL<<PFN_SECTION_SHIFT));
        BUG_ON(coded_mem_map & ~SECTION_MAP_MASK);
        return coded_mem_map;
}

할당 받은 mem_map의 주소 – 섹션에 해당하는 base pfn 값을 엔코딩 값으로 반환한다.

  • vmemmap을 사용하지 않는 경우 pfn_to_page() 함수에서 산술 연산을 한 번 제거하는 효과가 있다.

 

sparse_decode_mem_map()

mm/sparse.c

/*
 * Decode mem_map from the coded memmap
 */
struct page *sparse_decode_mem_map(unsigned long coded_mem_map, unsigned long pnum)
{
        /* mask off the extra low bits of information */
        coded_mem_map &= SECTION_MAP_MASK;
        return ((struct page *)coded_mem_map) + section_nr_to_pfn(pnum);
}

엔코딩된 mem_map 주소에서 플래그 정보를 제거한 후 디코딩하여 mem_map 주소만 반환한다.

 


VMEMMAP 관련

  • arm64에서는 CONFIG_SPARSEMEM_VMEMMAP을 기본적으로 사용한다.
  • 시스템 리소스가 충분하여 vmemmap을 사용하는 경우 pfn_to_page() 및 page_to_pfn() 함수의 동작이 가장 효과적으로 빨라진다.

 

__populate_section_memmap()

mm/sparse-vmemmap.c

struct page * __meminit __populate_section_memmap(unsigned long pfn,
                unsigned long nr_pages, int nid, struct vmem_altmap *altmap)
{
        unsigned long start = (unsigned long) pfn_to_page(pfn);
        unsigned long end = start + nr_pages * sizeof(struct page);

        if (WARN_ON_ONCE(!IS_ALIGNED(pfn, PAGES_PER_SUBSECTION) ||
                !IS_ALIGNED(nr_pages, PAGES_PER_SUBSECTION)))
                return NULL;

        if (vmemmap_populate(start, end, nid, altmap))
                return NULL;

        return pfn_to_page(pfn);
}

CONFIG_SPARSEMEM_VMEMMAP 커널 옵션을 사용하는 경우 mem_map 영역에 해당하는 @pfn을 vmemmap 영역에 @nr_pages만큼 매핑한다. 요청한 섹션으로 주소 범위를 구한 후 해당 주소 범위와 관련한 pgd, pud 및 pmd 테이블들에 구성되지 않은 테이블이 있는 경우 노드에서 페이지를 할당하여 구성하고 매핑한다.

  • sparse 버퍼에 할당된 mem_map 용 공간들을 pte 엔트리에 연결하여 매핑한다.

 

vmemmap_populate()

arch/arm64/mm/mmu.c

#if !ARM64_KERNEL_USES_PMD_MAPS
int __meminit vmemmap_populate(unsigned long start, unsigned long end, int node,
                struct vmem_altmap *altmap)
{
        WARN_ON((start < VMEMMAP_START) || (end > VMEMMAP_END));
        return vmemmap_populate_basepages(start, end, node, altmap);
}
#else   /* !ARM64_KERNEL_USES_PMD_MAPS */
int __meminit vmemmap_populate(unsigned long start, unsigned long end, int node,
                struct vmem_altmap *altmap)
{
        unsigned long addr = start;
        unsigned long next;
        pgd_t *pgdp;
        p4d_t *p4dp;
        pud_t *pudp;
        pmd_t *pmdp;

        WARN_ON((start < VMEMMAP_START) || (end > VMEMMAP_END));
        do {
                next = pmd_addr_end(addr, end);

                pgdp = vmemmap_pgd_populate(addr, node);
                if (!pgdp)
                        return -ENOMEM;

                p4dp = vmemmap_p4d_populate(pgdp, addr, node);
                if (!p4dp)
                        return -ENOMEM;

                pudp = vmemmap_pud_populate(p4dp, addr, node);
                if (!pudp)
                        return -ENOMEM;

                pmdp = pmd_offset(pudp, addr);
                if (pmd_none(READ_ONCE(*pmdp))) {
                        void *p = NULL;

                        p = vmemmap_alloc_block_buf(PMD_SIZE, node, altmap);
                        if (!p) {
                                if (vmemmap_populate_basepages(addr, next, node, altmap))
                                        return -ENOMEM;
                                continue;
                        }

                        pmd_set_huge(pmdp, __pa(p), __pgprot(PROT_SECT_NORMAL));
                } else
                        vmemmap_verify((pte_t *)pmdp, node, addr, next);
        } while (addr = next, addr != end);

        return 0;
}
#endif  /* !ARM64_KERNEL_USES_PMD_MAPS */

요청 주소 범위와 관련한 pgd, p4d, pud 및 pmd 테이블들에 구성되지 않은 테이블이 있는 경우 노드에서 페이지를 할당하여 구성하고 매핑한다.

  • 4K 페이지를 사용하는 경우 ARM64_KERNEL_USES_PMD_MAPS 값은 1이다.
  • @start: mem_map이  매핑될 시작 가상 주소(vmemmap 영역에 매핑)
  • @end: mem_map이 매핑될 끝 가상 주소 (vmemmap 영역에 매핑)

 

  • 코드 라인 6에서 mem_map을 4K 단위로 매핑을 수행한다.
  • 코드 라인 20~21에서 addr을 순회하며, pmd 단위로 매핑할 예정이므로 pmd 단위의 끝 주소를 구해 next에 대입한다.
  • 코드 라인 23~25에서 해당 가상 주소 위치에 대응하는 pgd 엔트리를 populate 한다.
  • 코드 라인 27~29에서 해당 가상 주소 위치에 대응하는 p4d 엔트리를 populate 한다.
  • 코드 라인 31~33에서 해당 가상 주소 위치에 대응하는 pud 엔트리를 populate 한다.
  • 코드 라인 35~46에서 pmd 엔트리 주소에 매핑되어 있지 않은 경우 PMD 사이즈(2M)의 메모리를 할당한 후 pmd_set_huge() 명령으로 매핑한다.
  • 코드 라인 47~48에서 이미 pmd 매핑이 되어 있는 경우 vmemmap_verify() 함수를 통해 mem_map이 다른 노드에 할당된 경우 경고 로그를 출력한다.
  • 코드 라인 49에서 대음 pmd 단위의 주소로 이동하고 루프를 계속한다.
  • 코드 라인 51에서 정상 결과 0을 반환한다.

 

vmemmap_populate_basepages()

mm/sparse-vmemmap.c

int __meminit vmemmap_populate_basepages(unsigned long start, unsigned long end,
                                         int node, struct vmem_altmap *altmap)
{
        unsigned long addr = start;
        pgd_t *pgd;
        p4d_t *p4d;
        pud_t *pud;
        pmd_t *pmd;
        pte_t *pte;

        for (; addr < end; addr += PAGE_SIZE) {
                pgd = vmemmap_pgd_populate(addr, node);
                if (!pgd)
                        return -ENOMEM;
                p4d = vmemmap_p4d_populate(pgd, addr, node);
                if (!p4d)
                        return -ENOMEM;
                pud = vmemmap_pud_populate(p4d, addr, node);
                if (!pud)
                        return -ENOMEM;
                pmd = vmemmap_pmd_populate(pud, addr, node);
                if (!pmd)
                        return -ENOMEM;
                pte = vmemmap_pte_populate(pmd, addr, node, altmap);
                if (!pte)
                        return -ENOMEM;
                vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
        }

        return 0;
}

mem_map을 4K 단위로 매핑을 수행한다.

  • @start: mem_map이  매핑될 시작 가상 주소(vmemmap 영역에 매핑)
  • @end: mem_map이 매핑될 끝 가상 주소 (vmemmap 영역에 매핑)

 

  • 코드 라인 11에서 페이지 단위로 순회한다.
  • 코드 라인 12~14에서 해당 가상 주소 위치에 대응하는 pgd 엔트리를 populate 한다.
  • 코드 라인 15~17에서 해당 가상 주소 위치에 대응하는 p4d 엔트리를 populate 한다.
  • 코드 라인 18~20에서 해당 가상 주소 위치에 대응하는 pud 엔트리를 populate 한다.
  • 코드 라인 21~23에서 해당 가상 주소 위치에 대응하는 pmd 엔트리를 populate 한다.
  • 코드 라인 24~26에서 해당 가상 주소 위치에 대응하는 pte 엔트리에 @altmap을 매핑한다.
  • 코드 라인 27에서 vmemmap_verify() 함수를 통해 mem_map이 다른 노드에 할당된 경우 경고 로그를 출력한다.
  • 코드 라인 30에서 정상 결과 0을 반환한다.

 

vmemmap_verify()

mm/sparse-vmemmap.c

void __meminit vmemmap_verify(pte_t *pte, int node,
                                unsigned long start, unsigned long end)
{
        unsigned long pfn = pte_pfn(*pte);
        int actual_node = early_pfn_to_nid(pfn);

        if (node_distance(actual_node, node) > LOCAL_DISTANCE)
                pr_warn("[%lx-%lx] potential offnode page_structs\n",
                        start, end - 1);
}

@pte가 가리키는 mem_map 노드와 @node가 같은 로컬 노드에 있지 않고, remote distance로 떨어져 있는 경우 경고 로그를 출력한다.

 

다음 함수들의 코드 및 설명은 생략한다.

  • vmemmap_pgd_populate()
  • vmemmap_p4d_populate()
  • vmemmap_pud_populate()
  • vmemmap_pmd_populate()

 


구조체 및 주요 변수

mem_section[]

mm/sparse.c

#ifdef CONFIG_SPARSEMEM_EXTREME
extern struct mem_section **mem_section;
#else
extern struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT];
#endif

mem_section은 섹션 별로 mem_map[] 배열과 usemap[] 배열 정보와 연결된다.

 

mem_section 구조체

include/linux/mmzone.h

struct mem_section {
        /*
         * This is, logically, a pointer to an array of struct
         * pages.  However, it is stored with some other magic.
         * (see sparse.c::sparse_init_one_section())
         *
         * Additionally during early boot we encode node id of
         * the location of the section here to guide allocation.
         * (see sparse.c::memory_present())
         *
         * Making it a UL at least makes someone do a cast
         * before using it wrong.
         */
        unsigned long section_mem_map;

        struct mem_section_usage *usage;
#ifdef CONFIG_PAGE_EXTENSION
        /*
         * If SPARSEMEM, pgdat doesn't have page_ext pointer. We use
         * section. (see page_ext.h about this.)
         */
        struct page_ext *page_ext;
        unsigned long pad;
#endif
        /*
         * WARNING: mem_section must be a power-of-2 in size for the
         * calculation and use of SECTION_ROOT_MASK to make sense.
         */
};
  • section_mem_map
    • 여기엔 실제 섹션에 대한 mem_map을 직접 가리키지 않고, 다음과 같이 엔코딩된 값을 사용한다.
      • 섹션의 mem_map을 가리키는 주소섹션에 대한 pfn 값에 플래그들을 엔코딩하여 사용된다. (mem_map은 페이지 단위로 정렬되어 사용된다.)
    • 다음은 엔코딩에 사용할 플래그 정보들이다.
      • bit0: SECTION_MARKED_PRESENT
        • 섹션에 메모리가 있는지 여부를 표현한다.
      • bit1: SECTION_HAS_MEM_MAP
        • 섹션에 mem_map이 연결되었는지 여부를 표현한다.
        • 연결된 mem_map은 섹션 base pfn 값을 빼고 저장된다(엔코딩)
      • bit2: SECTION_IS_ONLINE
        • online 섹션 여부를 표현한다.
      • bit3: SECTION_IS_EARLY
        • early 섹션 여부를 표현한다.
      • bit4: SECTION_TAINT_ZONE_DEVICE
        • 존 디바이스 여부를 표현한다.
      • bits[6~]: 노드 번호
        • 노드 번호와 mem_map 정보가 동시에 저장되지 않는다.
        • 노드 번호는 early 부트업 중에만 잠깐 사용된다. 이후 노드 번호는 page 구조체의 flags 멤버에서 관리한다.
  •  *usage
    • mem_section_usage 구조체를 가리킨다. (usemap과 subsection 비트맵을 관리한다)
  • *page_ext
    • 디버그를 위해 CONFIG_PAGE_EXTENSION 커널 옵션을 사용하였을 때 사용되며, page_ext 구조체들이 있는 곳을 가리킨다.

 

mem_section_usage 구조체

include/linux/mmzone.h

struct mem_section_usage {
#ifdef CONFIG_SPARSEMEM_VMEMMAP
        DECLARE_BITMAP(subsection_map, SUBSECTIONS_PER_SECTION);
#endif
        /* See declaration of similar field in struct zone */
        unsigned long pageblock_flags[0];
};
  • subsection_map
    • 섹션 메모리내에서 서브 섹션 단위(2M)의 메모리 존재 여부를 표현하는 비트맵
  • pageblock_flags[]
    • usemap을 가리키는 주소

 

기타

SECTION_SIZE_BITS 및 MAX_PHYSMEM_BITS

  • arm
    • 28,  32 (256M, 4G, Realview-PBX)
    • 26,  29 (64M, 512M, RPC)
    • 27, 32 (128M, 4G, SA1100)
  • 32bit arm with LPAE
    • 34, 36 (16G, 64G, Keystone)
  • arm64
    • 27, 48 (128M, 256T)
  • x86
    • 26, 32 (64M, 4G)
  • x86_32 with PAE
    • 29, 36 (512M, 64G)
  • x86_64
    • 27, 46 (128M, 64T)

 

참고

 

 

zone_sizes_init()

<kernel v5.15>

존 사이즈 결정 및 초기화

zone_sizes_init()

arch/arm64/mm/init.c

static void __init zone_sizes_init(unsigned long min, unsigned long max)
{
        unsigned long max_zone_pfns[MAX_NR_ZONES]  = {0};

#ifdef CONFIG_ZONE_DMA
        max_zone_pfns[ZONE_DMA] = PFN_DOWN(arm64_dma_phys_limit);
#endif
#ifdef CONFIG_ZONE_DMA32
        max_zone_pfns[ZONE_DMA32] = PFN_DOWN(arm64_dma32_phys_limit);
#endif
        max_zone_pfns[ZONE_NORMAL] = max;

        free_area_init(max_zone_pfns);
}

메모리 모델에 따른 노드, 존의 초기화를 다룬다. (@min에 물리 메모리의 최소 pfn, @max에 물리 메모리의 최대 pfn+1 값. 예: min=0x40000, max=0x80000)

  • 코드 라인 6에서 dma 존의 끝(pfn)을 지정한다.
  • 코드 라인 9에서 dma32 존의 끝(pfn)을 지정한다.
  • 코드 라인 11에서 normal 존의 끝(pfn)을 지정한다.
    • 메모리가 dma32 존에 들어갈 정도로 작은 경우 dma32 존과 normal 존에 대한 max_zone_pfns[] 값은 동일하다.
    • 예) 1G 메모리
      • max_zone_pfns[] = { 0x8_0000, 0x8_0000, 0x8_0000, 0x0 }
  • 코드 라인 13에서 max_zone_pfns[] 배열 정보를 사용하여 빈 페이지들을 초기화한다.

 

다음 그림은 max_zone_pfns[] 값을 산출하는 과정이다.

  • DMA 영역은 물리 공간에서 최대 4G 영역을 사용할 수 있으며, 4G 단위의 영역 경계를 넘어가지 못한다.

 


노드 초기화

노드를 초기화하기 사용된 함수명은 free_area_init_nodes()였지만 커널 v5.8-rc1부터 free_area_init() 함수명으로 변경되었다.

 

다음 그림은 미리 산출된 존 구획 정보를 넘겨받아 각 노드를 초기화하는 흐름을 보여준다.

  • NUMA 시스템의 경우 커널 파라미터등을 사용하여 movable 존을 추가할 수 있는데 이 때에는 각 노드의 last 존을 커널 파라미터에서 요청한 사이즈만큼 movable 존으로 분리하여 구획한다.
    • 아키텍처 및 메모리의 크기에 따라 last 존은 다르며 highmem -> normal -> dma32 -> dma 순서대로 실제 메모리가 존재하는 가장 마지막 존이 지정된다.
    • 예) normal, dma32, dma 존에 실제 메모리가 존재하는 경우 last 존은 normal 존이 된다.
  • arm64 아키텍처 커널 v4.7-rc1 부터 NUMA 설정을 지원한다. arm 아키텍처의 경우 별도의 NUMA 패치를 적용해야 한다.

 

다음 그림은 지정된 노드 정보와 사용 가능한 존 정보들을 초기화하는 흐름을 보여준다.

 

NUMA 시스템의 노드들 초기화

free_area_init()

mm/page_alloc.c -1/2-

/**
 * free_area_init - Initialise all pg_data_t and zone data
 * @max_zone_pfn: an array of max PFNs for each zone
 *
 * This will call free_area_init_node() for each active node in the system.
 * Using the page ranges provided by memblock_set_node(), the size of each
 * zone in each node and their holes is calculated. If the maximum PFN
 * between two adjacent zones match, it is assumed that the zone is empty.
 * For example, if arch_max_dma_pfn == arch_max_dma32_pfn, it is assumed
 * that arch_max_dma32_pfn has no pages. It is also assumed that a zone
 * starts where the previous one ended. For example, ZONE_DMA32 starts
 * at arch_max_dma_pfn.
 */
void __init free_area_init(unsigned long *max_zone_pfn)
{
        unsigned long start_pfn, end_pfn;
        int i, nid, zone;
        bool descending;

        /* Record where the zone boundaries are */
        memset(arch_zone_lowest_possible_pfn, 0,
                                sizeof(arch_zone_lowest_possible_pfn));
        memset(arch_zone_highest_possible_pfn, 0,
                                sizeof(arch_zone_highest_possible_pfn));

        start_pfn = find_min_pfn_with_active_regions();
        descending = arch_has_descending_max_zone_pfns();

        for (i = 0; i < MAX_NR_ZONES; i++) {
                if (descending)
                        zone = MAX_NR_ZONES - i - 1;
                else
                        zone = i;

                if (zone == ZONE_MOVABLE)
                        continue;

                end_pfn = max(max_zone_pfn[zone], start_pfn);
                arch_zone_lowest_possible_pfn[zone] = start_pfn;
                arch_zone_highest_possible_pfn[zone] = end_pfn;

                start_pfn = end_pfn;
        }

        /* Find the PFNs that ZONE_MOVABLE begins at in each node */
        memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
        find_zone_movable_pfns_for_nodes();

시스템내의 모든 active 노드 및 ZONE 정보를 초기화하고 구성한다.

  • 코드 라인 13에서 물리 메모리의 시작 pfn을 구해온다.
  • 코드 라인 14에서 아키텍처의 존 순서가 거꾸로되어 있는지 여부를 알아온다.
    • ARC 아키텍처를 제외하면 모두 false이다.
  • 코드 라인 16~30에서 인자로 받은 @max_zone_pfn 배열에서 모든 존을 순회하며 movable 존을 제외하고 각 zone의 경계를 구분한다.
    • arch_zone_lowest_possible_pfn[]에 각 존별로 시작 pfn 값이 지정된다.
    • arch_zone_highest_possible_pfn[]에 각 존별로 끝 pfn 값이 지정된다.
  • 코드 라인 33~34에서 노드별로 movable 존의 시작 pfn 값을 알아와서 zone_movable_pfn[ ]에 담아온다.

 

mm/page_alloc.c -2/2-

        /* 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++) {
                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, and initialize the
         * subsection-map relative to active online memory ranges to
         * enable future "sub-section" extensions of the memory 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);
                subsection_map_init(start_pfn, end_pfn - start_pfn);
        }

        /* Initialise every node */
        mminit_verify_pageflags_layout();
        setup_nr_node_ids();
        for_each_online_node(nid) {
                pg_data_t *pgdat = NODE_DATA(nid);
                free_area_init_node(nid);

                /* Any memory on that node */
                if (pgdat->node_present_pages)
                        node_set_state(nid, N_MEMORY);
                check_for_memory(pgdat, nid);
        }

        memmap_init();
}
  • 코드 라인 2~16에서 movable 존을 제외한 각 존별 pfn 영역을 출력한다.
  • 코드 라인 19~24에서 각 노드에 등록된 movable 존 정보를 출력한다.
  • 코드 라인 31~37에서 각 노드에 등록된 early 노드 메모리 정보를 출력한다. 또한 사용하는 서브섹션 메모리를 1과 0으로 표현하는 서브섹션비트맵을 설정한다.
  • 코드 라인 40에서 page->flags에 들어갈 섹션 비트 수, 노드 비트 수, 존 비트 수 등을 점검한다.
  • 코드 라인 41에서 active 노드 수를 결정하기 위해 마지막 possible 노드 + 1을 전역 nr_node_ids에 설정한다.
  • 코드 라인 42~50에서 모든 온라인 노드를 초기화한다.
  • 코드 라인 52에서 mem_map을 초기화한다.

 

지정한 노드 초기화

free_area_init_node()

mm/page_alloc.c

static void __init free_area_init_node(int nid)
{
        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->kswapd_highest_zoneidx);

        get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);

        pgdat->node_id = nid;
        pgdat->node_start_pfn = start_pfn;
        pgdat->per_cpu_nodestats = NULL;

        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);
        calculate_node_totalpages(pgdat, start_pfn, end_pfn);

        alloc_node_mem_map(pgdat);
        pgdat_set_deferred_range(pgdat);

        free_area_init_core(pgdat);
}

요청한 노드 @nid의 모든 ZONE 정보를 초기화하고 구성한다.

  • 코드 라인 3 에서 노드에 속한 페이지를 관리하는 구조체 포인터를 알아온다.
  • 코드 라인 10에서 지정된 노드의 memory memblock에서 파편화되지 않은 페이지 프레임 시작 번호와 끝 번호를 알아온다.
  • 코드 라인 12~14에서 노드 정보에 노드 id와 노드의 시작 pfn 등을 설정한다.
  • 코드 라인 16~18에서 메모리 정보를 출력한다.
    • 예) “Initmem setup node 0 [mem 0x0000000040000000-0x000000007fffffff]
  • 코드 라인 19에서 zones_size[]와 zholes_size[] 정보를 사용하여 노드 정보를 산출한 후 기록한다.
    • ZONE_MOVABLE이 있는 경우 메모리가 존재하는 마지막 zone의 영역이 조정된다.
  • 코드 라인 21에서 flat 물리 메모리 모델용 mem_map을 할당한다.
    • page[] 구조체로 구성된 mem_map을 위해 memblock 할당을 한다
    • Spasrse 메모리 모델을 사용하는 경우 mem_map은 이미 sparse_init() 함수에서 section_mem_map[]으로 구성되었다.
  • 코드 라인 22에서 메모리가 큰 시스템인 경우 page 구조체를 초기화하려면 많은 시간이 걸린다. 이러한 경우 시스템에 필요한 일부 메모리를 제외한 후  CONFIG_DEFERRED_STRUCT_PAGE_INIT 커널 옵션을 사용하여 별도의 커널 스레드가 동작시켜 나중에 page 구조체를 초기화한다. 즉 page 구조체 초기화를 유예시키는 옵션이다.
  • 코드 라인 24에서 노드의 zone 관리를 위해 zone 구조체와 관련된 정보들을 초기화한다. 내부에서 usemap을 할당하고 초기화하며, 버디 시스템에 사용하는 free_area[], lruvec, pcp 등도 초기화한다.

 

다음 그림은 1G 메모리를 가진 ARM32에서 존 구획이 나뉘는 모습을 보여준다.

  • 참고로 4G 이하 메모리를 사용하는 대부분의 ARM32는 ZONE_DMA를 사용하지 않는다.

 

다음 그림은 메모리 영역이 2개로 분리된 6G 메모리를 가진 ARM64에서 존 구획이 나뉘는 모습을 보여준다.

 


Movable 존 구성

NUMA 시스템에서는 대용량 메모리가 사용된다. 이러한 경우 특정 메모리 노드들에 대해 movable 페이지들만을 할당하도록 제한된 movable 존을 설정할 수 있다. movable 존은 시스템의 last 존의 영역을 나누어 사용한다. ARM32 시스템의 경우 마지막 존인 highmem 영역의 일부를 사용하고, ARM64의 경우 highmem을 사용하지 않으므로 마지막 존인 normal 영역의 일부를 사용한다. 시스템에서 마지막 존과 movable 존의 구획을 나누어 구성하는 다음 방법들을 알아본다.

  • “movable_node” 커널 파라미터 지정
    • hotplug 메모리 설정된 노드들을 모두 movable 존으로 설정한다.
    • 0번 노드는 hotplug 설정할 수 없다.
  • “kernelcore=mirror” 커널 파라미티 지정
    • 고신뢰성 확보를 위해 x86 시스템에서 일부 메모리를 mirror로 사용할 수 있다. 이렇게 신뢰성이 확보되는 영역의 사용에 커널 코어가 사용되도록 할 수 있다. 이런 경우에는 나머지 영역들은 movable zone으로 설정한다.
    • 이 옵션을 사용 시 4G 이하의 메모리 영역에 mirror 플래그가 설정되어 있어야 한다.
  • “kernelcore=nn” 커널 파라미터 지정
    • 전체 페이지에서 kernelcore로 지정된 용량을 제외한 나머지를 movablecore 페이지로 하고 이를 각 노드들에 배분하여 movable 존으로 설정한다.
  • “movablecore=nn” 커널 파라미터 지정
    • 전체 페이지에서 movablecore로 지정된 용량 만큼의 페이지를 각 노드들에 배분하여 movale 존으로 설정한다.

 

커널 v4.0 이후 다음과 같은 내용이 추가되었다.

 

find_zone_movable_pfns_for_nodes()

mm/page_alloc.c -1/4-

/*
 * Find the PFN the Movable zone begins in each node. Kernel memory
 * is spread evenly between nodes as long as the nodes have enough
 * memory. When they don't, some nodes will have more kernelcore than
 * others
 */
static void __init find_zone_movable_pfns_for_nodes(void)
{
        int i, nid;
        unsigned long usable_startpfn;
        unsigned long kernelcore_node, kernelcore_remaining;
        /* save the state before borrow the nodemask */
        nodemask_t saved_node_state = node_states[N_MEMORY];
        unsigned long totalpages = early_calculate_totalpages();
        int usable_nodes = nodes_weight(node_states[N_MEMORY]);
        struct memblock_region *r;

        /* Need to find movable_zone earlier when movable_node is specified. */
        find_usable_zone_for_movable();

        /*
         * If movable_node is specified, ignore kernelcore and movablecore
         * options.
         */
        if (movable_node_is_enabled()) {
                for_each_mem_region(r) {
                        if (!memblock_is_hotpluggable(r))
                                continue;

                        nid = memblock_get_region_node(r);

                        usable_startpfn = PFN_DOWN(r->base);
                        zone_movable_pfn[nid] = zone_movable_pfn[nid] ?
                                min(usable_startpfn, zone_movable_pfn[nid]) :
                                usable_startpfn;
                }

                goto out2;
        }

        /*
         * If kernelcore=mirror is specified, ignore movablecore option
         */
        if (mirrored_kernelcore) {
                bool mem_below_4gb_not_mirrored = false;

                for_each_mem_region(r) {
                        if (memblock_is_mirror(r))
                                continue;

                        nid = memblock_get_region_node(r);

                        usable_startpfn = memblock_region_memory_base_pfn(r);

                        if (usable_startpfn < 0x100000) {
                                mem_below_4gb_not_mirrored = true;
                                continue;
                        }

                        zone_movable_pfn[nid] = zone_movable_pfn[nid] ?
                                min(usable_startpfn, zone_movable_pfn[nid]) :
                                usable_startpfn;
                }

                if (mem_below_4gb_not_mirrored)
                        pr_warn("This configuration results in unmirrored kernel memory.\n");

                goto out2;
        }

노드별 movable 존 구획을 나눈다.

  • 코드 라인 8에서 모든 노드의 페이지 수를 알아온다.
  • 코드 라인 9에서 메모리가 있는 노드의 수를 알아온다.
  • 코드 라인 13에서 모든 zone에서 메모리가 있는 마지막 zone을 알아와서 전역 변수 movable_zone에 대입한다.
    • ZONE_MOVABLE은 마지막 zone의 일부 또는 전부를 사용하여 구성하게 된다.
  • 코드 라인 19~33에서 Hot-plug용 ZONE_MOVABLE을 구성하기 위해 노드별 zone movable의 시작 주소를 전역 zone_movable_pfn[] 배열에 산출한다.
    • CONFIG_MOVABLE_NODE 커널 옵션을 사용하면서 “movable_node” 커널 파라메터를 사용한 경우 true를 반환한다.
    • hotplug 설정이 있는 memblock의 시작 주소를 zone_movable_pfn[]에 대입하되 zone_movable_pfn[] 값보다 큰 경우에 한정 한다.
  • 코드 라인 38~63에서 “kernelcore=mirror” 커널 파라미터가 설정된 경우 “movablecore” 커널 파라미터는 무시한다.
    • 4G 이하의 메모리에 대해서는 mirror가 설정되어 있지 않은 경우 경고 메시지를 출력한다.

 

다음 그림은 “movable_node” 커널 파라미터를 사용하여 movable zone을 구성하는 모습을 보여준다.

 

다음 그림은 “movablecore=mirror” 커널 파라미터를 사용하여 movable zone을 구성하는 모습을 보여준다.

 

mm/page_alloc.c -2/4-

.       /*
         * If kernelcore=nn% or movablecore=nn% was specified, calculate the
         * amount of necessary memory.
         */
        if (required_kernelcore_percent)
                required_kernelcore = (totalpages * 100 * required_kernelcore_percent) /
                                       10000UL;
        if (required_movablecore_percent)
                required_movablecore = (totalpages * 100 * required_movablecore_percent) /
                                        10000UL;

        /*
         * If movablecore= was specified, calculate what size of
         * kernelcore that corresponds so that memory usable for
         * any allocation type is evenly spread. If both kernelcore
         * and movablecore are specified, then the value of kernelcore
         * will be used for required_kernelcore if it's greater than
         * what movablecore would have allowed.
         */
        if (required_movablecore) {
                unsigned long corepages;

                /*
                 * Round-up so that ZONE_MOVABLE is at least as large as what
                 * was requested by the user
                 */
                required_movablecore =
                        roundup(required_movablecore, MAX_ORDER_NR_PAGES);
                required_movablecore = min(totalpages, required_movablecore);
                corepages = totalpages - required_movablecore;

                required_kernelcore = max(required_kernelcore, corepages);
        }

        /*
         * If kernelcore was not specified or kernelcore size is larger
         * than totalpages, there is no ZONE_MOVABLE.
         */
        if (!required_kernelcore || required_kernelcore >= totalpages)
                goto out;
  • 코드 라인 5~7에서 퍼센트를 사용한 “kernelcore=nn%” 커널 파라미터에서 전체 페이지로부터 지정한 퍼센트를 커널 코어 페이지로 지정한다.
  • 코드 라인 8~10에서 퍼센트를 사용한 “movablecore=nn%” 커널 파라미터에서 전체 페이지로부터 지정한 퍼센트를 movable 코어 페이지로 지정한다.
  • 코드 라인 20~33에서”movablecore=” 커널 파라미터가 지정된 경우 “kernelcore=” 보다 우선하여 처리한다. 전체 페이지에서 movable로 지정한 페이지를 제외한 나머지를 커널 코어로 지정한다.
    • movable 페이지는 MAX_ORDER_NR_PAGES 단위로 올림 정렬하여 사용한다.
  • 코드 라인 39~40에서 커널 코어에 배정한 페이지가 없거나 전체 페이지보다 큰 경우 out 레이블을 통해 빠져나간다.

 

다음 그림은 “kernelcore=” 보다 “movablecore=” 커널 파라미터의 사용을 우선하는 것을 보여준다.

 

mm/page_alloc.c -3/4-

        /* usable_startpfn is the lowest possible pfn ZONE_MOVABLE can be at */ 
        usable_startpfn = arch_zone_lowest_possible_pfn[movable_zone];

restart:
        /* Spread kernelcore memory as evenly as possible throughout nodes */
        kernelcore_node = required_kernelcore / usable_nodes;
        for_each_node_state(nid, N_MEMORY) {
                unsigned long start_pfn, end_pfn;

                /*
                 * Recalculate kernelcore_node if the division per node
                 * now exceeds what is necessary to satisfy the requested
                 * amount of memory for the kernel
                 */
                if (required_kernelcore < kernelcore_node)
                        kernelcore_node = required_kernelcore / usable_nodes;

                /*
                 * As the map is walked, we track how much memory is usable
                 * by the kernel using kernelcore_remaining. When it is
                 * 0, the rest of the node is usable by ZONE_MOVABLE
                 */
                kernelcore_remaining = kernelcore_node;

                /* Go through each range of PFNs within this node */
                for_each_mem_pfn_range(i, nid, &start_pfn, &end_pfn, NULL) {
                        unsigned long size_pages;

                        start_pfn = max(start_pfn, zone_movable_pfn[nid]);
                        if (start_pfn >= end_pfn)
                                continue;

                        /* Account for what is only usable for kernelcore */
                        if (start_pfn < usable_startpfn) {
                                unsigned long kernel_pages;
                                kernel_pages = min(end_pfn, usable_startpfn)
                                                                - start_pfn;

                                kernelcore_remaining -= min(kernel_pages,
                                                        kernelcore_remaining);
                                required_kernelcore -= min(kernel_pages,
                                                        required_kernelcore);

                                /* Continue if range is now fully accounted */
                                if (end_pfn <= usable_startpfn) {

                                        /*
                                         * Push zone_movable_pfn to the end so
                                         * that if we have to rebalance
                                         * kernelcore across nodes, we will
                                         * not double account here
                                         */
                                        zone_movable_pfn[nid] = end_pfn;
                                        continue;
                                }
                                start_pfn = usable_startpfn;
                        }

                        /*
                         * The usable PFN range for ZONE_MOVABLE is from
                         * start_pfn->end_pfn. Calculate size_pages as the
                         * number of pages used as kernelcore
                         */
                        size_pages = end_pfn - start_pfn;
                        if (size_pages > kernelcore_remaining)
                                size_pages = kernelcore_remaining;
                        zone_movable_pfn[nid] = start_pfn + size_pages;

                        /*
                         * Some kernelcore has been met, update counts and
                         * break if the kernelcore for this node has been
                         * satisfied
                         */
                        required_kernelcore -= min(required_kernelcore,
                                                                size_pages);
                        kernelcore_remaining -= size_pages;
                        if (!kernelcore_remaining)
                                break;
                }
        }
  • 코드 라인 2에서 루프를 돌기 전에 가장 아래에 위치한 movable 존의 시작 pfn 값을 usable_startpfn에 지정한다.
    • 이 usable_startpfn 보다 아래에 있는 영역들은 커널 코어에 해당한다.
  • 코드 라인 4~6에서 restart 레이블을 통해 반복되는 경우 요청 커널 코어 페이지 수를 메모리 노드로 나눈 노드별 커널 코어 페이지 수를 산출한다.
  • 코드 라인 7~16에서 메모리 memblock을 순회하며요청 코어 페이지가 노드별 커널 코어 페이지보다 적어진 경우 요청 커널 코어 페이지를 미처리된 메모리 노드로 나눠 노드별 커널 코어 페이지 수를 재산출한다.
  • 코드 라인 26~31에서 메모리 memblock을 순회하며 시작 pfn과 끝 pfn을 알아오고, 해당 메모리 영역 사이에 노드의 movable pfn 경계가 없는 경우 movable 영역으로 처리할 필요가 없으므로 skip 한다.
    • 노드의 첫 memblock 영역을 처리할 때 zone_movable_pfn[nid] 값은 처음에 0 이므로 이 조건에 걸리지 않는다.
  • 코드 라인 34~57에서 커널 코어 영역으로 사용할 페이지 양을 결정한다.
  • 코드 라인 64~67에서 현재 진행되고 있는 노드의 movable 존을 산출하기 위해 현재까지 처리한 커널 코어 양의 끝 위치를 zone_movable_pfn[nid]에 저장해둔다.
  • 코드 라인 74~78에서 커널 코어 잔량 처리를 수행한다.

 

다음 그림은 “kernelcore=nn” 또는 “movablecore” 커널 파라미터를 사용하여 movable zone을 각 노드에 고르게 분배하여 구성하는 모습을 보여준다.

 

mm/page_alloc.c -4/4-

        /*
         * If there is still required_kernelcore, we do another pass with one
         * less node in the count. This will push zone_movable_pfn[nid] further
         * along on the nodes that still have memory until kernelcore is
         * satisfied
         */
        usable_nodes--;
        if (usable_nodes && required_kernelcore > usable_nodes)
                goto restart;

out2:
        /* Align start of ZONE_MOVABLE on all nids to MAX_ORDER_NR_PAGES */
        for (nid = 0; nid < MAX_NUMNODES; nid++)
                zone_movable_pfn[nid] =
                        roundup(zone_movable_pfn[nid], MAX_ORDER_NR_PAGES);

out:
        /* restore the node_state */
        node_states[N_MEMORY] = saved_node_state;
}
  • 코드 라인 7~9에서 커널 코어에 필요한 페이지가 모두 배정되지 않은 경우 다시 한 번 restart 레이블로 이동하여 처리한다.
  • 코드 라인 11~15에서 out2: 레이블에서는 zone_movable_pfn[] 배열 값을 MAX_ORDER_NR_PAGES 단위로 올림 정렬하도록 한다.
  • 코드 라인 17~19에서 이 함수의 처음에 저장해 둔 메모리 노드 비트맵을 복원한다.

 

다음 그림은 “kernelcore=”로 지정된 페이지 수를 가용 노드 만큼 나누어 배치를 하고 이에 대한 끝 주소를 zone_movable_pfn[] 배열에 저장하는 모습을 보여준다. 이 값은 추후 각 노드별 ZONE_MOVABLE이 시작되는 주소를 설정하는데 사용된다.

 

다음 4개의 그림은 NUMA 시스템에서 “kernelcore=” 커널 파라메터 값에 따라 ZONE_MOVABLE 영역이 지정되는 모습을 보여준다.

find_zone_movable_pfns_for_nodes-5

find_zone_movable_pfns_for_nodes-7

find_zone_movable_pfns_for_nodes-8a

 

find_usable_zone_for_movable()

mm/page_alloc.c

/*
 * This finds a zone that can be used for ZONE_MOVABLE pages. The
 * assumption is made that zones within a node are ordered in monotonic
 * increasing memory addresses so that the "highest" populated zone is used
 */
static void __init find_usable_zone_for_movable(void)
{
        int zone_index;
        for (zone_index = MAX_NR_ZONES - 1; zone_index >= 0; zone_index--) {
                if (zone_index == ZONE_MOVABLE)
                        continue;

                if (arch_zone_highest_possible_pfn[zone_index] >
                                arch_zone_lowest_possible_pfn[zone_index])
                        break;
        }

        VM_BUG_ON(zone_index == -1);
        movable_zone = zone_index;
}

가용 zone에서 가장 높이 위치한 zone 인덱스 값을 전역 변수 movable_zone에 저장한다.

 

find_usable_zone_for_movable-1

 

get_pfn_range_for_nid()

mm/page_alloc.c

/**
 * get_pfn_range_for_nid - Return the start and end page frames for a node
 * @nid: The nid to return the range for. If MAX_NUMNODES, the min and max PFN are returned.
 * @start_pfn: Passed by reference. On return, it will have the node start_pfn.
 * @end_pfn: Passed by reference. On return, it will have the node end_pfn.
 *
 * It returns the start and end page frame of a node based on information
 * provided by memblock_set_node(). If called for a node
 * with no available memory, a warning is printed and the start and end
 * PFNs will be 0.
 */
void __init get_pfn_range_for_nid(unsigned int nid,
                        unsigned long *start_pfn, unsigned long *end_pfn)
{
        unsigned long this_start_pfn, this_end_pfn;
        int i;

        *start_pfn = -1UL;
        *end_pfn = 0;

        for_each_mem_pfn_range(i, nid, &this_start_pfn, &this_end_pfn, NULL) {
                *start_pfn = min(*start_pfn, this_start_pfn);
                *end_pfn = max(*end_pfn, this_end_pfn);
        }

        if (*start_pfn == -1UL)
                *start_pfn = 0;
}

해당 노드 @nid의 memory memblock 에서 해당 페이지가 파편화되지 않고 온전히 포함된 페이지 번호만을 찾아 시작 pfn과 끝 pfn으로 알아온다.

  • 만일 노드 지정에 MAX_NUMNODES가 지정되면 DRAM의 min_pfn과 max_pfn이 리턴된다.
  • 참고: for_each_mem_pfn_range() 함수 -> Memblock (2) | 문c

 


노드 및 노드의 모든 존별 spanned 및 present 페이지 산출

calculate_node_totalpages()

mm/page_alloc.c

static void __init calculate_node_totalpages(struct pglist_data *pgdat,
                                                unsigned long node_start_pfn,
                                                unsigned long node_end_pfn)
{
        unsigned long realtotalpages = 0, totalpages = 0;
        enum zone_type i;

        for (i = 0; i < MAX_NR_ZONES; i++) {
                struct zone *zone = pgdat->node_zones + i;
                unsigned long zone_start_pfn, zone_end_pfn;
                unsigned long size, real_size;

                size = zone_spanned_pages_in_node(pgdat->node_id, i,
                                                  node_start_pfn,
                                                  node_end_pfn,
                                                  &zone_start_pfn,
                                                  &zone_end_pfn);
                real_size = size - zone_absent_pages_in_node(pgdat->node_id, i,
                                                  node_start_pfn, node_end_pfn);

                size = spanned;
                real_size = size - absent;

                if (size)
                        zone->zone_start_pfn = zone_start_pfn;
                else
                        zone->zone_start_pfn = 0;
                zone->spanned_pages = size;
                zone->present_pages = real_size;
#if defined(CONFIG_MEMORY_HOTPLUG)
                zone->present_early_pages = real_size;
#endif

                totalpages += size;
                realtotalpages += real_size;
        }

        pgdat->node_spanned_pages = totalpages;
        pgdat->node_present_pages = realtotalpages;
        pr_debug("On node %d totalpages: %lu\n", pgdat->node_id, realtotalpages);
}

요청 노드 및 요청 노드에 대한 모든 존별로 홀을 포함한 spanned_pages와 실제 사용할 수 있는 페이지인 present_pages를 구한다.

  • pgdat->node_spanned_pages 및 pgdat->node_present_pages 멤버에 노드에 대해 산출한 spanned 및 present 페이지를 저장한다.
  • 존별 zone->spanned_pages 및 zone->present_pages 멤버에 각 존에 대해 산출한 spanned 및 present 페이지를 저장한다.

 

다음 그림은 0번 노드에서 normal 존과 0번 노드 전체에 대한 spanned 및 present 페이지를 구하는 모습을 보여준다.

calculate_node_totalpages-1b

 

spanned 페이지 산출 – 작업중

zone_spanned_pages_in_node()

mm/page_alloc.c

/*
 * Return the number of pages a zone spans in a node, including holes
 * present_pages = zone_spanned_pages_in_node() - zone_absent_pages_in_node()
 */
static unsigned long __init zone_spanned_pages_in_node(int nid,
                                        unsigned long zone_type,
                                        unsigned long node_start_pfn,
                                        unsigned long node_end_pfn,
                                        unsigned long *zone_start_pfn,
                                        unsigned long *zone_end_pfn)
{
        unsigned long zone_low = arch_zone_lowest_possible_pfn[zone_type];
        unsigned long zone_high = arch_zone_highest_possible_pfn[zone_type];
        /* When hotadd a new node from cpu_up(), the node should be empty */
        if (!node_start_pfn && !node_end_pfn)
                return 0;

        /* Get the start and end of the zone */
        *zone_start_pfn = arch_zone_lowest_possible_pfn[zone_type];
        *zone_end_pfn = arch_zone_highest_possible_pfn[zone_type];
        adjust_zone_range_for_zone_movable(nid, zone_type,
                                node_start_pfn, node_end_pfn,
                                zone_start_pfn, zone_end_pfn);

        /* Check that this node has pages within the zone's required range */
        if (*zone_end_pfn < node_start_pfn || *zone_start_pfn > node_end_pfn)
                return 0;

        /* Move the zone boundaries inside the node if necessary */
        *zone_end_pfn = min(*zone_end_pfn, node_end_pfn);
        *zone_start_pfn = max(*zone_start_pfn, node_start_pfn);

        /* Return the spanned pages */
        return *zone_end_pfn - *zone_start_pfn;
}

해당 노드에 대해 요청 zone의 hole을 포함한 페이지 수를 알아온다. NUMA 시스템에서는 ZONE_MOVABLE을 사용하는 경우가 있으므로 이 때 highest zone의 영역의 일정 양을 나누어 사용하므로 이에 대한 페이지 수 계산을 해야 한다.

  • 코드 라인 8~23에서 movable 존이 사용되는 경우 실제 movable 가능한 페이지 영역을 기준으로 last 존 의 영역을 조정한다.
  • 코드 라인 26에서 zone 끝 pfn 값이 노드 끝 pfn 값을 초과하지 않도록 한다.
  • 코드 라인 27에서 zone 시작 pfn 값이 노드 시작 pfn 값보다 작지 않도록 한다.
  • 코드 라인 30에서 재조정된 zone의 hole을 포함한 페이지 수를 리턴한다.

 

absent 페이지 산출

zone_absent_pages_in_node()

mm/page_alloc.c

/* Return the number of page frames in holes in a zone on a node */
static unsigned long __init zone_absent_pages_in_node(int nid,
                                        unsigned long zone_type,
                                        unsigned long node_start_pfn,
                                        unsigned long node_end_pfn)
{
        unsigned long zone_low = arch_zone_lowest_possible_pfn[zone_type];
        unsigned long zone_high = arch_zone_highest_possible_pfn[zone_type];
        unsigned long zone_start_pfn, zone_end_pfn;
        unsigned long nr_absent;

        /* When hotadd a new node from cpu_up(), the node should be empty */
        if (!node_start_pfn && !node_end_pfn)
                return 0;

        zone_start_pfn = clamp(node_start_pfn, zone_low, zone_high);
        zone_end_pfn = clamp(node_end_pfn, zone_low, zone_high);

        adjust_zone_range_for_zone_movable(nid, zone_type,
                        node_start_pfn, node_end_pfn,
                        &zone_start_pfn, &zone_end_pfn);
        nr_absent = __absent_pages_in_range(nid, zone_start_pfn, zone_end_pfn);

        /*
         * ZONE_MOVABLE handling.
         * Treat pages to be ZONE_MOVABLE in ZONE_NORMAL as absent pages
         * and vice versa.
         */
        if (mirrored_kernelcore && zone_movable_pfn[nid]) {
                unsigned long start_pfn, end_pfn;
                struct memblock_region *r;

                for_each_mem_region(r) {
                        start_pfn = clamp(memblock_region_memory_base_pfn(r),
                                          zone_start_pfn, zone_end_pfn);
                        end_pfn = clamp(memblock_region_memory_end_pfn(r),
                                        zone_start_pfn, zone_end_pfn);

                        if (zone_type == ZONE_MOVABLE &&
                            memblock_is_mirror(r))
                                nr_absent += end_pfn - start_pfn;

                        if (zone_type == ZONE_NORMAL &&
                            !memblock_is_mirror(r))
                                nr_absent += end_pfn - start_pfn;
                }
        }

        return nr_absent;
}

해당 노드에 대해 요청 zone에서 빈 공간(hole) 페이지 수를 알아온다.

  • 코드 라인 15에서 node_start_pfn 값을 zone 영역으로 들어가도록 조정하여 zone 시작 pfn으로 대입한다.
  • 코드 라인 16에서 node_end_pfn 값을 zone 영역으로 들어가도록 조정하여 zone 끝 pfn으로 대입한다.
  • 코드 라인 18~20에서 실제 movable 가능한 페이지 영역을 기준으로 zone 들의 영역을 조정한다.
  • 코드 라인 21에서 지정된 영역내에서 빈 공간(hole) 페이지 수를 알아온다.
  • 코드 라인 28~46에서 mirrored 커널 코어를 사용한 경우 movable 페이지들을 absent 페이지로 카운트 한다.
  • 코드 라인 48에서 absent 페이지 수를 반환한다.

 

__absent_pages_in_range()

mm/page_alloc.c

/*
 * Return the number of holes in a range on a node. If nid is MAX_NUMNODES,
 * then all holes in the requested range will be accounted for.
 */
unsigned long __meminit __absent_pages_in_range(int nid,
                                unsigned long range_start_pfn,
                                unsigned long range_end_pfn)
{
        unsigned long nr_absent = range_end_pfn - range_start_pfn;
        unsigned long start_pfn, end_pfn;
        int i;

        for_each_mem_pfn_range(i, nid, &start_pfn, &end_pfn, NULL) {
                start_pfn = clamp(start_pfn, range_start_pfn, range_end_pfn);
                end_pfn = clamp(end_pfn, range_start_pfn, range_end_pfn);
                nr_absent -= end_pfn - start_pfn;
        }
        return nr_absent;
}

지정된 영역내에서 빈 공간(hole) 페이지 수를 알아온다.

  • 코드 라인 5에서 nr_absent  초기값을 영역에 대한 페이지 수로 한다.
  • 코드 라인 9에서 노드 번호에 해당하는 memory memblock에서 파편화된 페이지를 제외한 즉 온전한 페이지가 1개 이상인 경우의 시작 pfn과 끝 pfn 값을 루프를 돌며 하나씩 알아온다.
  • 코드 라인 10에서 영역에 들어가게 start_pfn 값을 바꾼다.
  • 코드 라인 11에서 영역에 들어가게 end_pfn 값을 바꾼다.
  • 코드 라인 12에서 영역에 포함된 페이지 수를 빼면 사용할 수 없는 페이지 수가 계산된다.
  • 코드 라인 14에서 absent 페이지를 반환한다.

 

아래 그림은 함수 처리 예를 보인다. (실제 NUMA 시스템 사용 사례는 아니므로 계산에 참고만 한다.)

__absent_pages_in_range-1b

 

movable 존에 맞춰 각 존 조정

adjust_zone_range_for_zone_movable()

page_alloc.c

/*
 * The zone ranges provided by the architecture do not include ZONE_MOVABLE
 * because it is sized independent of architecture. Unlike the other zones,
 * the starting point for ZONE_MOVABLE is not fixed. It may be different
 * in each node depending on the size of each node and how evenly kernelcore
 * is distributed. This helper function adjusts the zone ranges
 * provided by the architecture for a given node by using the end of the
 * highest usable zone for ZONE_MOVABLE. This preserves the assumption that
 * zones within a node are in order of monotonic increases memory addresses
 */
static void __init adjust_zone_range_for_zone_movable(int nid,
                                        unsigned long zone_type,
                                        unsigned long node_start_pfn,
                                        unsigned long node_end_pfn,
                                        unsigned long *zone_start_pfn,
                                        unsigned long *zone_end_pfn)
{
        /* Only adjust if ZONE_MOVABLE is on this node */
        if (zone_movable_pfn[nid]) {
                /* Size ZONE_MOVABLE */
                if (zone_type == ZONE_MOVABLE) {
                        *zone_start_pfn = zone_movable_pfn[nid];
                        *zone_end_pfn = min(node_end_pfn,
                                arch_zone_highest_possible_pfn[movable_zone]);

                /* Adjust for ZONE_MOVABLE starting within this range */
                } else if (!mirrored_kernelcore &&
                        *zone_start_pfn < zone_movable_pfn[nid] &&
                        *zone_end_pfn > zone_movable_pfn[nid]) {
                        *zone_end_pfn = zone_movable_pfn[nid];

                /* Check if this whole range is within ZONE_MOVABLE */
                } else if (*zone_start_pfn >= zone_movable_pfn[nid])
                        *zone_start_pfn = *zone_end_pfn;
        }
}

실제 movable 가능한 페이지 영역을 기준으로 현재 zone의 영역을 다음과 같이 조정 한다.

  • 처리할 zone이 ZONE_MOVABLE인 경우 실제 movable 가능한 페이지 범위로 조정한다.
  • 처리할 zone이 ZONE_MOVABLE이 아닌 경우 movable 가능한 페이지 범위와 겹치지 않도록 조정한다.
  • 코드 라인 9에서 해당 노드에 movable 존이 있는 경우
  • 코드 라인 11~14에서 현재 zone이 ZONE_MOVABLE인 경우 zone 시작 pfn 에 zone_movable_pfn[nid] 값을 대입하고, zone 끝 pfn을 moveable 가능한 페이지 번호로 줄인다.
  • 코드 라인 17~20에서 mirrored 커널 코어가 아니면서 노드의 메모리 일부가 ZONE_MOVABLE로 구성한 경우 last zone의 끝 주소를 조정한다.
  • 코드 라인 23~24에서 요청 zone이 movable 영역보다 높은 경우 잘못 요청한 경우 이므로 zone 시작 pfn에 zone 끝 pfn 값을 대입하여 요청 zone의 size가 0이되게 하여 이 zone을 처리하지 않게 한다.

 


Flat 메모리 모델에서 노드용 mem_map 할당

alloc_node_mem_map()

mm/page_alloc.c

#ifdef CONFIG_FLATMEM
static void __init 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;

        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 = memmap_alloc(size, SMP_CACHE_BYTES, MEMBLOCK_LOW_LIMIT,
                                   pgdat->node_id, false);
                if (!map)
                        panic("Failed to allocate %ld bytes for node %d memory map\n",
                              size, pgdat->node_id);
                pgdat->node_mem_map = map + offset;
        }
        pr_debug("%s: node %d, pgdat %08lx, node_mem_map %08lx\n",
                                __func__, pgdat->node_id, (unsigned long)pgdat,
                                (unsigned long)pgdat->node_mem_map);
#ifndef CONFIG_NUMA
        /*
         * 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 (page_to_pfn(mem_map) != pgdat->node_start_pfn)
                        mem_map -= offset;
        }
#endif
}
#else
static inline void alloc_node_mem_map(struct pglist_data *pgdat) { }
#endif /* CONFIG_FLATMEM */

page[] 구조체로 구성된 flat 메모리 모델용 mem_map을 위해 memblock 할당을 한다. (ARM64의 경우 sparse 메모리 모델을 사용한다.)

  • 코드 라인 8~9에서 노드에 사용 가능한 메모리 페이지 영역이 없는 경우 리턴한다.
  • 코드 라인 11~12에서 노드 시작 pfn을 버디 할당자용 최대 페이지 요청의 절반 단위로 정렬하고, 정렬하여 조정된 만큼을 offset에 저장한다.
  • 코드 라인 14~32에서 ia64 시스템에서는 별도의 mem_map이 미리 지정되어 있다. 따라서 나머지 시스템을 위해 flat 메모리 모델용 mem_map을 할당한다.
  • 코드 라인 33~35에서 노드에 대한 mem_map 정보를 출력한다.
  • 코드 라인 40~44에서 싱글 노드의 경우 mem_map 전역 변수에 할당된 mem_map을 지정한다.
    • flat 메모리 모델을 사용하는 2M 이하를 사용하는 arm 보드에 문제가 있어 offset을 제거하는 패치를 사용하였다.
    • 커널 v4.4-rc1에서 2M 이하의 flatmem을 사용하는 특정 시스템에서 문제가 있어 패치를 하였다.

 

다음 그림은 지정된 노드에 대한 mem_map[]을 할당 받아 노드 구조체의 node_mem_map에 연결시키는 모습을 보여준다.

alloc_node_mem_map-1a


노드 및 노드에 해당하는 존 초기화

free_area_init_core()

mm/page_alloc.c

/*
 * 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.
 * NOTE: this function is only called during early init.
 */
static void __init free_area_init_core(struct pglist_data *pgdat)
{
        enum zone_type j;
        int nid = pgdat->node_id;

        pgdat_init_internals(pgdat);
        pgdat->per_cpu_nodestats = &boot_nodestats;

        for (j = 0; j < MAX_NR_ZONES; j++) {
                struct zone *zone = pgdat->node_zones + j;
                unsigned long size, freesize, memmap_pages;
                unsigned long zone_start_pfn = zone->zone_start_pfn;

                size = zone->spanned_pages;
                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
                 */
                memmap_pages = calc_memmap_size(size, freesize);
                if (!is_highmem_idx(j)) {
                        if (freesize >= memmap_pages) {
                                freesize -= memmap_pages;
                                if (memmap_pages)
                                        pr_debug("  %s zone: %lu pages used for memmap\n",
                                               zone_names[j], memmap_pages);
                        } else
                                pr_warn("  %s zone: %lu memmap pages exceeds freesize %lu\n",
                                        zone_names[j], memmap_pages, freesize);
                }

                /* Account for reserved pages */
                if (j == 0 && freesize > dma_reserve) {
                        freesize -= dma_reserve;
                         pr_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_init_internals(zone, j, nid, freesize);

                if (!size)
                        continue;

                set_pageblock_order();
                setup_usemap(zone);
                init_currently_empty_zone(zone, zone->zone_start_pfn, size);
        }
}

지정된 노드의 zone 관리를 위해 zone 구조체와 관련된 정보들을 설정한다. 그리고 usemap을 할당하고 초기화하며, 버디 시스템에 사용하는 free_area[], lruvec, pcp 등도 초기화한다.

  • 코드 라인 6에서 노드에서 사용하는 내부 락 및 큐등을 초기화한다.
  • 코드 라인 7에서 per cpu 노드 stat을 지정한다.
  • 코드 라인 9~22에서 zone 수 만큼 순회하며, page 구조체 배열이 들어갈 페이지 수를 구한다.
  • 코드 라인 23~32에서 highmem 존이 아닌 경우 freesize에서 memmap에 사용될 pages 수 만큼을 감소시킨다.
  • 코드 라인 35~38에서 freesize에서 dma_reserve에 사용될 pages 수 만큼을 감소시킨다.
  • 코드 라인 40~41에서 highmem 존이 아닌 경우 전역 변수 nr_kernel_pages 에 freesize를 추가한다.
  • 코드 라인 43~44에서 nr_kernel_pages가 memmap_pages * 2 보다 큰 경우 nr_kernel_pages에서 memmap_pages 만큼 줄인다.
    • memmap은 보통 lowmem 영역에 할당되는데, 메모리가 무척 큰 32 비트 시스템에서는 lowmem 영역이 작아 memmap을 모두 lowmem에 할당하기에는 부담스러워진다. 이렇게 memmap 영역이 lowmem 영역의 절반 이상을 차지 하는 경우 memmap을 highmem에 생성한다.
  • 코드 라인 45에서전역 변수 nr_all_pages에 freesize를 추가한다.
    • nr_kernel_pages와 nr_all_pages 값은 각 zone의 워터마크를 설정할 때 사용된다.
    • nr_kernel_pages=
      • lowmem pages – mem_map pages(highmem 제외) – dma_reserve
        • 조건: highmem memmap 비중이 lowmem의 절반 이하일 때 – mem_map pages(highmem)
    • nr_all_pages=
      • lowmem pages – mem_map pages(highmem 제외) – dma_reserve + highmem pages
    • 두 개의 변수는 alloc_large_system_hash()에서 해쉬의 크기를 결정할 때 엔트리 크기가 지정되지 않을 경우 메모리의 크기에 비례하여 만들기 위해 사용된다.
      • 예) uhash_entries=, ihash_entries=, dhash_entries=, mhash_entries=
  • 코드 라인 52에서 존에 대한 내부 정보를 초기화한다.
  • 코드 라인 54~55에서 사이즈가 0이 된 경우 다음 존을 처리한다.
  • 코드 라인 57에서 CONFIG_HUGETLB_PAGE_SIZE_VARIABLE 커널 옵션을 사용한 경우 런타임에 pageblock_order를 설정한다.
  • 코드 라인 58에서 zone별로 usemap을 memblock에 할당 받는다.
  • 코드 라인 59에서 wait table에 대한 해시 엔트리 수를 결정하고 관련 메모리를 할당 받은 후 초기화(waitqueue 및 spinlock) 하고 버디시스템에서 사용하는 free_area[].free_list[]를 초기화한다.

 

다음 그림은 각 zone의 managed_pages와 전역 nr_kernel_pages 및 nr_all_pages를 한눈에 알아볼 수 있도록 도식화하였다

free_area_init_nodes-2a

 

다음 그림은 위에서 산출한 값을 실제 연산과정으로 살펴보았다.

free_area_init_nodes-1b

 

다음은 ZONE_NORMAL만 있는 경우의 예(rpi2)로 산출한 사례이다.

 

1) 내부 노드 데이터 초기화

pgdat_init_internals()

mm/page_alloc.c

static void __meminit pgdat_init_internals(struct pglist_data *pgdat)
{
        pgdat_resize_init(pgdat);

        pgdat_init_split_queue(pgdat);
        pgdat_init_kcompactd(pgdat);

        init_waitqueue_head(&pgdat->kswapd_wait);
        init_waitqueue_head(&pgdat->pfmemalloc_wait);

        pgdat_page_ext_init(pgdat);
        lruvec_init(&pgdat->__lruvec);
}

노드에서 사용하는 내부 락 및 큐등을 초기화한다.

  • 코드 라인 3에서 node_size_lock 초기화
  • 코드 라인 5에서 split 큐를 초기화한다.
  • 코드 라인 6에서 kcompactd용 대기큐를 초기화한다.
  • 코드 라인 8에서 kswapd용 대기큐를 초기화한다.
  • 코드 라인 9에서 pfmemalloc용 대기큐를 초기화한다.
  • 코드 라인 11에서 CONFIG_SPARSEMEM 커널 옵션이 설정되어 있지 않으면 pgdat->node_page_ext에 NULL을 대입한다.
  • 코드 라인 12에서 __lruvec 리스트를 초기화한다.

 

lruvec_init()

mm/mmzone.c

void lruvec_init(struct lruvec *lruvec)
{
        enum lru_list lru;

        memset(lruvec, 0, sizeof(struct lruvec));

        for_each_lru(lru)
                INIT_LIST_HEAD(&lruvec->lists[lru]);
}

lruvec 리스트를 초기화한다.

 

2) mem_map 사이즈 산출

calc_memmap_size()

mm/page_alloc.c

static unsigned long __paginginit calc_memmap_size(unsigned long spanned_pages,
                                                   unsigned long present_pages)
{
        unsigned long pages = spanned_pages;

        /*
         * Provide a more accurate estimation if there are holes within
         * the zone and SPARSEMEM is in use. If there are holes within the
         * zone, each populated memory region may cost us one or two extra
         * memmap pages due to alignment because memmap pages for each
         * populated regions may not naturally algined on page boundary.
         * So the (present_pages >> 4) heuristic is a tradeoff for that.
         */
        if (spanned_pages > present_pages + (present_pages >> 4) &&
            IS_ENABLED(CONFIG_SPARSEMEM))
                pages = present_pages;

        return PAGE_ALIGN(pages * sizeof(struct page)) >> PAGE_SHIFT;
}

spanned size 수 만큼 page 구조체 배열이 들어갈 페이지 수를 구한다.

  • 다만 CONFIG_SPARSEMEM 커널 옵션을 사용하는 경우에는 spanned size가 real size 수의 125% 만큼 보다 큰 경우 spanned size 대신 real size를 대신 사용한다.

 

3) 내부 존 데이터 초기화

zone_init_internals()

mm/page_alloc.c

static void __meminit zone_init_internals(struct zone *zone, enum zone_type idx, int nid,
                                                        unsigned long remaining_pages)
{
        atomic_long_set(&zone->managed_pages, remaining_pages);
        zone_set_nid(zone, nid);
        zone->name = zone_names[idx];
        zone->zone_pgdat = NODE_DATA(nid);
        spin_lock_init(&zone->lock);
        zone_seqlock_init(zone);
        zone_pcp_init(zone);
}

zone 내부에서 사용하는 락 및 리스트 등을 초기화한다.

  • 코드 라인 4에서 managed_pages 값에 remaining_pages를 대입한다.
  • 코드 라인 5에서 존에 노드 id를 설정한다.
  • 코드 라인 6에서 존 이름을 지정한다.
  • 코드 라인 7에서 존에 해당하는 노드를 지정한다.
  • 코드 라인 8~9에서 존 관리용 락을 초기화한다.
  • 코드 라인 10에서 0 오더 페이지 전용 버디 캐시인 pcp를 준비한다.

 

zone_pcp_init()

mm/page_alloc.c

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->per_cpu_pageset = &boot_pageset;
        zone->per_cpu_zonestats = &boot_zonestats;
        zone->pageset_high = BOOT_PAGESET_HIGH;
        zone->pageset_batch = BOOT_PAGESET_BATCH;

        if (populated_zone(zone))
                pr_debug("  %s zone: %lu pages, LIFO batch:%u\n", zone->name,
                         zone->present_pages, zone_batchsize(zone));
}

0 오더 페이지 전용 버디 캐시인 pcp를 준비한다.

  • zone->pageset에 boot_pageset per-cpu 데이터의 주소를 대입한다.

 

4) 페이지 블럭 order 지정

set_pageblock_order()

mm/page_alloc.c

#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE

/* Initialise the number of pages represented by NR_PAGEBLOCK_BITS */
void __init set_pageblock_order(void)
{
        unsigned int order;

        /* Check that pageblock_nr_pages has not already been setup */
        if (pageblock_order)
                return;

        if (HPAGE_SHIFT > PAGE_SHIFT)
                order = HUGETLB_PAGE_ORDER;
        else
                order = MAX_ORDER - 1;

        /*
         * Assume the largest contiguous order of interest is a huge page.
         * This value may be variable depending on boot parameters on IA64 and
         * powerpc.
         */
        pageblock_order = order;
}
#else /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

/*
 * When CONFIG_HUGETLB_PAGE_SIZE_VARIABLE is not set, set_pageblock_order()
 * is unused as pageblock_order is set at compile-time. See
 * include/linux/pageblock-flags.h for the values of pageblock_order based on
 * the kernel config
 */
void __init set_pageblock_order(void)
{
}

#endif /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */

페이지 블럭 order 값을 설정한다.

  • huge 페이지를 지원하면 HUGETLB_PAGE_ORDER를 사용하고 그렇지 않은 경우 MAX_ORDER(11)-1 값으로 지정할 수 있다.
    • HUGETLB_PAGE_ORDER는 PMD_SHIFT – PAGE_SHIFT 값으로 사용한다.
    • 예) 4K 페이지 + 2M huge 페이지를 사용하는 경우 HUGETLB_PAGE_ORDER=21-12=9 이다.

 

5) usemap 할당

setup_usemap()

이 함수는 SPARSEMEM 메모리 모델이 아닌 경우에 한해 요청한 노드의 usemap을 할당받는다.

mm/page_alloc.c

static void __ref setup_usemap(struct zone *zone)
{
        unsigned long usemapsize = usemap_size(zone->zone_start_pfn,
                                               zone->spanned_pages);
        zone->pageblock_flags = NULL;
        if (usemapsize) {
                zone->pageblock_flags =
                        memblock_alloc_node(usemapsize, SMP_CACHE_BYTES,
                                            zone_to_nid(zone));
                if (!zone->pageblock_flags)
                        panic("Failed to allocate %ld bytes for zone %s pageblock flags on node %d\n",
                              usemapsize, zone->name, zone_to_nid(zone));
        }
}

요청한 노드용 usemap을 할당받고 지정한다.

  • usemap은 partial 페이지 관리를 위해 블록 단위로 mobility를 관리한다.

 

usemap_size()

SPARSE 메모리 모델을 사용하는 경우와 아닌 경우 두 가지 구현이 있지만 다음은 sparse 메모리 모델이 아닌 경우의 구현을 소개한다.

mm/page_alloc.c

/*
 * Calculate the size of the zone->blockflags rounded to an unsigned long
 * Start by making sure zonesize is a multiple of pageblock_order by rounding
 * up. Then use 1 NR_PAGEBLOCK_BITS worth of bits per pageblock, finally
 * round what is now in bits to nearest long in bits, then return it in
 * bytes.
 */
#ifndef CONFIG_SPARSEMEM
static unsigned long __init usemap_size(unsigned long zone_start_pfn, unsigned long zonesize)
{
        unsigned long usemapsize;

        zonesize += zone_start_pfn & (pageblock_nr_pages-1);
        usemapsize = roundup(zonesize, pageblock_nr_pages);
        usemapsize = usemapsize >> pageblock_order;
        usemapsize *= NR_PAGEBLOCK_BITS;
        usemapsize = roundup(usemapsize, 8 * sizeof(unsigned long));

        return usemapsize / 8; 
}
#endif

SPARSEMEM 메모리 모델이 아닌 경우의 usemap 사이즈를 산출한다.

  • 코드 라인 6에서 zonesize를 페이지 블럭 단위로 반올림한다.
  • 코드 라인 7~12에서 usemap 사이즈를 산출하여 반환한다.
    • 페이지 블럭 당 사용할 usemap 비트는 NR_PAGEBLOCK_BITS(4)이다.
    • 산출된 usemap 사이즈는 unsingned long 사이즈 단위로 정렬한다.

 

6) empty 존 초기화

init_currently_empty_zone()

mm/page_alloc.c

void __meminit init_currently_empty_zone(struct zone *zone,
                                        unsigned long zone_start_pfn,
                                        unsigned long size)
{
        struct pglist_data *pgdat = zone->zone_pgdat;
        int zone_idx = zone_idx(zone) + 1;

        if (zone_idx > pgdat->nr_zones)
                pgdat->nr_zones = zone_idx;

        zone->zone_start_pfn = zone_start_pfn;

        mminit_dprintk(MMINIT_TRACE, "memmap_init",
                        "Initialising map node %d zone %lu pfns %lu -> %lu\n",
                        pgdat->node_id,
                        (unsigned long)zone_idx(zone),
                        zone_start_pfn, (zone_start_pfn + size));

        zone_init_free_lists(zone);
        zone->initialized = 1;
}

현재 비어 있는 존에 대한 초기화를 수행한다. 이 때 해당 존의 버디 리스트도 초기화한다.

  • 코드 라인 5~9에서 현재 노드에서 사용할 zone 수를 지정한다.
  • 코드 라인 11에서 zone의시작 pfn을 지정한다.
  • 코드 라인 19에서 zone용 버디 리스트를 초기화한다.
  • 코드 라인 20에서 존이 초기화되었음을 식별하게 한다.

 

zone_init_free_lists()

mm/page_alloc.c

static void __meminit zone_init_free_lists(struct zone *zone)
{
        unsigned int order, t;
        for_each_migratetype_order(order, t) {
                INIT_LIST_HEAD(&zone->free_area[order].free_list[t]);
                zone->free_area[order].nr_free = 0;
        }
}

zone에 해당하는 버디 시스템을 초기화한다.

  • free_area[] 배열의 초기화를 수행한다.

 

7) mem_map 초기화

memmap_init()

mm/page_alloc.c

static void __init memmap_init(void)
{
        unsigned long start_pfn, end_pfn;
        unsigned long hole_pfn = 0;
        int i, j, zone_id = 0, nid;

        for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid) {
                struct pglist_data *node = NODE_DATA(nid);

                for (j = 0; j < MAX_NR_ZONES; j++) { 
                        struct zone *zone = node->node_zones + j;

                        if (!populated_zone(zone))
                                continue;

                        memmap_init_zone_range(zone, start_pfn, end_pfn,
                                               &hole_pfn);
                        zone_id = j;
                }
        }

#ifdef CONFIG_SPARSEMEM
        /*
         * Initialize the memory map for hole in the range [memory_end,
         * section_end].
         * Append the pages in this hole to the highest zone in the last
         * node.
         * The call to init_unavailable_range() is outside the ifdef to
         * silence the compiler warining about zone_id set but not used;
         * for FLATMEM it is a nop anyway
         */
        end_pfn = round_up(end_pfn, PAGES_PER_SECTION);
        if (hole_pfn < end_pfn)
#endif
                init_unavailable_range(hole_pfn, end_pfn, zone_id, nid);
}

할당받은 mem_map(page 디스크립터 배열)을 초기화한다.

  • 코드 라인 7~20에서 memory memblock을 순회하며 메모리가 존재하는 존의 mem_map을 초기화한다.
  • 코드 라인 32~35에서 hole에 대응하는 page 구조체를 Reserved 플래그를 추가한 채로 초기화한다.

 

memmap_init_zone_range()

mm/page_alloc.c

static void __init memmap_init_zone_range(struct zone *zone,
                                          unsigned long start_pfn,
                                          unsigned long end_pfn,
                                          unsigned long *hole_pfn)
{
        unsigned long zone_start_pfn = zone->zone_start_pfn;
        unsigned long zone_end_pfn = zone_start_pfn + zone->spanned_pages;
        int nid = zone_to_nid(zone), zone_id = zone_idx(zone);

        start_pfn = clamp(start_pfn, zone_start_pfn, zone_end_pfn);
        end_pfn = clamp(end_pfn, zone_start_pfn, zone_end_pfn);

        if (start_pfn >= end_pfn)
                return;

        memmap_init_range(end_pfn - start_pfn, nid, zone_id, start_pfn,
                          zone_end_pfn, MEMINIT_EARLY, NULL, MIGRATE_MOVABLE);

        if (*hole_pfn < start_pfn)
                init_unavailable_range(*hole_pfn, start_pfn, zone_id, nid);

        *hole_pfn = end_pfn;
}

존의 mem_map을 초기화한다.

  • 코드 라인 6~8에서 zone의 시작과 끝 pfn을 및 노드 id를 구한다.
  • 코드 라인 10~11에서 초기화할 pfn의 시작과 끝을 zone 영역 범위 이내로 제한한다.
  • 코드 라인 13~14에서 진행할 영역이 없으면 함수를 빠져나간다.
  • 코드 라인 16~17에서 존의 mem_map을 초기화한다.
  • 코드 라인 19~20에서 page 구조체에 대응하는 메모리가 hole인 경우 Reserved 플래그를 설정한다.
  • 코드 라인 22에서 hole_pfn의 처리가 완료하였으므로 end_pfn으로 대입한다.

 

memmap_init_range()

mm/page_alloc.c -1/2-

/*
 * Initially all pages are reserved - free ones are freed
 * up by memblock_free_all() once the early boot process is
 * done. Non-atomic initialization, single-pass.
 *
 * All aligned pageblocks are initialized to the specified migratetype
 * (usually MIGRATE_MOVABLE). Besides setting the migratetype, no related
 * zone stats (e.g., nr_isolate_pageblock) are touched.
 */
void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone,
                unsigned long start_pfn, unsigned long zone_end_pfn,
                enum meminit_context context,
                struct vmem_altmap *altmap, int migratetype)
{
        unsigned long pfn, end_pfn = start_pfn + size;
        struct page *page;

        if (highest_memmap_pfn < end_pfn - 1)
                highest_memmap_pfn = end_pfn - 1;

#ifdef CONFIG_ZONE_DEVICE
        /*
         * Honor reservation requested by the driver for this ZONE_DEVICE
         * memory. We limit the total number of pages to initialize to just
         * those that might contain the memory mapping. We will defer the
         * ZONE_DEVICE page initialization until after we have released
         * the hotplug lock.
         */
        if (zone == ZONE_DEVICE) {
                if (!altmap)
                        return;

                if (start_pfn == altmap->base_pfn)
                        start_pfn += altmap->reserve;
                end_pfn = altmap->base_pfn + vmem_altmap_offset(altmap);
        }
#endif

할당받은 mem_map(page 디스크립터 배열)을 초기화한다.

  • 코드 라인 9~10에서 end_pfn – 1 보다 작으면, highest_memmap_pfn의 값을 end_pfn – 1로 초기화한다.
  • 코드 라인 20~27에서 zone 디바이스의 경우 메모리 크기가 매우 크므로 전체 페이지 디스크립터의 초기화를 일부 제한한다.

 

mm/page_alloc.c -2/2-

        for (pfn = start_pfn; pfn < end_pfn; ) {
                /*
                 * There can be holes in boot-time mem_map[]s handed to this
                 * function.  They do not exist on hotplugged memory.
                 */
                if (context == MEMINIT_EARLY) {
                        if (overlap_memmap_init(zone, &pfn))
                                continue;
                        if (defer_init(nid, pfn, zone_end_pfn))
                                break;
                }

                page = pfn_to_page(pfn);
                __init_single_page(page, pfn, zone, nid);
                if (context == MEMINIT_HOTPLUG)
                        __SetPageReserved(page);

                /*
                 * Usually, we want to mark the pageblock MIGRATE_MOVABLE,
                 * such that unmovable allocations won't be scattered all
                 * over the place during system boot.
                 */
                if (IS_ALIGNED(pfn, pageblock_nr_pages)) {
                        set_pageblock_migratetype(page, migratetype);
                        cond_resched();
                }
                pfn++;
        }
}
  • 코드 라인 1~11에서 시작 pfn 부터 끝 pfn 까지 루프를 돌며 인자 @context가 MEMMAP_EARLY(0)로 진입한 경우 overlap된 페이지는 skip 하고, 노드 내 마지막 존이 큰 영역은 유예시킨다.
    • 대용량 메모리가 장착된 시스템에서 한꺼번에 페이지 디스크립터를 초기화하는 것은 매우 긴 시간이 소요된다. 따라서 부트 후 병렬로 처리하기 위해 초기화를 유예시킨다.
  • 코드 라인 13~14에서 pfn 번호에 해당하는 페이지 디스크립터를 초기화한다.
  • 코드 라인 15~16에서 인자 @context가 MEMMAP_HOTPLUG(1)로 진입한 경우 페이지에 Reserved 플래그를 추가한다.
  • 코드 라인 23~26에서 페이지 블럭 단위로 migrattype을 지정한다.
  • 코드 라인 27~28에서 pfn을 증가시켜고 반복한다.

 

init_unavailable_range()

mm/page_alloc.c

/*
 * Only struct pages that correspond to ranges defined by memblock.memory
 * are zeroed and initialized by going through __init_single_page() during
 * memmap_init_zone_range().
 *
 * But, there could be struct pages that correspond to holes in
 * memblock.memory. This can happen because of the following reasons:
 * - physical memory bank size is not necessarily the exact multiple of the
 *   arbitrary section size
 * - early reserved memory may not be listed in memblock.memory
 * - memory layouts defined with memmap= kernel parameter may not align
 *   nicely with memmap sections
 *
 * Explicitly initialize those struct pages so that:
 * - PG_Reserved is set
 * - zone and node links point to zone and node that span the page if the
 *   hole is in the middle of a zone
 * - zone and node links point to adjacent zone/node if the hole falls on
 *   the zone boundary; the pages in such holes will be prepended to the
 *   zone/node above the hole except for the trailing pages in the last
 *   section that will be appended to the zone/node below.
 */
static void __init init_unavailable_range(unsigned long spfn,
                                          unsigned long epfn,
                                          int zone, int node)
{
        unsigned long pfn;
        u64 pgcnt = 0;

        for (pfn = spfn; pfn < epfn; pfn++) {
                if (!pfn_valid(ALIGN_DOWN(pfn, pageblock_nr_pages))) {
                        pfn = ALIGN_DOWN(pfn, pageblock_nr_pages)
                                + pageblock_nr_pages - 1;
                        continue;
                }
                __init_single_page(pfn_to_page(pfn), pfn, zone, node);
                __SetPageReserved(pfn_to_page(pfn));
                pgcnt++;
        }

        if (pgcnt)
                pr_info("On node %d, zone %s: %lld pages in unavailable ranges",
                        node, zone_names[zone], pgcnt);
}

page 구조체에 대응하는 메모리가 hole인 경우 Reserved 플래그를 설정한다.

 

__init_single_page()

mm/page_alloc.c

static void __meminit __init_single_page(struct page *page, unsigned long pfn,
                                unsigned long zone, int nid)
{
        mm_zero_struct_page(page);
        set_page_links(page, zone, nid, pfn);
        init_page_count(page);
        page_mapcount_reset(page);
        page_cpupid_reset_last(page);
        page_kasan_tag_reset(page);

        INIT_LIST_HEAD(&page->lru);
#ifdef WANT_PAGE_VIRTUAL
        /* The shift won't overflow because ZONE_NORMAL is below 4G. */
        if (!is_highmem_idx(zone))
                set_page_address(page, __va(pfn << PAGE_SHIFT));
#endif
}

요청한 페이지 디스크립터 하나를 초기화한다.

  • 코드 라인 4에서 페이지 디스크립터를 0으로 채운다.
  • 코드 라인 5에서 페이지 디스크립터에 존과 노드 및 섹션 정보를 기록한다.
  • 코드 라인 6에서 페이지 참조 카운터를 1로 초기화한다.
  • 코드 라인 7에서 페이지 매핑 카운터를 -1로 초기화한다.
  • 코드 라인 8에서 last_cpuid에 해당하는 비트를 1로 채워 초기화한다.
  • 코드 라인 9에서 KASAN(Kernel Address SANitizer)용 플래그 비트를 초기화한다.
  • 코드 라인 11에서 페이지의 lru 연결용 노드를 초기화한다.
  • 코드 라인 12~16에서 zone이 highmem이 아니면 page->virtual에  pfn을 가상주소로 바꾼 주소로 설정한다.

 

참고