Updated:

입출력 하드웨어의 원리

입출력 장치(I/O Devices)

 입출력 장치들은 블록 장치와 문자 장치로 나눌 수 있다.

  • 블록 장치(Block devices)
    • 정보를 각각이 자신의 주소를 가지는 고정된 크기의 블록들에 저장한다.
    • 공통적인 블록 크기는 512 Bytes 부터 32,768 Bytes 까지의 범위를 가진다.
    • 모든 전송은 하나 혹은 전체(연속적인) 블록들의 단위로 이루어진다.
    • 다른 블록들과 독립적으로 각 블록에 대한 read와 write가 가능하다.
    • 하드 디스크, CD-ROM, USB 메모리
  • 문자 장치(Character devices)
    • 일련의 문자들을 블록 구조와 관계없이 전송 혹은 받아들인다.
    • 주소지정이 불가능하고, 탐색 연산도 수행할 수 없다.
    • 프린터, 네트워크 인터페이스. 마우스, 디스크 같지 않은 다른 장치들
  • Other devices
    • 클록, 메모리 맵 화면

 입출력 장치는 속도 측면에 있어서 상당히 넓은 범위를 다루기 때문에 다양한 데이터 전송률에 대해 좋은 성능을 보이기 위해서는 소프트웨어가 일을 잘 처리해 주어야한다. 다음은 일반적인 입출력 장치들의 데이터 전송률을 보여준다. 이 장치들은 날이 갈수록 더 빨라지고 있다.

장치 컨트롤러

 입출력 장치는 전형적으로 기계적인 부분과 전기적인 부분으로 구성된다. 더 모듈화하고 일반적인 설계를 제공하기 위하여 두 부분을 분리하는 것은 가능하다. 전기 부분은 장치 컨트롤러(Device Controllers) 혹은 아답터라고 불린다. 많은 컨트롤러는 두 개, 네 개, 혹은 심지어 여덟 개의 동일한 장치를 조절할 수 있다.
 컨트롤러의 역할은 연속적인 비트 열을 바이트 단위의 블록으로 변환하고 필요한 오류 수정 작업을 수행한다. 블록의 바이트들은 일반적으로 먼저 컨트롤러 내부의 버퍼에 비트 단위로 모아진다. 그것에 대한 체크섬이 진행되고 블록이 오류가 없는 것으로 판명된 다음에야 메인 메모리로 복사될 수 있다.(make available to main memory)

메모리 맵 I/O

 각각의 컨트롤러는 CPU와의 통신에 사용되는 몇 개의 레지스터를 가지고 있다. 컨트롤 레지스터(Control registers)에 대한 쓰기에 의해서 운영체제는 장치에게 데이터를 전송하고, 데이터를 받아들이고, 자신의 스위치를 켜고 끄는 혹은 다른 기능을 수행하는 명령을 내릴 수 있다. 컨트롤 레지스터로부터의 읽기를 통해 운영체제는 장치의 상태가 어떠한지, 새로운 명령을 받아들일 준비가 되어있는지 아닌지 등등에 대해 알 수 있다.
 컨트롤 레지스터에 더하여 많은 장치들은 운영체제가 읽고 쓸 수 있는 데이터 버퍼(Data buffer)를 가지고 있다. 예를 들어, 컴퓨터가 픽셀을 화면에 출력하는 공통적인 방법은 비디오 램을 갖는 것인데, 이는 기본적으로 프로그램들 혹은 운영체제가 쓰기를 할 수 있는 데이터 버퍼이다.
 따라서 CPU가 어떻게 컨트롤 레지스터들 그리고 장치 데이터 버퍼와 통신을 하느냐에 관한 궁금함이 발생한다. 세가지 방법이 존재한다.

  • (a)별도의(Separate) 입출력과 메모리 공간
    • 각 컨트롤 레지스터는 입출력 포트번호, 즉 8 또는 16비트 정수가 할당된다. 모든 입출력 포트들의 집합은 입출력 포트 공간을 형성하고 보호를 받는다. 그래서 평벙한 사용자 프로그램들이 접근할 수 없도록 한다.
    • IN REG, PORT“와 같이 특별한 입출력 명령어를 사용하여 CPU는 컨트롤 레지스터 PORT를 읽어들여 CPU 레지스터 REG 내에 그 결과를 저장할 수 있다.
      • IN R0,4 와 MOVE R0, 4는 완전히 다르다. 전자는 포트4의 내용을 읽고 R0에 넣는 반면, 후자는 메모리 워드 4의 내용을 읽고 그것을 R0에 넣는다.
    • OUT PORT, REG” 명령어들 사용하여 CPU는 REG의 내용을 컨트롤 레지스터에 쓸 수 있다.
      • OUT 7, R1은 CPU 레지스터 R1의 내용을 컨트롤 레지스터 7에 쓴다.
  • (b)메모리 맵 I/O
    • 모든 컨트롤 레지스터를 메모리 공간에 매핑하는 것이다.
    • 각각의 컨트롤 레지스터는 어떤 메모리도 할당되지 않은 유일한 메모리 주소가 할당된다. 대개 할당된 주소들은 주소 공간의 상위 지역이다.
  • (c)Hybrid
    • 메모리 맵 I/O 데이터 버퍼와 별도의 입출력 포트의 혼합된 메커니즘이다.
    • 펜티엄(Pentium)
      • 0에서 64K - 1까지의 입출력 포트 외에 추가로 640K 에서 1M - 1의 주소가 IBM PC 호환기종에서 장치 데이터 버퍼를 위해 예약된다.

