compressed/head.S – wont_overwrite

wont_overwrite:

compressed 커널 영역과 decompressed 커널 영역이 더 이상 중복되지 않는다고 판단하면 이 루틴으로 진입한다.

  •  이 루틴에 진입했다는 의미는 이미 relocation이 필요 없는 상황이었거나 relocation이 완료된 경우다.
  • 이 루틴부터는 본격적으로 압축을 풀고 커널 시작(start_kernel) 으로 가기위한 약간의 보정 활동을 한다.
wont_overwrite:
/*
 * If delta is zero, we are running at the address we were linked at.
 *   r0  = delta
 *   r2  = BSS start
 *   r3  = BSS end
 *   r4  = kernel execution address (possibly with LSB set)
 *   r5  = appended dtb size (0 if not present)
 *   r7  = architecture ID
 *   r8  = atags pointer
 *   r11 = GOT start
 *   r12 = GOT end
 *   sp  = stack pointer
 */
                orrs    r1, r0, r5
                beq     not_relocated

                add     r11, r11, r0
                add     r12, r12, r0
  • if (delta = 0) & (r5(dtb size) = 0)
    • delta가 0이면서 dtb_size도 0이면 BSS 및 GOT 영역들의 위치가 바뀔 필요가 없으므로 리로케이션 로직을 수행하지 않았던 경우이다.
  • r11, r12
    • GOT 영역을 알리는 시작과 끝에 delta를 더해 교정함.

relocation이 수행된 경우의 보정 활동

CONFIG_ZBOOT_ROM 옵션에 의해 compressed 커널이 ROM에서 시작한 경우와 부트로더에 의해 RAM에 로드되어 시작한 경우를 나누어 분석한다.

#ifndef CONFIG_ZBOOT_ROM
                /*
                 * If we're running fully PIC === CONFIG_ZBOOT_ROM = n,
                 * we need to fix up pointers into the BSS region.
                 * Note that the stack pointer has already been fixed up.
                 */
                add     r2, r2, r0
                add     r3, r3, r0

                /*
                 * Relocate all entries in the GOT table.
                 * Bump bss entries to _edata + dtb size
                 */
