세그멘테이션

코승호딩·2022년 10월 20일
0

운영체제

목록 보기
7/10
post-thumbnail

📌세그멘테이션이란

베이스와 바운드 방식에는 몇가지 비효율성이 있다.

  • 너무 큰 빈 영역
  • 빈 공간이 물리 메모리를 차지한다.
  • 맞는 주소 공간이 물리 메모리에 없다면 실행이 어렵다 (너무 크거나 쪼개져 있거나)

이러한 문제를 해결하기 위해 나온 아이디어가 있다.
바로 세그멘테이션이다.

  • 세그먼트 : 일정한 크기의 연속된 메모리 공간
    일반적인 주소 공간은 3개의 세그먼트(Code, Stack, Heap)으로 구성된다.
    논리적으로 하나의 프로세스를 여러 개의 세그먼트로 나눌 수 있다.
  • 각 세그먼트는 물리메모리의 여러 곳에 산재할 수 있고 세그먼트 별로 베이스와 바운드가 존재한다.

위 그림처럼 프로세스를 세그먼트를 사용하여 메모리에 배치한 경우, heap과 stack사이의 공간 낭비가 없는 것을 볼 수 있다.
따라서 이렇게 세그먼트를 사용하여 메모리에 프로세서를 배치하려먼 위 사진과 같은 레지스터 쌍들이 필요하다.

  • 코드 세그먼트의 경우 32KB에 배치가 되며 크기는 2K이다.
  • 힙 세그먼트는 34KB에 배치가 되며 크기는 2K이다.
  • 스택 세그먼트는 28KB에 배치되며 크기는 2K이다.

📌세그먼트의 주소변환


가상 주소를 실제 물리주소로 변환하는 방법이다.
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으로 방향을 결정해준다.


📌세그먼트 오류(Fault, Violation)


만약 위 그림처럼 Heap영역이 아닌 7KB의 잘못된 주소에 접근하게 되면 세그먼트 폴트 또는 세그먼트 위반 처리를 해야 한다.
HW가 메모리 접근을 검사하여 오류가 났다면 인터럽트를 발생시킬 수 있어야 한다.


📌공유 지원

세그먼트의 매우 효율적인 장점이 있다.
같은 물리 메모리를 여러 세그먼트에서 공유할 수 있다는 것이다.
코드 공유 형식으로 많이 사용하며 보호를 위해서 추가적인 하드웨어 지원이 필요하다.
따라서 Protection bits라는 추가 하드웨어가 등장하는데

위 그림을 보면 세그먼트당 몇 개의 추가 bit로 Read, Write, Execute 권한을 표시할 수 있다.
또한 특정 세그먼트에서 오류나 버그들을 OS가 처리해줘야 한다.


📌대단위 vs 소단위 세그멘테이션

현재 까지 알아본 세그멘테이션은 3개만 존재하는 시스템이었다.
그렇다면 사이즈가 큰 소단위 세그먼트가 좋을까 아니면 사이즈가 작은 대단위 세그먼트가 좋을까
당연히 둘다 장단점이 존재한다.

  • 대단위 세그먼트 장점 : 세그먼트의 수가 줄어 관리가 편하며 하드웨어가 간단해진다
  • 소단위 세그먼트 장점 : 유연하게 메모리를 구성할 수 있지만 관리가 힘들다.

그러나 현재는 이 세그먼트를 잘 쓰지는 않는다.


📌OS지원 : 단편화(Fragmentation)

세그멘테이션은 OS에게 문제점을 야기한다.

  • 외부 단편화 : 작은 크기의 빈 공간들이 많이 생기는 현상
    힙 스택 사이 빈공간을 쓰지 않아 낭비가 있는 내부 단편화는 세그먼트로 해결했다.
    그러나 프로세스들간의 빈 공간이 많이 생기며 크기가 작아 사용하기 어려운 외부 단편화가 발생한다.
    따라서 24KB의 빈 공간이 있지만 여기저기 흩어져 있다면 20KB의 요청을 OS가 들어줄 수 없게 될 것이다.
  • 압축 : 기존의 세그먼트들의 물리적 위치를 재조정 하는 것
    압축에는 비용이 든다.


📌빈 공간 관리

앞서 나온 외부 단편화 문제를 최소화 할 수 있는 방법을 알아보자
단편화를 관리하기 위한 기법으로

  • 저수준 기법
    분할과 병합
    개별 리스트
  • 버디 시스템

먼저 분할을 알아보자


분할

메모리 할당은 기존의 빈 공간을 여러개로 쪼갤 수 있다.
빈 공간보다 필요한 메모리 크기가 작은 경우 가능
ex) 30바이트의 빈 공간이 10바이트 요청으로 인해 3개로 쪼개어지는 경우

이 상황에서 1바이트의 요청이 들어온다면

다음 그림과 같이 빈공간 리스트의 주소크기가 하나 증가하며 크기가 하나 감소하게 되고 결국 빈공간이 줄어들 것이다.
이것이 바로 메모리 분할이다.


