Home 5장 컴퓨터 아키텍처와 운영체제
Post
Cancel

5장 컴퓨터 아키텍처와 운영체제

기본적인 구조 요소들

가장 흔한 컴퓨터 구조는 폰 노이만 구조와 하버드 구조이다. 두 구조의 유일한 차이는 메모리 배열이다.

메모리에서 동시에 명령어와 데이터를 가져올 수 없기 때문에 폰 노이만 구조가 약간 더 느리다. 하버드 구조는 동시에 명령어와 데이터를 가져올 수 있어 좀 더 빠르지만 두 번째 메모리를 처리하기 위한 버스가 더 필요하다.

1) 프로세서 코어

처음 만들어진 멀티프로세서 시스템은 단일 CPU보다 훨씬 더 좋은 성능을 얻어내기 위한 방법이다. 하지만 성능을 더 높이는게 그렇게 쉽지는 않았다. CPU를 더 빠르게 만듦으로써 더 나은 성능을 달성할 수 있었지만 기계가 빨라지면 전력을 더 많이 소모하고, 회로 크기는 줄어드는데 기계가 빨라져서 전력을 더 많이 소모하게 되면서 단위 면적당 열 발생은 더 많아졌다.

사람들은 작아진 회로 크기를 활용한 새로운 해결책을 찾았고, CPU를 프로세서 코어라고 부른다. 그리고 이런 코어가 여럿 들어가는 멀티코어 프로세서를 일반적으로 쓴다.

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

물리적인 패키징 구조에 따라 구분하면 메모리와 I/O가 프로세서 코어와 같은 패키지에 들어 있지 않으면 이런 프로세서를 마이크로프로세서 라고 부르고 모든 요소를 한 칩 안에 패키징 하면 마이크로 컴퓨터 라고 부른다.

칩 안에서 메모리가 차지하는 영역이 크기 때문에 일반적으로 마이크로프로세서보다 마이크로 컴퓨터가 덜 강력하다. 따라서 마이크로프로세서는 보통 큰 시스템에 들어가는 부품으로 쓰이고, 마이크로 컴퓨터는 식기세척기 등에서 찾을 수 있는 작은 컴퓨터다.

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

함수, 프로시저, 서브루틴은 코드를 재사용하는 주요 수단이다. 함수를 작성면 같은 코드를 여러 번 작성하지 않아도 함수를 호출함으로써 해당 코드의 행위를 할 수 있다. 이렇게 동작하기 위해서는 함수를 호출하는 부분에서 함수를 실행하고 다시 원래 자리로 돌아올 방법이 필요하다. 이러한 과정은 상당히 많은 작업이 필요하다. 따라서 대부분 이런 과정을 돕는 명령어를 제공한다. 예)ARM 프로세서에 링크 레지스터를 사용해 분기처리하는 명령어가 있음

스택

함수가 자기 자신을 호출하는 경우가 있는데 이를 ‘재귀’라고 하고, 재귀는 아주 쓸모가 많다. 재귀 함수가 제대로 작동하려면 반환 주소를 여럿 저장할 수 있어야 한다. 그리고 함수에서 호출 지점으로 반환할 때 저장된 주소 중 어떤 주소를 사용할지 결정할 수 있어야 한다. 예를 들면 식당에 쌓아둔 접시 더미를 생각해보자 함수를 호출할 때는 반환 주소를 접시에 넣어 접시 더매 맨 위에 넣는다. 함수 호출에서 돌아올 때는 접시 더미 맨 위의 접시를 보고 반환 주소를 결정한 다음 접시를 제거한다. 이러한 구조를 스택 이라고 한다.

그리고 스택에 물건을 넣을때 더이상 들어갈 공간이 없으면 이를 스택 오버플로우 라고 한다. 또 함수를 호출할 때 해당 지역의 변수값을 그냥 덮어쓰면 안된다. 지역변수도 스택에 같이 저장해야 각각의 함수 호출이 서로 독립적이게 된다.

인터럽트

집에서 레시피에 따른 요리를 하는 중이라고 할 때 레시피 대로 요리하는 활동을 순서도(작업이 이뤄지는 순서를 표현한 다이어그램)로 표현할 수 있다. 하지만 중간에 소포 배달원이 온다는 등 예외상황이 발생한다면 순서도 중간에 예외를 처리야하 할 것이다. 순서도 중간중간에 문 앞에 누가 와있는지 확인하는 것을 넣에 되는 방법을 폴링이라고 부른다. 이러한 방법은 문 앞에 누가 왔는지 검사하는 데 많은 시간을 소모하게 된다.

