16.vm-segmentation
# 시작하며
동적재할당 기법을 활용하여 주소공간 전체를 메모리에 탑재하는 것을 공부했었다. 이 기법은 base - bound를 사용하여 사용하는 주소공간 전체를 물리메모리에 탑재시킨다. 그러나 몇가지 문제점이 발생했다. 먼저 스택과 힙 사이에는 사용하지 않은 꽤 큰 영역이 존재하는데, 이러한 공간에 유연하게 대처하지 못했다. 또 주소공간이 물리 메모리보다 큰 경우는 실행이 어렵다. 당연한 결과이다.
대용량 주소 공간을 어떻게 지원해야하는가에 대한 의문점이 제기된다.
# 세그멘테이션 적용하기 : 베이스(base) / 바운드(bound)의 일반화
위 문제를 해결하기 위해 세그멘테이션(segmentation)이라는 새로운 기법이 등장한다. MMU안에는 하나의 베이스-바운드 쌍이 존재하는 것이 아니라, 논리공간으로 나눈 세그멘테이션마다의 논리공간을 가지는 것이다. 여기서 세그멘테이션은 특정길이를 가지는 연속 주소 공간이며 같은 논리적 요소에 따라서 나뉜다. 즉 코드, 스택, 힙, 데이터 영역과 같다. 교재의 예제에서는 데이터영역을 배제하고, 3가지의 세그멘테이션 영역을 사용한다. 뒤로가면 알겠지만 결국 2진수를 사용하기 때문에 일단 나눠지는건 4개로 나눠질 것이다.
MMU안에 세그멘테이션 공간마다의 base-bound쌍을 가진다는 것은 한 프로그램의 주소공간을 표시하기위해 하나의 base-bound쌍으로 표시해 왔다면, 한 프로그램의 주소공간을 표시하기 위해 총 세개의(코드, 스택, 힙) base-bound쌍 레지스터가 필요하다는 말이다. 이 개념을 처음 접했을때는 꽤 헷갈렸었다.
이제 세그멘테이션이 어떻게 이루어지는 살펴보자.
먼저 주소공간은 똑같이 코드, 힙, 스택으로 분리된다. 주소 공간을 하나의 base-bound쌍으로 구분하는 것이 아닌, 논리 단위로 나눈 코드, 힙, 스택에 각각 분리하는데 이것이 세그멘테이션이다. 그리고 각 각의 세그멘테이션은 각각의 base-bound쌍을 가지게 된다. 16KB의 주소공간이 세그멘테이션을 사용한다면 물리주소에 어떻게 할당되는지 살펴보자.
위 그림을 보면 연속된 주소 공간이 아닌 물리주소의 미사용 공간에 각각 할당됐다. 스택은 거꾸로 증가하는 성질이 있다고 말했었기 때문에 이점에 유의하자. 이제 해당 프로세서의 세그멘트 베이스(base)-바운드(bound == 크기) 레지스터는 어떻게 구성되는지 봐보자. 물론 예측이 가능할거다.
각 세그멘트의 베이스는 세그멘트가 물리적 공간에서 가지는 첫 시작 주소이다. 여기서 크기는 bound값이며 범위가 된다. 이 범위가 벗어나게 되면 세그멘트 폴트(segment fault)라는 에러를 발생시킨다라고 일단 이렇게 생각하고 있자!
예를들어 가상주소의 4200byte지점의 코드를 내가 읽고싶다. 그 실제 데이터는 물리주소에 있기 때문에 물리주소로 접근을 해야한다. 4200byte는 가상주소공간에서 힙 세그멘트에 속한다. 4200byte는 가상주소에서 힙영역의 시작지점인 4KB(1024*4)에서 104byte만큼 떨어져 있다. 이 offset값 104는 이제 절대적이기 때문에, 물리메모리에서도 그 세그멘트의 시작부분부터 104만큼 떨어져 있게 된다. 그럼 힙 세그멘트의 물리적 주소에서의 시작 주소인 34KB에 offset(104byte)를 더해주자. 물리주소 34920byte는 가상주소 4200byte에서 접근했을때 나오는 주소인 셈이다.
# 세그멘트를 표현해보자
가상주소를 세그멘트로 나눌 수 있다는 것은 그 주소(bit)를 세그멘테이션으로 표현 할 수 있어야 한다. 만일 가상주소가 총 14bit(2^14)로 표현된다고 가정해보자. 2^14은 16384byte를 표현할수 있으며 이는 즉 16KB가 된다. 가상주소는 총 16KB를 표현 할 수 있는 것이다. 그럼 이 공간을 세그멘테이션으로 나눠보자. 최상위 2비트를 세그멘테이션으로 사용한다면 2bit(2^2)로는 4를 표현 할 수 있다. 즉 16KB의 주소공간이 4개의 세그멘테이션으로 나뉘게 된다는 것이고, 각 세그멘테이션은 남은 12bit로 offset을 표현할 수 있다. 단순히 생각해보면 16KB을 4공간으로 나누니까 각 세그멘트는 4KB의 크기를 가지게 되는것이고, 이걸 bit로 계산해보면, 남은 12bit(2^12, 4kB)이 된다. 결국 이 12bit(offset)은 bound를 계산할때 사용할 수 있다. 바운드가 offset보다 작다면 잘못된 주소접근이 일어난 것이다.
코드로 표현하면 아래와 같다.
주석이 달린대로 세그멘트와 오프셋을 비트마스킹을 하여 가져온 호, 바운드 값과 비교한다. 여기서는 PROTECTION_FAULT핸들러를 발생시킨다. 어찌 됐든 bound값을 벗어난 오류에 대해서 처리를 하고 있다는 것이다. 뒤로가서는 이 세그멘이션 폴트와 프로텍션 폴트를 구분시켜야한다. 세그멘트 폴트(segment fault)사용하고 있지 않는 세그멘테이션에 접근할때 발생하는 오류의 개념의로 확장된다. 즉 여기선 3개의 영역( 코드 , 스택 , 힙)의 영역만을 세그멘티이션으로 나눴는데, 여기서 나눠지지 않은 곳 세그멘테이션에 접근할때 발생한다. PROTECTION_FAULT는 프로세스끼리의 간섭을 막기 위해서 발생하는 에러라고 생각하면 되는데, 여기서도 서로다른 세그멘테이션 영역에 침범하기 때문에 PROTECTION_FAULT의 개념으로 확장시킨 것이다.
# 세그멘테이션에서 스택 표현하기
스택은 조금 특별한 성질을 가지고 있었다. 바로 거꾸로 증가한다는 것인데, 반대로 확장된다는 것을 의미한다. 즉 베이스가 10이고 바운드가 2이면 스택의 영역의 끝(?) 부분은 12가아닌 8이 된다. 이를 위해 하나의 비트를 희생시켜 주소가 양의 방향으로 확장하면1, 음의방향으로 확장하면 0으로 설정하게 한다. 하드웨어가 가지고 있어야하는 정보이다.
# 공유 지원
메모리를 절약하기 위해 때로는 주소 공간들 간에 특정 메모리 세그멘트를 공유하는 것이 가능하다. 이런 경우에는 하드웨어에 protection bit의 추가가 필요하다. 세그멘트를 읽거나 쓸 수 있는지 혹은 코드를 실행시킬 수 있는지와 같은 정보를 나타낸다. 읽기 전용으로 세그멘테이를 구성하면, 수정을 불가능하고 읽게만 가능하게하여 공유를 가능케 한다.
protection bit를 추가했기 때문위 위에서 세그멘테이션을 사용하는 코드에 해당부분을 확인하는 동작을 추가해야한다.
# 작은단위 세그멘테이션, 큰단위 세그멘테이션
세그멘테이션을 어떻게 분류하냐에 따라서 두가지로 나뉜다. 지금은 이 세그멘테이션을 큰 목적(코드,스택 힙)을 지원하기위해 나눴지만, 주소공간을 조금 더 작은 세그멘테이션 단위로 나눌 수 도 있다.
# 운영체제의 지원
세그멘테이션은 하나의 쌍만을 가지고 base-bound을 구성하는 것보다 메모리를 매우 많이 절약할 수 있게 된다. 그런데 이 것들은 운영체제의 몇가지 도움이필요하다.
첫번째는 문맥교환(context swtich)시 운영체제는 어떤 일을 해야하는지에 대해서 생긴다. 운영체제는 당연히 세그멘트 레지스터들(base-bound-protection bit)을 저장과 복원을 해야한다.
두번째는 미사용중인 물리메모리에 대한 관리이다. 세그멘트를 물리메모리에 올리기위해서는 공간을 찾아서 줘야한다. 근데 여기서 문제가 발생한다. 물리메모리들은 작은 크기의 빈공간들로 채워진다는 것이며, 거기서 외부 단편화가 발생한다(external fragmentation)
메모리가 부분부분 크기가 서로 다른 공간들로 할당 돼 빈곳이 생기게 된다. 빈곳의 총 용량은 매우 많지만 세그멘테이션이 들어갈 만큼 충분한 공간이 아니기 때문에 결국 세그멘테션이 할당화 되지 못하는 것이다. 위 그림 처럼 압축을 하면 상당히 많은 미사용 공간이 존재하는데도 말이다. 메모리 낭비가 발생하는 것이다.
그렇다면 미사용공간들을 잘 정리해야 하는데, 그 미사용 공간(free space)를 어떻게 관리해야할까? 자세한 정책은 다음장에서 다루도록 하겠지만 알고있지만 키워드가 안나는 분들을 위해 정리하자면, 최적 적합(best-fit), 최악 적합(worst-fit), 최초 적합(first-fit) 및 버디 알고리즘(buddy algorithm)을 사용 할 수 있다.
# 세그멘테이션의 문제점
세그멘테이션은 한쌍의 base-bound형식으로 메모리를 구성했던 방식보다는 좋았졌지만 문제점이 발생한다. 첫번째는 위에서 언급했던 외부단편화이다. 다시 설명하지 않겠다.
두번째는 적게 사용되거나 드문드문 공간을 띄어그 세그멘테이션은 결국 전체가 메모리에 올라와야 한다는 것이다. 제법 큰 힙이 존재하는데 그 힙의 전체공간을 사용하지 않고 큰 index씩 건너 뛰거나, 맨처음 몇개만 사용한다 하더라도 그 전체의 힙 세그멘테이션이 메모리에 할당 되어야 하는 것이다. 이제 이건 그 다다음 장에서 정리할 page을 통해서 극복 가능하다.
# 끝내며
OSTEP의 장점을 VM단원을 공부하면서 여실히 느꼈다. 뭔가 하나하나 단계를 업그레이드해가면서 문제점 발생 -> 해결 -> 새로운 문제 발생 -> 해결의 방식으로 서술하는게 너무 이해가 쉽고 좋다.
중간에 세그멘테이션 폴트와 프로텍션폴트가 헷갈리게 나오는데, 책에도 그대로 나와있다. 하드웨어단계는 내가 핸들러를 구성하기 나름인데, 결국 뒤에서 프로텍션 에러는 이러한 읽기-쓰기 방식을 위반할때 호출하게 되고, 잘못된 주소접근에 의해서는 세그멘테이션폴트를 반환시키게 한다. 자바로 코딩하다보면 배열같은거 잘못쓰면 세그멘테이션폴트가 많이나오는데, 운영체제를 공부하면서 왜 그런게 발생하는지 드디어 알게 됐다.
'•Compter Science > Operating System' 카테고리의 다른 글
[OS/OSTEP] 18.vm-paging : 메모리 페이징,PFN과 VPN #12 (1) | 2022.04.18 |
---|---|
[OS/OSTEP] 17.vm-freespace : 메모리 빈 공간 관리하기 #11 (0) | 2022.04.18 |
[OS/OSTEP] 15.vm-mechanism : 주소 변환의 원리, 동적 재배치(dynamic relocation) , 내부단편화 #9 (0) | 2022.04.18 |
[OS/OSTEP] 13.vm-intro : 주소공간(address space)와 메모리 가상화(virtual memory) 의 개념 #8 (0) | 2022.04.18 |
[OS/OSTEP] 09.CPU-sched-lottery-CFS : 비례, 배분을 이용한 스케줄링 #7 (0) | 2022.04.18 |