병합

가장 큰 빈 공간보다 큰 크기의 요청이 있다면 들어줄 수 없다.
이런 경우 빈 공간이 이웃해 있는 경우에 하나로 합쳐 크기를 늘려준다.
이것이 바로 병합이다.

위 그림과 같이 여러 개의 빈공간이 존재하고 서로 이웃한 경우에는 병합하여 사이즈를 늘린다.
이 병합은 비용이 안들기 때문에 무조건적으로 좋다고 볼 수 있다.


📌할당 영역 크기 관리


free는 c언어 사용자라면 모두 알다시피 크기를 매개변수로 받지 않는다.

ptr = malloc(20)

여기서 20바이트만큼 할당을 받고 모두 사용시 해제를 해줘야 한다.
그런데 free에 매개 변수로 크기를 넣어주지 않았는데 어떻게 알아서 빈공간 리스트에 넣을 크기를 알 수 있을까?
실제로는 메모리를 할당 받을 때 20바이트 만큼이 아닌 더 큰 크기를 할당 받아서 가장 위에 존재하는 헤더 블록에 정보를 추가한다.

이와 같이 말이다. 헤더 부분에 사이즈 정보를 저장하는 공간과 매직 넘버라는 공간이 저장되어 있는것을 볼 수 있다.
매직 넘버란 간단히 할당받은 메모리임을 확인시켜주는 일종의 사인이라고 볼 수 있다.
만약 매직 넘버가 1234567이 아니라면 버그가 생김을 알려줄 수 있다.

이처럼 메모리 조각 헤더는 다음과 같은 구조체로 이뤄져 있다.

위 그림을 보면 free를 할 때 헤더의 사이즈 만큼 뺀 여유 공간의 정보를 알 수 있다.


📌빈 공간 리스트


빈 공간 리스트에서 빈 공간의 위치는 어떻게 알 수 있을까
이것은 운영체제가 별도로 해준다.
운영체제가 빈 공간을 관리해주고 빈공간끼리 링크드리스트로 연결이 된다.
전부 해제되었을 경우 외부 단편화가 발생하고 병합이 필요하다.
병합을 해야 큰 메모리 할당이 가능하다.


📌힙의 확장

대부분 메모리 관리자는 처음의 작은 크기의 힙을 할당해 시작하고 부족하면 OS에게 더 요청한다.

다음과 같이 sbrk -> set break를 통해 break를 재설정 하여 확장한다.


📌빈 공간 관리 : 기본 전략

많은 여유 공간이 있을 때 어디에 어떻게 할당을 해야할까

  • 최적 접합(Best Fit) : 요청보다 작거나 같은 조각을 찾는다. 조각중 가장 작은 것을 반환한다.
  • 최악 접합(Worst Fit) : 요청보다 크거나 같은 조각을 찾는다. 조각중 가장 큰 것을 반환한다.
  • 최초 접합(First Fit) : 처음 만나는 요청에 할당한다.
  • 다음 접합(Next Fit) : 처음 만나는 요청이 아닌 다음에 만나는 요청에 할당한다.

📌다른 접근법 : 개별 리스트

  • 개별 리스트 : 많이 사용되는 크기별로 별도의 리스트를 만들어 관리
    해당 크기의 조각의 재활용은 외부 단편화가 생기지 않는다.
    해당 크기의 조각의 할당은 검색이 필요가 없다.
  • 문제 : 각 크기별로 얼마나 할당해야 할까? 크기를 미리 알 수 없다.
    -> 슬랩 할당기를 사용해야 한다.
  • 슬랩 할당기 : 자주 요청되는 메모리 크기를 객체 캐시로 할당한다. 해당 크기만큼 요청이 발생하면 바로 메모리 할당이 가능하다.
    빈 객체들을 사전에 초기화 된 상태로 유지한다.
    캐시 관리 : 리스트가 비어가면 미리 한번에 여러개를 할당 받아 저장한다.

📌다른 접근법 : 버디 할당

  • 이진 버디 할당 : 메모리 요청이 있을 때, 맞는 사이즈의 조각이 나올 때까지 반으로 자르며 빈 공간을 관리한다. -> 더이상 자를 필요가 없을 때까지

만약 총 64KB의 메모리에 8KB의 요청이 들어왔다고 하였을 때 64KB를 32KB 16KB 8KB로 쪼개어 현재 Best Fit으로 요청을 할당한다.
그런 뒤 이 공간을 해제할 때는 다시 쪼개진 메모리, 즉 버디를 찾고 사용하고 있지 않다면 합병한다.

  • 버디할당 장점 : 병합이 매우 쉽다
    이웃 블록만 가능
  • 버디할당 단점 : 7KB 할당시 1KB 낭비로 내부 단편화와 40KB할당 불가한 외부 단편화가 있음

다음으로 가변 크기 할당이 아닌 고정 크기 할당 방법인 페이지에 대해 알아보자

profile
코딩 초보 승호입니다.

0개의 댓글

관련 채용 정보