8. 메모리

김현우·2024년 5월 23일
0

운영체제

목록 보기
9/11
post-thumbnail

💻 메모리관리

알다시피 메모리는 보조기억장치에 비해 용량이 작다.
모든 프로그램을 올리지 못하며 효율적인 사용이 필요하다.

이를 위해서 메모리 공간을 최대한 낭비가 없이 
그리고 다른 프로그램의 영역을 침범치 않게 관리해야한다.

💻 메모리 분할

메모리 관리의 주된 작업은 프로세서가 실행할 프로세스를 메인 메모리로 가져오는 것이다.
이 장에서는 가상메모리 시스템이 아닌 
그냥 메모리 시스템에서 사용하는 그리고 했던 방법을 알아보자

메모리를 최대한 효율적으로 사용하기 위해서는 메모리를 어떻게 분할해야할지 고민해야한다.

1. 고정 분할 
2. 동적 분할

방식이 존재한다.

📖 고정 분할

고정분할은 메모리의 크기를 미리 나눠놓고 그에 맞는 프로세스를 할당 시키는 것이다.

두가지가 존재하는데

균등분할과 비균등분할이 있다.

<균등분할>

1. 메모리를 모두 같은 크기의 파티션으로 나눈다.
2. 파티션의 크기보다 작거나 같다면 프로그램을 적재한다.
3. 현재 실행가능한 프로세스가 없다면 스왑아웃하여 교체한다.

<단점>
1. 파티션보다 큰 프로세스가 존재할수 있다.

2. 메모리 사용에 매우 비효율적이다. 
   파티션보다 작은 크기의 프로세스도 무조건 해당 파티션에 집어넣어야 하기에 메모리가 낭비된다.
   이를 내부단편화(internal fragmentation)이라한다.

<비균등 분할>
위 사진처럼 비균등하게 분할한다.

내부 단편화의 문제는 어느정도 해결이 가능하나

다음과 같은 단점이 있다.

1. 특정 크기로 나눈 파티션의 경우 사용이 거의 안되는 경우 발생
2. 특정 크기의 파티션의 사용이 많아 그 크기의 프로세스들은 빠르게 회전이 안되는 상황

등의 문제가 발생할수 있다.
고정 분할은 사실상 사용하지 않는다.

📖 동적 분할

고정 분할에서의 문제를 해결하기 위해서 동적분할을 사용한다.
동적 분할에서는 파티션의 크기와 개수가 가변적이다.

프로세스의 크기만큼 할당을 시켜 언뜻보면 메모리 낭비가 없이 사용이 가능하게 보인다.
하지만 문제는 한 프로세스가 종료,스왑아웃 등의 이유로 메모리에서 나갈때 발생한다.

위 사진처럼 하나씩 프로세스가 비워질경우 문제가 발생하는데
점점 사용이 불가한 공간들이 생겨 메모리 사용률을 낮추는 문제가 발생한다.

이를 외부 단편화(external fragementation)이라 한다.

이를 해결하는 방법으로 매모리 집약(compaction)이 존재하는데
위로 모든 프로세스들을 쭈욱 미는것이다.

하지만 이 방법은 시간이 오래걸리고 비효율적이다.
📄 배치
집약은 매우 비효율적이기에 우리는 최대한 프로그램을 메모리에 잘 집어넣어야한다.

시작할때 메모리의 낭비없이 집어넣어야하는데 4가지의 방법이 존재한다.

1. best-fit : 요청한 크기와 제일 비슷한 크기의 공간을 찾아서 넣기
2. worst-fit : 제일 큰 공간을 찾아서 넣기
3. first-fit : 처음부터 찾다가 넣을수 있는것이 보이면 넣기
4. next-fit : 이전에 넣었던곳 부터 찾다가 넣을수 있는곳 나오면 넣기

1번과 2번은 공간을 찾아야 하기때문에 시간이 많이 소요된다는 단점이 존재한다.

3번과 4번중에서는 4번이 약간이라도 더 효율적인데
이미 이전에 공간을 찾았다면 그위에는 유의미한 공간이 없을 가능성이 높기에
그 이후 부터 찾는게 조금더 효율적이라 할수있다.

