5장 '컴퓨터 아키텍처와 운영체제', 컴퓨터는 어떻게 프로그램과 메모리를 조직적으로 관리할까

컴퓨터 아키텍처 : 컴퓨터의 여러 구성요소를 배치하는 방법

발전을 통해 더 빨라지고, 더 적은 전력을 소모하고, 프로그래밍이 쉬워진 현대 컴퓨터는 많은 아키텍처의 변환이 있었다.

이 장에서는 그 중에서 메모리 처리에 대한 내용이다. 명령어 집합 설계, 다른 유형의 레지스터들, 전력 관리, 실행 장치, 멀티태스킹 등이 그것들이다.

여러 프로그램들을 실행하고 제어하기 위해서는 운영체제, OS가 필요하다.

기본적인 구조 요소들

가장 흔한 두 가지 컴퓨터 구조는 폰 노이만, 하버드 구조이다. 이 두구의 유일한 차이는 메모리 배열뿐이다. (P.194 그림 참고)

프로세서 코어

위에 두 구조는 모두 CPU가 하나뿐이다.

멀티프로세서 : 1980년대에 처음 만들어져 단일 CPU 보다 훨씬 더 좋은 성능을 얻어내기 위한 방법

병렬화 : 많은 수학 계산이 필요한 몇몇 경우에는 잘 작동하지만 일반적인 경우에는 많이 필요로 하지 않는다.

위에 두 방법으로 CPU 성능을 향상시켜려 했지만, 읽다시피 잘 되지 않았고, 반도체 회로 크기가 줄어들면서 한 웨이퍼로 더 많은 칩을 생산할 수 있었고, 성능 향상이 가능됐다.

하지만 2000년경 전력 장볍(power wall)에 부딪힌다. 크기가 작아진 회로가 녹는점 이상으로 온도가 올라가는 것을 방지하면서 회로를 소형화, 고성능화하기가 어려워졌다는 뜻이다.

사람들은 작아진 회로 크기를 활용한 새로운 해결책을 찾아냈다. 예전에 CPU라고 불리던 것들은 요즘 프로세서 코어(processor core)라고 부른다. 이런 코어가 여럿 들어가는 멀티코어 프로세서가 이제는 일반적으로 쓰인다.

마이크로프로세서와 마이크로컴퓨터

이와 별도로 물리적인 패키징에 따라 구조를 구분할 수 있다.

  • 마이크로프로세서 : 메모리와 I/O가 프로세서 코어와 같은 패키지에 들어 있지 않은 구조.
  • 마이크로컴퓨터 : 모든 요소를 한 칩안에 패키징하는 구조. 마이크로컨트롤러라고도 불림

칩 안에서의 메모리가 차지하는 영역이 크기 때문에 통상적으로 마이크로프로세서가 비교적 강력하다.

두 부품의 쓰임새로써는 마이크로프로세서는 큰 시스템에 들어가는 부품으로 쓰이고, 마이크로컴퓨터는 식기세척기 등에서 찾을 수 있는 작은 단일 칩으로 된 컴퓨터이다.

단일 칩 시스템 (SoC, System on a Chip) : 더 복잡한 마이크로컴퓨터이다. 상대적으로 간단한 온-칩(on-chip) I/O를 제공한느 대신, WiFi 회로 등의 더 복잡한 장치가 들어있다. (핸드폰에서 볼 수 있다.) 원하는 대로 커스텀이 가능한 FPGA(field programmable gate array)를 제공하는 SoC도 있다.

프로시저, 서브루틴, 함수

프로그래머들은 함수, 프로시저, 서브루틴 등은 코드를 재사용하는 수단으로 사용한다.

용어가 다 다른 까닭은 언어나 시스템마다 정의가 달라서 그렇다. (함수는 프로그래밍 언어, 프로시저는 SQL 등...)

함수를 호출하면 파라미터를 입력값으로 받고 함수가 출력값을 반환하는 단순한 구조이지만 반복적인 작업이 있을 때 프로그래머들에게 이보다 더한 도구가 없다.

함수 호출 명령어 순서(p.197 표 참고)