1:              ldr     r1, [r11, #0]           @ relocate entries in the GOT
                add     r1, r1, r0              @ This fixes up C references
                cmp     r1, r2                  @ if entry >= bss_start &&
                cmphs   r3, r1                  @       bss_end > entry
                addhi   r1, r1, r5              @    entry += dtb size
                str     r1, [r11], #4           @ next entry
                cmp     r11, r12
                blo     1b

                /* bump our bss pointers too */
                add     r2, r2, r5
                add     r3, r3, r5

decompressed 커널이 RAM에서 동작한 경우이다

  •  BSS 영역 교정
    • r2, r3
      • BSS 영역을 알리는 시작과 끝에 delta를 더해 교정한다.
  • GOT 영역내 엔트리 교정
    • GOT 엔트리는 재배치가 일어나면 연결된 주소가 다 엉뚱한 곳을 가리키기 때문에 링크가 존재하는 모든 엔트리들은 delta 및 dtb size 만큼 더해서 교정해야 한다.
    • 엔트리들은 PLT를 가리킬 수도 있고, 외부 함수를 가리킬 수 있다.
    • 그러나 zImage는 외부함수가 연결될 가능성은 없고 내부에서 BSS 영역에 존재하는 관련 함수등이 있을거라 예상된다.
    • ldr r1, [r11, #0]
    • r11(GOT start) 주소에서 GOT 엔트리를 읽어 r1에 대입
      • r1(엔트리) += delta로 교정
    • dtb size 만큼 추가로 교정할지 판단
    • str r1, [r11], #4
    • 교정을 한 후 4바이트 만큼 이동
    • 다음 엔트리 주소가 마지막이 아니면 다시 레이블 1로 이동하여 계속 루프를 돈다.
#else

                /*
                 * Relocate entries in the GOT table.  We only relocate
                 * the entries that are outside the (relocated) BSS region.
                 */
1:              ldr     r1, [r11, #0]           @ relocate entries in the GOT
                cmp     r1, r2                  @ entry < bss_start ||
                cmphs   r3, r1                  @ _end < entry
                addlo   r1, r1, r0              @ table.  This fixes up the
                str     r1, [r11], #4           @ C references.
                cmp     r11, r12
                blo     1b
#endif

decompressed 커널이 ROM에서 동작한 경우이다.

  • BSS 영역 교정
    • r2, r3
      • BSS 영역을 알리는 시작과 끝에 DTB 사이즈만큼 추가 교정(delta는 이미 추가하였었음)
  • GOT 영역내 엔트리 교정
    • ROM또는 플래쉬에서 직접 수행되는 경우
    • r11(GOT start)주소에서 GOT엔트리를 읽어 r1에 대입
    • if r1(엔트리) < r2(bss_start) ||
      • r3(_end) < r1(엔트리)
        • r1 += r0(delta)
    • 직역)
      • if r1 >= r2 then
        • if r3 < r1 then
          • r1 += r0
        • else
          • r1 += r0

not_relocated:

not_relocated:  mov     r0, #0
1:              str     r0, [r2], #4            @ clear bss
                str     r0, [r2], #4
                str     r0, [r2], #4
                str     r0, [r2], #4
                cmp     r2, r3
                blo     1b
  • BSS 영역 데이터를0x00으로 초기화 한다.
    • rpi: BSS 공간이 0x1c개 만큼인데 일부 over해서 초기화 함
    • 사실 16바이트 align을 기대하고 readelf로 분석을 해보니 해당 공간은 8바이트 align 상태여서 뭔가 맞지 않는다는 느낌이다. 어쨋든 그 최대 파괴되는 일부 바이트가 위치한 공간은 스택 중 가장 아래에 위치한 공간이고 확실히 아직 스택을 많이 사용한 적이 없으므로 전혀 상관 없다고 함.
    • 캐시가 on된 상태에서 캐시라인을 충분히 사용하므로 4번의 Word를 연속 저장하여 성능을 높이려 함
    • .align
      • default align은 32비트 시스템은 4바이트, 64비트 시스템은 8바이트
      • .align 지시자는 인수의 해석을 시스템에 따라 달리한다.
      • 어떤 시스템에서는 n 바이트 단위로 지정을 하고, 또 다른 시스템에서는 2^n 바이트 단위로 동작한다.
      • – n 바이트 단위:
        • a29k, hppa, m68k, sparc, Xtensa, Renesas / SuperH SH, i386 using ELF format
      • – 2^n 바이트 단위:
        • i386 using a.out (old format), arm, strong ARMv3
        • rpi2와 같은 ARM은 2^N 바이트를 사용한다.
      • 대문자로 표현된 ALIGN(8)과 같은 매크로는 8바이트를 의미.
                /*
                 * Did we skip the cache setup earlier?
                 * That is indicated by the LSB in r4.
                 * Do it now if so.
                 */
                tst     r4, #1
                bic     r4, r4, #1
                blne    cache_on
  • 유보된 캐시(LSB 0 in r4)가 있는 경우 cache_on을 호출
  • r4 레지스터는 커널 시작 주소로 계속 사용되어야 하므로 하위 1비트를 다른 용도로 잠시 사용하였던 것을 없애기 위해 clear 해야 한다.
    • 주의: blne 명령전에 사용했던 tst는 cmp 명령과 서로 다른 결과를 갖는다.
  • tst 명령은 r4와 #1을 and 연산한 결과가 0이 아닌경우 cache_on을 호출
/*
 * The C runtime environment should now be setup sufficiently.
 * Set up some pointers, and start decompressing.
 *   r4  = kernel execution address
 *   r7  = architecture ID
 *   r8  = atags pointer
 */
                mov     r0, r4
                mov     r1, sp                  @ malloc space above stack
                add     r2, sp, #0x10000        @ 64k max
                mov     r3, r7
                bl      decompress_kernel
                bl      cache_clean_flush
                bl      cache_off
                mov     r1, r7                  @ restore architecture number
                mov     r2, r8                  @ restore atags pointer
  • decompress_kernel(kernel execution address, sp, sp+0x10000, architecture ID) 호출
  • decompress가 끝났으면 커널 설정을 하러가기 전에 캐쉬를 클린 + 플러쉬 + off 한다.
#ifdef CONFIG_ARM_VIRT_EXT
                mrs     r0, spsr                @ Get saved CPU boot mode
                and     r0, r0, #MODE_MASK
                cmp     r0, #HYP_MODE           @ if not booted in HYP mode...
                bne     __enter_kernel          @ boot kernel directly

                adr     r12, .L__hyp_reentry_vectors_offset
                ldr     r0, [r12]
                add     r0, r0, r12

                bl      __hyp_set_vectors
                __HVC(0)                        @ otherwise bounce to hyp mode

                b       .                       @ should never be reached

                .align  2
.L__hyp_reentry_vectors_offset: .long   __hyp_reentry_vectors - .
#else
                b       __enter_kernel
#endif
  • 하이퍼바이저가 정의되어 있는 경우
    • 실제 상태 레지스터 값을 확인하여 하이퍼모드가 동작하지 않는 경우 __enter_kernel 루틴으로 점프한다.
    • 만일 하이퍼모드가 동작중이면 .L__hyp_reentry_vectors_offset의 값을 읽어 offset을 보정한 후 __hyp_set_vectors 함수를 호출하여 하이퍼바이저의 벡터 오프셋을 설정하고 하이퍼바이저에 의해 __enter_kernel(?)로 점프한다.
  • 하이퍼바이저가 커널에 정의되어 있지 않는 경우 곧바로 __enter_kernel 루틴으로 점프한다.

 

참고

compressed/head.S – restart:

restart:

restart:        adr     r0, LC0
                ldmia   r0, {r1, r2, r3, r6, r10, r11, r12}
                ldr     sp, [r0, #28]

                /*
                 * We might be running at a different address.  We need
                 * to fix up various pointers.
                 */

                sub     r0, r0, r1              @ calculate the delta offset
                add     r6, r6, r0              @ _edata
                add     r10, r10, r0            @ inflated kernel size location

#ifndef CONFIG_ZBOOT_ROM
                /* malloc space is above the relocated stack (64k max) */
                add     sp, sp, r0
                add     r10, sp, #0x10000
#else
                /*
                 * With ZBOOT_ROM the bss/stack is non relocatable,
                 * but someone could still run this code from RAM,
                 * in which case our reference is _edata.
                 */
                mov     r10, r6
#endif

                mov     r5, #0                  @ init dtb size to 0
  • restart:
    • 이 위치는 압축이 풀려지는 위치와 현재 압축된 커널의 공간이 중복되는 경우 현재의 코드(압축된커널 + 재배치코드(restart-끝))를 이동시킨 후 다시 restart 이 위치로 점프된다.
  • ldr r0, LC0
    • 실제 동작중인 LC0의 주소를 r0에 읽어온다.
    • ldr 명령을 사용하지 않는 이유는 코드가 relocation 되어 다시 이 루틴에 진입하는 경우 LC0의 위치가 변한다. 따라서 현재 코드가 수행되고 있는 위치(pc)로 부터 LC0까지의 위치를 상대적으로 계산하여 주소를 알아와야 하기 때문에 adr 명령을 사용해야 한다. ldr 명령은 컴파일 시 계산된 주소로부터 LC0까지의 상대적인 주소 값을 반영하여 가져오는것과는 비교되어야 한다.
  • ldr sp, [r0, #28]
    • 스택을 설정한다. sp <- L_user_stack_end(stack + 4K)
  • delta 값 계산
    • r0에 현재 실행되는 상태의 LC0의 주소 값이 담기고, r1은 처음 이미지가 만들어질 때 당시의 주소 값(0을 기준으로)이 담겨 있고 이 둘의 차이 값이 r0(delta) 값이 된다.이 델타 값으로 Image의 위치를 가리키는 레지스터들을 보정하는데 사용한다.
    • 코드가 재배치가 이루어진 후 이곳으로 재진입하게 되면 또 한 번 delta가 바뀌고 이 때에도 바뀐 주소로인해 관련 레지스터들의 주소를 교정한다.
  • r6 = _edata + delta
  • r10 = _input_data_end-4 + delta
    • _input_data_end-4 = compressed 커널(piggy)의 마지막 어드레스를 의미함.
    • 데이터 내용은 decompressed 커널의 길이.
                /*
                 * The kernel build system appends the size of the
                 * decompressed kernel at the end of the compressed data
                 * in little-endian form.
                 */
                ldrb    r9, [r10, #0]
                ldrb    lr, [r10, #1]
                orr     r9, r9, lr, lsl #8
                ldrb    lr, [r10, #2]
                ldrb    r10, [r10, #3]
                orr     r9, r9, lr, lsl #16
                orr     r9, r9, r10, lsl #24
  • decompressed 커널 길이가 기록될 때 리틀엔디안으로 기록이되며 이를 엔디안과 관계 없이 알아온다.
    • r9(커널길이) = (r10(리틀엔디안으로 길이가 저장된 주소)
      • 예) 길이=0x44332211 (리틀엔디안형식으로 기록된 저장소: 11 22 33 44)
        • 0x00000011 <- 처음 바이트만 r9에 가져옮
        • 0x00000011 <- 처음 바이트만 r9에 가져옮
        • 0x00000022 <- 두번째 바이트를 lr에 가져옮
        • 0x00002211 <- lr레지스터 내용을 8회 shift후 r9 레지스터와 or 연산
        • 0x00000033 <- 세번째 바이트를 lr에 가져옮
        • 0x00000044 <- 네번째 바이트를 r10에 가져옮
        • 0x00332211 <- lr레지스터 내용을 16회 shift후 r9 레지스터와 or 연산
        • 0x44332211 <- r10레지스터 내용을 24회 shift후 or r9 레지스터와 연산
    • 만일 SoC가 리틀엔디안으로 구동되고 있는 경우 변환전과 변환후가 동일.
    • 만일 SoC가 빅엔디안으로 구동되고 있는 경우 변환전과 변환후가 다름.
#ifndef CONFIG_ZBOOT_ROM
                /* malloc space is above the relocated stack (64k max) */
                add     sp, sp, r0
                add     r10, sp, #0x10000
#else
                /*
                 * With ZBOOT_ROM the bss/stack is non relocatable,
                 * but someone could still run this code from RAM,
                 * in which case our reference is _edata.
                 */
                mov     r10, r6
#endif
  • CONFIG_ZBOOT_ROM
    • ROM이나 플래쉬에 있는 compressed 이미지를 RAM으로복사하지 않고 직접 구동하여 사용하려고 할 때 사용.
    • rpi2:
      • 각 패키징 업체가 준비해 놓은 설정을 사용하는데 특정패키징을 사용하지 않고 다운로드한 라즈베리파이 원 소스의 기본 설정으로빌드하였을 때 이 옵션은 사용하지 않는 것으로 되어 있음.
      • 라즈베리안이라는 기본 패키징도 마찬가지로 이 옵션을 사용하지 않음
  • RAM에서 구동하는 경우
    • 기존에 설정한 스택 주소를 delta만큼 재조정
    • r10에 64K의 버퍼 마지막을 저장(delta 재조정된 스택 + 64K)
  • ROM에서 구동하는 경우
    • 부트롬 또는 플래쉬에서 이미지 코드를 직접 동작시킨 경우 r10(이미지의 끝)에 r6(_edata)를 대입한다.

DTB 사이즈 관련

                mov     r5, #0                  @ init dtb size to 0

#ifdef CONFIG_ARM_APPENDED_DTB
/*
 *   r0  = delta
 *   r2  = BSS start
 *   r3  = BSS end
 *   r4  = final kernel address (possibly with LSB set)
 *   r5  = appended dtb size (still unknown)
 *   r6  = _edata
 *   r7  = architecture ID
 *   r8  = atags/device tree pointer
 *   r9  = size of decompressed image
 *   r10 = end of this image, including  bss/stack/malloc space if non XIP
 *   r11 = GOT start
 *   r12 = GOT end
 *   sp  = stack pointer
 *
 * if there are device trees (dtb) appended to zImage, advance r10 so that the
 * dtb data will get relocated along with the kernel if necessary.
 */


                ldr     lr, [r6, #0]
#ifndef __ARMEB__
                ldr     r1, =0xedfe0dd0         @ sig is 0xd00dfeed big endian
#else
                ldr     r1, =0xd00dfeed
#endif
                cmp     lr, r1
                bne     dtb_check_done          @ not found
  • CONFIG_ARM_APPENDED_DTB
    • compressed 커널뒤에 DTB를 붙여서 제공하는 경우 enable 하여 빌드한다.
    • 부트로더는 커널이 DTB를 찾을 수 있도록 compressed 커널의 바로 뒤에 공백없이 붙여야 한다.
  • DTB(Device Tree Blob)가 compressed 커널 뒤에 존재하는지 확인한다
    • DTB 매직넘버를 찾아낸다.
    • r6(_edata = _bss_start)가 가리키는 곳에 0xd00dfeed라는 DTB magic number)가 있는지 확인하여 없으면 dtb_check_done 레이블로 이동
#ifdef CONFIG_ARM_ATAG_DTB_COMPAT
                /*
                 * OK... Let's do some funky business here.
                 * If we do have a DTB appended to zImage, and we do have
                 * an ATAG list around, we want the later to be translated
                 * and folded into the former here. No GOT fixup has occurred
                 * yet, but none of the code we're about to call uses any
                 * global variable.
                */

                /* Get the initial DTB size */
                ldr     r5, [r6, #4]

#ifndef __ARMEB__

                /* convert to little endian */
                eor     r1, r5, r5, ror #16
                bic     r1, r1, #0x00ff0000
                mov     r5, r5, ror #8
                eor     r5, r5, r1, lsr #8
#endif
  • CONFIG_ARM_ATAG_DTB_COMPAT
    • DTB를 뿐만 아니라 옛날 부트로더가 사용한 ATAG도 지원한다.
    • DTB를 지원하지 못하는 오래된 부트로더로 부터 전달받은 ATAG로 부터 받은 정보는 메모리 정보, 램디스크 주소, 커널 cmdline, etc… 등이 있고 이들 정보는 모두 DTB로 변환되어 사용한다.
    • 특이하게 DTB와 ATAG 둘 다 사용해야 하는 경우가 있다. 이런 경우 DTB 사이즈가 부족한 경우가 있어 이를 일정부분 확장해서 사용해야 한다.
    • 처음에 하드코딩된 64k의 DTB 사이즈 때문에 ATAG 정보를 추가하면 용량이 부족해서 DTB 사이즈를 읽어서 확장(32K ~ 1M)시키는 코드가 추가되었다.
    • 참고: ATAG_DTB_COMPAT: remove the DT workspace’s hardcoded 64K… 
  • ldr, r5, [r6, #4]
    • r5 <- 초기 DTB size를 읽어온다. (fdt_header->dt_total_size(fdt_size))
  • 시스템이 리틀엔디안으로 동작하는 경우 읽은 DTB 사이즈(DTB는 항상 빅엔디안으로 저장)를 리틀엔디안으로 변환.
  • 다음의 알고리즘을 사용하여 빅엔디안 바이트들을 리틀 엔디안으로 변환한다. (A=msb, D=lsb)
 *         r5          A    B    C    D
 *              ror    C    D    A    B
 *              eor   A^C  B^D  C^A  D^B
 *              bic   A^C   0   C^A  D^B
 *              ror    D    A    B    C       
 *              lsr    0   A^C   0   C^A
 *         r5          D    C    B    A
                /* 50% DTB growth should be good enough */
                add     r5, r5, r5, lsr #1
                /* preserve 64-bit alignment */
                add     r5, r5, #7
                bic     r5, r5, #7
                /* clamp to 32KB min and 1MB max */
                cmp     r5, #(1 << 15)
                movlo   r5, #(1 << 15)
                cmp     r5, #(1 << 20)
                movhi   r5, #(1 << 20)
                /* temporarily relocate the stack past the DTB work space */
                add     sp, sp, r5
  • DTB 사이즈를 50% 증가. (r5 += shift to right) 증가시킨다.
  • 사이즈를 7 byte를 더한 후 8 byte round down align 한다.
    • (64비트 align 하기위해 7바이트를 추가)
    • 이렇게 7바이트를 더해놔야 마지막 데이터를 안전하게(8바이트) 꺼낼 수 있다.
    • 데이터를 읽는 루틴에서 항상 8바이트씩 읽기 때문에 8바이트 align이 중요하다.
  • DTB 사이즈가 32KB보다 작으면 32K로 설정, 1M보다 크면 1M로 제한한다
    • clamp는 하위와 상위 범위 제한을 두어 벗어나는 경우 잘라내어 항상 범위내에 있을 수 있게 한다.
  • 스택을 임시적으로 DTB 데이터 작업 영역 위로 설정한다.
    • C 함수를 사용할 때에는 스택이 필요하다.
                stmfd   sp!, {r0-r3, ip, lr}
                mov     r0, r8
                mov     r1, r6
                mov     r2, r5
                bl      atags_to_fdt

                /*
                 * If returned value is 1, there is no ATAG at the location
                 * pointed by r8.  Try the typical 0x100 offset from start
                 * of RAM and hope for the best.
                 */
                cmp     r0, #1
                sub     r0, r4, #TEXT_OFFSET
                bic     r0, r0, #1
                add     r0, r0, #0x100
                mov     r1, r6
                mov     r2, r5
                bleq    atags_to_fdt

                ldmfd   sp!, {r0-r3, ip, lr}
                sub     sp, sp, r5
#endif

                mov     r8, r6                  @ use the appended device tree
  • stmfd
    • 레지스터 몇 개를 잠시 백업 목적으로 스택에 보관한다.
    • stmfd = full descending = decrement before
  • atags_to_fdt()
    • ATAG 정보가 별견되면 분석하여 DTB로 변환한다.
    • 전달하는 인수:
      • r0:      void *atag_list         = ATAG 또는 DTB가 놓인 주소
      • r1:      void *fdt                     = _edata (커널 데이터의 마지막 주소)
      • r2:      int total_space         = DTB 사이즈
  • 만일 설정된 장소에서 ATAG를 발견하지 못하면 1이 리턴되는데 이 때 다시 한 번 시작램+0x100에서 ATAG 발견을 시도한다.
    • 물론 리눅스는 ATAG를 시작램(물리메모리)+0x100에 위치하도록 추천함.
    • r4=decompressed 커널 시작 주소
  • ldmfd
    • 백업해둔 레지스터를 다시 복원한다.
  • 임시로 스택을 사이즈 조절된 DTB 만큼 위로 올려놨던 것을 다시 원위치 시킨다.

bss 사용 시 DTB 손상 여부 판단

decompress_head.s분석12

 

bss 사용 시 DTB 손상을 보호하기 위해 공간(gap) 추가

 

decompress_head.s분석13

 

  • DTB가 decompressed kernel의 bss 영역안에 위치하지 않도록 하여야 한다.
  • .bss 영역 사이즈가 재배치 코드 사이즈(_edata – wont_overwrite)보다 큰 경우 재배치 코드 뿐만 아니라 DTB 영역도 bss 영역에 들어가게된다.
  • relocation 되고 압축이 풀려 decompressed 커널이 동작할 때에 relocation code는 삭제되어도 상관없다. 그러나 DTB 영역은 보존된 상태에 있어야 한다.
  • decompressed 커널이 동작하면 decompressed 커널의 바로 상단에 위치한 bss 영역이 사용되면서 그 영역이 DTB 영역과 겹치는 경우 DTB가 손상이 갈 것이다.
  • 그렇기 때문에 bss 영역과 relocation 코드가 겹치는 것은 상관이 없지만 DTB 코드영역까지 겹치면 안되기 때문에 이러한 영우를 피하기 위해 bss 사이즈가 relocation 영역 크기보다 큰 경우에는 그 초과되는 사이즈 만큼 r9(decompressed 커널 사이즈)에 추가하여 relocation 코드가 더 위쪽으로 자리할 수 있도록 한다.
                /*
                 * Make sure that the DTB doesn't end up in the final
                 * kernel's .bss area. To do so, we adjust the decompressed
                 * kernel size to compensate if that .bss size is larger
                 * than the relocated code.
                 */
                ldr     r5, =_kernel_bss_size
                adr     r1, wont_overwrite
                sub     r1, r6, r1
                subs    r1, r5, r1
                addhi   r9, r9, r1
  • ldr r5, =_kernel_bss_size
  • adr     r1, wont_overwrite
    • r1에 wont_overwrite 주소를 일단 담는다.
  • sub     r1, r6, r1
    • r1에 relocation 코드의 사이즈를 담는다.
      • r1 = _edata – addr(wont_overwrite)
  • subs    r1, r5, r1
    • kernel bss size에서 r1(relocation 코드 사이즈)을 뺀다.
  • addhi   r9, r9, r1
    • kernel bss size가 r1보다 크면 DTB 영역의 시작 주소가 bss 영역에 들어가게되므로 이를 방지하기 위해 decompressed 커널 사이즈가 담긴 r9을 r1(bss가 재배치 코드를 초과한 사이즈) 만큼 더 키운다.

DTB 사이즈를 다시 읽어 몇 개 주소에 반영

위에서 CONFIG_ARM_ATAG_DTB_COMPAT 옵션을 사용하면서 DTB 사이즈를 읽어온 것과 달리 CONFIG_ARM_ATAG_DTB_COMPAT 옵션을 사용하지 않더라도 DTB를 사용하는 경우 DTB 사이즈만큼 relocation 주소들을 바꿔야 한다.

decompress_head.s분석14

 

                /* Get the current DTB size */
                ldr     r5, [r6, #4]
#ifndef __ARMEB__
                /* convert r5 (dtb size) to little endian */
                eor     r1, r5, r5, ror #16
                bic     r1, r1, #0x00ff0000
                mov     r5, r5, ror #8
                eor     r5, r5, r1, lsr #8
#endif

                /* preserve 64-bit alignment */
                add     r5, r5, #7
                bic     r5, r5, #7

                /* relocate some pointers past the appended dtb */
                add     r6, r6, r5
                add     r10, r10, r5
                add     sp, sp, r5
dtb_check_done:
#endif
  • ldr     r5, [r6, #4]
    • dtb 사이즈를 다시 r5에 로드한다.
    • 다시 아키텍처가 빅엔디안인 경우 리틀엔디안으로 컨버팅한다.
    • 64비트 alignment를 수행한다.
  • 다음 3 개의 주소에 대해 DTB 사이즈를 더해준다.
    • r6(_edata)는 relocation 시 복사할 compressed 커널의 끝을 가리킨다.
      • _end를 사용하지 않고 _data를 사용한 이유
        • _data는 compressed 커널의 data 섹션의 끝 부분으로 compressed 커널의 bss 영역 시작과 같다. relocation 시 compressed 커널의 bss 영역은 더 이상 사용할 데이터가 없으므로 복사를 하지 않아도 된다. 따라서 _end 대신 _data를 사용한다.
    • r10(_end)은 compressed 커널의 마지막 끝으로 XIP 커널로 빌드된 경우가 아니면 bss 및 스택을 포함한 끝 주소이다.
    • 스택을 DTB 위로 이동시킨다.

 

참고

Memblock – (2)

<kernel v5.10>

Early 메모리 할당

early 메모리 할당자로서 커널 부트업 프로세스 및 메모리 핫스왑(hot-swap) 과정에서 memblock API를 사용하여 메모리의 할당과 해제를 요청하게 된다.

 

paging_init()이 완료되기 전 memblock 사용

paging_init()이 완료되기 전에도 memblock_add() 함수로 메모리를 추가할 수 있고, memblock_reserve() 함수로 reserve 공간을 등록할 수 있다. 그러나 커널 메모리에 대해 아직 리니어 매핑되지 않은 상태이므로 memblock_alloc()으로 메모리 할당을 할 수 없다. 따라서 paging_init()이 완료된 후에 memblock_alloc()을 사용하여야 한다.

 

다음 그림은 정규 메모리 할당자가 준비되기 전의 주요 메모리 매핑과 할당자들의 단계별 상태를 보여준다.

 

다음 그림은 memblock 할당과 관련한 API들의 흐름을 보여준다.

 

memblock_alloc()

mm/memblock.c

static inline void * __init memblock_alloc(phys_addr_t size,  phys_addr_t align)
{
        return memblock_alloc_try_nid(size, align, MEMBLOCK_LOW_LIMIT,
                                      MEMBLOCK_ALLOC_ACCESSIBLE, NUMA_NO_NODE);
}

@align 단위로 정렬된 @size만큼의 memblock을 할당 요청한다. 성공 시 할당된 메모리의 가상 주소를 반환하고, 실패 시 null 을 반환한다.

  • MEMBLOCK_LOW_LIMIT(0)
    • 할당 시 top down으로 할당할 메모리의 아래쪽 끝을 제한한다.
  • MEMBLOCK_ALLOC_ACCESSIBLE(0)
    • 할당 시 memblock의 current_limit 값으로 제한한다.
  • NUMA_NO_NODE(-1)
    • 어떠한 노드에서 할당하든 제한 없이 요청한다.

 

memblock_alloc_try_nid()

mm/memblock.c

/**
 * memblock_alloc_try_nid - allocate boot memory block with panicking
 * @size: size of memory block to be allocated in bytes
 * @align: alignment of the region and block's size
 * @min_addr: the lower bound of the memory region from where the allocation
 *        is preferred (phys address)
 * @max_addr: the upper bound of the memory region from where the allocation
 *            is preferred (phys address), or %MEMBLOCK_ALLOC_ACCESSIBLE to
 *            allocate only from memory limited by memblock.current_limit value
 * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
 *
 * Public function, provides additional debug information (including caller
 * info), if enabled. This function zeroes the allocated memory.
 *
 * Return:
 * Virtual address of allocated memory block on success, NULL on failure.
 */
void * __init memblock_alloc_try_nid(
                        phys_addr_t size, phys_addr_t align,
                        phys_addr_t min_addr, phys_addr_t max_addr,
                        int nid)
{
        void *ptr;

        memblock_dbg("%s: %llu bytes align=0x%llx nid=%d from=%pa max_addr=%pa %pS\n",
                     __func__, (u64)size, (u64)align, nid, &min_addr,
                     &max_addr, (void *)_RET_IP_);
        ptr = memblock_alloc_internal(size, align,
                                           min_addr, max_addr, nid, false);
        if (ptr)
                memset(ptr, 0, size);

        return ptr;
}

노드 @nid에서 @align 단위로 정렬된 @size만큼의 memblock 영역을 할당 요청한다. 성공 시 할당된 메모리의 가상 주소를 반환하고, 실패 시 null 을 반환한다.

 

memblock_alloc_internal()

mm/memblock.c

/**
 * memblock_alloc_internal - allocate boot memory block
 * @size: size of memory block to be allocated in bytes
 * @align: alignment of the region and block's size
 * @min_addr: the lower bound of the memory region to allocate (phys address)
 * @max_addr: the upper bound of the memory region to allocate (phys address)
 * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
 * @exact_nid: control the allocation fall back to other nodes
 *
 * Allocates memory block using memblock_alloc_range_nid() and
 * converts the returned physical address to virtual.
 *
 * The @min_addr limit is dropped if it can not be satisfied and the allocation
 * will fall back to memory below @min_addr. Other constraints, such
 * as node and mirrored memory will be handled again in
 * memblock_alloc_range_nid().
 *
 * Return:
 * Virtual address of allocated memory block on success, NULL on failure.
 */
static void * __init memblock_alloc_internal(
                                phys_addr_t size, phys_addr_t align,
                                phys_addr_t min_addr, phys_addr_t max_addr,
                                int nid, bool exact_nid)
{
        phys_addr_t alloc;

        /*
         * Detect any accidental use of these APIs after slab is ready, as at
         * this moment memblock may be deinitialized already and its
         * internal data may be destroyed (after execution of memblock_free_all)
         */
        if (WARN_ON_ONCE(slab_is_available()))
                return kzalloc_node(size, GFP_NOWAIT, nid);

        if (max_addr > memblock.current_limit)
                max_addr = memblock.current_limit;

        alloc = memblock_alloc_range_nid(size, align, min_addr, max_addr, nid,
                     alloc = memblock_alloc_range_nid(size, align, 0, max_addr, nid,
                                                     exact_nid);

        /* retry allocation without lower limit */
         if (!alloc && min_addr)
                alloc = memblock_alloc_range_nid(size, align, 0, max_addr, nid,
                                                exact_nid);

        if (!alloc)
            return NULL;

        return phys_to_virt(alloc);
}

요청한 노드 @nid에서 @align 단위로 정렬된 @size만큼의 memblock 영역을 할당 요청한다. 할당된 물리 주소를 가상 주소로 변환하여 반환한다.

  • 코드 라인 13~14에서 정규 메모리 슬랩 할당자가 동작한 이후에 이 함수가 호출된 경우 슬랩할당자를 사용하여 메모리를 할당한다.
  • 코드 라인 16~17에서 @max_addr가 memblock의 current_limit을 초과하지 않도록 제한한다.
  • 코드 라인 19~21에서 제한된 limit 범위내에서 메모리 할당을 시도한다.
  • 코드 라인 24~26에서 할당이 실패한 경우 limit 범위를 해제한 다음 메모리 할당을 시도한다.
  • 코드 라인 28~29에서 그래도 할당이 실패한 경우 null을 반환한다.
  • 코드 라인 31에서 할당이 성공한 경우 가상 주소(lm)로 변환하여 반환한다.

 

memblock_alloc_range_nid()

mm/memblock.c

/**
 * memblock_alloc_range_nid - allocate boot memory block
 * @size: size of memory block to be allocated in bytes
 * @align: alignment of the region and block's size
 * @start: the lower bound of the memory region to allocate (phys address)
 * @end: the upper bound of the memory region to allocate (phys address)
 * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
 * @exact_nid: control the allocation fall back to other nodes
 *
 * The allocation is performed from memory region limited by
 * memblock.current_limit if @end == %MEMBLOCK_ALLOC_ACCESSIBLE.
 *
 * If the specified node can not hold the requested memory and @exact_nid
 * is false, the allocation falls back to any node in the system.
 *
 * For systems with memory mirroring, the allocation is attempted first
 * from the regions with mirroring enabled and then retried from any
 * memory region.
 *
 * In addition, function sets the min_count to 0 using kmemleak_alloc_phys for
 * allocated boot memory block, so that it is never reported as leaks.
 *
 * Return:
 * Physical address of allocated memory block on success, %0 on failure.
 */
phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
                                        phys_addr_t align, phys_addr_t start,
                                        phys_addr_t end, int nid,
                                        bool exact_nid)
{
        enum memblock_flags flags = choose_memblock_flags();
        phys_addr_t found;

        if (WARN_ONCE(nid == MAX_NUMNODES, "Usage of MAX_NUMNODES is deprecated. Use NUMA_NO_NODE instead\n"))
                nid = NUMA_NO_NODE;

        if (!align) {
                /* Can't use WARNs this early in boot on powerpc */
                dump_stack();
                align = SMP_CACHE_BYTES;
        }

again:
        found = memblock_find_in_range_node(size, align, start, end, nid,
                                            flags);
        if (found && !memblock_reserve(found, size))
                goto done;

        if (nid != NUMA_NO_NODE && !exact_nid) {
                found = memblock_find_in_range_node(size, align, start,
                                                    end, NUMA_NO_NODE,
                                                    flags);
                if (found && !memblock_reserve(found, size))
                        goto done;
        }

        if (flags & MEMBLOCK_MIRROR) {
                flags &= ~MEMBLOCK_MIRROR;
                pr_warn("Could not allocate %pap bytes of mirrored memory\n",
                        &size);
                goto again;
        }

        return 0;

done:
        /* Skip kmemleak for kasan_init() due to high volume. */
        if (end != MEMBLOCK_ALLOC_KASAN)
                /*
                 * The min_count is set to 0 so that memblock allocated
                 * blocks are never reported as leaks. This is because many
                 * of these blocks are only referred via the physical
                 * address which is not looked up by kmemleak.
                 */
                kmemleak_alloc_phys(found, size, 0, 0);

        return found;
}

요청한 노드 @nid에서 @align 단위로 정렬된 @size만큼의 memblock 영역을 @start ~ @end 범위에서 할당 요청한다. 성공한 경우 할당된 물리 주소가 반환된다.

  • 코드 라인 6에서 할당에 사용할 플래그를 가져온다. (미러 플래그)
  • 코드 라인 9~10에서 nid 값으로 MAX_NUMNODES(디폴트=16)을 사용한 경우 대신 NUMA_NO_NODE(-1) 값을 사용해야 한다. 이를 메시지로 경고한다.
  • 코드 라인 12~16에서 @align 요청 값이 없는 경우 호출한 함수를 알아보기 위해 스택 덤프를 수행한 후 @aline 값에 L1 캐시 단위로 정렬하도록 SMP_CACHE_BYTES(ARM64:64) 값을 사용한다.
  • 코드 라인 18~22에서 again: 레이블이다. 빈 영역이 있는지 찾아보고 이를 할당하기 위해 reserve 한다.
  • 코드 라인 24~30에서 @nid가 지정되었지만, @exact_nid가 false인 경우 노드 제한을 풀고 다시 한 번 빈 영역이 있는지 찾아본 후 이를 할당하기 위해 reserve 한다.
  • 코드 라인 32~37에서 시스템 메모리에 미러 설정이 있는 경우 미러 영역이 아닌 곳에서도 할당을 시도하기 위해 미러 플래그를 제거한 후 다시 시도하기 위해 again: 레이블로 이동한다.
  • 코드 라인 39에서 최종적으로 할당할 영역을 못찾은 경우 0을 반환한다.
  • 코드 라인 41~52에서 out: 레이블이다. 정상적으로 할당한 경우 찾은 물리 주소를 반환한다.

 

memblock_reserve()

mm/memblock.c

int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
        phys_addr_t end = base + size - 1;

        memblock_dbg("memblock_reserve: [%pa-%pa] %pF\n",
                     &base, &end, (void *)_RET_IP_);

        return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
}

물리 주소 @base 부터 @size 만큼의 영역을 memblock의 reserved 영역에 추가한다.

 


Memblock 빈 영역 찾기

memblock_find_in_range()

mm/memblock.c

/**
 * memblock_find_in_range - find free area in given range
 * @start: start of candidate range
 * @end: end of candidate range, can be %MEMBLOCK_ALLOC_ANYWHERE or
 *       %MEMBLOCK_ALLOC_ACCESSIBLE
 * @size: size of free area to find
 * @align: alignment of free area to find
 *
 * Find @size free area aligned to @align in the specified range.
 *
 * Return:
 * Found address on success, 0 on failure.
 */
phys_addr_t __init_memblock memblock_find_in_range(phys_addr_t start,
                                        phys_addr_t end, phys_addr_t size,
                                        phys_addr_t align)
{
        phys_addr_t ret;
        enum memblock_flags flags = choose_memblock_flags();

again:
        ret = memblock_find_in_range_node(size, align, start, end,
                                            NUMA_NO_NODE, flags);

        if (!ret && (flags & MEMBLOCK_MIRROR)) {
                pr_warn("Could not allocate %pap bytes of mirrored memory\n",
                        &size);
                flags &= ~MEMBLOCK_MIRROR;
                goto again;
        }

        return ret;
}

@start ~ @end 범위에서 @align 요청된 @size 만큼의 공간을 reserved memblock 타입에서 찾는다.

  • 미러 플래그가 요청된 상태에서 공간을 찾지 못한 경우 미러 플래그를 제거하고 다시 공간을 찾아본다.

 

memblock_find_in_range_node()

mm/memblock.c

/**
 * memblock_find_in_range_node - find free area in given range and node
 * @size: size of free area to find
 * @align: alignment of free area to find
 * @start: start of candidate range
 * @end: end of candidate range, can be %MEMBLOCK_ALLOC_ANYWHERE or
 *       %MEMBLOCK_ALLOC_ACCESSIBLE
 * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
 * @flags: pick from blocks based on memory attributes
 *
 * Find @size free area aligned to @align in the specified range and node.
 *
 * When allocation direction is bottom-up, the @start should be greater
 * than the end of the kernel image. Otherwise, it will be trimmed. The
 * reason is that we want the bottom-up allocation just near the kernel
 * image so it is highly likely that the allocated memory and the kernel
 * will reside in the same node.
 *
 * If bottom-up allocation failed, will try to allocate memory top-down.
 *
 * Return:
 * Found address on success, 0 on failure.
 */
phys_addr_t __init_memblock memblock_find_in_range_node(phys_addr_t size,
                                        phys_addr_t align, phys_addr_t start,
                                        phys_addr_t end, int nid,
                                        enum memblock_flags flags)
{
        phys_addr_t kernel_end, ret;

        /* pump up @end */
        if (end == MEMBLOCK_ALLOC_ACCESSIBLE ||
            end == MEMBLOCK_ALLOC_KASAN)
                end = memblock.current_limit;

        /* avoid allocating the first page */
        start = max_t(phys_addr_t, start, PAGE_SIZE);
        end = max(start, end);
        kernel_end = __pa_symbol(_end);

        /*
         * try bottom-up allocation only when bottom-up mode
         * is set and @end is above the kernel image.
         */
        if (memblock_bottom_up() && end > kernel_end) {
                phys_addr_t bottom_up_start;

                /* make sure we will allocate above the kernel */
                bottom_up_start = max(start, kernel_end);

                /* ok, try bottom-up allocation first */
                ret = __memblock_find_range_bottom_up(bottom_up_start, end,
                                                      size, align, nid, flags);
                if (ret)
                        return ret;

                /*
                 * we always limit bottom-up allocation above the kernel,
                 * but top-down allocation doesn't have the limit, so
                 * retrying top-down allocation may succeed when bottom-up
                 * allocation failed.
                 *
                 * bottom-up allocation is expected to be fail very rarely,
                 * so we use WARN_ONCE() here to see the stack trace if
                 * fail happens.
                 */
                WARN_ONCE(IS_ENABLED(CONFIG_MEMORY_HOTREMOVE),
                          "memblock: bottom-up allocation failed, memory hotremove may be affected\nn
");
        }

        return __memblock_find_range_top_down(start, end, size, align, nid,
                                              flags);
}

요청한 노드 @nid에서 @start ~ @end 범위에서 @align 요청된 @size 만큼의 공간을 reserved memblock 타입에서 찾는다.

  • 코드 라인 9~11에서 limit 범위내에서 검색을 하라고 한 경우 @end를 memblock의 current_limit로 제한한다.
  • 코드 라인 14에서 @start는 0 페이지를 제외한다.
  • 코드 라인 22~48에서 아키텍처에 따라 bottom-up으로 검색을 시도한다. 이 때 @end가 커널 공간보다는 상위에 있어야 한다.
    • 커널 버전 3.13-rc1부터 bottom up 메모리 할당 기능이 추가되었다.
  • 코드 라인 49~50에서 top-down으로 검색을 시도한다.
    • ARM, ARM64, x86 NUMA, 등등에서 top-down을 사용한다.

 

MEMBLOCK_HOTPLUG
  • 리눅스에 메모리 hotplug 기능이 추가되면서 메모리 노드에 movable 속성 필드가 새롭게 생겨났다.
  • movable 가능한 노드들은 hotplug 기능으로 인해 탈착 기능을 수행하는 동안 해당 노드 전체의 사용중인 페이지들이 다른 노드로 이주(migration)를 하게 하는 코드가 추가되었다.
  • 커널과 커널이 사용하는 메모리는 hotplug 기능을 지원하지 않는 노드에 설치가 되며 이 노드는 항상 non-movable 속성을 갖는다.
  • 현재 x86 64비트 대용량 서버들이 hotplug 기능을 지원하는데 이러한 노드들은 최소 16G 용량씩을 가지고 보통 커널이미지가 있는 non-moveable 속성의 노드의 위쪽으로 떨어져 있다. 이러한 조건에서 커널이 메모리를 할당하게 되면 메모리의 최 상단 movable 노드부터 할당을 하게된다. 이러한 경우 moveable 노드를 탈착하게 되면 그 동안 할당하여 사용한 많은 메모리들이 movable 노드에서 대량으로 다른 노드로 이주(migration)이 되어야 하는 경우가 발생한다.
  • hotplug 기능으로 인하여 데이터 페이지의 이주가 발생하는 빈도를 낮추기 위해 메모리 할당을 bottom up으로(낮은 주소부터 높은 주소 방향으로) 할당할 수 있는 기능이 필요하게 되었고 커널이 있는 곳에서 즉, 커널의 끝(_end)부터 상단으로 메모리 할당이 가능할 수 있도록 코드가 추가되었다. 이렇게 하여 할당된 메모리는 커널이 있는 non-movable 노드에 같이 있게될 확률이 매우 높다.
  • ARM 아키텍처는 현재(커널 v4.4)까지 CPU hotplug 기능과는 달리 메모리 hotplug 기능이 적용되지 않았다. 따라서 메모리 할당은 top down 방식 을 사용한다.
    • CONFIG_MEMORY_HOTPLUG, CONFIG_HOTPLUG_CPU
  • 참고: mm/memblock.c: introduce bottom-up allocation mode

 

MEMBLOCK_MIRROR
  • x86 XEON 서버 시스템 기능 중에 메모리를 dual로 mirror 설정하여 고신뢰성을 확보할 수 있다. 전체 메모리를 mirror 설정하는 경우에는 커널 소프트웨어가 개입할 필요가 없다. 그러나 전체 메모리가 아닌 일부 메모리만 mirror 설정하여 사용하여야 할 때에는 커널 메모리에 우선 순위를 두고 사용할 수 있도록 등록된 memory memblock 영역에 이 플래그를 설정하여 사용한다. 이러한 정보는 서버 시스템의 UEFI 펌웨어가 mirror 설정을 커널에 전달하여 구성한다.

참고: address range mirror (2016) | Taku Izumi – 다운로드 pdf

 

다음 그림은 memblock_find_in_range() 함수의 처리 과정을 보여준다.

memblock4

 

__memblock_find_range_top_down()

mm/memblock.c

/**
 * __memblock_find_range_top_down - find free area utility, in top-down
 * @start: start of candidate range
 * @end: end of candidate range, can be %MEMBLOCK_ALLOC_ANYWHERE or
 *       %MEMBLOCK_ALLOC_ACCESSIBLE
 * @size: size of free area to find
 * @align: alignment of free area to find
 * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
 * @flags: pick from blocks based on memory attributes
 *
 * Utility called from memblock_find_in_range_node(), find free area top-down.
 *
 * Return:
 * Found address on success, 0 on failure.
 */
static phys_addr_t __init_memblock
__memblock_find_range_top_down(phys_addr_t start, phys_addr_t end,
                               phys_addr_t size, phys_addr_t align, int nid,
                               enum memblock_flags flags)
{
        phys_addr_t this_start, this_end, cand;
        u64 i;

        for_each_free_mem_range_reverse(i, nid, flags, &this_start, &this_end,
                                        NULL) {
                this_start = clamp(this_start, start, end);
                this_end = clamp(this_end, start, end);

                if (this_end < size)
                        continue;

                cand = round_down(this_end - size, align);
                if (cand >= this_start)
                        return cand;
        }

        return 0;
}

메모리가 존재하는 구간의 reserved 안된 공간을 대상으로 위에서 아래로 검색하며 요청한 노드 @nid의 @start ~ @end 범위내에 @align된 @size 만큼의 공간이 발견되면 그 물리 주소의 시작주소를 반환한다.

  • 코드 라인 9~12에서 빈 memblock 공간을 루프를 돌며 하나씩 알아온다.
  • 코드 라인 14~15에서 아래 cand를 산출할 때 this_end – size를 수행해야 하는데 산출된 결과가 0보다 작아 under-flow 시 미리 skip을 하도록 한다.
  • 코드 라인 17~19에서 요청 사이즈의 비교는 align된 크기로 한다. 알아온 free 영역의 범위에 size가 포함될 수 있으면 성공적으로 cand 주소를 리턴한다.

아래의 그림에서 __memblock_find_range_top_down() 함수가 동작하면 for_each_free_mem_range_reverse()를 통해 7개의 free 공간이 얻어지며, start 에서 end까지의 범위에 포함되는 케이스가 4개로 압축이 되고 1번 부터 4번 까지 순서로 요청 사이즈가 포함되는 매치 조건에 적합하면 이 공간의 주소를 리턴한다.

 


Iteration 매크로

for_each_mem_region()

mm/memblock.c

/**
 * for_each_mem_region - itereate over memory regions
 * @region: loop variable
 */
#define for_each_mem_region(region)                                     \
        for (region = memblock.memory.regions;                          \
             region < (memblock.memory.regions + memblock.memory.cnt);  \
             region++)

memblock의 memory 영역을 처음부터 끝까지 @region에 지정하며 순회한다.

 

for_each_reserved_mem_region()

mm/memblock.c

/**
 * for_each_reserved_mem_region - itereate over reserved memory regions
 * @region: loop variable
 */
#define for_each_reserved_mem_region(region)                            \
        for (region = memblock.reserved.regions;                        \
             region < (memblock.reserved.regions + memblock.reserved.cnt); \
             region++)

memblock의 reserved 영역을 처음부터 끝까지 @region에 지정하며 순회한다.

 

for_each_memblock_type()

mm/memblock.c

#define for_each_memblock_type(i, memblock_type, rgn)                   \
        for (i = 0, rgn = &memblock_type->regions[0];                   \
             i < memblock_type->cnt;                                    \
             i++, rgn = &memblock_type->regions[i])

요청한 @memblock_type의 영역을 처음부터 끝까지 @rgn에 지정하고, 0번부터 시작하는 순번은 @i에 지정하며 순회한다.

 

동작 과정 예)
.   struct memblock_region *r;
    for_each_reserved_mem_region(r) {
	print_info(“base=0x%x, size=0x%x\n”, r.base, r.size);
    }

reserved 영역이 아래와 같이 4개가 있을 때 r 값은 가장 하단의 memblock region부터 최상단 memblock region까지 반복하여 memblock_region 구조체 포인터를 지정한다.

memblock10

 

for_each_mem_pfn_range()

include/linux/memblock.h

/**
 * for_each_mem_pfn_range - early memory pfn range iterator
 * @i: an integer used as loop variable
 * @nid: node selector, %MAX_NUMNODES for all nodes
 * @p_start: ptr to ulong for start pfn of the range, can be %NULL
 * @p_end: ptr to ulong for end pfn of the range, can be %NULL
 * @p_nid: ptr to int for nid of the range, can be %NULL
 *
 * Walks over configured memory ranges.
 */
#define for_each_mem_pfn_range(i, nid, p_start, p_end, p_nid)           \
        for (i = -1, __next_mem_pfn_range(&i, nid, p_start, p_end, p_nid); \
             i >= 0; __next_mem_pfn_range(&i, nid, p_start, p_end, p_nid))

요청한 노드 @nid에서 파편화된 페이지를 제외한 즉 온전한 페이지가 1개 이상인 memblock 영역을 조건으로 루프를 돈다. 매치된 memblock 영역의 정보로 @i에는 memblock 영역의 인덱스 번호, @p_start에는 파편화된 페이지가 아닌 온전한 페이지의 시작 pfn, @p_end에는 파편화된 페이지가 아닌 온전한 페이지의 끝 pfn + 1을 담고, 마지막으로 노드 id를 @p_nid에 알아온다.

 

아래의 그림은 nid=1 값으로 for_each_mem_pfn_range() 매크로 루프를 동작시킬 때 조건에 매치되는 경우를 붉은 박스로 표현하였고 각각의 값은 다음과 같다.

  • 다음과 같이 가장 하단의 nid=0인 memblock 영역을 제외하고, 2번의 매치 건이 발생한다.
    • i 값이 1로 p_start=3, p_end=4, p_nid=1을 얻어온다.
    • i값이 2로 p_start=5, p_end=8, p_nid=1을 얻어온다.
  • 실제 memory memblock 들은 아래 그림과 다르게 매우 큰 페이지들로 이루어졌고 거의 대부분 align 된 상태이므로 아래 그림과는 많이 다르지만 계산 방법에 대해 이해를 돕는 것으로 활용해야 한다.

__next_mem_pfn_range()

mm/memblock.c

/*
 * Common iterator interface used to define for_each_mem_range().
 */
void __init_memblock __next_mem_pfn_range(int *idx, int nid,
                                unsigned long *out_start_pfn,
                                unsigned long *out_end_pfn, int *out_nid)
{
        struct memblock_type *type = &memblock.memory;
        struct memblock_region *r;

        while (++*idx < type->cnt) {
                r = &type->regions[*idx];

                if (PFN_UP(r->base) >= PFN_DOWN(r->base + r->size))
                        continue;
                if (nid == MAX_NUMNODES || nid == r->nid)
                        break;
        }
        if (*idx >= type->cnt) {
                *idx = -1;
                return;
        }

        if (out_start_pfn)
                *out_start_pfn = PFN_UP(r->base);
        if (out_end_pfn)
                *out_end_pfn = PFN_DOWN(r->base + r->size);
        if (out_nid)
                *out_nid = r->nid;
}

다음 memblock 영역부터 시작하여 요청한 @nid 이면서 파편화되지 않은 페이지들을 대상으로 시작 pfn과 끝 pfn+1값을  @out_start_pfn 및 @out_end_pfn에 알아온다. 그리고 매치된 노드의 id 값도 @out_nid에 알아온다.

  • 코드 라인 8~12에서 memory memblock 타입에 등록된 영역 중 요청한 @idx 영역을 제외하고 그 영역부터 루프를 돈다.
  • 코드 라인 14~15에서 memblock 영역에 파편화되지 않은 온전한 1개 이상의 페이지가 포함되지 않은 경우 skip 한다.
  • 코드 라인 16~17에서 요청 노드 @nid와 영역의 노드가 매치되었거나 또는 any 노드 요청인 경우 루프를 탈출한다.
  • 코드 라인 19~22에서 루프가 끝날 때까지 매치되지 않았으면 idx에 -1을 담고 리턴한다.
  • 코드 라인 24~25에서 out_start_pfn에 페이지가 파편화되지 않고 온전히 포함된 시작 pfn을 담는다.
  • 코드 라인 26~27에서 out_end_pfn에 페이지가 파편화되지 않고 온전히 포함된 끝 pfn을 담는다.
  • 코드 라인 28~29에서 out_nid에 memblock 영역의 노드 id를 담는다.

 

아래 그림은 7개의 case 별로 __next_mem_pfn_range() 함수가 실행될 때 요청 idx+1부터 조건에 매치되는 블럭을 찾는 모습을 보여준다. 아래 그림 역시도 실제 memory memblock 들의 사이즈보다 훨씬 축소를 해놨기 때문에 계산 방법에 대해 이해를 돕는 것으로 활용해야 한다

  • A)의 경우 파편화되지 않은 온전한 1개 페이지가 없는 경우이다.
  • B)의 경우 노드 id를 지정한 경우이다.
  • @idx
    • 매치된 memblock 영역의 인덱스
  • @out_start_pfn
    • 파편화되지 않은 페이지의 시작 pfn
  • @out_end_pfn
    • 파편화되지 않은 페이지의 끝 pfn + 1
  • @out_nid
    • 매치된 memblock 영역의 노드 id

 

for_each_free_mem_range()

include/linux/memblock.h

/**
 * for_each_free_mem_range - iterate through free memblock areas
 * @i: u64 used as loop variable
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @flags: pick from blocks based on memory attributes
 * @p_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @p_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @p_nid: ptr to int for nid of the range, can be %NULL
 *
 * Walks over free (memory && !reserved) areas of memblock.  Available as
 * soon as memblock is initialized.
 */
#define for_each_free_mem_range(i, nid, flags, p_start, p_end, p_nid)          \
        for_each_mem_range(i, &memblock.memory, &memblock.reserved,     \
                           nid, flags, p_start, p_end, p_nid)

루프를 돌며 지정된 노드 @nid의 memory 영역에서 reserved 영역을 제외한 영역이 free 메모리이며 이 영역을 순서대로 시작 물리 주소를 @p_start, 끝 물리 주소를 @p_end, 노드 id를 @p_nid, 플래그를 @flags에 알아온다.

 

for_each_mem_range()

include/linux/memblock.h

/**
 * for_each_mem_range - iterate through memblock areas from type_a and not
 * included in type_b. Or just type_a if type_b is NULL.
 * @i: u64 used as loop variable
 * @type_a: ptr to memblock_type to iterate
 * @type_b: ptr to memblock_type which excludes from the iteration
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @flags: pick from blocks based on memory attributes
 * @p_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @p_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @p_nid: ptr to int for nid of the range, can be %NULL
 */
#define for_each_mem_range(i, type_a, type_b, nid, flags,               \
                           p_start, p_end, p_nid)                       \
        for (i = 0, __next_mem_range(&i, nid, flags, type_a, type_b,    \
                                     p_start, p_end, p_nid);            \
             i != (u64)ULLONG_MAX;                                      \
             __next_mem_range(&i, nid, flags, type_a, type_b,           \
                              p_start, p_end, p_nid))

루프를 돌며 지정된 노드 id의 A 타입 영역에서 B 타입 영역을 제외한 영역이 주어질 때 이 영역을 순서대로 p_start, p_end, p_nid 인수에 지정한다.

  • 인자 @type_b는 null로 주어질 수도 있다.

 

아래와 같이 우측 memory memblock 타입의 영역내에서 reserved memblock 타입의 영역들을 제외한 영역이 좌측 회색 박스의 free 영역이 매치된 영역이다. 그 매치된 영역들을 대상으로 루프가 제공된다.

memblock11

 

__next_mem_range()

mm/memblock.c

/**
 * __next__mem_range - next function for for_each_free_mem_range() etc.
 * @idx: pointer to u64 loop variable
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @flags: pick from blocks based on memory attributes
 * @type_a: pointer to memblock_type from where the range is taken
 * @type_b: pointer to memblock_type which excludes memory from being taken
 * @out_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @out_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @out_nid: ptr to int for nid of the range, can be %NULL
 *
 * Find the first area from *@idx which matches @nid, fill the out
 * parameters, and update *@idx for the next iteration.  The lower 32bit of
 * *@idx contains index into type_a and the upper 32bit indexes the
 * areas before each region in type_b.  For example, if type_b regions
 * look like the following,
 *
 *      0:[0-16), 1:[32-48), 2:[128-130)
 *
 * The upper 32bit indexes the following regions.
 *
 *      0:[0-0), 1:[16-32), 2:[48-128), 3:[130-MAX)
 *
 * As both region arrays are sorted, the function advances the two indices
 * in lockstep and returns each intersection.
 */
void __init_memblock __next_mem_range(u64 *idx, int nid,
                                      enum memblock_flags flags,
                                      struct memblock_type *type_a,
                                      struct memblock_type *type_b,
                                      phys_addr_t *out_start,
                                      phys_addr_t *out_end, int *out_nid)
{
        int idx_a = *idx & 0xffffffff;
        int idx_b = *idx >> 32;

        if (WARN_ONCE(nid == MAX_NUMNODES,
        "Usage of MAX_NUMNODES is deprecated. Use NUMA_NO_NODE instead\n"))
                nid = NUMA_NO_NODE;

        for (; idx_a < type_a->cnt; idx_a++) {
                struct memblock_region *m = &type_a->regions[idx_a];

                phys_addr_t m_start = m->base;
                phys_addr_t m_end = m->base + m->size;
                int         m_nid = memblock_get_region_node(m);

                /* only memory regions are associated with nodes, check it */
                if (nid != NUMA_NO_NODE && nid != m_nid)
                        continue;

                /* skip hotpluggable memory regions if needed */
                if (movable_node_is_enabled() && memblock_is_hotpluggable(m))
                        continue;

                /* if we want mirror memory skip non-mirror memory regions */
                if ((flags & MEMBLOCK_MIRROR) && !memblock_is_mirror(m))
                        continue;

                /* skip nomap memory unless we were asked for it explicitly */
                if (!(flags & MEMBLOCK_NOMAP) && memblock_is_nomap(m))
                        continue;

                if (!type_b) {
                        if (out_start)
                                *out_start = m_start;
                        if (out_end)
                                *out_end = m_end;
                        if (out_nid)
                                *out_nid = m_nid;
                        idx_a++;
                        *idx = (u32)idx_a | (u64)idx_b << 32;
                        return;
                }

reserved memblock 영역들 사이에서 노드 @nid 값과 플래그 @flags에 해당하는 영역을 대상으로 요청한 인덱스 @idx의 다음 free 공간을 찾아 반환한다. 64비트 @idx에 memory 타입 memblock 영역들의 인덱스와 reserved 타입 memblock 영역들의 인덱스를 반반 합쳐서 인덱스를 지정하여 요청한다. 출력 인자인 @out_start 및 @out_end에 찾은 free 영역의 시작 물리 주소와 끝 물리 주소를 알아온다. 또한 @out_nid에도 노드 id를 알아온다.

  • 코드 라인 8~9에서 idx 값을 절반으로 나누어 lsb 쪽을 idx_a의 카운터로 사용하고, msb 쪽을 idx_b의 카운터로 사용한다.
  • 코드 라인 11~13에서 노드 id 인수 값으로 deprecated된 MAX_NUMNODES를 사용하면 경고문을 출력한다.
  • 코드 라인 15~24에서 type_a의 memblock 영역을 대상으로 루프를 돌며 요청한 @nid가 아니면서 any 노드를 요청한 경우도 아니면 skip 한다.
    • 이 함수가 호출될 떄 type_a는 memory 타입, type_b는 reserved 타입이다.
    • m_start와 m_end는 현재 1차 루프 인덱스의 memblock 영역의 시작 주소와 끝 주소이다.
  • 코드 라인 27~28에서 hotplug 및 movable 노드 전용인 경우 skip 한다.
  • 코드 라인 31~32에서 mirror 플래그 요청되었지만 mirror 영역이 아닌 경우 skip 한다.
  • 코드 라인 35~36에서 nomap 플래그가 없는데 nomap 영역인 경우 skip 한다.
  • 코드 라인 38~48에서 type_b에 대한 영역이 지정되지 않으면(null) 현재 1차 루프 인덱스의 memblock에 대한 영역으로 out_start와 out_end를 결정하고 idx_a 만을 1 증가 시키고 함수를 성공리에 빠져나간다.

 

                /* scan areas before each reservation */
                for (; idx_b < type_b->cnt + 1; idx_b++) {
                        struct memblock_region *r;
                        phys_addr_t r_start;
                        phys_addr_t r_end;

                        r = &type_b->regions[idx_b];
                        r_start = idx_b ? r[-1].base + r[-1].size : 0;
                        r_end = idx_b < type_b->cnt ?
                                r->base : PHYS_ADDR_MAX;

                        /*
                         * if idx_b advanced past idx_a,
                         * break out to advance idx_a
                         */
                        if (r_start >= m_end)
                                break;
                        /* if the two regions intersect, we're done */
                        if (m_start < r_end) {
                                if (out_start)
                                        *out_start =
                                                max(m_start, r_start);
                                if (out_end)
                                        *out_end = min(m_end, r_end);
                                if (out_nid)
                                        *out_nid = m_nid;
                                /*
                                 * The region which ends first is
                                 * advanced for the next iteration.
                                 */
                                if (m_end <= r_end)
                                        idx_a++;
                                else
                                        idx_b++;
                                *idx = (u32)idx_a | (u64)idx_b << 32;
                                return;
                        }
                }
        }

        /* signal end of iteration */
        *idx = ULLONG_MAX;
}
  • 코드 라인 2~10에서 type_b의 memblock 영역들 + 1만큼 2차 루프를 돈다.
    • r 영역은 현재 2차 루프 인덱스가 가리킨 곳의 영역을 가리킨다.
    • r_start는 idx_b가 0보다 크면 현재 이전 memblock의 끝 주소를 가리키고 idx_b가 0이면 0번 주소를 지정한다.
    • r_end는 idx_b가 등록된 갯수보다 작은 경우 r 영역의 시작주소를 지정하고 아니면 시스템 최대 주소를 지정한다.
  • 코드 라인 16~17에서 reserve memblock 영역이 memory memblock 영역을 벗어난 경우 2차 루프를 빠져나가서 다음 memory memblock을 준비하도록 한다.
    • 조건 A) r_start >= m_end
  • 코드 라인 19~26에서 두 영역이 교차하는 경우이다. out_start에 하단 reserve 영역값의 끝 주소나 memory 영역값의 시작 주소중 가장 큰 주소를 담는다. 그리고 out_end에 상단 reserve 영역값의 시작 주소나 memory 영역값의 끝 주소중에 가장 작은 주소를 담는다.
    • 조건 B) m_start < r_end
  • 코드 라인 31~36에서 reserve 영역의 끝 주소가 memory 영역의 끝주소와 비교하여 큰 경우 idx_a를 증가시키고 다음 memory block을 준비하기 위해 빠져나가고, 크지 않은 경우 idx_b를 증가시키고 계속하여 다음 reserve 영역을 준비하기 위해 빠져나간다.
    • 조건 B-1) m_end < r_end
  • 코드 라인 42에서 1차 루프를 끝까지 완료하면 더 이상 처리할 수 없으므로 idx에 ULLONG_MAX(시스템 최대 주소) 값을 부여하고 빠져나간다.

 

다음 그림은 __next_mem_range() 함수가 reserve된 영역들 사이의 free 영역을 찾아 루프를 돌 때 free 영역의 산출에 사용되는 case 들을 보여준다.

 

아래 1개의 meory memblock과 2개의 reserve memblock가 등록되어 있는 상태에서 __next__mem_range(0x1UUL) 매크로를 사용한 경우의 예이다.

  • 루프 1의 idx_a 인덱스는 0으로 memory.regions[0]의 정보 사용
  • 루프 2의 idx_b 인덱스는 1로 reserve.regions[1]과 이전 reserve memblock의 영역 정보를 사용하는 중이다.
  • 확정된 free 영역으로 r_start는 reserve.regions[0]의 끝 주소로 지정되고 r_end는 reserve.regions[1]의 시작 주소로 지정된다.

memblock13

 

아래 2개의 meory memblock과 4개의 reserve memblock이 등록되어 있는 상태에서 __next__mem_range(0x1UUL) 매크로를 사용하여 free 영역이 검색되는 범위를 표현한다.

memblock14a

 

for_each_free_mem_range_reverse()

mm/memblock.c

/**
 * for_each_free_mem_range_reverse - rev-iterate through free memblock areas
 * @i: u64 used as loop variable
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @flags: pick from blocks based on memory attributes
 * @p_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @p_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @p_nid: ptr to int for nid of the range, can be %NULL
 *
 * Walks over free (memory && !reserved) areas of memblock in reverse
 * order.  Available as soon as memblock is initialized.
 */
#define for_each_free_mem_range_reverse(i, nid, flags, p_start, p_end,  \
                                        p_nid)                          \
        for_each_mem_range_rev(i, &memblock.memory, &memblock.reserved, \
                               nid, flags, p_start, p_end, p_nid)

루프를 역순으로 돌며 지정된 노드 id의 memory 영역에서 reserved 영역을 제외한 영역이 free 메모리이며 이 영역을 순서대로 p_start, p_end, p_nid 인수에 지정한다.

 

for_each_mem_range_rev()

mm/memblock.c

/**
 * for_each_mem_range_rev - reverse iterate through memblock areas from
 * type_a and not included in type_b. Or just type_a if type_b is NULL.
 * @i: u64 used as loop variable
 * @type_a: ptr to memblock_type to iterate
 * @type_b: ptr to memblock_type which excludes from the iteration
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @flags: pick from blocks based on memory attributes
 * @p_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @p_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @p_nid: ptr to int for nid of the range, can be %NULL
 */
#define for_each_mem_range_rev(i, type_a, type_b, nid, flags,           \
                               p_start, p_end, p_nid)                   \
        for (i = (u64)ULLONG_MAX,                                       \
                     __next_mem_range_rev(&i, nid, flags, type_a, type_b,\
                                         p_start, p_end, p_nid);        \
             i != (u64)ULLONG_MAX;                                      \
             __next_mem_range_rev(&i, nid, flags, type_a, type_b,       \
                                  p_start, p_end, p_nid))

루프를 역순으로 돌며 지정된 노드 id의 A 타입 영역에서 B 타입 영역을 제외한 영역이 주어질 때 이 영역을 순서대로 p_start, p_end, p_nid 인수에 지정한다. B 타입 영역이 null일 수도 있다.

  • 인덱스는 64비트 값이며 절반씩 나누어 상위 32비트는 memory memblock 영역에 대한 인덱스를 가리키고, 하위 32비트는 reserved memblock 영역에 대한 인덱스를 가리킨다.
    • 처음 시작 시 역순으로 루프를 시작하므로 ~0UUL 값이 대입되어 시작한다.
  • ARM은 free 메모리를 할당하기 위해 검색시 이 top down 방식의 매크로를 사용한다.

 

아래와 같이 memory 영역내에서 reserved 영역을 제외한 영역이 free 영역이고 2개의 영역이 역순 루프로 제공된다.

memblock15

 

아래와 같이 memory region이 여러 개일 때 i 값의 추적을 표현하였다.

  • i 값의 최초 시작은 0xffff_ffff_ffff_ffff로 시작하고 free 영역을 리턴할 때 마다 아래와 같이 변화가 됨을 알 수 있다.
  • for each 루프를 종료하는 순간 i 값은 다시 0xffff_ffff_ffff_ffff로 바뀐다.

 

__next_mem_range_rev()

mm/memblock.c

idx 값이 ~0UUL로 진입한 경우 idx_a에는 type_a 형태의 memblock 갯수에서 -1을 대입하고, idx_b에는 type_b 형태의 memblock 갯수 값을 대입한다. 더 이상 매치되는 값이 없어서 루프를 다 돌고  함수를 종료하는 경우에는 idx 값이 다시 ~0UUL로 지정된다. (no more data)

  • 아래 코드는 __next_mem_range()를 reverse하는 것 이외에 코드가 동일하므로 소스 해석은 하지 않는다.
/**
 * __next_mem_range_rev - generic next function for for_each_*_range_rev()
 *
 * @idx: pointer to u64 loop variable
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @flags: pick from blocks based on memory attributes
 * @type_a: pointer to memblock_type from where the range is taken
 * @type_b: pointer to memblock_type which excludes memory from being taken
 * @out_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @out_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @out_nid: ptr to int for nid of the range, can be %NULL
 *
 * Finds the next range from type_a which is not marked as unsuitable
 * in type_b.
 *
 * Reverse of __next_mem_range().
 */
void __init_memblock __next_mem_range_rev(u64 *idx, int nid,
                                          enum memblock_flags flags,
                                          struct memblock_type *type_a,
                                          struct memblock_type *type_b,
                                          phys_addr_t *out_start,
                                          phys_addr_t *out_end, int *out_nid)
{
        int idx_a = *idx & 0xffffffff;
        int idx_b = *idx >> 32;

        if (WARN_ONCE(nid == MAX_NUMNODES, "Usage of MAX_NUMNODES is deprecated. Use NUMA_NO_NODE inn
stead\n"))
                nid = NUMA_NO_NODE;

        if (*idx == (u64)ULLONG_MAX) {
                idx_a = type_a->cnt - 1;
                if (type_b != NULL)
                        idx_b = type_b->cnt;
                else
                        idx_b = 0;
        }

        for (; idx_a >= 0; idx_a--) {
                struct memblock_region *m = &type_a->regions[idx_a];

                phys_addr_t m_start = m->base;
                phys_addr_t m_end = m->base + m->size;
                int m_nid = memblock_get_region_node(m);

                /* only memory regions are associated with nodes, check it */
                if (nid != NUMA_NO_NODE && nid != m_nid)
                        continue;

                /* skip hotpluggable memory regions if needed */
                if (movable_node_is_enabled() && memblock_is_hotpluggable(m))
                        continue;

                /* if we want mirror memory skip non-mirror memory regions */
                if ((flags & MEMBLOCK_MIRROR) && !memblock_is_mirror(m))
                        continue;

                /* skip nomap memory unless we were asked for it explicitly */
                if (!(flags & MEMBLOCK_NOMAP) && memblock_is_nomap(m))
                        continue;

                if (!type_b) {
                        if (out_start)
                                *out_start = m_start;
                        if (out_end)
                                *out_end = m_end;
                        if (out_nid)
                                *out_nid = m_nid;
                        idx_a--;
                        *idx = (u32)idx_a | (u64)idx_b << 32;
                        return;
                }

 

                /* scan areas before each reservation */
                for (; idx_b >= 0; idx_b--) {
                        struct memblock_region *r;
                        phys_addr_t r_start;
                        phys_addr_t r_end;

                        r = &type_b->regions[idx_b];
                        r_start = idx_b ? r[-1].base + r[-1].size : 0;
                        r_end = idx_b < type_b->cnt ?
                                r->base : PHYS_ADDR_MAX;
                        /*
                         * if idx_b advanced past idx_a,
                         * break out to advance idx_a
                         */

                        if (r_end <= m_start)
                                break;
                        /* if the two regions intersect, we're done */
                        if (m_end > r_start) {
                                if (out_start)
                                        *out_start = max(m_start, r_start);
                                if (out_end)
                                        *out_end = min(m_end, r_end);
                                if (out_nid)
                                        *out_nid = m_nid;
                                if (m_start >= r_start)
                                        idx_a--;
                                else
                                        idx_b--;
                                *idx = (u32)idx_a | (u64)idx_b << 32;
                                return;
                        }
                }
        }
        /* signal end of iteration */
        *idx = ULLONG_MAX;
}

기타 Memblock API

  • memblock_type_name()
    • memblock 타입명(문자열)을 알아온다.
  • memblock_cap_size()
    • 영역이 시스템의 최대 주소를 넘어가는 경우 overflow 된 만큼 잘라낸다.
  • memblock_addrs_overlap()
    • 영역이 서로 겹치는지 검사한다.
  • memblock_overlaps_region()
    • 등록된 memblock 영역들과 인수로 주어진 영역이 겹치는지 검사하여 겹치는 영역의 index를 리턴하고 아니면 -1 리턴
  • get_allocated_memblock_reserved_regions_info()
    • reserved 타입의 영역이 초기화 시 할당 받은 주소가 아니고 새롭게 buddy/slab memory 할당자에 의해 재 할당 받은 경우라면 해당 할당 영역의 시작주소를 리턴하고 아니면 0을 리턴한다. 이 함수는 free_low_memory_core_early() 함수를 통하여 bootmem 영역의 사용을 종료하고 buddy allocator로 전환할 때 호출된다.
  • get_allocated_memblock_memory_regions_info()
    • memory 타입의 영역이 초기화 시 할당 받은 주소가 아니고 새롭게 buddy/slab memory 할당자에 의해 재 할당 받은 경우라면 해당 할당 영역의 시작주소를 리턴하고 아니면 0을 리턴한다.이 함수는 free_low_memory_core_early() 함수를 통하여 bootmem 영역의 사용을 종료하고 buddy allocator로 전환할 때 호출된다.
  • memblock_double_array()
    • 주어진 타입의 memblock_region 배열을 2배 크기로 확장한다.
  • memblock_free()
    • reserved 영역에서 주어진 영역을 삭제한다.
  • memblock_set_node()
    • 주어진 영역에 해당하는 모든 memblock의 노드id를 설정한다. 영역이 memblock의 중간에 걸치면 분리하여 노드id를 설정한다.
  • memblock_phys_mem_size()
    • memory 영역에 등록된 memblock의 전체 사이즈
  • memblock_mem_size()
    • 주어진 limit_pfn까지 총 등록된 memory memblock 페이지 수를 알아온다.
  • memblock_start_of_DRAM()
    • memory 영역에 등록된 첫 memblock의 시작 주소(DRAM 시작 주소)
  • memblock_end_of_DRAM()
    • memory 영역에 등록된 마지막 memblock의 끝 주소(DRAM 끝 주소)
  • memblock_enforce_memory_limit()
    • 메모리 영역이 limit를 초과하는 경우 truncate ?
  • memblock_search()
    • 주어진 type의 memblock에서 주어진 주소로 2진 탐색 알고리즘을 사용하여 memblock을 찾고 해당 인덱스 값을 얻는다.
  • memblock_is_reserved()
    • reserved 영역에 주소가 포함되어 있는지 reserved 영역을 검색하여 판단한다.
  • memblock_is_memory()
    • memory 영역에 주소가 포함되어 있는지 memory 영역을 검색하여 판단한다.
  • memblock_search_pfn_nid()
    • memory 영역에서 주어진 주소로 2진 탐색 알고리즘으로 memblock을 찾은 후, 찾은 영역의 시작 페이지와 끝 페이지를 알아온다.
  • memblock_is_region_reserved()
    • 주어진 영역이 reserved memblock 영역과 겹치는지 확인한다.
  • memblock_trim_memory()
    • memory memblock들 모두 지정된 align 바이트로 정렬한다. 사이즈가 align 크기보다 작은 경우 삭제한다.
  • memblock_set_current_limit()
    • memblock의 limit를 설정한다.
  • memblock_dump()
    • 주어진 타입의 memblock들 정보를 출력한다.
  • memblock_dump_all()
    • memory와 reserved 타입의 memblock들 정보를 출력한다.
  • memblock_allow_resize()
    • 전역변수 memblock_can_resize=1로 설정(resize 허용)
  • early_memblock()
    • early_param()으로 등록한 함수이며 인수로 “debug” 문자열을 받게되면 전역 변수 memblock_debug=1로 설정하여 memblock_dbg() 함수를 동작하게 한다.
  • memblock_debug_show()
    • DEBUG_FS가 동작중이면 debug 출력할 수 있다.

 

디버그 출력

CONFIG_DEBUG_FS 커널 옵션을 사용한 경우 다음과 같이 memblock 등록 상황을 확인할 수 있다.

 

예) rpi2 – ARM32

# cat /sys/kernel/debug/memblock/memory
   0: 0x00000000..0x3affffff

# cat /sys/kernel/debug/memblock/reserved
   0: 0x00004000..0x00007fff   <- 페이지 테이블
   1: 0x00008240..0x0098c1ab   <- 커널
   2: 0x2fffbf00..0x2fffff08
   3: 0x39e9e000..0x39f95fff
   4: 0x39f989c4..0x3a7fefff
   5: 0x3a7ff540..0x3a7ff583
   6: 0x3a7ff5c0..0x3a7ff603
   7: 0x3a7ff640..0x3a7ff6b7
   8: 0x3a7ff6c0..0x3a7ff6cf
   9: 0x3a7ff700..0x3a7ff70f
  10: 0x3a7ff740..0x3a7ff743
  11: 0x3a7ff780..0x3a7ff916
  12: 0x3a7ff940..0x3a7ffad6
  13: 0x3a7ffb00..0x3a7ffc96
  14: 0x3a7ffc9c..0x3a7ffd14
  15: 0x3a7ffd18..0x3a7ffd60
  16: 0x3a7ffd64..0x3a7ffd7e
  17: 0x3a7ffd80..0x3a7ffd9b
  18: 0x3a7ffda4..0x3a7ffdbe
  19: 0x3a7ffdc0..0x3a7ffe37
  20: 0x3a7ffe40..0x3a7ffe43
  21: 0x3a7ffe48..0x3affffff

 

예) rock960 – ARM64

$ /sys/kernel/debug/memblock$ cat memory
   0: 0x0000000000200000..0x00000000f7ffffff

$ /sys/kernel/debug/memblock$ cat reserved
   0: 0x0000000002080000..0x00000000033b5fff  <- 커널
   1: 0x00000000ef400000..0x00000000f5dfffff
   2: 0x00000000f5eef000..0x00000000f5f01fff
   3: 0x00000000f6000000..0x00000000f7bfffff
   4: 0x00000000f7df4000..0x00000000f7df4fff
   5: 0x00000000f7df5e00..0x00000000f7df5fff
   6: 0x00000000f7e74000..0x00000000f7f61fff
   7: 0x00000000f7f62600..0x00000000f7f6265f
   8: 0x00000000f7f62680..0x00000000f7f626df
   9: 0x00000000f7f62700..0x00000000f7f6282f
  10: 0x00000000f7f62840..0x00000000f7f62857
  11: 0x00000000f7f62880..0x00000000f7f62887
  12: 0x00000000f7f648c0..0x00000000f7f6492b
  13: 0x00000000f7f64940..0x00000000f7f649ab
  14: 0x00000000f7f649c0..0x00000000f7f64a2b
  15: 0x00000000f7f64a40..0x00000000f7f64a47
  16: 0x00000000f7f64a64..0x00000000f7f64aea
  17: 0x00000000f7f64aec..0x00000000f7f64b1a
  18: 0x00000000f7f64b1c..0x00000000f7f64b4a
  19: 0x00000000f7f64b4c..0x00000000f7f64b7a
  20: 0x00000000f7f64b7c..0x00000000f7f64baa
  21: 0x00000000f7f64bac..0x00000000f7fcdff7
  22: 0x00000000f7fce000..0x00000000f7ffffff

 

참고

 

 

Memblock – (1)

<kernel v5.10>

Memblock

memblock 메모리 할당자는 커널 부트업 타임에 가장 먼저 활성화되는 메모리 할당자로, 다른 커널 메모리 할당자가 준비되기 전에 메모리 범위를 등록하여 사용한다. 주로 부팅 타임에 사용되지만 메모리 핫플러그 기능이 활성화되어 있으면 런타임에도 사용한다. 2010년 커널 v2.6.35에서 memblock이 처음 소개되어 사용하기 전에는 bootmem이라는 메모리 할당자를 사용했었다. bootmem이 부트업에 필요한 메모리의 일부(lowmem)만을 관리하는 것에 비해 memblock은 전체 메모리를 관리한다. 커널에서 페이지 할당자로 사용하는 버디 시스템이 준비되기 전까지 메모리 할당을 먼저(early) 할 수 있는 메모리 관리자는 memblock이 유일하다. 따라서 early 메모리 할당자라는 용어를 사용한다. 또한 memblock은 LMB(Logical Memory Block)라고 알려져 있다. 또한 핫플러그메모리를 지원하면서 런타임에 추가된 메모리를 memblock을 사용하여 추가한 이후 버디 할당자로 전환하는데에도 사용된다.

 

Memblock의 구조

memblock은 다음과 같이 두 가지 타입으로 나누어 관리하고 있다

  • memory 타입
    •  memory 타입은 사용할 물리 메모리 영역을 등록하여 사용한다. 커널 파라미터에 의해 실제 물리 메모리의 일부 영역만을 사용하게끔 제한되어 등록 가능하다. 처음에는 regions[ ] 배열에 최대 128개의 영역을 사용할 수 있고, 추후 2배 단위로 계속 확장될 수 있다.
  • reserved 타입
    • reserved 타입은 사용 중이거나 사용할 물리 메모리 영역을 등록하여 사용한다. 처음에는 regions[ ] 배열에 최대 128개의 영역을 사용할 수 있고, 추후 2배 단위로 계속 확장될 수 있다.

 

초기에 memblock 영역은 아래의 타입과 영역 배열로 등록되어 있다.

 

physmem 타입

physmem 타입은 2014년에 추가된 타입이며, 물리적으로 감지된 메모리 영역을 등록하여 사용되고 이 값은 등록된 후 수정되지 않는다는 특성이 있다. memory 타입과 달리 커널 파라미터에 의해 제한된 메모리가 아닌 실제 물리 메모리 크기를 등록하여 사용할 계획으로 코드가 추가되었고, 현재 s390 아키텍처에서 사용하고 있다. regions[ ] 배열로 처음에 최대 4개의 엔트리를 사용한다.

 

CONFIG_HAVE_MEMBLOCK_PHYS_MAP

  • 2014년 1월 kernel 3.16-rc1에서 추가된 옵션
  • 사용가능한 물리메모리의 영역을 4개(INIT_PHYSMEM_REGIONS)까지 추가할 수 있으며 입력된 영역은 수정되지 않는다.
  • 다른 memblock 구조체와 다르게 메모리의 전체 range를 담고 있다.

CONFIG_ARCH_KEEP_MEMBLOCK

  • memblock에 등록된 데이터를 early 부트 프로세스 이후 메모리에서 제거하지 않고 보존할 수 있게 vmlinux.lds.h 에서 제어할 수 있는 옵션이다.

 

include/linux/memblock.h

#ifndef CONFIG_ARCH_KEEP_MEMBLOCK
#define __init_memblock __meminit
#define __initdata_memblock __meminitdata
void memblock_discard(void);
#else
#define __init_memblock
#define __initdata_memblock
static inline void memblock_discard(void) {}
#endif

 

include/linux/init.h

#define __meminit        __section(.meminit.text) __cold notrace \
                                                  __latent_entropy
#define __meminitdata    __section(.meminit.data)

 

CONFIG_MEMORY_HOTPLUG

  • CONFIG_ARCH_KEEP_MEMBLOCK 옵션과 같이 사용한다.
    • CONFIG_MEMORY_HOTPLUG 옵션을 사용하지 않는 경우 부트 프로세스가 끝나고 나면 memblock 영역을 삭제할 수 있다. 이러한 경우 부팅 이후에 memblcok을 사용하면 안된다.
    • CONFIG_MEMORY_HOTPLUG를 사용하는 경우 부트 프로세스가 끝나더라도 계속 사용할 수 있도록 보존 한다.

 

include/asm-generic/vmlinux.lds.h

#if defined(CONFIG_MEMORY_HOTPLUG)
#define MEM_KEEP(sec)    *(.mem##sec)
#define MEM_DISCARD(sec)
#else
#define MEM_KEEP(sec)
#define MEM_DISCARD(sec) *(.mem##sec)
#endif

 

초기화

전역 구조체명 memblock은 컴파일 타임에 아래와 같이 초기화된다.

  • cnt 변수는 각 영역의 갯수로 초기 1로 설정된다.
    • 영역 데이터가 하나도 없어도 기본 1로 설정되어 있으며, 영역 데이터를 처음 하나 추가하는 경우 cnt는 변동되지 않는다. 그 후 부터는 추가할 때마다 1씩 증가한다.
  • bottom_up 변수는 arm 및 arm64에서 디폴트로 false로 되어 있어 allocation 요청 시 상단에서 하단으로 검색하여 free 공간을 찾는 방향성을 갖는다.
  • current_limit은 초기에 MEMBLOCK_ALLOC_ANYWHERE (~(phys_addr_t) 0)로 설정하고 설정될 수 있는 값은 다음과 같다.
    • MEMBLOCK_ALLOC_ANYWHERE (~(phys_addr_t) 0):
      • 물리 주소의 최대치
    • MEMBLOCK_ALLOC_ACCESSBLE (0):
      • 물리 메모리 주소의 최대치
    • 입력한 주소 값으로 최대 한계 제한

 

memblock_region 배열

mm/memblock.c

static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblocc
k;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdd
ata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS]
k;
#endif

컴파일 타임에 memblock 영역에 사용할 엔트리 배열의 개수를 지정한다. 배열의 크기는 위에서 아래 순서대로 각각 128, 128, 4개이다. 물리 메모리 등록은 몇 번 등록하지 않으므로 최대 4개로 제한된 상태이다.

 

memblock 배열

mm/memblock.c

struct memblock memblock __initdata_memblock = {
        .memory.regions         = memblock_memory_init_regions,
        .memory.cnt             = 1,    /* empty dummy entry */
        .memory.max             = INIT_MEMBLOCK_REGIONS,
        .memory.name            = "memory",

        .reserved.regions       = memblock_reserved_init_regions,
        .reserved.cnt           = 1,    /* empty dummy entry */
        .reserved.max           = INIT_MEMBLOCK_REGIONS,
        .reserved.name          = "reserved",

        .bottom_up              = false,
        .current_limit          = MEMBLOCK_ALLOC_ANYWHERE,
};

컴파일 타임에 3 가지 타입의 memblock 영역을 관리하는 memblock을 준비한다. 처음에 부트업에서는 각 memblock 영역들은 static 한 배열을 가리키고 있다가, 확장이 필요할 때 dynamic하게 추가될 수 있다.

  • 코드 라인 2~5에서 memory memblock을 128개 엔트리 배열로 초기화한다.
  • 코드 라인 7~10에서 reserved memblock을 128개 엔트리 배열로 초기화한다. 이 배열의 크기는 추후 reserved 영역이 등록되어 가득 차면 2배 단위로 확장하여 사용할 수 있다.
  • 코드 라인 12에서 비어 있는 영역의 검색을 위에서 아래 주소 방향으로 수행하도록 초깃값으로 설정한다.
  • 코드 라인 13에서 최대 메모리 할당 제한 값을 시스템이 사용하는 주소의 가장 큰 주소를 갖게 하여 제한이 없는 상태로 초기화한다

 

다음 그림은 memblock, memblock_type 및 memblock_regions 구조체들 사이의 연관성을 보여준다.

 

주요 구조체

struct memblock

include/linux/memblock.h

struct memblock {
        bool bottom_up;  	/* is bottom up direction? */
        phys_addr_t current_limit;
        struct memblock_type memory;
        struct memblock_type reserved;
};
  • bottom_up
    • 메모리 할당을 아래에서 위로 검색하여 빈 공간을 할당할 수 있도록 하는 옵션으로, 현재 x86_64 NUMA 시스템에 먼저 적용되어 있다. NUMA 시스템에서 메모리 핫플러그 기능을 사용하는 경우 효과가 있다. 커널이 할당한 메모리가 최대한 커널이 있는 low 메모리 주소 근처에 할당하게 유도하여 특정 노드의 메모리를 시스템에서 오프(off)할 때 그 노드에서 이미 할당되어 사용하는 페이지들이 적은 경우 마이그레이션(migration)되는 비율을 최대한 억제할 수 있기 때문에 설계된 기능이다(1 = 아래에서 위로 할당).
  • current_limit
    • 커널 부트업 프로세스 루틴에서 커널 메모리의 할당을 제한하고자 하는 경우에 사용한다. 64비트 시스템에서 모든 물리 메모리에 대해 가상 메모리에 1:1 direct 매핑되어 있으므로 별도로 lowmem 영역으로 관리하지 않기 때문에 current_limit에는 커널 초기 빌드 시 주어진 사용되는 MEMBLOCK_ALLOC_ANYWHERE(0xffffffff_ffffffff) 값으로 지정된다.
  • memory
    • 물리 메모리 영역 등록
  • reserved
    • reserved 영역 등록

 

struct memblock_type

include/linux/memblock.h

struct memblock_type {
        unsigned long cnt;
        unsigned long max;
        phys_addr_t total_size;
        struct memblock_region *regions;
        char *name;
};
  • cnt
    • 사용 영역 엔트리 수다. 엔트리 영역이 한 건도 등록되지 않아도 1부터 시작하도록 설계되었다. 첫 엔트리 영역이 추가되는 경우 이 값은 바뀌지 않고, 두 번째 엔트리가 추가될 때마다 증가된다.
  • max
    • 최대 사용 가능한 영역 엔트리 수
  • total_size
    • 해당 memblock 타입의 등록된 모든 영역의 크기를 더한 값(bytes)
  • *regions
    • 영역을 가리키는 포인터
  • *name
    • 영역명

 

struct memblock_region

include/linux/memblock.h

struct memblock_region {
        phys_addr_t base;
        phys_addr_t size;
        enum memblock_flags flags;
#ifdef CONFIG_NEED_MULTIPLE_NODES
        int nid;
#endif
};
  • base
    • 시작 물리 주소
  • size
    • 영역 크기
  • flags
    • flags에는 다음과 같이 4개의 매크로 상수를 사용하고 3개의 비트 요청을 조합하여 사용할 수 있다.
      • MEMBLOCK_NONE(0x0): 특이 요청 없는 영역
      • MEMBLOCK_HOTPLUG(0x1): 메모리 핫플러그 영역
      • MEMBLOCK_MIRROR(0x2): 미러된 영역
      • MEMBLOCK_NOMAP(0x4): 커널이 직접 매핑하지 않는 영역
  • nid
    • 노드 id

 


 

Memblock의 기본 관리 API

메모리를 추가할 때 멀티 노드(NUMA 등) 시스템이 아닌 경우 memblock_add( ) 함수를 사용하면 내부에서 노드 id는 0으로 처리된다. 그리고 멀티 노드 시스템을 사용하는 경우 memblock_add_node( ) 함수를 사용하여 노드 id까지 지정을 한다. 머신을 설계할 때 등록되는 메모리 영역이 서로 겹치지 않도록 되어 있지만, 여러 가지 예외 처리를 위해 다양한 상황에서 요청한 영역이 겹치지 않도록 조정을 한다.

 

메모리 영역 추가

memblock_add()

mm/memblock.c

/**
 * memblock_add - add new memblock region
 * @base: base address of the new region
 * @size: size of the new region
 *
 * Add new memblock region [@base, @base + @size) to the "memory"
 * type. See memblock_add_range() description for mode details
 *
 * Return:
 * 0 on success, -errno on failure.
 */
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
        phys_addr_t end = base + size - 1;

        memblock_dbg("memblock_add: [%pa-%pa] %pF\n",
                     &base, &end, (void *)_RET_IP_);

        return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}

메모리 영역을 memory memblock에 추가한다.

  • 물리 메모리 시작 주소 @base 부터 @size 만큼을 memory memblock에 추가한다.

 

reserve 영역 추가

memblock_reserve()

mm/memblock.c

int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
        phys_addr_t end = base + size - 1;

        memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,
                     &base, &end, (void *)_RET_IP_);

        return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
}

