Operating Systems Three Easy Pieces
작성자: Remzi H. Arpaci-Dusseau, Andrea C. Arpaci-Dusseau · 2018 번역본을 참고하였습니다.
https://github.com/remzi-arpacidusseau/ostep-translations/tree/master/korean
메모리 종류는 크게 3가지로 나뉜다.
1. CPU 안에 있는 레지스터, 캐시 메모리
2. 메인 메모리
3. 보조저장장치(HDD, SSD)
가장 빠른 연산을 하는 기억장소로 CPU 내에 존재한다. 컴퓨터의 전원이 꺼지면 데이터가 사라지기 때문에 휘발성 메모리이다. 32bit와 64bit는 레지스터의 크기를 말한다. CPU는 계산을 할 때 메인 메모리에 있는 값을 레지스터로 가져와 계산한다.
CPU 계산 시 메인 메모리에 있는 값을 레지스터로 가져와 계산하는 이유는? 속도와 효율성
1. 속도: CPU 내부의 레지스터는 메인 메모리에 비해 접근 속도가 매우 빠르다. 따라서 연산을 수행할 때 필요한 데이터를 레지스터에 저장하면, CPU는 그 데이터를 훨씬 빠르게 읽고 쓸 수 있다.
2. 효율성: CPU의 연산은 기본적으로 레지스터 간에서 이루어진다. 계산에 필요한 값들이 모두 레지스터 안에 있으면 CPU는 해당 연산을 보다 효율적으로 처리할 수 있다.
왜 계산 결과를 다시 메인 메모리에 저장할까?
1. 레지스터의 한정된 공간: CPU의 레지스터는 제한된 크기와 개수를 가지고 있다. 반면, 메인 메모리는 훨씬 큰 공간을 제공한다. 따라서 연산 결과를 보관하기 위해선 일반적으로 그 값을 다시 메인 메모리로 옮겼다가 나중에 필요할 때 다시 가져오게 된다.
2. 다른 프로세스와 데이터 공유: 프로그램과 운영체제 사이에서 데이터를 공유하려면 프로세스마다 자신의 주소 공간을 가지고 있으므로 주소를 참조하기 위해 메인 메모리와 같은 중앙 장소에 위치해야 한다.
레지스터는 CPU가 사용하는 메모리로 무지 빠른 반면, 메인 메모리는 매우 느리다. 그래서 메인 메모리에서 레지스터로 값을 옮길 때 한참 걸리기에 필요한 데이터를 미리 가져온다. 미리 가져온 데이터를 저장하는 장소를 케시 메모리라고 한다. CPU가 값을 요청해 레지스터로 값을 옮겨야 한다면, 단계에 따라 가장 속도가 빠른 L1 캐시를 확인하고, L2, 메인 메모리 순으로 확인한다.
실제 운영체제와 프로세스들이 올라가는 공간이다. 전원이 공급되지 않으면 데이터가 지워지므로 휘발성 메모리이다. HDD, SSD보다 속도는 빠르지만 가격이 비싸기 때문에 데이터를 저장하기 보다는 실행 중인 프로그램(프로세스)만 올린다.
위에서 언급한 메모리들은 휘발성이고 가격이 비싸서 저장이 어렵다. 가격이 저렴하고 전원이 공급되지 않아도 저장이 되는 비휘발성 메모리이다.
운영체제는 메모리를 관리하기 위해서 1바이트 크기로 구간을 나누고 번지수를 설정한다. 이 숫자를 주소라고 부른다. 64bit CPU는 레지스터의 크기이고, ALU 버스의 크기도 64bit이다. 메모리의 크기 는 2^6이다.
메모리를 컴퓨터에 연결하면 0x0번지부터 시작하는 주소공간이 있는데 이를 물리주소공간이라고 한다. 사용자 관점에서 바라보는 주소는 논리주소 공간이다. 이 때 운영체제는 운영체제를 위한 공간을 따로 만든다. 사용자 프로세스가 운영체제를 침범하면 굉장히 위험한 상황이 발생한다. 이를 방지하기 위해 OS 공간과 사용자 공간을 나누는 경계 레지스터가 있다.
경계 레지스터 : CPU 내에 존재하는 레지스터로 메모리관리자가 사용자 프로세스가 경계 레지스터의 값을 벗어났는지 검사하고 만약 벗어났을 경우 해당 프로세스를 강제로 종료한다.
사용자 프로세스가 운영체제를 침범하면 위험한 이유?
한 사용자 프로세서가 운영체제를 침범하면 다른 프로세서에도 영향을 미칠 수가 있는데, 예를 들어 다른 프로그램의 메모리 공간에 접근하게 되면 그곳에 저장된 정보를 읽어와 개인정보나 비밀번호가 노출 될 수 있다. 또한 불법적으로 메모리 공간에 쓰기 작업 시, 데이터를 손상시킬 수 있고 이로 인해 계산결과가 잘못되거나 예외처리가 제대로 실행되지 않는 문제가 발생할 수 있다. 따라서 운영체제는 커널/사용자 모드의 경계를 엄격하게 나누어 각각의 프로세스가 다른 프로세스의 메모리 영역에 직접 접근을 방지한다.
컴파일러는 컴파일을 할 때 메모리 사앧주소 0x0번지에서 실행한다고 가정한다. 실제 프로그램은 0x4000번지에 배치된다. 이는 메모리 관리자가 바라본 절대주소이자 물리 주소이다. 사용자가 바라본 0x0번지는 상대주소 이자 논리주소 공간이다. 순서는 아래와 같다.
- 유저 0x100번지 요청
- CPU는 메모리관리자에게 0x100번지에 있는 데이터를 가져오라고 명령
- 메모리 관리자는 CPU가 요청한 0x100번지와 재배치 레지스터에 있는 0x4000번지를 더한 0x4100번지(물리주소, 절대주소)에 접근하여 데이터를 가져온다.
- 재배치 레지스터는 프로그램의 시작 주소가 지정돼 있다.

