본문 바로가기
OS/만들면서 배우는 OS 커널의 구조와 원리

8-3. 페이징 실험

by 내용증명장인 2026. 4. 25.

앞서 배운 커널 구조들과 페이징을 구현하여 어떻게 소스코드가 구성되고 동작하는지를 알아보려한다.
앞서 배운 개념들에 대한 소스 코드는 거의 동일하고 페이징 기능 위주로 정리하였다.

init.inc

SysCodeSelector     equ 0x08
SysDataSelector     equ 0x10
VideoSelector       equ 0x18
TSSSelector         equ 0x20
UserCodeSelector    equ 0x28+3
UserDataSelector    equ 0x30+3

IDT_BASE            equ 0xC0001000
TSS_ESP0_WHERE      equ 0x90000

 

boot_paging.asm

%include "init.inc"

[org 0]
    jmp 07C0h:start

%include "a20.inc"

start:
    mov ax, cs
    mov ds, ax
    mov es, ax

    mov ax, 0xB800
    mov es, ax
    mov di, 0
    mov ax, word [msgBack]
    mov cx, 0x7FF

paint:
    mov word [es:di], ax
    add di, 2
    dec cx
    jnz paint

read:
    mov ax, 0x9000
    mov es, ax
    mov bx, 0

    mov ah, 2
    mov al, 2
    mov ch, 0
    mov cl, 2
    mov dh, 0
    mov dl, 0
    int 13h

    jc read
    
read_kernel:
    mov ax, 0x8000
    mov es, ax
    mov bx, 0

    mov ah, 2
    mov al, 7
    mov ch, 0
    mov cl, 4
    mov dh, 0
    mov dl, 0
    int 13h

    jc read_kernel

read_user1:
    mov ax, 0x7000
    mov es, ax
    mov bx, 0

    mov ah, 2
    mov al, 1
    mov ch, 0
    mov cl, 11
    mov dh, 0
    mov dl, 0
    int 13h

    jc read_user1

read_user2:
    mov ax, 0x7000
    mov es, ax
    mov bx, 0x200

    mov ah, 2
    mov al, 1
    mov ch, 0
    mov cl, 12
    mov dh, 0
    mov dl, 0
    int 13h

    jc read_user2

read_user3:
    mov ax, 0x7000
    mov es, ax
    mov bx, 0x400

    mov ah, 2
    mov al, 1
    mov ch, 0
    mov cl, 13
    mov dh, 0
    mov dl, 0
    int 13h

    jc read_user3

read_user4:
    mov ax, 0x7000
    mov es, ax
    mov bx, 0x600

    mov ah, 2
    mov al, 1
    mov ch, 0
    mov cl, 14
    mov dh, 0
    mov dl, 0
    int 13h

    jc read_user4

read_user5:
    mov ax, 0x7000
    mov es, ax
    mov bx, 0x800

    mov ah, 2
    mov al, 1
    mov ch, 0
    mov cl, 15
    mov dh, 0
    mov dl, 0
    int 13h

    jc read_user5

    mov dx, 0x3F2
    xor al, al
    out dx, al

    cli

    call a20_try_loop

    mov al, 0x11
    out 0x20, al
    dw 0x00eb, 0x00eb
    out 0xA0, al
    dw 0x00eb, 0x00eb

    mov al, 0x20
    out 0x21, al
    dw 0x00eb, 0x00eb
    mov al, 0x28
    out 0xA1, al
    dw 0x00eb, 0x00eb

    mov al, 0x04
    out 0x21, al
    dw 0x00eb, 0x00eb
    mov al, 0x02
    out 0xA1, al
    dw 0x00eb, 0x00eb

    mov al, 0x01
    out 0x21, al
    dw 0x00eb, 0x00eb
    out 0xA1, al
    dw 0x00eb, 0x00eb

    mov al, 0xFF
    out 0xA1, al
    dw 0x00eb, 0x00eb
    mov al, 0xFB
    out 0x21, al

    jmp 0x9000:0000

msgBack db '.', 0x67

times 510-($-$$) db 0
dw 0AA55h

 

a20.inc

; IF CPU is AMD ELAN
;
; mov al, 0x02
; out 0x92, al
; a20_elan_wait:
; call a20_test
; jz a20_elan_wait
; jmp a20_done
;

A20_TEST_LOOPS      equ 32
A20_ENABLE_LOOPS    equ 255
A20_TEST_ADDR       equ 4*0x80

a20_try_loop:
; Check the A20 gate exists on the computer

a20_none:
    call a20_test
    jnz a20_done

a20_bios:
    mov ax, 0x2401
    pushfd
    int 0x15
    popfd

    call a20_test
    jnz a20_done

a20_kbc:
    call empty_8042

    call a20_test
    jnz a20_done

    mov al, 0xD1
    out 0x64, al
    call empty_8042

    mov al, 0xDF
    out 0x60, al
    call empty_8042

a20_kbc_wait:
    xor cx, cx

a20_kbc_wait_loop:
    call a20_test
    jnz a20_done
    loop a20_kbc_wait_loop

a20_fast:
    in al, 0x92
    or al, 0x02
    and al, 0xFE
    out 0x92, al

a20_fast_wait:
    xor cx, cx

a20_fast_wait_loop:
    call a20_test
    jnz a20_done
    loop a20_fast_wait_loop

dec byte [a20_tries]
jnz a20_try_loop

a20_die:
    hlt
    jmp a20_die

a20_tries:
    db A20_ENABLE_LOOPS

a20_done:
    ret

a20_test:
    push cx
    push ax
    xor cx, cx
    mov fs, cx
    dec cx
    mov gs, cx
    mov cx, A20_TEST_LOOPS
    mov ax, word [fs:A20_TEST_ADDR]
    push ax

a20_test_wait:
    inc ax
    mov word [fs:A20_TEST_ADDR], ax
    call delay
    cmp ax, word [gs:A20_TEST_ADDR+0x10]
    loop a20_test_wait

    pop word [fs:A20_TEST_ADDR]
    pop ax
    pop cx
    ret

empty_8042:
    push ecx
    mov ecx, 100000

empty_8042_loop:
    dec ecx
    jz empty_8042_end_loop

    call delay

    in al, 0x64
    test al, 1
    jz no_output
    
    call delay
    in al, 0x60
    jmp empty_8042_loop
 
no_output:
    test al, 2
    jnz empty_8042_loop

empty_8042_end_loop:
    pop ecx
    ret

delay:
    out 0x80, al
    ret

 

setup.asm

%include "init.inc"

PAGE_DIR        equ 0x100000
PAGE_TAB_KERNEL equ 0x101000
PAGE_TAB_USER   equ 0x102000
PAGE_TAB_LOW    equ 0x103000

[org 0x90000]
[bits 16]

start:
    cld
    mov ax, cs
    mov ds, ax
    xor ax, ax
    mov ss, ax

    xor eax, eax
    lea eax, [tss]
    add eax, 0x90000
    mov [descriptor4+2], ax
    shr eax, 16
    mov [descriptor4+4], al
    mov [descriptor4+7], ah

    cli
    lgdt [gdtr]

    mov eax, cr0
    or eax, 0x00000001
    mov cr0, eax

    jmp $+2
    nop
    nop

    jmp dword SysCodeSelector:PM_Start

[bits 32]

PM_Start:
    mov bx, SysDataSelector
    mov ds, bx
    mov es, bx
    mov fs, bx
    mov gs, bx
    mov ss, bx

    lea esp, [PM_Start]

    mov esi, 0x80000
    mov edi, 0x200000
    mov cx, 512*7

kernel_copy:
    mov al, byte [ds:esi]
    mov byte [es:edi], al
    inc esi
    inc edi
    dec cx
    jnz kernel_copy

    mov esi, 0x70000
    mov edi, 0x300000
    mov cx, 512

user1_copy:
    mov al, byte [ds:esi]
    mov byte [es:edi], al
    inc esi
    inc edi
    dec cx
    jnz user1_copy

    mov esi, 0x70200
    mov edi, 0x301000
    mov cx, 512

user2_copy:
    mov al, byte [ds:esi]
    mov byte [es:edi], al
    inc esi
    inc edi
    dec cx
    jnz user2_copy

    mov esi, 0x70400
    mov edi, 0x302000
    mov cx, 512

user3_copy:
    mov al, byte [ds:esi]
    mov byte [es:edi], al
    inc esi
    inc edi
    dec cx
    jnz user3_copy

    mov esi, 0x70600
    mov edi, 0x303000
    mov cx, 512

user4_copy:
    mov al, byte [ds:esi]
    mov byte [es:edi], al
    inc esi
    inc edi
    dec cx
    jnz user4_copy

    mov esi, 0x70800
    mov edi, 0x304000
    mov cx, 512

user5_copy:
    mov al, byte [ds:esi]
    mov byte [es:edi], al
    inc esi
    inc edi
    dec cx
    jnz user5_copy

    mov edi, PAGE_DIR
    mov eax, 0
    mov ecx, 1024
    cld
    rep stosd

    mov edi, PAGE_DIR
    mov eax, 0x103000
    or eax, 0x01
    mov [es:edi], eax

    mov edi, PAGE_DIR+0x200*4
    mov eax, 0x102000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_DIR+0x300*4
    mov eax, 0x101000
    or eax, 0x01
    mov [es:edi], eax
    
    mov edi, PAGE_TAB_KERNEL
    mov eax, 0
    mov ecx, 1024
    cld
    rep stosd

    mov edi, PAGE_TAB_KERNEL+0x000*4
    mov eax, 0x200000
    or eax, 1
    mov [es:edi], eax

    mov edi, PAGE_TAB_KERNEL+0x001*4
    mov eax, 0x201000
    or eax, 1
    mov [es:edi], eax

    mov edi, PAGE_TAB_USER
    mov eax, 0x00
    mov ecx, 1024
    cld
    rep stosd

    mov edi, PAGE_TAB_USER+0x000*4
    mov eax, 0x300000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_TAB_USER+0x001*4
    mov eax, 0x301000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_TAB_USER+0x02*4
    mov eax, 0x302000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_TAB_USER+0x03*4
    mov eax, 0x303000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_TAB_USER+0x04*4
    mov eax, 0x304000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_TAB_LOW
    mov eax, 0x00000
    or eax, 0x01
    mov cx, 256

page_low_loop:
    mov [es:edi], eax
    add eax, 0x1000
    add edi, 4
    dec cx
    jnz page_low_loop

    lea eax, [tss_esp0]
    mov [TSS_ESP0_WHERE], eax

    mov eax, PAGE_DIR
    mov cr3, eax

    mov eax, cr0
    or eax, 0x80000000
    mov cr0, eax

    jmp 0xC0000000

;-----------------------DATA AREA------------------------
gdtr:
    dw gdt_end-gdt-1
    dd gdt

gdt:
    dd 0, 0
    dd 0x0000FFFF, 0x00CF9A00
    dd 0x0000FFFF, 0x00CF9200
    dd 0x8000FFFF, 0x0040920B

descriptor4:
    dw 104
    dw 0
    db 0
    db 0x89
    db 0
    db 0

    dd 0x0000FFFF, 0x00FCFA00
    dd 0x0000FFFF, 0x00FCF200
gdt_end:

tss:
    dw 0, 0

tss_esp0:
    dd 0
    dw SysDataSelector, 0
    dd 0
    dw 0, 0
    dd 0
    dw 0, 0
    dd 0x0100000

tss_eip:
    dd 0, 0
    dd 0, 0, 0, 0

tss_esp:
    dd 0, 0, 0, 0
    dw 0, 0
    dw 0, 0
    dw UserDataSelector, 0
    dw 0, 0
    dw 0, 0
    dw 0, 0
    dw 0, 0
    dw 0, 0

times 1024-($-$$) db 0

 

kernel_paging.asm

%include "init.inc"

[org 0xC0000000]
[bits 32]

    mov esp, 0xC0000FFF

%include "idt0.inc"

    lidt [idtr]

    mov al, 0xFC
    out 0x21, al
    sti

    mov ax, TSSSelector
    ltr ax

    mov eax, [CurrentTask]
    add eax, TaskList
    lea edx, [User1regs]
    mov [eax], edx
    add eax, 4
    lea edx, [User2regs]
    mov [eax], edx
    add eax, 4
    lea edx, [User3regs]
    mov [eax], edx
    add eax, 4
    lea edx, [User4regs]
    mov [eax], edx
    add eax, 4
    lea edx, [User5regs]
    mov [eax], edx
    add eax, 4

    mov eax, [CurrentTask]
    add eax, TaskList
    mov ebx, [eax]
    jmp sched

scheduler:
    lea esi, [esp]

    xor eax, eax
    mov eax, [CurrentTask]
    add eax, TaskList

    mov edi, [eax]

    mov ecx, 17
    rep movsd
    add esp, 68

    add dword [CurrentTask], 4
    mov eax, [NumTask]
    mov ebx, [CurrentTask]
    cmp eax, ebx
    jne yet
    mov byte [CurrentTask], 0

