The Kernel Boot Process

origin site : http://duartes.org/gustavo/blog/post/kernel-boot-process


지난 How computers boot up 에서 이어지는 내용임.


번역이 되는 문서에서 참고하고 있는 Kernel 의 version 은 2.6.25.6 이며 Linux Cross Reference site(2.6.25.6) 에서 source 를 Web 상으로 볼 수 있다. 


Intel x86 boot 에서 real-mode 시점에서 진행해보면 그 시점에는 1MB 의 메모리 접근만 가능하며 이에 대한 대략적인 그림은 아래와 같다.


I confirmed to re-use of image with gustavo-web@duartes.org, If you want to see a original image, just click this image

Ram contents after boot loader is done.


Kernel image 는 bootloader 에서 BIOS disk I/O service 를 이용하여 memory 로 loading 된다. (결국 bootloader 입장에서 올리는 것임.) 예를 들면 /boot/vmlinux-2.6.22-14-server 와 같은 image를 hard disk 에서 읽어온 것을 메모리로 복사본을 올려놓은 것이다.  이 image는 두 개의 조각으로 구분되는데, real-mode kernel code 를 포함하는 작은 영역은 640KB 하위에 loading 되며 다른 큰 part 의 kernel은(protected-mode 동작) 메모리 1MB 이후에 loading된다.


위 그림의 real-mode kernel header 위의 것들은 Linux boot protocol 로 kernel과 bootloader사이에 약속 같은 것을 해두고 memory 영역을 사용한다. 특정 영역은 bootloader가 동작 중에 loading 되는 것이며, human-readable 한 kernel version string을 포함한다. 또한 real-mode kernel 의 중요한 정보인 size 도 포함한다. Boot loader는 boot menu 에서 사용자로 부터 받은 parameter들을 command line 메모리 영역에 써주기도 한다. 일단 boot loader 일이 끝나면 kernel header 에 요구되는 parameter들을 특정역역에 채워준다. 그런 다음 kernel entry point로 jump 하면 boot loader는 일을 마친 것이라고 볼 수 있다. 아래의 diagram은 Kernel 초기화 과정의 흐름을 보여주는 것이다. 이것 또한 2.6.25.6 기반으로 작성된 것임을 다시 확인 한다.


I confirmed to re-use of image with gustavo-web@duartes.org, If you want to see a original image, just click this image

Architecture-specific Linux Kernel Initialization


Intel architecture 를 위한 early kernel start-up code 는  arch/x86/boot/header.S 에 있다. header.S는 assembly 언어로 되어 있는데 kernel을 위한 code라기 보다는 boot 를 위한 것이라고 보면 된다. 실제 이 파일은 boot sector code를 포함하고 있으며, 나머지는 boot loader없이 동작할 수 있는 linux code라고 보면 된다. 요즘 boot sector code 가 만약 실행이 된다면 "bugger_off_msg" 만 user와 reboot시 출력하도록 한다.(거의 안쓰인다는 얘긴가?). 그리고 요즘 boot loader는 이 legacy code를 무시한다. boot sector 위의 real-mode kernel header(15byte) 갖고 있다. 


"Ram contents after boot loader is done." 의 이미지에서 맨 하위 512byte 이후에(offset 0x200) linux kernel 의 일부를 수행하는 첫 instruction 을 발견할 수 있다.(real-mode entry point). 이는 header.S:110 에 있고 0x3aeb 의 machine code가 있는 곳으로 jump 한다. jump 이 후에 진행되는 과정은 start_of_setup label로 다시 jump 하는 것이며 이는 header.S:229 에 있다. 이 routine은 stack과 bss(0으로 초기화되는) segment를 설정하고 이제는 C code 인 arch/x86/boot/main.c:122 로 jump한다.


main() 은 memory layout을 검사하고 video mode를 설정하는 등과 같은 일을 한다. 그리고 go_to_protected_mode()를 호출한다. 그렇지만 CPU 가 protected mode로 전환하기 전에 아직 할일이 조금 남아는 있다. 두 가지 main issue 가 있는데 그 것은 interrupt와 memory 이다. real-mode에 있는 interrupt vector table은 항상 memory 0번지에 위치하는 반면에 protected mode에서는 interrupt vector table의 위치는 CPU의 register 중 하나인 IDTR에 저장되어 있다. 그래서 하나의 program에서 사용하는 logical memory address가 linear memory address로 변경되는 것이 real-mode와 protected-mode 가 서로 다르다. protected mode 에서는 GDTR register에 메모리를 위한 global descriptor table의 주소의 기반으로 addressing을 한다. 그래서 go_to_protected_mode() 에서는 setup_ide()setup_gdt() 함수 호출로 임시적으로(?) interrupt vector table과 global descriptor table을 설치한다. 


