운영체제 OS

[운영체제/ OS] Main Memory 메모리 관리1/2_ Address Binding, Segmentation, Paging, Memory Protection

명령어가 실행되는 절차는 

메인 메모리에서 명령어를 읽어와서 실행하고 결과를 다시 메모리에 저장해요.

 

하지만 메모리에 접근하는 과정은 단순하지 않고 cycle을 거쳐야 하고 stall(대기)상태에 자주 걸립니다.

 

CPU는 메인 메모리와 (CPU의)레지스터에 직접적으로 접근할 수 있어요.

레지스터는 접근이 작은 대신 빠르고 메모리는 크기가 크고 접근이 느려요.

CPU가 메모리를 오갈때 stall상태에 자주 안 걸리고 속도를 향상하기 위해 cache(캐쉬)를 CPU와 메모리 사이에 놓습니다.

 

속도와 protection 이 두 가지가 중요한 요소입니다.

 

protection을 위해서 base 레지스터와 limit 레지스터가 있습니다.

base(기준) 레지스터는 가장 작은 물리 메모리 주소(=시작주소)를 가지고, limit(상한) 레지스터는 프로세스 주소 공간의 크기를 저장해요.

CPU는 계속해서 user 모드로 하는 메모리 접근을 체크해요. base 레지스터와 limit 레지스터 사이에서 이루어지고 있는지를 체크해요. 아래 그림에서 base와 base+limit 사이를 벗어나면 trap을 발생시키고 error가 나요. 커널모드에서 진행되는 경우는 운영체제가 제한 없이 메모리 접근할 수 있어요. (user와 kernel의 dual-mode 개념 참고)

 

 

 

Address Binding 

쉽게 말해서 논리주소를 물리주소로 바꿔주는 거에요.

프로세스가 실행될 때 디스크와 메모리 사이를 이동하는 과정이죠.

디스크에 있는 프로세스는 input queue를 형성해서 메모리로 이동할 준비를 해요.

 

세 가지 binding 시점이 있는데요.

(1) Compile time : 고정방식으로 요즘 잘 안 써요. 어디로 올라갈지 absolute code(절대주소)가 정해져 있어야 해요. 주소가 바뀌면 무조건 재컴파일이 일어나야합니다.

 

(2) Load time : 컴파일 시 주소를 모르면 relocatable code(재배치 가능한 주소)를 형성하고 그 후론 주소가 바뀌어선 안됩니다. 

 

(3) Execution time : 런타임에 binding이 이루어지고, 그때 그때 필요 따라 주소를 바꾸어서 메모리 효율적이에요. base와 limit 레지스터가 필요해요.

 

 

 

Logical address(논리적 주소)는 CPU에 의해 생겨나고, virtual address라고도 부를 수 있어요. (다음에 가상주소를 자세히 다룰게요)

Physical address는 메모리 장치에 보여지는 주소에요. 말그대로 물리적으로.

 

이제 이 논리적(가상) 주소를 물리적 주소로 binding해야 하는데, 

compile time과 load time에서의 binding은 논리와 물리 주소가 같지만 execution time에선 두 주소가 다릅니다

이 address binding을 해주는 아이를 MMU(Memory-Management Unit)라고 한답니다.

하드웨어 장치로 논리주소에서 물리주소로 매핑을 해주어요.

 

Base 레지스터는 해당 프로세스를 어디에 배치할지, 재배치가능한 주소값을 사용하여 relocation 레지스터라고도 해요.

User(사용자)프로그램은 논리주소만을 다루고, 실제 물리주소는 절대로 못 봐요.

 

 

Dynamic Loading

프로세스 실행 시점에 (한꺼번이 아니라 그때 그때) storage에서 가져오는 방법이에요.

메모리 활용이 좋아지고(사용 안하는 루틴은 load되지 않기에) 

대용량의 코드가 가끔 사용되는 경우 유용해요. 모든 루틴은 reloacatable load format 형태로 디스크에 저장되요.

루틴이 호출되면 그제서야 load가 이뤄져요.

 

Dynamic Linking

실제 실행 시점에 내 코드 주소가 어딜 참조할지 연결하는 작업이에요. 메모리관리나 업데이트 작업이 유용해요.

 

메모리 할당 (Contiguous 연속할당기법)

(1) 고정 분할 기법 : 메모리를 동일 크기의 파티션으로 분할하여 하나의 프로세스는 하나의 파티션에 할당

구현/관리는 쉽지만 제약이 많아 요즘 잘 사용하지 않아요.

(2) 가변 분할 기법 : 시작주소와 크기가 얼마일지 결정해서 그때그때 가용공간에 프로세스를 할당해요.

 

메인 메모리는 두 파티션으로 나뉘어요. 커널과 user.

Base register는 시작주소인 가장 작은 physical address를 가져요.

Limit register는 크기인 논리주소의 범위를 저장해요. 

MMU 는 논리주소를 동적으로 매핑해요. 그리고 OS가 MMU의 값을 결정해요.

 

Multiple-partition Allocation

Internal과 external fragmentation이 많이 발생하여 비효율적이에요.

 

Dynaminc Storage-Allocation Problem 

가용메모리 블록을 hole이라고 할 때

free hole list에서 사이즈 n에 어떻게 만족시킬까?

(1) First-fit : first hole 할당 만족

(2) Best-fit : smallest hole 할당 만족. 남은 공간은 available하게 남김. 