메모리 직접 접근

 CPU가 메모리 맵 I/O를 가지던지 혹은 가지지 않을지라도 데이터를 교환하기 위하여 장치 컨트롤러에 주소를 지정할 필요가 있다. CPU는 입출력 컨트롤러로부터 한 번에 한 바이트씩의 데이터를 요청할 수 있으나 그렇게 하는 것은 CPU 시간을 낭비하게 된다. 그래서 DMA(메모리 직접 접근)라고 불리는 다른 메커니즘이 종종 사용된다.
 만약 DMA가 사용되지 않을 때 디스크 읽기가 발생하는지 알아보자. 먼저 디스크 컨트롤러는 드라이브로부터 순차적으로 하나의 블록을 전체 블록이 컨트롤러의 내부 버퍼에 들어올 때까지 비트 단위로 읽는다. 다음으로 읽기 오류가 발생하지 않았는지를 확인하기 위하여 체크섬을 계산한다. 그리고 나서 컨트롤러는 인터럽트(interrupt, 컨트롤러가 모든 전달이 완료되었다고 판단하면 완료를 알리는 것)를 발생시킨다. 운영체제가 실행을 시작할 때, 컨트롤러의 버퍼로부터 한 번에 한 바이트 혹은 한 워드씩 루프를 실행하면서 디스크 블록을 읽을 수 있는데, 각 반복 시 마다 컨트롤러 장치 레지스터로부터 한 바이트 혹은 워드를 읽어서 메인 메모리에 저장한다.
 DMA가 사용될 때는 과정이 달라진다. DMA 컨트롤러는 다음과같이 CPU와 독립적으로 시스템 버스에 접근할 수 있다.

 DMA 컨트롤러는 CPU에 의해서 쓰여지고 읽혀질 수 있는 여러 개의 레지스터들, 메모리 주소 레지스터, 바이트 카운트 레지스터, 여러 개의 컨트롤 레지스터,을 보유한다.

  • 먼저 CPU는 레지스터를 설정함에 의해서 DMA 컨트롤러를 프로그램 한다.(단계 1. CPU programs the DMA controller)
    • 이를 통해 무엇이 어디로 전송되어야 할지를 알게된다.
    • CPU는 또한 디스크 컨트롤러에게 디스크로부터 자신의 내부 버퍼로 데이터를 읽어오고 체크섬을 확인하라는 명령을 발동한다. 디스크 컨트롤러의 버퍼에 유효한 데이터가 들어왔을 때 DMA가 시작할 수 있다.
  • DMA 컨트롤러는 디스크 컨트롤러에 대한 읽기 요청을 버스 상에 발동하여 전송을 초기화한다.(단계 2. DMA requests transfer to memory)
    • 이 읽기 요청은 여느 다른 읽기 요청처럼 보인다. 디스크 컨트롤러는 그것이 CPU로부터 오는지 혹은 DMA 컨트롤러로부터 오는지 알지 못하고 또한 관심도 없다.
    • 일반적으로 쓰기가 이루어질 메모리 주소는 버스의 주소 라인상에 있어 디스크 컨트롤러가 자신의 내부 버퍼로부터 다음 워드를 페치(fetch)할 때 어디에 써야 할지를 알 수 있다.
  • 메모리에 대한 쓰기는 또 다른 표준 버스 주기다.(단계3. Data transferred)
  • 쓰기가 완료되었을 때, 디스크 컨트롤러는 답례 신호를 역시 버스를 통해 DMA 컨트롤러에게 보낸다.(단계4. Ack)
    • DMA 컨트롤러는 사용할 메모리 주소를 증가하고 바이트 카운트를 감소한다.

 지금까지 본 모델은 플라이바이 모드(fly-by-mode)라고 부르기도 하는데, DMA 컨트롤러가 장치 컨트롤러에게 데이터를 직접 메인 메모리로 전송할 것을 명령한다.

인터럽트 재방문

 전형적인 개인용 컴퓨터 시스템에서, 인터럽트 구조는 다음과 같다.

 하드웨어 레벨에서, 인터럽트는 다음과 같이 작동한다.

  • 입출력 장치가 주어진 일을 완료했을 때, 인터럽트를 발생한다.
    • 이는 그것에게 할당된 버스 라인에 시그널을 설정함으로써 가능하다. 이 시그널은 부모모드에 있는 인터럽트 컨트롤러 칩에 의해서 감지된 후 무엇을 할지를 결정한다.
  • 만약 대기하고 있는 다른 인터럽트들이 없다면, 인터럽트 컨트롤러는 인터럽트를 즉시 처리한다.
    • 만일 다른 하나가 진행 중 이라면 혹은 다른 장치가 버스에 있는 더 높은 우선 순위의 인터럽트 요청 라인에 요청을 했다면, 그 장치는 그냥 잠시 동안 무시된다. 이러한 경우에는 CPU가 서비스할 때까지 버스에 인터럽트 시그널 설정을 유지한다.
      • 인터럽트를 처리하기 위해 컨트롤러는 처리 받기를 원하는 장치를 지정하는 번호를 주소 라인을 통해 보내고, 이어 CPU를 인터럽트하기 위한 시그널을 설정한다. 인터럽트 시그널은 CPU가 현재 하고 있거나 무언가를 하려고 시작하는 것을 멈추게 한다.
      • 실행을 시작한 후 얼마 되지 않아서, 인터럽트 서비스 프로시저는 인터럽트 컨트롤러의 입출력 포트 중 하나에 특정 값을 씀으로써 인터럽트에 대한 답신을 하게된다.
      • 이 응답은 컨트롤러에게 이제 다른 인터럽트를 발생해도 좋다고 말해주는 역할을 한다. CPU가 다음 인터럽트를 처리할 준비가 될 때까지 이 응답을 지연하게 되면, 다수의 인터럽트들에 의해 발생할 수 있는 경쟁 조건을 회피할 수 있다.
  • 하드웨어는 서비스 프로시저를 시작하기 전에 항상 어떤 정보를 저장한다.
    • 대부분의 CPU들은 정보를 스택에 저장한다.

 오래된 시스템들에서는, 각 명령어의 실행이 종료된 다음에 마이크로프로그램 혹은 하드웨어가 기다리고 있는 인터럽트가 있는지를 검사했다. 만약 인터럽트가 있다면 프로그램 카운터와 프로그램 상태 규저어(PSW: Program Status Word)는 스택에 저장되고 일련의 인터럽트 과정이 시작된다. 인터럽트 핸들러가 실행된 다음에는, PSW와 프로그램 카운터가 스택으로부터 꺼내지고 이전의 처리과정이 계속된 다음에는, PSW와 프로그램 카운터가 스택으로부터 꺼내지고 이전의 처리 과정이 계속되는 역 프로시저가 발생한다.

