유저모드 Task Switching(유저모드와 콜 게이트)

아래 3개의 파일을 이용한다.

init.inc

SysCodeSelector equ 0x08

SysDataSelector equ 0x10

VideoSelector equ 0x18

TSSSelector equ 0x20

UserCodeSelector equ 0x28+3        ; 유저모드의 코드 세그먼트 디스크립터는 GDT의 첫번째에서 0x28만큼                                         떨어져 있음. 그리고 셀렉터의 PRL 필드에 2진수 11(10진수 3)을                                           추가하여 이 세그먼트가 유저 영역이라는 표시를 해야한다. 이를 위                                                 해 3을 더하였으며 더하기를 하는 것은 OR 연산과 같은 의미이다. 아래                                                   는 세그먼트 디스크립터의 모양이다.

0x28 -> 40바이트 -> 2진수로 101000, +3을 통해 RPL 값 지정
NULL 디스크립터를 포함하여 6번째 위치
TI가 0 이므로 이 세그먼트 디스크립
터는 GDT에 존재


UserDataSelector equ 0x30+3        유저모드의 데이터 세그먼트 디스크립터는 GDT의 맨 첫번째번지에서
                                        0x30만큼 떨어져 있다. 여기에도 마찬가지로 유저모드 라는 표시를 하                                         기 위해 +3을 해준다.




 boot.asm

%include "init.inc"

[org 0]
jmp 07C0h:start
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, 0x1000 ; ES:BX = 1000:1000
mov es, ax
mov bx, 0
mov ah, 2 ; 디스크에 있는 데이터를 es:bx의 주소로
mov al, 2 ; 2섹터를 읽을 것이다.
mov ch, 0 ; 0번째 Cylinder
mov cl, 2 ; 2번째 섹터부터 읽기 시작
mov dh, 0 ; Head = 0
mov dl, 0 ; Drive=0, A: 드라이브
int 13h ; read
jc read ; 에러가 나면 다시함
mov dx, 0x3F2 ; 플로피디스크 드라이브의
xor al, al ; 모터를 끈다.
out dx, al

cli
mov al, 0x11 ; PIC의 초기화
out 0x20, al ; 마스터 PIC
dw 0x00eb, 0x00eb ; jmp $+2, jmp $+2
out 0xA0, al ; 슬레이브 PIC
dw 0x00eb, 0x00eb
mov al, 0x20 ; 마스터 PIC 인터럽트 시작점
out 0x21, al
dw 0x00eb, 0x00eb
mov al, 0x28 ; 슬레이브 PIC 인터럽트 시작점
out 0xA1, al
dw 0x00eb, 0x00eb
mov al, 0x04 ; 마스터 PIC에 IRQ 2번에
out 0x21, al ; 스렐이브 PIC이 연결되어 있다.
dw 0x00eb, 0x00eb
mov al, 0x02 ; 스렐이브 PIC이 마스터 PIC의
out 0xA1, al ; IRQ 2번에 연결되어 있다.
dw 0x00eb, 0x00eb
mov al, 0x01 ; 8086 모드를 사용한다.
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 0x1000:0000
msgBack db '.', 0x67

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

kernel.asm

%include "init.inc"


[org 0x10000]

[bits 16]


start:

cld

mov ax, cs

mov ds, ax

xor ax, ax

mov ss, ax

xor eax, eax

lea eax, [tss] ; EAX에 tss의 물리 주소를 넣는다.

add eax, 0x10000

mov [descriptor4+2], ax

shr eax, 16

mov [descriptor4+4], al

mov [descriptor4+7], ah

; 커널 영역에 들어와 실행할 커널 함수의 오프셋을 콜게이트 디스크립터에 적재하는 일을 한다.
 
이 부분은 Protected Mode로 들어오기 전 16비트 코드로 구성되어 있으므로 org 0x10000의
 origin 선언이 적용되지 않으므로 printf 함수의 오프셋에 0x10000을 더한다. 그 후 콜게이트
 디스크립터의 알맞은 필드에 넣는다.

xor eax, eax

lea eax, [printf] ; EAX에 printf의 물리 주소를 넣는다.

add eax, 0x10000

mov [descriptor7], ax

shr eax, 16

mov [descriptor7+6], al

