%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]
add eax, 0x10000
mov [descriptor4+2], ax
shr eax, 16
mov [descriptor4+4], al
mov [descriptor4+7], ah ; TSS 디스크립터 설정
xor eax, eax
lea eax, [printf]
add eax, 0x10000
mov [descriptor7], ax
shr eax, 16
mov [descriptor7+6], al
mov [descriptor7+7], ah ; 콜게이트 설정
cli
lgdt[gdtr] ; GDT 등록
mov eax, cr0
or eax, 0x00000001
mov cr0, eax
jmp $+2
nop
nop
jmp dword SysCodeSelector:PM_Start ; 간다 Protected Mode
[bits 32]
times 80 dd 0 ; 스택 영역
PM_Start:
mov bx, SysDataSelector
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
mov ss, bx
lea esp, [PM_Start]
cld
mov ax, SysDataSelector
mov es, ax
xor eax, eax
xor ecx, ecx
mov ax, 256 ; IDT 영역에 256개의 빈 디스크립터 복사
mov edi, 0
loop_idt:
lea esi, [idt_ignore]
mov cx, 8 ; 디스크립터 하나가 8바이트이다.
rep movsb
dec ax
jnz loop_idt
mov edi, 8*0x20 ; 타이머 IDT 디스크립터 복사
lea esi, [idt_timer]
mov cx, 8
rep movsb
mov edi, 8*0x21 ; 키보드 IDT 디스크립터 복사
lea esi, [idt_keyboard]
mov cx, 8
rep movsb
mov edi, 8*0x80 ; 트랩 IDT 디스크립터(소프트웨어 인터럽트)
lea esi, [idt_soft_int]
mov cx, 8
rep movsb
lidt [idtr] ; IDT 등록
mov al, 0xfc ; 막아두었던 인터럽트 중(부트스트랩에서 막아둠)
out 0x21, al ; 타이머와 키보드만 유효하게
sti
mov ax, TSSSelector
ltr ax
mov eax, [CurrentTask] ; Task Struct 리스트를 만든다.
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
mov eax, [CurrentTask] ; 첫번째 Task 선택
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 [tss_esp0], esp ; 커널 영역의 스택 주소를 TSS에 기입
lea esp, [ebx] ; EBX에는 다음 태스크의 정보가 있다.
popad ; 레지스터 복원
pop ds
pop es
pop fs
pop gs ; 세그먼트들 복원
iret ; 다음 Task로 분기
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
;UserProcess routine
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 Process", 0
msg_user_process1_2 db ".I'm running now", 0
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
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
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
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
;data area
gdtr:
dw gdt_end-gdt-1
dd gdt
gdt:
dd 0, 0
dd 0x0000ffff, 0x00cf9a00
dd 0x0000ffff, 0x00cf9200
dd 0x8000ffff, 0x0040920b
descriptor4: ; TSS 디스크립터
dw 104
dw 0
db 0
db 0x89
db 0
db 0
dd 0x0000ffff, 0x00fcfa00 ; 유저 코드 세그먼트
dd 0x0000ffff, 0x00fcf200 ; 유저 데이터 세그먼트
descriptor7: ; 콜게이트 디스크립터
dw 0
dw SysCodeSelector
db 0x02
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 0, 0 ; SS,사용 안함
dw 0, 0 ; DS, 사용 안함
dw 0, 0 ; FS, 사용 안함
dw 0, 0 ; GS, 사용 안함
dw 0, 0 ; LDT, 사용 안함
dw 0, 0 ; 디버그용 T비트 IO 허가 비트맵
;; TSS 구조 참고
; user1 task structure
times 63 dd 0 ; 유저 스택 영역
User1Stack:
User1regs:
dd 0, 0, 0, 0, 0, 0, 0, 0 ; EDI, ESI, EBP, ESP, EBX, EDX, ECX, EAX
dw UserDataSelector, 0 ; DS
dw UserDataSelector, 0 ; ES
dw UserDataSelector, 0 ; FS
dw UserDataSelector, 0 ; GS
dd user_process1 ; EIP
dw UserCodeSelector, 0 ; CS
dd 0x200 ; EFLAGS
dd User1Stack ; ESP
dw UserDataSelector, 0 ; SS
; 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 user_process2
dw UserCodeSelector, 0
dd 0x200
dd User2Stack
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 user_process3
dw UserCodeSelector, 0
dd 0x200
dd User3Stack
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 user_process4
dw UserCodeSelector, 0
dd 0x200
dd User4Stack
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 user_process5
dw UserCodeSelector, 0
dd 0x200
dd User5Stack
dw UserDataSelector, 0
idtr:
dw 256*8-1
dd 0
;interrupt service routines
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
push eax
mov ax, SysDataSelector
mov DS, ax
mov ES, ax
MOV FS, ax
mov GS, ax
pop eax
mov edi, eax
lea esi, [ebx]
call printf
jmp ret_from_int
ret_from_int:
xor eax, eax
mov eax, [esp+52] ; 인터럽트가 발생되었을 때 CS 값을 가져옴
and eax, 0x00000003 ; CS 값에 포함된 RPL 을 얻을 수 있다.
xor ebx, ebx
mov bx, cs ; 현재 CS를 가져와서(커널 코드 세그먼트 셀렉터 값)
and ebx, 0x00000003 ; CPL을 가져오고
cmp eax, ebx ; 비교해서
ja scheduler ; eax가 크면 유저 영역 실행 중 인터럽트 발생한 것으로 판별
popad
pop ds
pop es
pop fs
pop gs
iret ; 커널영역 루틴 실행중에 발생한 인터럽트라면 그대로 복귀
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_soft_int db ".This is the soft_int interrupt", 0
; idt
idt_ignore:
dw isr_ignore
dw 0x08
db 0
db 0x8e
dw 0x0001
idt_timer:
dw isr_32_timer
dw 0x08
db 0
db 0x8e
dw 0x0001
idt_keyboard:
dw isr_33_keyboard
dw 0x08
db 0
db 0x8e
dw 0x0001
idt_soft_int:
dw isr_128_soft_int
dw 0x08
db 0
db 0xef
dw 0x0001
times 4608-($-$$) db 0
이 프로그램의 핵심은 Task Switching이 일어날 경우는 유저 영역에서 Interrupt가 발생되었을 때라는 것과 CurrentTask 값을 증가시켜가면서 계속 해당 Task를 실행시키고 마지막 Task까지 실행시키면 다시 처음 Task를 실행시키면서 반복한다는 것이다.
시작 후 처음은 Task1이 실행되고 그 Task1에서 int 0x80으로 콜게이트를 통한 인터럽트를 호출하면 그에 해당하는 인터럽트 핸들러 수행 후 ret_from_int 함수로 넘어가는데, 이곳에서 특권레벨을 비교하여 유저 영역에서 실행되었다고 판별이 되면 scheduler로 넘어간다.
scheduler에서는 각각의 프로세스에 대한 스택을 되돌려 놓고 CurrentTask 값을 증가시킨다. 증가시키면서 NumTask(총 Task 수)와 비교하여 같으면 CurrentTask를 0으로 초기화 한다. 다르면 CurrentTask에 해당하는 Task로 분기한다.
'Study > Kenrel' 카테고리의 다른 글
a20게이트 & 페이징 (0) | 2015.10.06 |
---|---|
유저모드 Task Switching (0) | 2015.10.01 |
Protected Mode에서의 보호 (0) | 2015.09.30 |
Task Switching in Kernel (0) | 2015.09.25 |
PIC 초기화 (0) | 2015.09.24 |