yet:
    xor eax, eax
    mov eax, [CurrentTask]
    add eax, TaskList
    mov ebx, [eax]

sched:
    mov eax, [TSS_ESP0_WHERE]
    mov [eax], esp

    lea esp, [ebx]

    popad
    pop ds
    pop es
    pop fs
    pop gs

    iretd

CurrentTask dd 0
NumTask dd 20
TaskList: times 5 dd 0

;------------------Subroutines--------------------
printf:
    push eax
    push es
    mov ax, VideoSelector
    mov es, ax

printf_loop:
    mov al, byte [esi]
    mov byte [es:edi], al
    inc edi
    mov byte [es:edi], 0x06
    inc esi
    inc edi
    or al, al
    jz printf_end
    jmp printf_loop

printf_end:
    pop es
    pop eax
    ret

; -------------------- Task Structure ---------------------

%include "user_task_structure.inc"

;---------------------- Interrupt Service Routines ----------------------------

%include "idt1.inc"

; ------------------------ IDT -------------------------
idtr:
    dw 256*8-1
    dd IDT_BASE

%include "idt2.inc"

times 512*7-($-$$) db 0

 

user_task_structure.inc

;----------------User1 Task_Structure-----------------
times 63 dd 0
User1Stack:
User1regs:
    dd 0, 0, 0, 0, 0, 0, 0, 0

    dw UserDataSelector, 0
    dw UserDataSelector, 0
    dw UserDataSelector, 0
    dw UserDataSelector, 0

    dd 0x80000000
    dw UserCodeSelector, 0
    dd 0x200
    dd 0x80000FFF
    dw UserDataSelector, 0

;----------------User2 Task_Structure-----------------
times 63 dd 0
User2Stack:
User2regs:
    dd 0, 0, 0, 0, 0, 0, 0, 0

    dw UserDataSelector, 0
    dw UserDataSelector, 0
    dw UserDataSelector, 0
    dw UserDataSelector, 0

    dd 0x80001000
    dw UserCodeSelector, 0
    dd 0x200
    dd 0x80001FFF
    dw UserDataSelector, 0

;----------------User3 Task_Structure-----------------
times 63 dd 0
User3Stack:
User3regs:
    dd 0, 0, 0, 0, 0, 0, 0, 0

    dw UserDataSelector, 0
    dw UserDataSelector, 0
    dw UserDataSelector, 0
    dw UserDataSelector, 0

    dd 0x80002000
    dw UserCodeSelector, 0
    dd 0x200
    dd 0x80002FFF
    dw UserDataSelector, 0

;----------------User4 Task_Structure-----------------
times 63 dd 0
User4Stack:
User4regs:
    dd 0, 0, 0, 0, 0, 0, 0, 0

    dw UserDataSelector, 0
    dw UserDataSelector, 0
    dw UserDataSelector, 0
    dw UserDataSelector, 0

    dd 0x80003000
    dw UserCodeSelector, 0
    dd 0x200
    dd 0x80003FFF
    dw UserDataSelector, 0

;----------------User5 Task_Structure-----------------
times 63 dd 0
User5Stack:
User5regs:
    dd 0, 0, 0, 0, 0, 0, 0, 0

    dw UserDataSelector, 0
    dw UserDataSelector, 0
    dw UserDataSelector, 0
    dw UserDataSelector, 0

    dd 0x80004000
    dw UserCodeSelector, 0
    dd 0x200
    dd 0x80004FFF
    dw UserDataSelector, 0

 

idt0.inc

    cld
    mov ax, SysDataSelector
    mov es, ax
    xor eax, eax
    xor ecx, ecx
    mov ax, 256
    mov edi, IDT_BASE

loop_idt:
    lea esi, [idt_ignore]
    mov cx, 8
    rep movsb
    dec ax
    jnz loop_idt

    mov edi, IDT_BASE+8*0x20
    lea esi, [idt_timer]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*0x21
    lea esi, [idt_keyboard]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*0x80
    lea esi, [idt_soft_int]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*0
    lea esi, [idt_exception00]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*1
    lea esi, [idt_exception01]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*2
    lea esi, [idt_exception02]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*3
    lea esi, [idt_exception03]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*4
    lea esi, [idt_exception04]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*5
    lea esi, [idt_exception05]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*6
    lea esi, [idt_exception06]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*7
    lea esi, [idt_exception07]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*8
    lea esi, [idt_exception08]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*9
    lea esi, [idt_exception09]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*10
    lea esi, [idt_exception10]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*11
    lea esi, [idt_exception11]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*12
    lea esi, [idt_exception12]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*13
    lea esi, [idt_exception13]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*14
    lea esi, [idt_exception14]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*15
    lea esi, [idt_exception15]
    mov cx, 8
    rep movsb

    mov edi, IDT_BASE+8*17
    lea esi, [idt_exception17]
    mov cx, 8
    rep movsb

 

