본문으로 이동
  • 현재 모든 문서는 작성 중입니다. 보시는 데 불편함이 있더라도 양해 부탁드립니다.

운영체제 기본/x86-64 Linux의 BIOS 부팅/GRUB에서 커널로

~ldmsys
관리자 (토론 | 기여)님의 2026년 10월 31일 (토) 08:44 판 (부팅 init 선택 장의 새 문서명으로 링크 갱신)

부팅 목차 · 다음: 진입 규약과 압축 해제

BIOS 환경의 GRUB 2.12가 linux 명령으로 Linux v6.12의 x86-64 bzImage를 부팅하는 경로를 다룬다. GRUB 메뉴에서 커널을 선택한 시점부터 시작한다.

x86-64 커널이라고 해서 첫 진입도 64비트 모드인 것은 아니다. 이 경로에서는 GRUB가 32비트 보호 모드로 커널의 압축 해제 코드에 진입한다. Linux의 16비트 setup 코드를 반드시 실행하는 것은 아니다. GNU GRUB 매뉴얼도 일반 linux 명령과 16비트 진입을 사용하는 linux16 명령을 구별한다.[1]

전체 경로

GRUB 2.12 (BIOS)
    |
    +-- linux  : read bzImage, prepare parameters
    +-- initrd : read initial RAM filesystem image
    +-- boot   : finalize memory map and handoff state
    |
    v
32-bit protected mode, paging off
    |
    v
arch/x86/boot/compressed/head_64.S
    startup_32
    |
    +-- replace GDT, set up early execution state
    +-- prepare transition to 64-bit mode
    |
    v
    startup_64
    |
    v
decompression and entry to the uncompressed kernel

이 그림의 startup_32와 startup_64는 arch/x86/boot/compressed/head_64.S 안의 기호이다. 압축이 풀린 커널 쪽 arch/x86/kernel/head_64.S에도 startup_64가 있으므로, 함수 이름만으로 부팅 위치를 판단하면 안 된다.[2]

GRUB가 파일에서 읽는 것

GRUB 메뉴의 항목은 대략 다음과 같은 명령으로 커널과 초기 RAM 파일 시스템 이미지를 지정한다. 경로와 root= 값은 설치 환경에 따라 다르다.

linux  /boot/vmlinuz-6.12 root=UUID=... ro
initrd /boot/initramfs-6.12.img
boot

여기서 linux는 파일을 불러오고 나중에 실행할 부팅 함수를 등록한다. 이 명령이 끝나는 순간 곧바로 커널이 실행되는 것은 아니다. initrd로 추가 데이터를 읽은 뒤 boot가 실제 인계를 수행한다. GRUB 2.12의 대응 구현은 grub_cmd_linux(), grub_cmd_initrd(), grub_linux_boot()이다.[3]

bzImage는 압축된 커널 본문만 들어 있는 파일이 아니다. 앞부분에는 부팅용 헤더와 setup 영역이 있고, 그 뒤에는 압축 해제 실행 코드와 압축된 커널을 포함하는 보호 모드 부분이 있다. GRUB는 먼저 헤더의 서명과 지원 규약을 검사하고, setup_sects로 두 부분의 경계를 찾는다. 보호 모드 부분의 파일 오프셋은 (setup_sects + 1) * 512이다. setup_sects가 0이면 호환 규칙에 따라 4로 해석한다.[4]

중요한 점은 setup 영역을 읽거나 그 헤더를 복사하는 것과 setup 코드를 실행하는 것은 다르다는 것이다. 이 경로의 GRUB는 필요한 헤더 내용을 부팅 매개변수 구조체로 복사하고 보호 모드 부분을 적재한다. 이어서 32비트 진입점으로 직접 넘어간다. 따라서 이 경로 앞에 arch/x86/boot/header.S의 16비트 진입과 arch/x86/boot/main.c의 실행을 끼워 넣으면 실제 실행 순서와 달라진다.[3]