reserved 영역을 reserved memblock에 추가한다.

  • 물리 메모리 시작 주소 @base 부터 @size 만큼을 reserved memblock에 추가한다.

 

영역 추가

memblock_add_range()

mm/memblock.c

/**
 * memblock_add_range - add new memblock region
 * @type: memblock type to add new region into
 * @base: base address of the new region
 * @size: size of the new region
 * @nid: nid of the new region
 * @flags: flags of the new region
 *
 * Add new memblock region [@base,@base+@size) into @type.  The new region
 * is allowed to overlap with existing ones - overlaps don't affect already
 * existing regions.  @type is guaranteed to be minimal (all neighbouring
 * compatible regions are merged) after the addition.
 *
 * RETURNS:
 * 0 on success, -errno on failure.
 */
int __init_memblock memblock_add_range(struct memblock_type *type,
                                phys_addr_t base, phys_addr_t size,
                                int nid, enum memblock_flags flags)
{
        bool insert = false;
        phys_addr_t obase = base;
        phys_addr_t end = base + memblock_cap_size(base, &size);
        int idx, nr_new;
        struct memblock_region *rgn;

        if (!size)
                return 0;

        /* special case for empty array */
        if (type->regions[0].size == 0) {
                WARN_ON(type->cnt != 1 || type->total_size);
                type->regions[0].base = base;
                type->regions[0].size = size;
                type->regions[0].flags = flags;
                memblock_set_region_node(&type->regions[0], nid);
                type->total_size = size;
                return 0;
        }
repeat:
        /*
         * The following is executed twice.  Once with %false @insert and
         * then with %true.  The first counts the number of regions needed
         * to accommodate the new area.  The second actually inserts them.
         */
        base = obase;
        nr_new = 0;

        for_each_memblock_type(idx, type, rgn) {
                phys_addr_t rbase = rgn->base;
                phys_addr_t rend = rbase + rgn->size;

                if (rbase >= end)
                        break;
                if (rend <= base)
                        continue;
                /*
                 * @rgn overlaps.  If it separates the lower part of new
                 * area, insert that portion.
                 */
                if (rbase > base) {
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
                        WARN_ON(nid != memblock_get_region_node(rgn));
#endif
                        WARN_ON(flags != rgn->flags);
                        nr_new++;
                        if (insert)
                                memblock_insert_region(type, idx++, base,
                                                       rbase - base, nid,
                                                       flags);
                }
                /* area below @rend is dealt with, forget about it */
                base = min(rend, end);
        }

        /* insert the remaining portion */
        if (base < end) {
                nr_new++;
                if (insert)
                        memblock_insert_region(type, idx, base, end - base,
                                               nid, flags);
        }

        if (!nr_new)
                return 0;

        /*
         * If this was the first round, resize array and repeat for actual
         * insertions; otherwise, merge and return.
         */
        if (!insert) {
                while (type->cnt + nr_new > type->max)
                        if (memblock_double_array(type, obase, size) < 0)
                                return -ENOMEM;
                insert = true;
                goto repeat;
        } else {
                memblock_merge_regions(type);
                return 0;
        }
}