함수를 호출하는 과정은 많은 작업이 필요하고 대부분의 기계는 이런 과정을 돕는 명령어를 제공한다. ex) ARM 프로세서에는 링크 레지스터를 사용해 분기(BL, Branch with Link) 명령어가 있다. 이 명령어는 함수로 호출하는 명령어와 현재 명령의 다음 위치를 저장하는 명령어를 하나로 합친 것이다.

스택

재귀 함수에 대한 컴퓨터 구조적 설명과 공간 데이터 구조(spatial data structure)로서의 쿼드트리 순회 방식, 값을 저장하는 구조, 그리고 스택(stack)을 설명한다. (p.199)

인터럽트

실행중인 프로그램을 잠깐 중단(interrupt)시켜서 주의를 기울여야 하는 외부의 요소에 대응할 수 있게 만들 방법이 필요하다고 하면서 절을 시작한다.

인터럽트 시스템은 적절한 신호가 들어오면 CPU 실행을 잠깐 중단시킬 수 있는 핀이나 전기 연결을 표한다.

CPU가 주의를 기울여야 하는 주변장치는 인터럽트 요청(interrupt request)을 생성한다. 프로세서는 현재 실행중인 명령어를 끝까지 실행하고, 현재 실행 중인 프로그램을 잠시 중단시키고 인터럽트 핸들러(interrupt handler)를 싱핸다. 인터럽트 핸들러가 필요한 작업을 다 마치고 나면 원래 실행 중이던 프로그램이 중단된 위치부터 다시 실행을 계속한다.

이에 몇가지 고려할 요소들이 있다.

  • 인터럽트에 대한 응답 시간(response time)이 있다. 인터럽트 처리를 정해진 시간 안에 끝내야 한다.
  • 현재 상태(state)를 저장할 방법이 필요하다. 예를 들어 인터럽트가 걸린 시점에 실행 중이던 프로그램이 레지스터에 어떤 값을 저장하고 있었다면, 인터럽트 핸들러는 그 레지스터를 저장했다가 나중에 원래 프로그램으로 돌아오기 전에 레지스터값을 복구해줘야 한다. (이는 스택에 저장한다.)

컴퓨터는 각 인터럽트가 저장된 메모리 주소에 가서 인터럽트 벡터(interrupt vector)를 확인하고, 벡터는 메모리 위치를 가르키는 포인터일 뿐이다, 저장된 주소를 살펴보고 제어를 그 주소로 옮긴다.

많은 기계가 물리적인 주소를 벗어나는 주소를 사용하러고 시도하거나, 스택 오버플로가 일어나는 등의 예외 상황에 대한 인터럽트 벡터를 제공한다.

인터럽트를 중단시킬 수 있는 마스크(mask), 인터럽트가 많을 때 사용하는 우선순위(priority), 일정 시간이 지나면 인터럽트를 발생시킬 수 있는 내장 타이머(timer)등 인터럽트를 제어하는 여러 장치가 있다.

운영체제는 일반적인 프로그램들은 접근할 수 없는 물리적 인터럽트에 접근할 수 있는 경우가 종종 있다. 가상 인터럽트나 소프트웨어 인터럽트 시스템을 제공하기도 한다. 유닉스는 시그널(signal)이라 하고, 최근 개발된 시스템들은 이벤트(event)라고 한다.

상대 주소 지정

여러 프로그램을 동시에 실행하려면 각 프로그램을 서로 전환시켜 줄 수 있는 일종의 관리자 프로그램이 필요한데 이를 운영체제 또는 운영체제 커널(kernel)이라고 한다.

또는 OS와 OS가 관리하는 프로그램을 구분하기 위해 OS를 시스템 프로그램이라고 하고, 다른 모든 프로그램을 사용자 프로그램이나 프로세스라고 한다.

OS는 타이머를 사용해 사용자 프로그램을 전환시켜줄 때가 됐는지 판단한다. 이런 식으로 사용자 프로그램의 실행 시간을 조절하는 스케줄링 기법을 시분할(time slicing)이라고 한다.

사용자 프로그램 상태 또는 문맥은 레지스터의 상태와 프로그램이 상요 중인 메모리의 상태를 뜻한다. 이때 메모리에는 스택도 포함된다.

프로그램을 메모리로 불러오되 각 프로그램에게 각기 다른 공간을 허용할 수 있으면 더 빠르게 시분할 싱행이 가능하다.