커널 적재 주소 역시 언제나 0x100000이라고 단정하면 안 된다. GRUB는 헤더의 재배치 가능 여부, 정렬 조건, 선호 주소와 초기 실행 공간 크기를 읽어 메모리를 확보한다. 실제 선택한 위치에 맞춰 code32_start도 조정한다. 여기까지는 압축된 이미지를 실행할 공간을 준비하는 단계이며, GRUB가 Linux의 압축 해제 작업을 대신 끝낸 상태는 아니다.[3]

initrd 명령은 별도의 이미지를 메모리에 올리고 그 주소와 크기를 기록한다. 이름이 initrd여도 실제 내용은 initramfs일 수 있다. 부트로더 관점에서는 커널에 전달할 바이트 배열이며, 이를 해석하여 초기 루트 파일시스템으로 사용하는 일은 커널 쪽 일이다. 이 데이터가 /init 파일로 이어지는 과정은 루트 파일시스템과 init 선택에서 다룬다.

boot_params로 부트 정보를 전달한다

GRUB는 커널에 파일 이름만 전달하지 않는다. 파일은 이미 메모리에 올라와 있어야 하며, 커널이 그 메모리를 찾고 기계를 파악하는 데 필요한 정보도 함께 준비한다.

전달 대상 GRUB 2.12에서 하는 일 커널이 알아야 하는 이유
boot_params 구조체를 0으로 초기화한 뒤 setup 헤더와 추가 정보를 채운다. 어느 부팅 규약과 환경에서 진입했는지 해석한다.
cmd_line_ptr 커널 명령행 문자열을 별도 공간에 복사하고 주소를 기록한다. root=, ro 등의 설정을 읽는다.
ramdisk_image, ramdisk_size initrd 명령으로 적재한 이미지의 주소와 크기를 기록한다. 초기 RAM 파일 시스템 이미지의 위치를 찾는다.
메모리 맵 GRUB가 파악한 물리 메모리 구간을 부팅 정보에 옮긴다. 사용 가능한 RAM과 예약 구간을 구분한다.
화면 정보 선택한 화면 상태를 부팅 정보에 반영한다. 커널의 초기 화면 출력을 준비한다.

GRUB 소스의 linux_params는 준비 중인 정보이며, grub_linux_boot()는 최종 배치할 공간을 확보하고 이를 복사한다. 같은 단계에서 명령행 주소와 메모리 맵도 마무리한다. 소스에 나오는 real_mode_target이라는 변수명만 보고 16비트 실행 경로라고 판단해서는 안 된다. 여기서는 최종 부팅 정보가 놓일 주소를 가리키며, 마지막 분기는 명백히 grub_relocator32_boot()이다.[3]

마지막 점프 직전의 CPU 상태

GDT의 디스크립터와 세그먼트 선택자가 주소 해석과 권한을 정하는 방식은 GDT와 LDT 초기화에서 설명한다.

부팅 규약은 데이터를 어디에 놓는지만 정하지 않는다. CPU가 어떤 상태로 첫 명령을 실행해야 하는지도 정한다. 이 32비트 진입에서는 다음 상태가 인계 계약이다.[4]

항목 인계 시점의 조건
실행 모드 32비트 보호 모드 페이징 꺼짐 인터럽트 비활성화 GDT 코드 선택자 0x10, 데이터 선택자 0x18에 대응하는 평탄 세그먼트가 유효함 세그먼트 레지스터 CS=0x10, DS=ES=SS=0x18 ESI boot_params의 시작 주소 EBP, EDI, EBX 0 실행 주소 적재된 보호 모드 부분의 32비트 진입점

이 조건을 실제로 만드는 GRUB 코드는 grub-core/lib/i386/relocator32.S에 있다. 이 코드는 자신의 작은 GDT를 사용하고 데이터 세그먼트 레지스터를 설정한 뒤 준비된 레지스터 값을 복원한다. 마지막에는 코드 선택자 0x10을 사용한 먼 점프로 커널 진입 주소에 도달한다. 이 GDT의 역할은 운영체제의 모든 세그먼트 구성을 완성하는 것이 아니라, 커널이 첫 명령부터 약속된 주소 해석으로 실행되도록 보장하는 것이다.[5]