입출력 소프트웨어의 원칙

 입출력이 수행될 수 있는 세 가지 기본적인 다른 방법이 있다.

프로그램 입출력

 입출력의 가장 간단한 형식은 CPU가 모든 일을 다 하도록 하는 것이다. 이러한 방법이 프로그램 입출력(programmed I/O)이다.

 프린터에 “ABCDEFGH”의 여덟 개의 문자열을 출력하기를 원하는 한 사용자 프로세스를 고려해보면, (a)에 보이는 것처럼 사용자 공간에 있는 버퍼에 있는 문자열을 조합한다.
 사용자 프로세스는 프린터를 오픈(open)하라는 시스템 호출을 통해 쓰기를 위한 프린터를 획득한다. 만일 프린터를 현재 다른 프로세스가 사용 중 이라면, 이 호출은 실패하고 오류 코드를 리턴 하거나 프린터가 가용할 때까지 블록할 수 있다. 프린터를 획득하면 사용자 프로세스는 운영체제에게 문자열을 프린터로 출력하라는 내용의 시스템 호출을 발생한다.
 운영체제는 버퍼에 있는 문자열을 커널 영역에 있는 배열에 복사한다. 그리고 나서 프린터가 현재 가용한지 알아 본다. 만약 가용하지 않는다면, 가용할 때까지 기다린다. 프린터가 가용하자마자 메모리 맵 I/O를 사용하는 이 예에서는 운영체제는 첫 번째 문자를 프린터의 데이터 레지스터로 복사한다. 이 조치는 프린터를 구동시킨다. (b)에서, 첫 번째 문자가 출력되었고 시스템이 “B”를 출력될 다음 문자로 표시한다.
 첫 번째 문자를 프린터로 복사하자마자, 운영체제는 프린터가 다른 것을 받을 준비가 되어 있는지 알아본다. 일반적으로 프린터는 자기의 상태를 나타내주는 레지스터를 가지고 있다. 데이터 레지스터에 쓰기를 하는 행위는 프린터를 준비되지 않음 상태로 만든다. 프린터 컨트롤러는 현재 문자를 처리하고 나면 프린터의 상태 레지스터의 특정 비트를 성정하거나 레지스터에 어떤 값을 넣음으로써 자신이 준비되어 있음을 표시한다.
 운영체제가 프린터가 다시 준비 상태가 되기를 기다린다. 준비가 되면 (c)처럼 다음 문자를 출력한다. 이 과정은 전체 문자열이 출력될 때까지 반복된다. 그러면 제어는 사용자 프로세스에게 돌아간다.
 다음은 운영체제가 다음으로 하는 조치이다.

 먼저 데이터는 커널에 복사된다. 그러면 운영체제는 한 번에 하나씩 문자를 출력하면서 반복 루프에 접어든다. 프로그램 입출력의 핵심적인 측면은, 한 문자를 출력한 다음에, CPU가 다른 것을 받아들일 준비가 되어있는지를 확인하기 위하여 계속적으로 장치에게 질문한다. 이러한 행동을 폴링(polling) 혹은 바쁜 대기(busy waiting)라고 불린다.
 프로그램 입출력은 단순하지만 모든 입출력이 완료될 때까지 CPU를 항상 구속한다는 단점이 있다. CPU가 해야 할 다른 일이 있는 시스템에서는 바쁜 대기는 비효율적이다.