📖 버디 시스템

버디 시스템은 동적과 고정의 중간쯤 되는 방식이다.

버디 시스템에서는 메모리의 공간이 2의 n승으로 구성된다.
프로그램이 x만큼의 공간을 요구한다면 2^(U-1) < s <= 2^U 에 맞는 크기만큼의 공간을 준다.

예를 들어 65kb가 필요하다면 128kb의 크기를 할당해주는 방식이다.
(고정된 크기로 나눠줌)

할당을 해주때는 독특한방식으로 메모리를 나눈다.

위 사진처럼 큰 메모리가 존재한다고 하자.

100k짜리 프로그램을 넣으려한다고 해보자.
1. 1M을 반으로 쪼개서 512K씩 2개가 나온다. 이 둘은 buddy다.
2. 512한개를 반으로 쪼개서 256K 2개로 만든다. 이 둘도 buddy다.
3. 256K한개를 반으로 쪼개 128k 2개로 만든다. 이 둘도 buddy다.
4. 128k한개에 프로그램을 넣는다.

위처럼 원하는 크기가 나올때까지 반으로 쪼개며 이때 반으로 쪼개진 공간은 둘이 버디가 된다.

프로그램이 종료가 된이후에 공간이 빈다면 다시 합쳐지는데

이때 아무 공간끼리 막 합치면 안되고 buddy끼리만 다시 합쳐질수가 있다.
위에서 100K짜리 프로그램이 종료된후 메모리를 합치는 상황이라면

1. 128K짜리 buddy 2개를 합친다.
2. 256K짜리 buddy 2개를 합친다.
3. 512K짜리 buddy 2개를 합친다.
의 과정을 거쳐서 메모리를 합치게 된다.

💻 주소

프로그램이 적재될때 저장되는 메모리의 주소는 항상 달라진다.
우리는 프로그램이 어디에 적재될지 예상치 못한다.

따라서 프로그램은 자신이 0번지에서 항상 시작되는거처럼 주소지를 짜고 이를 논리주소라한다.
실제 메모리에 적재되면 이 논리주소를 물리주소로 변환하는 과정을 거친다.
이를 재배치라 한다.

좀더 자세히 얘기하자면

1. 프로그램은 DISK에 .exe 형태로 존재한다.
   이때는 프로그램과 코드 영역만이 존재한다.
   
2. 실행파일을 실행시키면 OS는 DISK에 존재하는 프로그램을 메인 메모리로 가져온다.(I/O작업)

3. 이때 프로그램은 프로세스로 변하게 되면 PCB와 스택,힙 영역을 추가로 가지게 된다.

4. 프로세스가 만들어지면서 논리주소도 같이 만들어진다.

5. 이 논리주소는 CPU가 사용하는 주소로 데이터영역을 0번지로 잡고 시작된다.

6. MMU를 통해 이 논리주소가 실제 물리주소로 변환된다.

💻 재배치(rellocation)

위 사진은 재배치를 지원하는 하드웨어를 나타낸다.
고정분할,동적분할 버디 등 상대주소를 쓰는 모든 시스템은 위 방법으로 물리주소변환을 한다.

1. Base Register는 program code의 시작주소를 나타낸다.
2. 가산기를 통해 상대주소를 더한다.
3. 더한 값이 현재 프로그램이 사용가능한 주소 안에있는지 확인한다.(bound register사용)
4. 만약 bound register는 Data영역의 마지막주소를 가지고 있으며 
   이값을 넘어서면 interrupt가 걸린다.

***
1. 이 시스템에서는 프로세스를 최대 10개까지 실행하는 multiprogramming 시스템에서 
   Base Register와 BoundRegister가 몇개씩 필요할까?
   
-> 1개 필요하다. CPU안에는 Base,Bound register1개씩만 들어있다.
   프로세서에는 한 프로세스만 들어가기에 들어간 프로세스의 상대주소만 물리주소로 변환하면 된다.

