OS가 하는 여러가지 목표 중에 HW Resource를 보호하는 것이었다.
Memory도 Resource이다.
니 데이터가 300번지에 있다라고 알려주고, 실제로는 다른 번지에 있다. (보호하기 위해서)
➡️ 내가 알고 있는 주소가 실제 주소가 아닐 수도 있다.
Compile Time Binding
: compile(compile + linking이 큰 의미의 compile)할 때, 실행 시의 실제 주소가 미리 정해짐
compiler는 Absolute code(절대 주소)를 생성.
만약 위치가 변경되어야 한다면, 그 코드는 다시 컴파일 되어야 함
➡️ 작은 시스템이나 embedded system 등에서 사용 (OS가 없는 또는 매우 작은)
➡️ Firmware는 code, data를 절대 번지로 지정해놓음
Load Time Binding
: program이 memory에 올라오는 순간, 주소가 확정 된다.
Loader의 책임 하에 주소 부여.
compiler는 일단 Relocatable Code(재배치 가능 코드)로 만듦
➡️ 시작점 m, 상대번지는 m+1, m+10, …
이런 식으로 가정한 m번지는 실제 loading할 떄, 확정되어서 나머지의 주소가 모두 확정남
Execution Time Binding
: Loading될 때는 대충 올려놓고 실행될 때, 주소가 확정된다 == Binding된다.
CPU가 주소를 생성할 때마다 Binding을 점검 (Address Mapping Table)
실행될 때마다, 주소가 바뀔 수 있다.
이것이 가능하려면, 특별한 HW를 이용해야 함 (ex. Base & limit)
OS는 Execution Time Binding
을 대부분 사용한다.
compile
: source를 실행 가능한 명령어로 바꿔주는 과정
보통의 경우 compile되면, object file이 만들어짐
(Interpreter 언어는 소스코드를 바로바로 해석하여 실행됨)
➡️ compile을 하면, source code 언어가 다 달라도 system이 이해할 수 있는 중간 언어로 바뀐다.
각각의 언어의 장점에 맞게 코딩하고 모아서 사용할 수 있게 된다.
object 묶음이 library이다. (이미 컴파일된 것이기 때문에 바로 사용할 수 있다.
windows : .obj, linux : .o
object에 linker를 linking.
이때, 실행파일이 만들어짐.
실행파일을 실행시키면, loader가 동작하여 OS에게 memory에 올려달라는 요청.
OS가 실행파일을 memory에 올려줌. == loading
그때 A, B라고 하는 것의 주소를 언제 명확하게 할 것인가?
1. Compile Time Binding
: 위 program이 compile되는 순간.
100은 30번지, 330은 32번지, .. 에 저장됨
2. Load Time Binding
: A가 30번지?32번지?인지는 모르겠지만
A가 100번지라고 정해졌으면 B는 102, C는 106번지 ... 로 정해진다.
3. Execution Time Binding
: program이 실행될 때, 명령어가 읽힐 때, OS가 할당해줌
마지막 정해지기 전까지 모든 번지는 기호이거나 상대번지이다.
Compile Time Binding이 아니라고 하면,
memory를 관리해주는게 반드시 있어야 한다.
그것이MMU(Memory Management Unit)
이다.
physical address
: 실제 물리적으로 알고 있는 RAM에 배치되어 있는 주소.logical address
:MMU
가 모든 과정에서 address를 낚아채서MMU
가 알려주는 것일 뿐이다.Mapping
이라고 하고,이처럼 실제 HW memory에 저장되는 address가 physical address이고,
CPU와 사용자가 알고 있는 address를 logical address라고 한다.
logical address를 physical address로 바꿔주는 모든 작업을 mapping이라고 하고,
그 mapping하는 과정을 책임지는 OS의 program을 MMU라고 한다.
SW만 갖고 동작할 수도 있고, HW를 사용해서 동작시킬 수도 있고,
1. mapping table, 2. 단순한 숫자 덧셈, 3. hash 등의 방법을 사용할 수도 있다.
Rountine : 우리가 만든 program의 일부분.
Routine(program의 일부분)이 호출되기 전까지는 loading되지 않는다.
== program을 실행시킬 때, 전부를 main memory로 올리지 않겠다.
이유는?
필요할 때만 loading. OS가 아니라 User가 program을 직접 설계해야 함.
잘 안쓰는 것은 필요할 때만 쓰기 때문에 공간이 효율적이다.
Static Linking과 다르고, Dynamic Loading과도 다르다
Static Linking
:
Static Library(object file을 모아 library로 만들어놓은 것)
std lib라고 해서 printf, scanf 등등 수많은 함수들을 모아둔다.
그 중 하나의 함수만을 사용하기 위하여 std lib를 모두 사용하면, memory를 많이 차지하게 된다.
printf만 사용할 것이라면, printf만 떼서 사용하면 된다.
그런데 printf를 사용하는 program이 100개가 있다면 printf를 수행하는 subroutine이 100개가 있어서
전체 memory로 봤을 때, memory에 printf가 많이 있을 수 있다.
Dynamic Linking
:
이러한 비효율을 완화하고자 printf 1개를 정해진 곳에 올려주고,
100개의 program이 그 곳을 공유하여 갖다 쓸 수 있도록 하자.
program마다 출력은 다 달라야 하기 때문에 linking을 다르게 해야 한다.
나중에 printf가 필요할 때, printf가 있을 만한 위치에 stub이라고 하는
가짜 함수를 만들어서 연결하여 사용할 수 있다.
main program 입장에서는 printf가 자신의 program에 붙어있지 않고,
stub이라는 매개체를 통해서 printf를 연결하여 사용할 수 있다.
만약에 실행하려고 했는데,
memory에 해당 library가 없으면(어떤 Program이 처음으로 그 library를 사용하려고 하면)
OS가 그 library를 그때 한 번 loading해준다.
그리고나서 stub에 연결시켜준다.
또 다른 program이 똑같은 library를 사용하려고 할 때,
이미 loading이 되었기 때문에 연결만 시켜준다.
➡️ 여러 program에서 반복적으로 사용하는 library의 특정 module을 memory에 올리고, 여러 program이 그곳을 공유하여 갖다 쓸 수 있도록 stub이라는 매개체를 통해서 연결하여 사용한다.
만약 memory에 해당 library가 없으면, OS가 그 library를 그때 한 번 loading해주고나서 stub에 연결시켜준다.
➡️ 따라서 dynamic loading과 달리 OS의 도움이 필요하다.
자주 쓰던 안쓰던 반복될 수 있는 것들은 모아 stub을 이용하여 공유시키자.
(.dll : dynamic link library)
→ 공간 절약(disk, main memory에서 모두 공간을 절약할 수 있어서 현대 OS는 모두 이러한 방식을 사용)
(Dynamic Loading과 달리 OS의 도움이 필요하다.)
어떤 주어진 시간에 필요한 명령어와 데이터를 특정 memory에 유지하게 하는 것.
User에 의해 Implementation되고, OS는 관여하지 않음.
example)
main memory는 두 부분으로 나뉨
1. OS가 쓰는 것
2. OS에서 할당해준 user process를 위한 것
➡️ user process는 OS의 memory를 침범해서는 안 된다.
사용자 memory는 사용자 memory끼리 모여있게(Contiguous) 해야 한다.
Base and Limit Registers
:Limit Register
:Relocation Register
즉
process 안에서는 base, limit을 써서 연속적으로 하여 관리가 편하게 하는 것이 맞는데
굳이 limit이 끝난 그 지점에서부터 그 다음 process가 시작되게 할 필요가 없다.
그러면 어떻게 해야 하는가?
➡️ 그 다음 process는 연속적이지 않고 다른 memory에 있을 수 있다.
(하지만 다른 memory에 있기 때문에 또 문제가 생길 수 있다.)
Hole
이라고 한다.dynamic storage allocation problem(동적 메모리 할당 문제)
fit
: Hole에 맞춘다First-fit
:Best-fit
:Worst-fit
:Best-fit이 가장 좋아보이지만, Best-fit은 계속해서 비교해야 하기 때문에 복잡하다.
일반적인 경우에는 First-fit > Best-fit > Worst-fit 순으로 좋지만
항상 그런 것은 아니다.
또한 남아 있는 Hole은 가능한 크게 유지하는게 좋다.
그런 면에서 Worst-fit은 좋지 않다.
Best-Fit이 Best, Worst-Fit이 Worst가 아닐 수가 있다.
어떻게 응용하느냐, data에 따라 결과가 달라질 수 있다.
External Fragmentation
:
process들이 memory에 load되고 제거되는 일이 반복되다 보면,
어떤 가용 공간은 너무 작은 조각이 되어 버린다.
예를 들어, 100K process가 있고, 20K, 30K, 50K Hole이 떨어져 있다.
전체 가용 공간은 100K가 있지만, 100K Process는 Continuous해야 하기 때문에
세 block으로 나누어 할당할 수 없다.
Internal Fragmentation
:
어떤 block을 만드는 그 자체 때문에 필연적으로 생길 수 밖에 없는 공간.
예를 들어, 10K짜리 block에 8K process를 할당하면
10K짜리 block 내부의 2K짜리의 공간은 사용하지 못하는 공간이 된다.
이처럼 모든 process가 그 block에 대해서 정확히 딱 맞는 것이 아니기 때문에
어쩔 수 없이 내부에 필연적으로 사용하지 못하는 공간이 생기게 된다.
HDD를 쓰다보면 처음에는 잘 돌아간다.
2, 3년 뒤에는 느려진다.
file이 여기저기 흩어지게 되어 처음에 1초 걸렸다면 2, 3초가 걸릴 수도 있다.
➡️ 그래서 defragmentation을 한다. 흩어져 있던 단편들을 모아준다.
External Fragmentation
을 살펴보자.
앞서 External Fragmentation
의 문제점은 다음과 같았다.
100K process가 있고, 20K, 30K, 50K Hole이 떨어져 있다.
전체 가용 공간은 100K가 있지만, 100K Process는 Continuous해야 하기 때문에
세 block으로 나누어 할당할 수 없다.
➡️ 떨어져 있는 Free memory들을 하나의 Hole로 Compaction(압축=한곳에 몰기)
해보자.
External Fragmentation
의 문제점을 해결하고자 Compaction
을 고려할 수 있다.위 1, 2, 3 방법도 OS 입장에서 data를 옮기는 비효율이 생긴다.
그 원인은 Process를 Contiguous하게 한 것.
따라서 Process를 Contiguous하게 하지 말자
➡️Paging
지금까지 논의된 memory management는
process의 physical address space가 contiguous해야 했다.
그로 인해서 compaction 기법을 사용하였고,
그것도 I/O를 이용하여 Data를 읽고, 쓰는 비효율적인 방법이 될 수 있었다.
즉
process들이 contiguous하게 있어야 하는 것이 문제였기 때문에 contiguous하지 않게 해보자.
➡️ CPU 입장, user 입장에서 실제로는 연속적으로 있는 것처럼 해보자.
Paging
:
logical memory 입장(CPU 입장)
: 1, 2, 3, 4가 연속으로 있다.physical memory
: page table로 page 0이 physical memory의 어디에 있는지 정보를 저장. 이게 왜 좋을까? compaction을 할 필요가 없어짐.
paging을 쓰면, external fragmentation은 절대 생기지 않는다.
CPU : 내가 1000번지 읽겠다. → 1000번지 가서 data를 읽어오는 것이 아니다.
1000번지 : Logical Address
1000이 p, d로 이루어져 있다.
p
: page 번호, d
: offset (기준부터 얼만큼 떨어져 있는가)
(p, d)로 이루어져 있는 Logical address를
(f, d)로 이루어져있는 Physical Address로 바꾸겠다.
➡️ 똑같은 크기의 p(page)를 page table을 통해 똑같은 크기의 f(frame)으로 바꾼다.
일반적으로 logical memory가 physical memory보다 훨씬 크다.
: 그래서 physical memory를 마치 있는 것처럼 생각하는게 Virtual Memory이다. (추후에 공부..)
External Fragmentation
을 해결할 필요가 없다.
page table을 이용해서 값만 써주면 된다.
Internal Fragmentation
을 계산해보자.
Page size = 2,048 bytes
Process size = 72,766 bytes
라고 가정해보자.
Internal Fragmentation을 줄이기 위해서 Page의 크기가 줄어야 한다.
그럼 Page의 크기를 작게 하면 Internal Fragmentation을 해결할 수 있는 것 아닌가?
➡️ Internal Fragmentation을 해결할 수는 있지만, 무작정 Page를 작게할 수는 없다.
왜?
page 크기가 작아지면 Internal fragmentation이 줄어들지만, page table이 늘어난다.
page table도 결국 memory에 있기 때문에 무작정 page 크기를 작게 만들 수 없다.
따라서 page 크기를 무작정 크게 하는 것도 좋지 않고, 작게 하는 것도 좋지 않다.
Free Frames
: 가용 frame. 할당되지 않은 frame.
OS는 Physical Memory를 관리하기 때문에
Physical memory의 자세한 할당 상황에 대해 파악하고 있어야 한다.
즉, 어느 frame이 사용 가능한지, 총 frame은 몇 개나 되는지 등을 알아야 한다.
따라서 OS는 그러한 정보들을 위해 Free-Frame List
라는 목록으로 보관하고 있어야 한다.
OS는 각 Process마다 Table을 따로 따로 갖고 있다.
PTBF(Page-Table Base Register)
PTLR(Page-Table Length Register)
cahce
를 사용하자는 아이디어가 제시됨TLB(Translation Look-aside Buffer)
: main memory에서 data를 읽기 위해
(1) page table에서 주소를 읽고
(2) 그 실제 주소에서 가서 data를 읽어야 하기 때문에
시간이 오래 걸리는 것을 방지하고자,
TLB
라고 하는 cache를 이용해보자.
page table을 읽기 전에 읽은만한 예상된 것들을 모아둔 것을 cache에 넣어보자.
hit
: cache에 있다.miss
: cache에 없다.EAT(Effective Access Time)
: , , 정보가 있으면,
CPU에서 data를 읽을 때 걸리는 시간을 계산할 수 있다.
Example : Cache에서 data를 찾은 경우 == 최상의 경우
Example : Cache에서 data를 못 찾은 경우
Example
The percentage of times that the page number of interest is found in the TLB is called the hit ratio. An 80-percent hit ratio, for example, means that we find the desired page number in the TLB 80 percent of the time. If it takes 10 nanoseconds to access memory, then a mapped-memory access takes 10 nanoseconds when the page number is in the TLB.
➡️ 해석해봤을 때,
hit ratio = 80%, main memory 접근 시간 10ns, cache 접근 시간 00ns가 소요된다고 가정했을 때, EAT?
v
: valid bit (제대로 쓴거 맞다)i
: invalid bit (쓰레기 값이다)64-bit computer는 RAM을 까지 사용가능. 거의 무한대로 사용 가능하다.
하지만 memory가 커진다는 의미는 page table도 커진다는 의미이다.
이 큰 memory를 handling하는 Table을 만드는 것은 쉽지 않고, 용량이 매우 커지고, 찾아가기도 쉽지 않다.
Page Table Structure
가 있다.Hierarchical Paging
: Page Table의 Level을 나누자.
2 Level Example
32bit computer에서 2 Level로 한다고 가정
offset 크기가 라고 가정 == 한 page 크기가 12bit
➡️ 정확히 10, 10, 12 bit가 나온다.
왜 그럴까?
3 또는 4 Level
로 한다면?→ hit ratio가 올라가면, 빨라진다.
HW문제가 아니라, program을 만들 때 HW를 잘 이해하고 만드는 것이 중요하다.
offset(d)은 base ~ base+limit 안에 있어야 한다.
== d가 limit보다 작으면? physical memory로 mapping : trap; addressing error
➡️ physical memory = base + d
따라서 segment table만 잘 관리하면,
여러 개의 segmentation을 관리할 수 있다.
➡️ 현대의 큰 computer에서는 memory가 너무 커지니까 이렇게 하지 않고 page와 같은 기법 사용. embedded system에서 이러한 방법을 사용한다.
장점 1
: 작은 memory나 embedded system에서 서로 memory를 침범하지 않도록 하는 장점이 있다.
장점 2
: memory를 공유하여 physical memory 사용의 효율을 높일 수 있다.
예를 들어, editor(메모장)가 있고,
program 자체의 code와 그 process가 쓰는 data로 이루어져 있는데,
editor를 두 개를 띄웠을 때,
editor program code는 공유하고, 각각의 process가 갖고 있는 data만 다르게 하면 된다.
→ Code segment는 공유, data segment는 분리한 경우