OS Concepts 10th

[OS] Chapter 1-2. 컴퓨터 시스템의 구성

patrick-star 2023. 7. 24. 08:31
728x90

현대의 범용 컴퓨터 시스템은 아래 그림과 같이 하나 이상의 CPU버스를 통해 연결된 여러 가지 장치 컨트롤러로 구성된다.
각각의 장치 컨트롤러는 특정 유형의 장치들을 담당한다.

ex) 1개의 시스템 USB 포트 ⇒ 여러 장치를 연결할 수 있는 USB 허브에 연결할 수 있음

장치 컨트롤러(device controller)는 일부 로컬 버퍼 저장소 & 특수 목적 레지스터 집합을 유지관리한다.
또한 제어하고 있는 주변 기기들로컬 버퍼 저장소 간의 데이터가 이동할 수 있도록 한다.

일반적으로 OS에는 각 장치 컨트롤러(device controller)마다 장치 드라이버(device driver)가 있다.
장치 드라이버device controller의 동작에 대해 잘 알고 있고 나머지 OS에 대해 장치에 관한 일관된 인터페이스를 제공한다.

CPU와 device controller는 병렬로 실행되어서 메모리 사이클을 놓고 경쟁한다. 공유메모리에 질서있게 접근하기 위해서 메모리 컨트롤러는 메모리에 대한 접근(access)을 동기화한다.

이번 절에서는 컴퓨터 시스템의 주요한 측면 3가지를 통해 시스템의 작동 방식의 기본적인 내용에 대해서 다루도록 하겠다.

1. 인터럽트(Interrupts)

ex) 일반적인 컴퓨터 작업(입출력 수행하는 프로그램)을 생각해보자.

  • device driver는 device controller의 적절한 레지스터에 값을 저장
  • device controller는 레지스터의 내용을 읽어와서 수행할 작업을 결정
  • device controller가 device -> local buffer로 데이터 전송을 시작
  • 데이터 전송이 완료 -> device controller는 device driver에게 작업이 완료되었음을 알린다.

그 이후로 원하는 동작을 하면되는데
이때, device driver에게 작업을 완료했다는 사실을 알리기 위해서 interrupt를 사용한다.

1.1 개요

Interrupt는 많은 곳에서 사용되며 OS와 HW의 상호작용 방식의 핵심적인 부분이다.
때문에 HW는 어느 순간이든 시스템 버스를 통해서 CPU에 신호를 보내 인터럽트를 발생시킬 수 있다.

  • CPU가 인터럽트를 받으면
  • CPU는 하던 동작을 멈추고 즉시 고정된 위치로 실행을 옮긴다. (여기서 고정된 위치는 일반적으로 인터럽트를 위한 서비스 루틴이 위치한 시작 주소를 갖고 있다)
  • 인터럽트 서비스 루틴(ISR)이 실행된다.
  • ISR의 실행이 완료되면 CPU는 인터럽트로 인해 중간에 멈췄던 부분에서부터 연산을 재개한다

이러한 내용은 아래 그림에 잘 나와있다. (애니메이션이 있다??)

인터럽트는 컴퓨터 구조에 있어 중요한 부분이다. 각각의 컴퓨터 설계는 자신만의 인터럽트 메커니즘을 갖고 있지만 공통적인 기능이 몇 가지 있다.

  • 인터럽트는 반드시 적절한 서비스 루틴으로 제어(control)를 전달한다.
    • 이러한 전달을 관리하는 직접적인 방법은 인터럽트 정보를 조사하는 일반적인 루틴을 호출하는 방법이다.
    • 그런 다음에 이 루틴은 인터럽트 고유의 핸들러(handler)를 호출한다.
  • 하지만, 인터럽트는 매우 빈번하게 발생하기 때문에 빠르게 처리되어야 한다.
    • 빠른 속도를 위해서 인터럽트 루틴에 대한 포인터들의 테이블을 이용할 수 있다.
    • 이렇게 되면, 중간 과정없이 테이블을 통해 간접적으로 인터럽트 루틴이 호출될 수 있다.

