본론(영속성)에 들어가기에 앞서 먼저입력/출력 장치의 개념을 소개하고 운영체제가 이 장치들과 상호 작용하는 방법을 알아보자.
핵심 질문 : 어떻게 I/O를 시스템에 통합할까
- 시스템에 I/O를 어떻게 통합해야 하는가? 일반적인 방법은 무엇인가?
- 어떻게 효율적으로 통합할 수 있을까?
논의를 시작하기 위해 일반적인 시스템 구조를 살펴보자.


가상의 표준 장치를 살펴보고 이 장치를 효율적으로 활용하기 위해 필요한 것은 무엇인지 알아보자.

위에서 보인 단순화된 디바이스의 인터페이스는 세 개의 레지스터로 구성되어 있다.

이 레지스터들을 읽거나 쓰는 것을 통해 운영체제는 디바이스의 동작을 제어할 수 있다.
이번에는 디바이스가 운영체제를 대신하여 특정 동작을 할 때에 운영체제와 디바이스 간에 일어날 수 있는 상호 동작의 과정을 살펴보자. 이 경우 다음과 같은 방식을 따른다.
while (STATUS == BUSY)
; // 디바이스가 바쁜 상태가 아닐 때까지 대기
데이터를 DATA 레지스터에 쓰기
명령어를 COMMAND 레지스터에 쓰기
(그러면 디바이스가 명령어를 실행한다)
while (STATUS == BUSY)
; // 요청을 처리하여 완료할 때까지 대기
이 방식은 잘 동작하지만 폴링을 사용한다는 점에서 매우 비효율적이다.
폴링의 단점
핵심 질문 : 폴링 사용 비용을 어떻게 피하는가
어떻게 하면 자주 폴링을 하지 않으면서 운영체제가 디바이스의 상태를 확인할 수 있고, 디바이스를 관리하는 CPU의 오버헤드를 줄일 수 있을까?
인터럽트

팁 : 인터럽트가 폴링보다 항상 좋은 것은 아니다
하이브리드 방식
병합(coalescing) 방식
많은 양의 데이터를 디스크로 전달하기 위해 programmed I/O(PIO)를 사용하면 CPU가 또 다시 단순 작업 처리에 소모된다.

핵심 질문 : 어떻게 PIO의 오버헤드를 줄이는가
PIO를 사용하면 CPU는 너무 많은 시간을 데이터를 디스크에서 또는 디스크로 이동하는 데 사용한다. 이 작업을 어떻게 줄일 수 있으며 CPU를 더 효율적으로 활용하는 방법은 무엇인가?-> DMA 방식을 사용하자

메모리 상의 데이터 위치, 전송할 데이터의 크기, 대상 디바이스와 같은 정보를 DMA 엔진에 넘겨준다.디바이스와 운영체제가 실제로 어떻게 정보를 교환하는지 알아보자.
in과 out 명령어를 사용하여 디바이스들과 통신근데 두 방식 모두 대단히 큰 장점은 없고, 둘다 현재도 쓰인다고 한다...
최종적으로 다룰 문제는 서로 다른 인터페이스를 갖는 장치들과 운영체제를 연결시키는 가능한 일반적인 방법을 찾는 것이다.
핵심 질문 : 어떻게 장치 중립적인 운영체제를 만드는가
어떻게 하면 운영체제를 장치 중립적으로 만들고, 장치와의 상호작용을 위한 상세 내용을 운영체제로부터 숨길 수 있을까?
디바이스 드라이버를 이용한 추상화

