ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Pintos] 로딩 (Loading) - Loader, Real mode, Protected mode, A20 gate, Page Table
    프로젝트/Pintos 2021. 3. 19. 13:48

    로딩

    로딩은 Pintos 의 Loader 의 실행과 kernel initialization 작업을 포함한다.

     

    1. Loader

    여기서 로딩은 pintos 의 첫 실행시 진행되는 부팅의 과정을 나타낸다. 부팅이란 컴퓨터가 구동하여 기초적인 초기화 작업을 수행하고 운영 체제를 읽어오는 일련의 작업을 말한다. 로더(Loader) 는 이러한 부팅의 작업을 진행하는 프로그램으로 장치(Disk) 의 첫 번째 트랙의 첫 번째 섹터에 저장되어 있다. Pintos 에서 로딩의 대략적인 순서는 아래와 같다. (로더의 코드는 <threads/loader.S> 에 구현되어 있다.)

     

    1. BIOS 가 로더(Loader) 를 메모리에 로드(Load) 한다.

    • BIOS 는 컴퓨터가 구동된 후 저장 장치의 첫 번째 섹터(Sector) 를 탐색하여 이 장치가 부팅 가능한지 검사한다.
      • Sector : 저장 장치(Disk) 에서 데이터를 저장하는 최소 단위로, 한 섹터의 크기는 512 bytes 이다.
    • 이 첫 번째 섹터를 MBR(Master Boot Record) 라고 하고, 부팅 가능한 장치임을 나타내기 위해서는 섹터의 마지막 2 bytes 가 0xAA55 (Magic Number) 의 값을 가져야 한다. 이러한 검사 과정은 아래와 같이 구현되어 있다.

    <thread/loader.S>, line 261

    • 부팅 가능한 장치임이 판명되면 BIOS 는 이 첫번재 섹터를 읽어서 메모리의 0x7c00 위치에 올려놓고 작업을 시작한다.

    2. 로더가 디스크에서 커널(Kernel) 을 찾아 메모리의 0x20000 위치에 로드한다.

    3. 커널의 entry point 로 이동한다. (kernel 의 ELF header 가 entry point 의 위치를 포함하고 있다.)

     

    2. Low-Level Kernel Initialization

    로더는 control 을 kernel 의 entry point 로 옮긴다.  이것은 start 라는 위치로 표현되어 있으며, <thread/start.S> 에 구현되어 있다.

    <thread/loader.S> line 168

    start() 코드의 역할은 CPU 를 16-bit 의 리얼모드(real mode) 에서 32-bit 보호모드(protected mode) 로 바꿔주고 main() 함수를 부르는 역할을 한다.

     

     

    Real mode     vs    Protected mode  ?

    real mode 란 컴퓨터가 기동되고 바로 동작하는 모드로 loader 의 코드는 real mode 에서 동작한다. 특징은 프로그램이 동작할 때 physical memory 에 직접 프로그램이 통째로 올라간다. 이러한 특성상 여러가지 단점이 존재한다. 첫 번째, physical memory 에는 한 번에 한 개의 프로그램만 적재 가능하기 때문에 한 번에 한 개의 프로그램밖에 실행되지 못한다. 두 번째, physical memory 에 올라간 프로그램이 종료되지 않으면 인터럽트나 시스템 종료를 통하지 않고는 다른 프로그램의 실행이 불가능하다. 마지막으로 physical memory 에 직접 접근하므로 응용 프로그램에서 시스템 코드로의 접근이 용이하고 이는 보안에 취약하고 시스템 손상을 유발한다.

     

    이러한 이유로 pintos 를 포함하여 최근 대부분의 OS 는 보호모드로 동작한다.

     

    protected mode 란 각 프로세스마다 보조기억장치에 각자의 가상 메모리(Virtual memory) 영역을 할당받는다(32bit system 의 경우 각 4GB). 이 영역은 각자가 real mode 인 것 처럼 동작하고 페이징 기법(Paging) 을 통하여 실제 필요한 부분만 physical memory 에 올라와서 실행되기 때문에 real mode 의 단점들을 보완한다. 실제 필요한 부분만 physical memory 에 올라와서 실행되기 때문에 여러 프로그램을 동시에 실행이 가능하고 응용 프로그램에서 시스템 코드로의 접근을 제한해 시스템 손상을 막는다.

     

     

    start() 코드의 전체적인 실행 과정은 아래와 같다.

      1. PC's memory size 를 얻어 init_ram_pages (in <threads/loader.h>)에 저장한다.

    • Memory size 를 얻기 위해서 interrupt를 사용하며, interrupt 번호는 0x15, 함수번호는 0x88 번이다.
    • 인터럽트 함수를 통해 반환되는 값은 (Physical Memory 의 실제 크기, 단위 KB) - 1024KB 이고, 로더는 여기에 1024KB 를 더해 실제 Physical Memory 의 크기를 얻는다.
    • 아직 16-bit 의 리얼모드(real mode) 에서 동작하므로 BIOS 는 최대 64MB(2^16 KB) 만을 탐색할 수 있기 때문에 pintos 에서 사용하는 Physical Memory 의 크기는 최대 64MB 로 제한된다.

     

      2. A20 line 을 활성화한다. (CPU's address line numbered 20)

    • A20 line 이란?
    • x86 기반의 CPU 의 numbered 20 address bus line 에 있는 and gate 를 말한다.
    • 20bit 에서 32bit 아키텍처로 넘어가는 기점에서 원래의 20bit 를 기반으로 구현된 프로그램을 실행할 때 아키텍처 차이로 인한 오버플로우를 막고 하위호환을 위하여 20번째 address bus line 에 and gate 를 달아서 20bit 아키텍처에서와 같은 동작을 하도록 만든 gate 이다.

    이미지 출처 - https://b.luavis.kr/os/a20

    • ex. A20 line 이 0 으로 초기화 되어 있는 상태에서 0x100000-0x1fffff, 0x300000-0x3fffff 는 각각 0x000000-0x0fffff, 0x200000-0x2fffff 로 매핑됨.
    • pintos 에서 1MB(2^20) 이상의 메모리에 접근하기 위하여 A20 line 을 1로 활성화 시켜 주어야 한다.

      3. 메모리 초기화

    • Page Table 이란?
      • 페이지(Page) 와 프레임(Frame)
      • OS 는 physical memory 와 virtual memory 의 관리를 용이하게 하기 위해 각 메모리를 같은 크기를 가진 단위 블록으로 나누어 관리한다. physical memory 를 구성하는 단위 블록을 Frame, virtual memory 를 구성하는 단위 블록은 Page 라고 한다. virtual memory 에 있는 page 들은 실제 실행될 때 physical memory 의 frame 을 할당받아 physical memory 에 위치하게 된다.(frame과 page 는 같은 크기로 관리되며 x86 아키텍처에서 하나의 page, frame 의 크기는 4KB 이다.)
      • Page 들을 관리하기 위한 테이블을 Page Table 이라고 하고, 이 Page Table 은 하나의 프로세스마다 하나씩 존재한다.
      • Page directoryPage Directory Entry(PDE) 들로 구성된 데이터이다. 각 PDE 는 하나의 Page Table 의 첫 주소, 즉 Page Table 의 포인터를 정보로 가지고 있다.
      • Page table 도 마찬가지로 Page Table Entry(PTE) 들로 구성된다. 각 PTE 는 하나의 Page 의 첫 주소, 즉 Page 의 포인터를 정보로 가진다.

    이미지 출처 -&nbsp;https://www.sciencedirect.com/topics/computer-science/page-directory-table

    • Pintos 의 Table 구조
      • Pintos 는 4KB 크기의 Page directory 를 하나 가지고 있다.
      • 32bit 아키텍처에서 하나의 주소 값은 4B(32bit) 를 차지하므로 4KB 의 Page directory 는 1024(4KB/4B = 1K = 1024)개의 PDE 를 가진다.
      • 하나의 Page table 은 4KB 의 크기를 가지고, Page directory 와 마찬가지로 1024개의 PTE 를 가진다.
      • 각 Page 는 4KB 의 크기를 가진다.
      • 이를 통해 pintos 가 32bit 의 Linear address 로 표현할 수 있는 Virtual Memory 의 크기는 1024(PDE) * 1024(PTE) * 4KB(Page) = 2^20 * 4KB = 1MB * 4KB = 4GB 라는 것을 알 수 있다. 즉, pintos 의 각 process 는 4GB 의 virtual memory 공간을 갖는다.
    • Basic Page Table Mapping
      • 보호모드로 전환된 후의 접근가능한 Virtual Memory 의 크기는 4GB 이고, pintos 는 이 중에서 0xc0000000-0xffffffff(3GB-4GB) 의 1GB 공간을 커널 영역으로 사용한다. (이 커널의 시작 위치는 LOADER_PHYS_BASE(0xc0000000) 라는 값으로 <thread/loader.h> 에 정의되어 있다.)
      • 기본적인 Page Table 을 만들어, 현재 Physical Memory address 를 Virtual Memory 의 0-64MB 에 그대로 mapping한다.
        • 기본적인 Page Table 을 구성하기 위한 과정이다. 리얼모드에서는 64MB 이상의 Virtual address 에 접근이 불가능하기 때문에 이 과정이 우선되어야 한다.
      • 동일한 Physical Memory address 를 Virtual Memory 의 LOADER_PHYS_BASE 에 mapping 한다.
        • 보호모드로 바뀐 후에는 Physical Memory 의 0-64MB 의 공간에 직접 접근해서 사용하는 것이 불가하므로 LOADER_PHYS_BASE 를 더한 커널 영역의 Virtual address 에 접근하여 사용하게 하기 위함이다.

     

      4. 모드를 리얼모드에서 보호모드로 변경한 후 main() 함수를 call한다.

     

        * 부팅 과정에서 Physical Memory 에 올라오는 값들은 아래 그림에 자세히 설명되어 있다.

     

    3. High-Level Kernel Initialization

    호출된 main() 함수는 실제 커널의 초기화와 수행을 맡는 코드로, 주로 pintos 의 여러 모듈의 초기화 함수 호출로 이루어져 있으며, <thread/init.c> 에 구현되어 있다. 모듈 호출 및 내용은 아래 순서별로 정리한다.

    1. bss_init() : 커널의 "BSS" 를 초기화한다.
      • BSS : bss(block started symbol) segment 를 뜻하고 이는 초기화 되지 않은 전역 변수와 정적 변수가 저장되는 공간이다.
      • 보통 메모리는 5개의 세그먼트로 나누어 지는데, 가장 낮은 주소에서부터 텍스트 세그먼트, 데이터 세그먼트, bss 세그먼트, 힙 세그먼트, 스택 세그먼트 의 5종류이다.
      • 텍스트 세그먼트는 코드(Code) 세그먼트라고도 하며 프로그램의 기계어 코드가 저장되어 읽기만 가능하고 쓰기는 불가능하다.
      • 데이터 세그먼트는 초기화가 된 전역 변수와 정적 변수가 저장되는 공간이다. (정적 변수란 static 키워드를 통해 선언된 변수를 말함)
      • 힙 세그먼트 : 사용자가 직접 할당한 메모리가 저장되는 영역. malloc() 함수를 통해 할당 가능하다.
      • 스택 세그먼트 : 지역 변수와 각종 정보들이 저장되는 영역으로 저장되는 방향은 힙 세그먼트와 반대로 주소의 높은 곳부터 낮은 곳으로 저장된다.
    2. read_command_line() : kernel command line 을 읽어와서 arguments 로 나눈다.
    3. parse_options() : command line 에서 options 을 읽어온다.
    4. thread_init() : 쓰레드 시스템을 초기화한다.
    5. console_init() : 콘솔 초기화 -> 콘솔 초기화 이후에는 printf() 를 사용 가능하다.
    6. 커널의 메모리 시스템 초기화
      • palloc_init() : page allocator 설정
      • malloc_init() : 사용자 메모리 할당(malloc 함수) 이 가능하게 설정
      • paging_init() : loader.S 에서 구성했던 페이지 테이블을 다시 구성한다.
    7. tss_init() : tss(task state segment) 를 설정한다. 이는 커널이 task 를 관리할 때 필요한 정보가 들어있는 segment이다.
    8. gdt_init() : gdt(global description table) 을 초기화한다. 마찬가지로 커널이 task 를 관리할 때 필요한 정보가 들어있다.
    9. 인터럽트 초기화
      • intr_init() : IDT(Interrupt Descriptor Table) 를 초기화한다. 이 table 은 인터럽트를 handling 하는 handler 함수들이  연결되는 table이다.
      • timer_init(), kbd_init() : 타이머, 키보드 인터럽트 초기화
      • input_init() : input 모듈 초기화
      • exception_init(), syscall_init() : 예외처리 인터럽트, system call 인터럽트 초기화
    10. thread_start() : 우선 가장 실행 우선순위가 낮은 idle 이라는 thread 를 생성하여 동작시키고 인터럽트를 활성화시킨다.
    11. serial_init_queue() : serial 로부터 인터럽트를 받아 커널을 제어할 수 있도록 한다.
    12. timer_calibrate() : 정확한 시간 측정을 위해 timer 를 보정한다.
    13. ide_init() : IDE disk 를 초기화한다.
    14. filesys_init() : 파일시스템을 초기화한다.

     

     

     

     

     

    참조

    Pintos Documentation : web.stanford.edu/class/cs140/projects/pintos/pintos.pdf

    Luavis'Dev Story - A20 gate : b.luavis.kr/os/a20

    Science Direct - Page Directory Table : www.sciencedirect.com/topics/computer-science/page-directory-table

     

     

     

     

Designed by Tistory.