[운영체제] 8. Memory Management
Logical vs Physical Address
- Logical address(=virtual address)
- 프로세스마다 독립적으로 가지는 주소공간
- 각 프로세스마다 0번지부터 시작
- CPU가 보는 주소가 이 주소
- Physical address
- 메모리에 실제 올라가는 위치
- 주소 바인딩(주소 변환)
- 주소를 결정하는것
- Symbolic Address(사용자가 만든) -> Logical Address(프로세스가 갖는) -> Physical address(실제 주소)
- Logical -> Physical로 바꿔주는 시점이 언제일까?
주소 변환
- Compile time binding
- 물리적 메모리주소가 컴파일 시 알려짐
- 시작 위치 변경시 재컴파일
- 컴파일러는 절대코드(absolute code) 생성 - 바꾸고 싶으면 컴파일을 다시 해야함
- Load time binding
- Loader의 책임 하에 물리적 메모리 주소 부여(비어있는 곳에 올림)
- 컴파일러가 재배치 가능코드(relocatable code)를 생성한 경우 가능함
- Execution time binding(=Run time binding)
- 수행이 시작된 이후에도 프로세스 메모리 상 위치를 옮길 수 있음
- CPU가 주소를 참조할 때마다 binding을 점검 (address mapping table)
- 하드웨어적인 지원이 필요(e.g., base and limite registers, MMU)
Memory-Management Unit (MMU)
- MMU
- logical address를 physical address로 매핑해주는 하드웨어 장치
- MMU scheme
- 사용자 프로세스가 CPU에서 수행되며 생성해내는 모든 주소 값에 대해 base register (=relocation register)의 값을 더한다
-
두개의 레지스터로 변환함(relocation register & limit register)
- limit register는 프로세스 주소공간의 최대 크기를 이용해 이 프로세스가 악의적으로 남의 주소공간을 접근할 수 없도록 제한한다(trap : addressing error)
- User program
- logical address 만을 다룬다
- 실제 physical address를 볼 수 없으며 알 필요가 없다
Dynamic Loading
- 로딩 : 메모리로 올리는것
- 프로세스 전체를 메모리에 미리 다 올리는 것이 아니라 해당 루틴이 불려질 때 메모리에 로드 하는것
- memory utilization의 향상
- 가끔씩 사용되는 많은 양의 코드의 경우 유용함(예 : 오류 처리 루틴 - 사용량은 적지만 코드 양은 많음)
- 운영체제의 특별한 지원 없이 프로그램 자체에서 구현 가능함(OS는 라이브러리를 통해 지원 가능)
- 운영체제의 페이징이랑 말과 혼동되기도 한다(원래 다르긴 함)
Overlays
- 메모리에 프로세스의 부분 중 실제 필요한 정보만을 올림
- 프로세스의 크기가 메모리보다 클 때 유용
- 운영체제의 지원 없이 사용자에 의해 구현
- 작은 공간의 메모리를 사용하던 초창기 시스템에서 수작업으로 프로그래머가 구현
- Manual Overlay
- 프로그래밍이 매우 복잡
- 다이나믹 로딩과의 차이점 : 쟤는 메모리가 딸려서 그런게 아니고 utilization높이려고 하는거고, 이거는 메모리가 딸려서 그런거임(초창기) 그리고 프로그래머가 직접 짜는것!
Swapping
- Swapping
- 프로세스를 일시적으로 메모리에서 backing stroe(하드디스크)로 쫓아 내는것
- Backing store(=swap area)
- 많은 사용자의 프로세스 이미지를 담을만큼 충분히 빠르고 큰 저장 공간
- Swap in / Swap out
- 일반적으로 중기 스케줄러(swapper)에 의해 swap out 시킬 프로세스를 선정함
- priority-based CPU scheduling algorithm : 낮으면 out, 높으면 in
- Compile time 혹은 load time binding에서는 원래 메모리 위치로 swap in 해야 함 - 좀 비효율적임
- Execution time binding(런타임 바인딩)에서는 추후 빈 메모리 영역 아무곳에나 올릴 수 있음 - 이게 좀 더 효율적
- Swap time은 대부분 transfer time(swap되는 양에 비례하는 시간)과 같음
Dynamic Linking
- Linking을 실행시간까지 미루는 기법
- Static linking
- 라이브러리가 프로그램의 실행 파일 코드에 포함됨
- 실행파일의 크기가 커짐
- 동일한 라이브러리를 각각의 프로세스가 메모리에 올리므로 메모리가 낭비됨(eg. printf 함수의 라이브러리 코드)
- Dynamic linking
- 라이브러리가 실행시 연결됨
- 라이브러리 호출 부분에 라이브러리 루틴의 위치를 찾기위한 stub이라는 작은 코드를 둠
- 라이브러리가 이미 메모리에 있으면 그 루틴의 주소로 가고 없으면 디스크에서 읽어옴
- 운영체제의 도움이 필요
- Shared library 라고 부른다
Allocation of Physical Memory
-
메모리는 일반적으로 두 영역으로 나뉘어 사용
- OS 상주 영역 : Interrupt vector와 함께 낮은 주소 영역 사용
- 사용자 프로세스 영역 : 높은 주소 영역 사용
-
사용자 프로세스 영역의 할당 방법
- Contiguous allocation(연속할당) : 각각의 프로세스가 메모리의 연속적인 공간에 통째로 적재되도록 하는것
- Fixed partition allocation
- Variable partition allocation
- Noncontiguous allocation(불연속할당) : 하나의 프로세스가 메모리의 여러 영역에 분산되어 올라갈 수 있음 - 현대
- Paging : 같은 크기로 잘라서 올리기
- Segmentation : 의미 단위로 잘라서 올리기(코드, 데이타, 스택 -> 물론 더 잘게 쪼개는것도 가능), 크기가 일정치 않기 때문에 당연히 조각이 발생됨
- Paged Segmentation
- Contiguous allocation(연속할당) : 각각의 프로세스가 메모리의 연속적인 공간에 통째로 적재되도록 하는것
Contiguous Allocation
- 고정분할(Fixed partition) 방식
- 물리적 메모리를 몇개의 영구적 분할(파티션)으로 나눔
- 분할의 크기가 모두 동일한 방식과 서로 다른 방식이 있다
- 분할당 하나의 프로그램 적재
- 딱봐도 융통성 없어보임;;
- 동시에 메모리에 로드되는 프로그램의 ‘수’가 고정됨
- 동시에 최대 수행 가능한 프로그램의 ‘크기’또한 제한됨
- internal fragmentation발생(external fragmentation도 발생)
- 내부조각 : 프로그램 올리고 남는 공간
- 외부조각 : 조각의 크기가 작아 프로그램을 올릴 수 없어서 남는 공간
- 가변분할(Variable partition) 방식
- 프로그램의 크기를 고려해서 할당
- 분할의 크기, 개수가 동적으로 변함
- 기술적 관리 기법이 필요
- External fragmentation 발생 (실행이 끝났다고 해서 그 공간이 채워지도록 땅겨 올라가는것이 아니기 때문)
- Hole
- 가용 메모리 공간
- 다양한 크기의 홀들이 메모리 여러곳에 흩어져 있음
- 프로세스가 도착하면 수용 가능한 홀을 할당
- 운영체제는 다음의 정보를 유지 : 할당공간, 가용공간(hole)
- Dynamic Storage-Allocation Problem
- 가변 분할 방식에서 size n인 요청을 만족하는 가장 적절한 hole을 찾는 문제
- First-fit
- 사이즈가 n 이상인 것 중 최초로 찾아지는 홀에 할당하기
- Best-fit
- Size가 n이상인 가장 작은 hole을 찾아서 할당
- 홀들의 리스트가 크기순으로 정렬되지 않은 경우 모든 리스트를 탐색해야함
- 많은 수의 아주 작은 hole들이 생성됨
- Worst-fit
- 가장 큰 hole에 할당하기
- 역시 모든 리스트를 탐색해야함
- 상대적으로 아주 큰 hole들이 생성됨
- first-fit, best-fit이 worst-fit보다 속도와 공간 이용률 측면에서 효과적인 것으로 알려짐(실험적 결과)
- Compaction
- external fragmentation 문제를 해결하는 한가지 방법
- 사용중인 메모리 영역을 한군데로 몰고, 홀들을 다른 한곳으로 몰아 큰 블럭을 만드는것
- 매우 비용이 많이 든다
- 최소한의 메모리 이동으로 compaction하는 방법(매우 복잡한 문제)
- 컴팩션은 프로세스의 주소가 실행시간에 동적으로 재배치 가능한 경우에만 수행될 수 있다
Paging
- 페이징
- 프로세스의 가상메모리를 동일한 사이즈의 페이지 단위로 나눔
- 가상메모리의 내용이 페이지 단위로 noncontiguous하게 저장
- 일부는 backing storage에, 일부는 physical memory에 저장
- Basic Method
- physical memory를 동일한 크기의 프레임으로 나눔
- logicalmemory를 동일 크기의 페이지로 나눔(프레임과 같은 크기겠죠 당연히?)
- 모든 가용 프레임들을 관리
- 페이지 테이블을 사용하여 logical address를 physical address로 변환
- 외부조각은 발생하지 않지만, 내부프레임은 발생 가능(꼬다리)
Implementation of Page Table
- Page table 은 main memory에 상주(크기가 커서 레지스터나 캐시에 보관하긴 무리임)
- Page-table base register(PTBR) 가 page table을 가리킴
- Page-table length register(PTLR) 가 테이블 크기를 보관
- 모든 메모리 접근 연산에는 2번의 메모리 접근이 필요
- 페이지 테이블 접근 한번, 실제 데이타/명령어 접근 1번
-
속도 향상을 위해 associative register 혹은 translation look-aside buffer(TLB)라 불리는 고속의 lookup hardware cache사용 - 주소변환을 위한 캐시메모리(페이지 테이블 전체를 갖고 있는건 아니고 자주쓰는 일부만)
- TLB는 전부 가지고 있는것이 아니기 때문에 p와 f를 쌍으로 가지고 있으며, 전체검색으로 다 찾아봐야함 (페이지 테이블은 위치 자체에 f가 딱 들어가서 전체검색 할 필요가 없음) - Parallel search
- 페이지 테이블은 프로세스마다 각각 존재함. TLB도 마찬가지
- Address Translation
- 페이지테이블 중 일부가 TLB 에 보관되어 있음
- 만약 해당 페이지 넘버가 TLB에 있는 경우 곧바로 프레임 넘버를 얻음
- 그렇지 않은 경우 메인메모리에 있는 페이지 테이블로부터 프레임 넘버를 얻음
- TLB는 문맥교환시 flush됩니다~(모든 엔트리 비우기)
Effective Access Time
- hit : TLB접근(ε) -> 찾았군! -> 메모리접근(1) = 1 + ε
- miss : TLB접근(ε) -> 못찾음 -> 페이지테이블 접근(1) -> 메모리접근(1) = 2 + ε
- TLB없이 그냥 페이지 테이블만 있으면 기본 2 + ε
- 2+ε > 2+ε-α 이므로 TLB가 있는게 이득이다
Two-Level Page Table
- 현대의 컴퓨터는 주소공간이 매우 큰 프로그램도 지원한다
- 32비트 주소 사용시 2^32(4G)의 주소공간
- 페이지 사이즈가 4K일때 백만개의 페이지 테이블 엔트리가 필요하게 됨
- 각 페이지 엔트리가 4바이트면, 프로세스당 4백만의 페이지 테이블이 각 프로세스마다 필요하단 얘기
- 그러나 대부분의 프로그램은 4기가의 주소공간 중 지극히 일부분만 사용하므로 페이지 테이블 공간 자체가 심하게 낭비되는 셈
-
따라서 페이지 테이블 자체를 페이지로 구성!
- 사용되지 않는 주소공간에 대한 outer page table의 엔트리값은 NULL (대응하는 inner page table이 없음) - 공간상의 이득을 취한다
2^10 인 이유 = 1000개이므로
- 32비트 주소 사용시 2^32(4G)의 주소공간
- 만약 64bit라면 ? 직접 생각해보자~
Multilevel Paging and Performance
- 주소공간이 더 커지면 다단계로 페이지 테이블을 만들어야한다
- 각 단계의 페이지 테이블이 메모리에 존재하므로 논리주소의 물리주소 변환에 더 많은 메모리 접근이 필요하게 된다
- TLB를 통해 메모리 접근 시간을 줄일 수 있다
- 4단계 페이지 테이블을 사용하는 경우
- 메모리 접근시간을 100ns, TLB접근시간이 20ns이고 TLB hit ratio가 98% 라면?
- effective memory access time = 0.98120 + 0.02520 = 128ns 즉 결과적으로 주소변환만을 위해 28ns(128-100)만 필요함
- valid-invalid bit : 사용되지 않는 주소영역에 대해서도 엔트리는 만들어져야 하기 때문에 6,7번페이지는 없지만 테이블에는 존재해야 한다. 대신 invalid로 표시
Memory Protection
-
페이지 테이블의 각 엔트리마다 아래의 비트를 둔다
- Protection bit
- 페이지에 대한 접근 권한(read/write/read-only)
- Valid-invalid bit
- valid : 해당 주소의 프레임에 그 프로세스를 구성하는 유효한 내용이 있음을 뜻함(접근 허용)
- invalid : 해당 주소의 프레임에 유효한 내용이 없음을 뜻함(접근 불허)
- 유효한 내용이 없다는 것은 프로세스가 그 주소부분을 사용하지 않는 경우도 있고, 해당 페이지가 swap area에 있는 경우도 있다
- Protection bit
Inverted Page Table
- 페이지 테이블은 대체 왜 큰걸까?
- 모든 프로세스별로 그 논리주소에 대응하는 모든 페이지에 대해 페이지 테이블 엔트리가 존재하기 때문
- 대응하는 페이지가 메모리에 있든 아니든 간에 페이지 테이블에는 엔트리로 존재하기 때문
- Inverted Page Table
- 페이지 프레임 하나당 페이지 테이블에 하나의 엔트리를 둔것(system-wide) - 프로세스별로 있는게 아님 - 물리적 메모리의 프레임 갯수만큼 엔트리가 존재
- 각 페이지테이블 엔트리는 각각의 물리적 메모리의 페이지 프레임이 담고있는 내용을 표시함(프로세스 id, 프로세스의 논리주소) 논리주소는 프로세스마다 겹칠 수 있으므로 테이지테이블에 프로세스 아이디도 같이 들어가야함
- 물리주소로 논리주소를 알아낼 수 있는 구조
- 단점 : 테이블 전체를 탐색해야함. 공간은 줄여주지만 시간에 대한 오버헤드가 있다.
- 조치 : TLB 쓰쟈~ 공짜는 아니지만~
Shared Page
- Shared code
- Re-entrant Code (=Pure code)
- read-only 로 하여 프로세스 간에 하나의 코드만 메모리에 올림(eg, text editors, compilers, window systems)
- Shared code 는 모든 프로세스의 logical address space에서 동일한 위치에 있어야 함 - 왜냐하면 코드안에 적혀있는 논리주소가 같다는걸 보장해야하기 때문
- Private code and data
- 각 프로세스들은 독자적으로 메모리에 올림
- private data는 논리주소공간의 아무곳에 와도 무방
Segmentation
- 프로그램은 의미 단위인 여러개의 segment로 구성
- 작게는 프로그램을 구성하는 함수 하나하나를 세그먼트로 정의
- 크게는 프로그램 전체를 하나의 세그먼트로 정의 가능
- 일반적으로는 코드,데이타,스택 부분이 하나씩의 세그먼트로 정의됨
- 세그먼트는 다음과 같은 논리적 단위들이다
- main함수 / 사용자함수 / 전역변수 / 스택 / 심볼테이블 / 배열들
- 논리 주소는 다음의 두가지로 구성 : segment-number, offset
- 세그먼트 테이블
- 각각의 테이블 엔트리는 다음으로 구성
- base = starting physical address of the segment
- limit = length of the segment (d > limit 이면 트랩 발생)
- 각각의 테이블 엔트리는 다음으로 구성
- Segment-table base register(STBR)
- 물리적 메모리에서의 세그먼트 테이블의 위치
- Segment-table length register(STLR)
- 프로그램이 사용하는 세그먼트의 수(몇개의 세그먼트로 되어있는지)
- segment number s is legal if s < STLR
페이지는 갯수가 굉장히 많지만 세그먼트는 갯수가 크지 않기때문에 테이블을 위한 메모리낭비가 비교적 적다
Segmentation Architecture(Cont.)
- Protection
- 각 세그먼트별로 protection bit가 있음
- 각각의 엔트리별로 valid bit, read/write/execution 권한 bit 이 있음
- Sharing
- 공유 세그먼트
- same segment number
- 세그먼트는 의미 단위로 쪼개진 것이므로 공유와 보안에 있어 페이징보다 훨씬 효과적이다
- Allocation
- first fit/ best fit
- 외부조각 발생 : 세그먼트의 길이가 동일하지 않으므로 가변분할 방식에서처럼 동일한 문제점들이 발생한다
Paged Segmentation
- 세그먼트를 여러가지 페이지로 구성하는 기법
- 외부조각이 생기는 문제를 해결
- 세그먼트 당 페이지 테이블이 존재
- 세그먼트에 대한 주소변환을 먼저함 -> 해당 페이지 테이블로 감
본 포스팅은 이화여대 반효경 교수님의 운영체제 강의를 기반으로 만들어졌습니다.
문제시 삭제하도록 하겠습니다.
Comments