idt1.inc

isr_ignore:
    push gs
    push fs
    push es
    push ds
    pushad

    mov ax, SysDataSelector
    mov DS, ax
    mov ES, ax
    mov FS, ax
    mov GS, ax

    mov al, 0x20
    out 0x20, al

    mov edi, (80*2*0)
    lea esi, [msg_isr_ignore]
    call printf
    inc byte [msg_isr_ignore]

    jmp ret_from_int

isr_32_timer:
    push gs
    push fs
    push es
    push ds
    pushad

    mov ax, SysDataSelector
    mov DS, ax
    mov ES, ax
    mov FS, ax
    mov GS, ax

    mov al, 0x20
    out 0x20, al

    mov edi, 80*2*0
    lea esi, [msg_isr_32_timer]
    call printf
    inc byte [msg_isr_32_timer]

    jmp ret_from_int

isr_33_keyboard:
    push gs
    push fs
    push es
    push ds
    pushad

    mov ax, SysDataSelector
    mov DS, ax
    mov ES, ax
    mov FS, ax
    mov GS, ax

    in al, 0x60

    mov al, 0x20
    out 0x20, al

    mov edi, (80*2*0)+(2*35)
    lea esi, [msg_isr_33_keyboard]
    call printf
    inc byte [msg_isr_33_keyboard]

    jmp ret_from_int

isr_128_soft_int:
    push gs
    push fs
    push es
    push ds
    pushad

    mov cx, SysDataSelector
    mov DS, cx
    mov ES, cx
    mov FS, cx
    mov GS, cx

    mov edi, eax
    lea esi, [ebx]

    call printf

    jmp ret_from_int

isr_00:
    mov edi, (80*12*2)
    lea esi, [msg_isr_00_zero_devide]
    call printf
    jmp $

isr_01:
    mov edi, (80*12*2)
    lea esi, [msg_isr_01_debug]
    call printf
    jmp $

isr_02:
    mov edi, (80*12*2)
    lea esi, [msg_isr_02_nmi]
    call printf
    jmp $

isr_03:
    mov edi, (80*12*2)
    lea esi, [msg_isr_03]
    call printf
    jmp $

isr_04:
    mov edi, (80*12*2)
    lea esi, [msg_isr_04_into]
    call printf
    jmp $

isr_05:
    mov edi, (80*12*2)
    lea esi, [msg_isr_05_bound]
    call printf
    jmp $

isr_06:
    mov edi, (80*12*2)
    lea esi, [msg_isr_06_opcode]
    call printf
    jmp $

isr_07:
    mov edi, (80*12*2)
    lea esi, [msg_isr_07_coprocess]
    call printf
    jmp $

isr_08:
    mov edi, (80*12*2)
    lea esi, [msg_isr_08_double_fault]
    call printf
    jmp $

isr_09:
    mov edi, (80*12*2)
    lea esi, [msg_isr_09_coprocess_segment]
    call printf
    jmp $

isr_10:
    mov edi, (80*12*2)
    lea esi, [msg_isr_10_bad_tss]
    call printf
    jmp $

isr_11:
    mov edi, (80*12*2)
    lea esi, [msg_isr_11_segment_not_present]
    call printf
    jmp $

isr_12:
    mov edi, (80*12*2)
    lea esi, [msg_isr_12_stack_fault]
    call printf
    jmp $

isr_13:
    mov edi, (80*12*2)
    lea esi, [msg_isr_13_gpf]
    call printf
    jmp $

isr_14:
    mov edi, (80*12*2)
    lea esi, [msg_isr_14_page_fault]
    call printf
    jmp $

isr_15:
    mov edi, (80*12*2)
    lea esi, [msg_isr_15_coprocessor_error]
    call printf
    jmp $

isr_17:
    mov edi, (80*12*2)
    lea esi, [msg_isr_17_alignment_check]
    call printf
    jmp $