이 함수에서는 요청받은 인자를 사용하여 새로운 memblock 영역을 추가하는데, 기존 memblock 영역들과 중복되는 곳은 중복되지 않게 사이사이에 끼워 넣고 마지막으로 인접 memblock과 경계가 붙어 있고 플래그 타입이 같은 memblock들을 merge한다. return 값은 항상 0이다.

  • 코드 라인 15~23에서 memblock 영역이 비어 있다면 중복 체크 없이 첫 memblock 영역에 대한 설정을 하고 함수를 종료한다.
  • 코드 라인 24에서 repeat 레이블을 통해 한 번 반복되는데, 첫 번째 수행을 first round라고 하고 다음번 수행을 second round라고 할 때 first round에서는 루프를 돌때 끼워 넣어야 할 memblock 수를 체크할 목적으로 카운터(nr_new)만 증가시킨다. second round에서는 루프를 돌며 실제로 memblock을 곳곳에 인접 memblock과 중복되지 않게 끼워 넣는다.
  • 코드 라인 33~35에서 요청한 memblock 타입들에 대해 루프를 돈다.
  • 코드 라인 37~38에서 new memblock 영역이 비교하는 memblock 영역의 하단에 위치하여 겹치지 않으므로 더이상 반복 루프를 진행할 필요가 없어서 루프를 빠져나간다.
    • 아래 그림의 (A)에 해당한다. 참고로 regions[] 배열은 base 주소 순으로 정렬되어 있다. regions[0]에 가장 하위의 base 주소가 위치한다.
  • 코드 라인 39~40에서 new memblock 영역이 비교하는 memblock 영역의 상부에 위치하여 겹치지 않으므로 스킵하여 다음 memblock과 비교하도록 한다.
    • 아래 그림의 (B)에 해당한다.
  • 코드 라인 45~55 new memblock 영역이 비교하는 memblock 영역과 겹치므로 요청한 new memblock을 이 위치에 끼워 넣는데, 그 영역은 비교하는 memblock 영역과 겹치지 않는 영역의 크기만큼으로 조절한다.
    • 아래 그림의 (C)에 해당한다.
  • 코드 라인 57에서 base를 끼워 넣어야 할 때를 대비하여 끼워 넣을 memblock 영역의 시작 주소를 미리 설정해놓는다.
  • 코드 라인 61~66에서 루프 종료 후 new memblock 영역의 끝부분이 상부로 돌출되어 남아 있다면 그 돌출된 부분만을 memblock으로 추가한다.
    • 아래 그림의 (D)에 해당한다.
  • 코드 라인 75~80에서 first round의 경우 기존 memblock들과 끼워 넣을 memblock들의 합이 최대 관리 개수를 넘어가는 경우에 한해 memblock 영역의 엔트리 수를 두 배 더 크게 넓히기 위해 memblock_double_array( ) 함수를 호출한다. 이 때 충분한 엔트리 개 수가 준비될 때까지 반복한다.
  • 코드 라인 81~84에서 second round에서 끼워 넣은 memblock들에 대해 주변 memblock들과 인접하고 flag 타입이 동일한 memblock들을 memblock_merge_regions( ) 함수를 사용하여 merge한다.

 