절대 주소 지정은 명령어 주소가 특정 주소를 가리킨다는 뜻이다. 인덱스 레지스터를 사용해 인덱스 레스터의 값을 명령어에 들어 있는 주소와 더해서 유효 주소(effective address)를 계산한다.

또 다른 방법은 상대 주소 지정(relative addressing)이다. 명령에 들어 있는 주소를 시작 위치로 해석하지 않고, 명령어의 주소를 기준으로 하는 상대적인 주소로 해석한다.

기계어에서 이를 직접 계산하는 것은 매우 어려운 일이지만 요즘 프로그래밍 언어 도구들은 모두 이런 계산을 알아서 해준다. 상대 주소 지정을 사용하면 프로그램을 메모리의 원하는 위치로 자유롭게 재배치 할 수 있다.

메모리 관리 장치

통신 작업은 계속해서 백그라운드(background)에서 실행(사용자 프로그램과)돼야 하기 때문에 멀티태스킹이 필수가 됐다.

인덱스 레지스터와 상대 주소 지정으로 멀티태스킹을 할 수 있지만, 여러 애로사항이 있어 오늘날 대부분의 마이크로프로세서에는 메모리 관리 장치(memory management unit)이 들어 있따.

MMU가 들어 있는 시스템은 가상 주소물리 주소를 구분한다. 프로그램은 가상 주소를 사용해 작성되고 MMU는 가상 주소를 물리 주소로 변환해 준다.

MMU는 물리 주소보다 가상 주소가 일반적으로 더 많다. (p.210 그림 참고)

프로그램 입장에선느 가상 메모리가 연속적인 것처럼 보이지만(실제로 그렇진 않다.) 실제 물리 메모리상의 위치는 굳이 연속적일 필요가 없다. 심지어 프로그램이 실행되는 도중에 프로그램이 위치한 물리적 메모리 주소가 바뀔 수도 있다.

그리고 프로그램들이 서로 협력하는 경우에는 여러 프로그램의 가상 메모리 중 일부가 같은 물리 메모리를 함께 사용하는 공유 메모리 기능을 제공할 수도 있다. 페이지 테이블의 내용이 프로그램 문맥의 일부분이 된다.

기계의 비트가 늘어남에 따라 사용되지 않고 낭비되는 페이지 크기들이 있고, 이를 작게 만들 필요가 있다. (작게 만들면 테이블 크기가 늘어난다.)

이를 해결하기 위해 현대적 프로세서의 MMU는 페이지 테이블 크기가 정해져 있다. 전체 페이지 테이블 항목은 주 메모리에 저장되거나 주 메모리가 부족한 경우 디스크에 저장된다. MMU는 페이지 테이블 항목 중 일부를 필요할 때만 자신의 페이지 테이블로 읽어 들인다.

가상 메모리

OS는 CPU, 주변 장치 뿐 아니라 메모리 또한 관리한다. OS는 MMU를 사용해 사용자 프로그램에게 가상 메모리를 제공한다.

OS는 요청받은 메모리가 사용 가능한 메모리의 크기보다 크다면 현재 필요하지 않은 메모리 페이지를 디스크로 옮긴다. (스왑 아웃이라 한다. 디스크에서 메모리로 페이지를 읽어오는 동작은 스왑 인이다. 느리지만, 용량이 더 크다)

스와핑 작업은 속도가 느리기 때문에 성능 저라를 막기 위해 최소 최근 사용(LRU) 같은 다양한 기법을 사용한다.

시스템 공간과 사용자 공간

MMU는 각 프로세스에게 자신만의 메모리 주소 공간을 제공해서 모든 프로그램에게 자신이 컴퓨터 안에서 실행되는 유일한 프로그램이(라고 생각할 수 있게끔) 되게 해준다.

하지만 인터럽트나 사용자 프로그램이 인터럽트 타이머를 변경하는 등에 작업을 하면 MMU가 프로그램을 서로 격리시키지 못하게 된다.

이를 위해 CPU에는 컴퓨터가 시스템 모드에 있는지 사용자모드에 있는지 결정하는 비트가 어떤 레지스터 안에 들어있다.

