베이스와 바운드 방식에는 몇가지 비효율성이 있다.
이러한 문제를 해결하기 위해 나온 아이디어가 있다.
바로 세그멘테이션이다.
위 그림처럼 프로세스를 세그먼트를 사용하여 메모리에 배치한 경우, heap과 stack사이의 공간 낭비가 없는 것을 볼 수 있다.
따라서 이렇게 세그먼트를 사용하여 메모리에 프로세서를 배치하려먼 위 사진과 같은 레지스터 쌍들이 필요하다.
가상 주소를 실제 물리주소로 변환하는 방법이다.
offset이라는 개념이 존재하며 현재 코드 세그먼트의 가상 주소는 0부터 2KB이고 접근하려는 메모리의 offset은 100이다.
따라서 코드의 Base 부분과 offset부분을 합치면 물리주소를 얻을 수 있다.
그러나 여기서 주의할 점이 있다.
Base + 가상주소가 물리주소가 아니라는 것이다.
이 그림에서 힙 세그먼트의 가상 주소 시작은 4096이다.
따라서 접근하려는 데이터는 가상 주소 4200에 위치하며 offset은 104이다.
그러므로 Base + offset을 통해 34K + 104 = 34920이 실제 물리 주소라는 것을 알 수 있다.
유의하자.
예를 들어 가상 주소 4200이 있다고 하였을 때 (01000001101000)2 이진수를 갖는다.
따라서 맨 앞의 01은 Heap을 의미하므로 우리는 이 세그먼트가 힙 세그먼트이구나를 명시적으로 알 수 있다.
이러한 가상 주소를 보고 어느 세그먼트에 있는가는 HW가 판단하는 것이다.
그러나 단점으로는 최대 크기가 고정이라는 것이다. 쉽게 말해 Heap, Code 등 각각만 많이 쓰는 프로그램은 제한적이라는 것이다.
다음은 세그먼트의 하드웨어 구현이다.
맨 처음 상위 2비트를 통해 어느 세그먼트인지 구분할 수 있다.
그 다음 하위 2비트를 통해 offset을 얻는다.
offset이 bound를 넘게 되면 오류가 나는 것이다.
넘지 않으면 무리주소에서 데이터를 얻을 수 있다.
스택은 힙과 코드와는 다르게 거꾸로 확장된다.
따라서 추가적인 하드웨어 지원이 필요하고 세그먼트 확장 방향을 결정할 수 있어야 한다.
그래서 세그먼트 레지스터는 Grows Positive라는 정보를 통해 세그먼트의 주소가 늘어나면 1, 줄어들면 0으로 방향을 결정해준다.
만약 위 그림처럼 Heap영역이 아닌 7KB의 잘못된 주소에 접근하게 되면 세그먼트 폴트 또는 세그먼트 위반 처리를 해야 한다.
HW가 메모리 접근을 검사하여 오류가 났다면 인터럽트를 발생시킬 수 있어야 한다.
세그먼트의 매우 효율적인 장점이 있다.
같은 물리 메모리를 여러 세그먼트에서 공유할 수 있다는 것이다.
코드 공유 형식으로 많이 사용하며 보호를 위해서 추가적인 하드웨어 지원이 필요하다.
따라서 Protection bits라는 추가 하드웨어가 등장하는데
위 그림을 보면 세그먼트당 몇 개의 추가 bit로 Read, Write, Execute 권한을 표시할 수 있다.
또한 특정 세그먼트에서 오류나 버그들을 OS가 처리해줘야 한다.
현재 까지 알아본 세그멘테이션은 3개만 존재하는 시스템이었다.
그렇다면 사이즈가 큰 소단위 세그먼트가 좋을까 아니면 사이즈가 작은 대단위 세그먼트가 좋을까
당연히 둘다 장단점이 존재한다.
그러나 현재는 이 세그먼트를 잘 쓰지는 않는다.
세그멘테이션은 OS에게 문제점을 야기한다.
앞서 나온 외부 단편화 문제를 최소화 할 수 있는 방법을 알아보자
단편화를 관리하기 위한 기법으로
먼저 분할을 알아보자
메모리 할당은 기존의 빈 공간을 여러개로 쪼갤 수 있다.
빈 공간보다 필요한 메모리 크기가 작은 경우 가능
ex) 30바이트의 빈 공간이 10바이트 요청으로 인해 3개로 쪼개어지는 경우
이 상황에서 1바이트의 요청이 들어온다면
다음 그림과 같이 빈공간 리스트의 주소크기가 하나 증가하며 크기가 하나 감소하게 되고 결국 빈공간이 줄어들 것이다.
이것이 바로 메모리 분할이다.
가장 큰 빈 공간보다 큰 크기의 요청이 있다면 들어줄 수 없다.
이런 경우 빈 공간이 이웃해 있는 경우에 하나로 합쳐 크기를 늘려준다.
이것이 바로 병합이다.
위 그림과 같이 여러 개의 빈공간이 존재하고 서로 이웃한 경우에는 병합하여 사이즈를 늘린다.
이 병합은 비용이 안들기 때문에 무조건적으로 좋다고 볼 수 있다.
free는 c언어 사용자라면 모두 알다시피 크기를 매개변수로 받지 않는다.
ptr = malloc(20)
여기서 20바이트만큼 할당을 받고 모두 사용시 해제를 해줘야 한다.
그런데 free에 매개 변수로 크기를 넣어주지 않았는데 어떻게 알아서 빈공간 리스트에 넣을 크기를 알 수 있을까?
실제로는 메모리를 할당 받을 때 20바이트 만큼이 아닌 더 큰 크기를 할당 받아서 가장 위에 존재하는 헤더 블록에 정보를 추가한다.
이와 같이 말이다. 헤더 부분에 사이즈 정보를 저장하는 공간과 매직 넘버라는 공간이 저장되어 있는것을 볼 수 있다.
매직 넘버란 간단히 할당받은 메모리임을 확인시켜주는 일종의 사인이라고 볼 수 있다.
만약 매직 넘버가 1234567이 아니라면 버그가 생김을 알려줄 수 있다.
이처럼 메모리 조각 헤더는 다음과 같은 구조체로 이뤄져 있다.
위 그림을 보면 free를 할 때 헤더의 사이즈 만큼 뺀 여유 공간의 정보를 알 수 있다.
빈 공간 리스트에서 빈 공간의 위치는 어떻게 알 수 있을까
이것은 운영체제가 별도로 해준다.
운영체제가 빈 공간을 관리해주고 빈공간끼리 링크드리스트로 연결이 된다.
전부 해제되었을 경우 외부 단편화가 발생하고 병합이 필요하다.
병합을 해야 큰 메모리 할당이 가능하다.
대부분 메모리 관리자는 처음의 작은 크기의 힙을 할당해 시작하고 부족하면 OS에게 더 요청한다.
다음과 같이 sbrk -> set break를 통해 break를 재설정 하여 확장한다.
많은 여유 공간이 있을 때 어디에 어떻게 할당을 해야할까
만약 총 64KB의 메모리에 8KB의 요청이 들어왔다고 하였을 때 64KB를 32KB 16KB 8KB로 쪼개어 현재 Best Fit으로 요청을 할당한다.
그런 뒤 이 공간을 해제할 때는 다시 쪼개진 메모리, 즉 버디를 찾고 사용하고 있지 않다면 합병한다.
다음으로 가변 크기 할당이 아닌 고정 크기 할당 방법인 페이지에 대해 알아보자