read/write 요청을 할 뿐이다.참고) 운영체제 코드의 70%가 디바이스 드라이버를 위한 코드이다.
- 어떤 디바이스를 시스템에 연결하든, 디바이스 드라이버가 필요하다.
- 때문에 시간이 흐름에 따라 디바이스 드라이버 코드가 커널 코드의 대부분을 차지하게 되었다.
IDE 디스크 드라이브를 살펴보자.
IDE 디스크는 시스템에 다음과 같은 4개의 레지스터로 이루어진 단순한 인터페이스를 제공한다.
Control, Command block, Status, Errorin과 out I/O 명령어를 사용하여 특정 "I/O 주소들"(아래의 0x3F6와 같은)을 읽거나 씀으로써 접근 가능하다.// IDE 인터페이스
Control Register:
Address 0x3F6 = 0x80 (0000 1RE0): R=reset , E=0 means "enable interrupt"
Command Block Registers:
Address 0x1F0 = Data Port
Address 0x1F1 = Error
Address 0x1F2 = Sector Count
Address 0x1F3 = LBA low byte
Address 0x1F4 = LBA mid byte
Address 0x1F5 = LBA hi byte
Address 0x1F6 = 1B1D TOP4LBA: B=LBA , D=drive
Address 0x1F7 = Command/status
Status Register (Address 0x1F7):
7 6 5 4 3 2 1 0
BUSY READY FAULT SEEK DRQ CORR IDDEX ERROR
Error Register (Address 0x1F1): (check when Status ERROR==1)
7 6 5 4 3 2 1 0
BBK UNC MC IDNF MCR ABRT T0NF AMNF
BBK = Bad Block
UNC = Uncorrectable data error
MC = Media Changed
IDNF = ID mark Not Found
MCR = Media Change Requested
ABRT = Command aborted
T0NF = Track 0 Not Found
AMNF = Address Mark Not Found
READY 상태가 될 때까지 Status 레지스터 (0x1F7) 를 읽는다.0x1F2-0x1F6)에 기록한다.0x00, 슬레이브인 경우0x10이다.0x1F7).대부분의 프로토콜은 xv6 IDE 드라이버에 나타나 있으며, (초기화 이후에) 네 개의 기본 함수를 통하여 동작한다.
// xv6 IDE 디스크 드라이버 (단순화한 버전)
static int ide_wait_ready() {
while (((int r = inb(0x1f7)) & IDE_BSY) || !(r & IDE_DRDY))
; // 드라이브가 바쁘지 않을 때까지 반복문 수행
}
static void ide_start_request(struct buf *b) {
ide_wait_ready();
outb(0x3f6 , 0); // 인터럽트 발생
outb(0x1f2 , 1); // 섹터는 몇 개?
outb(0x1f3 , b−>sector & 0xff); // 여기에 LBA 기록. . .
outb(0x1f4 , (b−>sector >> 8) & 0xff); // . . . 여기도
outb(0x1f5 , (b−>sector >> 16) & 0xff); // . . . 여기도!
outb(0x1f6 , 0xe0 | ((b−>dev&1)<<4) | ((b−>sector>>24)&0x0f));
if(b−>flags & B_DIRTY){
outb(0x1f7 , IDE_CMD_WRITE); // 이것이 WRITE 명령어
outsl(0x1f0 , b−>data , 512/4); // 데이터도 전송!
} else {
outb(0x1f7 , IDE_CMD_READ); // 이것이 READ (데이터 없음)
}
}
void ide_rw(struct buf *b) {
acquire(&ide_lock);
for (struct buf **pp = &ide_queue; *pp; pp=&(*pp)−>qnext)
; // 큐를 순회
*pp = b; // 요청을 맨 뒤에 추가
if (ide_queue == b) // q가 비었다면
ide_start_request(b); // 디스크에 req를 보냄
while ((b−>flags & (B_VALID|B_DIRTY)) != B_VALID)
sleep(b , &ide_lock); // 완료를 대기
release(&ide_lock);
}
void ide_intr() {
struct buf *b;
acquire(&ide_lock);
if ( ! ( b−>flags & B_DIRTY) && ide_wait_ready() >= 0)
insl(0x1f0 , b−>data , 512/4); // READ 라면: 데이터를 가져오기
b−>flags |= B_VALID;
b−>flags &= ~B_DIRTY;
wakeup(b); // 대기중은 프로세스를 깨우기
if ((ide_queue = b−>qnext) != 0) // 다음의 요청을 시작
ide_start_request(ide_queue); // (존재한다면)
release(&ide_lock);
}
동작을 위한 4개의 기본 함수
ide_rw()ide_start_request()를 통해). ide_start_request()in과 out 명령어가 장치 레지스터를 읽거나 쓰는 데 각각 사용된다.ide_wait_ready()ide_intr()ide_start_request()를 이용하여 다음 요청 처리를 시작한다.DMA를 어느 기계가 먼저 도입했는가?
근데 사실 누가 먼저 도입했는가는 중요하지 않고, 이 초기 기계를 만들 때부터 I/O 지원이 필요해졌다는 것이 더 중요하다.
인터럽트와 DMA 그리고 관련된 개념들은 빠른 CPU와 느린 장치들의 특성을 활용한 결과물이다.