일반적으로 포인터들의 테이블은 하위 메모리(low memory)에 저장된다 (처음 100개 정도의 위치).
이 위치에는 여러 장치에 대한 ISR의 주소값이 들어있다.

인터럽트 요청이 오면, 인터럽트를 유발한 장치에 대한 ISR의 주소값을 제공하기 위해서 주소값의 배열(인터럽트 벡터)가 유일한 번호로 인덱싱 된다. Windows, UNIX 같은 서로 다른 OS는 이런 방법으로 인터럽트를 처리한다.

또한 인터럽트 구조는 인터럽트된 모든 장치의 상태 정보를 저장해야 한다. 그래야 인터럽트를 수행하고 나서 해당 정보를 통해 원래 실행했던 시점에서부터 다시 동작을 실행할 수 있다.
인터럽트를 실행하고 나서 저정되어 있던 복귀 주소값을 프로그램 카운터에 저장하고 인터럽트에 의해 중단되었던 연산이 인터럽트가 발생하지 않은 것처럼 다시 시작된다.

1.2 구현

기본적인 인터럽트 메커니즘

  1. CPU 하드웨어에는 CPU가 명령어 실행을 완료할 때 마다 감지하는 Interrupt-request line이 있다.
  2. controller가 인터럽트 요청 라인에 signal을 보내면,
  3. CPU가 이를 감지해서 인터럽트 번호를 읽고 이 번호를 인터럽트 벡터의 인덱스로 사용해서 Interrupt handler routine으로 넘어간다.
  4. 해당 인덱스와 관련된 주소에서 실행을 시작한다.

인터럽트 핸들러는...

  • 작업 중에 변경될 상태를 저장
  • 인터럽트의 원인을 확인
  • 필요한 처리를 수행
  • 상태 복원을 수행
  • CPU를 이전 실행 상태로 되돌리기 위해서 return_from_interrupt 명령어를 실행

이 과정을 정리해서 표현하면 아래와 같다. (그림은 그 과정을 요약한 그림)

  1. 장치 컨트롤러가 Interrupt request line 신호를 보냄으로써interrupt를 발생시킨다.
  2. 그러면, CPU는 인터럽트를 인식해서
  3. 그걸 가지고 인터럽트 핸들러로 이동해서(dispatch)
  4. 핸들러가 장치에 서비스를 함으로써 원래 있던 인터럽트를 치운다.

앞서 설명한 기본적인 인터럽트 메커니즘은 CPU가 비동기 이벤트에 반응할 수 있도록 해준다. 하지만, 현대의 OS에서는 더욱 정교한 인터럽트 처리 기능이 필요하다.

  1. 중요한 처리를 하는 중에는 인터럽트 처리를 연기해야 한다.
  2. 장치의 적절한 인터럽트 핸들러로 효율적으로 dispatch할 방법이 필요하다.
  3. OS가 우선순위가 높은 인터럽트와 낮은 인터럽트를 구분해서 적절하게 수행할 수 있도록 multilevel 인터럽트가 필요하다.

최신 컴퓨터 HW에서는 CPU와 인터럽트 컨트롤러 HW가 위 3가지 기능을 제공한다.

대부분의 CPU는 2개의 인터럽트 요청 라인을 갖고 있다.

  1. Non-maskable interrupt : 복구할 수 없는 메모리 오류와 같은 이벤트를 위해 예약되어 있는 요청 라인
  2. maskable : 인터럽트 되지 않아야 하는 중요한 명령을 실행하기 전에 CPU에 의해서 꺼질 수 있다. 해당 요청 라인을 통해 장치 컨트롤러가 서비스를 요청한다.

앞서 벡터 방식의 인터럽트 기법을 통해 하나의 인터럽트 핸들러가 모든 인터럽트 소스를 검색할 필요가 없었다.
하지만, 실제로는 인터럽트 벡터의 주소 개수보다 더 많은 장치가 존재한다. 즉, 인터럽트 벡터의 주소 뿐만 아니라 다른 장치들을 추가적으로 검색해야 할 필요가 있는 것이 현실이다.

