운영체제 기본/x86-64 Linux의 BIOS 부팅/GRUB에서 커널로: 두 판 사이의 차이
잔글 관리자님이 운영체제 기본/x86-64 Linux의 부팅/GRUB에서 커널로 문서를 운영체제 기본/x86-64 Linux의 BIOS 부팅/GRUB에서 커널로 문서로 이동했습니다: x86-64 전용 각론의 아키텍처와 장치·부팅 범위를 제목에 명시 |
BIOS GRUB 부팅 경로 보강과 각론 형식 통일 |
||
| (같은 사용자의 중간 판 하나는 보이지 않습니다) | |||
| 1번째 줄: | 1번째 줄: | ||
[[운영체제 기본/x86-64 Linux의 부팅|부팅 목차]] · 다음: [[운영체제 기본/x86-64 Linux의 부팅/진입 규약과 압축 해제|진입 규약과 압축 해제]] | [[운영체제 기본/x86-64 Linux의 BIOS 부팅|부팅 목차]] · 다음: [[운영체제 기본/x86-64 Linux의 BIOS 부팅/진입 규약과 압축 해제|진입 규약과 압축 해제]] | ||
'''BIOS 환경의 GRUB 2.12가 <code>linux</code> 명령으로 Linux v6.12의 x86-64 bzImage를 부팅하는 경로'''를 다룬다. GRUB 메뉴에서 커널을 선택한 시점부터 시작한다. | '''BIOS 환경의 GRUB 2.12가 <code>linux</code> 명령으로 Linux v6.12의 x86-64 bzImage를 부팅하는 경로'''를 다룬다. GRUB 메뉴에서 커널을 선택한 시점부터 시작한다. | ||
| 51번째 줄: | 51번째 줄: | ||
커널 적재 주소 역시 언제나 <code>0x100000</code>이라고 단정하면 안 된다. GRUB는 헤더의 재배치 가능 여부, 정렬 조건, 선호 주소와 초기 실행 공간 크기를 읽어 메모리를 확보한다. 실제 선택한 위치에 맞춰 <code>code32_start</code>도 조정한다. 여기까지는 압축된 이미지를 실행할 공간을 준비하는 단계이며, GRUB가 Linux의 압축 해제 작업을 대신 끝낸 상태는 아니다.<ref name="grub-linux" /> | 커널 적재 주소 역시 언제나 <code>0x100000</code>이라고 단정하면 안 된다. GRUB는 헤더의 재배치 가능 여부, 정렬 조건, 선호 주소와 초기 실행 공간 크기를 읽어 메모리를 확보한다. 실제 선택한 위치에 맞춰 <code>code32_start</code>도 조정한다. 여기까지는 압축된 이미지를 실행할 공간을 준비하는 단계이며, GRUB가 Linux의 압축 해제 작업을 대신 끝낸 상태는 아니다.<ref name="grub-linux" /> | ||
<code>initrd</code> 명령은 별도의 이미지를 메모리에 올리고 그 주소와 크기를 기록한다. 이름이 initrd여도 실제 내용은 initramfs일 수 있다. 부트로더 관점에서는 커널에 전달할 바이트 배열이며, 이를 해석하여 초기 루트 파일시스템으로 사용하는 일은 커널 쪽 일이다. 이 데이터가 <code>/init</code> 파일로 이어지는 과정은 [[운영체제 기본/x86-64 Linux의 부팅/루트 파일시스템과 init 선택#init을 실행하려면 먼저 그 파일이 있어야 한다|루트 파일시스템과 init 선택]]에서 다룬다. | <code>initrd</code> 명령은 별도의 이미지를 메모리에 올리고 그 주소와 크기를 기록한다. 이름이 initrd여도 실제 내용은 initramfs일 수 있다. 부트로더 관점에서는 커널에 전달할 바이트 배열이며, 이를 해석하여 초기 루트 파일시스템으로 사용하는 일은 커널 쪽 일이다. 이 데이터가 <code>/init</code> 파일로 이어지는 과정은 [[운영체제 기본/x86-64 Linux의 BIOS 부팅/루트 파일시스템과 init 선택#init을 실행하려면 먼저 그 파일이 있어야 한다|루트 파일시스템과 init 선택]]에서 다룬다. | ||
== boot_params로 부트 정보를 전달한다 == | == boot_params로 부트 정보를 전달한다 == | ||
| 85번째 줄: | 85번째 줄: | ||
== 마지막 점프 직전의 CPU 상태 == | == 마지막 점프 직전의 CPU 상태 == | ||
GDT의 디스크립터와 세그먼트 선택자가 주소 해석과 권한을 정하는 방식은 [[운영체제 기본/x86-64 Linux의 부팅/GDT와 LDT 초기화|GDT와 LDT 초기화]]에서 설명한다. | GDT의 디스크립터와 세그먼트 선택자가 주소 해석과 권한을 정하는 방식은 [[운영체제 기본/x86-64 Linux의 BIOS 부팅/GDT와 LDT 초기화|GDT와 LDT 초기화]]에서 설명한다. | ||
부팅 규약은 데이터를 어디에 놓는지만 정하지 않는다. CPU가 어떤 상태로 첫 명령을 실행해야 하는지도 정한다. 이 32비트 진입에서는 다음 상태가 인계 계약이다.<ref name="boot-protocol" /> | 부팅 규약은 데이터를 어디에 놓는지만 정하지 않는다. CPU가 어떤 상태로 첫 명령을 실행해야 하는지도 정한다. 이 32비트 진입에서는 다음 상태가 인계 계약이다.<ref name="boot-protocol" /> | ||
| 93번째 줄: | 93번째 줄: | ||
|- | |- | ||
| 실행 모드 || 32비트 보호 모드 | | 실행 모드 || 32비트 보호 모드 | ||
|- | |||
| 페이징 || 꺼짐 | | 페이징 || 꺼짐 | ||
|- | |||
| 인터럽트 || 비활성화 | | 인터럽트 || 비활성화 | ||
|- | |||
| GDT || 코드 선택자 <code>0x10</code>, 데이터 선택자 <code>0x18</code>에 대응하는 평탄 세그먼트가 유효함 | | GDT || 코드 선택자 <code>0x10</code>, 데이터 선택자 <code>0x18</code>에 대응하는 평탄 세그먼트가 유효함 | ||
|- | |||
| 세그먼트 레지스터 || <code>CS=0x10</code>, <code>DS=ES=SS=0x18</code> | | 세그먼트 레지스터 || <code>CS=0x10</code>, <code>DS=ES=SS=0x18</code> | ||
|- | |||
| <code>ESI</code> || <code>boot_params</code>의 시작 주소 | | <code>ESI</code> || <code>boot_params</code>의 시작 주소 | ||
|- | |||
| <code>EBP</code>, <code>EDI</code>, <code>EBX</code> || 0 | | <code>EBP</code>, <code>EDI</code>, <code>EBX</code> || 0 | ||
|- | |||
| 실행 주소 || 적재된 보호 모드 부분의 32비트 진입점 | | 실행 주소 || 적재된 보호 모드 부분의 32비트 진입점 | ||
|} | |} | ||
| 122번째 줄: | 129번째 줄: | ||
BIOS용 GRUB에는 <code>linux16</code>과 <code>initrd16</code>도 있다. 이 경로는 Linux의 16비트 setup에 진입하며, setup이 BIOS 기반 초기 작업과 보호 모드 전환을 수행한다. 하지만 그것은 이 문서의 일반 <code>linux</code> 경로와 다른 선택이다. '''BIOS로 시작했다는 이유만으로 Linux의 16비트 setup도 실행되었다고 결론 내릴 수 없다.'''<ref name="grub-manual" /> | BIOS용 GRUB에는 <code>linux16</code>과 <code>initrd16</code>도 있다. 이 경로는 Linux의 16비트 setup에 진입하며, setup이 BIOS 기반 초기 작업과 보호 모드 전환을 수행한다. 하지만 그것은 이 문서의 일반 <code>linux</code> 경로와 다른 선택이다. '''BIOS로 시작했다는 이유만으로 Linux의 16비트 setup도 실행되었다고 결론 내릴 수 없다.'''<ref name="grub-manual" /> | ||
64비트 모드 전환과 압축 해제는 [[운영체제 기본/x86-64 Linux의 부팅/진입 규약과 압축 해제|다음 장]]으로 이어진다. | 64비트 모드 전환과 압축 해제는 [[운영체제 기본/x86-64 Linux의 BIOS 부팅/진입 규약과 압축 해제|다음 장]]으로 이어진다. | ||
== 참고 문헌 == | == 참고 문헌 == | ||
<references /> | <references /> | ||
2026년 10월 31일 (토) 09:25 기준 최신판
부팅 목차 · 다음: 진입 규약과 압축 해제
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()은 그 뒤의 단계이다. 부팅을 읽을 때는 다음 세 경계를 따로 표시하는 편이 정확하다.
- GRUB에서 압축 해제 코드로: 이 문서에서 다룬 32비트 진입 계약.
- 압축 해제 코드 안에서 64비트 실행으로: 커널 자신의 초기 실행 환경 구성.
- 압축 해제 코드에서 압축이 풀린 커널로: 별도의 초기 어셈블리 진입과 C 초기화 준비.
linux16과 혼동하지 않기
BIOS용 GRUB에는 linux16과 initrd16도 있다. 이 경로는 Linux의 16비트 setup에 진입하며, setup이 BIOS 기반 초기 작업과 보호 모드 전환을 수행한다. 하지만 그것은 이 문서의 일반 linux 경로와 다른 선택이다. BIOS로 시작했다는 이유만으로 Linux의 16비트 setup도 실행되었다고 결론 내릴 수 없다.[1]
64비트 모드 전환과 압축 해제는 다음 장으로 이어진다.
참고 문헌
- ↑ 1.0 1.1 GNU GRUB Manual, GNU/Linux. 명령의 구분을 설명하는 공식 매뉴얼. 이 문서의 구현 분석은 현재 매뉴얼 버전이 아니라 GRUB 2.12 소스에 고정한다.
- ↑ 2.0 2.1 2.2 Linux v6.12, arch/x86/boot/compressed/head_64.S.
startup_32의 진입 조건과 초기 GDT 교체를 확인할 수 있다. - ↑ 3.0 3.1 3.2 3.3 3.4 GNU GRUB 2.12, grub-core/loader/i386/linux.c. 이 문서에서는 BIOS 빌드에 해당하는 분기만 추적한다.
- ↑ 4.0 4.1 Linux v6.12, The Linux/x86 Boot Protocol. 특히 Memory Layout, Loading The Rest of The Kernel, 32-bit Boot Protocol 절.
- ↑ GNU GRUB 2.12, grub-core/lib/i386/relocator32.S. 코드 및 데이터 디스크립터와 마지막 먼 점프를 확인할 수 있다.