빙응의 공부 블로그

[OSTEP 영속성(persistence)]I/O 장치 본문

CS/운영체제

[OSTEP 영속성(persistence)]I/O 장치

빙응이 2025. 5. 27. 22:02

 

📝서론 

영속성에 들어가기 앞서 먼저 입력/출력 장치의 개념을 알아보자

 

핵심질문 : 어떻게 I/O를 시스템에 통합할까?
시스템에 I/O를 어떻게 통합해야 하는가?
일반적인 방법은 무엇인가?
어떻게 효율적으로 통합할 수 있을까?

 

📝시스템 구조 

논의를 시작하기 위해, 위의 시스템 구조를 살펴보자

 

이 그림에서는 CPU와 주메모리가 메모리 버스로 연결되어 있다.

몇 가지 장치들은 범용 I/O 버스에 연결되어 있다.

또한 현대의 많은 시스템들은 PCI(메인보드 연결 인터페이스)를 사용하고 있다.

 

마지막으로 아래의 SCSISATA 또는 USB와 같은 주변장치용 버스가 있다.

버스 이름 속도 연결 장치 특징
메모리 버스 가장 빠름 CPU ↔ RAM 고속, 고가, 짧은 거리
PCI / PCIe (고속 I/O 버스) 빠름 그래픽카드, SSD, 고속 네트워크 카드 등 병렬(PCI) → 직렬(PCIe), CPU와 가까움
SATA / USB / SCSI (저속 I/O 버스) 느림 디스크, 마우스, 키보드 등 저렴하고 길게 연결 가능, 대역폭 낮음

 

 

이러한 개념은 계층적인 개념 구조이다. 이러한 구조가 필요한 이유는 물리학적인 이유와 비용 때문이다.

 

  1. 버스가 고속화되려면 짧아져야 하지만 수용할 공간이 적다.
  2. 고속의 성능을 내는 버스를 만드는 기술은 꽤나 비싸다.

그렇기에 계측적인 구조를 택한 것이다. 

 

📝표준 장치

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

표준 장치는 위의 그림처럼 인터페이스와 내부 구조로 구성되어 있다.

 

첫번째로 인터페이스가 있다.

인터페이스는 상호작용에 사용된다. (소프트웨어와 마찬가지로 하드웨어도 인터페이스를 제공해야 동작을 제어할 수 있다.)

 

두번째로 내부 구조가 있다.

구현 방법에 따라 다르지만 시스템에서 제공하는 장치에 대한 추상화를 정의한다.

이러한 내부 동작 정의는 펌웨어라는 소프트웨어가 정의한다.

 

📝표준 방식

표준 장치의 인터페이스는 세 개의 레지스터로 구성되어 있다.

  • 상태 레지스터 : 현재 상태를 읽을 수 있다.
  • 명령어 레지스터 : 장치가 특정 동작을 수행하도록 요청할 수 있다.
  • 데이터 레지스터 : 장치에 데이터를 보내거나 받거나 할 때 사용한다. 
while(STATUS == BUSY) // 장치가 바쁜 상태가 아닐 때 까지 대기
    ;  
DATA = data; // 데이터를 DATA 레지스터에 쓰기
COMMAND = command;  // 명령어를 COMMAND 레지스터에 쓰기
while(STATUS == BUSY)   // 요청을 처리하여 완료될 때 까지 대기
    ;

 

📌 I/O 요청 방식

  1. I/O 장치의 상태 레지스터를 폴링하여 사용가능한 상태인지 확인한다.
  2. Programmed I/O: 운영체제(메인 CPU)가 데이터 레지스터에 데이터 전달
  3. 운영체제가 명령어 레지스터에 명령어 기록
  4. 디바이스가 요청을 처리했는지 폴링하여 확인

사실 이러한 방식은 매우 간단하지만 매우 비효율적이다. 

  1. 첫 번째 문제는 매우 비효율적인 폴링을 사용한다는 것이다.
    • 다른 프로세스에서 CPU를 양도하지 않고, 장치가 동작으로 완료하는 동안 계속 루프를 돌면서 장치상태를 체크하고 있기 때문이다.
  2. 두 번째 문제는 대기 시간에 아무것도 하지 않아 CPU 낭비가 발생한다. 

📝인터럽트를 이용한 CPU 오버헤드 개선

이미 수년 전에 엔지니어들이 장치와의 상호작용을 개선하기 위해 인터럽트라는 것을 개발하였다. 