우리는 이제 protected mode로 protected_mode_jump 호출로 진입하게 된다. 이 routine은 CR0 CPU register에 PE bit 을 설정하여 protected mode 가 enable되었다는 것을 확인한다. 지금 시점에서는 paging 이 disable 상태이다. 사실 protected mode 라도 당장 paging을 enable하여 설정할 필요는 없다. 당장 급한 것은 real-mode 에서 제한된 640KB barrier를 해제하고 4G Ram을 사용할 수 있도록 하는 것이다. 이 것을 위해 32-bit kernel entry point를 호출 해야 하며 이것은 compressed kernel 을 위한 startup_32 함수이다. (아직 압축된 상태의 kernel이며 향후 자기 스스로가 압축을 해제하고 사용가능하도록 수행한다) 이 startup_32는 기본적인 register를 초기화하며 decompressed_kernel() 을 호출하여 실제 압축해제를 수행하게 된다. 


decompress_kernel()은 "Decompressing Linux..." 라는 익숙한 message를 출력할 것이다. 압축해제는 압축된 이미지가 있던 그자리에서 바로 실행되며 overwrite 하게 된다. 그러므로 압축해제된 kernel image는 압축된 image가 loading된 1MB(address 임)와 동일하다. decompress_kernel() 이 압축해제가 끝나면 "done."이라는 메세지를 출력하고 "Booting the kernel" 을 바로 출력하여 이제서야 정말 커널의 제기능을 실행하는 것이다. 


startup_32는 assembly 로 작성되어 있는데 이것은 32-bit mode 초기화를 포함한다. protected-mode kernel을 위한 bss segment를 초기화 하고 최종적으로 메모리를 위한 global descriptor table 설정과 paging이 정상동작 할 수 있도록 page table 을 설치한다. 그 후 paging을 사용하도록 설정하고 stack 을 초기화, interrupt descritptor table을 만드는 작업을 완료 후에  architecture independent 한 kernel code start-up code 인 start_kernel()로 jump 한다. 

아래의 diagram을 살펴 보자.



I confirmed to re-use of image with gustavo-web@duartes.org, If you want to see a original image, just click this image

Architecture-independent Linux Kernel Initialization


start_kernel() 은 전형적인 kernel code 처럼 보인다. 이것은 거의 C 언어로 작성되어 있으며 machine에 비종속적 code들이 대부분이다. 이 함수는 다양한 kernel subsystem과 data structure를 초기화 한다. (scheduler, memory zones, time keeping 등) start_kernel()은 rest_init() 함수를 호출 하는데, entry point로써 kernel_init() 함수를 thread로 만들어주는 역활을 한다. 또한 rest_init()은 schedule() 함수를 호출하여 task scheduling을 시작하며 cpu_idle() 호출로 sleep 상태로 변경된다. cpu_idle()은 system이 죽을때 까지 수행되며 process 0 가 된다. 


이 idle loop은 reset vector ==> BIOS ==> MBR ==> boot loader ==> real-mode kernel ==> protected mode kernel 이후에 boot processor의 종착지 이다. <번역이 이부분 부터는 어려움이 많다.> 결국 PID 0 가 cpu_idle() 로 빠지기 전에 thread 를 하나 만들고 kernel_init()을 수행하게 되어 multi-core 나 multi-processor system의 나머지 CPU를 초기화하는 역활을 한다는 것이다. kernel_init() 에서 다른 CPU를 살리기 전에는 하나의 CPU에서 이 모든 설명의 것을 다 수행한 것이며 이를 Boot processor라 부른다. 다른 CPU는 application processor라고 하는데 이는 real-mode 내에서 준비가 되며 몇가지 초기화를 거친다. startup_32와 같은 기존 boot processor가 거친 내용과 common하게 사용된다. (물론 application processor를 위한 약간의 다른 작업은 있다.) 마지막으로 kernel_init()은 init_post()라는 함수를 호출 하고 이는 user mode process  인 /sbin/init, /etc/init, /bin/init 과 /bin/sh 를 실행한다. 만약 이모든 것이 실패하면 kernel 은 panic을 출력하고 그만 둔다. init process는 PID 1을 가지고 다양한 process 를 수행하게 된다. (X11 Windows, logging program(console), network demon등) 


여기까지 kernel boot 에서 init process가 실행되기 전까지 보여주는 내용의 글을 번역한 것이다. 중간 중간에 번역이 자연스럽게 되지 않아 넘어간 부분이 있으며, 차후 ARM boot-up을 볼 때 이 글을 기반으로 자세히 써볼까 한다. 


번역된 blog의 글에는 마지막에 Window booting 과정도 있으나 번역하지 않았으며 보고 싶으신 분은 원문의 블로그로 들어가서 보시면 되겠다. 이상



+ Recent posts