[Linux] 리눅스/x86 부트 프로토콜 (Linux/x86 boot protocol)

원문: https://www.kernel.org/doc/Documentation/x86/boot.txt

이 글은 GPL 라이센스를 따릅니다.

리눅스/x86 부트 프로토콜


번역: 양정석 (dasomoli@gmailREMOVETHIS.com)

x86 플랫폼 상에서 리눅스는 좀 복잡한 부트 컨벤션을 사용합니다. 이는 역사적인 관점에서 조금씩 진화한 것 뿐 아니라 커널 그 자체를 부팅가능한 이미지로 가졌던 초기 요구, 복잡한 PC 메모리 모델과 메인스트림 운영체제로서 리얼 모드 DOS의 실질적인 종말에 의한 PC 산업 내의 변화한 기대들로 인한 것입니다.

현재 리눅스/x86 부트 프로토콜의 다음 버전들이 존재합니다.

Old kernels:    zImage/Image만 지원. 극초기 몇몇 커널들은 커맨드 라인조차
                지원하지 않을 수 있음.

Protocol 2.00:  (커널 1.3.73) bzImage와 initrd 지원 추가 뿐만 아니라
                부트로더와 커널 간 통신을 위한 방법이 형식화됨.
                기존 셋업 공간이 writable한 것으로 가정되어도
                setup.S가 relocatable

Protocol 2.01:  (커널 1.3.76) 힙 오버런 경고를 추가.

Protocol 2.02:  (커널 2.4.0-test3-pre3) 새로운 커맨드 라인 프로토콜.
                더 낮은 관례적 memory ceiling. 기존 셋업 공간의 덮어쓰기 없앰,
                그래서 SMM이나 32비트 BIOS 엔트리 포인트로부터 EBDA를
                사용하는 시스템을 안전하게 부팅하도록 만듦.
                zImage를 지원은 하지만, 곧 사라지도록 함.

Protocol 2.03:  (커널 2.4.18-pre1) 명시적으로 부트로더에 가능한 가장 높은
                initrd 주소를 사용가능하도록 만듦.

Protocol 2.04:  (커널 2.6.14) syssize 필드를 4바이트로 확장.

Protocol 2.05:  (커널 2.6.20) 프로텍티드 모드 커널을 relocatable하게 만듦.
                relocatable_kernel과 kernel_alignment 필드를 도입.

Protocol 2.06:  (커널 2.6.22) 부트 커맨드 라인의 크기를 포함하는 필드를 추가.

Protocol 2.07:  (커널 2.6.24) paravirtualised 부트 프로토콜 추가.
                hardware_subarch와 hardware_subarch_data, 그리고
                load_flags 안의 KEEP_SEGMENTS 플래그를 도입.

Protocol 2.08:  (커널 2.6.26) crc32 체크섬과 ELF 형식 페이로드를 추가.
                payload_offset과 payload_length 필드를 페이로드를
                위치시키는데 조정할 수 있도록 도입.

Protocol 2.09:  (커널 2.6.26) setup_data 구조체의 링크드 리스트 하나로의
                64비트 물리 포인터 필드를 추가.

Protocol 2.10:  (커널 2.6.31) 추가된 kernel_alignment를 넘는 relaxed alignment를
                위한 프로토콜 추가, 새로운 new init_size와 pref_address 필드.
                확장된 부트로더 ID들 추가.

Protocol 2.11:  (커널 3.6) EFI 핸드오버 프로토콜 엔트리 포인트의 오프셋을 위한
                필드 추가.

Protocol 2.12:  (커널 3.8) 64비트에서 4G위로 bzImage와 ramdisk를 로딩하기 위한
                boot_params을 구조화하기 위한 xloadflags와 확장 필드들 추가.

**** 메모리 레이아웃
Image나 zImage 커널에 사용되었던 커널 로더를 위한 전통적인 메모리 맵은 보통 다음과 같습니다:

        |                        |
0A0000  +------------------------+
        |  BIOS를 위해 예약됨    |     사용하지 마세요.  BIOS EBDA를 위해 예약됨.
09A000  +------------------------+
        |  커맨드 라인           |
        |  스택/힙               |     리얼 모드 코드가 사용.
098000  +------------------------+  
        |  커널 셋업             |     커널 리얼 모드 코드.
090200  +------------------------+
        |  커널 부트 섹터        |     커널 레가시 부트 섹터.
090000  +------------------------+
        |  프로텍티드 모드 커널  |     대부분의 커널 이미지.
010000  +------------------------+
        |  부트로더              |  <- 부트 섹터 엔트리 포인트 0000:7C00
001000  +------------------------+
        |  MBR/BIOS에 의해 예약됨|
000800  +------------------------+
        |  일반적으로 MBR이 사용 |
000600  +------------------------+ 
        |  BIOS만 사용           |
000000  +------------------------+

bzImage를 사용하면 프로텍티드 모드 커널이 0x100000 (“하이 메모리”)에 재배치되어 있고, 커널 리얼 모드 블럭(부트 섹터, 셋업, 그리고 스택/힙)은 0x10000과 로우 메모리의 끝 사이의 어느 주소에 relocatable로 만들어져 있습니다. 불행히도, 프로토콜 2.00과 2.01에서는 0x9000+ 메모리 범위를 여전히 내부적으로 커널이 사용합니다; 2.02 프로토콜이 이 문제를 해결합니다.

새로운 BIOS들이 확장 BIOS Data 영역(EBDA)으로 부르는 메모리를 로우 메모리의 가장 위 가까이에 좀 많이 할당하기 시작했기 때문에 “memory ceiling” — 부트로더에 닿는 로우 메모리의 가장 높은 지점 –을 최대한 낮게 지키는 것이 바람직합니다. 부트로더는 로우 메모리가 얼마나 사용가능한지를 검증하기 위해서 “INT 12h” BIOS 콜을 사용해야 합니다.

불행히도, INT 12h가 메모리의 양이 너무 적다고 보고하면, 보통은 부트로더가 할 수 있는 게 없고, 사용자에게 에러만 보고할 수 있습니다. 부트로더는 그래서 로우 메모리 안에 필요한 만큼만 적은 공간을 취하도록 설계되어야 합니다. 0x90000 세그먼트 내로 쓰여지는 데이터가 필요한 zImage나 오래된 bzImage 커널을 위해 부트로더는 0x9A000 위의 메모리를 사용하지 않는지 확인해야 합니다; 너무나 많은 BIOS들이 이 지점 위를 깹니다.

근래의 부트 프로토콜 버전 >= 2.02인 bzImage 커널을 위해 다음과 같은 메모리 레이아웃이 제안되었습니다:

        ~                        ~
        |  프로텍티드 모드 커널  |
100000  +------------------------+
        |  I/O 메모리 홀         |
0A0000  +------------------------+
        |  BIOS를 위해 예약됨    |     가능한 사용하지 않은채로 두세요
        ~                        ~
        |  커맨드 라인           |     (X+10000 표시 아래도 사용 가능)
X+10000 +------------------------+
        |  스택/힙               |     커널 리얼 모드 코드가 사용하도록
X+08000 +------------------------+  
        |  커널 셋업             |     커널 리얼 모드 코드.
        |  커널 부트 섹터        |     커널 레가시 부트 섹터.
X       +------------------------+
        |  부트 로더             |  <- 부트 섹터 엔트리 포인트 0000:7C00
001000  +------------------------+
        |  MBR/BIOS를 위해 예약됨|