다음 그림은 요청한 메모리 영역을 추가하는 흐름을 보여준다

 

아래 그림에서 회색으로 6개의 기존 영역이 등록되어 있고, 하늘색으로 새로운 memblock을 6가지 케이스에 대해 추가할 때 region[0]부터 화살표 방향으로 비교하는 모습을 보여준다.

  • 추가할 영역이 기존 memblock 영역과 겹치는 경우 일단 겹쳐지지 않는 영역으로 쪼개어 추가한 후 나중에 인접 블럭들끼리 최종 merge한다.
  • insertion 수
    • memblock이 추가될 때 실제 insertion이 일어나는 개수
  • merge 후 cnt 수
    • 추후 merge된 후 최종적으로 남게되는 memblock 수

memblock3

 

memblock_insert_region()

mm/memblock.c

/**
 * memblock_insert_region - insert new memblock region
 * @type:       memblock type to insert into
 * @idx:        index for the insertion point
 * @base:       base address of the new region
 * @size:       size of the new region
 * @nid:        node id of the new region
 * @flags:      flags of the new region
 *
 * Insert new memblock region [@base,@base+@size) into @type at @idx.
 * @type must already have extra room to accomodate the new region.
 */
static void __init_memblock memblock_insert_region(struct memblock_type *type,
                                                   int idx, phys_addr_t base,
                                                   phys_addr_t size,
                                                   int nid, 
                                                   enum memblock_flags flags)
{
        struct memblock_region *rgn = &type->regions[idx];

