[운영체제] 메모리 - 페이징

Local Gaji·2024년 1월 7일

운영체제

목록 보기
16/19

통째로 넣는건 부담스러우니까 썰어서 넣자

🎈 페이징

메모리와 프로세스를 일정한 단위로 잘라서 불연속적으로 할당한다. 페이징에서 프로세스의 논리 주소페이지 라는 단위로 자르고, 메모리 공간을 프레임 이라는 단위로 자른다. (페이지와 프레임은 크기가 같음) 그리고 페이지를 프레임에 할당한다.

페이지에도 스와핑을 사용할 수 있는데, 프로세스 전체가 스와핑 되는게 아니라 페이지 단위로 스와핑된다. (페이지가 스왑아웃 될 때를 페이지 아웃, 스왑 인 될 때를 페이지 인 이라고 함)


그러면 이렇게 불연속적으로 배치되어있는 프로세스들의 실제 위치를 CPU가 어떻게 알아낼 수 있을까?


🎈 페이지 테이블

페이지 번호별로 프레임 위치가 저장된 테이블

프로세스마다 프레임의 위치와 페이지 번호가 매치된 페이지 테이블을 저장해 CPU가 실제 주소를 찾을 수 있게 한다.

각 프로세스의 페이지 테이블은 메모리에 저장되고, CPU는 페이지 테이블들의 주소를 페이지 테이블 베이스 레지스터(PTBR) 에 저장한다. 프로세스를 실행할 때, CPU는 PTBR에서 페이지 테이블의 위치를 찾고, 페이지 테이블에서 페이지가 실제로 위치하고 있는 프레임 주소를 알아낸다.


주소 변환

한 개의 페이지 또는 프레임에 여러개의 주소가 속해있다. CPU가 특정 주소 한개에 접근하려면 페이지 위치를 알아야 하고, 또 그 주소가 페이지 시작 위치로부터 얼마나 떨어져 있는지도 알아야 한다.

페이징 시스템에서 모든 논리 주소는 페이지 번호변위 를 조합해서 나타낸다. 페이지 시작 위치로부터 얼마나 떨어져 있는지에 대한 정보가 바로 변위다.

페이지 테이블에 1페이지 : 5프레임 이라고 저장되어있다.
5프레임은 실제 주소 10번지부터 시작한다.

CPU가 1페이지의 변위 2에 접근하고 싶다면?
페이지 테이블에서 1페이지의 위치를 찾는다. (5프레임)
5프레임의 시작 위치를 찾는다. (10번지)
10번지에서 2만큼 떨어진 12번지에 접근하면 된다.

TLB (변환 색인 버퍼)

최근에 사용한 페이지 테이블의 값(엔트리)을 저장하는 캐시 메모리

페이지 테이블을 메모리에 둔다면, 프레임 주소를 찾기 위해 메모리에 접근해야 하므로 시간이 오래 걸린다. 좀 더 효율적으로 페이지 테이블에 접근하기 위해 TLB를 활용한다.

  • TLB 히트 : 찾고자 하는 페이지 테이블이 TLB에 있는 경우
  • TLB 미스 : 찾고자 하는 페이지 테이블이 TLB에 없는 경우 (메모리에서 찾아야 함)

지역성 덕분에 TLB 히트율이 보통 매우 높다.


EAT (유효 접근 시간)

TLB 효과를 숫자로 재는 지표

히트율 : h
TLB 접근시간 : ε 
메모리 접근시간 : M

1단계 페이지 테이블인 경우, 유효 접근 시간은 아래와 같다.

TLB 히트 시 평균시간 = TLB 조회 + 데이터(메모리) 읽기 = ε + M
TLB 미스 시 평균시간 = TLB 조회 + 페이지 테이블(메모리) 읽기 + 데이터(메모리) 읽기 = ε + 2M
평균 접근 시간 EAT = h*(ε + M) + (1-h)*(ε + 2M)

N단계 페이지 테이블일 경우, 페이지 테이블이 모두 메모리에 있다고 가정하면 아래와 같다.

평균 접근 시간 EAT = h*(ε + M) + (1-h)*{ε + (1+N)*M}

🎈 페이지 테이블 엔트리

페이지 테이블의 컬럼들

페이지 번호와 프레임 번호 말고도 여러 컬럼이 있는데 대표적으로 유효 비트, 보호 비트, 참조 비트, 수정 비트가 있다. (참 / 거짓을 1 / 0 으로 저장)

🔰 유효 비트

현재 해당 페이지에 접근 가능한지 여부
페이지가 스왑 영역으로 쫓겨났거나 해서 메모리에 없다면 접근할 수 없다.
만약 메모리에 적재되어있지 않은 페이지에 접근하면 페이지 폴트 라는 예외가 발생한다.

페이지 폴트가 발생하면?
CPU가 처리를 해서 메모리로 가져오고 페이지에 접근한다.
기존 작업 내역 백업 > 페이지 폴트 처리 루틴 실행 > 원하는 페이지를 메모리로 가져옴 >유효 비트를 1로 변경 > 해당 페이지에 접근


🔰 보호 비트

해당 페이지가 읽기 / 쓰기 모두 가능한지 여부
읽기 권한, 쓰기 권한, 실행 권한을 세자리 숫자로 합쳐서 나타내기도 한다.

ex) 000 : 모두 불가 / 100 : 읽기 전용 / 111 : 모두 가능

🔰 참조 비트