2. 베이스 레지스터에는 프로세스의 시작주소가 들어있다
-> 틀렸다. 프로세스의 시작주소는 PCB의 시작주소이다. 
   프로그램이 처음 시작될때 Program과 Data만 존재하며 이를 기준으로 상대주소가 결정된다.
   그렇기에 PCB의 시작주소가 사용된다면 오류가 발생한다.
   
3. relocation 과정은 위 사진에서 Base register과 상대주소를 이용해서 더한거 까지다.
-> 그 이후는 protection을 위해 Bound register를 사용한다.

💻 페이징

앞서 살펴본 고정 분할이던 가변크기 분할이던 버디시스템이던 결국 메모리를 효율적으로 사용치 못한다.
프로세스를 자르지 못하고 통으로 집어넣으려 하기에 이러한 문제가 생겼다.

그렇다면 프로세스를 잘게 쪼갠뒤 메모리에 빈틈없이 넣으면 어떻게 될까?

메모리를 작은 고정된 파티션으로 나누고 프로세스 또한 같은 크기만큼 나눠보자.
프로세스 조각을 "페이지"라 하며 메모리 조각을 "프레임" 또는 "페이지 프레임"이라한다.

이 기법에선 외부 단편화가 없고 내부 단편화만 존재하는데 
그것도 각 프로세스의 마지막 페이지에서만 발생한다.

-> 메모리를 페이지 크기로 자르는데 프로세스가 페이지 크기만큼
   딱 맞아 떨어지지 않는다면 마지막 페이지는 내부 단편화가 발생가능

위 사진처럼 각 프로세스를 잘게 쪼갠후 페이지 프레임에 집어넣는다.
이때 D프로세스처럼 연속되지 않아도 중간에 끼워서 적재가 가능하기에 메모리를 효율적으로 사용가능하다.

📖 페이지 테이블

페이지에서는 앞에서 사용한 Base register와 Bound register를 통한 
relocation과 protection이 불가하다.

따라서 새로운 관리 방법을 생각해야하는데 이를 위해 "페이지 테이블"을 사용한다.

페이지 테이블은 각 프로세스들의 페이지를 관리한다. 

📖 논리주소

페이지 시스템에서는 페이지 번호와 오프셋을 이용한 논리 주소를 사용한다. 
프로세서는 논리 주소를 받으면 페이지 테이블을 활용하여 물리 주소를 찾아야 한다.
논리 주소는 페이지 테이블 내에서 각 프로세스의 메모리 조각이 어떤 페이지에 위치하는지를 나타낸다.

예를 들어, 페이지의 시작 주소를 0번지로 가정하고, 데이터가 상대 주소 1502번지에 있다고 해보자. 
페이지 하나의 크기가 1KB(1024바이트)일 때, 이 데이터의 논리주소는 다음과 같이 계산됀다:

1502 / 1024 = 1 () → 데이터는 1번째 페이지에 존재하며,
1502 - 1024 = 478 → 오프셋은 478이다.

주소가 16비트이고, 그 중 10비트를 오프셋을 나타내는 데 사용한다고 가정하면, 
1번 페이지는 '0000000001', 478번 오프셋은 '0111011110'으로 나타낼 수 있다. 
이는 16비트 이진수로 표현된 논리 주소와 같다.
📄 페이지크기와 offset
2bit는 총 4개의 주소를 표현할수 있다.
4bit는 16개의 주소를, n비트는 2의 n승 개의 주소를 표현 가능하다., offset표현에 n비트를 사용한다면 2의 n승개의 주소를 표현가능하며
이것이 페이지의 크기이다.

페이지 크기 = 2^(offset 표현하는 비트수)

*** 
프로그램 크기는 byte로 나타내는데 왜 n비트로 표현가능한 주소의 수가 프로그램의 최대 크기인가?

->
프로그램은 메모리상에 존재하며 이때 byte단위로 표현된다.
n비트로 표현이 가능한 주소의 수는 이 시스템에 접근가능한 주소의 범위를 나타낸다.

실제로 프로그램이 1KB더라도 비트의 수가 3개라면 8개의 주소만 표현이 가능한것이다.
따라서 어떤 시스템에서 n비트로 주소를 표현한다면
그 시스템에서의 프로그램의 최대 크기는 2^n승이라고 할수있다.
📄 주소변환(relocation)

