728x90
27. I/O 장치
27.1 시스템 구조
- 시스템 설계자들은 계층 구조를 택하여 고성능 장치들을 CPU에 가깝게 배치하고 느린 성능의 장치는 그보다 멀리 배치했다. 디스크처럼 느린 장치를 주변 장치 I/O 버스에 연결하여 얻는 이득이 많은데, 그 중 하나는 많은 장치를 연결할 수 있다는 것이다.
- CPU는 인텔의 DMI(Direct Media Interface) 기술을 통해 I/O 칩에 연결되어 있으며, 나머지 장치들이 이 칩에 여러 다른 종류의 연결 방식을 사용하여 연결된다.
27.2 표준 장치
- 두 개의 중요한 요소가 있다.
- 첫 번째는 시스템의 다른 구성 요소에게 제공하는 하드웨어 인터페이스다. 소프트웨어가 인터페이스를 제공하듯이 하드웨어도 인터페이스를 제공하여 시스템 소프트웨어가 동작을 제어할 수 있도록 해야한다. 그렇기 때문에 모든 하드웨어 장치들은 특정한 상호 동작을 위한 방식과 명시적인 인터페이스를 갖고 있다.
- 장치가 갖고 있어야 하는 두 번째 요소는 내부 구조이다. 구현 방법에 따라 다르지만 장치의 기능을 추상화하여 시스템에 제공하는 책임을 가지고 있다. 매우 단순한 장치들은 하나 또는 몇 개의 하드웨어 칩으로 기능이 구현되어 있고, 좀 더 복잡한 장치는 단순한 CPU와 범용 메모리 그리고 장치에 특화된 칩들을 사용하여 목적에 맞게 동작한다. 최신 RAID 컨트롤러는 수십만줄에 달하는 펌웨어(firmware) 라는 소프트웨어가 하드웨어 내부의 동작을 정의하고 있다.
27.3 표준 방식
- 표준 장치의 인터페이스(단순화 된)는 세 개의 레지스터로 구성되어있다. 상태(status) 레지스터는 장치의 현재 상태를 읽을 수 있으며 명령어(command) 레지스터는 장치가 특정 동작을 수행하도록 요청할 때, 그리고 데이터(data) 레지스터는 장치에 데이터를 보내거나 받거나 할 때 사용한다. 이 레지스터들을 읽거나 쓰는 것을 통해 운영체제는 장치의 동작을 제어할 수 있다.
- 운영체제와 장치간에 일어날 수 있는 상호동작의 과정은 네 단계로 이루어진다.
- 먼저 반복적으로 장치의 상태 레지스터를 읽어서 명령의 수신 가능 여부를 확인한다. 이 동작을 장치에 대해 폴링(polling)한다고 표현한다. 두 번째는 운영체제가 데이터 레지스터에 어떤 데이터를 전달한다. 데이터 전송에 메인 CPU가 관여하는 경우를 programmed I/O 라고 부른다. 세 번째로 운영체제가 명령 레지스터에 명령어를 기록한다. 이 레지스터에 명령어가 기록되면 데이터는 이미 준비되었다고 판단하고 명령어를 처리한다. 마지막으로 운영체제는 디바이스가 처리를 완료 했는지를 확인하는 폴링 반복문을 돌면서 기다린다(성공과 실패를 알리는 에러코드를 받게된다.)
- 이 기본 방식은 간단하지만 제대로 작동한다. 하지만 매우 비효율적이다. 이 방식이 갖고 있는 첫 번째 문제는 매우 비효율적인 폴링을 사용하고 있다는 점이다. 다른 프로세스에게 CPU를 양도하지 않고, 장치의 동작이 완료되기 전까지 계속 루프를 돌면서 장치 상태를 체크한다. 이를 폴링이라 한다. 입출력 장치는 무척 느리다. 그리고 대기하는 중에 특별히 따로 하는 일이 있는것도 아니다. CPU 시간을 많이 소모하게 된다.
27.4 인터럽트를 이용한 CPU 오버헤드 개선
- 디바이스를 폴링하는 대신 운영체제는 입출력 작업을 요청한 프로세스를 블록시키고 CPU를 다른 프로세스에게 양도한다. 장치가 작업을 끝마치고 나면 하드웨어 인터럽트를 발생시키고 CPU는 운영체제가 미리 정의해놓은 인터럽트 서비스 루틴(ISR : Interrupt Service Routine) 또는 간단하게 인터럽트 핸들러(Interrupt handler)를 실행한다. 이 핸들러는 운영체제 코드의 일부이다. 인터럽트 핸들러는 입출력 요청의 완료, I/O를 대기중인 프로세스 깨우기를 담당한다. 깨어난 프로세스가 작업을 계속할 수 있도록 한다.
- 사용률을 높이기 위한 핵심 방법 중 하나는 인터럽트를 활용하여 CPU 연산과 I/O를 중첩시키는 것이다.
- 인터럽트가 항상 최적의 해법은 아니라는 것에 유의해야 한다. 대부분 작업이 한 번의 풀링 만으로 끝날 정도로 매우 빠른 장치라고 해보자. 이 경우에 인터럽트를 사용하면 시스템이 느려지게 된다. 다른 프로세스로 문맥을 교환하고 인터럽트를 처리한 후 다시 I/O를 요청한 프로세스로 문맥 교환 하는 것은 매우 비싼 작업이다. 그러므로 빠른 장치라면 폴링이 최선이고, 느리다면 인터럽트를 사용하여 중첩 시키는 것이 최선이다. 만약 장치의 속도를 모른다거나, 빠를 때도 있고 느릴 때도 있는 장치라고 한다면, 짧은 시간 동안만 폴링을 하다가 처리가 완료되지 않으면 인터럽트를 사용하는 하이브리드 방식을 채용하는 것이 최선일 것이다. 이와 같이 두 단계 접근법으로 양쪽의 장점만 취할 수 있다.
- 인터럽트를 사용하지 않는 다른 이유는 네트워크 환경에서 찾아볼 수 있다. 패킷이 대량으로 도착할 때를 생각해보면, 각 패킷이 도착할 때만 인터럽트가 발생된다. 이 경우 인터럽트만 처리하다가 운영체제가 사용자 프로세스의 요청을 처리할 수 없도록 만드는 무한 반복(live lock)에 빠질 가능성 있다. 이 경우, 폴링을 사용하면 시스템의 상황을 보다 효율적으로 제어할 수 있다. 웹 서버가 새로운 패킷이 도착했는지 검사하기 전에 사용자 요청들을 좀 더 처리할 수 있기 때문이다.
- 또 다른 인터럽트 기반의 최적화 기법은 병합(coalescing)이다. 이 경우는 CPU에 인터럽트를 전달하기 전에 잠시 기다렸다가 인터럽트를 발생시킨다. 기다리는 동안에 다른 요청들도 끝나게되기 때문에 여러번 인터럽트를 발생시키는 대신 인터럽트를 한 번만 CPU에 전달하게 된다. 이 방법으로 인터럽트 처리의 오버헤드를 줄일 수 있다.
27.5 DMA를 이용한 효율적인 데이터 이동
- 많은 양의 데이터를 디스크로 전달하기 위해 programmed I/O(PIO)를 사용하면 또 다시 단순 작업 처리에 CPU가 소모된다. 다른 프로세스를 처리하기 위해 사용될 수 있는 많은 시간이 허비된다.
- 직접 메모리 접근 방식(DMA)를 통해 문제를 해결한다. DMA엔진은 시스템 내에 있는 특수장치로서 CPU의 간섭없이 메모리와 장치간에 전송을 담당한다.
- 데이터를 장치로 전송한다고 했을 때 운영체제는 DMA 엔진에 메모리 상의 데이터 위치와 전송할 데이터의 크기와 대상 장치를 프로그램 한다. 그 시점에 전송하기 위해 할 일은 끝나기 때문에 운영체제는 다른 일을 진행할 수 있다. DMA 동작이 끝나면 DMA 컨트롤러가 인터럽트를 발생시켜 전송이 완료되었다고 운영체제에게 알린다.
27.6 디바이스와 상호작용하는 방법
- 장치와 통신하는 두 가지 기본적인 방법
- 첫 번째는 I/O 명령을 명시적으로 사용하는 것이다. 이 명령어들은 운영체제가 특정 장치 레지스터에 데이터를 전송할 수 있는 방법을 제공한다. 데이터를 장치에 보내야 하는 경우에는 호출자가 데이터가 저장된 레지스터와 장치를 지칭하는 포트를 지정한다. 명령어를 실행하면 원하는 동작이 실행된다.
- 이 명령어들은 대부분 특권 명령어(Privileged instruction)들이다. 운영체제가 장치를 제어하는 역할을 한다. 때문에 운영체제만이 장치들과 직접 통신할 수 있다.
- 두 번째는 맵 입출력(memory mapped I/O)을 사용하는 것이다. 이 접근법에서 하드웨어는 장치의 레지스터들이 마치 메모리상에 존재하는 것 처럼 만든다. 특정 레지스터를 접근하기 위해서 운영체제는 해당 주소에 load(읽기) 또는 store(쓰기)를 하면 된다. 하드웨어 load/store 명령어가 주 메모리를 향하는 대신 장치로 연결되도록 한다.
27.7 운영체제에 연결하기 : 디바이스 드라이버
- 최종적으로 다룰 문제는 서로 다른 인터페이스를 갖는 장치들과 운영체제를 연결시키는 가능한 일반적인 방법을 찾는 것이다.
- 추상화(abstraction)라는 고전적인 방법을 사용하여 이 문제를 해결할 수 있다. 운영체제 최하위 계층의 일부 소프트웨어는 장치의 동작방식을 반드시 알고 있어야 한다. 이 소프트웨어를 우리는 디바이스 드라이버(device driver)라고 부르며 장치와의 상세한 상호작용은 그 안에 캡슐화 되어있다.
- Linux의 파일 시스템 소프트웨어 계층에서 파일 시스템은 어떤 디스크 종류를 사용하는지 전혀 알지 못한다. 파일 시스템은 범용 블록 계층(generic block layer)에 블록 read/write 요청할 뿐이다. 범용 블록 계층은 적절한 디바이스 드라이버로 받은 요청을 전달하며, 디바이스 드라이버는 특정 요청을 장치에 내리기 위해 필요한 일들을 처리한다.
- 응용 프로그램(파일 시스템 검사기(file-system checker) 또는 디스크 조각 모음(disk defragmentation 도구 등)) 이 파일 추상화를 사용하지 않고 직접 디스크의 블록에 읽고 쓸 수 있도록 하는 미가공 인터페이스(raw interface)도 표시되어 있다. 대부분의 시스템들은 저수준의 저장장치 관리 응용 프로그램을 지원하기 위해 이러한 인터페이스를 제공한다.
- 이러한 캡슐화는 단점도 있다. 특수 기능을 많이 갖고 있는 어떤 장치가 있다고 했을 때, 커널이 범용적인 인터페이스만을 제공할 수 밖에 없다면 그 많은 특수 기능들을 사용할 수 없게 된다
728x90
'책 > 운영체제' 카테고리의 다른 글
운영체제 29. Redundant Array of Inexpensive Disk(RAID) (0) | 2024.03.28 |
---|---|
운영체제 28. 하드 디스크 드라이브 (0) | 2024.03.27 |
운영체제 26. 이벤트 기반의 병행성(event-based concurrency) (1) | 2024.03.24 |
운영체제 25. 병행성 관련 버그 (0) | 2024.03.24 |
운영체제 24. 세마포어 (0) | 2024.03.23 |