[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이어야 합니다.

 

[RaspberryPi] Torrent 머신 만들기

Torrent 파일을 등록해놓으면, 집에 있는 서버에 해당 파일을 자동으로 다운로드 받아놓는다. transmission을 설치하면 된다.

# apt-get install transmission-cli transmission-common transmission-daemon
# service transmission-daemon stop
# vi /etc/transmission-daemon/settings.json

설정 파일의 다음 항목을 수정한다.

"download-dir": "/mnt/NAS/TorrentDownload",
"rpc-username": "dasomoli",
"rpc-password": "password",
"rpc-whitelist-enabled": false,
"umask": 2,

업로드 속도를 제한하고 싶다면 다음을 수정한다. 예를 들어, 10 KB/sec 로 제한한다면

"speed-limit-up": 10,
"speed-limit-up-enabled": true,

기본 포트를 바꾸고 싶다면 다음을 수정한다.

"rpc-port": 9000

# service transmission-daemon reload
# usermod -aG debian-transmission dasomoli

download-dir은 다운로드 받을 디렉토리, rpc-username은 웹 인터페이스로 들어갈 때 로그인 할 ID, rpc-password는 그 Password이다. rpc-whitelist-enabled를 false 로 설정하면, 말 그대로 whitelist를 사용하지 않으므로, 어디에서든 접속이 가능하다.

접속은 기본은 9091 포트로 접속하면 된다. 위에선 9000으로 바꿨다. 하지만, 포트 번호를 치긴 귀찮으므로 apache2의 proxy를 이용해서 접속하자.

# vi /etc/apache2/sites-available/torrent.dasomoli.org.conf

<VirtualHost *:80>
        # The ServerName directive sets the request scheme, hostname and port that
        # the server uses to identify itself. This is used when creating
        # redirection URLs. In the context of virtual hosts, the ServerName
        # specifies what hostname must appear in the request's Host: header to
        # match this virtual host. For the default virtual host (this file) this
        # value is not decisive as it is used as a last resort host regardless.
        # However, you must set it for any further virtual host explicitly.
        ServerName torrent.dasomoli.org
        ServerAdmin dasomoli@gmail.com

        ProxyPass / http://localhost:9000/
        ProxyPassReverse / http://localhost:9000/

        # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
        # error, crit, alert, emerg.
        # It is also possible to configure the loglevel for particular
        # modules, e.g.
        #LogLevel info ssl:warn

        ErrorLog ${APACHE_LOG_DIR}/torrent.log
        CustomLog ${APACHE_LOG_DIR}/torrent.access.log combined

        # For most configuration files from conf-available/, which are
        # enabled or disabled at a global level, it is possible to
        # include a line for only one particular virtual host. For example the
        # following line enables the CGI configuration for this host only
        # after it has been globally disabled with "a2disconf".
        #Include conf-available/serve-cgi-bin.conf
</VirtualHost>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

이제 proxy 모듈을 enable하고, site를 enable한 후, apache2를 재시작하자.

# a2enmod proxy
# a2enmod proxy_http
# a2ensite torrent.dasomoli.org.conf
# service apache2 restart

# sudo service transmission-daemon start

이제 http://torrent.dasomoli.org 로 접속하면 된다.
당연히 미리 DNS설정은 되어 있어야 한다.

부팅 시에 Web UI 가 연결이 안되는 경우가 있는데, 이 때는 transmission-daemon을 reload 하면 된다. 수많은 해결(이라고 주장하는) 방법이 있지만, 가장 간단하면서 잘 동작하는 방법은 그냥 raspi-config에서 “Network at Boot:”의 “Wait for network”을 체크하는 것이다.

가장 간단하지만 잘 동작하는 허무한 해결책…

참고: https://askubuntu.com/questions/157455/how-to-change-the-default-permissions-of-files-created-by-transmission-daemon

[Linux] ARM 리눅스 부팅(Booting ARM Linux)

ARM 리눅스 부팅

원문 : http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html
번역 : 양정석(dasomoli@gmailREMOVETHIS.com)

Vincent Sanders

Review and advice, large chunks of the ARM Linux kernel, all around good guy: Russell King

Review, advice and numerous clarifications.: Nicolas Pitre

Review and advice: Erik Mouw, Zwane Mwaikambo, Jeff Sutherland, Ralph Siemsen, Daniel Silverstone, Martin Michlmayr, Michael Stevens, Lesley Mitchell, Matthew Richardson

Review and referenced information (see bibliography): Wookey

  • This document is released under a GPL licence.

  • All trademarks are acknowledged.

While every precaution has been taken in the preparation of this article, the publisher assumes no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein.

2004-06-04

Revision History
Revision 1.00 10th May 2004 VRS

Initial Release.

Revision 1.10 4th June 2004 VRS
Update example code to be more complete.
Improve wording in places, changes suggested by Nicolas Pitre.
Update Section 2, “Other bootloaders”.
Update acknowledgements.

차례

1. 이 문서에 관해서 
2. 다른 부트로더
3. 개요
4. 시스템의 메모리 설정하기
5. 커널 이미지 로딩

6. 램디스크 로딩

7. 콘솔 초기화
8. 커널 파라미터
9. ARM 리눅스 머신 타입
 얻기
10. 커널 시작
A. 태그 레퍼런스
B. 완전한 예제
참고문헌

요약

이 문서는 ARM 리눅스 커널을 시작하기 위한 부트 로더의 필요사항과 절차를 구현 가이드, 예제와 함께 명확히 간결한 용어로 정의합니다.

1. 이 문서에 관해서

이 문서는 2.4.18 이후의 커널이 사용하는 “새로운” 부팅 절차를 설명합니다. 이전의 “struct” 메소드는 사용하지 마세요.

이 문서는 광범위한 종류의 출처(참고문헌 참고)와 저자들로부터의 정보를 포함하고 있습니다. 메인테이너들 또는 ARM 리눅스 메일링 리스트 상에 질문하기 전에 이 출처들을 찾아보는 것이 좋습니다. 이 분야의 대부분의 것들은 이전에 반복적으로 문의 및 답변되었고, 만약 여러분이 기본적인 조사도 하지 않았다면 여러분은 무시될 가능성이 큽니다.

추가적으로 이 문서에서 제공하는 가이드를 따르고, 커널을 시작하는 어셈블러의 모든 미묘한 차이를 이해하는 개발자가 될 필요는 없다는 것을 알아두세요. 또한 대부분의 부팅 문제는 여기 있는 코드와 상관없는 경우가 많았고, 많은 사람들이 말하는 코드는 매우 까다롭고 그 문제에 대해 간파할 수 없는 경우가 많았습니다.

2. 다른 부트로더

새 부트 로더의 작성을 시작하기 전에 개발자들은 이미 존재하는 부트 로더가 적절하지는 않은지 한번 생각해보는 것이 좋습니다. 간단한 GPL 부트 로더에서부터 상용으로 제공하는 것까지 다양한 영역의 부트 로더 예제가 있습니다. 여기서 다음 작은 표를 제공합니다만 참고문헌에 있는 문서는 더 많은 솔루션을 제공합니다.

표 1. 부트 로더

이름 URL 설명
Blob Blob bootloader SA11x0 (StrongARM)을 위한 GPL 부트 로더
Bootldr Bootldr GPL과 GPL 아닌 버전을 모두 제공하는 핸드헬드 디바이스에서 주로 사용하는 부트 로더
Redboot Redboot eCos 라이센스의 레드햇 부트로더
U-Boot U-Boot 몇 몇 CPU를 지원하는 GPL universal bootloader
ABLE ABLE bootloader 편리한 기능의 상용 부트로더

3. 개요

ARM 리눅스는 시스템을 초기화하는 특정 머신에서만 동작하는 작은 코드 없이는 시작될 수 없습니다. 몇몇 부트로더는 더 많은 추가 기능을 제공하긴 하지만, ARM 리눅스는 이 작은 일을 하는 부트 로더가 필요합니다. 최소한의 필요사항은 다음과 같습니다:

시스템의 메모리 설정하기.
정확한 메모리 주소에 커널 이미지 로딩.
필요하면 정확한 메모리 주소에 램디스크 로딩.
커널로 전달할 부트 파라미터 초기화.
ARM 리눅스 머신 타입 얻기
적당한 레지스터 값으로 커널로 진입.

부트로더는 일반적으로 이런 기본적인 작업들 뿐만 아니라 시리얼 또는 비디오 콘솔을 초기화해야 합니다. 사실 시리얼 포트는 거의 대부분의 설정을 위해 필수사항으로 생각됩니다.

위의 각 단계를 다음 절들에서 설명합니다.

4. 시스템의 메모리 설정하기

부트 로더는 커널이 시스템의 임시 저장 장소로 사용할 모든 램을 찾아 초기화해야 합니다. 이는 특정 머신에서만 동작하는 방법(모든 램의 크기와 위치를 찾는 내부 알고리즘을 사용하거나, 머신에 달린 램에 관한 정보를 사용하거나, 또는 부트 로더 설계자 관점의 다른 방법 등)으로 수행됩니다.

어떠한 경우에도 모든 셋업은 부트 로더에 의해서 수행되어야 합니다. 커널은 시스템 안의 램의 셋업 또는 설정에 대해서 부트 로더가 제공하는 것 외에는 아무 것도 몰라야 합니다. 커널 안의 machine_fixup()의 사용은 이런 것을 하기 위한 것이 절대 아닙니다. 이 부분에서의 커널과 부트 로더 간의 책임 구별은 명확합니다.

물리 메모리 배치는 ATAG_MEM 파라미터를 사용해서 커널로 전달됩니다. 메모리가 가장 적게 분리되어 있는 것이 좋긴 하지만, 완전히 연속적일 필요는 없습니다. 여러 메모리 지역을 위한 여러개의 ATAG_MEM 블록도 상관없습니다. 연속된 물리 메모리 지역이 있다면 커널은 그 블록들을 합칠 겁니다.

부트 로더는 또한 linux/Documentation/kernel-parameters.txt 에 완벽히 문서화되어 있는 ‘mem=’ 파라미터를 사용해서 커널의 커맨드라인으로 메모리를 조작할 수도 있습니다.

커널 커맨드 라인 ‘mem=’ 는 정의된 메모리 영역의 물리 메모리 위치와 크기를 위해서 mem=<size>[KM][,@<phys_offset>] 문법을 가집니다. mem= 파라미터는 각기 다른 곳의 여러개의 연속되지 않은 메모리 블록을 나타내기 위해 여러 번 쓸 수 있습니다.

5. 커널 이미지 로딩

커널 이미지는 커널 빌드 과정에서 생성된 압축되지 않은 “Image” 파일 또는 압축된 zImage 파일입니다.

압축되지 않은 Image 파일은 식별 가능한 매직 넘버를 포함하고 있지 않아서 일반적으로 사용되지 않습니다. 압축된 zImage 형식이 거의 보편적으로 사용됩니다.

zImage 파일은 매직 넘버 뿐만 아니라 여러 장점을 갖고 있습니다. 일반적으로 이미지의 압축 해제는 외부 저장 장치를 읽는 것보다 빠릅니다. 압축 해제 실패의 결과로 이미지의 무결성도 보장할 수 있습니다. 커널은 일반적인 외부 압축 방법보다 더 나은 결과를 알 수 있는 그 내부 구조와 상태에 대한 정보를 가집니다.

zImage는 그 앞 부분 근처에 유용한 정보와 매직 넘버를 가집니다.

표 2. zImage head 코드 안의 유용한 정보

zImage안의 오프셋 설명
0x24 0x016F2818 ARM 리눅스 zImage를 나타내는데 사용하는 매직 넘버
0x28 시작 주소 zImage가 시작하는 주소
0x2C 끝 주소 zImage가 끝나는 주소

시작과 끝 오프셋은 압축된 이미지의 크기(크기 = 끝 – 시작)를 결정하는데 사용됩니다. 몇몇 부트 로더들은 어떤 데이터가 커널 이미지에 추가된다면 이 정보를 사용합니다. 이 데이터는 일반적으로 초기 램디스크(initrd)를 위해 사용됩니다. 시작 주소는 일반적으로 zImage 코드가 위치에 무관하므로 0 입니다.

zImage 코드의 이용 가능한 주소 공간 안의 어떤 곳에도 로딩될 수 있는 위치에 무관한 코드(Position Independent Code – PIC) 입니다. 압축해제 된 후의 커널 크기는 최대 4 메가 바이트 입니다. 이는 bootpImage 타겟이 사용되었을 때의 initrd를 포함한 제약 사항입니다.

주의

zImage가 어느 곳이든 위치할 수 있다 하더라도 주의해야 합니다. 압축된 커널을 시작하는 것은 이미지를 압축해제할 추가적인 메모리를 필요로 합니다. 이 공간은 확실한 제약사항입니다.

zImage 압축해제 코드는 압축된 데이터를 덮어쓰지 않도록 확인할 겁니다. 커널이 어떤 충돌 같은 것을 발견한다면, 압축된 zImage 데이터 바로 뒤에 이미지를 압축 해제하고, 압축 해제 후에 커널을 재위치시킬 겁니다. zImage가 로딩된 메모리 영역은 그 뒤에 4 메가 바이트보다 큰 공간을 가져야 한다는 것, 즉 ZRELADDR로서 같은 4 메가 바이트 뱅크에 위치한 커널이 기대하는대로 동작하지 않는 것을 명확히 알 수 있습니다.

메모리 안에 어느 곳이든 zImage가 위치할 수 있지만 관례상 물리 메모리의 베이스에서 0x8000 (32K) 오프셋에 로딩됩니다. 이 것은 파라미터 블록이 일반적으로 위치하는 0x100 오프셋, Zero page exception 벡터와 페이지 테이블을 위한 공간을 남겨둡니다. 이 관례는 매우 일반적입니다.

6. 램디스크 로딩

램디스크는 많은 시스템상에서 일반적인 요구사항입니다. 그 것은 다른 드라이버나 설정에 접근할 필요없이 사용가능한 루트 파일 시스템을 제공합니다. 완전한 세부사항은  linux/Documentation/initrd.txt 에서 볼 수 있습니다.

ARM 리눅스가 램디스크를 얻는 방법은 두가지입니다. 첫번째는 빌드 할 때 램디스크를 가져와서 zImage 에 덧붙인 target bootpImage를 따로 빌드하는 겁니다. 이 방법은 부트 로더와의 상호 약속된 것이 없어도 된다는 장점이 있는 반면에 커널 빌드 과정에서 램디스크가 위치할 물리 주소를 (INITRD_PHYS 정의를 사용해서) 알려 주어야 한다는 단점이 4있습니다. 압축 해제된 커널과 initrd의 크기는 4 메가 바이트로 제한됩니다. 이 제한 사항 때문이 이 타겟은 실제로는 별로 사용되지 않습니다.

두번째 방법은 더 널리 사용되는 방법으로 부트로더가 어떤 저장 장치에서 램디스크를 받아 메모리의 지정된 위치에 위치시키는 것입니다. 이 위치는 커널에게 ATAG_INITRD2 와 ATAG_RAMDISK를 사용해서 전달됩니다.

관례적으로 initrd는 물리 주소 베이스의 8메가 바이트에 위치합니다. 위치된 곳이 어디 건간에 부팅 후에 램디스크를 실제 램디스크로 압축해제 하기 위한 충분한 메모리, 즉 zImage + 압축 해제된 zImage + initrd + 압축 해제된 램디스크를 위한 메모리가 있어야 합니다. 압축된 램디스크 메모리는 압축이 해제되고 나면 할당 해제될 겁니다. 램디스크의 위치 제한은 다음과 같습니다: 

한 개의 메모리 영역안에 있어야 합니다(다른 ATAG_MEM 파라미터에 의해 정의된 다른 영역에 걸쳐 있으면 안됩니다).
한 페이지 경계(일반적으로 4k)로 맞추어져 있어야 합니다.
zImage head 코드가 커널을 압축해제 하기 위해서 사용하는 메모리와의 충돌이 없어야 합니다, 아니면 체크 없이 덮어쓰게 될 것입니다.

7. 콘솔 초기화

콘솔은 시스템이 초기화될 때 커널이 무엇을 수행하는 지를 볼 수 있는 방법으로 매우 권장됩니다. 일반적인 경우인 비디오 프레임 버퍼 드라이버나 시리얼 드라이버 같은 적절한 드라이버로 어떤 입출력 장치도 사용될 수 있습니다. ARM 리눅스가 실행되는 시스템은 거의 항상 시리얼 콘솔 포트를 가지고 있습니다.

부트 로더는 부트로더 상에서 한 개의 시리얼 포트를 초기화하고 쓸 수 있도록 만들어야 합니다. 이 것은 그 포트를 사용하기 위한 하드웨어적인 파워 관리 같은 것들을 포함합니다. 이 것은 커널 시리얼 드라이버가 (일반적으로 디버깅 목적이나 타겟과의 통신 용도로 사용되는) 커널 콘솔로 사용되는 시리얼 포트를 자동으로 찾을 수 있도록 합니다.

아니면 부트로더는 linux/Documentation/kernel-parameters.txt에 설명된 Serial format 옵션과 포트를 지정하는 tagged list를 통해 이에 관련된 ‘console=’ 옵션을 커널로 전달할 수 있습니다.

8. 커널 파라미터

부트로더는 커널로 수행한 셋업, 시스템의 메모리의 크기와 영역, 그리고 필요시에는 다른 여러 값들을 설명하는 파라미터를 넘겨야 합니다.

Tagged list는 다음 제약 사항을 따라야 합니다.

리스트는 램에 저장 되어져야만 하고 커널 압축 해제기나 initrd 처리가 덮어쓰지 않는 메모리 영역에 위치해야만 합니다. 권장되는 곳은 RAM의 첫번째 16KiB 안이고, 일반적으로 물리 RAM의 시작에서 (Zero page exception 벡터를 피하기 위해서) 0x100 더하여 진 곳에 위치합니다.
Tagged list의 물리 주소는 커널로의 진입 시점에 R2 레지스터에 저장되어져 있어야 합니다. 예전에는 이 것이 필수적이지는 않았고, 커널이 물리 램의 시작에서 0x100 더한 곳을 고정값으로 사용했었습니다. 앞으로는 이렇게 하지 않을 겁니다.
리스트는 커널이 초기 페이지 테이블을 생성하는 0x4000 경계까지 확장되면 안됩니다.
리스트는 (권장되는 위치를 사용하지 않는다면) word 크기(32비트, 4바이트)로 맞추어져야 합니다.
리스트는 ATAG_CORE로 시작해서 ATAG_NONE으로 끝나야 합니다.
리스트는 ATAG_MEM을 적어도 하나는 포함해야 합니다.

리스트의 각 태그는 양수 32비트로 된 size와 tag, 두개의 값을 포함하는 헤더와 그 태그의 값들로 구성됩니다.

struct atag_header {
        u32 size; /* legth of tag in words including this header */
        u32 tag;  /* tag value */
};

데이터를 갖지 않는 ATAG_NONE와 필요하면 데이터를 쓰는 ATAG_CORE를 제외한 각 태그 헤더에는 그 태그와 관련된 데이터가 뒤따릅니다. 데이터의 크기는 헤더 안의 size 필드에 의해 결정되는데 이 값은 헤더 크기를 포함하기 때문에 최소 사이즈는 2입니다. ATAG_NONE은 size 필드를 0으로 셋팅해야 하는 유일한 태그입니다.

한 태그에는 덧붙여지는 정보 크기를 제공하는 필수 구조체 뒤에 추가 데이터를 포함할 겁니다. 이 것은 추후에 커널로 제공하는 데이터를 확장하기 위해서 사용될 수 있습니다. 예를 들면 부트 로더는 ATAG_SERIAL 안에 추가적인 제품 일련 번호 정보를 제공하여 이를 위해 수정된 커널이 이를 읽어갈 수 있도록 할 수 있습니다.

파라미터 리스트의 태그의 순서는 중요하지 않습니다. 중복된 태그에 대한 해석은 태그에 따라 다를 수는 있지만 필요한 만큼 여러번 포함 될 겁니다.

각각의 태그의 데이터는 부록 A. 태그 레퍼런스 절에서 설명합니다.

표 3. 유용한 태그의 목록

태그 이름 크기 설명
ATAG_NONE 0x00000000 2 리스트의 끝으로 사용하는 빈 태그
ATAG_CORE 0x54410001 5 (비었다면 2) 리스트의 시작으로 사용하는 첫번째 태그
ATAG_MEM 0x54410002 4 메모리의 물리 영역을 설명
ATAG_VIDEOTEXT 0x54410003 5 VGA text 디스플레이를 설명
ATAG_RAMDISK 0x54410004 5 커널에서 램디스크가 어떻게 사용되는지를 설명
ATAG_INITRD2 0x54420005 4 메모리에 램디스크 이미지가 어디에 있는지를 설명
ATAG_SERIAL 0x54410006 4 64비트의 제품 일련 번호
ATAG_REVISION 0x54410007 3 32비트의 보드 리비전
ATAG_VIDEOLFB 0x54410008 8 vesafb 타입의 프레임 버퍼의 초기값
ATAG_CMDLINE 0x54410009 2 + ((cmdline의 길이 + 3) / 4) 커널로 전달되는 커맨드 라인

구현 목적으로 태그는 다음과 같이 정의될 수 있습니다.

struct atag {
        struct atag_header hdr;
        union {
                struct atag_core         core;
                struct atag_mem          mem;
                struct atag_videotext    videotext;
                struct atag_ramdisk      ramdisk;
                struct atag_initrd2      initrd2;
                struct atag_serialnr     serialnr;
                struct atag_revision     revision;
                struct atag_videolfb     videolfb;
                struct atag_cmdline      cmdline;
        } u;
};

리스트를 만들 필요가 있는 구현을 정의해 본다면 코드는 아마 다음과 유사할 겁니다.

#define tag_next(t)     ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type)  ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
static struct atag *params; /* used to point at the current tag */

static void
setup_core_tag(void * address,long pagesize)
{
    params = (struct tag *)address;         /* Initialise parameters to start at given address */

    params->hdr.tag = ATAG_CORE;            /* start with the core tag */
    params->hdr.size = tag_size(atag_core); /* size the tag */

    params->u.core.flags = 1;               /* ensure read-only */
    params->u.core.pagesize = pagesize;     /* systems pagesize (4k) */
    params->u.core.rootdev = 0;             /* zero root device (typicaly overidden from commandline )*/

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_mem_tag(u32_t start, u32_t len)
{
    params->hdr.tag = ATAG_MEM;             /* Memory tag */
    params->hdr.size = tag_size(atag_mem);  /* size tag */

    params->u.mem.start = start;            /* Start of memory area (physical address) */
    params->u.mem.size = len;               /* Length of area */

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_end_tag(void)
{
    params->hdr.tag = ATAG_NONE;            /* Empty tag ends list */
    params->hdr.size = 0;                   /* zero length */
}


static void
setup_tags(void)
{
    setup_core_tag(0x100, 4096);            /* standard core tag 4k pagesize */
    setup_mem_tag(0x10000000, 0x400000);    /* 64Mb at 0x10000000 */
    setup_mem_tag(0x18000000, 0x400000);    /* 64Mb at 0x18000000 */
    setup_end_tag(void);                    /* end of tags */
}

여기 있는 코드가 완전하긴 하지만 이 코드는 파마리터 셋을 위한 절대적으로 최소한의 필요사항만을 나타내고 있고, 앞서 설명된 이 절에서의 컨셉을 보여주기 위한 용도로 작성되었습니다. 실제 부트로더는 아마 추가적인 값들을 전달하고 고정값을 사용하는 것보다는 실제 시스템 메모리에서 알아낸 값을 사용할 것입니다. 더 완전한 예제는 부록 B. 완전한 예제에서 볼 수 있습니다.

9. ARM 리눅스 머신 타입 얻기

부트 로더가 제공할 필요가 있는 추가 정보는 각 ARM 시스템에서 MACH_TYPE으로 불리는 간단하고 유일한 수, 머신 타입 뿐입니다.

머신 타입 넘버는 ARM 리눅스 웹사이트의 Machine Registry 페이지에서 획득할 수 있습니다. 머신 타입은 가능한한 프로젝트 초기에 획득해야 합니다. 커널이 그 스스로(머신 정의값들 등)를 포팅하는 것은 수많은 결과를 만들 수 있고 이후에 정의값들을 바꾸는 것은 수많은 원치 않는 문제를 만들 수 있습니다. 이 값들은 커널 소스 (linux/arch/arm/tools/mach-types) 안에 정의된 리스트에 의해 표현됩니다.

부트 로더는 어떤 방법으로든 실행될 머신의 타입을 획득해야 합니다. 그 방법이 하드 코딩된 값이던 연결된 하드웨어에서 탐색하는 어떤 알고리즘이던 상관없습니다. 그 구현 방법은 문서의 범위를 넘어섭니다. 

10. 커널 시작하기

부트 로더가 다른 모든 단계를 수행했다면 CPU 셋팅에 정확한 값들로 커널의 실행을 시작해야 합니다.

진입 제약 사항은:

CPU 는 IRQ 와 FIQ 모두 꺼진 상태로 SVC (supervisor) 모드에 있어야 합니다.
MMU는 반드시 꺼져 있어야, 즉 물리 램에서 주소를 변환하지 않는 상태로 코드가 실행되어야 합니다.
데이터 캐시는 반드시 꺼져 있어야 합니다.
인스트럭션 캐시는 켜져 있건 꺼져있건 상관없습니다.
CPU 레지스터 0 은 0 이어야 합니다.
CPU 레지스터 1 은 ARM 리눅스 머신 타입이어야 합니다.
CPU 레지스터 2 는 파라미터 리스트의 물리 주소여야 합니다.

부트 로더는 커널 이미지의 첫번째 명령으로 직접 점프하여 커널 이미지를 호출할 것입니다.

A. 태그 레퍼런스

ATAG_CORE

ATAG_CORE — 리스트의 시작으로 사용되는 시작 태그

0x54410001

크기

5 (빈 데이터면 2)

구조체 멤버

struct atag_core {
        u32 flags;              /* bit 0 = read-only */
        u32 pagesize;           /* systems page size (usually 4k) */
        u32 rootdev;            /* root device number */
};

설명

어떤 부트 로더도 전달해야만 하는 기본 정보를 포함하는 리스트의 시작은 다른 구조가 뒤에 붙지 않는 것을 나타내는 길이 2의 이 태그가 되어야 합니다.


ATAG_NONE

ATAG_NONE — 리스트의 끝으로 사용되는 끝 태그

0x00000000

크기

2

구조체 멤버

없음

설명

이 태그는 리스트의 끝을 나타내는데 사용됩니다. 헤더 안의 size 필드가 (2가 아닌) 0 이 되는 유일한 태그입니다.


ATAG_MEM

ATAG_MEM — 물리 메모리 영역을 설명하는데 사용되는 태그

0x54410002

크기

4

구조체 멤버

struct atag_mem {
        u32     size;   /* size of the area */
        u32     start;  /* physical start address */
};

설명

커널이 사용하는 물리 메모리 영역을 설명합니다.


ATAG_VIDEOTEXT

ATAG_VIDEOTEXT — VGA test 타입 디스플레이를 설명하는 데 사용되는 태그

0x54410003

크기

5

구조체 멤버

struct atag_videotext {
        u8              x;           /* width of display */
        u8              y;           /* height of display */
        u16             video_page;
        u8              video_mode;
        u8              video_cols;
        u16             video_ega_bx;
        u8              video_lines;
        u8              video_isvga;
        u16             video_points;
};

설명


ATAG_RAMDISK

ATAG_RAMDISK — 커널에 의해 램디스크가 어떻게 사용될 지를 설명하는 태그

0x54410004

크기

5

구조체 멤버

struct atag_ramdisk {
        u32 flags;      /* bit 0 = load, bit 1 = prompt */
        u32 size;       /* decompressed ramdisk size in _kilo_ bytes */
        u32 start;      /* starting block of floppy-based RAM disk image */
};

설명

(초기) 램디스크가 커널에 의해 어떻게 설정될지를 설명합니다. 특히 부트로더가 ATAG_INITRD2를 사용해서 전달하는 압축 해제된 초기 램디스크 이미지를 얻을 수 있을 만큼의 크기를 보장받을 수 있습니다.


ATAG_INITRD2

ATAG_INITRD2 — 압축된 램디스크의 물리 위치를 설명합니다.

0x54420005

크기

4

구조체 멤버

struct atag_initrd2 {
        u32 start;      /* physical start address */
        u32 size;       /* size of compressed ramdisk image in bytes */
};

설명

일반적으로 ATAG_RAMDISK와 같이 사용되는 압축된 램디스크 이미지의 위치. 커맨드 라인 파라미터의 ‘root=/dev/ram’으로써 초기 루트 파일 시스템으로 사용될 수 있습니다. 이 태그는 원래 가상 주소를 사용하는 ATAG_INITRD를 대신함으로써 문제가 될 수 있습니다. 모든 새로운 부트 로더는 이 태그를 사용하는 것이 좋습니다.


ATAG_SERIAL

ATAG_SERIAL — 64비트 값의 보드 일련 번호 태그

0x54410006

크기

4

구조체 멤버

struct atag_serialnr {
        u32 low;
        u32 high;
};

설명


ATAG_REVISION

ATAG_REVISION — 보드 리비전 태그

0x54410007

크기

3

구조체 멤버

struct atag_revision {
        u32 rev;
};

설명


ATAG_VIDEOLFB

ATAG_VIDEOLFB — 프레임버퍼 타입 디스플레이의 파라미터를 설명하는 태그

0x54410008

크기

8

구조체 멤버

struct atag_videolfb {
        u16             lfb_width;
        u16             lfb_height;
        u16             lfb_depth;
        u16             lfb_linelength;
        u32             lfb_base;
        u32             lfb_size;
        u8              red_size;
        u8              red_pos;
        u8              green_size;
        u8              green_pos;
        u8              blue_size;
        u8              blue_pos;
        u8              rsvd_size;
        u8              rsvd_pos;
};

설명


ATAG_CMDLINE

ATAG_CMDLINE — 커널로 커맨드 라인을 전달하는데 사용되는 태그

0x54410009

크기

2 + ((cmdline의 길이 + 3) / 4)

구조체 멤버

struct atag_cmdline {
        char    cmdline[1];     /* this is the minimum size */
};

설명

커널로 커맨드 라인 파라미터를 전달하는데 사용됩니다. 커맨드 라인은 NULL로 끝나야 합니다. cmdline의 길이 변수는 끝내는 문자를 포함한 길이여야 합니다.

B. 완전한 예제

여기 있는 것은 이 문서에서 설명하는 모든 정보를 보여주는 간단한 부트 로더에서 작동하는 예제입니다. 실제 부트로더는 더 많은 코드가 필요합니다. 이 예제는 그저 보여주기 위함입니다.

이 예제 코드는 BSD 라이센스로 배포됩니다. 자유롭게 복사하고, 필요하면 사용할 수 있습니다.

/* example.c
 * example ARM Linux bootloader code
 * this example is distributed under the BSD licence
 */

/* list of possible tags */
#define ATAG_NONE       0x00000000
#define ATAG_CORE       0x54410001
#define ATAG_MEM        0x54410002
#define ATAG_VIDEOTEXT  0x54410003
#define ATAG_RAMDISK    0x54410004
#define ATAG_INITRD2    0x54420005
#define ATAG_SERIAL     0x54410006
#define ATAG_REVISION   0x54410007
#define ATAG_VIDEOLFB   0x54410008
#define ATAG_CMDLINE    0x54410009

/* structures for each atag */
struct atag_header {
        u32 size; /* length of tag in words including this header */
        u32 tag;  /* tag type */
};

struct atag_core {
        u32 flags;
        u32 pagesize;
        u32 rootdev;
};

struct atag_mem {
        u32     size;
        u32     start;
};

struct atag_videotext {
        u8              x;
        u8              y;
        u16             video_page;
        u8              video_mode;
        u8              video_cols;
        u16             video_ega_bx;
        u8              video_lines;
        u8              video_isvga;
        u16             video_points;
};

struct atag_ramdisk {
        u32 flags;
        u32 size;
        u32 start;
};

struct atag_initrd2 {
        u32 start;
        u32 size;
};

struct atag_serialnr {
        u32 low;
        u32 high;
};

struct atag_revision {
        u32 rev;
};

struct atag_videolfb {
        u16             lfb_width;
        u16             lfb_height;
        u16             lfb_depth;
        u16             lfb_linelength;
        u32             lfb_base;
        u32             lfb_size;
        u8              red_size;
        u8              red_pos;
        u8              green_size;
        u8              green_pos;
        u8              blue_size;
        u8              blue_pos;
        u8              rsvd_size;
        u8              rsvd_pos;
};

struct atag_cmdline {
        char    cmdline[1];
};

struct atag {
        struct atag_header hdr;
        union {
                struct atag_core         core;
                struct atag_mem          mem;
                struct atag_videotext    videotext;
                struct atag_ramdisk      ramdisk;
                struct atag_initrd2      initrd2;
                struct atag_serialnr     serialnr;
                struct atag_revision     revision;
                struct atag_videolfb     videolfb;
                struct atag_cmdline      cmdline;
        } u;
};


#define tag_next(t)     ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type)  ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
static struct atag *params; /* used to point at the current tag */

static void
setup_core_tag(void * address,long pagesize)
{
    params = (struct tag *)address;         /* Initialise parameters to start at given address */

    params->hdr.tag = ATAG_CORE;            /* start with the core tag */
    params->hdr.size = tag_size(atag_core); /* size the tag */

    params->u.core.flags = 1;               /* ensure read-only */
    params->u.core.pagesize = pagesize;     /* systems pagesize (4k) */
    params->u.core.rootdev = 0;             /* zero root device (typicaly overidden from commandline )*/

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_ramdisk_tag(u32_t size)
{
    params->hdr.tag = ATAG_RAMDISK;         /* Ramdisk tag */
    params->hdr.size = tag_size(atag_ramdisk);  /* size tag */

    params->u.ramdisk.flags = 0;            /* Load the ramdisk */
    params->u.ramdisk.size = size;          /* Decompressed ramdisk size */
    params->u.ramdisk.start = 0;            /* Unused */

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_initrd2_tag(u32_t start, u32_t size)
{
    params->hdr.tag = ATAG_INITRD2;         /* Initrd2 tag */
    params->hdr.size = tag_size(atag_initrd2);  /* size tag */

    params->u.initrd2.start = start;        /* physical start */
    params->u.initrd2.size = size;          /* compressed ramdisk size */

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_mem_tag(u32_t start, u32_t len)
{
    params->hdr.tag = ATAG_MEM;             /* Memory tag */
    params->hdr.size = tag_size(atag_mem);  /* size tag */

    params->u.mem.start = start;            /* Start of memory area (physical address) */
    params->u.mem.size = len;               /* Length of area */

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_cmdline_tag(const char * line)
{
    int linelen = strlen(line);

    if(!linelen)
        return;                             /* do not insert a tag for an empty commandline */

    params->hdr.tag = ATAG_CMDLINE;         /* Commandline tag */
    params->hdr.size = (sizeof(struct atag_header) + linelen + 1 + 4) >> 2;

    strcpy(params->u.cmdline.cmdline,line); /* place commandline into tag */

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_end_tag(void)
{
    params->hdr.tag = ATAG_NONE;            /* Empty tag ends list */
    params->hdr.size = 0;                   /* zero length */
}


#define DRAM_BASE 0x10000000
#define ZIMAGE_LOAD_ADDRESS DRAM_BASE + 0x8000
#define INITRD_LOAD_ADDRESS DRAM_BASE + 0x800000

static void
setup_tags(parameters)
{
    setup_core_tag(parameters, 4096);       /* standard core tag 4k pagesize */
    setup_mem_tag(DRAM_BASE, 0x4000000);    /* 64Mb at 0x10000000 */
    setup_mem_tag(DRAM_BASE + 0x8000000, 0x4000000); /* 64Mb at 0x18000000 */
    setup_ramdisk_tag(4096);                /* create 4Mb ramdisk */ 
    setup_initrd2_tag(INITRD_LOAD_ADDRESS, 0x100000); /* 1Mb of compressed data placed 8Mb into memory */
    setup_cmdline_tag("root=/dev/ram0");    /* commandline setting root device */
    setup_end_tag(void);                    /* end of tags */
}

int
start_linux(char *name,char *rdname)
{
    void (*theKernel)(int zero, int arch, u32 params);
    u32 exec_at = (u32)-1;
    u32 parm_at = (u32)-1;
    u32 machine_type;

    exec_at = ZIMAGE_LOAD_ADDRESS;
    parm_at = DRAM_BASE + 0x100

    load_image(name, exec_at);              /* copy image into RAM */

    load_image(rdname, INITRD_LOAD_ADDRESS);/* copy initial ramdisk image into RAM */

    setup_tags(parm_at);                    /* sets up parameters */

    machine_type = get_mach_type();         /* get machine type */

    irq_shutdown();                         /* stop irq */

    cpu_op(CPUOP_MMUCHANGE, NULL);          /* turn MMU off */

    theKernel = (void (*)(int, int, u32))exec_at; /* set the kernel address */

    theKernel(0, machine_type, parm_at);    /* jump to kernel with register set */

    return 0;
}

참고문헌

Setting R2 correctly for booting the kernel (explanation of booting requirements). Russell M King.

Makefile defines and symbols. Russell M King.

Bootloader guide. Wookey.

Kernel boot order. Russell M King.

Advice for head.S Debugging. Russell M King.

Linux kernel 2.4 startup. Bill Gatliff.

Blob bootloader. Erik Mouw.

[Android] boot.img 구성

Android 에서 사용되는 boot.img 는 다음과 같이 구성되어 있다(ICS 기준).
각 구성부는 Page 크기에 맞춰 Align 되어 있다.
– Boot Image Header(1 page) (system/core/mkbootimng/bootimg.h 참고)
 + magic(8 bytes) : ANDROID!
 + kernel size(4 bytes)
 + kernel address(4 bytes)
 + ramdisk size(4 bytes)
 + ramdisk address(4 bytes)
 + second stage size(4 bytes)
 + second stage address(4 bytes)
 + kernel tags physical address(4 bytes)
 + page size(4 bytes)
 + reserved(8 bytes) : Should be 0
 + product name(16 bytes)
 + cmdline(512 bytes)
 + id(32 bytes) : timestamp, checksum, sha1, etc
– Kernel(zImage)
– Ram Disk
– Second stage

우분투 설치 CD를 USB로 부팅

http://unetbootin.sourceforge.net/ 에서 unetbootin 을 다운받는다.
우분투 설치 ISO 파일을 http://www.ubuntu.com/getubuntu/download 에서 다운로드받는다.
미러를 통해 다운로드받는 것이 빠르다.
USB를 꽂고, unetbootin 을 실행시킨 후 Disk image의 ISO 에 다운로드 받은 ISO를 선택한 후 OK를 누른다.

1~4까지 진행된 후 뽑아서 쓰면 된다.
윈도우즈가 깨진 노트북에서 외장하드로 파일 복사(데이터 백업)가 필요해서 사용했다.