[운영체제] I/O device

.·2023년 6월 6일

앞에서는 Concurrency에 대한 내용을 다루었습니다. 이제 운영체제의 마지막 주요 이슈인 영속성(Persistence)에 대해 공부하겠습니다. 영속성은 실제 영속성을 유지하는 장치의 특성File System으로 나뉠 수 있습니다.
먼저 본격적인 영속성에 들어가기에 앞서 먼저 입력/출력 장치의 개념과 운영체제가 이 장치들과 상호 작용 하는 방법을 알아보겠습니다.

1. 전체 시스템 구조

전체적인 구조는 계층적으로 이루어진다.
CPU와 메인 메모리는 메모리 버스로 연결되어 있고, 여러가지 장치들은 I/O 버스로 연결되어 있다. PCI에는 그래픽이나 고성능 I/O 장치들이 연결되고, SCSI나 SATA 또는 USB는 주변장치용 버스에 연결된다.
이런 계층적인 구조가 필요한 이유물리학적인 이유와 비용 때문이다. 버스가 고속화되려면 더 짧아져야 하는데 고속의 버스는 여러 장치들을 수용할 공간이 없고 비용이 비싸다. 따라서 그래픽 카드와 같은 고성능 장치들은 CPU에 가깝게 배치하고, 디스크처럼 느린 주변 장치들은 주변 장치 I/O 버스에 연결한다.

2. 표준 I/O Device에 대하여

표준 장치에는 위 그림과 같이 2개의 중요한 요소가 있다. 첫 번째 요소시스템의 다른 구성 요소에게 제공하는 하드웨어 인터페이스다. 소프트웨어가 인터페이스를 제공하듯이 하드웨어도 인터페이스를 제공하여 시스템 소프트웨어가 동작을 제어할 수 있도록 해야한다. 그렇기 때문에 모든 하드웨어 장치들은 특정한 상호 동작을 위한 방식과 명시적인 인터페이스를 갖고 있다.
두 번째 요소인터페이스를 구현한 내부 구조이다. 구현 방법에 따라 다르지만 일반적으로 시스템에게 제공하는 장치에 대한 추상화를 정의하는 책임을 가지고 있다. (매우 단순한 장치들은 하나 또는 몇 개의 하드웨어 칩을 사용하여 기능을 구현할 것이고 좀 더 복잡한 장치는 단순한 CPU와 범용 메모리 그리고 장치에 특화된 칩들을 사용하여 목적에 맞는 동작을 한다.) 최신 RAID 컨트롤러는 수십만 줄에 달하는 펌웨어라는 소프트웨어가 하드웨어 내부의 동작을 정의한다.

위의 그림에서 장치의 인터페이스는 세 개의 레지스터로 구성되어 있다. 상태 레지스터는 장치의 현재 상태를 읽을 수 있으며 명령어 레지스터는 장치가 특정 동작을 수행하도록 요청할 때, 그리고 데이터 레지스터는 장치에 데이터를 보내거나 받거나 할 때 사용한다. 이 레지스터들을 읽거나 쓰는 것을 통해 운영체제는 장치의 동작을 제어할 수 있다.

장치가 특정 동작을 할 때에 운영체제와 장치간에 가장 기본적인 방식의 상호 동작 과정을 알아보자. 4단계로 정리할 수 있다.
첫 번째는 반복적으로 장치의 상태 레지스터를 읽어서 명령의 수신 가능 여부를 확인한다. (폴링 방식) 두 번째는 운영체제가 데이터 레지스터에 어떤 데이터를 전달한다. 예를 들어 장치가 디스크라면 디스크 블럭에 해당하는 데이터를 전달한다. (데이터 전송에 CPU가 직접 관여하는 경우는 programmed I/O) 세 번째로 운영체제가 명령 레지스터에 명령어를 기록한다. 이 레지스터에 명령어가 기록되면 데이터는 이미 준비되었다고 판단하고 명령어를 처리한다. 마지막으로 운영체제는 디바이스가 처리를 완료했는지를 확인하는 폴링 반복문을 돌면서 기다린다.

위와 같은 방식은 매우 비효율적이다. 각각의 역할에 따라 다음과 같은 방식이 존재한다.

