1. 스택 영역을 사용한 태스크 스위칭
(1) 인터럽트를 위한 스택
1) 인터럽트가 시작되었을 때, 커널 스택 영역은 다음과 같이 데이터를 쌓습니다.
|
←SS0 |
|
|
||
|
←ESP0 |
|
|
SS |
|
ESP |
|
|
EFLAGS |
|
|
|
CS |
|
EIP |
|
2) 유저 데이터 세그먼트 셀렉터 값을 스택에 저장
DS, ES, FS, GS에 유저 데이터 세그먼트 셀렉터 값이 존재하여, 이 값을 커널 데이터 세그먼트 셀렉터 값으로 바꾸어야 인터럽트 핸들러에서 데이터를 사용할 수 있습니다. 유저 데이터 세그먼트 셀렉터 값을 스택에 저장 한후, 커널 데이터 세그먼트 셀렉터 값으로 바꾸어 줍니다.
|
←SS0 |
|
|
||
|
←ESP0 |
|
|
SS |
|
ESP |
|
|
EFLAGS |
|
|
|
CS |
|
EIP |
|
|
|
GS |
|
|
FS |
|
|
ES |
|
|
DS |
←ESP |
|
||
|
|
|
|
|
3) PIC을 초기화
4) 인터럽트 루틴 수행
여기서는 .This is the timer interrupt 라는 문자열을 표시하고, 첫번째 문자의 값을 1 증가 시킵니다.
여기 까지의 스택 모습은 다음과 같습니다
|
←SS0 |
|
|
||
|
←ESP0 |
|
|
SS |
|
ESP |
|
|
EFLAGS |
|
|
|
CS |
|
EIP |
|
|
|
GS |
|
|
FS |
|
|
ES |
|
|
DS |
|
EAX |
||
EBX |
|
|
ECX |
|
|
EDX |
|
|
EBX |
|
|
EBP |
|
|
ESI |
|
|
EDI |
←ESP |
|
|
5) 인터럽트를 마치고 ret_from_int 함수로
Ret_from_int 함수에서는 인터럽트 핸들러가 유저 영역 실행 중에 발생 했는지 커널 영역 실행 중에 발생 했는지 알아보는 루틴으로 되어 있습니다.
mov eax, [esp+52] and eax, 0x00000003 |
ESP+52 (ESP+4*13)은 인터럽트 실행 전의 CS 값을 참조합니다. 이 값에 and 3을 하면 CS의 RPL 값을 알 수 있습니다.
mov bx, cs and ebx, 0x00000003 cmp eax, ebx |
마찬가지로 인터럽트 핸들러가 실행하고 난 뒤의 CS 값의 RPL을 구합니다.
이 두 값을 구하여 비교하고, 만약 스택에서 뽑아낸 CS의 값이 크다면 그 이전에 실행되던 태스크는 유저모드 태스크 이므로 태스크 스위칭을 위한 루틴 scheduler:로 점프하고, 작거나 같다면 커널 모드 루틴 실행 중에 인터럽트이므로 스택에 저장해 두었던 범용 레지스터, 데이터 세그먼트 셀렉터 모두를 POP하여 스택에서 꺼내고, IRET을 통해 EIP, Cs, Eflag를 스택에서 꺼낸다음, 인터럽트가 발생한 시점으로 돌아갑니다.
(2) 유저 모드 태스크를 위한 스택
각 유저 모드 태스크에 각각 실행하던 레지스터 값을 저장하기 위해 RAM 상에 스택을 가지고 있습니다. 이 스택은 커널 모드 스택이며, 유저 모드에서 실행되는 루틴은 실행되선 안됩니다.
1) 유저 프로그램을 위한 레지스터 저장 영역 예시
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 user_process1 dw UserCodeSelector, 0 dd 0x200 dd User1Stack dw UserDataSelector, 0 |
Times 63 dd 0 은 유저 태스크의 스택을 만들어 주는 영역입니다. 다음과 같이 스택이 생성됩니다.
0 |
UserDataSelector(SS) |
User1Stack(ESP) |
|
0x200(EFLAGS) |
|
UserCodeSelector(CS) |
0 |
User_process1(EIP) |
|
0 |
UserDataSelector(GS) |
0 |
UserDataSelector(FS) |
0 |
UserDataSelector(ES) |
0 |
UserDataSelector(DS) |
0(EAX) |
|
0(EBX) |
|
0(ECX) |
|
0(EDX) |
|
0(EBX) |
|
0(EBP) |
|
0(ESI) |
|
0(EDI) |
라벨이 아래쪽에 지정되어 있는 것은 스택이여서, PUSH 될때 메모리의 작은 번지수를 향하여 진행되기 때문입니다. 스택의 아래부분에는 범용 레지스터들이 저장되는 공간이 있으며, POPAD 명령을 사용하여 메모리의 큰 번지수방향으로 내려가며 복구하게 됩니다.
데이터 세그먼트 셀렉터 부분은 순서대로 pop 하며 복구합니다.
그 위의 EIP, CS, EFLGAS, ESP, SS는 iret명령을 통해 한번에 CPU에 복구되게 됩니다.
이 소스에는 초기값으로 EIP 부분에 user_process1이 지정되어 있으며, 프로그램이 실행되면서 EIP 영역의 값이 계속 변할 것입니다. 이 영역은 태스크스위칭이 일어났을 때 처음으로 실행 해야하는 명령어가 저장되어 있는 부분입니다.
2) 유저 프로세스
다음 코드는 유저 프로세스의 예시입니다.
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 |
Int 0x80은 IDT의 0x80번째 있는 트랩 게이트를 이용하겠다는 의미입니다.
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 |
인터럽트 서비스 루틴의 코드의 일부입니다. 세그먼트 셀렉터 레지스터를 커널의 데이터 세그먼트 셀렉터로 설정하였습니다. 이 과정에서 eax를 사용하기 때문에, 스택에 eax를 옮겨놓고 작업이 끝나면 다시 꺼내서 eax에 저장합니다.
3) 태스크 스위칭
1) 태스크 스위칭 준비
CurrentTask dd 0 ; 현재 실행 중인 태스크 번호 NumTask dd 20 ; 모든 태스크의 수 TaskList : times 5 dd 0 ; 각 태스크 저장 영역의 포인터 배역 |
다음 코드는 태스크 스위칭을 위해서 정의한 변수 선언입니다. 다음의 C언어 코드와 유사합니다.
int CurrentTask = 0; int NumTask = 20; int TaskList[5] = {0,}; |
TaskList에 유저 영역 태스크가 사용하는 레지스터 저장 영역의 포인터를 계속해서 넣어줍니다. (4byte 포인터이므로 4씩 더하면서 넣습니다)
mov eax, [CurrentTask] add eax, TaskList lea edx, [User1regs] mov [eax], edx add eax, 4 lea edx, [User2regs] mov [eax], edx add eax, 4 |
2) 태스크 스위칭
scheduler: lea esi, [esp]
xor eax, eax mov eax, [CurrentTask] add eax, TaskList
mov edi, [eax]
mov ecx, 17 rep movsd add esp, 68 |
현재 커널 영역에 저장된 모든 레지스터 값을 유저 모드 태스크의 스택 위에 있는 레지스터 저장 공간으로 복사를 합니다. 이를 구현하기 위해서 ESP 값(스택 포인터)를 ESI에 넣고 레지스터 저장 공간의 주소 포인터를 EDI에 넣은뒤 rep movsd로 값을 복사합니다.
이때, 커널 스택 영역에 저장된 레지스터 값은 이제 필요가 없어, 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] |
CurrentTask 변수는 포인터로 사용되는데, 이 값에 4를 더 한 뒤, NumTask와 비교하여, 같으면 모든 태스크를 한 번씩 수행한 것으로 보아, CurrentTask 변수를 0으로 만들어 다시 처음부터 수행합니다. 그 다음 다음에 실행할 태스크의 레지스터영역의 주소를 ebx에 넣어줍니다.
3)TSS
TSS 영역을 하나만 사용하여, 인터럽트로 커널로 진입했을 때, ESP에 있는 포인터로 스택을 사용합니다. TSS 영역의 ESP0에 현재 ESP 값을 넣습니다.
ESP 값은 스택을 이용하여 태스크 스위칭을 하고 레지스터 값을 복원하기 위해 사용됩니다.
이와 같이 스택에 레지스터를 저장하고 복원하는 것을 반복하면서 태스크 스위칭이 이루어 집니다.
(POPAD, POP, iret 등등등)
3. 첫 태스크 실행
mov eax, [CurrentTask] add eax, TaskList mov ebx, [eax] jmp sched |
CurrentTask 변수를 이용하여 레지스터 저장 영역의 포인터를 꺼내, EBX에 넣고 sched 번지로 점프하여 레지스터 값을 복원하여 IRET 명령을 통해 첫 태스크를 실행합니다.
하여 레지스터 값을 복원하여 IRET 명령을 통해 첫 태스크를 실행합니다.
'Operating Systems > OS 커널 제작' 카테고리의 다른 글
8. 페이징 (2) – 페이징 (0) | 2015.02.04 |
---|---|
8. 페이징 (1) – A20 게이트 (0) | 2015.02.03 |
7. 유저모드 Task Switching (1) – 유저모드와 콜게이트 (0) | 2015.01.31 |
6. 보호 (2) – 특권 레벨 (0) | 2015.01.29 |
6. 보호 (1) - cpu (0) | 2015.01.28 |