CPU가 이 페이지에 접근한 적 있는지 여부

🔰 수정 비트

해당 페이지의 데이터가 변경된 적 있는지 여부
수정된 적이 있다면 해당 페이지가 메모리에서 사라질 때 보조기억장치에도 값을 동일하게 업데이트해줘야 한다.


🎈 페이지 테이블 관리

페이지 테이블이 프로세스마다 있고, 그 안에 가상 페이지 수만큼 엔트리가 있다.
페이지 테이블을 모두 메모리에 저장하면 메모리를 매우 많이 차지하게 된다.


계층적 페이징

페이지 테이블 > 페이지 테이블의 테이블 > 페이지 테이블의 테이블의 테이블...

페이지 테이블을 또 페이징해서 페이지 테이블의 테이블만 메모리에 저장하고, 원본은 보조기억장치로 보내버린다면 메모리를 절약할 수 있다.

이때 논리 주소는 (바깥 페이지 번호, 안쪽 페이지 번호, 변위) 로 구성된다.


역페이지 테이블

시스템 전체에 단 하나의 테이블만 유지함으로써 메모리 낭비를 줄인다.

계층적 페이징을 하더라도 메모리에 부담이 남는다. 이를 근본적으로 해결하기 위해 시스템 전체에 딱 하나의 테이블을 두고, 페이지 기준이 아니라 물리 프레임 마다 엔트리를 하나씩 할당한다.

각 프레임의 엔트리 = 이 프레임에 어느 프로세스의 몇 번 페이지가 들어있는가 = (프로세스 id, 페이지 번호)
논리 주소 = [ (프로세스 id, 페이지 번호), 변위 ]

엔트리가 물리 프레임 개수 만큼만 있으므로 크기가 작다. 프로세스가 아무리 많고 주소 공간이 아무리 커도 테이블 크기는 물리 메모리 크기에만 비례해서 고정된다.

대신 검색이 느리므로 해시 테이블로 검색을 가속하고, TLB와 함께 쓴다.


🎈 공유 페이지

  • 여러 프로세스에서 같은 데이터 (라이브러리, 코드 등)를 공유해야 할 때, 각 프로세스 페이지마다 데이터를 따로 저장하면 비효율적이다.
  • 따라서 여러 프로세스의 페이지 테이블이 같은 물리 프레임을 가리키게 한다.
  • 각자의 가상 페이지 번호는 달라도 프레임 번호가 같으면 물리적으로 한 벌을 공유하게 된다.

P1과 P2의 libc 페이지가 둘 다 프레임 7을 가리킨다. 데이터는 각자 다른 프레임(2, 5)에 따로 있다 .


주요 용도

  • 공유 라이브러리 / 코드 공유 : 여러 프로세스가 같은 라이브러리를 쓰거나 같은 프로그램을 여러 개 띄울 때 코드를 공유함
    • 공유되는 코드는 읽기 전용이어야 한다
  • 공유 메모리 IPC : 두 프로세스가 데이터를 주고받을 때 (프로세스 간 통신)
  • COW (Copy-on-Write) : fork 후 부모와 자식이 페이지를 읽기 전용으로 공유하다가 누가 쓰면 그때 복사한다. (공유 페이지 위에 세워진 최적화 방식)

단점

  • 역페이지 테이블은 프레임당 엔트리가 하나 (pid, page) 라서 공유 페이지를 담기 어렵다. 역페이지 테이블 환경에서 공유를 지원하려면 추가 장치가 필요함

🎈 내부 단편화 문제

페이징 때문에 메모리 공간이 낭비됨

프로세스를 페이지 라는 단위로 자르는데, 프로세스 크기가 항상 페이지 크기로 나누어 떨어지는건 아니다보니 마지막에 애매하게 남을 수도 있다. 페이지 크기가 10이고 뒤에 남은 프로세스 크기가 2라고 하면 8만큼의 공간이 낭비된다. 페이지 크기가 작을수록 내부 단편화 크기도 작아지지만, 페이지 테이블의 크기가 커지게 되므로, 페이지는 적당한 크기로 설정해야 한다.


세그먼테이션

  • 내부 단편화 문제를 해결하기 위해 메모리를 논리적인 가변 단위 (의미 단위)로 자른다.
    • 프로그래머가 생각하는 구조와 실제 메모리 구조가 일치하게 되어 보호와 공유가 자연스러워진다.
  • 페이지와 마찬가지로 세그먼트 테이블에 실제 주소를 저장한다.
    • 세그먼트 시작 주소 + 길이
  • 내부 단편화는 해결되지만 외부 단편화 문제가 다시 발생한다.
  • 페이징이 단순하고 충분히 강력해서 최근 OS에서는 잘 사용되지 않는다.


🎈 거대 페이지

페이지 크기가 4KB인데 JVM 같은 큰 프로세스가 힙 8GB를 쓰면 페이지 수가 200만 개가 된다. 그런데 TLB는 겨우 수천 개 변환만 캐시할 수 있어서 TLB 히트율이 뚝 떨어지고, 미스마다 4단계 페이지 테이블을 걸어야 한다. (TLB 압박)

이럴 땐 페이지 크기를 크게 만들어서 TLB 미스를 줄이고, 페이지 테이블 엔트리 수와 단계도 줄인다.

단점 : 페이지가 커서 내부 단편화가 커진다. 작은 할당엔 손해고, 큰 연속 할당(JVM 힙, DB 버퍼 풀)에만 이득임

0개의 댓글