크래프톤정글/운영체제

[OSTEP][영속성] CH35 대화 + CH36 I/O 장치

아람2 2024. 11. 17. 20:19
반응형

CH35 영속성에 대한 대화 

영속성 Persistence 의 사전적인 의미는 "곤경과 반대에도 불구하고 굳게 또는 완고하게 행동 방침을 유지"한다는 것이다 
컴퓨터가 멈추고 디스크가 고장나고 전원이 꺼지더라도 정보를 그대로 유지시키는 것은 엄청나게 많은 노력이 필요하다 
 

CH36 I/O 입출력 장치 

I/O 는 컴퓨터 시스템에서 상당히 중요한 부분이다, 컴퓨터 시스템을 유용하게 쓰려면 입력과 출력이 모두 필요하다 

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

36.1 시스템 구조

왼쪽 그림과 같이 고전적 구조에서는, CPU 와 주메모리가 메모리 버스로 연결되어 있다 
그래픽이나 다른 고성능 I/O 장치들은 범용 I/O 버스 (현대에는 PCI 사용) 에 연결될 수 있다 
아래에는 SCSISATA 또는 USB 와 같은 주변장치용 버스가 있고, 디스크, 마우스, 키보드와 같은 느린 장치들이 연결된다 
 
이렇게 계층 구조가 필요한 이유는, 물리적인 이유와 비용 때문이다 
버스가 고속화되려면 더 짧아야 하는데 짧은 메모리 버스는 여러 장치들을 수용할 공간이 없을 뿐더러 만드는 기술도 꽤나 비싸다 
그래서 그래픽 카드와 같은 고성능 장치들은 CPU 에 가깝게 배치하였고, 느린 성능의 장치는 그보다 멀리 배치하는 계층 구조를 사용한다 
디스크처럼 느린 장치를 주변 장치 I/O 에 연결하여 얻는 이득 중 하나는 많은 장치들을 연결할 수 있다는 것이다 
 
오른쪽 그림 (인텔 Z270 칩셋 구조) 처럼 현대식 시스템은 특별한 칩셋들과 고속의 점대점 연결 방식의 사용을 늘리고 있다 
CPU는 메모리 시스템과 가장 가까이 연결되어 있고, 그래픽 카드도 고속의 인터페이스로 연결되어 있다 
CPU는 인텔의 DMI Direct Media Interface 기술을 통해 I/O 칩에 연결되어 있고, 나머지 장치들도 다른 종류의 방식으로 연결된다 
오른쪽에는 하드디스크들이 eSATA 방식으로 연결되어 있는데, ATA (AT Attachment, 고급 기술 결합)가 발전하여 SATA (Serial ATA) 와 외장형 인터페이스인 eSATA (External SATA) 로 개발되었다 
아래쪽에는 여러 개의 USB Universal Serial Bus 인터페이스가 있어 느린 성능의 장치들을 연결하기 위해 사용된다 
왼쪽에는 시스템에 또 다른 고성능 장치를 연결할 수 있는 PCIe Peripheral Component Interconnect Express, 주변 장치 연결 익스프레스 인터페이스가 있으며, NVMe (영속 저장 장치 등) 와 같은 고속 저장 장치 뿐 아니라 그래픽 카드, 네트워크 카드 등도 연결된다 

36.2 표준 장치

장치에서 중요한 두 가지 요소 중 하나는 시스템의 다른 구성 요소에게 제공하는 하드웨어 인터페이스다 
모든 하드웨어 장치들은 특정한 상호 동작을 위한 방식과 명시적인 인터페이스를 가지고 있다 
두번째 요소는 내부 구조이다, 구현 방법에 따라 다르지만 장치의 기능을 추상화하여 시스템에 제공하는 책임을 가지고 있다 
최신 RAID 컨트롤러는 수십만 줄에 달하는 펌웨어 Firmware 라는 소프트웨어가 하드웨어 내부의 동작을 정의하고 있다 

36.3 표준 방식 

놀랍게도 모든 하드웨어가 자체 레지스터들을 가진다. 이러한 레지스터는 소프트웨어가 하드웨어를 제어할 수 있도록 도와주는 중요한 인터페이스 역할을 해준다

