운영체제 기본/Linux/x86-64/BIOS 부팅/진입 규약과 압축 해제: 두 판 사이의 차이
목차 순서에 맞춰 다음 장을 GDT 초기화로 연결 |
문장형 절 제목을 직관적인 명사형으로 통일하고 기존 절 링크 보존 |
||
| 4번째 줄: | 4번째 줄: | ||
<span id="1._첫_명령어에도_실행_조건이_필요하다"></span> | <span id="1._첫_명령어에도_실행_조건이_필요하다"></span> | ||
== | <span id="첫_명령어에도_실행_조건이_필요하다"></span> | ||
== 커널 진입 조건 == | |||
이 경로의 진입점은 <code>arch/x86/boot/compressed/head_64.S:startup_32</code>다. 파일 이름의 <code>64</code>는 x86-64 커널용 소스라는 뜻이지, 그 파일의 모든 명령어가 64비트라는 뜻은 아니다. 이 심볼은 <code>.code32</code> 구역에 있고, 부트 프로토콜이 정한 보호 모드 이미지의 오프셋 0에 놓인다. bzImage 파일의 처음은 아니며, <code>start_kernel()</code>의 주소도 아니다.<ref name="compressed">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/boot/compressed/head_64.S?h=v6.12 Linux v6.12, arch/x86/boot/compressed/head_64.S], startup_32, startup_64, .Lrelocated.</ref> | 이 경로의 진입점은 <code>arch/x86/boot/compressed/head_64.S:startup_32</code>다. 파일 이름의 <code>64</code>는 x86-64 커널용 소스라는 뜻이지, 그 파일의 모든 명령어가 64비트라는 뜻은 아니다. 이 심볼은 <code>.code32</code> 구역에 있고, 부트 프로토콜이 정한 보호 모드 이미지의 오프셋 0에 놓인다. bzImage 파일의 처음은 아니며, <code>start_kernel()</code>의 주소도 아니다.<ref name="compressed">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/boot/compressed/head_64.S?h=v6.12 Linux v6.12, arch/x86/boot/compressed/head_64.S], startup_32, startup_64, .Lrelocated.</ref> | ||
| 20번째 줄: | 20번째 줄: | ||
이어서 자기 GDT를 적재하고 데이터 세그먼트와 부트 스택을 설정한다. GDT를 바꾼 뒤 CS도 새 테이블에 맞게 다시 적재한다. 단순히 <code>lgdt</code>만 실행하면 이미 세그먼트 레지스터에 캐시된 속성까지 자동으로 바뀌지는 않기 때문이다. 구체적인 디스크립터와 LDT 처리는 [[운영체제 기본/x86-64 Linux의 BIOS 부팅/GDT와 LDT 초기화|GDT와 LDT 초기화]]에서 설명한다. | 이어서 자기 GDT를 적재하고 데이터 세그먼트와 부트 스택을 설정한다. GDT를 바꾼 뒤 CS도 새 테이블에 맞게 다시 적재한다. 단순히 <code>lgdt</code>만 실행하면 이미 세그먼트 레지스터에 캐시된 속성까지 자동으로 바뀌지는 않기 때문이다. 구체적인 디스크립터와 LDT 처리는 [[운영체제 기본/x86-64 Linux의 BIOS 부팅/GDT와 LDT 초기화|GDT와 LDT 초기화]]에서 설명한다. | ||
== 32비트 보호 | <span id="32비트_보호_모드에서_64비트_모드로"></span> | ||
== 32비트 보호 모드와 64비트 모드 전환 == | |||
<code>verify_cpu()</code>로 필요한 CPU 기능을 확인한 뒤, 커널은 롱 모드에 필요한 페이징을 준비한다. 롱 모드는 명령어 하나로 32비트 레지스터를 64비트로 바꾸는 기능이 아니다. CPU 모드, 주소 변환 방식, 코드 세그먼트 속성을 서로 맞추어야 한다.<ref name="compressed"/> | <code>verify_cpu()</code>로 필요한 CPU 기능을 확인한 뒤, 커널은 롱 모드에 필요한 페이징을 준비한다. 롱 모드는 명령어 하나로 32비트 레지스터를 64비트로 바꾸는 기능이 아니다. CPU 모드, 주소 변환 방식, 코드 세그먼트 속성을 서로 맞추어야 한다.<ref name="compressed"/> | ||
| 41번째 줄: | 41번째 줄: | ||
<span id="2._지금_들어온_곳은_커널_본체가_아니다"></span> | <span id="2._지금_들어온_곳은_커널_본체가_아니다"></span> | ||
== | <span id="지금_들어온_곳은_커널_본체가_아니다"></span> | ||
== 압축 해제기와 커널 본체 == | |||
소스에는 <code>startup_64</code>가 두 군데 있다. 지금 실행하는 것은 <code>arch/x86/boot/compressed/head_64.S</code>의 압축 해제기다. 압축이 끝난 뒤 실행할 것은 <code>arch/x86/kernel/head_64.S</code>의 커널 본체다. 둘은 따로 링크되는 이미지에 속하며, 같은 이름을 공유할 뿐 서로 다른 진입점이다.<ref name="compressed"/><ref>[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/head_64.S?h=v6.12 Linux v6.12, arch/x86/kernel/head_64.S], startup_64.</ref> | 소스에는 <code>startup_64</code>가 두 군데 있다. 지금 실행하는 것은 <code>arch/x86/boot/compressed/head_64.S</code>의 압축 해제기다. 압축이 끝난 뒤 실행할 것은 <code>arch/x86/kernel/head_64.S</code>의 커널 본체다. 둘은 따로 링크되는 이미지에 속하며, 같은 이름을 공유할 뿐 서로 다른 진입점이다.<ref name="compressed"/><ref>[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/head_64.S?h=v6.12 Linux v6.12, arch/x86/kernel/head_64.S], startup_64.</ref> | ||
| 56번째 줄: | 56번째 줄: | ||
<span id="3._압축_해제_중_자기_자신을_덮어쓰지_않아야_한다"></span> | <span id="3._압축_해제_중_자기_자신을_덮어쓰지_않아야_한다"></span> | ||
== 압축 | <span id="압축_해제_중_자기_자신을_덮어쓰지_않아야_한다"></span> | ||
== 압축 해제기의 재배치와 메모리 배치 == | |||
압축 입력보다 풀린 출력이 크다. 원래 적재 위치에서 곧바로 낮은 주소부터 출력하면 아직 읽지 않은 입력, 현재 실행 중인 해제기, 스택을 덮어쓸 수 있다. Linux는 부트 헤더의 <code>init_size</code>로 확보한 초기 작업 공간의 뒤쪽에 압축 이미지를 옮긴 뒤 작업한다. 옮길 위치는 출력 기준 주소에 <code>init_size</code>를 더하고 압축 이미지의 <code>_end</code> 오프셋을 빼서 구한다. 이 여유 공간의 크기는 이미지 빌드 과정에서 압축 해제와 초기 실행에 필요한 크기를 고려해 계산된다.<ref name="compressed"/><ref name="protocol"/> | 압축 입력보다 풀린 출력이 크다. 원래 적재 위치에서 곧바로 낮은 주소부터 출력하면 아직 읽지 않은 입력, 현재 실행 중인 해제기, 스택을 덮어쓸 수 있다. Linux는 부트 헤더의 <code>init_size</code>로 확보한 초기 작업 공간의 뒤쪽에 압축 이미지를 옮긴 뒤 작업한다. 옮길 위치는 출력 기준 주소에 <code>init_size</code>를 더하고 압축 이미지의 <code>_end</code> 오프셋을 빼서 구한다. 이 여유 공간의 크기는 이미지 빌드 과정에서 압축 해제와 초기 실행에 필요한 크기를 고려해 계산된다.<ref name="compressed"/><ref name="protocol"/> | ||
| 75번째 줄: | 75번째 줄: | ||
새 위치에서는 압축 해제기 BSS를 비우고 다음 단계의 예외 처리와 identity mapping을 준비한다. BSS 초기화는 C의 초기값 없는 전역·정적 변수가 0이라는 조건을 실제 메모리에 구현하는 과정이다. 뒤에서 커널 본체가 비울 BSS와는 별개다. | 새 위치에서는 압축 해제기 BSS를 비우고 다음 단계의 예외 처리와 identity mapping을 준비한다. BSS 초기화는 C의 초기값 없는 전역·정적 변수가 0이라는 조건을 실제 메모리에 구현하는 과정이다. 뒤에서 커널 본체가 비울 BSS와는 별개다. | ||
== | <span id="압축을_푸는_일과_실행_이미지를_만드는_일"></span> | ||
== 압축 해제와 실행 이미지 구성 == | |||
Linux v6.12에서 어셈블리가 호출하는 함수는 <code>extract_kernel(boot_params, output)</code>이다. 이 함수는 필요한 출력 공간과 배치 위치를 정하고, 정렬과 주소 범위를 검사한다. 배치 위치는 커널 구성과 부팅 옵션에 따라 달라질 수 있다. 필요한 공간은 압축 해제 결과의 크기뿐 아니라 본체의 BSS와 초기 작업 공간까지 수용해야 한다. 따라서 “압축 파일 크기의 몇 배” 같은 임의 계산으로 출력 버퍼를 정하지 않는다.<ref name="misc">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/boot/compressed/misc.c?h=v6.12 Linux v6.12, arch/x86/boot/compressed/misc.c], extract_kernel, decompress_kernel, parse_elf, handle_relocations.</ref> | Linux v6.12에서 어셈블리가 호출하는 함수는 <code>extract_kernel(boot_params, output)</code>이다. 이 함수는 필요한 출력 공간과 배치 위치를 정하고, 정렬과 주소 범위를 검사한다. 배치 위치는 커널 구성과 부팅 옵션에 따라 달라질 수 있다. 필요한 공간은 압축 해제 결과의 크기뿐 아니라 본체의 BSS와 초기 작업 공간까지 수용해야 한다. 따라서 “압축 파일 크기의 몇 배” 같은 임의 계산으로 출력 버퍼를 정하지 않는다.<ref name="misc">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/boot/compressed/misc.c?h=v6.12 Linux v6.12, arch/x86/boot/compressed/misc.c], extract_kernel, decompress_kernel, parse_elf, handle_relocations.</ref> | ||