이런 작업을 더 잘 처리하기 위해 실행중인 프로그램을 잠깐 중단 시켜서 주의를 기울여야 하는 외부의 요소에 대응할 수 있게 만들 방법이 필요하다. 그래서 요즘 프로세서 대부분은 인터럽트 시스템이 들어간다. 인터럽트 시스템은 적절한 신호가 들어왔을 때 CPU 실행을 잠깐 중단시킬 수 있는 핀이나 전기 연결을 포함한다.

인터럽트 시스템이 작동되는 방식은 1)CPU가 주의를 기울여야 하는 주변장치는 인터럽트 요청을 생성한다. 2)프로세서는 현재 실행중인 명령어를 끝까지 실행한다. 3)이후 프로세서는 현재 실행중인 프로그램을 잠시 중단하고 인터럽트 핸들러라는 프로그램을 실행한다. 4)인터럽트 핸들러가 작업을 마치면 원래 실행중이던 프로그램이 중단된 위치부터 다시 실행을 계속한다.

인터럽트 시스템을 이용할때 고려해야할 사항이 있다. 1)인터럽트에 대한 응답시간 2)인터럽트를 서비스하고 나중에 다시 원래대로 돌아오기 위해서 현재 상태를 저장할 방법이 필요하다. 인터럽트 시스템은 서비스 후 돌아올 프로그램 위치를 스택에 저장한다.

또 여러가지 종류의 인터럽트 제어가 가능하다. 인터럽트를 중단시킬 수 있는 마스크, 인터럽트 간의 우선순위, 일정 시간이 지나면 인터럽트를 발생시킬 수 있는 내장 타이머 등이 있다.

상대 주소 지정

여러 프로그램을 동시에 실행할때 각 프로그램을 서로 전환시켜줄 수 있는 일종의 관리자 프로그램이 필요하다. 이런 프로그램을 운영체제 또는 운영체제 커널이라고 부른다. OS는 타이머를 사용해 사용자 프로그램을 전환시켜줄 때가 됐는지 판단한다. 이런식으로 프로그램의 실행 시간을 조절하는 스케줄링 기법을 ‘시분할’이라고 한다.

주소 지정 모드에서 절대 주소 지정 모드는 명령어가 특정 메모리 주소를 가리킨다. 그래서 1000번지에서 실행되도록 만들어진 프로그램을 2000번지에 읽어 들이면 제대로 실행되지 않을 수 있다. 그래서 어떤 컴퓨터들은 이런 문제를 ‘인덱스 레지스터’를 추가해 해결한다. 이것을 사용하면 인덱스 레지스터에 있는 값을 명령어에 들어있는 주소와 더해서 유효 주소를 계산한다.

예)1000번지에서 실행되도록 만들어졌다면 os는 이 프로그램을 3000번지에서 실행하기 위해 인텍스 레지스터를 2000으로 설정할 수 있다.

이 문제를 해결하기 위한 또 다른 방법으로 상대 주소 지정을 사용하는 것이다. 이는 명령어에 들어있는 주소를 명령어의 주소를 기준으로 하는 상대적인 주소로 해석한다.

메모리 관리 장치

오늘날 대부분의 마이크로프로세서에는 메모리 관리 장치(MMU)가 있다. MMU가 들어있는 시스템은 가상 주소와 물리 주소를 구분한다. MMU의 가상 주소 범위는 물리적 메모리 주소보다 큰 경우가 많다. 이는 가상 메모리 주소를 두 부분으로 나뉘어 하위 부분은 물리적 주소 범위와 같고, 상위 부분은 ‘페이지 테이블’이라는 RAM 영역을 통해 주소를 변환한다.

페이지 테이블에는 각 페이지가 물리 메모리상에서 차지하는 실제 위치 정보가 들어 있다. 이를 통해 1000번지에서 시작하는 프로그램을 2000번지나 다른 곳에 넣을 수 있다.(단, 모든 내용이 페이지 경계안에 있어야 한다.) 그리고 프로그램 입장에서는 가상 메모리가 연속적으로 보이지만 실제 물리 메모리상의 위치는 굳이 연속적일 필요가 없다.

현대적 프로세서의 MMU는 페이지 테이블 크기가 정해져 있다. 전체 페이지 테이블 항목은 주 메모리에 저장되거나 주 메모리가 부족한 경우 디스크에 저장된다. MMU는 페이지 테이블 항목 중 일부를 필요할 때만 자신의 페이지 테이블로 읽어 들인다. 일부 MMU 설계는 페이지 테이블에 제어 비트를 추가 제공한다. 예로 실행 불가 비트를 들때 이것이 설정되어 있다면 CPU가 이 페이지에 있는 명령어를 실행할 수 없다.

