I/O를 처리하기 위해서는 일련의 Seek, Read, Write, Sort, Merge등의 과정이 필요합니다. 이것을 좀더 효율적으로 흐름제어를 위해서 I/O처리에 대한 특화된 Scheduler의 고안이 필요했습니다.
문제의 고민
Writes-Starving-Reads
Write동작은 버퍼에 밀어넣고 버퍼에서 Merge 및 Sort를 하여 일련의 연속된 Write동작으로 처리될수 있습니다. 하지만 이 과정에서 Read동작이 끼어들게 되면 I/O요청순서에 입각하여 처리되면서 연속적인 Read동작을 하지 않게 되고 자주 Seek하면서 성능이 극대화 되기 힘들게 됩니다. 이러한 I/O Scheduler의 동작에 의한 현상을 "Writes-starving-reads"라고 합니다.
Effects of High Read Latency
논리적으로 연속적인 커다란 Data를 Read하는 중에 작은 Data를 읽는 동작이 끼어들게 되면 작은 Data를 Read하는것에 대하여 어떤것에 성능의 초점을 맞춰야 하는지에 대한 갈등의 문제가 생깁니다. 이것을 "Effects of High Read Latency"라고 합니다.
Linux Elevator
요청된 I/O를 Sort하면서 Queue에 넣었다가 오래된 요청을 먼저 처리하는 방식입니다.
Deadline I/O Scheduler
Read와 Write가 별도의 Queue로 Sort되어 넣었다가 요청순서대로 처리하게 되는데 이때 각 요청에는 제한시간이 있어서 제한시간을 넘어서까지 처리되지 않은 요청이 있는 경우 해당 요청을 먼저처리하도록 하여 처리하는 방식입니다.
Anticipatory I/O Scheduler
논리적인 연속적인 Data A와 B가 있을때 한번의 Read요청으로 A를 Read하고 다음 요청이 B를 Read하고 그 다음 요청이 이전의 A Read위치의 다음에 위치한 부분인 경우를 위해서 처음 A의 Read요청이 완료된후 일정시간 Seek위치를 유지하는 방식으로 연속적인 Data Read에 대한 성능에 초점을 맞춘 방식입니다.
CFQ(Complete Fair Queue) I/O Scheduler
각 Process별로 Queue를 소유하고 주어진 Time Slice동안 Round robin방식으로 작업을 처리하도록 하는 방식인데 만약 Time Slice를 전부 소모하지 않은 상태에서 Queue를 모두 처리한 경우 일정시간을 기다리면서 다른 요청을 추가적으로 기다려주는 조건을 포함합니다. 동기적인 요청(Read)이 비동기적인 요청(Write)보다 우선순위가 높게 처리되는 특성을 가지고 있고 Writes-Starving-Reads 현상을 어느정도 개선하는 방식입니다.
No-op I/O Scheduler
요청된 I/O를 Sort하지는 않고 Merge만 하여 처리하는 것으로 Sort동작이 필요없는 경우에 (예를 들어서 Seek동작이 무시할정도로 빠른 Flash같은 장치들) 대해서 사용하는 방식입니다.
예전에 "link:1차 버젼"으로 이와 비슷한 소스를 올린적이 있습니다. 그러나 꽤 많은 분들이 컴파일 안된다고 그 자체를 이해하지 못하시더군요. 그래서 이번에는 아주 쬐금 소스를 가다듬어서 실제 컴파일 가능하게 코드를 개선하여 올려봅니다. 이제 이해 못한다고 하시는 분들은 없겠죠?
지금 개발중인 Kernel 의 선점형(비선점형 동시지원) Thread code는 이와는 아주 많이 다르고 실제로 소스의 분량이 굉장히 방대하여 이해하기 어려울까봐 이렇게 굉장히 축소한 버젼만 공개하지만 추후 실제로 실전에 사용가능한 library 로 언젠가는 revision 하여 올릴 계획임을 미리 밝혀둡니다. (언제가 될지는 저도 모름)
main.c
코드:
/* [ GPL ] Code by JaeHyuk Cho <mailto:minzkn@infoeq.com> KOREA
t_STACK *ML_CreateSTACK(int s_StackSize); t_STACK *ML_DestroySTACK(t_STACK *s_STACK); int ML_PushSTACK(t_STACK *s_STACK, int s_Value); int ML_PopSTACK(t_STACK *s_STACK, int *s_Value); int ML_SetSTACK(t_STACK *s_STACK, int s_StackPointer);
Interrupt $0x1e 에 해당하는 벡터위치에 DPT의 위치주소가 담겨있습니다.
우리는 이 값을 통해서 플로피의 정보를 얻어볼수 있습니다. Interrupt 0x1e 는 0x1e * 4 = 0x0078 에
해당하는 주소이며 이 주소에 DPT가 담겨져 있는 메모리를 가르키는 주소값이 존재하게 됩니다. 여기서 Interrupt
0x1e는 "Disk Initialization Parameter Table Vector" 라고 부릅니다. 그리고 DPT는
"Disk parameter table"이라고 합니다.
참고로 아래의 내용중에 시간의 단위는 Milli second입니다.
디스크로부터 읽는 것은 Interrupt $0x13 을 사용하는 방법이 있고 PIO 방식도 있으니 그 부분은 가장 많이 알려져 있는 "랄프브라운의 인터럽트 리스트"를 참고하시면 될겁니다.
코드:
Offset 내용
0x00 Bit 0~3까지 4bit는 Head의 지연시간을 가집니다.
Bit 4~7까지 4bit는 Head가 자유로워지는에 걸리는 시간입니다.
0x01 Bit 0은 DMA의 사용가능 여부입니다.
Bit 1~7까지 7Bit는 Head의 접근시간에 대하여 2를 나누어 1을 뺀 값의 시간을 뜻합니다.
0x02 Motor의 전원이 완전히 꺼지기까지의 Clock tick수를 의미합니다.
0x03 FM 또는 MFM모드에 대하여 각각 내용이 다른데 그냥 섹터당 바이트수를 128로 나눈값으로 생각하시면 무난할듯.
0x04 Track당 Sector수 (본래 의미는 Track에서 마지막 섹터번호입니다.)
0x05 Sector당 byte수 (본래 의미는 Sector간격입니다.)
0x06 Format을 결정하는 값 (0x80이면 섹터당 128byte이고 그 외의 값은 필자도 정확히 모르며 정확하지 않아도 잘 되네요.)
0x07 Format간격 (포맷시에 하나의 그룹을 단위로 포맷을 하게 되는데 이때 이 그룹간격)
0x08 위와 비슷한데 Data형식 지정자라고 해야 할까? (필자는 이것에 대해서도 잘 모르겠습니다.)
0x09 Head가 접근후에 준비까지의 시간
0x0a Motor가 기동되어 준비될때까지의 시간
0x0b Motor가 중지될때까지의 시간 (디스켓 빼려면 이것이 정지될때까지는 빼지 말라고 할수 있겠죠?)
;;
;; enableA20.s (adapted from Visopsys OS-loader)
;;
;; Copyright (c) 2000, J. Andrew McLaughlin
;; You're free to use this code in any manner you like, as long as this
;; notice is included (and you give credit where it is due), and as long
;; as you understand and accept that it comes with NO WARRANTY OF ANY KIND.
;; Contact me at <andy@visopsys.org> about any bugs or problems.
;;
enableA20:
;; This subroutine will enable the A20 address line in the keyboard
;; controller. Takes no arguments. Returns 0 in EAX on success,
;; -1 on failure. Written for use in 16-bit code, see lines marked
;; with 32-BIT for use in 32-bit code.
pusha
;; Make sure interrupts are disabled
cli
;; Keep a counter so that we can make up to 5 attempts to turn
;; on A20 if necessary
mov CX, 5
.startAttempt1:
;; Wait for the controller to be ready for a command
.commandWait1:
xor AX, AX
in AL, 64h
bt AX, 1
jc .commandWait1
;; Tell the controller we want to read the current status.
;; Send the command D0h: read output port.
mov AL, 0D0h
out 64h, AL
;; Wait for the controller to be ready with a byte of data
.dataWait1:
xor AX, AX
in AL, 64h
bt AX, 0
jnc .dataWait1
;; Read the current port status from port 60h
xor AX, AX
in AL, 60h
;; Save the current value of (E)AX
push AX ; 16-BIT
;; push EAX ; 32-BIT
;; Wait for the controller to be ready for a command
.commandWait2:
in AL, 64h
bt AX, 1
jc .commandWait2
;; Tell the controller we want to write the status byte again
mov AL, 0D1h
out 64h, AL
;; Wait for the controller to be ready for the data
.commandWait3:
xor AX, AX
in AL, 64h
bt AX, 1
jc .commandWait3
;; Write the new value to port 60h. Remember we saved the old
;; value on the stack
pop AX ; 16-BIT
;; pop EAX ; 32-BIT
;; Turn on the A20 enable bit
or AL, 00000010b
out 60h, AL
;; Finally, we will attempt to read back the A20 status
;; to ensure it was enabled.
;; Wait for the controller to be ready for a command
.commandWait4:
xor AX, AX
in AL, 64h
bt AX, 1
jc .commandWait4
;; Send the command D0h: read output port.
mov AL, 0D0h
out 64h, AL
;; Wait for the controller to be ready with a byte of data
.dataWait2:
xor AX, AX
in AL, 64h
bt AX, 0
jnc .dataWait2
;; Read the current port status from port 60h
xor AX, AX
in AL, 60h
;; Is A20 enabled?
bt AX, 1
;; Check the result. If carry is on, A20 is on.
jc .success
;; Should we retry the operation? If the counter value in ECX
;; has not reached zero, we will retry
loop .startAttempt1
;; Well, our initial attempt to set A20 has failed. Now we will
;; try a backup method (which is supposedly not supported on many
;; chipsets, but which seems to be the only method that works on
;; other chipsets).
;; Keep a counter so that we can make up to 5 attempts to turn
;; on A20 if necessary
mov CX, 5
.startAttempt2:
;; Wait for the keyboard to be ready for another command
.commandWait6:
xor AX, AX
in AL, 64h
bt AX, 1
jc .commandWait6
;; Tell the controller we want to turn on A20
mov AL, 0DFh
out 64h, AL
;; Again, we will attempt to read back the A20 status
;; to ensure it was enabled.
;; Wait for the controller to be ready for a command
.commandWait7:
xor AX, AX
in AL, 64h
bt AX, 1
jc .commandWait7
;; Send the command D0h: read output port.
mov AL, 0D0h
out 64h, AL
;; Wait for the controller to be ready with a byte of data
.dataWait3:
xor AX, AX
in AL, 64h
bt AX, 0
jnc .dataWait3
;; Read the current port status from port 60h
xor AX, AX
in AL, 60h
;; Is A20 enabled?
bt AX, 1
;; Check the result. If carry is on, A20 is on, but we might warn
;; that we had to use this alternate method
jc .warn
;; Should we retry the operation? If the counter value in ECX
;; has not reached zero, we will retry
loop .startAttempt2
;; OK, we weren't able to set the A20 address line. Do you want
;; to put an error message here?
jmp .fail
.warn:
;; Here you may or may not want to print a warning message about
;; the fact that we had to use the nonstandard alternate enabling
;; method