논리주소에서 앞에 비트는 페이지의 번호를 나타낸다.
각 페이지 별로 할당된 프레임의 주소가 존재한다.

이를 참조하여 실제 메모리주소 + offset을 해주면 물리 주소가 나온다.

*** 
헷갈린부분

그림 7.9의 전체 메모리도 페이지 테이블이고
프로세스별 페이지 테이블도 따로 존재하는줄 알고 
그림이 나타내는 페이지 테이블이 무엇인지 헷갈림

그것이 아니라 메모리의 프레임을 나눈뒤에 각 프레임별 주소가 존재하고
페이지 테이블에는 프로세스별 페이지 번호와 번호 안에는 메모리내의 프레임 주소가 적혀있는것
offset은 어차피 한 페이지의 크기를 넘기지 못하니 그대로 더해주면 물리주소 완성
📄 protection
페이지에서 protection은 어떻게 할까?

페이지에서 주소는 2개로 나타낸다. 
1. 페이지번호 2. offset

이때 offset은 어차피 페이지의 최대크기를 넘지못하니 신경쓸 필요가없다.
페이지의 번호에 대해서는 페이지 테이블이 각프로세스 마다 관리를 한다.

따라서 내 페이지만 사용을 하려면 페이지 테이블에 있는 공간인지 아닌지를 비교한후
페이지 테이블내에서 존재할때만 접근하게끔 하면된다.
📄 단점
리얼 메모리 시스템에서는 사실 상관이 없다.
하지만 virtual 메모리 시스템에서는 모든페이지가 메모리에 들어올수가없다.
전체 페이지중 일부만 메모리에 들어오게 되는데 이때 문제가 발생한다.

페이징은 프로그램을 같은 크기로 쪼개게 된다.
이때 모든 프로그램을 가져오지 못하는데 
함수나 배열 등이 두개의 페이지에 걸쳐서 써있다면 함수나 배열을 쓰기위해서
굳이 2페이지를 가져와야한다.

또한 사용자 영역과 커널영역이 겹쳐서 한 페이지에 잘려버린다면 문제가 발생할수도 있다.

이를 막기위해 "세그먼테이션"이라는 기법을 활용하여 
연관된 코드를 세그먼트라는 단위로 나눠서 저장한다.

💻 세그먼테이션

세그먼트는 우선 연관이 있는 영역별로 쪼개는 것이라 보면된다.
따라서 세그먼트의 크기는 각기 다를수 있다. (, 최대크기는 정해져있다.)

내부 단편화 문제는 존재하지않는다. 
앞서 배운 동적 분할과 유사하기에 외부단편화의 문제가 있다.

하지만 세그먼트의 크기는 매우 작고 연속되게 저장치 않고 페이지처럼 여기저거 나눠서 저장이 가능하여
외부단편화의 문제가 적은편이며 compaction 또한 자주 해야할 필요가 없다.

위 사진은 페이징

📖 세그먼트 테이블과 주소변환

세그먼테이션에서도 페이징 시스템처럼 세그먼트 주소와 offset으로 논리주소를 나타낸다.

하지만 세그먼테이션에서는 프로그램의 크기를 각기 다른크기로 분할하기에
페이징에서의 기법을 그대로 사용이 불가하다.

세그먼트 테이블에는 2가지의 값이 존재한다.
length와 Base이다.

Base는 해당 세그먼트가 실제 메모리의 어디부터 시작되는지를 나타낸다.

이 Base에 offset을 더하면 실제 세그먼트의 물리주소가 나온다.

Length는 protection을 위한 값이다.

세그먼트는 페이지와 다르게 각각 허용된 크기가 다르다.
따라서 나의 최대 크기를 알고 있어야한다.

앞서 사용한 Bound register와 비슷한 원리인데
Length에는 세그먼트의 마지막 offset값이 들어있다., 세그먼트의 크기가 저장되어있다.

offset의 값과 Length를 비교하여 
offset>Length라면 허용되지 않은 영역을 접근 하려하는 것이다.
따라서 오류가 발생한 것이고 프로그램을 중단 시켜야한다.
profile
학생

0개의 댓글