인터럽트-구동 입출력

 문자를 버퍼링하지 않고 도착하는 대로 각각을 출력하는 프린터에 출력하는 경우를 보면, 그 프린터가 초당 100문자를 출력할 수 있다면, 각 문자의 출력에는 10msec가 소요된다. 이것은 각 문자가 프리터의 데이터 레지스터에 쓰여지고 나서 CPU는 다음 문자를 출력할 수 있을 때까지 10msec 동안 할 일 없는 루프를 돌면서 기다려야 한다는 것을 의미한다.
 CPU가 프린터가 준비될 때를 기다리는 동안 다른 것을 할 수 있도록 하는 방법은 인터럽트를 사용하는 것이다. 문자열을 출력하기 위한 시스템 호출이 발생했을 때, 버퍼는 커널 영역으로 복사되고 첫 번째 문자는 프린터가 문자를 받아드릴 준비가 되자 마자 프린터로 복사된다. 그 시점에 CPU는 스케줄러를 호출하여 다른 프로세스를 실행한다. 출력될 문자열을 요청했던 프로세스는 전체 문자열이 출력될 때까지 블록된다. 시스템 호출에 의해 이루어진 일은 다음 그림의 (a)이다.

 프린터가 문자를 출력하고 다음 것을 받아들일 준비가 되었을 때, 프린터는 인터럽트를 발생시킨다. 이 인터럽트는 현재 프로세스를 중단시키고, 그 상태를 저장한다. 그 다음에 프린터 인터럽트 서비스 프로시저가 실행된다. 이를 표현한 것이 (b)이다. 만약 출력할 문자들이 더 이상 존재하지 않으면 인터럽트 핸들러는 사용자를 언블록하기 위한 행동을 취하게 된다. 그렇지 않으면, 다음 문자를 출력하고, 인터럽트에 대한 답신을 하고, 그리고 인터럽트 이전에 실행하고 있던 바로 그 프로세스에게 돌아가서, 아까 실행 하던 곳을 이어서 실행한다.

DMA를 사용하는 입출력

 인터럽트-구동 입출력의 명백한 단점은 모든 문자마다 인터럽트가 발생한다는 것이다. 인터럽트는 시간이 걸리기 때문에, 어느 정도의 CPU 시간을 낭비하게 된다. 해결책은 DMA를 사용하는 것이다. 이는 DMA 컨트롤러가 CPU의 간섭 없이 프린터에 문자를 공급해주도록 하는 것이다. 본질적으로 DMA는 프로그램 입출력인데, CPU 대신 DMA 컨트롤러가 모든 일을 한다. 이러한 전략은 특별한 하드웨어를 필요로하지만, CPU를 입출력 동안에 자유롭게 하여 다른 일을 할 수 있도록 한다. 다음은 이 방식을 보여준다.

 DMA의 큰 장점은 인터럽트의 수를 문자당 한번에서 출력된 버퍼당 한번으로 줄일 수 있다는 점이다. 반면에 DMA 컨트롤러는 대개 주 CPU 보다는 느리다. 만일 DMA 컨트롤러가 장치를 최대의 속도로 구동할 능력이 없다면 혹은 CPU가 DMA 인터럽트를 기다리는 동안 특별히 할 일이 없다면, 인터럽트-구동 입출력 혹은 심지어 프로그램 입출력이 더 나을 수 있다. 그럼에도 대부분의 경우에 DMA는 가치 있다.

입출력 소프트웨어 계층

 입출력 소프트웨어는 다음처럼 대체로 네 계층으로 구성된다. 각각의 계층은 수행할 잘 정의된 기능과 인접 계층에 대한 잘 정의된 인터페이스를 가지고 있다. 기능성과 인터페이스는 시스템마다 다르다.

인터럽트 핸들러

 프로그램 입출력은 때떄로 유용한 반면에, 대부분의 입출력에 대해서는, 인터럽트는 회피할 수 없다. 따라서 운영체제의 최소한 부분만이 이를 인식할 수 있도록 해야한다. 가장 좋은 방법은 입출력이 완료되고 인터럽트가 발생할 때까지 입출력 연산을 시작한 드라이버를 블록하는 것이다.
 인터럽트가 발생할 때, 인터럽트 프로시저는 인터럽트를 처리하기 위하여 해야 할 일을 한다. 그래야 그것을 시작한 드라이버를 언블록할 수 있다. 인터럽트의 궁극적인 효과는 이전에 블록되었던 드라이버가 실행할 수 있게 되는 것이다.
 다음은 하드웨어 인터럽트가 완료된 후에 소프트웨어로 수행되어야 할 단계별 일이다. 이는 특정한 컴퓨터에서는 없을 수도 있고, 순서가 다를 수도 있다.

  • 인터럽트 하드웨어에 의해 저장되어지지 않은 모든 레지스터의 값을(PSW를 포함하여) 저장한다.
  • 인터럽트 서비스 프로시저를 위한 문맥을 설정한다.
    • TLB, MMU, 페이지 테이블을 설정하는 일을 포함할 수 있다.
  • 인터럽트 서비스 프로시저를 위한 스택을 설정한다.
  • 인터럽트 컨트롤러에 답신을 한다.
    • 만일 중앙 집중화된 인터럽트 컨트롤러가 없다면, 인터럽트를 다시 가능하게 한다.
  • 레지스터를 그들이 저장되었던 곳(스택)부터 프로세스 테이블에 복사한다.
  • 인터럽트 서비스 프로시저를 실행한다.
    • 이 프로시저는 인터럽트를 일으킨 장치 컨트롤러의 레지스터로부터 정보를 추출한다.
  • 어떤 프로세스를 다음에 실행할 것인지를 선택한다.
    • 만약 인터럽트가 블록되어있던 어떤 높은 우선순위 프로세스를 준비상태로 만들었다면 이제 이 프로세스가 선택될 수도 있다.
  • PSW를 포함하여 새로운 프로세스의 레지스터를 적재한다.
  • 새로운 프로세스의 실행을 시작한다.

디스크

 디스크는 회전하는 하나 또는 여러 개의 금속 플래터로 구성되어 있다. 기계적인 암이 한 구석으로부터 시작하여 플래터 전체 위를 움직인다. 정보는 디스크의 동심원들 위에 쓰여지게 된다. 암의 끝에는 헤드가 있으며, 각 해드는 트랙(track)이라고 부르는 동심원 상의 영역을 읽을 수 있다. 특정 위치에 있는 암에서 모든 트랙들의 모음이 실린더(cylinder)를 이룬다.
 각 트랙은 여러 개의 섹터(sector)로 나누어지는데 각섹터는 보통 512바이트 크기이다.

