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 threadarch_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 에 대해 이야기를 하면서 좀 더 살펴보기로 한다.

주의
ELF Image 를 메모리에 적재하고 실행 시킬 때, 임의의 위치에 ELF Image 를 올려도 동작 가능한 image 를 Relocatable image 라고 하며, Relocate 를 하기 위해서 ELF Format 을 채택한다.
stage2_init.png
Initialization sequence of the stage 2

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 를 이용한다.

bits 16
SECTION .init
; This label is not for exporting, Just I want to get rid of warnings of ld.
GLOBAL entry
entry:

다음은 ld script 에서 Stage 2 가 적재될 메모리의 주소를 지정하고, 가장 처음 .init 섹션이 오도록 구성하는 부분이다.

OUTPUT_FORMAT("binary")
ENTRY(entry)
phys = 0x0700;
SECTIONS
{
.text phys : AT(phys) {
(.init)
(.text)
. = ALIGN(4096);
} = 0x0
...

entry 코드에서는 기본적인 dummy GDT 를 구성하고, MRD 를 얻어 메모리의 특정 위치에 저장한다. MRD 를 미리 저장해두는 이유는 BIOS 가 제공하는 Service Routine 을 사용하기 위해서 이다.

; Get the memory from the ACPI (E820h) services
mov dword [KERNEL_ARGUMENT], 0
xor ebx, ebx ; Continuation
mov es, ebx ; ES
mov edi, KERNEL_ARGUMENT + 4 ; DI (ES:DI) location of storing data
mov ecx, 20 ; Buffer size, 20 bytes
mov edx, 0x534d4150 ; Signature
e820_get_desc:
mov eax, 0x0000E820 ; function ID
clc ; Clear carry flag
int 0x15 ; Invoke interrupt 0x15
jc e820_error ; Check the carry flag
cmp eax, 0x534d4150 ; Check the signature
jne e820_error
cmp ecx, 20 ; Check the minimum size of descriptor
jl no_e820
add edi, ecx ; Move pointer to next
cmp edi, E820_BOUND ; Compare the boundary of memory for descriptors
jge e820_overflow
inc dword [KERNEL_ARGUMENT]
cmp ebx, 0 ; Check the continuation value
jne e820_get_desc

이렇게 MRD 와 dummy GDT 를 미리 준비한 다음에 Protected Mode 를 Enable 하고 32 Bit entry 로 jump 를 한다. 그리고 C 언어로 만들어진 함수로 진입을 한다.

; Load global descriptor table
cli
lgdt [gdt_ptr]
; Change the CPU mode to protected
mov eax, cr0
or al, 1 ; Toggle PE bit
mov cr0, eax

32 Bit entry 로 진입한 다음에는 Segment Selector 와 Stack 을 구성하고, C 로 구현된 entry 로 이동한다. 여기 까지 진행 하면, C 언어를 이용해서 구현을 할 수 있는 단계까지 온 것이다.

SECTION .text
GLOBAL entry32
extern ncloader_entry
entry32:
mov eax, kernel_ds
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov eax, kernel_ss
mov ss, eax
mov esp, KTHREAD_MAIN_ESP ; I have to initialize the stack pointer
mov ebp, esp ; Base stack pointer
jmp ncloader_entry

한가지 앞에서 얘기 하지 않은 것이 있는데, 16 bits 와 32 bits 코드를 혼재 시키는 방법이다.

우리는 nasm 을 사용하고 있기 때문에, nasm 에서 설명하고 있는 방식으로 다음과 같이 한다.

; Now we got the code descripor and its selector,
; so we use it now through FAR jump

32 Bits 초기화를 마친 후에 ncloader_entry 함수가 호출 될 때 까지, Interrupt 는 Disable 된 상태이다. 이제는 Interrupt 를 Enable 하기 위해서 IDT Table 을 구성하고, Interrupt Service Routine 일부를 구현한다. 물론 가장 먼저 Memory 관리자도 초기화 한다.

eoc = (unsigned long)&_eoc;
mrd_init((void *)KERNEL_ARGUMENT);
for (ret = 0; ret < mrd_count(); ret++) {
if (mrd_get_type(ret) != MRD_MEMORY) {
continue;
}
base = (unsigned long)mrd_get_base(ret);
size = mrd_get_size(ret);
if (base + size <= eoc) {
continue;
}
if (base < eoc) {
size -= (eoc - base);
base = eoc;
}
if (!(memory_mask & MEMORY_BOOTM)) {
memory_info[BOOTM_IDX].base = base;
memory_info[BOOTM_IDX].size = size;
memory_mask |= MEMORY_BOOTM;
} else if (!(memory_mask & MEMORY_SYSTEM)) {
memory_info[SYSTEM_IDX].base = base;
memory_info[SYSTEM_IDX].size = size;
memory_mask |= MEMORY_SYSTEM;
}
if ((memory_mask & (MEMORY_BOOTM | MEMORY_SYSTEM)) == (MEMORY_BOOTM | MEMORY_SYSTEM)) {
break;
}
}
if (memory_mask & MEMORY_BOOTM) {
bootm_init(memory_info[BOOTM_IDX].base, memory_info[BOOTM_IDX].size);
if (model_init() < 0) {
// Failed to initialize the model
halt();
}
}
if (memory_mask & MEMORY_SYSTEM) {
struct page_allocator *handle;
void *info;
unsigned int info_size;
info_size = page_allocator_info_size(memory_info[SYSTEM_IDX].size);
info = malloc(info_size);
if (!info) {
// Failed to allocator information block
halt();
}
handle = page_allocator_init(info, (void *)memory_info[SYSTEM_IDX].base, memory_info[SYSTEM_IDX].size);
if (!handle) {
// Failed to initialize the page allocator
halt();
}
if (model_register("page,allocator", handle) < 0) {
// Failed to register the handle of page allocator
halt();
}
}

Memory Allocator 를 Initialize 하기 위해서 우리는 앞에서 미리 준비해 두었던 MRD 를 이용한다. 앞서 우리는 MRD 를 0x500 위치에 저장해 뒀었다. MRD 에서 최초 1 MB 영역을 찾아서 Code 가 저장된 다음 위치 부터를 Memory Allocator 가 관리할 수 있도록 한다.

Memory Allocator 가 초기화 된 후에는 interrupt table 을 준비하고,

static inline void build_idt(void)
{
struct interrupt_gate *idt;
struct idtr idtr;
idtr.limit = (sizeof(*idt) << 8) - 1;
idt = malloc((unsigned long)idtr.limit + 1);
if (!idt) {
return;
}
memset(idt, 0, (unsigned long)idtr.limit + 1);
idtr.base = (unsigned long)idt;
install_isr(idt + IRQ_NR_DEBUG, TRAP, isr_debug_entry);
install_isr(idt + IRQ_NR_OVERFLOW, TRAP, isr_overflow_entry);
install_isr(idt + IRQ_NR_BOUND, TRAP, isr_bound_entry);
install_isr(idt + IRQ_NR_MATH, TRAP, isr_math_fault_entry);
install_isr(idt + IRQ_NR_TIMER, INTERRUPT, isr_timer_entry);
install_isr(idt + IRQ_NR_IRQ2, INTERRUPT, isr_irq2_entry);
install_isr(idt + 0x25, INTERRUPT, isr_dummy_entry);
install_isr(idt + IRQ_NR_FDC, INTERRUPT, isr_fdc_entry);
install_isr(idt + 0x27, INTERRUPT, isr_dummy_entry);
install_isr(idt + IRQ_NR_RTC, INTERRUPT, isr_rtc_entry);
install_isr(idt + 0x2A, INTERRUPT, isr_dummy_entry);
install_isr(idt + 0x2B, INTERRUPT, isr_dummy_entry);
install_isr(idt + 0x2C, INTERRUPT, isr_dummy_entry);
install_isr(idt + 0x2D, INTERRUPT, isr_dummy_entry);
install_isr(idt + IRQ_NR_HDC, INTERRUPT, isr_hdc_entry);
install_isr(idt + 0x2F, INTERRUPT, isr_dummy_entry);
asm volatile ("lidt (%0)"::"a"(&idtr));
}

PIC 도 초기화 한다.

PIC 에 대한 자세한 사항은 아래를 참조한다.

stage2_x86_isr_pic

stage2_x86_isr

날짜
2011-8-8
작성자
Sung-jae Park nices.nosp@m.j@ni.nosp@m.cesj..nosp@m.com
Sung-jae Park nices.nosp@m.j.pa.nosp@m.rk@sa.nosp@m.msun.nosp@m.g.com
날짜
2011-7-22

stage2 loader 에서 각 Architecture 별 초기화를 위해 arch_init 및 아래에 정으된 함수들을 호출한다.

해당 함수들은 각 architecture 별로 같은 함수 이름으로 구현이 되며, Build 시에 선택된 architecture 코드들이 빌드에 포함된다.

AddressDescription
Low address (High - SIZE:4MB)ENV[4KB] + KERNEL[~1MB]
initm[ENV:initm]
heap
stack
High addressReserved 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,
  ... 
)
값:
do { \
printf("[%s:%d] " fmt, __func__, __LINE__, ##__VA_ARGS__); \
halt(); \
} while (0)

시스템 오류 발생시 현재 CPU context 와 Stack 정보를 dump 하고 정지한다.

N/A

Remarks
N/A
매개변수
[in]fmtFormatted 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

Remarks
N/A
반환값
struct thread *쓰레드 구조체
참고
thread_create()
task_pool_alloc()
arch_init_thread_stack()

+ 이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:

+ 이 함수를 호출하는 함수들에 대한 그래프입니다.:

int arch_init ( void  )

[Stage 2 Build Interrupt Table]

Architecture 초기화 함수 구현체는 각 포팅 레이어에서 구현한다.

N/A

Remarks
N/A
반환값
int 성공하면 0, 실패하면 에러코드

[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

Remarks
N/A
매개변수
[in]handle쓰레드 핸들러
[in]stack_size할당된 메모리 크기, handle 의 저장 공간은 직접 빼야함
[in]e엔트리 함수
[in]arg엔트리 함수에 전달 될 인자
반환값
int 성공시 0, 실패시 에러코드
참고
thread_create()
주의
스택은 거꾸로 자란다.
스택은 거꾸로 자란다.

+ 이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:

+ 이 함수를 호출하는 함수들에 대한 그래프입니다.:

unsigned long arch_setup_env ( struct memmap map)

사용가능한 메모리 정보 및 Architecture 정보를 환경변수 형태로 반환한다. 사용가능한 가장 큰 메모리 영역의 시작 주소에 환경변수 블록을 구성하고, 그 주소를 반환한다.

   블럭의 가장 처음에 unsigned long type 으로 구성된 블럭의 크기를
   기록한다.

N/A

Remarks
N/A
매개변수
[out]mapInformation for building memory map
반환값
int 성공하면, 할당된 env 크기를 반환, 실패시 0

시스템 구성을 위해 사용한 상위 영역을 커널이 실제 사용 가능한 페이지 프레임에서 제외 시키기 위해, 전체 크기에서 뺀다. 커널은 메모리 관련 정보를 받을 때, 이미 할당된 영역들에 대한 정보를 제하고 받게 되므로, 커널에서 Page frame 을 관리하기 위해 buddy allocating system 을 초기화 할 때 이 영역 에 대해서는 신경을 쓰지 않아도 된다.

+ 이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:

void arch_switch_to_thread ( struct thread thread)

지정된 쓰레드로 즉시 전환한다.

N/A

Remarks
N/A
매개변수
[in]thread전환 할 대상 쓰레드
반환값
void 없음
참고
thread_create()

+ 이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:

+ 이 함수를 호출하는 함수들에 대한 그래프입니다.:

int drivers_init ( void  )

Drivers 초기화 함수 구현체는 각 포팅 레이어에서 구현한다.

N/A

Remarks
N/A
반환값
int 성공하면 0, 실패하면 에러코드

+ 이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:

void halt ( void  )

시스템을 정지 시킨다.

N/A

Remarks
N/A
반환값
void 없음

+ 이 함수를 호출하는 함수들에 대한 그래프입니다.: