Logical Address (=Virtual Address) (논리주소=가상주소)
프로세스마다 독립적으로 가지는 주소 공간
각 프로세스마다 0번지부터 시작
CPU가 보는 주소는 Logical Addrss
Physical Address (물리적 주소)
* 주소 바인딩 : (어디로 올라갈 지) 주소를 결정하는 것 (주소 변환)
Symbolic Address -> Logical Address -> Physical Address
* 각 프로그램마다 가지고 있는 논리적 주소가 물리적 주소로 언제 결정되는가? => 총 3가지 시점으로 나눌 수 있다.
Compile Time Binding (컴파일 시)
물리적 메모리 주소(Physical Address)가 컴파일 시 알려짐
시작 위치 변경시 재컴파일
컴파일러는 절대 코드(Absolute Code) 생성 ( = 컴파일시 주소가 Fixed 된다.)
Load Time Binding (실행이 시작될 시)
Loader의 책임하에 물리적 메모리 주소 부여
컴파일러가 재배치가능코드(Relocatable Code)를 생성한 경우 가능 ( = 정해져 있는 것이 아니라 실행시 비어있는 곳 어디든 올라간다.)
Execution Time Binding (=Run Time Binding) (실행도중)
수행이 시작된 이후에도 프로세스의 메모리 상 위치를 옮길 수 있음.
CPU가 주소를 참조할 때마다 Binding을 점검 (Address Mapping Table)
하드웨어적인 지원이 필요 (ex. base and limit registers, MMU) - 주소 변환을 도와줌
주소가 계속 바뀔 수 있음
MMU (Memory Management Unit)
MMU Scheme
사용자 프로세스가 CPU에서 수행되며 생성해내는 모든 주소 값에 대해 Base Register (=Relocation Register)의 값을 더한다.
User Program
Logical Address만을 다룬다.
실제 Physical Address를 볼 수 없으며 알 필요가 없다.
P1 프로세스 실행 중 상황 (논리주소: 0-3000번지, 물리 주소: ~14000번지) 346번지를 달라고 할 때,
시작 위치 14000번지와 논리주소를 더해주면 됨 (요청한 논리주소+시작 위치=물리주소)
리미트 레지스터를 체크해줌. 프로그램의 크기를 담고 있음(3000). 프로그램이 악의적이어서 3000번지까지밖에 없는데 메모리4000번지를 달라고 하면 시작위치를 4000에 더해주면 18000번지. 다른 프로그램이 존재하는 메모리위치가 됨. 이걸 막아야 하므로 limit register를 넘는 주소를 요청하면 trap이 걸림
### hardware Support for Address Translation
운영체제 및 사용자 프로세스 간의 메모리 보호를 위해 사용하는 레지스터
- Relocation register : 접근할 수 있는 물리적 메모리 주소의 최소값 (=base register)
- Limit register : 논리적 주소의 범위
프로그램을 메모리에 동적으로 올린다 =그때그때 필요할 때마다 루틴을 메모리에 올린다
프로세스 전체를 메모리에 미리 다 올리는 것이 아니라 해당 루틴이 불려질 때 메모리에 load
Memory Utilization의 향상
가끔씩 사용되는 많은 양의 코드의 경우 유용
운영체제의 특별한 지원 없이 프로그램 자체에서 구현 가능 ( 운영체제가 라이브러리를 제공해주며 그걸 이용하여 구현)
Loading : 메모리에 올리는 것을 의미
현재 컴퓨터 시스템도 필요한 부분만 메모리에 올라가고 필요 없는 부분은 다시 내림 => Dynamic Loading(프로그래머가 관리)이 아니라 운영체제가 직접 관리해주는 Paging System에 해당(지금은 이 용어를 섞어 쓰기도 함)
Swapping
Backing store (=swap area)
Swap In / Swap Out
일반적으로 중기 스케줄러(swapper)에 의해 Swap Out 시킬 프로세스 선정
Priority-Based CPU Scheduling Algorithm
Compile Time 혹은 Load Time Binding에서는 원래 메모리 위치로 Swap In 해야 함
Execution time binding에서는 추후 빈 메모리 영역 아무 곳에나 올릴 수 있음
Swap Time은 대부분 Transfer Time (Swap 되는 양에 비례하는 시간)임
### Schematic View of Swapping
Swap Time? 보통 디스크를 접근하는 시간은 Seek Time(디스크 헤더가 이동하는 시간)이 대부분을 차지하고 Transfer Time(데이터 전송 시간)은 미미하다. 그런데, 용량이 방대한 *Swapping에서는 파일입출력과는 다르게 디스크 접근 시간 대부분이 Swap 되는 데이터 양에 비례하는 Transfer Time이 차지한다.
프로그램 작성 후 컴파일하고 링크에서 실행파일을 만듬. 링킹이란 여려 곳에 존재하는 컴파일된 파일들을 하나로 묶어서 실행파일로 만드는 과정
Linking을 실행 시간(execution time)까지 미루는 기법
Static Linking
라이브러리가 프로그램의 실행 파일 코드에 포함됨
실행 파일의 크기가 커짐
동일한 라이브러리를 각각의 프로세스가 메모리에 올리므로 메모리 낭비 (eg. printf 함수의 라이브러리 코드)
Dynamic Linking (=shared Library, 리눅스-Shared Object, 윈도우-DLL (Dynamic Linking Library))
라이브러리가 실행시 연결(link)됨
라이브러리 호출 부분에 라이브러리 루틴의 위치를 찾기 위한 stub이라는 작은 코드를 둠
라이브러리가 이미 메모리에 있으면 그 루틴의 주소로 가고(가서 실행), 없으면 디스크에서 읽어옴
운영체제의 도움이 필요
eg. printf의 라이브러리 위치를 찾는 코드만 프로그램 안에 집어 넣어두는 것
메모리는 일반적으로 두 영역으로 나누어 사용
사용자 프로세스 영역의 할당 방법 (관리 방법)
Contiguous Allocation(연속 할당)
NonContiguous Allocation(불연속 할당)
Contiguous Allocation
고정 분할 방식 : 프로그램이 들어갈 사용자 영역을 미리 파티션으로 나누어 두는 것.
- 가변 분할 방식: 미리 나눠두지 않는 것.
- 프로그램의 크기를 고려해서 할당
- 분할의 크기, 개수가 동적으로 변함
- 기술적 관리 기법 필요
- external fragmentation(외부 단편화) 발생
프로그램 A는 분할 1에 넣으면 됨.
프로그램 B는 분할 2에 들어갈 수 없음(분할 2가 크기가 더 작기 때문에) 분할 3에 들어감=> 낭비되는 조각이 발생(외부 조각-프로그램을 올리려고 하는데, 올리려는 프로그램보다 메모리 조각이 작은 경우/내부 조각-프로그램의 크기가 공간보다 작아서 할당 되었지만 사용되지 않아 남는 공간)
굳이 분할의 크기를 나눠놓을 필요가 있는가? -> 가변분할 방식: 프로그램이 실행될 때마다 차곡차곡 메모리에 올려놓는 방법 (가변 분할 방식으로 쓰더라도 프로그램 크기가 균일하지 않기 때문에 외부조각은 생길 수 있음 (내부조각은 생기지 않음))
Hole
가용 메모리 공간
다양한 크기의 Hole들이 메모리 여러 곳에 흩어져 있음
프로세스가 도착하면 수용가능한 Hole을 할당
운영체제는 다음의 정보를 유지
프로그램을 실행할 때 가용곤간 어디에 프로그램을 할당할 것인지? Dynamic Storage-Allocation Problem
Dynamic Storage-Allocation Problem
: 가변 분할 방식에서 size n인 요청을 만족하는 가장 적절한 hole을 찾는 문제
First-Fit
Best-Fit
Size가 n 이상인 가장 작은 Hole을 찾아서 할당 (가장 적합한 Hole에 할당)
Hole들의 리스트가 크기순으로 정렬되지 않은 경우 모든 Hole의 리스트를 탐색해야함 (전체 홀을 탐색해야하므로 시간 부담)
많은 수의 아주 작은 Hole들이 생성됨
Worst-Fit
가장 큰 Hole에 할당
역시 모든 리스트를 탐색해야함
상대적으로 아주 큰 Hole들이 생성됨
지금 적합한 홀이 있을텐데 제일 큰 홀을 써서 작은 홀로 만들어버리기 때문에 좋은 방법은 아님
First-fit과 best-fit이 worst-fit보다 속도와 공간 이용률 측면에서 효과적인 것으로 알려짐(실험적인 결과)
Compaction
external fragmentation (외부 단편화) 문제를 해결하는 한 가지 방법
사용 중인 메모리 영역을 한군데로 몰고 Hole들을 다른 한 곳으로 몰아 큰 Block을 만드는 것
매우 비용이 많이 드는 방법 (전체 프로그램의 binding에 관련된 문제여서)
최소한의 메모리 이동으로 Compaction하는 방법 (매우 복잡한 문제)
Compaction은 프로세스의 주소가 실행 시간에 동적으로 재배치가 가능한 경우에만 수행될 수 있다
Runtime Binding이 지원되야지만 사용할 수 있다.
=> 프로그램을 구성하는 논리적 메모리를 동일한 크기의 Page로 잘라서 각각의 페이지별로 물리적 메모리 어디든 비어있는 위치에 올라갈 수 있게 해줌.
각각의 논리적 메모리에 있는 Page들이 물리적 메모리 어디에 올라가 있는가를 알려줌
Page Table : Page 갯수만큼 엔트리가 존재함
인덱스를 통해 곧바로 접근 가능한 자료구조.
내부에서 상대적 위치는 똑같기 때문에 페이지 내의 offset은 주소 변환에 영향이 없고, 페이지 번호만 바뀜
4kb, 프로그램 하나의 주소 공간이 백만개로 짤림- 많은 용량이 필요함 -> 레지스터에 넣을 수도 없고 하드디스크에 저장할 수도 없음. -> 메모리에 집어넣게 됨
메모리에 접근하기 위해 주소변환, 주소변환 후 데이터를 실제로 접근하기 위해
page table로 가기 전 먼저 TLB를 체크해서 주소변환이 가능한지 확인해서 접근 한 번을 줄임
프로세스마다 TLB의 정보도 다름.
속도는 줄어들지 않으나 page table 공간이 줄어드는 것이 목적임.
logical address (on 32-bit machine with 4K page size)의 구성
page table 자체가 page로 구성되기 때문에 page number는 다음과 같이 나뉜다(각 page table entry가 4B)
따라서, logical address는 다음과 같다
P1은 outer page table의 index이고
P2는 outer page table의 page에서의 변위(displacement)
Address Space가 더 커지면 다단계 페이지 테이블 필요
각 단계의 페이지 테이블이 메모리에 존재하므로 Logical Address의 Physical Address 변환에 더 많은 메모리 접근 필요
TLB를 통해 메모리 접근 시간을 줄일 수 있음
4단계 페이지 테이블을 사용하는 경우
메모리 접근 시간이 100ns, TLB 접근시간이 20ns이고
TLB hit ratio가 98%인 경우
Effective Memory Access Time = 0.98 x 120 + 0.02 x 520
= 128 nanoseconds.
결과적으로 주소변환을 위해 28ns만 소요
TLB 덕분에 다단계 Page Table이 크게 오버헤드가 되지 않는다.
페이지 갯수만큼 엔트리가 존재하고 페이지 프레임 주소 변환 정보가 들어 있음. 추가로 bit가 들어 있음.
사용되지 않는 영역도 엔트리가 만들어져야 함 (6번, 7번) - 사용되지 않기 때문에 invalid로 표시
Page Table의 각 Entry 마다 아래의 bit를 둔다.
Protection bit
Valid-Invalid bit
Page Table이 매우 큰 이유
Inverted Page Table
Page Frame 하나당 Page Table에 하나의 Entry를 둔 것 (System-Wide)
각 Page Table Entry는 각각의 물리적 메모리의 Page Frame이 담고 있는 내용 표시 (Process-id, Process의 Logical Address) 페이지 프레임 갯수만큼 엔트리가 존재함
페이지 테이블을 위한 공간을 줄일 수 있음.
단점
조치
Shared 가능한 코드는 물리적 메모리에 하나만 올리는 방식
여러 프로세스가 공유 가능한 코드를 같은 물리적 메모리 프레임으로 mapping 해주는 기법
반드시 read-only로 셋팅해야 함 / 동일한 logical address에 위치해야 한다
Shared code
Private Code and Data
프로그램은 의미 단위인 여러 개의 segment로 구성
Segment는 다음과 같은 logical unit 들임
main (), function, global variables, stack, symbol table, arrays
Logical Address는 다음의 두 가지로 구성
<segment-number, offset> 세그먼트 번호와 얼마나 떨어져 있는지 offset
Segment table
Segment-table base register (STBR)
Segment-table length register (STLR)
프로그램이 사용하는 segment의 수
segment number s is legal if s < STLR
세그먼트 번호와 길이 체크
세그먼트의 시작 위치 체크
세그먼트 길이보다 요구가 크지 않은지 체크
Protection
Sharing
Allocation
페이징은 개수가 많지만 세그먼트는 개수가 몇 개 안됨.
테이블을 위한 메모리 낭비가 심한 것은 페이징, 세그먼트가 낭비가 더 적다
서로 다른 프로세스가 공유하는 것을 보여주는 예제
0번 세그먼트는 코드를 담고 있음. 같은 역할을 하는 코드이기 때문에 sharing함
두 개의 세그먼트는 같은 위치인 물리적 메모리에 위치하게 됨.
Paging 기법과 Segmetation 기법을 혼합하는 기법
세그먼트 하나를 여러개의 페이지로 구성하는 기법
pure segmentation과의 차이점
세그먼트에 대한 주소 변환 -> s번째 엔트리에 가면 주소 변환 방법이 있음(시작 위치) -> 페이지 개수의 배수로 구성될 것.
allocation 문제가 발생하지 않음. 의미나 보안 같은 경우는 세그먼트 테이블 레벨에서 시행 => 두 가지의 장점을 모두 누릴 수 있음.
세그먼트 당 페이지가 존재할 것. 세그먼트의 길이가 얼마인지를 보면 됨. 세그먼트길이와 요청한 offset을 비교해서 그 이내일 경우에만 주소 변환을 해줌. d(세그먼트 offset) / (p, d) = (페이지번호, 페이지 offset)
프레임번호, 오프셋 = 물리적 메모리 주소
=> 운영체제의 역할은 없고 주소 변환은 하드웨어가 함
🔗강의 바로가기 운영체제 - 이화여자대학교 반효경 교수님 강의를 듣고 정리한 내용입니다.