ret_from_int:
    xor eax, eax
    mov eax, [esp+52]
    and eax, 0x00000003
    xor ebx, ebx
    mov bx, cs
    and ebx, 0x00000003
    cmp eax, ebx
    ja scheduler

    popad
    pop ds
    pop es
    pop fs
    pop gs

    iret

msg_isr_00_zero_devide db "Zero Devide", 0 
msg_isr_01_debug db "Debug", 0 
msg_isr_02_nmi db "NMI", 0 
msg_isr_03 db "int3", 0 
msg_isr_04_into db "INTO", 0 
msg_isr_05_bound db "Bound", 0
msg_isr_06_opcode db "Invalid Opcode", 0 
msg_isr_07_coprocess db "Coprocess Not Available", 0 
msg_isr_08_double_fault db "Double Fault", 0 
msg_isr_09_coprocess_segment db "Coprocess Segment Overrun", 0 
msg_isr_10_bad_tss db "Bad TSS", 0 
msg_isr_11_segment_not_present db "Segment Not Present", 0 
msg_isr_12_stack_fault db "Stack Fault", 0 
msg_isr_13_gpf db "GPF", 0 
msg_isr_14_page_fault db "Page Fault", 0 
msg_isr_15_coprocessor_error db "Coprocess Error" , 0 
msg_isr_17_alignment_check db "Alignment Check", 0 
msg_isr_ignore db "This is an ignorable interrupt", 0 
msg_isr_32_timer db ".This is the timer interrupt", 0 
msg_isr_33_keyboard db ".This is the keyboard interrupt", 0 
msg_isr_128_sofUnt db ".This is the soft_int interrupt", 0

 

idt2.inc

idt_exception00: 
    dw isr_00
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception01: 
    dw isr_01
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception02: 
    dw isr_02
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception03: 
    dw isr_03
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception04: 
    dw isr_04
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception05: 
    dw isr_05
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception06: 
    dw isr_06
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception07: 
    dw isr_07
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception08: 
    dw isr_08
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception09: 
    dw isr_09
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception10: 
    dw isr_10
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception11: 
    dw isr_11
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception12: 
    dw isr_12
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception13: 
    dw isr_13
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception14: 
    dw isr_14
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception15: 
    dw isr_15
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_exception17: 
    dw isr_17
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000

idt_ignore:
    dw isr_ignore
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000
idt_timer: 
    dw isr_32_timer 
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000
idt_keyboard: 
    dw isr_33_keyboard 
    dw 0x08
    db 0
    db 0x8E
    dw 0xC000
idt_soft_int: 
    dw isr_128_soft_int 
    dw 0x08
    db 0
    db 0xEF
    dw 0xC000

 

user_program1.asm

[org 0x80000000]
[bits 32]

user_process1:
    mov eax, 80*2*2+2*5
    lea ebx, [msg_user_process1_1]
    int 0x80
    mov eax, 80*2*3+2*5
    lea ebx, [msg_user_process1_2]
    int 0x80
    inc byte [msg_user_process1_2]
    jmp user_process1

msg_user_process1_1 db "User process1", 0
msg_user_process1_2 db ".I'm running now.", 0

times 512-($-$$) db 0

user_program2.asm

[org 0x80001000]
[bits 32]

user_process2:
    mov eax, 80*2*2+2*35
    lea ebx, [msg_user_process2_1]
    int 0x80
    mov eax, 80*2*3+2*35
    lea ebx, [msg_user_process2_2]
    int 0x80
    inc byte [msg_user_process2_2]
    jmp user_process2

msg_user_process2_1 db "User process2", 0
msg_user_process2_2 db ".I'm running now.", 0

times 512-($-$$) db 0

user_program3.asm

[org 0x80002000]
[bits 32]

user_process3:
    mov eax, 80*2*5+2*5
    lea ebx, [msg_user_process3_1]
    int 0x80
    mov eax, 80*2*6+2*5
    lea ebx, [msg_user_process3_2]
    int 0x80
    inc byte [msg_user_process3_2]
    jmp user_process3

msg_user_process3_1 db "User process3", 0
msg_user_process3_2 db ".I'm running now.", 0

times 512-($-$$) db 0

user_program4.asm

[org 0x80003000]
[bits 32]

user_process4:
    mov eax, 80*2*5+2*35
    lea ebx, [msg_user_process4_1]
    int 0x80
    mov eax, 80*2*6+2*35
    lea ebx, [msg_user_process4_2]
    int 0x80
    inc byte [msg_user_process4_2]
    jmp user_process4