        BUG_ON(type->cnt >= type->max);
        memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));
        rgn->base = base;
        rgn->size = size;
        rgn->flags = flags;
        memblock_set_region_node(rgn, nid);
        type->cnt++;
        type->total_size += size;
}

new 영역을 지정된 타입의 지정된 index memblock 위치에 끼워(insert) 넣는다. memblock_type 구조체의 필드 cnt와 total_size에는 카운터를 증가시키고 증가된 size도 더한다.

  • 코드 라인 10에서 지정된 index의 memblock부터 마지막 memblock까지 한 칸씩 뒤로 복사한다.
  • 코드 라인 11~14에서 멀티 노드를 지원하기 위해 노드 id를 지정한다.
  • 코드 라인 15~16에서 memblock 타입 정보에는 카운터를 증가 시키고 증가된 size도 더한다.

 

memblock 배열 확장

memblock_double_array()

memblock이 위치한 배열이 작아서 확장이 필요할 때 호출되면 두 배로 키울 수 있다. 다음 코드를 통해 memblock_double_array( ) 함수를 분석해보자.

mm/memblock.c -1/2-

/**
 * memblock_double_array - double the size of the memblock regions array
 * @type: memblock type of the regions array being doubled
 * @new_area_start: starting address of memory range to avoid overlap with
 * @new_area_size: size of memory range to avoid overlap with
 *
 * Double the size of the @type regions array. If memblock is being used to
 * allocate memory for a new reserved regions array and there is a previously
 * allocated memory range [@new_area_start, @new_area_start + @new_area_size]
 * waiting to be reserved, ensure the memory used by the new array does
 * not overlap.
 *
 * Return:
 * 0 on success, -1 on failure.
 */