장치 인터페이스는 3개의 레지스터로 구성되어 있고, 이 레지스터들을 읽거나 쓰는 것을 통해 동작을 제어할 수 있다 
* 상태 Status 레지스터 - 장치의 현재 상태를 읽을 때 사용 
* 명령어 Command 레지스터 - 장치가 특정 동작을 수행하도록 요청할 때 사용 
* 데이터 Data 레지스터 - 장치에 데이터를 보내거나 받거나 할 때 사용 
 
장치가 운영체제를 대신하여 특정 동작을 할 때 운영체제와 장치 간에 일어날 수 있는 상호 도작의 과정은 아래와 같은 방식을 따른다 

While (STATUS == BUSY)
	; // 장치가 바쁜 상태가 아닐 때까지 대기 
데이터를 DATA 레지스터에 쓰기 
명령어를 COMMAND 레지스터에 쓰기 
	(그러면 장치가 명령어를 실행한다)
While (STATUS == BUSY)
	; // 요청을 처리하여 완료할 때까지 대기

1) 반복적으로 장치의 STATUS 레지스터를 읽어서 명령의 수신 가능 여부를 확인한다 - 폴링한다 polling
2) 운영체제가 DATA 레지스터에 어떤 데이터를 전달한다 - 데이터 전송에 메인 CPU 가 관여하는 경우를 Programmend I/O 라고 한다 
3) 운영체제가 COMMAND 레지스터에 명령어를 기록한다
   이 레지스터에 명령어가 기록되면 데이터는 이미 준비되었다고 판단하고 명령어를 처리한다 
4) 운영체제는 디바이스가 처리를 완료했는지를 확인하는 폴링 반복문을 돌면서 기다린다 
 
 이 기본 방식은 간단하고 제대로 동작하지만, 매우 비효율적이다 
다른 프로세스에게 CPU를 양도하지 않고, 장치의 동작이 완료되기 전까지 계속 루프를 도는, 비효율적인 폴링을 사용하고 있다 
입출력 장치는 무척 느리고, 대기하는 중에 특별히 따로 하는 일도 없고, CPU 시간을 많이 소모하게 된다 

핵심 질문 - 폴링 사용 비용을 어떻게 피하는가 
어떻게 하면 자주 폴링을 하지 않으면서 운영체제가 장치의 상태를 확인할 수 있고,
장치를 관리하는 CPU의 오버헤드를 줄일 수 있을까? 

 

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

인터럽트를 사용해서, 디바이스를 폴링하는 대신 입출력 작업을 요청한 프로세스를 블록시키고 CPU를 다른 프로세스에게 양도할 수 있다 
장치가 작업을 끝마치고 나면 하드웨어 인터럽트를 발생시키고 CPU는 운영체제가 미리 정의해 놓은
인터럽트 서비스 루틴 Interrupt Service Routine (ISR) - 인터럽트 핸들러 Interrupt Handler 를 실행한다 
인터럽트 핸들러는 운영체제 코드의 일부로, 입출력 요청의 완료나 I/O 를 대기 중인 프로세스 깨우기 등을 담당한다 
 
사용률을 높이기 위한 핵심 방법 중 하나는 인터럽트를 활용하여 CPU 연산과 I/O 를 중첩시키는 것이다 

왼쪽 그림은 CPU 에서 프로세스 1 이 실행되다가, 디스크의 데이터를 읽기 위해 I/O 요청을 발생시킨 상황이다 
인터럽트가 없다면 시스템은 I/O 요청이 완료될 때까지 반복적으로 장치의 상태를 폴링 (그림에서 p) 하고
디스크가 요청의 처리를 완료해야 프로세스 1 이 다시 동작할 수 있다 
오른쪽 그림은 프로세스 1 의 요청이 디스크에서 처리되는 동안 프로세스 2 를 CPU 에서 실행시키는 상황이다 
디스크 요청이 완료되어 인터럽트가 발생하면 운영체제가 프로세스 1 을 깨워 다시 실행시킬 수 있다 
 