000800  +------------------------+
        |  일반적으로 MBR이 사용 |
000600  +------------------------+ 
        |  BIOS만 사용           |
000000  +------------------------+

여기서 주소 X는 부트로더의 디자인이 허용하는 한 낮습니다.

 

**** 리얼 모드 커널 헤더

다음 본문에서 커널 부트 시퀀스의 어디에서든, “섹터”는 512 바이트로 참조됩니다. 이는 밑바탕의 미디어의 실제 섹터 크기에 무관합니다.

리눅스 커널을 로딩하는 첫 단계는 리얼 모드 코드(부트 섹터와 셋업 코드)를 로드하고 난 후 오프셋 0x01f1에 뒤따르는 헤더를 검사하는 것이어야 합니다. 리얼 모드 코드는 부트로더가 처음 두 섹터 (1K)만 로드하고나서 부트업 섹터 크기를 검사하도록 골라질 수 있다 하더라도, 전부 32K까지 될 수 있습니다.

헤더는 다음과 같습니다:

오프셋  Proto   이름                  의미
/크기

01F1/1  ALL(1   setup_sects           섹터로 된 셋업의 크기
01F2/2  ALL     root_flags            셋팅되면 그 루트는 readonly로 마운트됨
01F4/4  2.04+(2 syssize               16-byte paras로 된 32비트 코드의 크기
01F8/2  ALL     ram_size              사용 금지 - bootsect.S만 사용
01FA/2  ALL     vid_mode              비디오 모드 제어
01FC/2  ALL     root_dev              디폴트 루트 디바이스 번호
01FE/2  ALL     boot_flag             0xAA55 매직 넘버
0200/2  2.00+   jump                  점프 명령
0202/4  2.00+   header                매직 시그니처 "HdrS"
0206/2  2.00+   version               지원되는 부트 프로토콜 버전
0208/4  2.00+   realmode_swtch        부트로더 훅 (아래 참조)
020C/2  2.00+   start_sys_seg         load-low 세그먼트 (0x1000) (구식)
020E/2  2.00+   kernel_version        커널 버전 스트링의 포인터
0210/1  2.00+   type_of_loader        부트로더 식별자
0211/1  2.00+   loadflags             부트 프로토콜 옵션 플래그
0212/2  2.00+   setup_move_size       하이 메모리로 옮기는 크기 (훅과 사용)
0214/4  2.00+   code32_start          부트로더 훅 (아래 참조)
0218/4  2.00+   ramdisk_image         initrd 로드 주소 (부트로더가 셋팅)
021C/4  2.00+   ramdisk_size          initrd 크기 (부트로더가 셋팅)
0220/4  2.00+   bootsect_kludge       사용 금지 - bootsect.S만 사용
0224/2  2.01+   heap_end_ptr          셋업 끝 이후 남는 메모리
0226/1  2.02+(3 ext_loader_ver        확장된 부트로더 버전
0227/1  2.02+(3 ext_loader_type       확장된 부트로더 ID
0228/4  2.02+   cmd_line_ptr          커널 커맨드 라인으로의 32비트 포인터
022C/4  2.03+   initrd_addr_max       가장 높이 허용되는 initrd 주소
0230/4  2.05+   kernel_alignment      커널을 위해 요구되는 물리 메모리 얼라인먼트
0234/1  2.05+   relocatable_kernel    커널이 재배치 가능한지 아닌지
0235/1  2.10+   min_alignment         2의 거듭제곱인 얼라인먼트의 최소값
0236/2  2.12+   xloadflags            부트 프로토콜 옵션 플래그
0238/4  2.06+   cmdline_size          커널 커맨드 라인의 최대 크기
023C/4  2.07+   hardware_subarch      하드웨어의 서브 아키텍처
0240/8  2.07+   hardware_subarch_data 서브 아키텍처 전용 Data
0248/4  2.08+   payload_offset        커널 페이로드의 오프셋
024C/4  2.08+   payload_length        커널 페이로드의 길이
0250/8  2.09+   setup_data            setup_data 구조체의 링크드 리스트로의 64비트 물리 포인터
0258/8  2.10+   pref_address          선호하는 로딩 주소
0260/4  2.10+   init_size             초기화하는 동안 필요한 리니어(Linear) 메모리
0264/4  2.11+   handover_offset       핸드오버 엔트리 포인트의 오프셋

(1) 하위 호환을 위해, setup_sects필드가 0을 가지면, 실제 값은 4.

(2) 2.04 이전의 부트 프로토콜을 위해, syssize 필드의 상위 2바이트는 사용 불가다.
    이는 bzImage 커널의 크기는 결정될 수 없음을 의미한다.

(3) 무시되지만 부트 프로토콜 2.02-2.09를 위해 셋팅하는 것이 안전하다.

“HdrS” (0x53726448) 매직 넘버가 오프셋 0x202에 없다면 부트 프로토콜 버전은 “오래된” 것입니다. 오래된 커널을 로딩하면 다음 파라미터들이 가정되어야 합니다:

이미지 타입 = zImage
initrd는 지원하지 않음
리얼 모드 커널은 0x90000에 위치해야 함.

아니면, “version” 필드가 프토토콜 버전을, 예를 들면 프로토콜 버전 2.01은 0x0201을 이 필드 안에 가지는 것처럼 가집니다. 헤더 안의 필드를 셋팅할 때 사용하는 프로토콜 버전이 지원하는 필드만 셋팅하는지 확인해야 합니다.

 

**** 헤더 필드의 상세 사항

각 필드에 대해 어떤 것들은 커널로부터 부트로더로의 정보이고(“read”), 어떤 것들은 부트로더에 의해 채워질 것으로 기대되고(“write”), 그리고 어떤 것들은 부트로더에 의해 읽히고 수정될 것으로 기대됩니다(“modify”).

모든 일반 목적의 부트로더들은 (obligatory) 표시된 필드들을 써야합니다. 커널을 비표준 주소에 로드하길 원하는 부트로더들은 (reloc) 표시된 필드들을 채워야 합니다; 다른 부트로더들은 이들 필드를 무시할 수 있습니다.

모든 필드의 바이트 오더는 리틀엔디안입니다 (이건 전부 x86입니다).

필드 이름:    setup_sects
타입:         read
오프셋/크기:  0x1f1/1
프로토콜:     ALL

셋업 코드의 크기는 512 바이트 섹터 수로 되어 있습니다. 이 필드가 0이면 실제 값은 4입니다. 리얼 모드 코드는 부트 섹터 (항상 512 바이트 하나) + 셋업 코드로 구성됩니다.

필드 이름:    root_flags
타입:         modify (옵션)
오프셋/크기:  0x1f2/2
프로토콜:     ALL

이 필드가 0이 아니면, 루트는 디폴트로 readonly. 이 필드는 앞으로 사라집니다; 대신 커맨드 라인 상에 “ro” 또는 “rw” 옵션을 사용하세요.

필드 이름:    syssize
타입:         read
오프셋/크기:  0x1f4/4 (프로토콜 2.04+) 0x1f4/2 (모든 프로토콜)
프로토콜:     2.04+

16 바이트 단위로 된 프로텍티드 모드 코드의 크기. 2.04 이전의 프로토콜 버전에서 이 필드는 2바이트 길이로만 사용되었기 때문에 LOAD_HIGH 플래그가 셋팅되어 있다면 커널의 크기를 믿을 수 없습니다.

필드 이름:    ram_size
타입:         kernel internal
오프셋/크기:  0x1f8/2
프로토콜:     ALL

이 필드는 구식입니다.

필드 이름:    vid_mode
타입:         modify (obligatory)
오프셋/크기:  0x1fa/2

특별한 커맨드 라인 옵션 절을 보세요.

필드 이름:    root_dev
타입:         modify (optional)
오프셋/크기:  0x1fc/2
프로토콜:     ALL

디폴트 루트 디바이스의 디바이스 넘버. 이 필드는 곧 사라집니다. 대신 커맨드 라인 상에서 “root=” 옵션을 사용하세요.

필드 이름:    boot_flag
타입:         read
오프셋/크기:  0x1fe/2
프로토콜:     ALL

0xAA55를 가짐. 이는 오래된 리눅스 커널들이 가지는 매직 넘버와 가장 비슷합니다.

필드 이름:    jump
타입:         read
오프셋/크기:  0x200/2
프로토콜:     2.00+

0xEB 다음 부호있는 0x202 바이트에서의 상대적인 오프셋이 뒤에 오는 x86 점프 명령을 가짐. 이는 헤더의 크기를 결정하는데 사용될 수 있습니다.

필드 이름:    header
타입:         read
오프셋/크기:  0x202/4
프로토콜:     2.00+

매직 넘버 “HdrS” (0x53726448)를 가짐.

필드 이름:    version
타입:         read
오프셋/크기:  0x206/2
프로토콜:     2.00+

(메이저 << 8) + 마이너 형식, 예를 들면, 버전 2.04는 0x0204, 가상의 버전 10.17은 0x0a11로 된 부트 프로토콜 버전을 가짐.

필드 이름:    realmode_swtch
타입:         modify (optional)
오프셋/크기:  0x208/4
프로토콜:     2.00+

부트로더 훅 (아래의 고급 부트로더 훅을 보세요)

필드 이름:    start_sys_seg
타입:         read
오프셋/크기:  0x20c/2
프로토콜:     2.00+

Load low 세그먼트 (0x1000). 구식.

필드 이름:    kernel_version
타입:         read
오프셋/크기:  0x20e/2
프로토콜:     2.00+

0이 아닌 값으로 셋팅되어 있다면 NUL로 끝나는 사람이 읽을 수 있는 커널 버전 넘버 스트링으로의 포인터보다 0x200보다 작은 포인터를 담습니다. 이는 유저에게 커널 버전을 보여주는데 사용될 수 있습니다. 이 값은 (0x200*setup_sects) 보다 작아야 합니다.

예를 들어, 이 값이 0x1c00으로 셋팅되어 있으면, 커널 버전 넘버 스트링은 커널 파일 내의 오프셋 0x1e00에서 찾을 수 있습니다. 이 값은 다음처럼 “setup_sects” 필드가 15 이상의 값을 담고 있을 때만 유효합니다:

0x1c00 < 15*0x200 (= 0x1e00) 그러나
0x1c00 >= 14*0x200 (= 0x1c00)

0x1c00 >> 9 = 14, 그래서 setup_sects의 최소값은 15.

필드 이름:    type_of_loader
타입:         write (obligatory)
오프셋/크기:  0x210/1
프로토콜:     2.00+

부트로더가 할당된 id를 가지면, 0xTV가 여기에 들어가는데, T는 부트로더의 identifier이고, V는 버전 넘버입니다. 아니면 0xFF가 여기에 들어갑니다.

T = 0xD 위의 부트로더 ID들을 위해서는, T = 0xE를 이 필드에 쓰고 확장된 ID – 0x10을 ext_loader_type 필드에 씁니다. 비슷하게 ext_loader_ver 필드는 부트로더 버전의 4비트 이상을 제공하는데 사용될 수 있습니다.

예를 들면, T = 0x15, V = 0x234는 다음처럼 씁니다:

  type_of_loader  <- 0xE4
  ext_loader_type <- 0x05
  ext_loader_ver  <- 0x23

할당된 부트로더 ID들 (16진수):

   0  LILO         (0x00이 pre-2.00 bootloader에 예약됨)
    1  Loadlin
    2  bootsect-loader  (0x20, 다른 모든 값은 예약됨)
    3  Syslinux
    4  Etherboot/gPXE/iPXE
    5  ELILO
    7  GRUB
    8  U-Boot
    9  Xen
    A  Gujin
    B  Qemu
    C  Arcturus Networks uCbootloader
    D  kexec-tools
    E  Extended     (ext_loader_type를 보세요)
    F  Special      (0xFF = undefined)
       10  Reserved
       11  Minimal Linux Bootloader <http://sebastian-plotz.blogspot.de>
       12  OVMF UEFI virtualization stack

부트로더 ID 값의 할당이 필요하면, <hpa@zytor.com>에 연락하세요.

필드 이름:    loadflags
타입:         modify (obligatory)
오프셋/크기:  0x211/1
프로토콜:     2.00+

이 필드는 비트마스크이다.

  Bit 0 (read):    LOADED_HIGH
    - 0이면, 프로텍티드 모드 코드가 0x10000에 로드됨.
    - 1이면, 프로텍티드 모드 코드가 0x100000에 로드됨.

  Bit 1 (커널 내부용): KASLR_FLAG
    - 압축된 커널이 적절히 커널로 KASLR 상태를 통신하는데 내부적으로 사용됨
      1이면, KASLR 켜짐.
      0이면, KASLR 꺼짐.

  Bit 5 (write): QUIET_FLAG
    - 0이면, 얼리(early) 메시지를 출력.
    - 1이면, 얼리(early) 메시지를 찍지 않음.
        이는 커널 (decompressor와 얼리 커널)에게 디스플레이 하드웨어에
        직접 접근이 필요한 얼리 메시지를 쓰지 않도록 요청함.

  Bit 6 (write): KEEP_SEGMENTS
    프로토콜: 2.07+
    - 0이면, 32비트 엔트리 포인트 내에 세그먼트 레지스터들을 다시 로드.
    - 1이면, 32비트 엔트리 포인트 내에 세그먼트 레지스터들을 다시 로드하지 않음.
        %cs %ds %ss %es 가 모두 베이스 0에 (또는 그들의 환경에 동등하게)
        플랫(flat) 세그먼트로 셋팅되었음을 가정.

  Bit 7 (write): CAN_USE_HEAP
    heap_end_ptr 내에 들어있는 값이 유효함을 나타내기 위해서 이 비트를 1로 셋팅.
    이 필드가 셋팅되어 있지 않으면, 어떤 셋업 코드 기능은 꺼짐.
필드 이름:    setup_move_size
타입:         modify (obligatory)
오프셋/크기:  0x212/2
프로토콜:     2.00-2.01

프로토콜 2.00 또는 2.01을 사용할 때, 리얼 모드 커널이 0x90000에 로드되지 않으면, 로딩 시퀀스 안에서 나중에 옮겨집니다. 리얼 모드 커널 자체 뿐만 아니라 추가로 옮길 (커널 커맨드 라인 같은) 추가 데이터를 원한다면 이 필드를 채우세요.

단위는 부트 섹터의 시작에서 시작하는 바이트 수입니다.

이 필드는 프로토콜 2.02 이상 또는 리얼 모드 코드가 0x90000에 로드된다면 무시될 수 있습니다.

필드 이름:    code32_start
타입:         modify (optional, reloc)
오프셋/크기:  0x214/4
프로토콜:     2.00+

프로텍티드 모드 내에서 점프할 주소. 이는 커널의 로드 주소가 디폴트이고 부트로로가 적절한 로드 주소를 결정하는데 사용될 수 있습니다.

이 필드는 두가지 목적으로 바뀔 수 있습니다:

1. 부트로더 훅으로써 (아래의 고급 부트로더 훅을 보세요)

2. 혹이 설치되지 않은 부트로더가 재배치 가능한 커널을 비표준 주소에 로드하면 로드 주소를 가리키는 이 필드를 바꿔야 합니다.

필드 이름:    ramdisk_image
타입:         write (obligatory)
오프셋/크기:  0x218/4
프로토콜:     2.00+

32비트 초기 램디스크나 ramfs의 리니어(linear) 주소. 초기 램디스크/ramfs가 없으면 0으로 두세요.

필드 이름:    ramdisk_size
타입:         write (obligatory)
오프셋/크기:  0x21c/4
프로토콜:     2.00+

초기 램디스크나 ramfs의 크기. 초기 램디스크/ramfs가 없으면 0으로 두세요.

필드 이름:    bootsect_kludge
타입:         kernel internal
오프셋/크기:  0x220/4
프로토콜:     2.00+

이 필드는 구식입니다.

필드 이름:    heap_end_ptr
타입:         write (obligatory)
오프셋/크기:  0x224/2
프로토콜:     2.01+

셋업 스택/힙의 끝의 (리얼 모드 코드의 시작으로부터의) 오프셋 – 0x0200로 이 필드를 셋팅하세요.

필드 이름:    ext_loader_ver
타입:         write (optional)
오프셋/크기:  0x226/1
프로토콜:     2.02+

이 필드는 type_of_loader 필드 내의 버전 넘버의 확장으로 사용됩니다. 합친 버전 넘버는 (type_of_loader & 0x0f) + (ext_loader_ver << 4)로 처리됩니다.

이 필드의 사용은 부트로더에 따라 다릅니다. 안쓰이면 0입니다.

2.6.31 이전의 커널은 이 필드를 인식하지 않습니다만 프로토콜 버전 2.02 이상을 위해 쓰이는 것이 안전합니다.

필드 이름:    ext_loader_type
타입:         write (obligatory if (type_of_loader & 0xf0) == 0xe0)
오프셋/크기:  0x227/1
프로토콜:     2.02+

이 필드는 type_of_loader 필드 내의 타입 넘버의 확장으로 사용됩니다. type_of_loader의 타입이 0xE이면, 실제 타입은 (ext_loader_type + 0x10)입니다.

이 필드는 type_of_loader가 0xE가 아니면 무시됩니다.

2.6.31 이전의 커널은 이 필드를 인식하지 않습니다만, 프로토콜 버전 2.02 이상을 위해 쓰이는 것이 안전합니다.

필드 이름:    cmd_line_ptr
타입:         write (obligatory)
오프셋/크기:  0x228/4
프로토콜:     2.02+

커널 커맨드 라인의 리니어 주소로 이 필드를 셋팅하세요. 커널 커맨드 라인은 셋업 힙의 끝에서 0xA0000 사이의 어디에나 위치될 수 있습니다; 리얼 모드 코드 그 자체와 같은 64K 세그먼트내에 있을 필요 없습니다.

부트로더가 커맨드 라인을 지원하지 않는다해도 이 필드를 빈 스트링으로 (또는 더 나은 “auto” 스트링으로) 가리킬 수 있게 채우세요. 이 필드가 0으로 남아있다면 커널은 부트로더가 2.02+ 프로토콜을 지원하지 않는다고 가정할 겁니다.

필드 이름:    initrd_addr_max
타입:         read
오프셋/크기:  0x22c/4
프로토콜:     2.03+

초기 램디스크/ramfs 내용으로 채워질 수 있는 최대 주소. 부트 프로토콜 2.02 이전에는 이 필드가 없고, 최대 주소가 0x37FFFFFF입니다. (이 주소는 가장 안전한 바이트 주소로 정의되므로, 램디스크가 정확히 131072 바이트 길이이고 이 필드는 0x37FFFFFF라면 램디스크는 0x37FE0000에서 시작할 수 있습니다)

필드 이름:    kernel_alignment
타입:         read/modify (reloc)
오프셋/크기:  0x230/4
프로토콜:     2.05+ (read), 2.10+ (modify)

(relocatable_kernel 이 true라면) 커널에 필요한 얼라인먼트(alignment) 단위. 이 필드 내에 값으로 얼라인먼트에 맞지 않는 곳에 로드된 재배치가능 커널은 커널 초기화시에 다시 얼라인될 겁니다.

프로토콜 버전 2.10부터, 이는 최적의 성능을 위해 선호되는 커널 얼라인먼트를 반영합니다; 로더가 더 적은 얼라인먼트를 허용하기 위해 이 필드를 바꾸는 것이 가능합니다. 아래의 min_alignment와 pref_address 필드를 보세요.

필드 이름:    relocatable_kernel
타입:         read (reloc)
오프셋/크기:  0x234/1
프로토콜:     2.05+

이 필드가 0이 아니면, 커널의 프로텍티드 모드 부분이 kernel_alignment 필드를 만족하는 어느 주소에도 로드될 수 있습니다. 로딩 후에 부트로더는 로드된 코드 또는 부트로더 훅을 가리키도록 code32_start 필드를 셋팅해야 합니다.

필드 이름:    min_alignment
타입:         read (reloc)
오프셋/크기:  0x235/1
프로토콜:     2.10+

이 필드가 0이 아니면, 선호되는 것과 반대로, 커널이 부팅하기 위해 필요한 최소 얼라인먼트의 2의 거듭제곱 값을 나타냅니다. 부트로더가 이 필드를 사용한다면, 원하는 얼라인먼트 단위로 kernel_alignment 필드를 업데이트해야 합니다; 일반적으로:

kernel_alignment = 1 << min_alignment

지나치게 정렬되지 않은 커널에는 상당한 성능 비용이 있을 수 있습니다. 그래서 로더는 일반적으로 kernel_alignment로부터 2의 거듭제곱씩 이 얼라인먼트로 내려가면서 시도하는 것이 좋습니다.

필드 이름:    xloadflags
타입:         read
오프셋/크기:  0x236/2
프로토콜:     2.12+

이 필드는 비트 마스크입니다.

  Bit 0 (read):    XLF_KERNEL_64
    - 1이면, 이 커널은 0x200에 레가시 64비트 엔트리 포인트를 갖습니다.

  Bit 1 (read): XLF_CAN_BE_LOADED_ABOVE_4G
        - 1이면, kernel/boot_params/cmdline/ramdisk은 4G 위에 있을 수 있습니다.

  Bit 2 (read): XLF_EFI_HANDOVER_32
    - 1이면, 커널이 handover_offset에서 주어지는 32비트 EFI 핸드오프 엔트리 포인트를 지원합니다.

  Bit 3 (read): XLF_EFI_HANDOVER_64
    - 1이면, 커널이 handover_offset + 0x200에서 주어지는 64비트 EFI 핸드오프 엔트리
          포인트를 지원합니다.

  Bit 4 (read): XLF_EFI_KEXEC
    - 1이면, 커널이 EFI 런타임 지원으로 kexec EFI 부트를 지원합니다.
필드 이름:    cmdline_size
타입:         read
오프셋/크기:  0x238/4
프로토콜:     2.06+

0으로 끝나는 것을 뺀 커맨드 라인의 최대 크기. 이는 커맨드 라인이 최대 cmdline_size 문자를 담을 수 있음을 의미합니다. 프로토콜 버전 2.05 이전에는 그 최대 크기가 255였습니다.

필드 이름:    hardware_subarch
타입:         write (optional, defaults to x86/PC)
오프셋/크기:  0x23c/4
프로토콜:     2.07+

paravirtualized 환경에서 인터럽트 처리, 페이지 테이블 처리, 그리고 레지스터를 제어하는 접근 처리와 같은 하드웨어 로우 레벨 아키텍처적인 부분은 다르게 수행될 필요가 있습니다.

이 필드는 부트로더가 커널에게 우리가 아래와 같은 환경 내에 있음을 알릴 수 있도록 합니다.

  0x00000000   기본 x86/PC 환경
  0x00000001    lguest
  0x00000002    Xen
  0x00000003    Moorestown MID
  0x00000004    CE4100 TV 플랫폼
필드 이름:    hardware_subarch_data
타입:         write (subarch-dependent)
오프셋/크기:  0x240/8
프로토콜:     2.07+

하드웨어 서브아키(subarch) 전용 데이터로의 포인터. 이 필드는 현재 기본 x86/PC 환경에선 사용되지 않으므로, 수정하지 마세요.

필드 이름:    payload_offset
타입:         read
오프셋/크기:  0x248/4
프로토콜:     2.08+

0이 아니면, 이 필드는 프로텍티드 모드 코드의 시작에서 페이로드까지의 오프셋을 담습니다.

페이로드는 압축될 수 있습니다. 압축된, 그리고 압축되지 않은 데이터 둘 모두의 형식은 표준 매직 넘버를 사용해서 결정되어야 합니다. 현재 지원하는 압축 형식은 gzip(매직 넘버는 1F 8B 또는 1F 9E), bzip2 (매직 넘버 42 5A), LZMA (매직 넘버 5D 00), XZ (매직 넘버 FD 37), 그리고 LZ4 (매직 넘버 02 21) 입니다. 압축되지 않은 페이로드는 현재 항상 ELF (매직 넘버 7F 45 4C 46) 입니다.

필드 이름:    payload_length
타입:         read
오프셋/크기:  0x24c/4
프로토콜:     2.08+

페이로드의 길이

필드 이름:    setup_data
타입:         write (special)
오프셋/크기:  0x250/8
프로토콜:     2.09+

NULL로 끝나는 struct setup_data의 싱글 링크드 리스트로의 64비트 물리 포인터. 메카니즘을 넘기는 더 확장된 부트 파라미터를 정의하는데 사용됩니다. struct setup_data의 정의는 다음과 같습니다:

  struct setup_data {
      u64 next;
      u32 type;
      u32 len;
      u8  data[0];
  };

여기서 next는 링크드 리스트의 다음 노드로의 64비트 물리 포인터이고, 마지막 노드의 next 필드는 0입니다; type은 데이터의 내용을 식별하는데 사용됩니다; len은 data 필드의 길이입니다; data는 진짜 페이로드를 담습니다.

이 리스트는 부팅 과정 중에 여러 곳에서 수정될 수 있습니다. 그래서 이 리스트를 수정할 때 항상 이 링크드 리스트가 이미 엔트리를 담고 있는 경우를 고려해 확인해야 합니다.

필드 이름:    pref_address
타입:         read (reloc)
오프셋/크기:  0x258/8
프로토콜:     2.10+

0이 아니면, 이 필드는 커널을 위한 선호하는 로드 주소를 나타냅니다. 재배치 중인 부트로더는 가능하면 이 주소에 로드를 시도해야 합니다.

재배치 가능이 아닌 커널은 무조건 이 주소로 그 자신을 옮기고 실행할 겁니다.

필드 이름:    init_size
타입:         read
오프셋/크기:  0x260/4

이 필드는 커널이 그 메모리 맵을 시험할 수 있기도 전에 커널이 필요로 하는 커널 런타임 시작 주소에서 시작하는 리니어하게 연속된 메모리의 양을 나타냅니다. 이는 커널이 부팅하기 위해 필요한 전체 메모리의 양과는 다릅니다만, 재배치 중인 부트로더가 커널을 안전하게 로드할 주소를 고르는데 도움을 주는데 사용될 수 있습니다.

커널 런타임 시작 주소는 다음 알고리듬으로 결정됩니다:

  if (relocatable_kernel)
    runtime_start = align_up(load_address, kernel_alignment)
  else
    runtime_start = pref_address
필드 이름:    handover_offset
크기:         read
오프셋/크기:  0x264/4

이 필드는 커널 이미지에서 EFI 핸드오버 프로토콜 엔트리 포인트까지의 오프셋입니다. 커널을 부팅하는데 EFI 핸드오버 프로토콜을 사용하는 부트로더는 이 오프셋으로 점프해야 합니다.

더 자세한 사항은 EFI 핸드오버 프로토콜을 보세요.

 

**** 이미지 체크섬

부트 프로토콜 버전 2.08 부터 CRC-32가 특유의 폴리노미얼 0x04C11DB7과 초기 나머지 0xffffffff를 사용해서 전체 파일에 걸쳐 계산됩니다. 체크섬은 파일에 덧붙여집니다; 그래서 헤더의 syssize 필드 내에 지정된 한계까지의 그 파일의 CRC는 항상 0입니다.

 

**** 커널 커맨드 라인

커널 커맨드 라인은 부트로더와 커널이 통신하는 중요한 수단이 되었습니다. 그 옵션 중 일부는 또한 부트로더 그 자신과 관련됩니다. 아래의 “특별한 커맨드 라인 옵션”을 보세요.

커널 커맨드 라인은 null로 끝나는 스트링입니다. 최대 길이는 필드 cmdline_size로부터 얻을 수 있습니다. 프로토콜 버전 2.06 전에는 최대 255 문자였습니다. 너무 긴 스트링은 커널에 의해 자동으로 잘릴 겁니다.

부트 프로토콜 버전이 2.02 이후라면, 커널 커맨드 라인의 주소는 헤더 필드 cmd_line_ptr에 의해 주어집니다 (위를 보세요). 이 주소는 setup heap의 끝과 0xA0000 사이의 어느 곳에든 될 수 있습니다.

프로토콜 버전이 2.02가 아니거나 더 높다면, 커널 커맨드 라인은 다음 프로토콜을 사용해서 들어갑니다:

   오프셋 0x0020 (word)에, "cmd_line_magic", 매직 넘버 0xA33F가 들어갑니다.

    오프셋 0x0022 (word)에, "cmd_line_offset", (리얼 모드 커널의 시작과 관련된)
    커널 커맨드 라인의 오프셋이 들어갑니다.
    
    커널 커맨드 라인은 *반드시* setup_move_size로 커버되는 메모리 영역 내에 있어야 하므로,
    이 필드는 조정될 필요가 있을 수 있습니다.

 

**** 리얼 모드 코드의 메모리 레이아웃

리얼 모드 코드는 셋업되기 위해 스택/힙뿐 만 아니라 커널 커맨드 라인을 위해 할당된 메모리를 필요로 합니다. 이는 아래쪽 메가바이트 내에 리얼 모드 접근 가능한 메모리 내에서 수행될 필요가 있습니다.

근래의 머신들은 종종 상당히 큰 확장된 BIOS 데이터 영역(EBDA = Extended BIOS Data Area)을 갖는다는 것을 주의해야 합니다. 그래서 가능한한 적게 낮은 메가바이트를 사용하는 것이 권고됩니다.

불행히도 다음 상황 아래에서 0x90000 메모리 세그먼트가 사용되어야 합니다:

   - zImage kernel ((loadflags & 0x01) == 0)을 로딩할 때.
    - 2.01 또는 그 이전의 부트 프로토콜 커널을 로딩할 때.

      -> 2.00과 2.01 부트 프로토콜에서는 리얼 모드 코드가 다른 주소에
         로드될 수 있지만 내부적으로 0x90000에 재배치됩니다.
         "오래된" 프로토콜에서는 리얼 모드 코드는 0x90000에 로드됩니다.

0x90000에 로딩될 때, 0x9a000 위의 메모리 사용을 피하세요.

부트 프로토콜 2.02 또는 그 이상에서는, 커맨드 라인이 리얼 모드 셋업 코드와 같은 64K 세그먼트 내에 있을 필요는 없습니다; 그래서 스택/힙에 64K 세그먼트 전체를 주고, 그 위쪽에 커맨드 라인을 두는 것이 허용됩니다.

커널 커맨드 라인은 리얼모드 코드 아래 쪽 또는 하이 메모리 내에 두지 않아야 합니다.

 

**** 샘플 부트 설정

샘플 설정으로 리얼 모드 세그먼트의 아래 레이아웃을 가정합니다:

    0x90000 아래에 로딩할 때, 전체 세그먼트를 사용합니다:

    0x0000-0x7fff   리얼 모드 커널
    0x8000-0xdfff   스택과 힙
    0xe000-0xffff   커널 커맨드 라인

    0x90000에 로딩할 때 또는 프로토콜 버전이 2.01 또는 그 이전일 때:

    0x0000-0x7fff   리얼 모드 커널
    0x8000-0x97ff   스택과 힙
    0x9800-0x9fff   커널 커맨드 라인

부트로더는 그 헤더 내에 다음 필드를 넣어야 합니다:

   unsigned long base_ptr; /* 리얼 모드 세그먼트의 베이스 주소 */

    if ( setup_sects == 0 ) {
        setup_sects = 4;
    }

    if ( protocol >= 0x0200 ) {
        type_of_loader = <type code>;
        if ( loading_initrd ) {
            ramdisk_image = <initrd_address>;
            ramdisk_size = <initrd_size>;
        }

        if ( protocol >= 0x0202 && loadflags & 0x01 )
            heap_end = 0xe000;
        else
            heap_end = 0x9800;

        if ( protocol >= 0x0201 ) {
            heap_end_ptr = heap_end - 0x200;
            loadflags |= 0x80; /* CAN_USE_HEAP */
        }

        if ( protocol >= 0x0202 ) {
            cmd_line_ptr = base_ptr + heap_end;
            strcpy(cmd_line_ptr, cmdline);
        } else {
            cmd_line_magic  = 0xA33F;
            cmd_line_offset = heap_end;
            setup_move_size = heap_end + strlen(cmdline)+1;
            strcpy(base_ptr+cmd_line_offset, cmdline);
        }
    } else {
        /* 매우 오래된 커널 */

        heap_end = 0x9800;

        cmd_line_magic  = 0xA33F;
        cmd_line_offset = heap_end;

        /* 매우 오래된 커널은 그 리얼 모드 코드를 0x90000에
           로드해야만 한다 */

        if ( base_ptr != 0x90000 ) {
            /* 리얼 모드 커널 복사 */
            memcpy(0x90000, base_ptr, (setup_sects+1)*512);
            base_ptr = 0x90000;      /* 재배치 */
        }

        strcpy(0x90000+cmd_line_offset, cmdline);

        /* 32K 표시까지 메모리를 깨끗이 하는 것이 권장된다 */
        memset(0x90000 + (setup_sects+1)*512, 0,
               (64-(setup_sects+1))*512);
    }

 

**** 커널의 나머지 로딩

32비트 (리얼 모드가 아닌) 커널은 커널 파일 내의 오프셋 (setup_sects+1)*512에서 시작합니다(다시 한번, setup_sects == 0이면, 실제 값은 4). Image/zImage 커널에서는 주소 0x10000에, bzImage 커널에서는 주소 0x100000에 로드되어야 합니다.

프로토콜 >= 2.00 이고, loadflags 필드 내의 0x01 비트 (LOAD_HIGH)가 셋팅되어 있으면, 커널은 bzImage 커널입니다:

   is_bzImage = (protocol >= 0x0200) && (loadflags & 0x01);
    load_address = is_bzImage ? 0x100000 : 0x10000;

Image/zImage 커널은 그 크기로 512K까지 될 수 있음을, 그래서 메모리의 0x10000-0x90000 범위 전체를 사용함을 주의하세요. 이는 이들 커널이 리얼 모드 부분을 0x90000에 로드하는 중요한 요구사항임을 의미합니다. bzImage 커널은 더 유연하게 허용합니다.

 

**** 특별한 커맨드 라인 옵션

부트로더가 제공한 커맨드 라인이 사용자에 의해 입력되었다면, 사용자는 다음 커맨드 라인 옵션이 동작할 거라고 기대할 수 있습니다. 그들은 보통 커널 커맨드 라인으로부터 그들 전체가 실제로 커널에 의미가 없다 하더라도 지우지 말아야 합니다. 부트로더 그 자신을 위한 추가 커맨드 라인 옵션이 필요한 부트로더 작성자들은 지금 또는 그 이후에 실제 커널 옵션과 충돌하지 않음을 확인하기 위해서 Documentation/admin-guide/kernel-parameters.rst 내에 등록된 것들을 보아야 합니다.

  vga=<mode>
    <mode> 여기는 정수(C 표기로, 10진수, 8진수, 또는 16진수)거나
    "normal" (0xFFFF를 의미), "ext" (0xFFFE를 의미) 또는 "ask"(0xFFFD를 의미)
    중 하나의 스트링입니다. 이 값은 커맨드 라인이 파싱되기 전에 커널이 사용하는
    vid_mode 필드 내에 입력되어야 합니다.

  mem=<size>
    <size> 는 (대소문자 구별 없는) K, M, G, T, P 또는 E (<< 10, << 20,
    << 30, << 40, << 50 or << 60을 의미)가 옵션으로 붙는 C 표기의 정수입니다.
    이는 메모리의 끝을 커널에 지정합니다. 이는 initrd를 메모리의 끝 근처에
    두어야 하기 때문에 initrd의 가능한 배치에 영향을 미칩니다.
    이는 커널과 부트로더 *둘 모두*에 옵션임을명심하세요!

  initrd=<file>
    initrd가 로드되어야 합니다. <file>의 의미는 부트로더에 따라 명확히 다르고,
    어떤 부트로더들(e.g. LILO)은 이런 커맨드를 갖지 않습니다.

추가로 어떤 부트로더들은 다음 옵션을 사용자 지정 커맨드 라인으로 추가합니다:

  BOOT_IMAGE=<file>
    로드되었던 부트 이미지. 다시 한번, <file>의 의미는 부트로더에 따라 
    명확히 다릅니다.

  auto
    명확한 사용자 간섭없이 커널이 부팅되었습니다.

이들 옵션을 부트로더가 추가하면, 그들을 사용자 지정 혹은 설정을 지정하는 커맨드 라인보다 먼저 두는 것을 강력히 권합니다. 아니면 “auto” 옵션에 의해 “init=/bin/sh”가 혼란스럽게 됩니다.

 

**** 커널 실행

커널은 리얼 모드 커널의 시작으로부터 세그먼트 오프셋 0x20 에 위치한 커널 엔트리 포인트로 점프하면서 시작됩니다. 이는 여러분의 리얼 모드 커널 코드를 0x90000에 로드하였다면, 커널 엔트리 포인트는 9020:0000 이라는 뜻입니다.

엔트리에서, ds = es = ss 는 리얼 모드 커널 코드의 시작 (0x90000에 코드가 로드되었다면 0x9000)을 가리키고, sp는 적절히, 보통 힙의 가장 높은 곳을 가리키도록 셋업되어야 하고, 인터럽트들이 꺼져있어야 합니다. 추가로 커널 내의 버그를 막기 위해서 부트로더가 fs = gs = ds = fs = ss로 셋팅하는 것이 권장됩니다.

위의 우리의 예제에서 우리는 이렇게 할 겁니다:

   /* 주의: "오래된" 커널 프로토콜의 경우, 이 시점에 base_ptr 은 == 0x90000여야
       합니다; 이전의 샘플 코드를 보세요. */

    seg = base_ptr >> 4;

    cli();  /* 인터럽트가 꺼진 상태로 들어감! */

    /* 리얼 모드 커널 스택을 셋업 */
    _SS = seg;
    _SP = heap_end;

    _DS = _ES = _FS = _GS = seg;
    jmp_far(seg+0x20, 0);   /* 커널 실행 */

여러분의 부트 섹터가 플로피 드라이브를 접근한다면, 커널 부트는 인터럽트를 끈 상태로 놔두고, 그래서 그 모터를 끄지 않을 것이기 때문에 특별히 로드된 커널이 플로피 드라이버를 demand-loaded 모듈로 가진다면, 커널이 실행되기 전에 플로피 모터를 끄는 것을 권합니다!

 

**** 고급 부트로더 훅

부트로더가 (DOS 하에서 실행되는 LOADLIN 같은) 특정 우호적이지 않은 환경에서 실행된다면, 표준 메모리 위치 요구사항을 따르는 것이 불가능할 수 있습니다. 그런 부트로더는 셋팅되었다면, 적절한 시점에 커널이 실행하는 다음 훅을 사용할 수 있습니다. 이들 훅의 사용은 아마 최후의 수단으로 고려되어야 합니다!

중요사항: 모든 훅은 호출하고 나서 %esp, %ebp, %esi, 그리고 %edi를 보존하는 것이 요구됩니다.

  realmode_swtch:
    프로텍티드 모드로 들어가기 전에 바로 실행되는 16비트 모드 far 서브루틴.
    디폴트 루틴은 NMI를 끕니다. 그래서 아마 여러분의 루틴 역시 그렇게 동작하여야 할 겁니다.

  code32_start:
    프로텍티드 모드로 전이된 후, 그러나 커널 압축이 해제되기 전에 바로 *점프되는*
    32비트 flat 모드 루틴. CS를 제외한 세그먼트가 셋업됨(현재 커널은 합니다만,
    합니다만, 오래된 것들은 하지 않습니다)이 보장되지 않습니다; 여러분 스스로
    BOOT_DS (0x18)로 그들을 셋업해야 합니다.

    여러분의 훅이 완료된 후, 여러분의 부트로더가 덮어씌우기 전의 이 필드 안에 있던
    (적당하다면 재배치된) 주소로 점프해야 합니다.

 

**** 32비트 부트 프로토콜

레가시 BIOS보다 새로운 BIOS의 머신들에는 EFI, LinuxBIOS, 기타, 그리고 kexec, 레가시 BIOS에 기초한 커널 내의 16비트 리얼 모드 셋업 코드가 사용될 수 없어서, 32비트 부트 프로토콜이 정의될 필요가 있습니다.

32비트 부트 프로토콜내에서, 리눅스 커널을 로딩하는데 있어 첫번째 단계는 (전통적으로 “zero page”로 알려진 struct boot_params) 부트 파라미터들을 셋업하는 것이어야 합니다. struct boot_params를 위한 메모리는 모두 0으로 초기화되어 할당되어야 합니다. 그러고나면 커널 이미지의 오프셋 0x01f1으로부터의 셋업 헤더가 struct boot_params내로 로드되어 시험되어야 합니다. 셋업 헤더의 끝은 다음과 같이 계산될 수 있습니다:

   0x0202 + byte value at offset 0x0201

16비트 부트 프로토콜의 것처럼 struct boot_param의 셋업 헤더를 읽고/바꾸고/쓰는 것에 더해, 부트로더는 또한 zero-page.txt 내에 설명된 것처럼 struct boot_params의 추가 필드들을 채워야 합니다.

struct boot_params를 셋업하고 나서, 부트로더는 32/64비트 커널을 16비트 부트 프로토콜의 것과 같은 방식으로 로드할 수 있습니다.

32비트 부트 프로토콜에서는 커널은 로드된 32/64비트 커널의 시작 주소인 32비트 커널 엔트리 포인트로 점프함으로써 시작됩니다.

엔트리에서 CPU는 페이징이 꺼진 32비트 프로텍티드 모드 내에 있어야 합니다; GDT는 셀렉터 __BOOT_CS(0x10)과 __BOOT_DS(0x18)을 위한 디스크립터로 로드되어야 합니다; 두 디스크립터 모두 4G flat 세그먼트여야 합니다; __BOOT_CS는 execute/read 권한을 가져야 하고, __BOOT_DS는 read/write 권한을 가져야 합니다; CS는 __BOOT_CS여야 하고, DS, ES, SS는 __BOOT_DS여야 합니다; 인터럽트는 꺼져 있어야 합니다; %esi는 struct boot_params의 베이스 주소를 갖고 있어야 합니다; %ebp, %edi 그리고 %ebx는 0이어야 합니다.

 

**** 64비트 부트 프로토콜

64비트 CPU와 64비트 커널의 머신에서 우리는 64비트 부트로더를 사용할 수 있고, 64비트 부트 프로토콜이 필요합니다.

64비트 부트 프로토콜에서 리눅스 커널을 로딩하는 첫번째 단계는 (전통적으로 “zero page”로 알려진 struct boot_params) 부트 파라미터들을 셋업하는 것이어야 합니다. struct boot_params를 위한 메모리는 어느 곳에나 (4G 위 조차도) 할당될 수 있고, 모두 0으로 초기화됩니다. 그럼 커널 이미지 상의 offset 0x01f1의 셋업 헤더가 struct boot_params내로 로드되고 시험되어야 합니다. 셋업 헤더의 끝은 다음과 같이 계산될 수 있습니다:

   0x0202 + byte value at offset 0x0201

16비트 부트 프로토콜의 것처럼 struct boot_params의 셋업 헤더를 읽고/바꾸고/쓰는 것에 더해, 부트로더는 또한 zero-page.txt 내에 설명된 것처럼 struct boot_params의 추가 필드들을 채워야 합니다.

struct boot_params를 셋업한 후, 부트로더는 16비트 부트 프로토콜과 같은 방식으로 64비트 커널을 로드할 수 있습니다만, 커널은 4G 위에 로드될 수 있습니다.

64비트 부트 프로토콜에서 커널은 로드된 64비트 커널의 시작 주소 + 0x200인 64비트 커널 엔트리 포인트로 점프함으로써 시작됩니다.

엔트리에서, CPU는 페이징이 켜진 64비트 모드 내에 있어야 합니다. 로드된 커널의 시작 주소로부터 setup_header.init_size로의 범위와 zero page와 커맨드 라인 버퍼는 ident 맵핑을 얻습니다; GDT는 셀렉터 __BOOT_CS(0x10)과 __BOOT_DS(0x18)을 위한 디스크립터로 로드되어야 합니다; 두 디스크립터 모두 4G flat 세크먼트여야 합니다; __BOOT_CS는 execute/read 권한을 가져야 하고, __BOOT_DS는 read/write 권한을 가져야 합니다; CS는 __BOOT_DS여야 하고, DS, ES, SS는 __BOOT_DS여야 합니다; 인터럽트들은 꺼져 있어야 합니다; %rsi는 struct boot_params의 베이스 주소를 갖고 있어야 합니다.

 

**** EFI 핸드오버 프로토콜

이 프로토콜은 부트로더가 EFI 부트 스텁으로 초기화하는 것을 미루도록 할 수 있습니다. 부트로더는 커널/initrd(s)를 부트 미디어로부터 로드하고, startup_{32,64}의 시작으로부터 hdr->handover_offset 바이트인 EFI 핸드오버 프로토콜 엔트리 포인트로 점프하는 것이 요구됩니다.

핸드오버 엔트리 포인트를 위한 함수 원형은 다음처럼 보입니다.

    efi_main(void *handle, efi_system_table_t *table, struct boot_params *bp)

‘handle’은 EFI 펌웨어가 부트로더로 넘기는 EFI 이미지 핸들이고, ‘table’은 EFI 시스템 테이블 – 이들은 UEFI 스펙의 2.3절 “handoff state”의 첫 두 아규먼트입니다. ‘bp’는 부트로더가 할당한 boot params입니다.

부트로더는 bp 내의 다음 필드를 반드시 채워놔야 합니다.

    o hdr.code32_start
    o hdr.cmd_line_ptr
    o hdr.ramdisk_image (사용 가능하다면)
    o hdr.ramdisk_size  (사용 가능하다면)

다른 모든 필드들은 0이어야 합니다.

 

[Linux:Kernel] AArch64 리눅스의 메모리 배치

이 문서의 저작권은 GPL 라이센스를 따릅니다(This document is released under the GPL license).

Documentation/arm64/memory.txt

     AArch64 리눅스의 메모리 배치
     ============================

Author: Catalin Marinas <catalin.marinas@arm.com>
번역  : 양정석 <dasomoli@gmailREMOVETHIS.com>
Date  : 20 February 2012

이 문서는 AArch64 리눅스 커널이 사용하는 가상 메모리 배치를 설명합니다.
이 아키텍처는 4KB 페이지 크기의 4단계 변환 테이블과 64KB 페이지 크기의
3단계 변환 테이블을 허용합니다.

AArch64 리눅스는 유저와 커널 양 쪽 모두 39비트 (512GB) 가상 주소를 허용하는
4KB 페이지 설정의 3단계 변환 테이블을 사용합니다. 64KB 페이지는 오직
2단계 변환 테이블이 사용되지만 메모리 배치는 같습니다.

유저 주소는 63:39 비트가 0으로 셋팅되는 반면, 커널 주소는 같은 곳의 비트에
1로 셋팅됩니다. TTBRx 선택은 가상 주소의 비트 63에 의해 결정됩니다.
swapper_pg_dir은 오직 커널 (전역) 맵핑만 포합하는 반면,
유저 pgd는 오직 유저 (비전역) 맵핑만 포함합니다. swapper_pgd_dir 주소는
TTBR1으로 쓰여지고, TTBR0로 절대 쓰여지지 않습니다.


4KB 페이지의 AArch64 리눅스 메모리 배치:

시작 크기 용도
———————————————————————–
0000000000000000 0000007fffffffff 512GB 유저

ffffff8000000000 ffffffbbfffeffff ~240GB vmalloc

ffffffbbffff0000 ffffffbbffffffff  64KB [guard page]

ffffffbc00000000 ffffffbdffffffff   8GB vmemmap

ffffffbe00000000 ffffffbffbbfffff  ~8GB [guard, 추후 vmmemap]

ffffffbffa000000 ffffffbffaffffff  16MB PCI I/O 공간

ffffffbffb000000 ffffffbffbbfffff  12MB [guard]

ffffffbffbc00000 ffffffbffbdfffff   2MB 고정 맵핑

ffffffbffbe00000 ffffffbffbffffff   2MB [guard]

ffffffbffc000000 ffffffbfffffffff  64MB 모듈들

ffffffc000000000 ffffffffffffffff 256GB 커널 논리 메모리 맵


64KB 페이지의 AArch64 리눅스 메모리 배치:

시작 크기 용도
———————————————————————–
0000000000000000 000003ffffffffff   4TB 유저

fffffc0000000000 fffffdfbfffeffff  ~2TB vmalloc

fffffdfbffff0000 fffffdfbffffffff  64KB [guard page]

fffffdfc00000000 fffffdfdffffffff   8GB vmemmap

fffffdfe00000000 fffffdfffbbfffff  ~8GB [guard, 추후 vmmemap]

fffffdfffa000000 fffffdfffaffffff  16MB PCI I/O 공간

fffffdfffb000000 fffffdfffbbfffff  12MB [guard]

fffffdfffbc00000 fffffdfffbdfffff   2MB 고정 맵핑

fffffdfffbe00000 fffffdfffbffffff   2MB [guard]

fffffdfffc000000 fffffdffffffffff  64MB 모듈들

fffffe0000000000 ffffffffffffffff   2TB 커널 논리 메모리 맵


4KB 페이지의 변환 테이블 탐색:

+——–+——–+——–+——–+——–+——–+——–+——–+
|63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
+——–+——–+——–+——–+——–+——–+——–+——–+
 |                 |         |         |         |         |
 |                 |         |         |         |         v
 |                 |         |         |         |   [11:0]  페이지 내의 오프셋
 |                 |         |         |         +-> [20:12] L3 인덱스
 |                 |         |         +———–> [29:21] L2 인덱스
 |                 |         +———————> [38:30] L1 인덱스
 |                 +——————————-> [47:39] L0 인덱스 (미사용)
 +————————————————-> [63] TTBR0/1


64KB 페이지의 변환 테이블 탐색:

+——–+——–+——–+——–+——–+——–+——–+——–+
|63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
+——–+——–+——–+——–+——–+——–+——–+——–+
 |                 |    |               |              |
 |                 |    |               |              v
 |                 |    |               |            [15:0]  페이지 내의 오프셋
 |                 |    |               +———-> [28:16] L3 인덱스
 |                 |    +————————–> [41:29] L2 인덱스 (38:29 만 사용)
 |                 +——————————-> [47:42] L1 인덱스 (미사용)
 +————————————————-> [63] TTBR0/1

KVM을 사용할 때, 하이퍼바이저는 커널 페이지를 EL2에서 커널 VA로부터 고정된
오프셋(커널 VA의 상위 24비트를 0으로 셋팅한)에 맵핑합니다:

시작 크기 용도
———————————————————————–
0000004000000000 0000007fffffffff 256GB HYP 내에서 맵핑된 커널 객체