인터럽트는 이미 다루었다. 디바이스를 폴링하는 대신 운영체제는 입출력 작업을 요청한 프로세스를 블록 시키고 CPU를 다른 프로세스에게 양도한다. 장치가 작업을 끝마치고 나면 하드웨어 인터럽트를 발생시키고 CPU는 운영체제가 미리 정의해 놓은 인터럽트 서비스 루틴 또는 간단하게 인터럽트 핸들러를 실행한다. 이 핸들러는 운영체제 코드의 일부이며 입출력 요청의 완료, I/O를 대기 중인 프로세스 깨우기 등을 담당한다.\

 

정리해보자면 

 

  • 과거에는 장치 상태를 계속 폴링(polling) 하며 CPU가 기다렸음 → CPU 자원 낭비 심함.
  • 인터럽트는 장치가 작업을 마치면 CPU에 알려주는 신호.
  • 운영체제는 I/O 작업 요청 프로세스를 블록(block) 시키고 CPU를 다른 프로세스에게 넘김.
  • 작업 완료 시 인터럽트가 발생 → 인터럽트 핸들러(ISR) 실행 → 작업 완료 처리 및 대기 프로세스 깨움 → 다시 작업 계속.

 

더 쉽게 그림으로 이해해보자

위 그림은 CPU에서 프로세스 1이 일정 시간 동안 실행되다 데이터를 읽기 위해 I/O 요청을 발생시킨다.

인터럽트가 없다면 시스템은 I/O가 완료될 때까지 지속적으로 폴링할 것이다. 

 

 

위 그림은 프로세스 1의 요청이 디스크에서 처리되는 동안에 운영체제는 프로세스 2가 CPU를 점유한다. 

디스크 요청이 완료되면 인터럽트가 발생하면서 다시 프로세스 1을 실행한다. 

 

인터럽트가 무조건적을 좋아보이지만 항상 최적의 해법은 아니기에 유의하자
만약 풀링이 1번만해도 되는 매우 빠른 작업만 있다면 인터럽트 방식의 오버헤드가 더 클 것이다. 

 

또한 네트워크처럼 엄청난 양의 패킷이 들어오는 경우 인터럽트만 처리하느라 다른 작업을 못하는 현상인 라이블록이 발생할 수 있다.

 

📌개선 기법

  • 짧은 시간 폴링 후 인터럽트 사용 : 장치가 빠르면 폴링, 느리면 인터럽트
  • 인터럽트 병합 : 인터럽트 신호를 잠시 모아서 한꺼번에 발생 -> 오버헤드 감소

 

📝DMA를 이용한 효율적인 데이터 이동

앞서 설명한 표준 방식은 하나의 문제가 더있다.

많은 양의 데이터를 디스크로 전달하는 programmed I/O를 사용하면 CPU는 또 다시 단순 작업을 처리하게 된다.

 

  • PIO 방식이란?
    • CPU가 직접 데이터 전송을 수행하는 방식
    • 결과적으로 CPU가 단순 반복 작업에 붙잡혀 있어서 다른 중요한 일을 할 수 없다.

 

 

이 문제에 대한 해법은 직접 메모리 접근 방식(DMS)로 해결 가능하다.

  • DMS 방식이란?
    • CPU 대신, 전용 하드웨어가 데이터 전송을 수행하는 방식이다.
    • CPU는 데이터의 출발지, 목적지, 크기만 알려주고 나서 자유롭게 다른 작업을 수행한다.

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

이번에는 운영체제가 I/O 장치와 실제로 어떻게 통신하는지 알아보자.

 

  1. 명시적인 I/O 명령어 사용 (Port-mapped I/O, 또는 I/O-mapped I/O)
    • 개념 :
      • CPU가 특수한 I/O 명령어를 사용해서 장치와 직접 통신
      • 장치가 메모리 주소 공간과 완전히 분리된 I/O 포트 주소 공간을 가짐
      • 특정 포트 번호를 통해서 해당 장치의 레지스터에 접근한다. 
    • 특징 :
      • 특권 명령어이기 때문에 운영체제만 실행 가능 
      • 보안 : 일반 사용자 프로그램이 직접 I/O 수행 불가 
    • 단점 :
      • 별도의 명령어 세트가 필요
      • 메모리 접근과는 다르게 I/O를 다루는 별도의 흐름이 필요 
  2. Memory-Mapped I/O (메모리 맵 I/O)
    • 개념 :
      • 장치의 레지스터를 메모리 공간 안에 매핑한다.
      • 즉, 장치를 메모리처럼 접근하는 것
    • 동작 방식:
      • CPU가 어떤 메모리 주소에 데이터를 쓰면, 실제론 그 주소가 장치의 레지스터를 의미
    • 특징 : 
      • 메모리에 접근하듯이 장치를 제어
      • 별도의 명령어 없이 장치 제어 가능
    • 단점 :
      • 메모리 주소 공간을 일부 차지
      • 메모리 캐시와 충돌을 피하기 위한 하드웨어 적 처리 필요

 

