|
ncloader
0.1
|
Stage 2 for x86. 더 자세히 ...
x86에 대한 협력 다이어그램:모듈 | |
| Memory initializing for Protected Mode | |
| 보호모드 메모리 초기화 | |
| Interrupt Service Routines | |
| Interrupt Service Routine. | |
| Direct Memory Access | |
| DMA 콘트롤러를 제어하는 코드를 구현 | |
| Floppy Drive Controller | |
| 플로피 디스크 드라이버를 구현한다. | |
| Serial Controller | |
| 시리얼 접근을 위한 x86 전용 함수 인터페이스를 정의한다. | |
| Video Controller | |
| x86 전용 비디오 메모리 접근을 위한 함수 인터페이스를 정의한다. | |
| In/Out Port | |
| Port In/Out 기능을 위한 함수 인터페이스를 정의한다. | |
데이타 구조 | |
| struct | memmap |
| This structure has to be shared with nckernel. 더 자세히 ... | |
매크로 | |
| #define | LOW16(r) (((unsigned long)(r)) & 0x0000FFFF) |
| #define | HIGH16(r) (((unsigned long)(r)) >> 16) |
| #define | MAX_ENTRY 8 |
| #define | PRESENT 1 |
| #define | RING3 3 |
| #define | RING0 0 |
| #define | DESC_NULL 0x00 |
| #define | DESC_TSS 0x07 |
| #define | DESC_COUNT 0x08 |
| #define | TI(t) ((t) << 2) |
| #define | GDT 0 |
| #define | LDT 1 |
| #define | INITM_SIZE (1 << 20) |
| #define | TCB_SIZE 4096 |
| #define | ENV_SIZE 4096 |
| #define | KERNEL_SIZE (1048576 - ENV_SIZE) |
| #define | HEAP_SIZE 1048576 |
| #define | STACK_SIZE (16 << 10) |
| #define | BOOTM_IDX 0 |
| #define | SYSTEM_IDX 1 |
| #define | MMAP_SIZE(mmap) ((mmap)->tcb_size + (mmap)->env_size + (mmap)->kernel_size + (mmap)->initm_size + (mmap)->heap_size + (mmap)->stack_size) |
| #define | PANIC(fmt,...) |
| 시스템 오류 발생시 현재 CPU context 와 Stack 정보를 dump 하고 정지한다. 더 자세히 ... | |
함수 | |
| unsigned long _eoc | __attribute__ ((section(".eoc"))) |
| .eoc 섹션을 만들고, eoc 라는 변수를 그곳에 위치 시킨다. ldscript 에서 .eoc 섹션을 가장 마지막에 위치시킨다. eoc라는 변수는 .eoc 섹션 즉, 가장 마지막 섹션에 자리를 잡게 되고 메모리에 적재된 후에 eoc 변수의 주소가 우리가 사용 가능한 메모리 영역의 시작 주소가 된다. 더 자세히 ... | |
| int | arch_init (void) |
| [Stage 2 Build Interrupt Table] 더 자세히 ... | |
| unsigned long | arch_setup_env (struct memmap *map) |
| 사용가능한 메모리 정보 및 Architecture 정보를 환경변수 형태로 반환한다. 사용가능한 가장 큰 메모리 영역의 시작 주소에 환경변수 블록을 구성하고, 그 주소를 반환한다. 더 자세히 ... | |
| int | arch_init_thread_stack (struct thread *handle, unsigned int stack_size, void *(*e)(void *arg), void *arg) |
| 주어진 메모리 영역에 architecture 의존적인 task stack 을 구성한다. 더 자세히 ... | |
| struct thread * | arch_current_thread (void) |
| 현재 쓰레드의 구조체를 가져온다. 더 자세히 ... | |
| void | arch_switch_to_thread (struct thread *thread) |
| 지정된 쓰레드로 즉시 전환한다. 더 자세히 ... | |
| __NORET void | halt (void) |
| 시스템을 정지 시킨다. 더 자세히 ... | |
| int | drivers_init (void) |
| Drivers 초기화 함수 구현체는 각 포팅 레이어에서 구현한다. 더 자세히 ... | |
Stage 2 for x86.
각 Architecture 들은 여기에 정의된 함수들을 반드시 구현해야 한다.
architecture 의존적인 코드의 구현
Stage 2 에서는 CPU 를 Protected Mode 로 변경한 후 Kernel 로 실행권을 옮긴다.
Real Mode 와 Protected Mode 의 가장 큰 차이점은, Segment 를 재정의 할 수 있다는 것과, Interrupt Vector Table 을 재정의 할 수 있다는 것이다. 또한 Real Mode 는 16 Bits 이고 Protected Mode 는 32 Bits 이다.
물론, Real Mode 에서도 Segment Register 에 특정 주소값을 대입하는 방식으로 Segment 를 설정할 수 있었으나, Protected mode 와 같이 다양한 정보를 설정할 수 는 없었다.
이번 장에서는 Global Descriptor Table 과 Interrupt Vector Table 설정 방법에 대해 알아보고, FAT12 파일 시스템에서 커널을 읽어 Memory 로 적재 시키는 방법까지 알아 볼 것이다.
Disk I/O 를 하기 위해서, 간단한 Thread 를 정의하고, DMA Controller 제어 방법, FAT12 File system 의 구조, ELF 의 구조등에 대해서도 간략히 살펴 볼 것이다.
NCKernel 은 ELF Format 으로 Memory 에 임의 위치로 적재되면서 Relocate 될 것이다
Relocate 에 대해서는 ELF Format 에 대해 이야기를 하면서 좀 더 살펴보기로 한다.
Stage 2 는 Stage 1 과 같이 Executable File Format 을 가지지 않고 Flat binary 로 만들어진다. 컴파일과 Linking 과정을 거쳐 마지막으로 생성되는 Binary 의 가장 처음 부터 코드가 시작 된다. 이렇게 만들기 위해서, Object 파일은 ELF 형식으로 만들고 마지막에 Link 하는 과정에서 ELF Header 들을 모두 빼고, Flat Binary 형식으로 만든다.
Stage 2 에서 가장 처음 entry code (16 Bits) 가 오도록 하기 위해서 .init 섹션을 만들고, ld script 를 이용한다.
다음은 ld script 에서 Stage 2 가 적재될 메모리의 주소를 지정하고, 가장 처음 .init 섹션이 오도록 구성하는 부분이다.
entry 코드에서는 기본적인 dummy GDT 를 구성하고, MRD 를 얻어 메모리의 특정 위치에 저장한다. MRD 를 미리 저장해두는 이유는 BIOS 가 제공하는 Service Routine 을 사용하기 위해서 이다.
이렇게 MRD 와 dummy GDT 를 미리 준비한 다음에 Protected Mode 를 Enable 하고 32 Bit entry 로 jump 를 한다. 그리고 C 언어로 만들어진 함수로 진입을 한다.
32 Bit entry 로 진입한 다음에는 Segment Selector 와 Stack 을 구성하고, C 로 구현된 entry 로 이동한다. 여기 까지 진행 하면, C 언어를 이용해서 구현을 할 수 있는 단계까지 온 것이다.
한가지 앞에서 얘기 하지 않은 것이 있는데, 16 bits 와 32 bits 코드를 혼재 시키는 방법이다.
우리는 nasm 을 사용하고 있기 때문에, nasm 에서 설명하고 있는 방식으로 다음과 같이 한다.
32 Bits 초기화를 마친 후에 ncloader_entry 함수가 호출 될 때 까지, Interrupt 는 Disable 된 상태이다. 이제는 Interrupt 를 Enable 하기 위해서 IDT Table 을 구성하고, Interrupt Service Routine 일부를 구현한다. 물론 가장 먼저 Memory 관리자도 초기화 한다.
Memory Allocator 를 Initialize 하기 위해서 우리는 앞에서 미리 준비해 두었던 MRD 를 이용한다. 앞서 우리는 MRD 를 0x500 위치에 저장해 뒀었다. MRD 에서 최초 1 MB 영역을 찾아서 Code 가 저장된 다음 위치 부터를 Memory Allocator 가 관리할 수 있도록 한다.
Memory Allocator 가 초기화 된 후에는 interrupt table 을 준비하고,
PIC 도 초기화 한다.
PIC 에 대한 자세한 사항은 아래를 참조한다.
stage2 loader 에서 각 Architecture 별 초기화를 위해 arch_init 및 아래에 정으된 함수들을 호출한다.
해당 함수들은 각 architecture 별로 같은 함수 이름으로 구현이 되며, Build 시에 선택된 architecture 코드들이 빌드에 포함된다.
| Address | Description |
|---|---|
| Low address (High - SIZE:4MB) | ENV[4KB] + KERNEL[~1MB] |
| initm[ENV:initm] | |
| heap | |
| stack | |
| High address | Reserved for TCB[4KB] |
| #define BOOTM_IDX 0 |
| #define DESC_COUNT 0x08 |
| #define DESC_NULL 0x00 |
| #define DESC_TSS 0x07 |
| #define ENV_SIZE 4096 |
| #define GDT 0 |
| #define HEAP_SIZE 1048576 |
| #define HIGH16 | ( | r) | (((unsigned long)(r)) >> 16) |
| #define INITM_SIZE (1 << 20) |
| #define KERNEL_SIZE (1048576 - ENV_SIZE) |
| #define LDT 1 |
| #define LOW16 | ( | r) | (((unsigned long)(r)) & 0x0000FFFF) |
| #define MAX_ENTRY 8 |
| #define MMAP_SIZE | ( | mmap) | ((mmap)->tcb_size + (mmap)->env_size + (mmap)->kernel_size + (mmap)->initm_size + (mmap)->heap_size + (mmap)->stack_size) |
| #define PANIC | ( | fmt, | |
| ... | |||
| ) |
시스템 오류 발생시 현재 CPU context 와 Stack 정보를 dump 하고 정지한다.
N/A
| [in] | fmt | Formatted string |
| [in] | ... | Variable arguments |
| #define PRESENT 1 |
| #define RING0 0 |
| #define RING3 3 |
| #define STACK_SIZE (16 << 10) |
| #define SYSTEM_IDX 1 |
| #define TCB_SIZE 4096 |
| #define TI | ( | t) | ((t) << 2) |
| unsigned long _eoc __attribute__ | ( | (section(".eoc")) | ) |
.eoc 섹션을 만들고, eoc 라는 변수를 그곳에 위치 시킨다. ldscript 에서 .eoc 섹션을 가장 마지막에 위치시킨다. eoc라는 변수는 .eoc 섹션 즉, 가장 마지막 섹션에 자리를 잡게 되고 메모리에 적재된 후에 eoc 변수의 주소가 우리가 사용 가능한 메모리 영역의 시작 주소가 된다.
TODO: 원래는 __ld_end 라는 keyword 를 ldscript 에서 PROVIDER keyword 를 이용해 export 시킨 후 C program 에서 접근, 사용하려고 했는데, Flat binary 에서는 동작하지 않는 것으로 보여서 이 방법으로 바꿨다 정확한 것은 확인이 필요하다.
| struct thread * arch_current_thread | ( | void | ) |
현재 쓰레드의 구조체를 가져온다.
N/A
이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:
이 함수를 호출하는 함수들에 대한 그래프입니다.:| int arch_init | ( | void | ) |
[Stage 2 Build Interrupt Table]
Architecture 초기화 함수 구현체는 각 포팅 레이어에서 구현한다.
N/A
[Stage 2 Initialize Memory Allocator]
[Stage 2 Initialize Memory Allocator]
이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:
이 함수를 호출하는 함수들에 대한 그래프입니다.:| int arch_init_thread_stack | ( | struct thread * | handle, |
| unsigned int | stack_size, | ||
| void *(*)(void *arg) | e, | ||
| void * | arg | ||
| ) |
주어진 메모리 영역에 architecture 의존적인 task stack 을 구성한다.
N/A
| [in] | handle | 쓰레드 핸들러 |
| [in] | stack_size | 할당된 메모리 크기, handle 의 저장 공간은 직접 빼야함 |
| [in] | e | 엔트리 함수 |
| [in] | arg | 엔트리 함수에 전달 될 인자 |
이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:
이 함수를 호출하는 함수들에 대한 그래프입니다.:| unsigned long arch_setup_env | ( | struct memmap * | map) |
사용가능한 메모리 정보 및 Architecture 정보를 환경변수 형태로 반환한다. 사용가능한 가장 큰 메모리 영역의 시작 주소에 환경변수 블록을 구성하고, 그 주소를 반환한다.
블럭의 가장 처음에 unsigned long type 으로 구성된 블럭의 크기를 기록한다.
N/A
| [out] | map | Information for building memory map |
시스템 구성을 위해 사용한 상위 영역을 커널이 실제 사용 가능한 페이지 프레임에서 제외 시키기 위해, 전체 크기에서 뺀다. 커널은 메모리 관련 정보를 받을 때, 이미 할당된 영역들에 대한 정보를 제하고 받게 되므로, 커널에서 Page frame 을 관리하기 위해 buddy allocating system 을 초기화 할 때 이 영역 에 대해서는 신경을 쓰지 않아도 된다.
이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:| void arch_switch_to_thread | ( | struct thread * | thread) |
지정된 쓰레드로 즉시 전환한다.
N/A
| [in] | thread | 전환 할 대상 쓰레드 |
이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:
이 함수를 호출하는 함수들에 대한 그래프입니다.:| int drivers_init | ( | void | ) |
Drivers 초기화 함수 구현체는 각 포팅 레이어에서 구현한다.
N/A
이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:| void halt | ( | void | ) |
시스템을 정지 시킨다.
N/A
이 함수를 호출하는 함수들에 대한 그래프입니다.: