2022-12-06 @이영훈
현대 컴퓨터 구조의 가장 큰 문제는 CPU와 메모리, 주변장치의 작업 속도가 다르다는 것이다.
CPU 내부 버스의 속도가 시스템 버스의 속도보다 빠르기 때문에 메모리를 비롯한 주변장치의 속도가 CPU의 속도를 따라가지 못한다.
장치 간 속도 차이를 개선하고 시스템의 작업 속도를 올리기 위해 개발된 기술 중 운영체제와 관련된 기술을 살펴볼 것이다.
1. 버퍼
버퍼의 개념
버퍼(buffer)는 일정량의 데이터를 모아 옮김으로써 속도의 차이를 완화하는 장치이다.
느린 입출력장치에서 데이터를 읽을 때마다 하나씩 전송하면 작업량에 비해 실제로 전송되는 데이터의 양이 매우 작지만. 일정량의 데이터를 모아 한꺼번에 전송하면 적은 노력으로도 많은 양의 데이터를 옮길 수 있다.
하드디스크에는 메모리 버퍼가 있다. 하드디스크이 1TB, 7200rpm, 32MB이라면, 이는 하드디스크의 용량이 1TB, 디스크의 회전 속도가 7500rpm, 버퍼의 용량이 32MB라는 의미이다.
버퍼가 소프트웨어적으로 사용되는 대표적인 예는 동영상 스트리밍(steaming)이다. 플레이어가 재 생되는 도중에 데이터가 도착하지 않으면 동영상이 끊기는데, 이러한 현상을 방지하기 위해 동영상 데이터의 일정 부분을 버퍼에 넣은 후 실행한다.
모니터 버퍼
모니터도 버퍼를 사용한다. 프로그램에서 처리한 결과를 화면에 출력할 때 한 줄이 다 차지 않으면 출력이 안 되는 경우가 있다. C 언어의 출력문 printf(”Hello \n”);에서 ‘\n’은 줄 바꿈의 의미도 있지만 버퍼에 저장된 내용을 출력하라는 의미도 있다
스풀
스풀은 CPU와 입출력장치가 독립적으로 동작하도록 고안된 소프트웨어적인 버퍼로, 대표적인 예는 프린터에 사용되는 스풀러(spooler)이다. 스풀러는 인쇄할 내용을 순차적으로 출력하는 소프트웨어로, 출력 명령을 내린 프로그램과 독립적으로 동작한다.
워드프로세서로 작업하고 프린터로 출력하는 경우를 생각해보자. 스풀러가 없다면 모든 출력을 워드프로세서가 알아서 처리해야 하므로 인쇄가 끝날 때까지 워드프로세서를 사용할 수 없다. 그러나 스풀러를 사용하면 인쇄할 내용을 하드디스크의 스풀러 공간에 저장하고 워드 프로세서는 다른 작업을 할 수 있다. 문서 작업과 프린터 출력 작업이 독립적으로 진행되는 것이다.
버퍼의 경우 어떤 프로그램이 사 용하는 데이터든 버퍼가 차면 이동이 시작된다. 다시 말해 프로그램들이 버퍼를 공유한다. 반면에 스풀러는 한 인쇄물이 완료될 때까지 다른 인쇄물이 끼어들 수 없으므로 프로그램 간에 배타적이다.
2. 캐시
캐시의 개념
캐시(cache)는 메모리와 CPU 간의 속도 차이를 완화하기 위해 메모리의 데이터를 미리 가져와 저장해두는 임시 장소이다.
캐시는 필요한 데이터를 모아 한꺼번에 전달하는 버퍼의 일종으로 CPU가 앞으로 사용할 것으로 예상되는 데이터를 미리 가져다놓는다. 이렇게 미리 가져오는 작업을 미리 가져오기(Prefetch)라고 한다
캐시는 CPU 안에 있으며 CPU 내부 버스의 속도로 작동한다. 메모리의 경우 시스템 버스의 속도로 작동하기 때문에 느리다. 캐시는 빠른 속도로 작동하는 CPU와 느린 속도로 작동하는 메모리 사이에서 두 장치의 속도 차이를 완화해준다.
캐시는 메모리의 내용 중 일부를 미리 가져오고, CPU는 메모리에 접근해야 할 때 캐시를 먼저 방문하여 원하는 데이터가 있는지 찾아본다. 캐시에서 원하는 데이터를 찾았다면 캐시 히트(cache hit)라고 하며, 그 데이터를 바로 사용한다. 그러나 원하는 데이터가 캐시에 없으면 메모리로 가서 데이터를 찾는데 이를 캐시 미스(cache miss)라고 한다. 캐시 히트가 되는 비율을 캐시 적중률(cache hit rate) 이라고 하며, 일반적인 컴퓨터의 캐시 적중률은 약 90%이다.
컴퓨터의 성능을 향상하려면 캐시 적중률이 높아야 한다. 캐시 적중률을 높이는 방법 중 하나는 캐시의 크기를 늘리는 것이다. 캐시의 크기가 커지면 더 많은 데이터를 미리 가져올 수 있어 캐시 적중률이 올라간다.
캐시 적중률을 높이는 또 다른 방법은 앞으로 많이 사용될 데이터를 가져오는 것이다. 이와 관련된 이론으로는 현재 위치에 가까운 데이터가 멀리 있는 데이터보다 사용될 확률이 더 높다는 지역성(locality) 이론이 있다. 예컨대 현재 프로그램의 10번 행이 실행되고 있다면 다음에 11번 행이 실행될 확률이 101번 행이 실행될 확률보다 더 높다. 따라서 현재 10번 행을 실행하는 경우 지역성 이론에 따라 11~20번 행을 미리 가져오면 된다.
지역성 이론에 따르면 프로그래밍 시 goto 문을 사용하지 않는 것이 좋다 미리 가져온 데이터가 쓸모없
어지기 때문이다
즉시 쓰기와 지연 쓰기
캐시에 있는 데이터가 변경되는 경우 이를 반영해야 하는 문제도 있다. 캐시는 메모리에 있는 데이터를 임시로 가져온 것이기 때문에 캐시에 있는 데이터가 변경되면 메모리에 있는 원래 데이터를 변경해야 한다.
즉시 쓰기
즉시 쓰기(write through)는 캐시에 있는 데이터가 변경되면 이를 즉시 메모리에 반영하는 방식이다.
•
메모리와의 빈번한 데이터 전송으로 인해 성능이 느려진다는 것이 단점이지만,
•
메모리의 최신 값이 항상 유지되기 때문에 급작스러운 정전에도 데이터를 잃어버리지 않는다.
지연 쓰기
지연 쓰기(write back)는 캐시에 있는 데이터가 변경되면 이를 즉시 메모리에 반영하는 것이 아니라 변경된 내용을 모아서 주기적으로 반영하는 방식으로, 카피백(copy back)이라고도 한다.
•
메모리와의 데이터 전송 횟수가 줄어들어 시스템의 성능을 향상할 수 있으나
•
메모리와 캐시된 데이터 사이의 불일치가 발생할 수도 있다는 것이 단점이다.
L1 캐시와 L2 캐시
프로그램의 명령어는 크게 어떤 작업을 할지 나타내는 명령어 부분과 작업 대상인 데이터 부분으로 나눌 수 있다.
그리고 캐시는 명령어와 데이터의 구분 없이 모든 자료를 가져오는 일반 캐시(L2), 명령어와 데이터를 구분하여 가져오는 특수 캐시(L1)라는 두 가지 레벨로 구분된다.
명령어 캐시는 명령어 레지스터와 연결되어 있고, 데이터 캐시는 데이터 레지스터와 연결되어 있다. 명령어 캐시나 데이터 캐시는 CPU 레지스터에 직접 연결되기 때문에 L1(Level 1) 캐시라고 부르며, 일반 캐시는 메모리와 연결되기 때문에 L2(Level 2) 캐시라고 부른다.
3. 저장장치의 계층 구조
가격과 컴퓨터 성능 사이의 타협점으로 저장장치의 계층 구조(storage hierarchy)가 존재한다.
저장장치의 계층 구조는 속도가 빠르고 값이 비싼 저장장치를 CPU 가까운 쪽에 두고, 값이 싸고 용량이 큰 저장장치를 반대쪽에 배치하여 적당한 가격으로 빠른 속도와 큰 용량을 동시에 얻는 방법이다.
컴퓨터는 CPU와 메모리의 협업으로 작업을 하지만 메모리의 속도가 CPU보다 느리다. 저장 장치의 계층 구조에서는 CPU와 가까운 쪽에 레지스터나 캐시를 배치하여 CPU가 작업을 빨리 진행할 수 있게 한다. 또한 메모리에서 작업한 내용을 하드디스크와 같이 저렴하고 용량이 큰 저장장치에 영구적으로 저장할 수 있게 한다. 저장장치의 계층 구조는 사용자가 저렴한 가격으로 용량은 하드디스크처럼 사용하고 작업 속도는 레지스터처럼 빠르도록 만들어준다.
저장장치의 계층 구조는 중복되는 데이터의 일관성을 유지하는 것이 문제이다.
4. 인터럽트
인터럽트의 개념
초기의 컴퓨터 시스템에는 주변장치가 많지 않았다. 당시에는 CPU가 직접 입출력장치에서 데이터를 가져오거나 내보냈는데. 이러한 방식을 폴링(polling) 방식이라고 한다.
폴링 방식에서는 CPU가 입출력장치의 상태를 주기적으로 검사하여 일정한 조건을 만족할 때 데이터를 처리한다. CPU가 명령어 해석과 실행이라는 본래 역할 외에 모든 입출력까지 관여해야 하므로 작업 효율이 떨어진다.
오늘날의 컴퓨터에는 많은 주변장치가 있기 때문에 CPU가 모든 입출력에 관여하면 작업 효율이 현저하게 떨어진다. 이러한 문제를 해결하기 위해 등장한 것이 인터럽트(interrupt) 방식이다.
인터럽트 방식의 동작 과정
인터럽트 방식의 동작 과정
1.
CPU가 입출력 관리자에게 입출력 명령을 보낸다.
2.
입출력 관리자는 명령받은 데이터를 메모리에 가져다놓거나 메모리에 있는 데이터를 저장장치로 옮긴다.
3.
데이터 전송이 완료되면 입출력 관리자는 완료 신호(인터럽트)를 CPU에 보낸다.
CPU는 입출력 관리자에게 작업 지시를 내리고 다른 일을 하다가 완료 신호(인터럽트)를 받으면 하던 일을 중단하고 옮겨진 데이터를 처리한다. 이처럼 하던 작업을 중단하고 처리해야 하는 신호라는 의미에서 인터럽트라고 불리게 되었다.
인터럽트 방식에서는 많은 주변장치 중 어떤 것의 작업이 끝났는지를 CPU에 알려주기 위해 인터럽트 번호(interrupt number)를 사용한다. 인터럽트 번호는 완료 신호를 보낼 때 장치의 이름 대신 사용하는 장치의 고유 번호로서 운영체제마다 다르다.
윈도우 운영체제의 경우 인터럽트 번호를 IRQ(lnterrupt ReQuest)라고 부르며, 키보드의 IRQ는 1번, 마우스의 IRQ는 12번, 첫 번째 하드디스크의 IRQ는 14번과 같이 구분해서 사용한다.
CPU는 입출력 관리자에게 여러 개의 입출력 작업을 동시에 시킬 수 있다. 이 경우 여러 작업이 동시에 완료되고 그때마다 인터럽트를 여러 번 사용해야 하는데 이는 매우 비효율적이다. 그래서 여러 개의 인터럽트를 하나의 배열로 만든 인터럽트 벡터(interrupt vector)를 사용한다.
인터럽트는 1. 입출력 요청, 2. 데이터 전송, 3. 인터럽트 발생 순으로 진행
아래 그림에서는 인터럽트 0번과 3번의 작업이 완료되어 인터럽트 0번과 3번이 동시에 발생. CPU가 인터럽트 벡터를 받으면 인터럽트 0번과 3번의 작업을 동시에 처리한다.
다양한 종류의 인터럽트
•
사용자가 컴퓨터의 전원 버튼을 눌러 강제로 종료하면 CPU는 하던 일을 모두 멈추고 처리 중인 데이터를 안전하게 보관한 뒤 시스템을 종료해야 한다
•
메모리에서 실행 중인 어떤 작업이 자신에게 주어진 메모리 영역을 넘어서 작업을 하려 하거나
•
0으로 숫자를 나누면 인터럽트가 발생한다
직접 메모리 접근
명령을 받은 입출력 관리자는 CPU가 요청한 데이터를 메 모리에 가져다놓아야 하는데 이때 문제가 있다. 메모리는 CPU만 접근 권한을 가진 작업 공간 이라 입출력 관리자는 접근이 불가하다는 것이다.
따라서 입출력 관리자에게는 CPU의 허락 없이 메모리에 접근할 수 있는 권한이 필요한데, 이러한 권한을 직접 메모리 접근(DMA)이라고 한다.
메모리 매핑 입출력
직접 메모리 접근은 인터럽트 방식의 시스템을 구성하는 데 필수 요소이다.
직접 메모리 접근을 통해 들어온 데이터를 메모리에 아무렇게나 둔다면 CPU가 사용하는 데이터와 섞여서 관리하기 어려울 것이므로 이를 막기 위해 메모리를 나누어 사용하는 방법이 도입되었다.
CPU가 사용하는 메모리 공간과 직접 메모리 접근을 통해 들어오거나 나가는 데이터를 위한 공간을 분리하는 것이다.
이렇게 메모리의 일정 공간을 입출력에 할당하는 기법을 메모리 매핑 입출력(Memory Mapped I/O. MMIO)
사이클 훔치기
CPU와 직접 메모리 접근이 동시에 메모리에 접근하려 한다면 어떤 일이 발생할까?
두 장치가 동시에 메모리에 접근하면 누군가는 양보를 해야 하는데 보통은 CPU가 메모리 사용 권한을 양보한다. CPU의 작업 속도보다 입출력장치의 속도가 느리기 때문에 직 접 메모리 접근에 양보하는 것으로, 이러한 상황을 사이클 훔치기(cycle stealing)라고 부른다.
CPU 입장에서는 직접 메모리 접근이 사이클(순서)을 훔쳐 간 것이기 때문에 이렇게 일컫는 것이다.
Reference
요리책 운영체제