Kernel image
아래의 내용은 대부분 문c 블로그에서 가져온 내용입니다. 더 자세한 내용은 문영일 선생님의 블로그를 참고하시기 바랍니다. 어디까지나 공부 목적으로 용어 해설을 첨부하여 정리한 글입니다.
Image
컴퓨터에서 이미지(image
) 는 기록 미디어 안에 있는 내용이 저장된 파일을 가리킨다. 이러한 디스크 이미지는 일부 압축 프로그램을 사용하여 압축 파일을 풀듯이 풀 수도 있고, 별도의 가상 CD/DVD 소프트웨어를 이용하여 마치 실제 CD/DVD 미디어를 사용하는 것처럼 에뮬레이트하는 데 쓸 수도 있다.
부트 이미지는 디스트 이미지(저장 매체에 대한 완전한 구조와 내용들을 포함하는 컴퓨터 파일)의 일종이다. 이것이 부트 디바이스(boot device
) 로 옮겨지면 관련된 하드웨어가 부팅을 진행할 수 있다.
부트 이미지는 일반적으로 운영체제, 유틸리티, 진단장비 뿐만 아니라 부트와 데이터 복구 정보 또한 포함하고 있다.
부트 디바이스(시동 장치, 부팅 장치)란 운영체제가 로드되는 장치이다. 현대 PC 의 UEFI
혹은 BIOS
펌웨어는 다양한 장치를 통한 부팅을 지원한다. 이러한 장치에는 대표적으로 GPT
혹은 마스터 부트 레코드 (MBR
) 를 통한 솔리드 스테이트 드라이브(SSD
) 와 하드 디스크 드라이브(HDD
) 가 있다. 뭐 기타 등등 더 있는데 중략.
여기서 UEFI
, BIOS
, GPT
, MBR
, HDD
, SSD
등은 생략... 여기까지 설명하면 너무 길어진다.
위에서 설명한 UEFI
와 BIOS
와 같은 펌웨어를 부트로더라 부르며 이러한 소프트웨어는 AArch64 linux
에서 보통 아래와 같은 기능을 수행한다:
RAM 초기화 및 설정 (필수)
디바이스 트리 설정 (필수)
디바이스 트리 블롭(DTB
, Device tree Blob) 은 반드시 8-byte
경계를 기점으로 배치되어야 하고, 반드시 2 MiB
를 넘겨선 안된다. x0
레지스터에 DTB
의 물리주소가 저장된다.
커널 이미지 압축 해제 (선택)
AArch64
커널은 현재 더 이상 압축 해제자를 제공하지 않으므로, 만일 압축 해제된 이미지 타기싱 이용된다면, 부트로더에 의해 압축 해제가 수행되는 것을 필요로 한다. 이러한 구현이 필수사항으로 들어가지 않는 부트로더의 경우, 압축되어지지 않은 이미지 타겟이 필요로 되어진다.
커널 이미지 호출 (필수)
커널 이미지의 첫 주소로 분기하여 커널의 head.S
루틴을 시작한다.
Image
: 압축하지 않고 사용하는 이미지zImage
: 압축하여 사용, 자체 압축 해제 루틴이 포함되어 있음 (ARM32)Image.gz
: 압축하여 사용하는 이미지, 해제 루틴 x (ARM64)gz
, bz2
, lz4
, lzma
, lzo
)uImage
: u-Boot
부트로더가 zImage
를 변형(64 바이트 헤더 추가)하여 사용하는 이미지.bzImage
: zImage
를 확장시켜 PC 에서 사용하는 포맷xipImage
: eXecute In Place Image, 압축하지 않고 바로 플래시 ROM
에서 커널이 동작하는 이미지. Flash
에서 읽고, 압축 해제하고, RAM
으로 복사하는 과정이 없음.`
종류 | 초기 위치 | 부트로더가 이미지를 SDRAM 에 로드 하는가? | 실행 위치 | SDRAM 으로 커널 copy | 커널 실행 메모리 |
---|---|---|---|---|---|
Image | ROM /Flash | Y | SDRAM | Y | SDRAM |
Image with ZBOOT | ROM /Flash | N | ROM /Flash | Y | SDRAM |
zImage | ROM /Flash | Y | SDRAM | Y + Decompress | SDRAM |
zImage with ZBOOT | ROM /Flash | N | ROM /Flash | Y + Decompress | SDRAM |
xipImage | ROM /FLASH | N | ROM /Flash | N But... | ROM /Flash |
SDRAM
에서 코드를 동작시키면 ROM/Flash
보다 수행 속도는 빠르지만 ROM/Flash
에서 SDRAM
으로 로딩하는 절차가 추가된다.ROM/Fash
에서 zImage
를 직접 실행하게 되면 Decompres
결과를 SDRAM
으로 바로 쏴버린다.ZBOOT
란...CONFIG_ZBOOT_ROM
옵션으로 Image
나 zImage(uImage)
를 SDRAM
으로 옮기지 않고 ROM/Flash
에서 곧바로 동작 시킬 때 사용하는 옵션.
xipImage
: SDRAM
으로의 copy
커널 코드는 SDRAM
으로 옮기지 않지만, Data
공간은 SDRAM
에 옮긴다.
말 그대로, Ready Only Memory
RAM
: Random Access Memory, 임의의 영역에 접근하여 읽고 쓰기가 가능한 주기억 장치.SRAM
: Static RAM, 전원이 공급되는 동안 데이터가 유지되는 RAM
DRAM
: Dynamic RAM, 데이터를 유지하기 위해 일정 시간마다 재생(refresh) 해주어야 하는 램SDRAM
: Synchronous DRAM, DRAM
의 발전된 형태. 보통의 DRAM
과 달리 제어 장치 입력을 클락 신호와 동시에 일어나도록 한 동기화 방식의 DRAM
.Flash memory
: 값이 싸고 집적도는 높지만 느리다.FRAM
: Ferroelectric RAM, DRAM
과 비슷하지만 데이터를 유지하기 위해 재생할 필요 없음.PRAM
: Phase-change RAM, 상변태를 하는 물질을 이용하여 저항차이로 데이터를 저장, 전력소모 매우 적음.zImage
물리 시작 주소 with ARM32
zImage
가 실행되는 물리 주소는 시스템마다 다르며 이를 커널에 알리기 위해 TEXT_OFFSET
을 정의하여 사용한다.
Kernel 4.0
이전에는 시스템 오프셋을 시스템마다 미리 지정해서 물리 메모리에서 최하단(TEXT_BASE
) 에서 어느 정도 거리를 띄워서 사용했다.
ZBOOT
의 경우 압축된 커널 이미지를 ROM/Flash
에서 동작시켜 SDRAM
에 압축을 풀어, 실제 커널은 SDRAM
에서 실행된다.
xipImage
는 ROM/Flash
에서 바로 실행되지만, Kernel Data
는 SDRAM
으로 로드하여 실행한다.
Image.gz
생성 과정vmlinux
(ELF
)
리눅스 커널 코드를 컴파일하여 생성된 *.o
파일과 *.a
파일들을 링크하여 하나의 vmlinux
파일을 생성한다.
Image
(bin
)
objcopy -O binary -R .note -R \
.note.gnu.build-id -R .comment \
-S vmlinux {arch}/Image
생성된 vmlinux
파일에 대해 objcopy
를 진행한다. 이 과정에서 일부 Section
정보를 지운다. 결과물로 Image
파일이 생성된다.
아래부턴 ISA 별로 갈린다.
piggy.gz
gzip
을 통해 piggy.gz
이라는 파일을 생성한다.
piggy.o
생성된 piggy.gz
파일에서 디버깅 정보 등을 삭제하여 piggy.o
를 생성한다.
zImage
or bzImage
마지막으로 커널의 압축을 해제시켜주는 코드를 커널 앞부분에 덧붙여서 링커를 통해 zImage
혹은 bzImage
파일을 생성한다.
물리 메모리가 1MiB
위치에 로드될 수 있는 작은 크기의 커널은 zImage
가 생성되고, 그렇지 못한 경우엔 bzImage
(big zImage
) 가 생성된다.
Image.gz
(bin
)cat arch/arm64/boot/Image | \
gzip -n -f -9 > arch/arm64/boot/Image.gz
일부 섹션 정보를 지운 이미지 파일을 gzip
으로 압축해서 동일한 디렉토리에 Image.gz
라는 이름으로 저장한다.vmlinux
) 분석file
명령어 실행 결과vmlinux
파일의 구조를 file
명령어를 통해 확인한 결과이다. ELF 64-bit
, ARM aarch64
, not stripped
로 확인된다.
readelf
실행 결과ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: AArch64
Version: 0x1
Entry point address: 0xffff800010000000
Start of program headers: 64 (bytes into file)
Start of section headers: 329082624 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 3
Size of section headers: 64 (bytes)
Number of section headers: 40
Section header string table index: 39
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .head.text PROGBITS ffff800010000000 00010000
0000000000010000 0000000000000000 AX 0 0 65536
[ 2] .text PROGBITS ffff800010010000 00020000
0000000000dfb178 0000000000000008 AX 0 0 2048
[ 3] .got.plt PROGBITS ffff800010e0b178 00e1b178
0000000000000018 0000000000000008 WA 0 0 8
[ 4] .rodata PROGBITS ffff800010e10000 00e20000
00000000006f5f80 0000000000000000 WA 0 0 4096
[ 5] .pci_fixup PROGBITS ffff800011505f80 01515f80
00000000000025e0 0000000000000000 A 0 0 16
[ 6] __ksymtab PROGBITS ffff800011508560 01518560
000000000000f570 0000000000000000 A 0 0 4
[ 7] __ksymtab_gpl PROGBITS ffff800011517ad0 01527ad0
0000000000013ea8 0000000000000000 A 0 0 4
[ 8] __ksymtab_strings PROGBITS ffff80001152b978 0153b978
0000000000038dc7 0000000000000001 AMS 0 0 1
[ 9] __param PROGBITS ffff800011564740 01574740
0000000000004178 0000000000000000 A 0 0 8
[10] __modver PROGBITS ffff8000115688b8 015788b8
00000000000000f0 0000000000000000 A 0 0 8
[11] __ex_table PROGBITS ffff8000115689a8 015789a8
0000000000002068 0000000000000000 A 0 0 8
[12] .notes NOTE ffff80001156aa10 0157aa10
000000000000003c 0000000000000000 A 0 0 4
[13] .init.text PROGBITS ffff800011570000 01580000
00000000000718b4 0000000000000000 AX 0 0 4
[14] .exit.text PROGBITS ffff8000115e18b4 015f18b4
00000000000076f4 0000000000000000 AX 0 0 4
[15] .altinstructions PROGBITS ffff8000115e8fa8 015f8fa8
000000000003570c 0000000000000000 A 0 0 1
[16] .init.data PROGBITS ffff800011620000 01630000
0000000000093c8e 0000000000000000 WA 0 0 256
[17] .data..percpu PROGBITS ffff8000116b4000 016c4000
000000000000db98 0000000000000000 WA 0 0 64
[18] .hyp.data..percpu PROGBITS ffff8000116c2000 016d2000
0000000000000e20 0000000000000000 WA 0 0 16
[19] .rela.dyn RELA ffff8000116c2e20 016d2e20
000000000045f8e8 0000000000000018 A 0 0 8
[20] .data PROGBITS ffff800011b30000 01b40000
00000000002a4520 0000000000000000 WA 0 0 4096
[21] __bug_table PROGBITS ffff800011dd4520 01de4520
0000000000015e88 0000000000000000 WA 0 0 4
[22] .mmuoff.data[...] PROGBITS ffff800011dea800 01dfa800
0000000000000018 0000000000000000 WA 0 0 2048
[23] .mmuoff.data.read PROGBITS ffff800011deb000 01dfb000
0000000000000008 0000000000000000 WA 0 0 8
[24] .pecoff_edat[...] PROGBITS ffff800011deb008 01dfb008
00000000000001f8 0000000000000000 WA 0 0 1
[25] .bss NOBITS ffff800011dec000 01dfb200
0000000000080d34 0000000000000000 WA 0 0 4096
[26] .debug_aranges PROGBITS 0000000000000000 01dfb200
0000000000036d50 0000000000000000 0 0 16
[27] .debug_info PROGBITS 0000000000000000 01e31f50
000000000d24e71f 0000000000000000 0 0 1
[28] .debug_abbrev PROGBITS 0000000000000000 0f08066f
00000000006da629 0000000000000000 0 0 1
[29] .debug_line PROGBITS 0000000000000000 0f75ac98
00000000014a1c53 0000000000000000 0 0 1
[30] .debug_frame PROGBITS 0000000000000000 10bfc8f0
0000000000359b88 0000000000000000 0 0 8
[31] .debug_str PROGBITS 0000000000000000 10f56478
0000000000410681 0000000000000001 MS 0 0 1
[32] .debug_ranges PROGBITS 0000000000000000 11366b00
0000000000000220 0000000000000000 0 0 16
[33] .comment PROGBITS 0000000000000000 11366d20
0000000000000034 0000000000000001 MS 0 0 1
[34] .debug_loclists PROGBITS 0000000000000000 11366d54
0000000001bcb113 0000000000000000 0 0 1
[35] .debug_rnglists PROGBITS 0000000000000000 12f31e67
00000000003b5cb0 0000000000000000 0 0 1
[36] .debug_line_str PROGBITS 0000000000000000 132e7b17
0000000000036636 0000000000000001 MS 0 0 1
[37] .symtab SYMTAB 0000000000000000 1331e150
000000000043f620 0000000000000018 38 160120 8
[38] .strtab STRTAB 0000000000000000 1375d770
0000000000278dce 0000000000000000 0 0 1
[39] .shstrtab STRTAB 0000000000000000 139d653e
00000000000001bd 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000010000 0xffff800010000000 0xffff800010000000
0x0000000001deb200 0x0000000001e6cd34 RWE 0x10000
NOTE 0x000000000157aa10 0xffff80001156aa10 0xffff80001156aa10
0x000000000000003c 0x000000000000003c R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
Section to Segment mapping:
Segment Sections...
00 .head.text .text .got.plt .rodata .pci_fixup __ksymtab __ksymtab_gpl __ksymtab_strings __param __modver __ex_table .notes .init.text .exit.text .altinstructions .init.data .data..percpu .hyp.data..percpu .rela.dyn .data __bug_table .mmuoff.data.write .mmuoff.data.read .pecoff_edata_padding .bss
01 .notes
02
커널 이미지 (vmlinux
) 파일을
readelf --headers vmlinux
명령어로 읽은 결과를 출력한 것이다. 여러 섹션이 존재하는데 앞서 말했던 일부 정보들은 Image.gz
생성 중 삭제된다.
.notes
).comment
).head.text
).text
).init.text
).exit.text
).rodata
).init.data
).data..percpu
).hyp.data..percpu
).data
).bss
)앞서 얘기했던 것처럼 부트로더가 커널을 압축 해제하고 커널 이미지를 호출하는데 이 시작 주소는 물리 DRAM
의 2 MiB
단위로 정렬된 주소라면 어디에서든 동작할 수 있는 position independent
코드로 구성된다. 이 이미지의 시작 주소는 시스템마다 모두 다르다.
요약) 물리주소는 시스템마다 가변적. but, 2 MiB
로 정렬된 위치에 존재함. adrp
명령으로 확인 가능함.
v4.6
이전리눅스 커널 v4.6 이전까진 AArch64
의 실제 커널 시작 코드는 보안 문제로 TEXT_OFFSET
만큼 떨어져 있었다 . 0x...0000
이 시작주소가 아니고 여기에서 TEXT_OFFSET
만큼 더해야 실제 커널의 시작 주소로 접근이 가능하다.
v4.6
이후KASLR
(Kernel Address Sanitizer Location Randomization) 의 relocatable kernel
개념이 도입되면서 TEXT_OFFSET
에 의미가 사라졌고, v5.8-rc2
에서 완전히 제거되었다.
KASLR
(Kernel Address Sanitizer Location Randomization)Kernel Adress Sanitizer Location Randomization, (커널 주소 살균제 주소 무작위화???) 의 약자로 보안을 위해 커널 가상 주소 공간에서 커널 이미지 및 커널 모듈이 위치해 있는 곳을 알 수 없게 런타임 에 랜덤 배치한다.
KASAN
은 Kernel Address SANitizer
의 약자이다. 이후 KASAN
이란 용어가 나오면 이를 말하는 것이다.
Static
페이지 테이블커널이 컴파일 될 때 5 개의 페이지 테이블이 미리 생성된다. 기존에는 두 가지 페이지 테이블만 존재했으나 보안 목적으로 여러 테이블로 분리했다.
init_pg_dir
초기 부팅 시 사용되며 사용할 페이지 테이블의 단계와 개수를 컴파일 타임에 계산. 정규 맵핑(swapper_pg_dir
사용) 이전에 잠깐 만들어서 사용. paging_init()
후에 swapper_pg_dir
로 전환한 후 init_pg_dir
는 할당 해제한다.
swapper_pg_dir
보안을 위해 read-only
로 맵핑하여 사용하고, 커널의 정규 루틴만을 사용하여 변경 가능하도록 막아 놓았다. 따라서 외부에서 커널이 노출되더라도 쉽게 접근하여 사용이 불가능함.
Identity
매핑물리 주소 공간과 유저 가상 주소 공간 동일하도록 1대 1 로 매핑하는 것을 의미한다. MMU
(Memory Management Unit) 가 켜지는 순간, 물리 주소가 아닌 가상 주소를 사용하게 되는데 이 경우 문제가 발생할 수 있으므로, 동일한 주소에 1대 1 로 매핑하여 커널 코드가 이어서 정상적으로 동작할 수 있도록 만든다.
DRAM
이 너무 높은 주소 공간한다면?만일 DRAM 의 주소(예를 들어, 52-bit
) 가 너무 높고 유저 가상 주소가 너무 작은(예를 들어, 48-bit
) 경우에는 어떻게 할까? 이 경우에는 어쩔 수 없이 유저 가상 주소 공간(DRAM
의 가상 주소 공간 크기인 52-bit
로)을 키운다.
아주 기본적인 배경지식이고 더 정확한 내용은 이후에 자세히 정리한다.
[사이트] http://jake.dothome.co.kr/image1/
[사이트] https://ko.wikipedia.org/wiki/랜덤_액세스_메모리
[사이트] https://ko.wikipedia.org/wiki/SDRAM
[사이트] https://elinux.org/Kernel_XIP
[사이트] https://ko.wikipedia.org/wiki/디스크_이미지
[사이트] https://en.wikipedia.org/wiki/Boot_image
[사이트] https://en.wikipedia.org/wiki/Booting#Boot_device
[사이트] https://www.kernel.org/doc/html/latest/arm64/booting.html
[사이트] https://github.com/torvalds/linux/commit/a7f8de168ace487fa7b88cb154e413cf40e87fc6
[사이트] http://jake.dothome.co.kr/head-64-510/
[사이트] https://github.com/torvalds/linux/commit/120dc60d0bdbadcad7460222f74c9ed15cdeb73e
[책] 리눅스 커널 내부구조 (저자: 백승제, 최종무 저)