디스크 하드웨어

 디스크는 다양한 타입이 존재한다. 가장 일반적인 것은 자기 디스크이다.(하드디스크, 플로피 디스크) 이는 읽기와 쓰기가 동일하게 빠르다는 특징을 가지고 있는데, 이는 보조 메모리로서는 아주 이상적인 것이다.

자기 디스크
 자기 디스크는 실린더들로 구성되는데, 각각은 수직으로 쌓여진 헤드만큼이나 많은 트랙들로 구성된다. 트랙들은 섹터로 나뉘어지는데, 전형적으로 플로피 디스크에는 8에서 32개, 하드 디스크에는 수백 개의 섹터들이 존재한다. 헤드의 수는 1부터 16개 정도 까지이다.
 다음은 플로피 디스크와 하드디스크의 예를 보여준다.

 위 예에서 “Sectors per disk = Number of cylinders * Tracks per cylinder * Sector per track”이고, “Disk capacity = Sector per disk * Bytes per sector”이다. 평균 탐색 시간은 7배, 전송률은 1,300배 개선된 반면에 용량은 50,000배 정도 증가하였다.

Disk Geometry
 다음은 (a) 두 개의 구역이 있는 디스크의 물리적인 기하학적 구조와 (b) 이 디스크에 대해 가능한 가상 기하학 구조를 보여준다.

 (a) 디스크는 밖으로 나갈수록 섹터가 많아진며, 두 경우 모두 디스크는 192섹터를 가진다.
 PC의 경우 (x실린더, y헤드, z섹터)의 최대 값은 (65536, 16, 63)인데, 이는 최초의 IBM PC의 제약으로 인해 호환되기 위한 것 때문이다. 이 인자와 섹터당 512바이트로, 가능한 가장 큰 디스크는 31.5GB(65536 * 16 * 63 * 512)이다. 이 한계를 극복하기 위하여, 모든 현대의 디스크들은 논리 블록 주소라고 불리는 시스템을 지원하는데, 이는 디스크 섹터들이 0에서 시작하여 디스크의 기하학적 구조를 고려하지 않고 그냥 번호가 매겨진다.

RAID
 CPU 성능을 더 향상시키기 위하여 병렬 처리가 점점 더 많이 사용되고 있다. 디스크의 성능, 안정성 혹은 모두를 향상시켜줄 수 있는 여섯 개로 구성되는 디스크 구성안이 제안되었는데 이를 산업에 채택되었고 RAID(Redundant Array of Inexpensive(Independent) Disks)라 불리는 새로운 타입의 입출력 장치가 탄생하였다. 이와 반대되는 개념으로 SLED(Single Large Expensive Disk)가 있다.
 RAID에 숨겨져 있는 기본적인 아이디어는 컴퓨터, 통상 큰 서버, 옆에 디스크로 가득한 상자를 설치하여 디스크 컨트롤러가 카드를 RAID 컨트롤러로 대체하고, 데이터를 RAID에 복사하고, 정상 연산을 계속하는 것이다. 이러한 방식으로, RAID를 사용하기 위하여 소프트웨어를 변경할 필요는 없다.
 소프트웨어에게 하나의 디스크처럼 보이는 것 이외에 모든 RAID는 병렬 연산을 위하여 데이터가 드라이브에 분산되는 특성을 가지고 있다. 이것을 하기 위해 여러 다른 메커니즘들이 정의되었는데 RAID 레벨 0부터 RAID 레벨 5로 알려져있다. 다음 그림에서 백업용 및 패리티 드라이브는 짙게 보여진다. 참고로 1byte = 8bits, 1 nibble = 4bits이다.

 RAID 레벨 0는 위 그림의 (a)이다. 이것은 섹터 0에서 $k-1$이 스트립 0, 섹터 $k$에서 $2k-1$이 스트립 1을 구성하는 방식으로 각각 $k$개 섹터의 스트립으로 나뉘어진 RAID로(스트립 하나가 $K$개의 섹터로 이루어져 있다.) 시뮬레이션된 가상 단일 디스크를 보여준다. $K=1$일 경우에, 각 스트립은 한 섹터이다. $K=2$의 경우에 한 스트립은 두 개의 섹터이다. RAID 레벨 0구성은 (a)에 있는 네 개의 디스크 드라이브로 구성된 RAID처럼 연속적인 스트립을 드라이브에 라운드-로빈 방식으로 쓴다.
 이렇게 데이터를 여러 드라이브에 대해 분산하는 것을 스트라이핑(striping)이라 부른다. 예를 들어, 만약 소프트웨어가 스트립 경계에서 시작하는 네 개의 연속적인 스트립으로 구성된 데이터 블록을 읽기 위한 명령을 발동하면, RAID 컨트롤러는 이 명령어를 네 개의 디스크에 대해 각 하나씩, 네 개의 분리된 명령어로 분해하여 그들을 병렬로 수행하게 한다. 따라서 병렬 입출력을 수행하게 할 수 있다.
 RAID 레벨 0는 큰 요청과 가장 잘 동작하며 더 클수록 더 좋은 효과가 있다. 하지만 한 번에 한 섹터의 데이터를 끊임없이 요청하는 운영체제와 최악으로 동작한다. 결과는 올바르지만, 병렬성이 존재하지 않아 RAID로 얻을 수 있는 이익이 존재하지 않는다. 또한 중복성이 존재하지 않아 SLED보다 좋지 않다. 예를들어, RAID가 네 개의 디스크로 구성되어 있고, 각각의 평균 고장 시간은 20,000시간이면, 약 매 5,000시간에 한 번씩 드라이브가 고장 나게 될 것이고, 모든 데이터가 손실될 것이다. 20,000시간의 고장 시간을 갖는 SLED는 네 배나 더 안정적이다.
 (b)에서 보여지는 RAID 레벨 1이 진정한 RAID이다. 여기서는 모든 디스크들을 중복시켜 네 개의 주요 디스크들이 있고 네 개의 백업 디스크가 있다. 쓰기의 경우에 모든 스트립은 두 번 쓰여 진다(Duplicates all the disks). 읽기의 경우 각 복사물이 모두 사용될 수 있어 부하를 모든 드라이브에 분배하는 효과가 있다. 따라서 쓰기의 성능은 하나의 드라이브 보다 나은 점이 없지만, 읽기 성능은 두 배나 좋아졌다. 결함 허용은 뛰어나다. 만약 한 드라이브가 고장 나면, 복사본이 대신 사용된다. 회복은 단순히 새로운 드라이브를 설치해서 전체 백업 드라이브를 그것에 복사하면 된다.
 RAID 레벨 2는 워드를 기본으로 동작하며 심지어 바이트 단위로도 가능하다. 예를들어, 단일 가상 디스크의 각 바이트를 4비트 조각들로 분할하고, 각 조각에 Hamming 코드를 더해서 7비트 워드를(이 중 1, 2, 4비트는 패리티 비트이다.) 만든다고 한다. (c)의 일곱 드라이브들이 암의 위치와 회전 위치의 관점에서 동기화되었다고 생각하면, 일곱 개의 드라이브에 7비트 해밍 코드 방식의 워드를 드라이브 당 한 비트씩 쓰는 것이 가능하다.
 하나의 드라이브를 잃게 되더라도 문제를 일으키지 않는데, 각 워드의 읽기에서 한 드라이브에서 1 비트를 읽어버린다고 해도 해밍 코드 메커니즘에 의해 실행 중에 즉시 문제를 해결할 수 있기 때문이다. 다음 그림을 보자.

 위에서 bit 3, 5, 6, 7에 데이터가 저장되어 있고 1, 2, 4 bit가 패리티 비트(parity bit)라고 하면 해밍 코드를 완성하기 위해 even parity의 경우(1이 짝수개) 1, 2, 4에 어떤 값이 쓰여질지를 보자. 1357을 보면 1에는 0이 저장된다. 2367을 보면 2에는 1이 저장된다. 4567을 보면 4에는 0이 저장된다. 만약 1bit 에러만 발생한다고 하면 고칠 수 있다. 5의 값이 1로 바뀌었다면 1357 = (0111)이므로 에러가 발생하고(odd parity), 2367 = (1111)으로 에라가 발생하지 않고, 4567=(0111)로 에러가 발생한다. 한 비트가 에러이고 2367은 에러가 발생하지 않았으므로 1357과 4567중에 3, 6, 7은 에러가 아니다. 따라서 1, 5, 4중에 에러가 발생한 것인데 한 비트만 에러이므로 두 곳에 모두 있는 bit 5가 에러이다. 7개의 비트가 동시에 읽어져야하므로 동기화가 되어있어야한다.
 다음 그림의(d) RAID 레벨 3는 RAID 레벨 2를 단순화한 것이다.

 단일 패리티 비트는 각 데이터 워드에 대하여 계산되어 패리티 드라이브에 쓰여진다. RAID 레벨 2에서처럼, 개별적인 데이터 워드들이 여러 드라이브에 분산되어 있기 때문에 모든 드라이브들이 정확히 동기화되어야 한다.
 드라이브 고장의 경우에는 불량 비트의 위치가 알려져 있기 때문에 완전한 1비트 오류 수정이 가능하다. 만일 드라이브가 고장 나면, 컨트롤러는 이 드라이브의 모든 비트들이 0 값을 갖는 것으로 간주한다. 만일 한 워드에 대해 패리티 오류가 발생하면 고장난 드라이브의 비트는 1이라는 것을 의미하고 이에 따라 그 워드의 값을 수정해 주면 된다.
 RAID 레벨4(e)는 여분의 드라이브에 스트입을 위한 스트립 패리티가 있어 RAID 레벨 0와 비슷하다. RAID 레벨 4와 5는 패티리가 있는 개별 워드가 아니라 다시 스트립으로 동작하고, 동기화된 드라이브를 요구하지 않는다. 만일 드라이브가 고장나면, 손실된 바이트는 전체 드라이브의 집합을 읽어서 패리티 드라이브로부터 재계산될 수 있다.
 이 설계는 드라이브의 손실에 대해 보호할 수 있지만, 작은 갱신에 대하여 성능이 나쁘다. 만약 한 섹터가 변화하면 패리티를 계산하기 위하여 모든 드라이브를 읽어야 하고, 또 다시 쓰여져야 한다. 또한 패리티 드라이브에 대한 높은 부하로 인해, 병목 현상이 생길 수 있다.
 RAID 레벨 4에서 병목 현상은 (f)의 RAID 레벨 5에서 패리티 비트들을 모든 드라이브에 라운드-로빈 방식으로 분산함으로써 해결된다. 그러나 드라이브 고장의 경우에 고장난 드라이브의 내용을 복구하는 것은 매우 복잡해진다.

