개요
이번 주차에서는 가상메모리(Virtual Memory)에 대해 다루었다. 이 문서에는 operating system: three easy pieces의 VM 내용을 대략적으로 살펴보려 한다.
메로리 가상화란
운영체제가 각 프로세스마다 자신만의 커다란 전용 메모리를 가진다는 환상을 제공하는 것
13 주소 공간의 개념
13.1 초기 시스템
- 초기 컴퓨터에서, 운영체제는 메모리에 상주하는 라이브러리의 집합이었을 뿐이다
13.2 멀티프로그래밍과 시분할
- 멀티 프로그래밍(multi-programming)의 시대가 되어 컴퓨터를 공유하기 시작했다
- 프로세스 전환을 통해 CPU의 이용률, 효율성을 개선했다.
- 시분할(time-sharing)의 시대, interactivity의 중요성 부각
- 여러 프로그램이 메모리에 동시에 존재하면서 보호(protection)가 중요한 문제로 부각
13.3 주소 공간
- 사용하기 쉬운 메모리 개념
- 주소공간은 실행 프로그램의 모든 메모리 상태를 가짐
- 코드, 스택(함수 호출 지역변수, 인자, 반환 값 등), 힙(동적 할당) 등
- 실제로는 변환되어 임의의 물리 주소(physical address)에 탑재
13.4 VM의 목표
- 투명성(transparency): 실행 중인 프로세스가 가상 메모리의 존재를 인지하지 못하도록 해야 함(오히려 자신의 전용 물리 메모리를 소유한 것처럼 행동해야 함)
- 효율성(efficiency): 시간, 공간적으로 효율적으로 작동해야 함(멀티 레벨, TLB 등을 통해 구현)
- 보호(protection): 자신의 주소 공간 밖에 접근할 수 없다
볼 수 있는 모든 주소는 가상 주소이다
- C 프로그램에서 포인터로 출력할 수 있는 주소는 모두 가상 주소이다
14 메모리 관리 API
14.1 메모리 공간의 종류
- 스택: 컴파일러에 의해 할당, 반환이 암묵적으로 이루어지는 자동(automatic) 메모리
- 힙: 할당과 반환이 프로그래며에 의해 명시적으로 처리된다
14.4 흔한 오류
- 메모리 할당 잊어버리기
- 메모리 부족하게 할당받기: 버퍼 오버플로우(buffer overflow)
- 할당받은 메모리 초기화하지 않기
- 메모리 해제하지 않기: 메모리 누수(memory leak)
- 메모리 사용이 끝나기 전에 메모리 해제하기
- 반복적으로 메모리 해제하기
- free() 잘못 호출하기
15 주소 변환의 원리
- 효율성: 하드웨어 지원 활용
- 제어: 응용 프로그램이 자기 자신의 메모리 이외의 다른 메모리에 접근하지 못하는 것을 운영체제가 보장
- 유연성: 프로그래머가 원하는 대로 주소 공간을 사용하고, 프로그래밍하기 쉬운 시스템
- 주소 변환(address translation): 가상주소를 실제 존재하는 물리 주소로 변환
- 목표: 프로그램이 자신의 전용 메모리를 소유하고 그 안에 자신의 코드와 데이터가 있다는 환상을 만드는 것
15.3 동적 재배치(베이스와 바운드)
- 2개의 레지스터: 베이스(base) 레지스터, 바운드(bound) 레지스터
- PA(physical address) = VA(virtual address) + base
- 바운드 레지스터는 보호(protection)를 지원하기 위해 존재
- 베이스, 바운드 레지스터는 CPU 칩상에 존재하는 하드웨어(MMU, memory management unit)의 일부
동적 재배치를 위한 하드웨어 요구사항
- 특권모드: 사용자 모드 프로세스가 특권 연산을 실행하는 것을 방지하기 위해 필요
- 베이스/ 바운드 레지스터: 주소 변환과 범위 검사를 지원
- 가상주소 변환, 범위 내 검사: 간단한 회로를 통해 지원
- 베이스/ 바운드 갱신을 위한 특권 명령어: 프로그램 시작 전 운영체제가 베이스, 바운드 레지스터 값을 지정할 수 있어야 함
- 예외 핸들러 등록을 위한 특권 명령어: 운영체제가 예외 처리 코드를 하드웨어에게 알려줄 수 있어야 함
- 예외 발생 기능: 프로세스가 특권 명령어 실행을 시도하거나 범위를 벗어난 메모리의 접근을 시도할 때 예외를 발생시킬 수 있어야 함
15.5 운영체제 이슈
- 프로세스 생성시, 운영체제는 주소 공간이 저장될 메모리 공간을 찾아 조치를 취해야 한다(free-list 등)
- 프로세스 종료시, 프로세스가 사용하던 메모리를 회수해 다른 프로세스 혹은 운영체제가 재사용할 수 있어야 한다
- 문맥교환(context switch)시 베이스, 바운드 쌍을 저장하고 복원해야 한다(PCB: process control block)
- 예외 핸들러 제공: 운영체제는 부팅할 때 특권 명령어를 사용하여 예외 핸들러를 설치한다
16 세그멘테이션
- 베이스/ 바운드 방식은 메모리 낭비가 심하다(스택과 힙 사이의 사용하지 않는 공간)
- 주소 공간이 물리 메모리보다 큰 경우 실행 어렵다
- 유연성이 없다
16.1 세그멘테이션: 베이스/바운드(base/ bound)의 일반화
- MMU 안에 오직 한 쌍의 베이스/ 바운드 레지스터가 있는 것이 아니라 주소 공간의 논리적인 세그먼트마다 베이스/ 바운드 쌍이 존재한다
- 각 논리 세그먼터(코드, 힙, 스택)마다의 오프셋을 통해 위치 파악
- 세그먼트 폴트(segment fault): 하드웨어가 주소 범위를 벗어났다는 것을 감지하고 운영체제에 트랩 발생
16.2 세그먼트 종류의 파악
- 가상 주소 최상위 2 bit 혹은 1 bit를 세그먼트 구분을 위해 할당
- 가상 주소의 나머지 bit를 해당 세그먼트에서의 offset으로 활용
- 혹은 묵시적(implicit) 접근방식: 주소가 어떻게 형성되었는지를 관찰하여 세그먼트를 결정
16.3 스택
- 스택은 다른 세그먼트와 달리 반대 방향으로 확장
- 간단한 하드웨어가 추가로 필요: 1 bit 할당하여 음의 방향으로 확장한다는 정보 표시
- offset도 다른 방식으로 얻는다: 보수(complement) 계산 방식이랑 비슷하다
- (기존 오프셋 - 최대 세그먼트 크기)로 음(-)의 오프셋을 얻는다
16.4 공유 지원
- protection bit를 통해 주소 공간들 간에 특정 메모리 세그먼트를 공유하는 것이 가능하다
- 코드 세그먼트를 읽기 전용으로 설정하여 주소 공간의 독립성을 유지하면서, 여러 프로세스가 주소 공간의 일부를 공유할 수 있다
16.5 운영체제의 지원
- 문맥교환(context switch)시 세그먼트 레지스터의 저장과 복원
- 미사용 중이 물리 메모리 공간의 관리: free-list
- 외부 단편화(external fragmentation): 물리 메모리 압축(compact), free-list관리 기법(best-fit, worst-fit, first-fit 등)
- 좋은 알고리즘은 외부 단편화를 가능한 줄이는 것이 목표이다
- 외부 단편화를 해결하는 "최선"의 해결책은 존재하지 않는다
세그먼트 문제
- 세그먼트 크기가 일정하지 않기 때문에 외부 단편화 발생(불가피함)
- 유연하지 못함
17 빈 공간 관리
- 메모리 가상화 뿐만 아니라 메모리 관리 시스템의 근본적인 측면
- malloc 라이브러리, 프로세스 주소 공간, 운영체제 자체 등 빈 공간의 관리
- 고정 크기단위로 나뉜 경우(페이징) 빈 공간 관리는 쉽다
- 가변 크기의 빈 공간들의 집합일 경우 외부 단편화 발생
17.2 저수준 기법: 분할과 병합
- 분할: 비교적 큰 빈 공간에 할당할 경우 할당 공간/ 남는 빈 공간으로 분할하여 "남는 빈 공간"을 새로 빈 공간 리스트에 추가한다
- 병합: 새로 해제된 메모리 청크의 인접 청크가 빈 공간이라면 그들을 합쳐 하나의 큰 메모리 청크로 만들어 관리한다
- 할당된 공간 크기의 파악: 추가 정보(meta-data)를 헤더 블록에 저장
- 사실 빈 영역의 크기는 "헤더 크기 + 사용자에게 할당된 영역의 크기"이다
기본 전략들
- 속도가 빠르고 단편화를 최소로 해야 한다
- 어떠한 전략도 잘 맞지 않는 입력(메모리 할당 패턴)을 만나면 성능이 매우 좋지 않을 수 있다
- 최선은 없다, 장단점이 있을 뿐이다
- 최적 적합(best fit)
- 후보자 그룹(할당 가능한 빈 공간들)에서 가장 작은 크기의 청크를 할당
- 사용자가 요청한 크기에 가까운 블럭을 반환함으로써 공간의 낭비를 줄이려고 노력
- 성능 저하(높은 비용): 항상 전체를 검색
- 최악 적합(worst fit)
- 후보자 그룹에서 가장 큰 청크에 할당
- 많은 작은 청크를 남기는 대신 커다란 빈 청크를 남기려고 시다
- 성능 저하(높은 비용): 항상 전체를 검색
- 엄청난 단편화 발생
- 최초 적합(first fit)
- 요청보다 큰 첫 번째 빈 블록 할당
- 속도가 빠르다
- 리스트의 시작 부분(앞부분)에 크기가 작은 객체가 많이 생길 수 있다
- 다음 적합(next fit)
- 최초적합과 같으나 탐색 시작이 마지막으로 찾았던 원소 다음
- 리스트의 첫부분에만 단편이 집중적으로 발생하는 현상 방지
- 개별 리스트(segregated list)
- 크기별로 리스트 운용
- 슬랩 할당기(slab allocator)
18 페이징
- 세그멘테이션(가변 크기 분할)은 단편화 발생
- 페이징은 공간을 동일 크기의 조각으로 분할
- 물리 메모리도 페이지 프레임(page frame)으로 불리는 고정 크기의 슬롯의 배열이라고 간주한다
페이징 개요
- 유연성: 프로세스의 주소 공간 사용 방식(힙, 스택)과 상관 없이 효율적으로 주소 공간 개념 지원
- 빈 공간 관리의 단순함: 모든 비어있는 페이지의 빈 공간 리스트를 관리하고 비어있는 n개의 칸을 찾으면 된다
- 운영체제는 프로세스마다 페이지 테이블(page table)이라는 자료구조 유지(## 페이지 테이블은 프로세스마다 존재한다 ##)
주소 변환 (VA to PA)
가상 주소를 두 부분으로 나눔: VA(virtual address) = VPN(virtual page number) + OFS(offset)
VPN: 해당 페이지의 번호(페이지 테이블에서 몇 번째 페이지인지)
OFS: 그 페이지에서 몇 번째에 해당하는 위치
ex) 32bit주소체계: VA = |<--- VPN 20 bit --->|<--- OFS 12 bit --->|
주소 변환을 통해 VPN을 PFN으로 변환하고 실제 물리 주소를 만든다: PA(physical address) = PFN(physical frame number) + OFS
PFN: 해당 물리 프레임의 번호(실제 물리 메모리에서 몇 번째 프레임인지)
OFS는 동일하다: 해당 프레임에서 해당하는 위치는 (가상 페이지 내부에서의 위치와) 같다
ex) PA = |<--- PFN n bit --->|<--- OFS 12 bit --->|
페이지 테이블
- 가상주소의 VPN을 물리 주소의 PFN으로 매핑하는 자료구조
- 페이지 테이블은 메모리에 위치하나, 트리 구조로 단계를 높일 경우 디스크에 스왑될 수 있다(중요한 첫 부분은 메모리 상주)
- 선형 자료구조 이외에도 트리 등 고급 자료구조 활용
- 각 PTE(page table entry)에는 VPN에 해당하는 PFN 이외에도 valid bit, protection bit, dirty bit, reference bit, 읽기/쓰기 비트, 사용자/관리자 비트로 각 페이지에 대한 추가 정보를 전한다
페이징 단점
- 느리다: 물리 주소를 알기 위해 페이지 테이블에 접근해야 함(메모리에 2번 접근) --> TLB(하드웨어) 도입
- 메모리 낭비: PTE가 많을 경우 페이지 테이블을 위해 많은 메모리를 할당해야 함 --> 멀티 레벨 페이지 테이블 도입
19 TLB(translation lookaside buffer)
- 주소 변환을 위해 페이지 테이블에 접근하려면 메모리에 한번 이상 접근해야 한다 --> 느리다!!
- 하드웨어의 도움을 통해 실행 속도를 향상: TLB
- TLB: 자주 참조되는 "가상주소 - 실제 주소"의 변환 정보를 저장하는 하드웨어 캐시
- 가상메모리 참조 시, 페이지 테이블(메모리)을 참조하기 전 TLB(빠른 캐시)에 정보가 있는지 먼저 확인
- 지역성 활용
- 시간 지역성(temporal locality): 최근에 접근된 명령어 또는 데이터는 곧 다시 접근될 확률이 높다(반복문)
- 공간 지역성(spatial locality): 참조된 데이터에 인접한 데이터에 접근될 확률이 높다(배열)
가상 주소 참조 순서
- 가상 주소에서 VPN 추출하여 TLB에 존재하는지 검사 --> TLB 히트 --> 물리 주소 획득
- TLB 미스의 경우, 페이지 테이블 접근
- 가상메모리 참조가 유효하고 접근 가능한 경우 해당 정보를 TLB에 갱신
- 가상 주소 참조 재실행 --> TLB 히트
TLB 미스의 처리
- 하드웨어의 관리
- CISC(complex instruction set computers)
- 하드웨어 관리 TLB
- 소프트웨어(운영체제) 관리:
- RISC(reduced instruction set computing)
- 예외 시그널 박생, 커널모드 변경, 트랩 핸들러
- 유연성: 하드웨어 변경 없이 페이지 테이블 구조 변경 가능
- 단순함: 하드웨어가 할 일이 별로 없음, TLB 미스 핸들러가 처리함
TLB valid bit != 페이지 테이블 vaild bit
- TLB valid bit: TLB에 탑재된 정보가 유효한지
- PT valid bit: 해당 페이지가 프로세스에 할당되었는지 여부
TLB의 문제: 문맥 교환(context switch)
- 문맥교환시, 기존 TLB 내용 지우기: TLB valid bit = 0
- 교체 정책: LRU(least-recently-used), 랜덤
20 더 작은 테이블
- 페이지 테이블 엔트리(PTE)가 많아지면 페이지 테이블 자체의 크기가 커져, 메모리를 많이 차지한다
20.1 해법1: 더 큰 페이지
- 페이지를 크게 하면 VPN bit가 줄어들어 PTE가 감소한다
- 다만 페이지 크기가 커지면 페이지 내부의 낭비 공간이 증가할 수 있다(내부 단편화: internal fragmentation)
20.2 하이브리드: 페이징과 세그먼트
- 논리 세그먼트마다 페이지 테이블을 따로 둔다
- 세그먼트마다 베이스/ 바운드 레지스터를 따로 둔다
- 세그먼테이션을 사용하기 때문에 유연하지 못함
- 외부 단편화
20.3 멀티 레벨 페이지 테이블
- 선형 페이지 테이블을 트리 구조로 표현
- 페이지 테이블을 페이지 크기로 나눈다
- 페이지 디렉토리(page directory)를 두어 각 엔트리마다 나뉘어진 페이지 테이블의 할당 여부와 위치를 파악한다
- 장점1: 사용된 주소공간에 비례해서 페이지 테이블 공간이 할당 --> 적은 크기의 페이지 테이블로 주소 공간 표현 가능
- 장점2: 페이지 테이블을 페이지 크기로 분할하여 메모리 관리가 용이함(페이지 테이블을 할당하거나 확장할 때, 빈 페이지를 가져다 쓰면 된다)
- 단점1: 추가 비용 발생(TLB 미스시, 주소 변환을 위해 2번 이상의 메모리 로드 발생)
- 단점2: 복잡도
- 변환 과정
- VPN 추출하여 TLB에 존재하는지 확인 --> TLB 히트
- TLB 미스시, VPN에서 페이지 디렉토리 항목 추출 --> 페이지 테이블 참조
- 페이지 테이블에서 PTE(페이지 테이블 엔트리) 추출 --> TLB에 추가
- 다시 시도 --> TLB 히트
21 물리 메모리 크기의 극복: 메커니즘(mechanism)
- 큰 주소 공간을 지원하기 위해 운영체제는 주소 공간 중에 현재는 크게 필요하지 않은 일부를 보관해 둘 공간이 필요하다
- 스왑 공간이 추가되면 운영체제는 실행되는 각 프로세스들에게 큰 가상 메모리가 있는 것 같은 환상을 줄 수 있다
21.1 스왑 공간
- 스왑공간: 디스크에 페이지들을 저장할 공간
- swap out: 메모리 페이지를 읽어서 스왑 공간에 쓴다
- swap in: 스왑 공간에서 페이지를 읽어 메모리에 탑재
- 운영체제는 스왑 공간에 있는 모든 페이지들의 디스크 주소를 기억해야 한다
- file-backed 페이지들은 디스크에 원본이 있으므로 스왑 영역 이외에 swap out할 수 있다(원본 영역)
21.3 페이지 폴트(page fault)
- 페이지 폴트: 물리 메모리에 존재하지 않는 페이지에 접근하는 행위
- 페이지 폴트 발생 시, 운영체제로 제어권이 넘어가 페이지 폴트 핸들러(page fault handler)가 실행된다
- 요청된 페이지가 메모리에 없고, 디스크로 스왑되었다면, 운영체제는 페이지를 메모리로 스왑해온다
- 해당 페이지의 스왑 공간상의 위치는 페이지 테이블에 저장되어있다
- 페이지 폴트를 하드웨어로 처리하지 않는 이유는 페이지 폴트로 인한 디스크 접근이 느리기 때문이다
- 그리고 페이지 폴트 처리를 위해 하드웨어가 스왑 공간의 구조, 디스크에 I/O 요청 등 세부 사항을 알아야 한다
- TLB 미스 발생 시, 세 가지의 중요한 경우가 있다
- 페이지 유효o/ 존재o: TLB미스 핸들러가 PTE(페이지 테이블)에서 PFN을 가져와 TLB 갱신 후 재시도 --> TLB 히트
- 페이지 유효o/ 존재x: 페이지 폴트 핻들러 실행 --> 물리 메모리에 페이지 탑재 후 재시도
- 페이지 유효x: 무효한 접근에 대한 트랩 핸들러 실행
- 페이지 폴트의 처리 과정:
- 탑재할 물리 프레임 확보
- 교체 알고리즘 실행: 메모리에서 내보낼 페이지 결정 후 여유공간 확보
- I/O 요청으로 스왑 영역에서 페이지를 읽어온다
- 페이지 테이블 갱신 후 명령어 재시도
- TLB 미스 처리 후 명령어 재시드 --> TLB 히트
22 물리 메모리 크기의 극복: 정책(policy)
- 교체 정책(replacement policy): 메모리에 여유 공간이 없을 경우, 내보낼 페이지를 결정하는 방식
22.1 캐시 관리
- 메인 메모리는 시스템의 가상 메모리(VM) 페이지를 가져다 놓기 위한 캐시로 생각할 수 있다
- 캐시 미스의 최소화: 디스크로부터 페이지를 가져우는 횟수를 최소로 만드는 것
- 캐시 히트 횟수 최대화: 접근된 페이지가 이미 존재하는 횟수를 최대로 하는 것
- 평균 메모리 접근 시간(AMAT, average memory access time) = P_hit * T_M + P_miss * T_D
- P_hit(캐시 히트 확률), P_miss(캐시 미스 확률), T_M(메모리 접근 비용), T_D(디스크 접근 비용)
- T_D 디스크 접근 비용이 매우매우 크기 때문에 P_miss를 줄여야한다!
- 캐시 미스를 최대한 줄여야 하는 것이 교체 정책의 목표이다
최적 교체 정책(the optimal replacement policy)
- Belady 선생께서 말씀하시길, "가장 나중에 접근될 페이지를 교체하는는 것이 최적이며, 가장 적은 횟수의 미스를 발생시킨다"
- 가장 먼 시점에 필요한 페이지보다 캐시에 존재하는 다른 페이지들이 더 중요하다
- 미래를 알 수 없기에 최적 기법의 구현은 불가능
- 다른 정책의 비교 기준으로 사용 --> "정답에 얼마나 가까운지"
FIFO
- 가장 먼저 들어온 페이지부터 교체
- 순차 반복 입력에서 안좋은 성능
무작위 선택(random)
- 내보낼 페이지 무작위로 선택
과거 정보의 사용: LRU(least recently used)
- 미래 예측을 위해 과거 사용 이력 확인
- 지역성의 원칙: 최근에 접근된 페이지일수록 가까운 미래에 접근될 확률이 높을 것
- 순차 반복 입력에서 안좋은 성능
- 구현이 어려움: 어떤 페이지가 가장 최근, 오래 전에 참조되었는지 관리하기 위해 모든 메모리 참조 정보를 기록해야 함
- LRU 근사 정책 출현
LRU 근사 정책
- use bit(reference bit) 사용: 페이지가 참조될 때마다 하드웨어에 의해 use bit = 1로 설정, use bit = 0으로 바꾸는 것은 운영체제의 몫
- clock algorithm: 페이지를 환형 리스트로 구성하고, 페이지 교체 시 현재 바늘이 가리키고 있는 페이지의 use bit 검사
- use bit = 1 이면 최근에 참조된 페이지 --> 0 으로 변경 후 다음 페이지 이동
- use bit = 0 인 페이지를 발견할 때까지 반복 --> use bit = 0 인 페이지는 최근에 참조된 적이 없는 페이지
갱싱된 페이지 고려: dirty bit
- 변경된 페이지 내보낼 때, 디스크에 변경 내용을 기록해야 함
- 더티 페이지(변경된 페이지)를 내보낼 때 추가적인 I/O 필요하기 때문에 깨끗한 페이지(변경되지 않은 페이지)를 내보내는 것 선호
- 이를 위해 dirty bit를 추가하여 페이지 변경 상태를 기록
- 예를 들어, 시계 알고리즘에서 "사용되지 않은 상태(use bit = 0)이고, 깨끗한(dirty bit = 0)인 페이지"를 먼저 찾도록 함
기타 용어들
- 요구 페이징 정책(demanding paging): 페이지가 실제로 접근될 때, 운영체제가 해당 페이지를 메모리로 읽어들인다.
- 클러스터링(clustering), 모으기(gathering): 운영체제가 변경된 페이지를 디스크에 반영할 때, 기록해야 할 페이지들을 모은 후 한번에 디스크에 기록( 디스크는 여러 개의 작은 쓰기 요청을 처리하는 것보다 하나의 큰 쓰기 요청을 더 효율적으로 처리할 수 있다)
- 쓰래싱(thrashing): 메모리 사용 요구가 감당할 수 없을 만큼 많고, 실행 중인 프로세스가 요구하는 메모리가 가용 물리 메모리 크기를 초과하는 경우 시스템은 끊임없이 페이징 시도
'dev > SW정글' 카테고리의 다른 글
PINTOS 6주차 file system (0) | 2021.11.02 |
---|---|
PINTOS 2-3주차: USER PROGRAM (3) | 2021.10.13 |
PINTOS 1주차: priority scheduling (0) | 2021.10.04 |
정글일기 20210825 (1) | 2021.08.25 |
WEEK01 돌아보는 시간 (4) | 2021.08.08 |