메모리 계층 구조

메모리 시스템 내의 모든 기억 공간을 포괄적으로 부르는 말(일반적으로는 메인 메모리를 가리키긴 한다) 실행될 프로그램의 코드와 데이터가 적재되며, 실행 중 발생하는 데이터가 저장된다

  • 메모리 계층 구조

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)

  • 코드나 데이터, 자원 등이 짧은 시간 내에 다시 사용되는 특성
  • 캐시를 통한 빠른 데이터 엑세스로 얻는 이득이 캐시 미스로 인한 손실보다 크다
메모리 관리

메모리가 운영체제에 의해 관리되어야 하는 이유

  1. 메모리는 공유 자원이다
    • 여러 프로세스에 의해 사용되는 공유 자원이므로 임의 사용을 막는다
  2. 메모리 보호 기능
    • 비선점, 커널 공간 접근 제한 등
  3. 메모리 용량 한계 극복
    • 가상 메모리와 같은 기법을 통해 용량 한계를 극복한다
  4. 메모리 사용 효율을 높이기 위함
    • 다중프로그래밍 정도를 운영체제가 담당한다

여러 기능 중에서도 반드시 메모리 할당, 메모리 보호 기능은 제공되어야 한다

메모리 주소

물리 주소(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, 컴파일러와 링커, 운영체제, 로더 등이 세그먼테이션을 지원해야 한다

세그먼트 주소 매핑 과정

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

운영체제 역할

  • 할당된 물리 세그먼트 리스트와 빈 메모리 리스트를 만들고 관리한다
  • 세그먼트 테이블을 생성, 관리, 유지한다
  • 프로세시 생성 시, 빈 메모리 리스트에서 선택한 물리 세그먼트를 할당하고 프로세스 종료 시, 할당된 물리 세그먼트를 반환한다

컴파일러, 링커, 로더 역할

  • 컴파일러
    • 프로세스를 세그먼트 단위로 분할
  • 링커
    • 세그먼트별로 코드/데이터 결합, 세그먼트 주소(Base, Limit) 설정
  • 로더
    • 세그먼트를 적절한 메모리 위치에 배치, MMU에 세그먼트 테이블 등록

댓글남기기