디스크 포매팅

 디스크의 형식은 중심이 같은 트랙의 시리즈로 구성되는데, 각각은 몇 가지의 섹터를 포함하고, 섹터들 사이에는 좁은 공간이 있다.

 섹터의 형식은 다음과 같다.

  • 프리엠블(preabme)
    • 하드웨어가 섹터의 시작을 인지할 수 있는 어떤 비트 패턴으로 시작한다.
    • 실린더와 섹터 번호, 어떤 다른 정보들을 포함한다.
  • 데이터
    • 크기는 저레벨 포매팅 프로그램에 의해서 결정된다.
    • 대부분의 디스크는 512바이트 섹터들을 사용한다.
  • ECC(Error-Correcting Code) 필드
    • 읽기 오류로부터 회복하기 위해 사용될 수 있는 중복된 정보를 포함한다.

 더욱이 모든 하드 디스크들은 제조시의 결함이 있는 섹터를 대체하기 위해 사용되도록 할당된 여분의 섹터 수를 가지고 있다.(Spare sectors)
 저레벨 포맷이 이루어질 때 각 트랙의 섹터 0은 이전 트랙으로부터 밀려서 설정된다. 실린더 스큐(cylinder skew)라고 불리는 이 밀림 현상은 성능을 향상하기 위해 있는 것이다. 이것은 디스크가 다중 트랙을 데이터의 손실 없이 한번의 연속적인 연산으로 읽을 수 있도록 하기 위한 것이다. 다음 그림을 보자.

 위 그림에서 디스크 회전 방향은 CCW이고 디스크가 읽히는 방향은 CW이다. 한 요청이 트랙의 가장 안쪽에 있는 0부터 시작하여 18섹터를 필요로 하다고 가정하면, 처음 16섹터를 읽은 것은 한 번의 디스크 회전이 소요된다. 그러나 17번째 섹터를 얻기 위해 한 트랙 바깥으로 나가기 위한 탐색이 필요하다. 헤드가 한 트랙 움직였을 때에는, 섹터 0은 헤드를 이미 지나 회전한 상황이라 그것이 다시 되돌아 올 때까지 전체 회전이 필요하다. 그 문제는 다음 그림과 같이 섹터들을 밀려 설정함으로써 해결할 수 있다.

 실린더 스큐의 양은 드라이브의 구조에 달려있다. 예를 들어 10,000rpm 드라이브는 6msec마다 일회전한다. 만일 트랙이 300섹터들을 포함한다면, 새로운 섹터는 헤드 밑을 매 20$\mu$sec마다 지나간다. 만일 트랙-대-트랙 탐색 시간이 800$\mu$sec이면, 40개의 섹터들은 탐색 동안 지나갈 것이고, 그러면 스큐는 위 그림처럼 3섹터가 아니고 40섹터가 되어야만 한다.

$800 \mu s \div (\frac{60 \times 1000}{10000} \times \frac{1}{300}) = 800 \times \frac{300}{6(= 6000 \mu s)} = 40\mathrm{sectors}$


 위 식에서 $\frac{60 \times 1000}{10000}$은 1회전 하는데 걸리는 시간[ms]이고, $\frac{60 \times 1000}{10000} \times \frac{1}{300}$은 한 트랙을 도는데 걸리는 시간이다.
 디스크를 연속적으로 읽기 위해서는 컨트롤러에 큰 버퍼를 필요로 한다. 예를 들어, 두 개의 연속적인 섹터를 읽도록 명령이 주어진 한 섹터 크기의 버퍼를 가진 컨트롤러를 고려할 때, 디스크로부터 한 섹터를 읽고 ECC 계산을 진행한 다음에, 데이터는 메인 메모리에 전송되어야 한다. 이러한 전송이 일어나는 도중에 헤드가 다음 섹터를 지나게되고 메모리에 대한 복사가 완료될 때 컨트롤러는 두 번째 섹터로 다시 돌아오기 위하여 거의 전체 회전시간을 기다려야 한다. 다음 그림의 (a)에서 인터리빙이 없는 일상적인 번호 매기는 패턴을 볼 수 있다.

 이러한 문제는 디스크를 포멧할 때 섹터에 대하여 인터리빙된 방식으로 번호를 매기면 해결할 수 있다. (b)에서는 단일 인터리빙(single interleaving)을 보여주는데, 컨트롤러에게 어떤 버퍼의 내용을 메인 메모리로 복사하기위해 연속적인 섹터들 사이에 소강 구역을 제공하는 것이다.
 만일 복사 과정이 매우 느리다면 (c)의 이중 인터리빙(double interleaving)이 필요할 수 있다. 인터리빙의 필요성을 회피하기 위하여 컨트롤러는 전체 트랙을 버퍼링할 수 있어야 한다.
 저레벨 포매팅이 완료된 다음에, 디스크는 파티션으로 분할된다. 파티션들은 여러 개의 운영체제가 공존할 수 있도록 하기 위하여 필요하다. 또 어떤 경우에 있어서는 파티션은 스와핑을 위하여 사용될 수 있다. 대부분의 컴퓨터에서 섹터 0은 마스터 부트 레코드(master boot record)를 포함하고 있는데, 여기에는 부트 코드와 끝부분에 파티션 테이블을 포함하고 있다.
 디스크를 사용하기 위해 준비함에 있어서 마지막 단계는 각 파티션에 대해 별도로 고레벨 포맷(high-level format)을 수행하는 것이다. 이 연산은 부트 블록, 가용 저장 공간 관리, root 디렉터리 그리고 빈 파일 시스템을 규정한다. 이 시점에서 시스템이 부팅될 수 있다.
 디스크 포매팅의 순서를 정리하면, 저레벨 포매팅(Low-level formatting), 파티션(Partitioning), 고레벨 포매팅(High-level formatting)이다.
 전원이 켜지면, 초기에 BIOS가 실행되고 마스터 부트 레코드를 읽어 들이고 그곳으로 점프한다. 이어 이 부트 프로그램이 어떤 파티션이 활성 상태인지 알아내기 위하여 검사를 한다. 이어 활성 상태의 파티션으로부터 부트 섹터를 읽어들여 그것을 실행한다. 부트 섹터는 운영체제 커널을 찾기 위하여 파일 시스템을 탐색하는 더 큰 부트스트랩 로더를 적재하는 작은 프로그램을 포함한다. 그 프로그램이 메모리에 적재되어 실행된다.