📝디바이스 드라이버

최종적으로 다룰 문제는 서로 다른 인터페이스를 갖는 장치들과 운영체제를 연결시키는 가능한 일반적인 방법을 찾는 것이다.

 

우리가 할 것은
"운영체제는 수많은 장치들과 어떻게 잘 연동하면서도, 그 장치들에 종속되지 않을 수 있는가? "
이다.

 

📌 디바이스 드라이버

  • 디바이스 드라이버는 운영체제가 특정 하드웨어 장치와 통신할 수 있도록 해주는 소프트웨어 모듈이다.
  • 디바이스 드라이버는 하드웨어의 세부 명령어와 프로토콜을 가지고 있다.
    • 운영체제가 직접 하드웨어를 다루지 않도록 도와주는 중간 계층 역할을 한다.
  • 결과적으로 운영체제는 "디스크야, 이 파일 좀 읽어줘!" 같은 추상적인 요청만으로 실제 하드웨어 제어가 가능하다는 것이다.

 

위의 계층처럼  상위 사용자는 하위 레이어의 인터페이스만 사용하여 원하는 기능을 수행할 수 있게한다.

각각의 레이어는 캡슐화 되어 있기에 어떻게 동작하는지 알 수 없다.

 

📌 물론 단점도 있다.

  • 이 추상화 덕분에 장치 종류 없이 동작하지만...
  • 반대로, 특수 기능이 있는 장치는 그 기능 사용이 불가능하다. 

 

 

📝요약

🧩 1. I/O는 왜 중요한가?

  • 컴퓨터는 입력 없이 실행되거나 출력 없이 결과를 내지 못하면 아무 의미가 없습니다.
  • 따라서 운영체제가 I/O 장치를 어떻게 시스템에 통합할지, 효율적으로 관리할지가 중요합니다.

🧱 2. 시스템 구조 (그림 39.1 기반)

  • CPU와 메모리는 고속 메모리 버스로 연결됨.
  • 다양한 I/O 장치는 계층적 버스 구조(PCI, SATA, USB 등) 로 연결됨.
  • 왜 계층 구조인가?
    • 물리적 한계: 고속 버스는 짧고, 장치를 많이 연결하기 어려움.
    • 경제적 이유: 고속 버스는 비용이 큼. 느린 장치는 저렴한 버스로도 충분.

⚙️ 3. 표준 장치 모델 (그림 39.2 기반)

  • 하드웨어 장치도 소프트웨어처럼 인터페이스를 제공해야 함.
  • 표준 인터페이스는 다음과 같은 세 가지 레지스터로 구성:
    1. STATUS: 장치의 상태 확인
    2. DATA: 데이터 읽고 쓰기
    3. COMMAND: 장치에게 명령 전달

🔁 4. PIO(Programmed I/O) 방식

  • 운영체제가 직접 CPU를 이용해 장치와 데이터 송수신.
  • 방식:
    1. 상태 레지스터 STATUS가 BUSY인지 반복 확인 → 폴링(polling)
    2. 데이터 레지스터 DATA에 데이터 전달
    3. 명령어 레지스터 COMMAND에 명령 기록
    4. 다시 STATUS를 체크하며 완료 확인
  • 문제점: CPU가 바쁘게 계속 장치 상태만 확인 → 비효율적

🚨 5. 인터럽트 기반 I/O

  • 폴링의 단점 해결책: 장치가 완료되면 스스로 CPU를 인터럽트(interrupt) 함.
  • 운영체제는:
    • I/O 작업을 요청한 프로세스를 블로킹(blocking) 상태로 만들고
    • 다른 프로세스에게 CPU를 양도
    • 장치가 완료되면 인터럽트 서비스 루틴(ISR) 실행
  • 장점: CPU 낭비 줄이고 I/O와 연산 중첩 실행 가능
  • 주의사항:
    • 너무 빠른 장치에는 인터럽트보다 폴링이 더 낫다.
    • 지나치게 많은 인터럽트 → 시스템 과부하 (ex: livelock)

⚡ 6. DMA(Direct Memory Access) 방식

  • 많은 데이터를 I/O 장치에 전달할 때, 매번 CPU가 관여하면 비효율적.
  • DMA는 장치가 직접 메모리와 통신할 수 있게 하여 CPU 개입 없이 데이터 이동 가능.
  • 장점: CPU를 더 중요한 작업에 사용할 수 있음 → 성능 개선