static int __init_memblock memblock_double_array(struct memblock_type *type,
                                                phys_addr_t new_area_start,
                                                phys_addr_t new_area_size)
{
        struct memblock_region *new_array, *old_array;
        phys_addr_t old_alloc_size, new_alloc_size;
        phys_addr_t old_size, new_size, addr, new_end;
        int use_slab = slab_is_available();
        int *in_slab;

        /* We don't allow resizing until we know about the reserved regions
         * of memory that aren't suitable for allocation
         */
        if (!memblock_can_resize)
                return -1;

        /* Calculate new doubled size */
        old_size = type->max * sizeof(struct memblock_region);
        new_size = old_size << 1;
        /*
         * We need to allocated new one align to PAGE_SIZE,
         *   so we can free them completely later.
         */
        old_alloc_size = PAGE_ALIGN(old_size);
        new_alloc_size = PAGE_ALIGN(new_size);

        /* Retrieve the slab flag */
        if (type == &memblock.memory)
                in_slab = &memblock_memory_in_slab;
        else
                in_slab = &memblock_reserved_in_slab;

        /* Try to find some space for it. */
        if (use_slab) {
                new_array = kmalloc(new_size, GFP_KERNEL);
                addr = new_array ? __pa(new_array) : 0;
        } else {
                /* only exclude range when trying to double reserved.regions */
                if (type != &memblock.reserved)
                        new_area_start = new_area_size = 0;

                addr = memblock_find_in_range(new_area_start + new_area_size,
                                                memblock.current_limit,
                                                new_alloc_size, PAGE_SIZE);
                if (!addr && new_area_size)
                        addr = memblock_find_in_range(0,
                                min(new_area_start, memblock.current_limit),
                                new_alloc_size, PAGE_SIZE);

                new_array = addr ? __va(addr) : NULL;
        }
  • 코드 라인 14~15에서 memblock_allow_resize( )가 호출되어 memblock_can_resize 전역 변수가 1로 설정된 후에만 이 함수가 동작하도록 제한한다.
    • 커널 이미지와 메모리 영역이 매핑 완료되고, arm64_memblock_init() 루틴이 수행되어 memblock이 사용될 준비가 되면 다음 함수 호출 경로에서 사용된다.
      • setup_arch() -> paging_init()의 마지막에서 -> memblock_allow_resize()이 호출된다.
  • 코드 라인 18~25에서 새로 관리할 영역을 기존 관리 영역보다 두 배 큰 크기로 할당받을 준비를 한다.
  • 코드 라인 28~31에서 기존 memblock 관리 맵이 static이나 memblock에서 할당받은 것이 아니라 이미 슬랩(slab)으로 전환하여 운영되고 있는 중인지 여부를 확인한다.
  • 코드 라인 34~36에서 정규 메모리 할당자인 슬랩을 사용할 수 있는 단계에서 kmalloc( )으로 메모리를 할당받는다.
  • 코드 라인 37~40에서 슬랩을 사용하지 못하는 경우 memblock_add( ) 함수에서 사용 요청한 영역을 피해 memblock_find_in_range( )로 새로 만들 관리 영역을 사용할 수 있는 공간을 찾아온다. 요청 타입이 reserved 타입이 아닌 경우, 즉 memory 타입인 경우에는 할당 영역 검색을 0부터 시작하게 한다.
    • 메모리 타입을 등록하는 중에 관리 영역이 확장되는 경우, 새로 할당받아야 하는 영역은 그 전에 등록한 모든 메모리 영역 내에서 빈자리를 검색하면 된다. 어차피 새로 추가될 메모리 공간은 기존 메모리 공간과 간섭이 없는 영역일 것이다. 우선, 새로 관리 영역을 할당받을 공간은 추가 요청 영역을 피해야 하므로 요청 영역의 상부를 먼저 검색한다.
  • 코드 라인 42~48에서 만일 첫 번째 검색에서 할당받지 못했거나 요청 타입이 reserved인 경우에 추가 요청 영역을 피해 하부를 검색한다.

 

mm/memblock.c -2/2-

        if (!addr) {
                pr_err("memblock: Failed to double %s array from %ld to %ld entries !\n",
                       type->name, type->max, type->max * 2);
                return -1;
        }

        new_end = addr + new_size - 1;
        memblock_dbg("memblock: %s is doubled to %ld at [%pa-%pa]",
                        type->name, type->max * 2, &addr, &new_end);

        /*
         * Found space, we now need to move the array over before we add the
         * reserved region since it may be our reserved array itself that is
         * full.
         */
        memcpy(new_array, type->regions, old_size);
        memset(new_array + type->max, 0, old_size);
        old_array = type->regions;
        type->regions = new_array;
        type->max <<= 1;

        /* Free old array. We needn't free it if the array is the static one */
        if (*in_slab)
                kfree(old_array);
        else if (old_array != memblock_memory_init_regions &&
                 old_array != memblock_reserved_init_regions)
                memblock_free(__pa(old_array), old_alloc_size);

        /*
         * Reserve the new array if that comes from the memblock.  Otherwise, we
         * needn't do it
         */
        if (!use_slab)
                BUG_ON(memblock_reserve(addr, new_alloc_size));

        /* Update slab flag */
        *in_slab = use_slab;

        return 0;
}
  • 코드 라인 16~20에서 새로 할당받은 관리 영역 시작 주소에 기존 memblock 영역들을 모두 복사하고 복사되지 않은 빈 곳은 0으로 초기화한다. 또한 max를 기존 값의 두 배로 변경한다.
  • 코드 라인 23~27에서 확장하기 전의 기존 관리 영역을 해제한다. 초기 관리 영역은 컴파일 타임에 선언된 배열 변수 영역에 있으므로 삭제할 수 없어서 그냥 버린다. 삭제할 관리 영역이 초기 영역이 아니면 다음과 같이 할당자 종류에 따라 처리한다.
    • 기존에 슬랩을 사용 중이었으면 kfree( )로 기존 영역을 해제한다.
    • 슬랩을 사용하지 않았다면 memblock_free( )로 기존 영역을 해제한다.

 

memblock_allow_resize()

mm/memblock.c

void __init memblock_allow_resize(void)
{
        memblock_can_resize = 1;
}

memblock 영역이 필요 시 확장이 가능하도록 1을 대입한다.

 

memblock 병합

memblock_merge_regions()

mm/memblock.c

/**
 * memblock_merge_regions - merge neighboring compatible regions
 * @type: memblock type to scan
 *
 * Scan @type and merge neighboring compatible regions.
 */
