[완료]OS커널의 태스크스위칭에 대해서 설명좀 부탁드립니다.

esllo의 이미지

안녕하세요;
"만들면서 배우는 OS커널의 구조와 원리"를 공부하고 있는 학생입니다.
하다가..도저히 막혀서 ㅠㅜ 도움좀 요청하려고 합니다.
부탁드립니다.

우선 소스를 요약해 드리겠습니다.
1. boot.bin
setup.bin, kernel.bin, user_program1.bin, user_program2.bin, user_program3.bin, user_program4.bin, user_program5.bin 파일을 각 메모리에 할당을 시킵니다
두개의 PIC 칩을 초기화 합니다. ->setup.bin으로 넘어갑니다.

2. setup.bin
TSS의 BaseAddress를 세팅하고, 32비트 보호모드로 진입합니다.
아까 1번과정에서 메모리에 상주시킨 파일들을 1M 윗부분으로 옮깁니다.
페이징 디렉토리와 테이블을 세팅하고 맞게 선언해 줍니다. ->kernel.bin으로 넘어갑니다.

3. kernel.bin
include idt0.inc
IDT를 등록합니다.
mov ax, TSSSelector ; TSSSelector=0x20
ltr ax ; Load Task Register
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를 선택한다. (CurrentTask = 0)
add eax, TaskList
mov ebx, [eax]
jmp sched

scheduler:
lea esi,[esp] ; 커널 ESP에는 유저 레지스터들이 있다.

xor eax, eax
mov eax, [CurrentTask]
add eax, TaskList
; 레지스터 저장공간의 주소 포인터를 edi에 넣는 과정
mov edi,[eax] ; 현재 실행 중인 태스크의 저장영역을 선택한다.

mov ecx,17 ; 17개의 DWORD(68 BYTE) 모든 레지스터의 바이트 합.
rep movsd ; 복사하고,
add esp,68 ; 17개의 DWORD 만큼 스택을 되돌려 놓는다.

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] ; TSS_ESP0_WHER=0x90000
mov [eax], esp ; 커널영역의 스택주소를 TSS에 기입해 둔다.

lea esp,[ebx] ; EBX에는 다음 태스크의 저장영역의 주소가 있다.

popad ; EAX, EBX, ECX, EDX, EBP, ESI, EDI 를 복원한다.
pop ds ; DS, ES, FS, GS 복원한다.
pop es
pop fs
pop gs

iretd ; 다음 유저 태스크로 스윗칭 된다.

CurrentTask dd 0 ; 현재 실행 중인 태스크 번호
NumTask dd 20 ; 모든 태스크의 수
TaskList: times 5 dd 0
서브루틴 printf를 선언합니다.
include user_task_structure.inc
include idt1.inc
ldtr: 의 limit와 BASE를 선언합니다;
include idt2.inc

4. user_task_structure.inc
각 유저 태스크의 레지스터 저장 영역을 선언합니다.

5. user_program1.bin ~ user_program5.bin
간단한 메세지를 출력합니다.
[org 0x80000000]
[bits 32]
user_process1:
mov eax, 80*2*2+2*5 ; 좌표
lea ebx, [msg_user_process1_1] ; 문자열 포인터
int 0x80 ; 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

6. idt0.inc
인터럽트 디스크립터 테이블을 만듭니다.

7. idt1.inc
인터럽트 서비스 루틴 ISR을 구현합니다.
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 ; PIC초기화
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_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
ret_from_int:
xor eax, eax
mov eax, [esp+52] ; 13개의 레지스터 * 4
and eax, 0x00000003 ; 3 AND 연산 : CS->RPL확인
xor ebx, ebx
mov bx, cs
and ebx, 0x00000003
cmp eax, ebx
ja scheduler ; ja=jump above eax가 더 크다면 : 유저모드에서 커널모드로 진입하였음을 확인

popad
pop ds
pop es
pop fs
pop gs ; 레지스터 복구

iret ; 기존 코드로 리턴

8. idt2.inc
인터럽트 디스크립터 테이블 IDT를 구현합니다.