msg_user_process4_1 db "User process4", 0
msg_user_process4_2 db ".I'm running now.", 0

times 512-($-$$) db 0

user_program5.asm

[org 0x80004000]
[bits 32]

user_process5:
    mov eax, 80*2*9+2*5
    lea ebx, [msg_user_process5_1]
    int 0x80
    mov eax, 80*2*10+2*5
    lea ebx, [msg_user_process5_2]
    int 0x80
    inc byte [msg_user_process5_2]
    jmp user_process5

msg_user_process5_1 db "User process5", 0
msg_user_process5_2 db ".I'm running now.", 0

times 512-($-$$) db 0

 

위 파일을 작성한 뒤 asm 확장자 파일을 컴파일하여 부팅하면 다음과 같은 화면이 출력된다.

위 파일에서 태스크나 비디오 위치 출력이 잘못된 것 같은데, 그냥 넘어갔다.


소스코드 분석

boot_paging 각 파일들은 다음과 같은 물리 메모리 주소에 로드된다고 명시된다.

  1. setup.bin : 0x90000
  2. kernel.bin(kernel_paging.asm) : 0x80000 (주소 값 이동과 페이징을 거쳐서 kernel_paging.asm 파일의 시작 주소는 다른 주소로 명시되어있다.)
  3. user_program
    • user_program1 : 0x70000
    • user_program2 : 0x70200
    • user_program3 : 0x70400
    • user_program4 : 0x70600
    • user_program5 : 0x70800

boot_paging에서 각 프로그램들을 메모리에 로드하고 jmp 0x9000:0000 명령어를 통해 setup.bin으로 점프하게 된다.


setup.bin

    mov esi, 0x80000
    mov edi, 0x200000
    mov cx, 512*7

kernel_copy:
    mov al, byte [ds:esi]
    mov byte [es:edi], al
    inc esi
    inc edi
    dec cx
    jnz kernel_copy

setup.bin에서 16비트 리얼모드에서 32비트 보호모드로 변환 후 kernel 주소인 0x80000번지부터 0xE00(512*7)만큼의 주소의 데이터를 0x200000번지부터 0xE00만큼의 주소로 옮긴다.
user 주소 5개도 마찬가지로 데이터를 옮긴다.

    mov edi, PAGE_DIR
    mov eax, 0
    mov ecx, 1024
    cld
    rep stosd

    mov edi, PAGE_DIR
    mov eax, 0x103000
    or eax, 0x01
    mov [es:edi], eax

    mov edi, PAGE_DIR+0x200*4
    mov eax, 0x102000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_DIR+0x300*4
    mov eax, 0x101000
    or eax, 0x01
    mov [es:edi], eax

해당 코드는 페이지 디렉토리 엔트리의 인덱스와 포인터 값을 정의하는 소스코드이다.
PAGE_DIR은 0x100000의 값을 가지고 있고, 0x100000번지부터 1024번을 4바이트씩 0으로 초기화한다.
rep stosd를 통해 ecx의 수만큼 eax의 값을 edi부터 4바이트씩 초기화 하게 된다.

이후 페이지 디렉토리의 0번째 엔트리 (PAGE_DIR)의 값으로 0x103000 + 1(커널)을 저장한다.
페이지 디렉토리의 512번째(0x200) 엔트리에는 0x102000 +7(유저)을 저장하고,
페이지 디렉토리의 768번째(0x300) 엔트리에는 0x101000 + 1(커널)을 저장한다.

위 코드로 인해 현재 페이지 디렉터리 엔트리 3개를 생성하였다.

  1. 하위 메모리
  2. 유저 영역
  3. 커널 영역

하위 메모리란?
CR3에 주소를 넣고 페이징이 활성화 되는 순간 가상 주소를 참조하게 되어 0번째 엔트리에 매핑된 포인터 주소를 찾아간다.
이 때 저장된 주소 값이 없으면 프로그램이 실행되지 않는다.

    mov edi, PAGE_TAB_KERNEL+0x000*4
    mov eax, 0x200000
    or eax, 1
    mov [es:edi], eax

    mov edi, PAGE_TAB_KERNEL+0x001*4
    mov eax, 0x201000
    or eax, 1
    mov [es:edi], eax

페이지 테이블을 매핑하는 코드이다.
PAGE_TAB_KERNEL은 0x101000이다. 페이지 디렉터리의 768번째(0x300) 엔트리를 타고 왔다.

