유저모드 Task Switching(유저모드와 콜 게이트)
아래 3개의 파일을 이용한다.
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 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
컴파일 후 플로피디스크에 삽입 시 아래 그림과 같은 화면이 출력된다.
출력 성공!
'OS 커널의 구조와 원리' 카테고리의 다른 글
6장 : 보호 (0) | 2015.06.09 |
---|---|
5장 : Task Switching(CALL 이용) (0) | 2015.05.08 |
5장 : Task Switching (0) | 2015.05.07 |
4장 : 인터럽트와 예외 - 4 (예외) (0) | 2015.05.06 |
4장 : 인터럽트와 예외 - 3(키보드 인터럽트 핸들러 구현) (0) | 2015.05.06 |