읽어주셔서 감사합니다 ㅜㅠ
이해가 안되면;; 소스 전체를 보여드리겠습니다 ㅡㅡ;
ㅠㅠ;
제가 궁금한것은;; 세부적으로는 분석을 할 수 있겠는데;
트랩게이트를 통해서 태스크 스위칭을 한다는 내용을 보았습니다.
그 과정이 이해가 잘 안됩니다.. 그리고 태스크 스위칭 과정에서
운영체제가 돌아가는.. 흐름이 잘 안잡히네요 ㅜㅜ
제가 보기로는 스케쥴링에 의해서 유저 프로그램이 한개씩 번갈아서
실행되는 것 같은데;; 그 사이에 인터럽트가 발생하는것 같구요.. ㅜㅠ
근데.. 확실히 어떤식으로 해서 그 과정이 이루어 지는건지 이해가 잘 안댑니다 ㅜㅠ
전체 소스 파일 첨부 드리겠습니다..
부탁드립니다 ^-^

File attachments: 
첨부파일 크기
Package icon chap8.zip10.39 KB
정태영의 이미지

Quote:
int 0x80 ; int 0x80=트랩 게이트를 통하여

이 부분을 얘기하시는 것 같군요. int 는 소프트웨어적으로 인터럽트를 일으키는 방법입니다.

인터럽트가 발생했을 경우 CPU 에서는 PC 에 있는 명령어를 수행하는 것이 아니라 인터럽트 테이블을 참조하여 해당 인터럽트를 처리하기 위한 핸들러를 실행시키게 되구요. 인터럽트 핸들러에서 태스크 스위칭이 처리되는 것으로 보입니다. (코드는 보지 않았습니다.)

실제 운영체제에서는 저렇게 사용자 프로그램에서 인터럽트를 발생시키는 것을 통해 태스크 스위칭이 일어나는 것은 아니고, 대게 timer 인터럽트를 이용합니다.

--
오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..

오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..

esllo의 이미지

우선 답변 진심으로 감사드립니다.

인터럽트 핸들러에서 태스크 스위칭이 처리된다는... 음..어렵네요 ^^

소프트웨어 인터럽트에 대해서 좀 더 공부를 해 봐야 겠어요 ㅜㅠ^^

grassman의 이미지

실제 idt1.inc의 내용을 보시면 모든 인터럽트 핸들러가 ret_from_int로 분기하게 되어 있습니다.
그 중에는 타이머 인터럽트도 있죠. 결국 타이머 및 기타 인터럽트 발생에 의해 scheduler 쪽으로
분기하여 태스크 스위칭을 하게 됩니다. 태스크 스위칭 방법은 간단합니다. 인터럽트가 발생했을
때 스택에 저장된 모든 레지스터를 현재 태스크의 스택 위치(User?regs)에 저장하고 다음 태스크의
스택 포인터를 복원(lea esp,[ebx])하는 겁니다. 그렇게 하면 인터럽트 핸들러가 종료되는 시점에서
리턴되는 위치(EIP)는 다음 태스크에서 인터럽트가 발생했던 명령의 위치가 되죠.

태스크 스위치를 구현하는 가장 일반적인 방법이니 좀 어렵더라도 차근차근 이해해 보시기 바랍니다.

esllo의 이미지

답변 정말정말 감사드립니다 ^^
조금 이해가 될듯도 한데; 확실히는 안되네요 ㅜㅠ 음.. 벌써 며칠째 이 소스하나만 잡고 있습니다 ㅜㅠ

"인터럽트 핸들러가 종료되는 시점에서
리턴되는 위치(EIP)는 다음 태스크에서 인터럽트가 발생했던 명령의 위치가 되죠."
이 부분이 어떻게 해서 되었는지 잘 이해가 안되네요 ㅜㅠ
그리고 kernel.bin에서 그리고 iretd를 시행하면 정확히 어느 주소로 가게 되는건지 ㅜㅠ 음;;;
부탁드립니다.^^

grassman의 이미지

인터럽트가 발생하면 현재의 ESP 위치에 CS와 EIP, EFLAGS 값을 저장한 뒤에
인터럽트 핸들러로 분기하게 됩니다. (그리고 추가로 보존해야 할 레지스터가
있다면 인터럽트 핸들러의 진입점에서 저장합니다) 이 사실을 이용하는 것이
태스크 스위칭입니다. 즉, 각각의 태스크마다 고유의 스택을 할당하고 최초에
인터럽트가 발생한 것처럼 스택을 꾸민 뒤 인터럽트가 발생할 때마다 ESP의 값
을 태스크에 할당된 스택의 마지막 ESP 값으로 바꿔주면 다른 태스크로 실행
위치를 바꿀수 있는 것이죠.