I/O를 처리하는 명령어 등 일부 명령어는 특권(privilaged) 명령어라서 오직 시스템 모드에서만 실행할 수 있따. 트랩이나 시스템 콜 같은 특별한 명령어를 통해 사용자 모데에서 실행 중인 프로그램이 시스템 모드 프로그램에게 요청을 보낼 수 있다.

여기에는 몇 가지 장점이 있다.

  • 사용자 프로그램으로부터 운영체제를 보호하고, 사용자 프로그램을 다른 사용자 프로그램으로부터 보한다.
  • 사용자 프로그램이 MMU 등의 몇몇 요소에 손을 댈 수 없기 때문에 운영체제가 프로그램에 대한 자원 할당을 전적으로 제어할 수 있다. 하드웨어 예외는 오직 시스템 공간에서만 처리된다.

좋은 프로그래머는 사용자 공간 뿐 아니라 시스템 공간에서 실행되는 프로그램 또한 다뤄야겠다.

메모리 계층과 성능

메모리와 프로세서 간에 속도 차이를 극복하고자 캐시(cache)라는 하드웨어를 추가한다.

그 외에 여러가지 하드웨어들을 많이 추가했는데 (p.215) 메모리 계층을 참고해보자.

소프트웨어 부분에서도 프리페치(prefetch), 분기 예측(branch prediction), 순서를 벗어나는 실행(out-of-order execution)을 처리하는 회로도 존재한다.

코프로세서

코프로세서 : 프로세서의 비서 같은 역할, 몇 가지 연산을 위임받아 연산을 수행한다. 대표적인 예가 직접 메모리 접근(direct memory access)가 있다.

메모리상의 데이터 배치

메모리에는 명령어 뿐만 아니라 데이터 담는데 이 데이터들은 정적(static) 데이터들이다. 즉, 정적 데이터라는 것은 프로그램을 작성할 때 얼마나 많은 메모리가 필요한지 알 수 있다는 뜻이다. 이런 여러 데이터 영역을 서로 충돌하지 않게 배치하는 것도 중요하다.

(p.217 그림)을 보면 MMU가 없을 때 폰 노이만 구조, 하버드 구조에 메모리 배치를 보여준다.

프로그램이 메모리를 사용하는 때가 한 가지 더 있는데 이 프로그램은 동적(dynamic) 데이터들을 사용한다. (프로그램을 시작하기 전에 크기를 알 수 없는 데이터) 동적 데이터는 주로 정적 데이터가 차지하는 영역의 바로 위 영역에 쌓이며, 이를 (heap)이라고 한다.

프로그램 실행

본격적인 프로그램들은 여러 이유들 때문에 보통 여러 개로 분리해서 개발하는 경향이 있다. 이렇게 조각만 프로그램들을 하나로 엮거나 연결하는 방법이 필요하겠다.

각 프로그램을 링크하기 편한 형식의 매개 파일(intermediate file)로 나누고, 링크(linker)라는 특별한 프로그램을 사용해 여러 조각을 하나로 연결해 실행한다.

과거에 여러 매개 파일 형식이 있었지만 현재는 실행과 링크가 가능한 형식(ELF, Executalbe and Linkable Format)이 가장 유명한 형식이다.

과거에는 라이브러리를 단지 필요한 함수가 들어 있는 파일로 간주해서 프로그램의 나머지 부분과 직접 연결해 실행 파일을 만들었다. 이를 정적 링크라고 했으며, 후에 공유 라이브러리를 사용하는 동적 링크를 통해 MMU가 여러 프로그램이 같은 라이브러리를 공유할 수 있게 해준다.

메모리 전력 소비

메모리는 성능 말고도 고려해야 되는 점이 있다. 전력이 그러하다. 데스크탑은 아니더라도 모바일 디바이스나, 데이터 센터 등은 전력 소비량이 문제가 될 수 있으니 이 또한 성능과 함께 고민해서 프로그래밍 해야겠다.

정리

정말 이해가 잘 된다. 너무 쉽게 설명해주려고 적절한 비유와 농담을 섞어서 글은 쓰신것 같다. 메모리 구조와 운영체제, 아키텍처에 대해 좀 더 잘 알게된 것 같다. 그리고 좋은 프로그래머들은 어디까지 고민해봐야 하는가에 대해서도 좀 더 넓게 생각할 수 있게 된 것 같다.

profile
Learning bunch, mostly computer and language

0개의 댓글