디스크 암 스케줄링 알고리즘

 디스크 블록을 읽거나 쓰는데 요구되는 시간은 세 요소에 의해 결정된다.

  • 탐색시간
    • 암을 적절한 실린더로 옮기는 시간.
  • 회전 지연
    • 적절한 섹터를 헤드 밑으로 회전하는 시간
  • 실제 데이터 전송 시간

 대부분의 디스크에게 있어서, 탐색 시간이 다른 두 시간보다 훨씬 더 많이 걸린다. 그래서 평균 탐색 시간을 줄이는 것이 시스템의 성능을 실질적으로 향상시킬 수 있다. 또한 Error checking is done by controllers.

First-Come, First-Served(FCFS)
 디스크 드라이브가 요청을 한 번에 하나씩 받아서, 그 순서대로 처리한다. 탐색 시간을 최적화하기 위하여 할 수 있는 일은 거의 없다. 그러나 디스크가 부하가 매우 클 때에는 다른 전략이 필요하다.
 40개의 실린더를 가진 상상의 디스크를 고려해보자. 한 요청이 실린더 11에 들어있는 블록을 읽기 위해 들어왔다. 실린더 11에 대한 탐색이 진행중인 동안, 실린더 1, 36, 16, 34, 9, 12에 대한 새로운 요청들이 순서대로 들어온다. 그들은 지연된 요청들의 테이블에 들어가는데 각 실린더마다 별도의 연결 리스트로 구성된다. 그 요청들은 다음과 같다.

 실린더 11에 대한 현재의 요청이 종료될 때, 디스크 드라이브는 어떤 요청을 다음에 처리할 것인지를 선택하야 한다. FCFS를 사용하면, 다음 실린더는 1, 36, 16, 34, 9, 12 순이 될 것이다. 이 알고리즘의 암의 움직음을 10, 35, 20, 18, 25, 3으로 진행하여, 전체 111 실린더가 될 것이다.

Shortest Seek First(SSF)
 탐색 시간을 최소화하기 위하여 항상 가장 인접한 요청을 다음에 처리한다.

 실린더 11다음 순서는 12, 9, 16, 1, 34, 36으로 전체 암의 움직임은 1, 3, 7, 15, 33, 2로서 전체 61개의 실린더가 된다. 이 알고리즘은 전체 암의움직임이 FCFS에 비해 거의 절반으로 감소하였다.
 SSF에는 문제가 있는다. 예를들어 실린더 16에 간 다음에, 실린더 8에 대한 새로운 요청이 존재하면 그 요청은 실린더 1에 비해 높은 우선 순위를 갖게 된다. 부하가 매우 많은 디스크의 경우에, 암은 대부분의 시간을 디스크의 중간 부분에 머무르게 되어 양 끝 단에 대한 요청들은 중간 부분에 요청이 없을 때까지 기다리게 될 것이다. 중간에서 먼 곳에 대한 요청은 나쁜 서비스를 받게 되는 것이다. 최소 응답 시간과 공평함이라는 목표가 여기에서 충돌된다.

엘리베이터 알고리즘(elevator algorithm, scan algorithm)
 대부분의 엘리베이터는 동일한 방향으로 움직임을 그 방향으로의 더 이상의 요청이 없을 때까지 유지한다. 그리고 나서 방향을 바꾼다. 이 알고리즘은 디스크에서 엘리베이터 알고리즘이라고 알려져있다. 소프트웨어적으로 현재의 방향 비트(UP, DOWN)를 유지해야한다. 현재 방향비트가 UP고 높은 방향으로 기다리는 요청이 없다면, 방향비트는 DOWN으로 설정된다.

 실린더가 서비스되는 순서는 11, 12, 16, 34, 36, 9, 1이고 암의 움직임은 1, 4, 18, 2, 27, 8로써 전체 60실린더가 된다. 엘리베이터 알고리즘이 가지고 있는 하나의 좋은 특성은 어떠한 요청들의 집합에도 전체 움직임에 대한 상한선이 고정되어있다는 것이다.

C-Scan (Circular-Scan) Algorithm
 Scan 알고리즘(엘리베이터 알고리즘)에 대한 약간의 변형으로 항상 동일한 방향으로 스캔하는 것이다. 가장 높은 숫자의 실린더에 대한 요청이 서비스될 때, 암은 지연된 요청 중 가장 낮은 수의 실린더로 내려간다. 그리고 다시 윗 방향으로 움직이기를 계속한다. 그 효과는 가장 낮은 수의 실린더를 가장 높은 숫자의 실린더의 바로 위에 있는 것처럼 생각하는 것이다.

댓글남기기