grub_linux_boot()는 ESI뿐 아니라 ESP에도 부팅 정보의 최종 주소를 넣는다. 그러나 이를 Linux가 계속 사용할 정식 커널 스택이라고 생각해서는 안 된다. Linux의 startup_32는 곧 boot_params 안의 scratch 필드를 이용해 현재 적재 위치를 계산하고, 자신의 초기 부팅 스택을 설정한다.[3][2]

인계 이후에는 누가 무엇을 책임지는가

GRUB가 진입점으로 점프하면 정상 부팅 흐름에서 제어권은 Linux로 넘어간다. Linux는 GRUB의 함수 호출을 계속 이용하여 운영체제를 실행하지 않는다. 그렇다고 넘어오는 순간 GRUB가 적재한 모든 메모리를 지워도 되는 것은 아니다. 커널 이미지, 부팅 정보, 명령행, initrd는 커널이 읽어야 할 입력으로 남아 있다.

Linux v6.12의 compressed/head_64.S:startup_32는 방향 플래그와 인터럽트 상태를 정리하고 자신의 적재 위치를 계산한다. 이어서 자기 GDT를 lgdt로 설치하고 세그먼트 레지스터와 스택을 다시 설정한다. 따라서 GRUB가 제공한 GDT와 Linux가 이후에 설치하는 GDT는 역할과 수명이 다르다. 전자는 진입 계약을 성립시키는 임시 기반이며, 후자는 Linux 자신의 실행을 준비하는 기반이다.[2]

이 시점에 아직 일반 커널 초기화가 끝난 것은 아니다. 64비트 모드 진입, 초기 페이지 테이블, 압축 해제와 최종 커널 진입이 이어진다. start_kernel()은 그 뒤의 단계이다. 부팅을 읽을 때는 다음 세 경계를 따로 표시하는 편이 정확하다.

  1. GRUB에서 압축 해제 코드로: 이 문서에서 다룬 32비트 진입 계약.
  2. 압축 해제 코드 안에서 64비트 실행으로: 커널 자신의 초기 실행 환경 구성.
  3. 압축 해제 코드에서 압축이 풀린 커널로: 별도의 초기 어셈블리 진입과 C 초기화 준비.

linux16과 혼동하지 않기

BIOS용 GRUB에는 linux16과 initrd16도 있다. 이 경로는 Linux의 16비트 setup에 진입하며, setup이 BIOS 기반 초기 작업과 보호 모드 전환을 수행한다. 하지만 그것은 이 문서의 일반 linux 경로와 다른 선택이다. BIOS로 시작했다는 이유만으로 Linux의 16비트 setup도 실행되었다고 결론 내릴 수 없다.[1]

64비트 모드 전환과 압축 해제는 다음 장으로 이어진다.

참고 문헌

  1. ↑ 1.0 1.1 GNU GRUB Manual, GNU/Linux. 명령의 구분을 설명하는 공식 매뉴얼. 이 문서의 구현 분석은 현재 매뉴얼 버전이 아니라 GRUB 2.12 소스에 고정한다.
  2. ↑ 2.0 2.1 2.2 Linux v6.12, arch/x86/boot/compressed/head_64.S. startup_32의 진입 조건과 초기 GDT 교체를 확인할 수 있다.
  3. ↑ 3.0 3.1 3.2 3.3 3.4 GNU GRUB 2.12, grub-core/loader/i386/linux.c. 이 문서에서는 BIOS 빌드에 해당하는 분기만 추적한다.
  4. ↑ 4.0 4.1 Linux v6.12, The Linux/x86 Boot Protocol. 특히 Memory Layout, Loading The Rest of The Kernel, 32-bit Boot Protocol 절.
  5. ↑ GNU GRUB 2.12, grub-core/lib/i386/relocator32.S. 코드 및 데이터 디스크립터와 마지막 먼 점프를 확인할 수 있다.