입출력 장치
입출력 장치는 크게 3가지로 분류할 수 있다.
- Block Device : 고정된 크기의 블록에 데이터를 저장하는 장치. 각각의 블록은 주소를 가지며 주로 512KB ~ 323768KB 의 크기를 갖는다. 대표젹인 예시는 Disk
- Character Device : 운영체제와 character stream을 주고 받는 장치. 블록 구조가 없기에 주소 체계도 없고, 무언가를 찾는 동작도 하지 않는다. 대표적인 예시는 프린터, 마우스, 네트워크 인터페이스
- Other device : 기타 장치들. 클락, Memory-mapped screens 등이 있다.
위 그림은 대표적인 입출력 장치들의 데이터 전송속도를 보여준다.
아래로 내려갈수록 입출력 속도가 빨라지며, 사용자와 직접 상호작용하는 디바이스에서 네트워크, bus 로 갈 수록 점점 빨라진다.
디바이스 컨트롤러
입출력 장치는 크게 2가지 컴포넌트를 갖는다.
- mechanical component (기계적 컴포넌트, 하드디스크의 모터와 같은 부분)
- electronic component (전자적 컴포넌트, 디바이스 컨트롤러 라고도 부른다.)
대표적으로 하드디스크의 디바이스 컨트롤러가 하는 일을 예시로 살펴보면,
- 플래터에서 읽은 비트 단위 데이터를 바이트 블록 단위로 변환한다.
- 필요하다면 에러를 검출하고 바로잡는다.
- 필요하다면 메인 메모리에 보낼 수 있도록 만든다.
각각의 디바이스 컨트롤러는 CPU와 데이터를 주고받기 위해 레지스터를 갖고 있다.
- 컨트롤 레지스터
OS가 특정 컨트롤 레지스터에 명령어를 써서, 데이터를 보내거나 받는 등의 동작을 지시하거나,
컨트롤 레지스터의 값을 읽어서 현재 디바이스가 명령어를 받을 준비가 되었는지 등의 상태를 체크할 수 있다.
- 데이터 버퍼
OS와 디바이스 컨트롤러가 주고, 받을 데이터가 담기는 공간이다.
CPU와 디바이스 컨트롤러가 데이터를 주고받는 방법은 크게 3가지가 있다.
- (a) 메모리 공간과 I/O 가 분리된 경우
분리된 I/O 포트 공간에 컨트롤 레지스터가 존재하고, 각 컨트롤 레지스터에는 I/O 포트 번호가 할당된다.
그리고 CPU 는 in, out 이라는 특별한 I/O 관련 명령어를 제공한다.
in 명령어는 I/O 포트 번호를 지정하여 특정 컨트롤 레지스터의 값을 읽어 CPU 레지스터에 저장하고,
out 명령어는 CPU 레지스터의 값을 특정 컨트롤 레지스터에 저장한다.
(형식은 in reg, port / out port, reg)
참고로 MOV 라는 명령어는 메모리에 있는 값을 읽어서 CPU 레지스터에 저장하는 명령어이다.
(보통 레지스터 -> 레지스터로 옮기지 않나..?)
- (b) Memory-mapped I/O
메모리 주소 공간 하나만 사용하여, 디바이스 컨트롤러에 있는 컨트롤 레지스터 각각은 메모리 주소를 할당받는다.
이 주소 공간에는 실제 메모리가 주소를 할당받지 않으며, 보통 메모리 주소 윗쪽에 위치한다.
- (c) 하이브리드
앞 2가지 방법을 섞은 방식이다.
먼저 컨트롤 레지스터는 분리된 I/O 포트 번호를 할당받는다.
대신 데이터 버퍼는 메모리 주소 공간을 받는 방법이다.
(GPT에게 물어보면서 이해한 바로는,
결국 컨트롤 레지스터와 데이터 버퍼는 실제로 I/O 장치 안에 존재하나, 이 공간에 접근할 때 메모리의 주소 체계를 빌려서 접근할 지, 별도의 I/O port 주소 체계를 사용해서 접근할 지의 차이라고 한다.)
이 그림은 memory-mapped I/O 방식을 사용할 때, single bus 구조로 설계한 모습과,
dual bus 구조로 설계한 모습을 보여준다.
(a) 는 싱글 버스 구조로, 모든 종류의 주소(메모리 주소와 I/O 조작할 때 사용하는 주소)가 하나의 버스를 통해 CPU로 들어가고 나간다.
(b) 는 듀얼 버스 구조로, 메모리에 읽고 쓰기할 때 사용하는 주소는 메모리와 직접 연결된 별도 bus를 사용하고,
I/O 디바이스가 메모리에 접근할 때는, 별도의 bus 통로를 사용하여 접근한다.
이때 CPU와 메모리가 주고받는 버스는 고대역이기 때문에 매우 빠르게 데이터를 주고받을 수 있다.
DMA
Direct Memory Access 의 줄임말로, 이름만 보면 동작처럼 보이지만 실제로는 하드웨어 유닛을 말한다.
먼저 DMA가 없을 때 CPU의 read 시스템 콜 처리를 생각해보면
1. 프로세스가 read 시스템 콜을 요청한다.
2. 트랩이 발생하고 리드 시스템 콜 핸들러가 실행되어 하드디스크의 특정 블록을 읽으라는 명령을 내린다.
3. 시스템 콜을 요청한 프로세스는 block 상태로 빠지고, 다른 프로세스가 선택되어 실행된다.
4. 하드디스크가 읽은 데이터를 버퍼에 모두 올린 뒤, CPU에게 인터럽트를 건다.
5. CPU는 실행하던 프로세스를 백업해두고, 인터럽트 핸들러를 실행하여 인터럽트를 처리한다. 이때 하드디스크가 읽은 데이터 버퍼에 있는 내용을 메모리에 직접 올린다.
6. 인터럽트 서비스 루틴이 끝나면 blocked 상태에 있던 프로세스를 ready 상태로 올리고, 기존에 실행하던 프로세스를 마저 불러와서 실행한다.
위와 같은 순서로 실행된다.
그런데 이때 하드디스크가 읽어서 버퍼에 올린 데이터를 메모리에 올리는 작업은 단순 반복작업인데, 이를 위해 고성능 유닛인 CPU가 낭비되는 것이 매우 아쉽다.
그래서 이 작업을 대신 수행할 중간 관리자로서 DMA를 둘 수 있다.
DMA가 있다면 다음과 같은 과정으로 처리한다.
1. 하드디스크가 인터럽트를 걸면 CPU는 DMA의 레지스터 값들을 세팅한다.
Control 레지스터는 데이터를 메모리로부터 읽을 지 쓸 지를 결정하고,
Count 레지스터는 읽거나 쓸 데이터의 byte 수를 지정하고,
Address는 데이터를 읽거나 쓸 메모리 주소를 지정한다.
이를 가리켜 CMA 컨트롤러를 프로그램한다고 표현한다.
2. CPU는 프로그램한 DMA 컨트롤러를 실행시키고 자신은 다른 프로세스를 실행하기 시작한다.
3. DMA 컨트롤러는 디스크 컨트롤러에게 '너 버퍼 안에 있는 데이터를 메모리에 직접 옮기라' 라고 명령한다.
이를 fly-by-mode 라고 말한다.
디스크가 1바이트를 옮기고 다 옮겼다고 신호를 보내면 DMA는 Address를 증가시키고, Count를 감소시킨 뒤 다시 데이터를 이 주소로 옮기라는 명령을 보낸다.
4. 메모리에 모든 데이터를 다 쓴 뒤에는 DMA가 CPU에게 인터럽트를 건다.
CPU는 인터럽트 핸들러를 실행하여 blocked 상태에 있는 프로세스를 ready 상태로 올리고 다시 원래 실행하던 프로세스를 실행하며, 이때 읽어온 데이터는 모두 메모리에 올라와있는 상태이므로 CPU가 특별히 더 작업할 것은 없다.
인터럽트 처리 과정
위 그림은 인터럽트에 대한 더 자세한 설명을 보여준다.
I/O가 CPU의 명령을 다 수행한 뒤에는 인터럽트를 거는데, 이때 중간에 인터럽트 컨트롤러가 있다.
그래서 인터럽트를 걸 때는 인터럽트 컨트롤러로 신호를 보낸다.
CPU에는 인터럽트 신호를 받아들이는 인터럽트 핀이 있고, 이 핀으로 신호를 보내는 것은 인터럽트 컨트롤러가 대신 신호를 보내준다.
그리고 신호를 보낼 때 인터럽트를 걸은 장비의 번호를 버스를 통해 함께 전달한다.
그러면 인터럽트 서비스 루틴이 들어있는 테이블(인터럽트 벡터)에서 디바이스 번호를 인덱스로 하여 접근한 뒤 인터럽트 서비스 루틴 주소를 가져와 인터럽트 서비스 루틴을 호출한다.
인터럽트 서비스 루틴이 동작을 끝내면 ACK 를 보내주고, 인터럽트 컨트롤러는 다시 인터럽트 신호를 전달할 수 있게 된다.
만약 두 I/O 장비가 동시에 인터럽트를 걸게되면 두 장비 사이에는 우선순위가 있기 때문에, 우선순위가 높은 장비의 인터럽트를 먼저 걸어주고, 나머지 장비는 시그널을 띄운 채로 기다린다.
그래서 먼저 보낸 인터럽트가 처리되면 기다렸던 인터럽트의 시그널을 받아서 처리해준다.
(13-1, 23분, 프로세스 강의록 마지막의 인터럽트 핸들러 처리 과정 다시 설명해주심)
I/O 수행 방법
프린터에 어떤 문자열을 출력하는 상황을 가정하고 I/O를 수행하는 구체적인 방법 3가지를 정리해본다.
먼저 공통적으로는 프린트 시스템 콜을 호출하면, 시스템 콜은 디바이스 컨트롤러에게 동작을 지시한다.
이때 디바이스 컨트롤러에게 동작을 지시할 때는 운영체제의 일부이면서, 디바이스의 동작을 어떻게 지시하고 어떻게 처리해야 할 지 잘 알고있는 디바이스 드라이버가 실행되어 대신 동작을 지시한다.
Programmed I/O
먼저 데이터를 보관할 커널 버퍼에 유저 버퍼에 있는 내용을 옮긴다.
(커널에서 유저 버퍼에 접근하는 것이 복잡하기 때문)
다음으로 출력할 모든 문자열에 대해 반복하면서 프린터의 상태가 준비될 때까지 프린터의 디바이스 컨트롤러 안에 있는 컨트롤 레지스터를 확인하며 READY가 될 때까지 기다렸다가 준비되면 데이터를 출력하는 과정을 반복한다.
이때 컨트롤 레지스터의 값에 접근할 때는 마치 메모리에 있는 변수 값을 읽듯이 접근하고 있는 모습을 통해, 컨트롤 레지스터의 주소를 표현할 때 memory mapped I/O 방식을 사용하고 있음을 유추할 수 있다.
최종적으로 모든 데이터를 출력한 뒤에는 유저모드로 돌아간다.
그런데 이 방법은 프린터의 상태를 확인하기 위해 반복적으로 반복문을 돌면서 레지스터의 값을 체크하고 있다.
이렇게 계속 상태를 체크하면서 기다리는 방법을 busy waiting 또는 polling 이라고 한다.
이는 CPU를 현명하게 사용하는 방법이 아니기 때문에 다른 방식이 등장하였다.
Interrupt-Driven I/O
(a)는 프린트 시스템 콜이 호출되면 실행하는 내용으로,
먼저 출력할 내용을 유저 버퍼에서 커널 버퍼로 복사하는 것은 똑같다.
다음으로는 enable_interrupts() 를 호출하여 인터럽트를 활성화 시킨다.
그리고 프린터 상태가 READY가 될 떄까지 기다렸다가 컨트롤 레지스터 중 데이터 레지스터에 출력할 첫번째 문자를 넣으면 프린터가 활성화되고, 마지막으로 스케줄러를 호출한다.
스케줄러를 호출하면 이 프로세스 자체를 blocked 상태로 빠뜨리게 된다.
프린터가 출력을 마치고 인터럽트 신호를 보내면 인터럽트 서비스 프로시저로서 (b) 가 실행된다.
이때는 프린터 상태가 READY 라는 뜻이므로, 카운트 값을 체크해서 다 출력해서 count = 0 이 아니라면 다음 문자를 컨트롤 레지스터 중 데이터 레지스터에 쓰고 카운트를 감소시킨다.
다음으로 인터럽트를 처리했다는 뜻으로 ACK를 응답하고, 인터럽트 상태에서 빠져나간다.
만약 모든 데이터를 다 출력해서 count = 0 이 되었다면 자신을 block 상태에서 빼달라는 뜻으로 unblock_user() 메서드를 호출한다.
이 방법은 첫 번째 방법보다는 CPU를 덜 사용하나, 8번이나 인터럽트를 걸고 처리해야 한다는 부담이 존재한다.
그래서 이 문제를 해결하고자 다음 방법이 등장했다.
I/O Using DMA
이 방법은 위에서 설명한 과정을 CPU 대신 DMA가 대신 실행하고, CPU는 그동안 다른 일을 처리하는 방식이다.
(a)는 프린트 시스템 콜이 호출할 때 실행되는 코드이다.
먼저 유저 버퍼의 데이터를 커널 버퍼로 똑같이 복사한 뒤, DMA 컨트롤러를 셋업 (프로그래밍) 한다.
그리고 스케줄러에게 자신을 block 상태로 빠뜨리고 다음 프로세스를 실행하도록 한다.
(b)는 똑같이 인터럽트 서비스 루틴이 발생할 때 실행되는 메서드지만, 이 메서드가 호출되었다는 것은 DMA가 알아서 모든 작업을 끝냈다는 뜻과 같으므로 인터럽트에 대해 ACK를 보내고, 블락 상태에서 현재 프로세스를 빼낸 뒤, 인터럽트 루틴을 종료한다.
Device Driver
위 그림은 최종적으로 I/O 디바이스를 컨트롤 하는 계층을 보여준다.
먼저 메모리의 유저 스페이스에 있는 유저 프로세스가 시스템 콜을 호출하여 I/O 동작을 요청하면,
커널 스페이스에 있는 운영 체제 프로그램이 실행되고, 시스템 콜 호출에 맞는 디바이스 드라이버를 실행한다.
디바이스 드라이버는 자신이 담당하는 디바이스 컨트롤러를 조작하여 실제 디바이스의 동작을 제어한다.
'CS > 운영체제' 카테고리의 다른 글
[운영체제] 23. Disk Format & Arm Scheduling (2) | 2024.12.10 |
---|---|
[운영체제] 22. Disk & RAID (0) | 2024.12.10 |
[운영체제] 20. UNIX V7 File System (0) | 2024.12.08 |
[운영체제] 19. 파일 시스템 구현 (0) | 2024.12.08 |
[운영체제] 18. File & Directory (0) | 2024.12.07 |