Status Check

  • polling
  • interrupts

Data transfer

  • Programmed I/O (PIO)
  • DMA

Control

  • Special instructions (e.g. x86의 in & out)
  • memory-mapped I/O (e.g. load & store)

Status Check

1. Polling

OS는 장치가 준비될때까지 status 레지스터의 값을 반복적으로 읽으면서 기다린다.
이 방법은 간단하고 잘 작동한지만, 단순히 장치가 준비되기를 기다리면서 CPU시간을 낭비한다. 다른 ready 프로세스로 switching하는 것이 CPU 이용률 측면에서 더 좋을 것이다.

2. Interrupts

I/O요청을 한 프로세스를 sleep 상태로 만들고 다른 프로세스로 context switch한다. 장치가 끝날 때 인터럽트를 발생시켜 I/O를 기다리고 있는 프로세스를 깨운다.

Polling vs Interrupts

인터럽트 방식이 항상 좋은 것은 아니다. 장치가 빠르게 수행될 경우 인터럽트가 더 느릴 수 있다. context switch의 비용이 발생하기 때문이다. 따라서 장치의 처리 속도가 빠를 경우 polling 방식이 더 좋을 수 있다.

Data transfer

1. Programmed I/O

CPU가 데이터의 이동에 직접 관여하는 방식으로 CPU는 메모리에서 장치로 데이터를 복사하는데 많은 시간을 소비한다.

2. DMA (Direct Memory Access)

CPU 개입 없이 장치가 메모리에 직접 접근하여 데이터를 복사한다. OS는 DMA에 메모리 상의 데이터 위치전송할 데이터의 크기와 대상 장치를 프로그램한다. DMA 동작이 끝나면 DMA 컨트롤러가 인터럽트를 발생시켜 운영체제가 전송이 완료되었음을 알 수 있도록 한다.

Control

디바이스와 상호작용 하는 방법

1. I/O instructions: OS가 I/O 명령을 명시적으로 이용하여 특정 장치의 레지스터에 데이터를 보내는 방식으로 동작한다. Ex) x86에서 in과 out 명령어

2. memory-mapped I/O: 장치의 레지스터들이 마치 메모리 상에 존재하는 것처럼 만드는 방법이다. 따라서 특정 장치의 레지스터를 접근하기 위해서 운영체제는 해당 주소에 read(읽기), store(쓰기)를 하면 된다. 즉 load/store 명령어를 메인 메모리 대신 장치로 연결한다.

3. 디바이스에 중립적인 OS 구축 방법

어떻게 운영체제를 장치 중립적으로 만들고, 장치와의 상호작용을 위한 상세 내용을 운영체제로 부터 숨길 수 있을까?
추상화를 사용한다. 운영체제 최하위 계층의 일부 소프트웨어는 장치의 동작 방식을 반드시 알고 있어야 한다. 이 소프트웨어를 디바이스 드라이버(device driver)라고 부르며 장치와의 상세한 상호작용을 캡슐화한다.

Device Drivers

  • 각각의 I/O 장치를 제어하기 위한 장치에 특화된 코드
    • 잘 정의된 모델과 표준적인 인터페이스 정의가 필요하다.
  • 구현
    • 커널과 정적으로 연결
    • Boot time동안 시스템으로 선택적으로 로드
    • 실행시간에 시스템에 동적으로 로드(특히 hot pluggable devices)
  • 다양성이 문제
    • 너무 많은 디바이스
    • 각각은 자신만의 프로토콜을 가짐

추상화의 문제

  • 만약 특수 기능을 많이 갖고 있는 장치가 있다면, 이 기능은 범용적인 인터페이스 층에서는 사용할 수 없을 것이다.

  • OS의 70%이상이 device drivers를 위한 코드이다.

    • 시스템에 장치를 plug하면 device drivers는 요구된다.
    • 이 device drivers는 kernel crash의 주요 원인이 되기도 한다. 상당한 버그를 포함할 수 있음

여기까지 전반적인 I/O 장치와 OS와의 상호작용에 대해 알아보았습니다.
다음에는 영속성 유지하는 장치인 HDD에 대해 알아보겠습니다.
profile
공부하고 정리하는 블로그

0개의 댓글