프로세스가 크면 메모리도 크게 할당 - 프로세스의 크기에 따라 메모리를 나누기 때문에 5MB, 2MB, 1MB를 확보하여 연속메모리 할당
장점 - 메모리에 연속된 공간에 할당되기 때문에 더 크게 할당돼서 낭비되는 공간인 내부단편화가 없다. 단점으로는 외부단편화가 발생
기존에 있던 50MB, x, y, 10MB가 작업을 마치고 프로세스가 종료되고 60MB크기의 프로세스가 들어오려고 하지만 연속된 공간이 아니기 때문에 새로운 프로세스에게 메모리를 할당할 수 없다. 이를 외부 단편화라고 한다. 외부 단편화가 발생한 공간을 조각모음을 하면되는데, 이를 위해서는 현재 메모리에서 실행되고 있는 프로세스들의 작업을 일시 중지해야하고 메모리 공간을 이동시키는 작업을 해야하기 때문에 오버헤드가 발생한다.
프로세스 크기와 상관없이 메모리를 할당 - 한 프로세스가 2MB 메모리에 분산되어 할당되기 때문에 비연속 메모리 할당.
장점 - 구현이 간단하고 오버헤드가 적다.
단점 - 작은 프로세스도 큰 영역에 할당이 돼서 공간이 낭비되는 내부 단편화 발생.
20MB크기로 분할한다고 했을 때 50MB는 공간이 부족해 남은 10MB는 하드디스크 내에 스왑영역에 존재하고 16MB, 13MB는 분할된 크기보다 작기 때문에 내부에 빈공간이 생겨 낭비된다. 이를 내부단편화라 하고, 분할되는 크기를 조정(20MB→ 16MB)해 내부 단편화를 최소화한다.
가변 분할과 고정 분할을 혼합해 단점을 최소화한 버디시스템이 있다. 이는 2의 승수로 메모리를 분할해 메모리를 할당하는 방식. 2^11의 메모리가 있을때 500B가 들어온다. 그럼 500B보다 작은 값을 만날때까지 2로 나눔. 2^11→10→9 = 512
이때도 내부단편화 12B가 발생하지만 이 프로세스가 사용을 마치고 메모리에서 나가도 근접한 메모리와 합치기 쉽다. 2의 승수로 동일하게 나누었기에 반대로 조립만하면 큰 공간을 만들 수 있기 때문에 조각모음보다 간단하다. 이는 가변분할공간처럼 프로세스 크기에 따라 할당되는 메모리 크기가 달라지고 외부 단편화를 방지하기 위해 메모리 공간을 확보하는 것이 간단하다. 또한 고정분할방식처럼 내부단편화가 발생하기는 하지만 많은 공간의 낭비가 발생하지 않는다.
내부단편화
내부 단편화(Internal Fragmentation)는 컴퓨터 시스템의 메모리 관리에서 발생하는 현상으로, 할당된 메모리 공간 내에 사용되지 않는 빈 공간이 생기는 것을 말한다. 이러한 현상은 주로 고정 크기의 메모리 블록을 할당할 때 발생한다.
예를 들어, 운영 체제가 1KB 크기의 메모리 블록을 할당하고 프로그램이 그 중 800B만 사용한다면, 나머지 200B는 내부 단편화로 인해 낭비되게 된다. 이러한 빈 공간은 해당 메모리 블록 내에서만 사용할 수 있으며, 다른 프로그램이나 프로세스가 이를 활용할 수 없다.
메모리 관리 전략에 따라 내부 단편화 문제를 최소화할 수 있다. 예를 들어 가변 파티션 방식이나 동적 메모리 할당 방식 등은 필요한 만큼의 메모리만 할당하여 내부 단편화를 줄일 수 있다.
외부 단편화
외부 단편화(External Fragmentation)는 컴퓨터 메모리 관리에서 발생하는 문제로, 메모리의 연속적인 빈 공간이 분산되어 하나의 큰 메모리 블록을 할당할 수 없게 되는 현상을 말한다.
예를 들어, 운영 체제가 여러 프로그램에게 메모리를 할당하고 반환하는 과정에서, 사용 가능한 여유 공간이 여러 조각으로 나뉘어져 있을 수 있다. 이 때 각각의 조각은 충분히 작아서 필요한 크기의 메모리 블록을 할당하는데 부족하다면, 그 합계가 충분하더라도 새로운 메모리 요청을 처리할 수 없게 된다. 이를 외부 단편화라고 한다.
외부 단편화 문제를 해결하기 위한 방법 중 하나는 컴팩션(Compaction)이다. 컴팩션은 사용 중인 메모리 조각들을 한쪽으로 몰아넣고, 사용 가능한 빈 공간들을 다른 한쪽으로 모으는 방식이다. 이렇게 하면 연속적인 큰 빈 공간이 생겼다가 사라지는 문제를 해결할 수 있다.
그러나 컴팩션은 상당한 오버헤드를 발생시키며 주기적으로 실행해야 하는 등의 단점이 있다.