메모리 관리 (명품_운영체제 Ch 08)
메모리 계층 구조
메모리 시스템 내의 모든 기억 공간을 포괄적으로 부르는 말(일반적으로는 메인 메모리를 가리키긴 한다) 실행될 프로그램의 코드와 데이터가 적재되며, 실행 중 발생하는 데이터가 저장된다
- 메모리 계층 구조

CPU레지스터, 캐시 메모리, 메인 메모리, 보조 기억 장치 순으로 계층 구조를 이룬다
- 이를
메모리 계층 구조라고 부른다 - CPU의 메모리 엑세스 시간을 줄이기 위함이다
- 계층 구조의 중심에 메인 메모리(RAM)이 존재한다
- 보조 기억 장치 쪽으로 갈수록 용량이 커지고, 가격이 싸지며, 처리 속도는 느려진다
메모리 계층 구조의 요소
CPU 레지스터(CPU registers)
- 현재 실행할 코드와 데이터, 혹은 다음에 실행할 몇 개의 코드와 데이터를 미리 저장한다
- 레지스터의 크기와 개수는 CPU 종류에 따라 다르며, 일반적으로 8~30개 정도가 있다
캐시 메모리(cache memory)
- CPU 칩 내부의
on-chip 캐시와 CPU 바깥의off-chip 캐시로 나뉘며, CPU가 실행할 프로세스 정보 중에서 당장 실행할 일부를 메인 메모리에서 미리 복사해서 사용한다- 코어마다 L1/L2 , L3이라는 이름의 캐시를 둔다
- 현대에서 L1~L3는 on-chip 캐시, L4는 off-chip 캐시이며, L4 캐시는 고성능 시스템(서버, 워크스테이션)에 사용된다
- L1~L4까지 순서대로 L1 캐시가 가장 빠르고, L4 캐시가 가장 느리다
- 사용자 프로그램과 운영체제 커널을 구분하지 않고 당장 필요한 코드 및 데이터를 복사한다
- 가격이 비싸서 소량만 사용되는 대신에 메인 메모리보다 빠른 속도를 가지며, CPU와 메인 메모리의 속도 차이에서 오는
메모리 엑세스 시간을 단축시키기 위해 도입되었다
메인 메모리(main memory, RAM)
- 일반적으로 ‘메모리’라는 단어가 가리키는 메모리로,
RAM이라고 부른다 - 현재 실행 중인 모든 프로세스의 코드 및 데이터, 접근한 파일들의 블록, 운영체제의 커널 코드와 커널 데이터 등이 저장된다
- 휘발성 저장장치로, 컴퓨터 시스템의 전원이 꺼지면 저장된 내용이 모두 사라진다
보조기억장치(secondary memory)
- 비휘발성 저장장치로, 전원을 꺼도 내용이 지워지지 않는 대용량 저장장치이다(ex 하드 디스크, SSD)
- 메인 메모리의 크기 한계로 인해 실행 프로그램의 코드와 데이터를 적재할 공간이 필요할 때 일시 저장하는 용도(스왑 메모리)로도 사용된다
| CPU 레지스터 | L1/L2 캐시 | L3 캐시 | 메인 메모리 | 보조 기억 장치 | |
|---|---|---|---|---|---|
| 용도 | 몇 개의 명령과 데이터 저장 | 한 코어에서 실행되는 명령과 데이터 저장 | 멀티 코어들에 의해 공유 명령과 데이터 저장 |
실행 중인 전체 프로세스들의 코드와 데이터, 입출력 중인 파일 블록들 저장 | 파일이나 데이터베이스, 그리고 메모리에 적재된 프로세스의 코드와 데이터의 일시 저장 |
| 옹량 | 바이트 단위, 8~30개 정도, 1KB 미만 |
KB 단위 (Core i7의 경우 32KB/256KB) |
MB 단위 (Core i7의 경우 8MB) |
GB 단위 (최근 PC의 경우 최소 8GB 이상) |
TB 단위 |
| 타입 | SRAM (Static RAM) |
SRAM (Static RAM) |
DRAM (Dynamic RAM) |
마그네틱 필드나 플래시 메모리 | |
| 속도 | < 1ns | < 5ns | < 5ns | < 50ns | < 20ms |
| 가격 | 고가 | 고가 | 보통 | 저가 | |
| 휘발성 | 휘발성 | 휘발성 | 휘발성 | 휘발성 | 비휘발성 |
계층화의 목적
계층화의 목적은 CPU가 메모리에 더 빨리 접근할 수 있도록 하여 시스템의 효율성을 높이는 것이다
- 사용자에게 가장 비싼 메모리(cache)의 속도를 제공하는 동시에 가장 싼 메모리(HD, SSD)만큼의 용량을 제공해준다
캐시가 있는 컴퓨터에서는 CPU는 캐시로부터만 프로그램 코드와 데이터를 읽고 실행한다
- 실행할 코드와 데이터를 반드시 캐시에 담아야 한다
캐시 미스(cache miss)
- CPU가 다른 프로세스나 스레드를 실행하려 할 때, 실행하고자 하는 데이터나 코드를 L1/L2 캐시에서 찾을 수 없는 상황
- 캐시의 크기가 작기 때문에 컨텍스트 스위칭이 아닌 프로그램 실행중에도 발생한다
- 이 때는 L3에서 필요 부분을 복사한다
- 캐시 미스가 발생하면 RAM에서 부터 L3 캐시, L1/L2 캐시 순으로 새 프로세스의 코드와 데이터가 이동한다
- 기존에 L1/L2 캐시에 있던 부분은 L3나 RAM에 다시 기록된다
이런 식의 메모리 계층화는 왜 효율적일까?
- 캐시 메모리는 크기가 작기 때문에 캐시 미스가 자주 발생하며, 다시 데이터를 복사해오는 동안 CPU는 대기하게 된다
- 그럼에도 계층 구조가 가져오는 득이 더 많다고 한다
- 참조의 지역성이란 프로그램 실행 특성 때문
참조의 지역성(locality of reference)
- 코드나 데이터, 자원 등이 짧은 시간 내에 다시 사용되는 특성
- 캐시를 통한 빠른 데이터 엑세스로 얻는 이득이 캐시 미스로 인한 손실보다 크다
메모리 관리
메모리가 운영체제에 의해 관리되어야 하는 이유
- 메모리는 공유 자원이다
- 여러 프로세스에 의해 사용되는 공유 자원이므로 임의 사용을 막는다
- 메모리 보호 기능
- 비선점, 커널 공간 접근 제한 등
- 메모리 용량 한계 극복
- 가상 메모리와 같은 기법을 통해 용량 한계를 극복한다
- 메모리 사용 효율을 높이기 위함
- 다중프로그래밍 정도를 운영체제가 담당한다
여러 기능 중에서도 반드시 메모리 할당, 메모리 보호 기능은 제공되어야 한다
메모리 주소
물리 주소(physical address)
- 물리 메모리(RAM)에 매겨진 주소
- 0번지부터 시작
논리/가상 주소(logical/virtual address)
- 프로세스 내에서 코드나 데이터의 주소
- 변수 n의 주소가 100번지라는 것은 가상 주소가 100번지라는 뜻이다(실제 RAM 상의 물리 주소는 다르다)
- 프로세스마다 각각 0번지부터 시작한다
- 실제 물리 메모리 주소는 따로 있고 프로세스가 차지하는 물리 메모리 주소 범위 내에서의 시작을 0번지로 한다
- 데이터들이 0번지부터 연속적으로 적재되어 있지는 않다
- 코드 안의 주소, CPU가 실행하는 기계 명령 속의 변수의 주소들도 모두 가상 주소이다

가상 주소 변환
메모리를 엑세스하기 위해서는 가상 주소룰 물리 주소로 바꿔서 물리 주소가 주소 버스를 통해 물리 메모리에 전달되어야 함
MMU(Memory Management Unit)
- CPU 내에 존재하는 하드웨어
- 매핑 테이블을 참고하여 프로세스의 논리 주소를 물리 주소로 변환한다
- CPU 칩 내부에서 매핑하기 때문에 결과적으로 CPU가 발생시키는 주소는 물리 주소가 된다
컴파일러(Compiler)
- 프로그래밍 언어로 작성된 코드를 기계어로 변환하는 도구
- 컴파일러는 작성한 프로그램을 논리 주소로 컴파일한다
- 모든 코드와 변수들이 논리 주소로 구성된다
코드와 데이터들이 물리 메모리에 적재되면, 프로세스 별로 물리 주소에 대한 매핑 테이블이 생성된다
- 매핑 테이블을 사용해 MMU가 논리 주소를 물리 주소로 바꾼다
- 매핑이 가능하기 때문에 논리 주소로 컴파일되어도 괜찮으며, 운영체제는 물리 메모리의 빈 곳(프로세스 범위 내) 아무데나 코드와 데이터를 적재하도 된다
* ASLR(Address Space Layout Randomization)
- 스택이나 힙, 라이브러리 영역을 프로그램이 실행될 때 마다 랜덤 배치하여 논리 주소가 달라지도록 하는 기법
- 시스템 공격에 필요한 Target address를 예측하기 어렵게 만들기 위해 사용된다
메모리 할당
프로세스를 실행하거나 실행중인 프로세스가 메모리를 필요로 하면 운영체제는 물리 메모리를 할당해준다
메모리 할당 기법
- 연속 메모리 할당(contiguous memory allocation)
- 각 프로세스에게 메모리 한 덩어리(partiton)씩 할당하는 기법
- 분배된 메모리 덩어리들은 연속된 공간이다
- 시작 위치와 크기 정보만 관리하면 되기에 구현과 관리가 쉽지만, 단편화로 인한 메모리 할당의 유연성이 부족하다
- 고정 크기 할당(fixed partitioning)
- 메모리 전체를 고정된 크기를 가지는 파티션 n개로 나누고 프로세스 당 고정 크기의 파티션 1개 할당
- 프로세스가 파티션 크기보다 크면 처음부터 실행될 수 없다
- 실행시킬 전체 프로그램들의 크기를 미리 계산해서 파티션 크기를 정한다
- 가변 크기 할당(variable partitioning)
- 프로세스의 크기에 맞춰 동적으로 동일한 크기의 메모리를 할당
- 분할 메모리 할당(non-contiguous memory allocation)
- 연속적이지 않고 필요한 메모리를 여러 덩어리로 나누어 분산 할당하는 기법
- 고정 크기 할당
- 페이징(paging) 기법을 사용한다
- 프로세스를 고정된 크기인 페이지로 분할하여 불연속 할당한다
- 가변 크기 할당
- 세그먼테이션(segmentation) 기법을 사용한다
- 프로세스를 세그먼트(segment)라고 부르는 블록으로 분할한다
- 대부분 코드, 데이터, 스택, 힙의 4개 세그먼트로 분할한다
- 각 세그먼트의 크기는 다르며, 필요한 크기만큼 할당한다

단편화
프로세스에 할당할 수 없는 작은 메모리 찌꺼기(홀, hole)들이 생기는 현상 홀이 생기는 위치에 따라 내부 단편화, 외부 단편화로 나뉘며 주로 연속 메모리 할당 기법에서 나타난다
내부 단편화(internal fragmentation)
- 프로세스에게 할당된 메모리 영역 내에 활용 불가한 홀이 생기는 것
- 프로세스의 크기는 무조건 파티션 크기보다 작거나 같기 때문에 발생할 수 있다
- 고정 크기 할당 시 발생할 수 있다 외부 단편화(external fragmentation)
- 프로세스에게 할당된 메모리 영역들 사이에 활용 불가한 홀이 생기는 것
- 프로세스가 가변 크기의 파티션을 할당/해제하면서 파티션 사이에 홀이 발생한다
- 가변 크기 할당 시 발생할 수 있다

동적 메모리 할당
메모리 할당/해제를 반복하며 발생한 홀들을 다시 프로세스에게 할당해주어야 할 때 홀을 선택하는 알고리즘이다
고정 크기 할당 시
- 해당 기법에서 홀 = 파티션이므로 파티션 리스트에서 관리하고 선택한다
가변 크기 할당 시
- 홀이 메모리 전체에 걸쳐 퍼져있으며, 각각 시작 주소와 크기가 다르기 때문에 따로 홀 리스트를 만들고 관리한다
- 할당 요청이 발생하면 홀 리스트에서 알고리즘에 따라 선택한다
- first-fit(최초 적합)
- 요청 크기를 만족하는 것 중에서 가장 먼저 검색된 홀 선택
- 선택 속도가 빠르지만 외부 단편화로 인한 메모리 낭비가 발생할 수 있다
- best-fit(최적 적합)
- 요청 크기를 만족하는 것 중 가장 작은 홀 선택
- 가장 작은 홀이 발생하여 낭비가 적다
- 리스트 내의 모든 홀을 검색해야 하는 부담이 발생
- worst-fit(최악 적합)
- 요청 크기를 만족하는 것 중에서 가장 큰 홀 선택
- 가장 큰 홀이 발생하여 낭비가 가장 심하다
- 리스트 내의 모든 홀을 검색하는 부담도 발생
* 홀 발생 = 단편화 : X, 사용할 수 없는 홀 발생 = 단편화 : O
세그먼테이션
프로세스를 논리 세그먼트로 나누고 각각에 크기에 맞는 한 덩어리의 물리 메모리(물리 세그먼트)를 할당하는 기법
보편적으로 한 프로세스를 4가지 세그먼트로 나눈다
- 코드 세그먼트
- 프로그램과 라이브러리 코드
- 데이터 세그먼트
- 전역 변수와 정젹 변수
- 스택 세그먼트
- 함수 내의 지역 변수나 매개변수, 리턴값 등
- 힙 세그먼트
- 동적 할당(객체 생성 등)
세그먼트 테이블
- 프로세스의 논리 세그먼트들이 할당된 물리 메모리의 위치를 관리하기 위한 테이블이며, 운영체제가 구성한다
- 시스템 전체에 1개의 테이블을 두고, 현재 적재된 모든 프로세스들의 세그먼트를 관리한다
- 세그먼트 위치의 시작 물리 주소(base), 세그먼트의 크기(limit)로 구성된다
- 세그먼트의 적재 범위는 base ~ base+limit 이 된다

세그먼테이션 구현
구현을 위해선 CPU, 컴파일러와 링커, 운영체제, 로더 등이 세그먼테이션을 지원해야 한다
세그먼트 주소 매핑 과정
- 논리 주소 생성
- 코드나 데이터 컴파일 시 세그먼트 내의 상대 주소로 컴파일되며, 세그먼트에는 번호가 새겨진다
- 세그먼테이션의 논리 주소는 [세그먼트 번호(s), 옵셋(offset)]으로 구성된다
- offset : 세그먼트 내에서의 상대 주소
- 세그먼트 테이블 조회
- 해당 세그먼트의 시작 주소(base)와 최대 크기(limit)을 검색한다
- 기본적으로 메모리에 위치하지만, 속도 향상을 위해 일부는 MMU의 캐시에 저장됨
- 주소 변환
- offset이 limit보다 작다면 유효한 주소로, base와 offset을 더해 물리 주소를 계산하여 출력한다
- 만약 offset이 limit보다 크다면 세그먼트 영역을 벗어나므로, Segmentation Fault를 발생시킨다

운영체제 역할
- 할당된 물리 세그먼트 리스트와 빈 메모리 리스트를 만들고 관리한다
- 세그먼트 테이블을 생성, 관리, 유지한다
- 프로세시 생성 시, 빈 메모리 리스트에서 선택한 물리 세그먼트를 할당하고 프로세스 종료 시, 할당된 물리 세그먼트를 반환한다
컴파일러, 링커, 로더 역할
- 컴파일러
- 프로세스를 세그먼트 단위로 분할
- 링커
- 세그먼트별로 코드/데이터 결합, 세그먼트 주소(Base, Limit) 설정
- 로더
- 세그먼트를 적절한 메모리 위치에 배치, MMU에 세그먼트 테이블 등록
댓글남기기