하지만 인터럽트가 항상 최적의 해법은 아니다, 다른 프로세스로 문맥을 교환하고 인터럽트를 처리한 후 다시 I/O 를 요청한 프로세스로 문맥 교환하는 것은 매우 비싼 작업이다 
그러므로 빠른 장치라면 폴링이 최선이고, 느리다면 인터럽트를 사용하여 중첩 시키는 것이 최선이다 
 
인터럽트를 사용하지 않은 또 다른 이유는 네트워크 환경에서 대량의 패킷이 도착하는 상황을 예로 들 수 있다  
각 패킷이 도착할 때마다 인터럽트가 발생한다면, 운영체제가 인터럽트를 처리하느라 사용자 프로세스의 요청을 처리할 수 없는 무한반복 Livelock 에 빠질 가능성이 있다 
 
또 다른 인터럽트 기반의 최적화 기법은 병합 Coalescing 이다 
CPU 에 인터럽트를 전달하기 전까지 잠깐 기다렸다가 발생시키면, 기다리는 동안에 다른 요청들고 끝나게 되기 때문에 
여러 번의 인터럽트를 발생시키는 대신 인터럽트를 CPU에 한번만 전달하게 된다 
이 방법으로 인터럽트 처리의 오버헤드를 줄일 수 있지만, 너무 오래 기다리면 요청에 대한 지연 시간이 늘어날 수 있다 

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

많은 양의 데이터를 디스크로 전달하기 위해 Programmed I/O (PIO) 를 사용하면 또 다시 단순 작업 처리에 CPU가 소모된다 
그래서 직접 메모리 접근 방식 Direct Memory Access, DMA 을 이용하면 CPU 의 간섭 없이 메모리와 장치 간의 전송이 가능하다 
DMA 동작이 끝나면 DMA 컨트롤러가 인터럽트를 발생시켜 전송이 완료되었다고 운영체제에게 알린다 

  • 하지만 DMA 방식에서는 이 모든 과정을 DMA 엔진에 위탁함으로써 CPU의 개입을 최소화했다. I/O가 발생하면 CPU는 DMA 엔진에 메모리에 위치한 데이터의 주소, 데이터의 크기, 장치의 주소 등을 전달하여 DMA가 모든 데이터 전송을 전담하게 한다
  • DMA 방식에서는 총 인터럽트가 두 번 발생할 수 있다. 장치가 작업을 완료했을 때 작업 완료를 CPU에게 알리기 위해 인터럽트를 한번, 이후에 DMA 엔진이 메모리로 데이터 전송을 완료했을 때 CPU에게 알리기 위해 또 한번 총 두번의 인터럽트가 한 번의 과정에서 발생할 수 있다. 이를 개선해주기 위해 Chained DMA라는 기술을 도입하여, DMA가 장치 작업 완료됐을 때 자동으로 메모리에 데이터를 옮겨주기까지 하게 설정해주었다. 이 경우에 인터럽트는 DMA가 메모리에 데이터 전송을 완료했을 때 딱 한번 발생한다

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

핵심 질문 - 장치와 어떻게 통신하는가?
하드웨어가 장치와 통신하는 방법은 무엇인가? 명시적인 명령어들이 있는가? 아니면 다른 방법이 있는가? 
  • 명시적 I/O 명령어 사용 간단하게 장치들을 위한 I/O 명령어를 사용해서 운영체제가 원하는 장치의 레지스터에 접근해 데이터를 읽고 쓸 수 있다. 명령어들은 대부분 **‘특권 명령어 previledged instruction’**이며, 오로지 운영체제만이 이 명령어를 통해 장치들과 직접적으로 소통할 수 있다.
  • 맵 입출력 memory mapped I/O 장치의 레지스터를 메모리 주소 상에 올려서 운영체제가 메모리에 접근해서 장치의 레지스터 값을 읽고 쓸 수 있게 해준다. 이 방식은 추가적인 명령어를 사용할 필요 없이 메모리에 관한 명령어(load, store)를 사용할 수 있다는 장점이 있다.


 

 

 

+ anonymous Page 는 런타임 중에 생기는 공간을 저장해 주기 위한 페이지이다, 반대말은 file backed page 

그리고 file backed 는 파일의 이름이 있지만 anonymous 는 이름이 없다는 의미도 있다 

 

반응형