프로그램이 물리적 메모리에 연관되지 않은 주소에 접근하면 페이지 폴트 예외가 발생한다. 이런 동작은 스택 오버플로 등이 일어난 경우 유용하다. 스택 오버플로가 발생하면 스택 범위를 벗어난 주소에 접근하므로 페이지 폴트가 발생하고, 이 예외가 발생시 OS는 실행 중인 프로그램을 중단시키는 대신 MMU가 추가 메모리를 할당하게 해서 스택 공간을 늘리고 사용자 프로그램 실행을 계속할 수 있다.

가상 메모리

운영체제는 프로그램들 사이의 자원 분배를 관리한다. 메모리도 역시 OS가 관리하는 자원이다. OS는 MMU를 사용해 사용자 프로그램에게 가상 메모리를 제공한다.

요청받은 메모리가 사용 가능한 메모리의 크기보다 큰 경우 OS는 현재 필요하지 않은 메모리 페이지를 더 느리지만 용량이 큰 대용량 저장장치인 디스크로 옮긴다. 이런 과정이 일어나게 되면 시스템 성능이 크게 저하된다. 이런 성능 저하를 막기 위해 사용하는 기법 중 최소 최근 사용 LRU 알고리즘이 있다. 이 알고리즘은 최근에 가장 자주 사용된 페이지는 물리 메모리에 그대로 남기고 최근에 가장 덜 사용한 페이지를 디스크로 옮긴다.

시스템 공간과 사용자 공간

메모리 계층과 성능

과거에는 CPU와 메모리가 같은 속도로 작업했으나 CPU가 더더욱 빨라지면서 메모리의 속도가 뒤처지기 시작했다. 메모리 시스템 내부에서 일어나는 일은 시스템 성능에 큰 영향을 끼친다.

CPU와 주 메모리 사이에는 작은 덩어리가 오간다. 주 메모리와 디스크 사이에는 더 큰 덩어리가 오간다. CPU가 주 메모리보다 10배 더 빠르게 작동한다면 CPU가 메모리를 기다리느라 많은 시간을 소비하게 된다. 이를 해결하기 위해 ‘캐시’라는 하드웨어를 CPU에 추가한다.

캐시 메모리에도 몇 가지 계층이 있다. CPU에서 멀어질수록 캐시는 더 느려지고 더 커진다. 이들을 L1, L2, L3 캐시라고 부른다. 그리고 여러가지 크기의 내용물을 채워 넣거나 내용물을 꺼내는 일을 담당하는 아주 큰 논리 회로인 디스패처도 있다.

코프로세서

프로세서 코어는 아주 복잡한 회로로 이뤄졌다. 몇 가지 연산을 코프로세서라는 더 단순한 회로에 위임하면 프로세서 코어가 일반적인 연산에 활용할 수 있는 공간을 더 확보할 수 있다. 주 메모리와 디스크 사이에 단순한 데이터 복사가 자주 일어날 수 있다. 일부 코프로세서는 다른 일은 처리하지 않고, 데이터 복사만 담당한다. 이런 방식을 ‘직접 메모리 접근’이라고 한다.

메모리상의 데이터 배치

메모리에는 명령어만 담는게 아니라 데이터도 담는다. 이 경우 데이터는 정적 데이터다. 대부분의 프로그램은 동적 데이터를 다뤄야 한다. 동적 데이터는 주로 정적 데이터가 차지하는 영역의 바로 위 영역에 쌓이며 이를 힙이라고 부른다. 스택은 아래로 자라나는 반면 힙은 위로 자라난다. 따라서 힙과 스택이 서로 충돌하지 않게 하는 것이 중요하다.

프로그램 실행

앞서 프로그래머가 함수를 사용해 코드를 재사용한다고 설명했다. 이렇게 관련 함수를 한데 모아서 라이브러리로 만들면 문자열 처리부터 복잡한 수학계산 등 다양하게 활용할 수 있다. 본격적인 프로그램은 라이브러리 뿐만 아니라 여러 조각으로 이뤄진다. 여러 파일로 나누게 되면 여러 사람이 한 프로그램의 여러 부분을 동시에 개발할 수 있다는 장점이 있다.

하지만 프로그램을 여러 조각으로 나누면 이 모든 조각을 하나로 엮거나 연결할 방법이 필요하다. 과거에넌 라이브러리를 직접 연결해 실행 파일을 만드는 정적 링크 방식을 사용했으나 메모리 낭비로 인하여 공유 라이브러리를 사용하는 동적 링크를 발명했다.

메모리 전력 소비

데이터를 메모리에서 이리저리 옮기려면 전력이 소비된다. 전력 소비와 성능 사이의 균형을 잡는 일은 아주 어렵다. 따라서 코드를 작성할 때도 이를 염두에 둬야 한다.

This post is licensed under CC BY 4.0 by the author.

4장 컴퓨터 내부 구조

6장 입출력과 네트워킹