간단히 예를 들어보죠. 간단하게 보이기 위해서 다른 레지스터는 보존하지 않는
것으로 가정합니다. 또, 위와 같은 태스크 스위칭 루틴을 사용하는 것으로 생각
합니다.

태스크 1은 EIP가 0x8000~0x8100 사이에 있을 수 있고 스택은 0x1000~0x2000에
있다고 가정합니다. 태스크 2는 EIP가 0x9000~0x9100, 스택이 0x2000~0x3000에
있다고 가정합니다.

최초에는 스택이 이런 모양으로 되어 있습니다.

                                            EIP   CS   EFLAGS
[STACK #1] ....                           0x8000  0    0x200       (주소: 0x2000)
[STACK #2] ....                           0x9000  0    0x200       (주소: 0x3000)

처음 ESP를 0x2000-10으로 설정한 뒤에 iret를 발생시킵니다. 그러면 EIP는 0x8000
으로 변경됩니다. 그러면 0x8000에 있던 명령부터 수행되기 시작합니다. (태스크 1
작동중) 그러다가 인터럽트가 발생합니다. 그러면 스택은 다음의 상태가 됩니다.
(EFLAGS 값이 변합니다만... 일단 무시합니다.)

                                            EIP   CS   EFLAGS
[STACK #1] ....                           0x8030  0    0x200       (주소: 0x2000)
[STACK #2] ....                           0x9000  0    0x200       (주소: 0x3000)

0x8030에서 인터럽트가 발생했군요. (EIP의 값은 인터럽트가 발생했을 시점에
서 수행하려던 명령의 위치입니다. 여기에서는 예로 0x8030을 적은 것입니다.)

그런데 인터럽트 핸들러에서 스케쥴러를 동작시킵니다. ESP 값을 0x3000-10으로 설정
한 뒤 iret을 해버립니다. 그러면 0x9000에 있던 태스크 2가 수행되기 시작합니다.

또 인터럽트를 발생시키면...

                                            EIP   CS   EFLAGS
[STACK #1] ....                           0x8030  0    0x200       (주소: 0x2000)
[STACK #2] ....                           0x9052  0    0x200       (주소: 0x3000)

태스크 2는 0x9052에서 멈췄습니다. (숫자는 예시이므로 신경쓸 필요가 없습니다.) 여기서
또 다시 스케쥴러를 동작시킵니다. ESP를 0x2000-10으로 설정하고 iret을 합니다. 그러면
EIP는 태스크 1이 인터럽트로 인해 동작을 중단했던 0x8030으로 설정됩니다. 즉, 태스크
1이 다시 수행됩니다.

더 이상 설명할 필요는 없을 것 같아서 여기에서 그만하겠습니다. (무한히 계속해볼까요?)

esllo의 이미지

친절한 답변 정말 감사드립니다..
이제서야 조금 이해가 되는거 같네요 ^^

제가 한번 분석을 해볼께요; 전체적인 흐름을요^^;틀릴찌도 몰라요 ㅋㅡㅡ;

IDT를 등록을 하면 타이머 인터럽트와 키보드인터럽트는 계속 동작을 하게 됩니다.
그 후에, 유저태스크들의 저장영역을 만들게 되고;
1유저태스크를 선택한 후에 커널영역의 스택주소를 TSS에 넣고, 1유저테스크의 레지스터들을 복구하고
iretd명령을 통해서 1유저태스크가작동을 하게 됩니다.

1유저태스크실행도중 int 0x80을 만나서 작업하던것을 1유저태스크의저장영역에 전부 백업을하고
2유저태스크를선택(레지스터복구)하고, iretd명령을 통해서 2유저태스크가작동을하게됩니다.

그러다가 5유저태스크까지 작동을 하다가 int 0x80을 만나면 작업하던것을 5유저태스크저장영역에 전부 백업을 하고
다시 1유저태스크를 선택하고(레지스터복구) iretd명령을 통해서 1유저태스크의 작업을 이어서 하게 됩니다;

이렇게 분석이 되니, 얼핏 이해는 되는거 같네요 -_-;
그런데 커널영역의 esp를 어떻게 이용한건지; 더 연구를 해봐야 겟어요ㅜㅠ

esllo의 이미지

어렵네요 ㅜㅠ ㅋ

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.