mov [descriptor7+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

; Protected Mode로 들어온 후 스택을 임의의 값으로 지정한다.

lea esp, [PM_Start]    ; PM_Start 번지를 ESP 레지스터에 넣어 커널이 사용하는 스택으로 사용
                             스택에 저장할 때 마이너스 방향으로 진행되므로 PM_Start 보다 작은 쪽 사용
                            PM_Start 번지보다 작은 쪽은 프로그램이 실행되고 더 이상 사용되지 않                                  는 루틴이 존재하므로 스택 영역으로 사용해도 무방하다.


; GDT에 등록된 TSSSelector를 TR레지스터에 적재하는 것을 통해 TSS영역을 사용하도록 한다.
       TSS영역은 한개만 사용한다.(여기서만.. 다음 장에서는 각 태스크마다 TSS처럼 모든 레지스터를 저장해
       두는 메모리 영역을 두어 수동으로 관리하는 방법이 있음)

mov ax, TSSSelector

ltr ax

; ESP에 있는 값을 다시 TSS영역에 있는 tss_esp0의 주소에 넣는다. 이렇게 넣어두면 유저 모드 태스크에서 콜게이트를 거치며 특권 레벨이 변할 때 tss_esp0에 있던 값이 esp 레지스터로 들어가면서 커널 레벨에서 이 값으로 스택을 사용하게 된다.

mov [tss_esp0], esp ; 특권 레벨 0의 스택을 TSS에 지정해 둔다.

lea eax, [PM_Start-256]          ; PM_Start-256의 값을 tss_esp에 넣음. 이 값은 유저모드 태스크에서 사
                                 용하는 스택의 주소
이다.

mov [tss_esp], eax ; 특권 레벨 3의 스택을 TSS에 지정해 둔다.

; 유저모드로의 태스크 스위칭 루틴
 Protected Mode로 들어와 TR 레지스터와 TSS 영역 등을 세팅한 후 유저 모드인 것처럼 가장하여 IRET 명령을 통해 유저모드의 태스크를 실행시킨다.

mov ax, UserDataSelector ; 데이터 세그먼트를 유저 모드로 지정해 둔다.

mov ds, ax

mov es, ax

mov fs, ax

mov gs, ax

; ESP 레지스터에 PM_Start-256 값을 넣어 유저모드에서 사용할 스택 주소를 넣어둔다.

lea esp, [PM_Start-256]

; 현재 ESP에는 커널모드에서 사용하는 스택 주소가 들어있으며, 이곳에 올 때 까지 PUSH 명령을 사용하지 않았으므로 ESP에 있는 값은 PM_Start 번지 일 것이다. 이 스택에 아래 PUSH 명령을 통해 유저모드의 SS, ESP, EFLAGS, CS, EIP를 차례로 저장하고 IRET 한다.

push dword UserDataSelector ; SS

push esp ; ESP

push dword 0x200 ; EFLAGS

push dword UserCodeSelector ; CS

lea eax, [user_process]               ; EIP에 들어갈 값으로 user_process 함수의 오프셋을 지정

push eax ; EIP

iretd ; 유저 모드 태스크로 점프

printf:

mov ebp, esp                ; esp의 값을 ebp에 복사

push es

push eax

mov ax, VideoSelector       ; VideoSelector를 es에 넣는다.

mov es, ax

mov esi, [ebp+8]

mov edi, [ebp+12]

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 eax

pop es

ret        ; ret 시 jmp $ 명령문이 있는 곳을 가리키게 된다. 그리고 그 자리에서 무한루프를 돈다.

; 유저모드의 태스크가 실행되면 이곳 user_process부터 진행된다.

user_process:

mov edi, 80*2*7

push edi ; 인수를 유저 모드 스택에 저장한다.

lea eax, [msg_user_parameter1]

push eax

call 0x38:0     ; 콜게이트를 통하여 커널 루틴을 호출, 커널모드의 함수 printf로 들어가게 된다.

jmp $        ; 무한루프 부분


msg_user_parameter1 db "This is User Parameter1", 0



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

; 유저모드용 세그먼트 영역을 지정하는 디스크립터 추가(이 디스크립터의 셀렉터 번호는 init 파일에 존재)

dd 0x0000FFFF, 0x00FCFA00 ; 유저 코드 세그먼트

dd 0x0000FFFF, 0x00FCF200 ; 유저 데이터 세그먼트

; GDT에 콜게이트 디스크립터를 만들어 둔다.

descriptor7:

dw 0                    ; 오프셋 0, 이 값은 Protected Mode로 들어오기 전에 설정한다.

dw SysCodeSelector

db 0x02                ; 인수는 2개

db 0xEC

db 0

db 0

gdt_end:


tss:

dw 0, 0 ; 이전 태스크로의 back link

tss_esp0:

dd 0 ; ESP0

dw SysDataSelector, 0 ; SS0, 사용 안함

dd 0 ; ESP1

dw 0, 0 ; SS1, 사용 안함

dd 0 ; ESP2

dw 0, 0 ; SS2, 사용 안함

dd 0


tss_eip:

dd 0, 0 ; EIP, EFLAGS

dd 0, 0, 0, 0

tss_esp:

dd 0, 0, 0, 0 ; ESP, EBP, ESI, EDI

dw 0, 0 ; ES, 사용안함

dw 0, 0 ; CS, 사용안함

dw UserDataSelector, 0 ; SS, 사용안함

dw 0, 0 ; DS, 사용안함

dw 0, 0 ; FS, 사용안함

dw 0, 0 ; GS, 사용안함

dw 0, 0 ; LDT, 사용안함

dw 0, 0 ; 디버그용 T 비트, IO 허가 비트맵

times 1024-($-$$) db 0


컴파일 후 플로피디스크에 삽입 시 아래 그림과 같은 화면이 출력된다.



출력 성공!


+ Recent posts