(3) Worst-fit : largest hole 할당. 전체 리스트를 검색해야하고 가급적 안 쓰는게 좋음. 

(1)과 (2)가 (3)보다 속도나 메모리관리면에서 좋습니다.

구체적으로 (1)이 속도에서 (2)가 공간효율에서 좋아요.

 

 

Fragmentation

External Fragmentation - 전체 메모리 공간은 요청에 만족하는데 contiguous(연속)적이지 않은 공간일 때.

Internal Fragmentation - 할당된 메모리가 요청된 메모리보다 약간 클때: 이런 사이즈차이 나면 파티션 사용하지 않음.

 

external fragmentation의 해결책으로 compaction(메모리 압축)인데, 나머지 공간을 모으는 것인데 I/O 오버헤드가 발생할 수 있어요. :(

또 다른 해결책은 segmentation과 paging입니다.

 

Segmentation

메모리 관리 방법으로 non-contiguous (비연속) 기법으로 크기가 다르면 segmentation, 크기가 같으면 paging이라고 보면되요. 보통 이 둘을 섞어서 씁니다.

Segmentation의 자료구조를 살펴보자면,

논리주소는 <segment-number, offset> 이렇게 이뤄져있습니다.

Segment table은 물리주소로 맵핑하고 각 table entry는 

base와 limit을 가집니다. 각 시작주소인 segment의 물리적주소와 segment의 길이를 나타냅니다.

offset이 limit을 넘어가면 trap을 발생시켜요.

 

segmentation hardware

Paging 

segmentation과 마찬가지로 noncontiguous 비연속적인 메모리 관리 방법이자, external fragmentation을 해결하기 위한 방법입니다.

 

물리메모리를 같은 고정사이즈의 블록들인 frames로 나눕니다.

논리메모리를 같은 고정사이즈의 블록들인 pages로 나눕니다.

free frames을 찾고 N pages 크기의 프로그램을 위해선 N개의 free frames을 찾습니다.

page table이 논리주소를 물리주소로 매핑합니다.

page table는 page number와 page offset로 이루어져있습니다.

 

paging은 internal fragmentation 또한 발생할 수 있습니다.

 

Paging model of Logical and Physical Memory

Paging 예제 1

page의 크기로 4Kbyte를 가장 많이 사용합니다.

physical memory는 32byte로 8프레임 

logical memory는 16byte로 4페이지

 

논리주소 4bits에 대해 상위2비트는 페이지번호, 하위2비트는 페이지오프셋입니다.

 

논리주소 0(0000)은 페이지 0, 오프셋 0으로 5*4 + 0 = 20  ->a

논리주소 11(1011)은 페이지 2, 오프셋 3으로 1*4 + 3 = 7  ->k

Paging 예제 2

Page size = 2048 bytes 

Process size = 72,766 bytes

35 pages + 1086 bytes

일 때,

Internal fragmentation 가정: 2048 - 1086 = 962 bytes --> 페이지 절반이 낭비된 것인데, 작은 공간이라 사실 괜찮은 정도입니다.

Average fragmentation: 1/2 frame size 입니다. 

 

 

 

 

paging은 external fragmentation은 거의 일어나지 않고 internal fragmentation은 있지만 크진 않아요.

실제로는 paging과 segmentation을 함께 사용하고 메모리 효율성의 권한을 부여해요.

대충 그림으로 그려보면

이렇게 되는데, 그러면 메모리 접근이 너무 많죠. (segmentation table과 page table도 메모리라고 봐야함)

하지만 2^64를 4Kbyte로 나눠야하는데 한번에 접근할 수 있다는 건 말이 안되죠.

어쩔 수 없이 많은 table을 두고 메모리 접근이 많을 수 밖에 없습니다.

이를 조금이라도 해결하기 위해(메모리table 접근 줄이기)

 

page table의 캐쉬라고 할 수 있는 TLB(Translation Look-aside Buffers)를 사용합니다.

최근에 접근한 page와 frame의 정보를 가지고 있습니다.

TLB를 사용한 paging 기법입니다.

맵핑을 할 때 최근 접근 정보로 TLB에서 hit가 되면 phy addr와 맵핑이 바로 진행되고 

정보가 없다면 TLB miss로 page table로 갑니다. 

 

 

Memory Protection 

위에서 메모리관리는 속도와 Protection, 두 가지가 중요하다고 언급했습니다.

메모리 보호는 각 프레임에 대한 protection bits에 의해 이루어져요.

각 프레임에 protection bit을 통해 read-only인지 read-write 접근권한이 명시되어있습니다.

그리고 valid-invalid bit이 page table이 각 entry에 붙어있습니다.

"valid"는 붙은 페이지는 주소가 logical address 공간에 해당한다는 뜻이고,

"invalid"는 logical address 공간에 해당하지 않는 주소라는 뜻이에요.

 

Shared pages

read-only의 중복 코드는 프로세스끼리 공유할 수 있습니다. 멀티스레드가 같은 프로세스공간을 공유하는 것이랑 비슷해요. 

하지만 각 프로세스들은 private한 code와 data영역을 가집니다. 

 

Shared Pages Example

Shared pages의 예시로 보면 ed1, ed2, ed3가 공유 영역인 걸 알 수 있습니다.

 

다음에 이어서 메모리 관리를 더 다루어볼게요.