첫 번째(0번) 페이지 테이블 엔트리의 포인터에는 0x200000 + 1 값이 저장되어있다.
두 번째(1번) 페이지 테이블 엔트리의 포인터에는 0x201000 + 1 값이 저장되어있다.

커널 레벨이기 때문에 1값이 더해졌으며 해당 페이지 테이블 엔트리를 통해 4KB 페이지로 이동한다.
0x200000은 커널 관련 4KB 페이지이고, 0x201000은 IDT 관련 4KB 페이지이다.

    mov edi, PAGE_TAB_USER+0x000*4
    mov eax, 0x300000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_TAB_USER+0x001*4
    mov eax, 0x301000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_TAB_USER+0x02*4
    mov eax, 0x302000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_TAB_USER+0x03*4
    mov eax, 0x303000
    or eax, 0x07
    mov [es:edi], eax

    mov edi, PAGE_TAB_USER+0x04*4
    mov eax, 0x304000
    or eax, 0x07
    mov [es:edi], eax

유저 테이블 페이지 엔트리의 포인터에는 0x300000부터 0x1000 단위씩  값들이 올라가면서 저장되어있다.
유저 권한이기 때문에 7을 더해준다.

    mov edi, PAGE_TAB_LOW
    mov eax, 0x00000
    or eax, 0x01
    mov cx, 256

page_low_loop:
    mov [es:edi], eax
    add eax, 0x1000
    add edi, 4
    dec cx
    jnz page_low_loop

PAGE_TAB_LOW는 0x103000 값을 가지고 있으며 이는 하위 메모리 관련 페이지 테이블 시작 주소이다.
0x103000에 0x00000+1을 저장하고 0x103000부터 4바이트 간격으로 0x1000바이트씩 0xFF000까지 매핑되게 된다.

커널 단계에서는 페이징 구조를 먼저 구축한 뒤, 물리 주소 배치한 후 가상 주소와 매핑하게 된다.
가상 주소는 페이지 디렉토리나 페이지 테이블의 엔트리 번호를 통해서 설정된다.

페이지 디렉토리에서 커널 부분 엔트리는 0x300번째이다.
0x300은 2진수로 표현하면 11 0000 0000이다.
선형주소의 10비트는 페이지 디렉토리 엔트리의 인덱스를 나타내므로 선형 주소로 표현하면
1100 0000 0000 0000 0000 0000 0000 0000, 즉, 0xC0000000이 된다.
kernel_paging.asm의 베이스 주소는 0xC0000000이다.

페이지 디렉토리에서 유저 부분 첫 번째 엔트리는 0X200번째이다.
0X200은 2진수로 표현하면 10 0000 0000이고 선형 주소의 상위 10비트에 해당한다.
이를 선형주소인 32비트로 표현하면 2진수 값은 1000 0000 0000 0000 0000 0000 0000 0000이며 0x80000000이다.
유저 부분 첫 번째 엔트리는 user_program1을 의미하고 페이지 테이블 하나의 크기는 4KB이다.
따라서 user_program1의 가상 주소는 0x80000000, user_program2의 가상 주소는 0x80001000이 된다.

다음은 소스 코드에서 페이지 디렉토리와 페이지 테이블의 구성도이다.

소스코드 페이징 구조

    mov eax, PAGE_DIR
    mov cr3, eax

    mov eax, cr0
    or eax, 0x80000000
    mov cr0, eax

    jmp 0xC0000000

cr3에 페이지 디렉토리 시작 주소가 저장된다.
그리고 기존 설정 값을 유지하기 위해 cr0 값과 0x80000000을 OR 연산한다.
cr0은 32비트이고 0x80000000는 2진수로 1000 0000 0000 0000 0000 0000 0000 0000이다.
31번째 비트는 PG라는 비트이고, 이 비트는 페이징 기능을 활성화 하는 비트이다.
이후 커널 페이지(0xC0000000)로 점프하게 된다.

user_program1 가상 주소와는 무관하다.

 

'OS > 만들면서 배우는 OS 커널의 구조와 원리' 카테고리의 다른 글

8-2. 페이징 - 페이징 구현  (0) 2026.04.23
8-1. 페이징 - A20 게이트  (0) 2026.04.21
7. 유저모드 Task Switching  (0) 2026.04.19
6. 보호  (0) 2026.04.13
5. 태스크 스위칭, 문맥 교환  (0) 2026.04.13