그래서 일반적으로 인터럽트 체인(Interrupt Chaining)을 사용한다.
인터럽트 체인에서는 인터럽트 벡터의 각각의 요소들이 인터럽트 핸들러의 리스트의 헤드를 가리킨다.

  • 인터럽트가 발생함
  • 그러면, 요청을 서비스할 수 있는 핸들러를 발견할 때 까지 리스트와 대응되는 핸들러를 하나씩 호출한다.

이러한 구조를 통해 커다란 인터럽트 테이블의 overhead하나의 인터럽트 핸들러로 디스패치 하는 비효율을 절충한다.

cf) Intel 프로세서의 인터럽트 벡터 설계 (0~31 : 오류 상황을 알리는데 사용 / 32 ~ 255 : 그외)

또한, 인터럽트 메커니즘은 인터럽트 우선순위 레벨(Interrupt Priority Level)을 구현한다.
이 레벨을 통해 CPU는 모든 인터럽트를 마스킹하지 않고도 우선순위가 낮은 인터럽트 처리를 연기하고 우선순위가 높은 인터럽트를 먼저 실행시키도록 할 수 있다.

1.3. 인터럽트 요약

인터럽트(interrupt)는 ...

  • 최신 OS에서 비동기 이벤트를 처리하기 위해 사용된다.
  • 장치 컨트롤러 및 HW 오류로 인해 인터럽트가 발생
  • 긴급한 작업을 먼저 수행하기 위해서 최신 컴퓨터에서는 인터럽트 우선순위 시스템을 사용

2. 저장장치 구조 (Storage Structure)

CPU는 메모리에서만 명령어를 로드할 수 있으므로 모든 프로그램은 먼저 메모리에 로드되어있어야 한다.
범용 컴퓨터는 프로그램의 대부분을 메인메모리(RAM)에서 가져온다.

cf) load(로드) : 어떤 데이터나 프로그램을 주기억장치(메모리)로 복사하여 사용할 수 있도록 하는 과정

또한 컴퓨터는 EEPROM(Electrically Erasable Programmable Read-Only Memeory) 및 기타 형태의 펌웨어(Firmware)를 사용한다.

  • EEPROM : 변경이 가능하지만 자주 변경할 수는 없다. 속도가 느려서 정적 프로그램 및 데이터를 포함하고 있다.
  • 펌웨어 : 쓰기 작업이 자주 발생하지 않고 비휘발성인 저장장치

모든 형태의 메모리는 Bytes의 배열로 제공된다. 각각의 바이트는 자신의 주소를 갖고 있다.
일련의 load, store를 통해 상호작용이 이뤄진다.

  • load : 메인메모리에서 CPU 내부의 레지스터로 1Byte 또는 1 Word를 옮기는 작업 => 쉽게 말하면 '읽기'
  • store: CPU 내부의 레지스터의 내용을 메인메모리로 옮기는 작업 => 쉽게 말하면 '쓰기'
    뿐만 아니라 CPU는 프로그램 카운터에 저장된 위치부터 실행하기 위해서 자동으로 메인메모리로 부터 명령어를 load한다.

폰 노이만 구조 시스템에서 실행되는 전형적인 명령-실행 사이클은
처음에 명령어를 메모리에서 가져와서 그 명령어를 명령 레지스터(instruction register)에 저장하는 구조다.
그리고 나서 그 명령을 해독해서 피연산자를 메모리로 부터 가져와서 내부 레지스터에 저장할 수 있다.
피연산자에 대한 명령이 실행되고 나면, 그 결과를 다시 메모리에 저장한다.

여기서 메모리 장치는 단지 일련의 메모리 주소만을 인식한다는 점을 기억하자. 메모리 주소들이 어떻게 생성되었고 무엇인지는 알지 못한다. 즉, 우리는 메모리 주소가 프로그램에 의해 어떻게 생성되었는지를 무시할 수 있다. 그저 프로그램에 의해 생성된 일련의 메모리 주소에만 관심이 있다.

이상적으로는 프로그램과 데이터가 메인 메모리에 영원히 존재하면 좋다. 하지만, 아래 2가지 이유 때문에 불가능하다.