static void __init_memblock memblock_merge_regions(struct memblock_type *type)
{
        int i = 0;

        /* cnt never goes below 1 */
        while (i < type->cnt - 1) {
                struct memblock_region *this = &type->regions[i];
                struct memblock_region *next = &type->regions[i + 1];

                if (this->base + this->size != next->base ||
                    memblock_get_region_node(this) !=
                    memblock_get_region_node(next) ||
                    this->flags != next->flags) {
                        BUG_ON(this->base + this->size > next->base);
                        i++;
                        continue;
                }

                this->size += next->size;
                /* move forward from next + 1, index of which is i + 2 */
                memmove(next, next + 1, (type->cnt - (i + 2)) * sizeof(*next));
                type->cnt--;
        }
}

인접 메모리 블록이 같은 플래그 타입을 사용하는 경우 병합한다

  • 코드 라인 6~8에서 요청 memblock 타입의 개수만큼 거꾸로 감소시키며 루프를 돈다.
  • 코드 라인 10~17에서 경계가 붙어 있지 않거나 두 memblock 간의 flag 상태가 다른 경우 memblock을 합치지 않고 스킵한다.
  • 코드 라인 19~22에서 경계가 붙어 있다면 memblock을 합친다. 참고로 병합이 된 경우 다시 한 번 다음 블럭과 비교하기 위해 regions[] 배열을 지정하는 인덱스 i 값은 증가시키지 않는다.

 

memblock_cap_size()

mm/memblock.c

/* adjust *@size so that (@base + *@size) doesn't overflow, return new size */
static inline phys_addr_t memblock_cap_size(phys_addr_t base, phys_addr_t *size)
{
        return *size = min(*size, PHYS_ADDR_MAX - base);
}

영역이 unsigned long 값을 넘어가는 경우(overflow) 넘친 부분을 잘라낸다.

    • 64bit: min(0xffff_ffff_ffff_ffff – base, size)
    • 32bit: min(0xffff_ffff – base, size)
  • 영역이 넘치는 경우 사이즈가 재계산되는데 사이즈가 1만큼 작아져 시스템의 마지막 주소 바이트를 사용할 수 없게된다.
    • 예) 32bit: base=0xffff_0000, size=0xffff -> size=0xffff (이건 정상)
    • 예) 32bit: base=0xffff_0000, size=0x10000 -> size=0xffff (1이 작아져서 0xffff_ffff 주소는 사용불가능)

 

memblock 삭제

다음 그림은 memblock_remove() 함수의 호출 관계이다.

memblock5

 

memblock_remove()

mm/memblock.c

int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
        phys_addr_t end = base + size - 1;

        memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,
                     &base, &end, (void *)_RET_IP_);

        return memblock_remove_range(&memblock.memory, base, size);
}

물리 메모리 주소 @base 부터 @size 만큼을 memory 타입 memblock 영역에서 제거한다.

 

memblock_free()

mm/memblock.c

/**
 * memblock_free - free boot memory block
 * @base: phys starting address of the  boot memory block
 * @size: size of the boot memory block in bytes
 *
 * Free boot memory block previously allocated by memblock_alloc_xx() API.
 * The freeing memory will not be released to the buddy allocator.
 */
int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
{
        phys_addr_t end = base + size - 1;

        memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,
                     &base, &end, (void *)_RET_IP_);

        kmemleak_free_part_phys(base, size);
        return memblock_remove_range(&memblock.reserved, base, size);
}

물리 메모리 주소 @base 부터 @size 만큼을 reserved 타입 memblock 영역에서 제거한다.

 

memblock_remove_range()

mm/memblock.c

static int __init_memblock memblock_remove_range(struct memblock_type *type,
                                          phys_addr_t base, phys_addr_t size)
{
        int start_rgn, end_rgn;
        int i, ret;

        ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);
        if (ret)
                return ret;

        for (i = end_rgn - 1; i >= start_rgn; i--)
                memblock_remove_region(type, i);
        return 0;
}

요청한 @type의 memblock에서 물리 주소 @base 부터 @size까지 영역을 제거한다.

  • 코드 라인 7~9에서 제거할 영역의 시작과 끝 주소를 기준으로 memblock을 분리한다.
  • 코드 라인 11~12에서 제거할 영역에 해당하는 memblock 영역을 삭제한다.

 

memblock_remove_region()

mm/memblock.c

static void __init_memblock memblock_remove_region(struct memblock_type *type, unsigned long r)
{
        type->total_size -= type->regions[r].size;
        memmove(&type->regions[r], &type->regions[r + 1],
                (type->cnt - (r + 1)) * sizeof(type->regions[r]));
        type->cnt--;

        /* Special case for empty arrays */
        if (type->cnt == 0) {
                WARN_ON(type->total_size != 0);
                type->cnt = 1;
                type->regions[0].base = 0;
                type->regions[0].size = 0;
                type->regions[0].flags = 0;
                memblock_set_region_node(&type->regions[0], MAX_NUMNODES);
        }
}

요청한 @type의 memblock에서 인덱스 r에 해당하는 memblock을 삭제한다. 상위 memblock들이 삭제한 위치로 이동해온다.

 

다음 그림은 4개의 memblock 영역이 있고 그 중 노란색의 remove 범위를 삭제하면 변경되는 memblock 영역을 보여준다.

memblock6

 

memblock 분리

다음 그림은 memblock_ioslate_range() 함수가 처리되는 과정을 보여준다.

 

memblock_isolate_range()

mm/memblock.c

/**
 * memblock_isolate_range - isolate given range into disjoint memblocks
 * @type: memblock type to isolate range for
 * @base: base of range to isolate
 * @size: size of range to isolate
 * @start_rgn: out parameter for the start of isolated region
 * @end_rgn: out parameter for the end of isolated region
 *
 * Walk @type and ensure that regions don't cross the boundaries defined by
 * [@base, @base + @size).  Crossing regions are split at the boundaries,
 * which may create at most two more regions.  The index of the first
 * region inside the range is returned in *@start_rgn and end in *@end_rgn.
 *
 * Return:
 * 0 on success, -errno on failure.
 */
static int __init_memblock memblock_isolate_range(struct memblock_type *type,
                                        phys_addr_t base, phys_addr_t size,
                                        int *start_rgn, int *end_rgn)
{
        phys_addr_t end = base + memblock_cap_size(base, &size);
        int idx;
        struct memblock_region *rgn;

        *start_rgn = *end_rgn = 0;

        if (!size)
                return 0;

        /* we'll create at most two more regions */
        while (type->cnt + 2 > type->max)
                if (memblock_double_array(type, base, size) < 0)
                        return -ENOMEM;

        for_each_memblock_type(idx, type, rgn) {
                phys_addr_t rbase = rgn->base;
                phys_addr_t rend = rbase + rgn->size;

                if (rbase >= end)
                        break;
                if (rend <= base)
                        continue;

                if (rbase < base) {
                        /*
                         * @rgn intersects from below.  Split and continue
                         * to process the next region - the new top half.
                         */
                        rgn->base = base;
                        rgn->size -= base - rbase;
                        type->total_size -= base - rbase;
                        memblock_insert_region(type, idx, rbase, base - rbase,
                                               memblock_get_region_node(rgn),
                                               rgn->flags);
                } else if (rend > end) {
                        /*
                         * @rgn intersects from above.  Split and redo the
                         * current region - the new bottom half.
                         */
                        rgn->base = end;
                        rgn->size -= end - rbase;
                        type->total_size -= end - rbase;
                        memblock_insert_region(type, idx--, rbase, end - rbase,
                                               memblock_get_region_node(rgn),
                                               rgn->flags);
                } else {
                        /* @rgn is fully contained, record it */
                        if (!*end_rgn)
                                *start_rgn = idx;
                        *end_rgn = idx + 1;
                }
        }

        return 0;
}

요청 영역의 상단과 하단 라인에 겹치는 memblock에서 영역을 분리한다. 분리된 memblock 시작 인덱스가 출력 인자의 @start_rgn에 저장되고 끝 인덱스 + 1 값이 출력 인자 @end_rgn에 저장된다. 만일 분리한 memblock 항목이 없으면 두 출력 인자에 0이 저장된다.

  • 코드 라인 5에서 영역의 끝 물리 주소는 아키텍처가 지원하는 물리 주소의 끝을 초과하지 않도록 제한한다.
  • 코드 라인 15~17에서 해당 타입의 최대 개수를 사용하려 할 때 관리 배열을 두 배로 확장한다. 확장이 불가능하면 메모리가 부족하다는 -ENOMEM을 리턴한다.
  • 코드 라인 19~21에서 첫 memblock 영역부터 마지막 영역에 대해 루프를 돌며 rbase와 rend에는 해당 루프 인덱스의 memblock 시작 주소와 끝 주소가 담긴다.
  • 코드 라인 23~24에서 현재 인덱스 영역의 시작 주소가 주어진 영역의 상단보다 크거나 같은 경우 더는 상위 인덱스 memblock에서 처리할 필요가 없으므로 루프를 빠져나가게 한다.  (아래 그림의 A 조건)
  • 코드 라인 25~26에서 현재 인덱스 영역의 끝 주소가 주어진 영역의 상단보다 같거나 큰 경우 아직 겹쳐지는 영역이 아니므로 다음 인덱스 memblock을 처리하기 위해 스킵한다. (아래 그림의 B 조건)
  • 코드 라인 28~38에서 현재 인덱스 영역의 시작 주소가 주어진 영역의 시작 주소보다 작은 경우, 즉 주어진 영역의 시작 주소가 현재 인덱스 memblock과 겹친 경우 다음과 같이 처리한다. (아래 그림의 C 조건)
    • 현재 인덱스 memblock을 하단의 겹친 라인을 기점으로 상부로 영역을 옮긴다. 또한 인덱스 memblock의 하단 겹친 라인을 기점으로 새롭게 하부에 memblock을 insert한다.
  • 코드 라인 39~49에서 현재 인덱스 영역의 끝 주소가 주어진 영역의 끝 주소보다 큰 경우, 즉 주어진 영역의 끝 주소가 현재 인덱스 memblock과 겹친 경우 다음과 같이 처리한다. (아래 그림의 D 조건)
    • 현재 인덱스 memblock을 상단의 겹친 라인을 기점으로 상부로 영역을 옮긴다. 또한 인덱스 memblock의 상단에 겹친 라인을 기점으로 새롭게 하부에 memblock을 삽입한다.
  • 코드 라인 50~55에서 어떠한 조건에도 걸리지 않은 경우로, 현재 인덱스 영역이 주어진 영역에 포함된 경우다. 이때는 start_rgn에 현재 인덱스 값을 지정하고, end_rgn에는 현재 인덱스 값 + 1을 지정한다.

 

다음 그림은 회색으로 표시된 6개의 memblock 영역이 있는 상황에서 6개 각각 하늘색의 isolation 영역이 주어졌을 때 region[0]부터 화살표 방향으로 비교하는 모습을 보여준다.

  • 분리할 영역이 기존 memblock 영역과 겹치는 경우 겹쳐지는 부분은 memblock을 나눈다. 그리고 memblock이 없는 부분은 추가한다.
  • insertion
    • memblock이 추가될 때 실제 insertion이 일어나는 개수를 표시하였다.
  • start_rgn
    • isolation 범위의 시작이 겹치는 기존 memblock 영역의 인덱스 번호
  • end_rgn
    • isolation 범위의 끝이 겹치는 기존 memblock 영역의 인덱스 번호 + 1

 

다음 그림은 위 그림 6개 예제의 첫 번째 부분을 조금 더 자세하게 표현하였다.

  • (C) 및 (D) case에서 insert를 하는 경우 처리 중인 idx 영역을 위로 밀어내고 추가한다.
  • (D) case를 진행하는 경우 idx에 해당하는 영역을 다시 한 번 진행하는 것에 주의한다.

 

다음 그림은 위 그림 6개 예제의 세 번째 부분을 조금 더 자세하게 표현하였다.

 

참고

 

setup_dma_zone

DMA zone을 필요로하는 머신의 경우 DMA size 등의 정보를 설정한다.

  • CONFIG_ZONE_DMA 옵션 사용
    • 특정 메모리 영역을 ZONE_DMA에 구성해야 할 때 필요한 옵션이다.
    • CONFIG_DMADEVICES 옵션과 관련 없다.
  • ARM 아키텍처 중 다음 머신들이 DMA zone을 사용한다.
    • mach-prima2, Cortex-A9
    • mach-shmobile, Renesas ARM SoCs, LPAE
    • mach-mvenu, Marvell Engineering Business Unit (MVEBU) SoCs, LPAE
    • mach-imx, Freescale LS1021A, LPAE
    • mach-realview, RealView(R) Platform Baseboard Explore
    • mach-axxia, LSI Axxia platforms, LPAE
    • mach-highbank, Calxeda ECX-1000/2000 (Highbank/Midway), LPAE
    • mach-keystone, Texas Instruments Keystone Devices, LPAE
    • 기타: mach-ixp4xx, mach-pxa, mach-davinchi, mach-sa1100 등등
  • ARM 아키텍처에서 ZONE_DMA size는 보통 1M, 16M, 64M, 128M 또는 256M이다.
  • LPAE를 지원하는 시스템에서의 ZONE_DMA size는 보통 1G, 2G 또는 4G이다.
  • 라즈베리파이2: DMA zone을 사용하지 않는다.

 

CONFIG_ZONE_DMA

  • DMA를 사용하는 디바이스가 아키텍처가 사용하는 전체 물리주소 영역에 대해 대응하지 못하는 경우 DMA 디바이스의 사용 주소 범위를 좁혀줄 필요가 있다. 따라서 이러한 디바이스를 위해 영역을 제한한 메모리 영역을 지정하여 메모리의 할당 관리를 별도로 한다.
  • x86 시스템에서는 24비트 주소를 사용하는 ISA 버스 방식을 사용하는 디바이스가 있고 이러한 디바이스가 DMA 방식으로 RAM과 communication을 하기 위해 DMA 존 크기를 16M로 지정하여 사용해야 한다.
  • ARM 시스템에서는 머신 설계 마다 다르다. DMA 디바이스가 32bit address를 갖춘 경우에는 DMA 존을 별도로 운영할 필요가 없다. 일부 머신에서는 address가 제한된 DMA 디바이스를 사용하는 경우 그 머신에 맞추어 DMA 존 크기가 지정되어 운용되어야 한다.
  • 64bit 머신에서는 32bit용으로 설계된 DMA 디바이스를 위해 CONFIG_ZONE_DMA32를 운용한다.

 

setup_dma_zone()

  • 3가지의 DMA zone 관련 전역 변수를 설정한다.
    • arm_dma_ zone_size <- mdesc->dma_zone_size 대입
    • arm_dma_zone_limit <- 물리 RAM 주소 + dma_zone_size – 1
      • 예) dma_zone_size=32M이면 dma_zone_limit=0x01ff_ffff
      • dma_zone_size가 0인 경우 dma_zone_limit <- 0xffff_ffff
    • arm_dma_pfn_limit <- arm_dma_zone_limit를 12비트 우측으로 쉬프트시켜 물리주소의 PFN으로 변환한다.
arch/arm/mm/init.c
void __init setup_dma_zone(const struct machine_desc *mdesc)
{
#ifdef CONFIG_ZONE_DMA
        if (mdesc->dma_zone_size) {
                arm_dma_zone_size = mdesc->dma_zone_size;
                arm_dma_limit = PHYS_OFFSET + arm_dma_zone_size - 1;
        } else
                arm_dma_limit = 0xffffffff;
        arm_dma_pfn_limit = arm_dma_limit >> PAGE_SHIFT;
#endif
}

 

예) mach-realview/realview-dt.c

DT_MACHINE_START(REALVIEW_DT, "ARM RealView Machine (Device Tree Support)")
#ifdef CONFIG_ZONE_DMA
        .dma_zone_size  = SZ_256M,
#endif
        .dt_compat      = realview_dt_platform_compat,
        .l2c_aux_val = 0x0,
        .l2c_aux_mask = ~0x0,
MACHINE_END