1. 메인 메모리는 크기가 작다.
2. 메인 메모리는 휘발성 저장장치다.

위에서 언급한 메인 메모리의 단점을 보완하기 위해 컴퓨터 시스템은 보조기억장치를 제공한다. 일반적으로 HDD, NVM, SSD가 있다.
대부분의 프로그램은 메인 메모리에 load 될 때 까지 보조저장장치에 저장된다. 이에 대해서는 11장에서 자세히 다루도록 하겠다.

cf) 3차 저장장치 : CD, Blu-ray와 같이 다른 장치에 저장된 자료를 저장하기 위해 사용하는 장치. 속도가 매우 느리고 용량이 충분이 크다.

아래 그림과 같이 다양한 저장장치 시스템은 저장 용량, 액세스 시간에 따라 계층 구조로 구성할 수 있다.

저장장치는 운영체제 구조에서 중요한 역할을 하기 때문에 교재에서 자주 언급된다. 앞으로 사용할 용어는 다음과 같다.

  • 메모리(memory) : 휘발성 저장장치를 일컫는 말. 특정 유형의 저장장치를 강조해야 한다면 명시적으로 표현할 것이다.(ex. 레지스터)
  • NVS (Non-Volatile Storage): 전원이 꺼져도 내용을 유지하는 저장장치. 대부분 보조기억장치를 의미한다.
    • 기계적(Mechanical) : HDD, 광 디스크, 홀로그램 저장 장치, 테이프 (이 중에서 특정 유형을 강조해야 한다면 명시적으로 해당 용어를 사용할 것)
    • 전기적(Electrical) : 플래시 메모리, FRAM, NRAM, SSD. 보통 전기적 저장장치를 NVM으로 언급할 것이다. 마찬가지로 특정 유형을 강조해야 한다면 명시적으로 해당 용어를 사용할 것
    • 기계적 저장장치는 일반적으로 전기적 저장장치보다 싸고 용량이 크다. 다만, 속도가 느리다.

완전한 저장장치 시스템을 설계하기 위해서는 앞서 다뤘던 모든 요소들이 균형을 맞춰야 한다.
가능한 한 많은 저렴한 비휘발성 저장장치를 제공하는 동시에 필요한 만큼만 비싼 메모리를 사용해야한다.
캐시는 두 구성요소 간에 액세스 시간이나 전송 속도의 차이가 큰 경우 성능 향상을 위해 설치될 수 있다.

3. 입출력 구조 (I/O structure)

OS 코드의 상당부분은 시스템의 안정성, 성능에 대한 중요성, 장치들의 다양한 특성때문에 I/O 관리에 할애된다.

I/O를 통해 대량의 데이터를 이동시킬때 높은 overhead를 유발할 수 있다. 그래서 DMA(Direct Memory Access)를 사용한다.

  • 장치에 대한 버퍼, 포인터, 입출력 카운트를 설정한 후
  • device controller(장치 제어기)는 CPU의 개입없이
    1) 메모리로부터 자신의 버퍼 장치로
    2) 자신의 버퍼로부터 메모리로 ⇒ 데이터 블록 전체를 전송한다.

위와 같이 블록 전체의 전송이 완료될 때 마다 인터럽트가 발생한다. 그렇게 device controller가 CPU의 개입 없이 데이터 블록을 전송하는 동안 CPU는 다른 작업을 수행하기 때문에 좀 더 효율적으로 I/O를 통해 데이터를 전송할 수 있다.

아래 그림은 컴퓨터 시스템의 구성요소 간의 상호작용을 표현한 그림이다.

'OS Concepts 10th' 카테고리의 다른 글

[OS] 1-10. 계산환경  (0) 2023.08.06
[OS] Chapter 1-4. 운영체제의 작동  (0) 2023.08.01
[OS] Chapter 1-3. 컴퓨터 시스템 구조  (0) 2023.07.28
[OS] Chapter 1-1. 운영체제가 할 일  (0) 2023.07.21
[OS] Chapter 1. 서론  (0) 2023.07.21