지난 스터디 시간에 세그멘테이션을 주제로 두 멤버님께서 열렬히? 토론 하시는것을 보았습니다.
쉬는 시간이었던지라 경원대 강의실의 첨단 시스템들을 요리조리 구경하면서 대강 흘려 넘겼습니다.ㅋ
평소 저도 제대로 이해를 하지 못하고 있던 부분이라 해답을 찾고자 "만들면서 배우는 OS 커널의 구조와 원리"를 완독하였습니다.
하지만 안타깝게도 세그멘테이션을 시원하게 이해하지 못하여 이렇게 자문을 구하는 바입니다.
기본적으로 세그멘테이션이라는 기법은 메모리 관리 기법으로 내부 단편화를 줄이기 위한 방법으로 이해하고 있습니다.
8086, DOS 기반 프로그래밍에서는 세그멘테이션 기법을 유용하게 사용했을거라 생각합니다.
640KB 메모리를 효율적으로 사용하기 위해서 프로그램이 사용하는 메모리를 CS와 DS 영역으로 적절하게 설정하여
프로그램의 코드와 데이터를 각각의 세그먼트 영역에 올려서 사용하지 않았을까 합니다.
여기서 질문1. 왜 8086은 ARM이나 MIPS 처럼 바로 페이징을 적용하지 않았나 하는 것입니다.
당시의 기술적 한계(MMU를 개발 못함)였다면 어쩔수 없었으리라 생각됩니다.
이후의 프로세서에는 리얼모드의 단점을 극복한 프로텍티드 모드가 지원됩니다.
리얼모드의 단점 1 : 제한된 메모리 사용 (최대 1MB)과 메모리 참조의 불편함.
참고 : http://blog.naver.com/hyuga777?Redirect=Log&logNo=80125702627
리얼모드의 단점 2 : 메모리 보호가 어려움.
프로텍티드 모드에서는 64비트 디스크립터에서 Limit로 세그먼트 옵셋의 범위를 제한함으로써 메모리 보호가 가능해졌습니다.
또한 디스크립터의 DPL과 세그먼트 셀렉터의 RPL을 통해 유저모드와 커널모드간의 메모리 보호가 가능해졌습니다.
여기서 질문2. 386, 486 등등 이후의 프로세서에서 8086과의 호환을 위해 리얼모드를 지원합니다. 하지만 현재 8086에서
돌아가던 프로그램을 사용하지도 않는데 아직까지 리얼모드를 지원하는 이유는 뭘까요?
프로텍티드 모드에서는 세그멘테이션과 함께 페이징을 지원합니다.
대부분의 커널 책에서 x86 의 주소변환 과정에 대해 아래와 같이 친절히 설명을 하고 있습니다.
세그멘테이션(논리주소) => 페이징(선형주소) => 물리주소
하지만 과연 가변 분할 메모리 관리라는 진정한 의미에서의 세그멘테이션 기법이 동작하고 있느냐? 라는 관점에서
생각을 했을때 그렇지 않다는 생각을 할 수 밖에 없었습니다.
유저모드 CS, DS 커널모드 CS, DS 모두 베이스 주소는 0이고 리미트는 4GB 입니다.
커널은 그렇다고 하더라도 어떠한 프로그램이 실행 되어도 0 ~ 4GB의 논리 주소를 가지게 됩니다.
세그멘테이션 기법을 따른다면 각각의 프로그램은 자신만의 64비트 디스크립터를 가지게 되고 서로 다른 베이스 주소와
리미트 크기를 가져야 하는것이 아닌가 생각합니다.
이러한 관점에서 봤을 때 x86은 모든 세그먼트를 0 ~ 4GB의 동일한 논리 주소 하나의 영역으로 보는것으로 판단 됩니다.
현제까지 제가 파악한 x86 세그멘테이션에서 유일하게 의미가 있는 것은 GDT를 통해 TSS에 접근 하는것이 아닌가 생각합니다.
여기서 질문3. 만일 그러하다면 실질적으로 무의미한 세그멘테이션 과정을 없애고 페이징 기법만을 사용하는 것이 효율적이지
않을까요?
여기서 질문4. 컴파일 타임에 생성되는 프로그램의 주소는 가상주소(선형주소)로 알고있습니다.
세그먼트는 0 ~ 4GB로 하나이고 실질적으로 의미있는 세그멘테이션 동작을 하지 않으므로 프로그램에 할당 되는
가상 주소와 매핑 되는 논리주소는 가상주소와 동일하거나 또는 없다고 봐야하지 않을까요?
현재 저의 이해 수준에서 글을 쓰다보니 제대로 쓴건지 모르겠습니다.
생각이 정리되고 해답을 찾게되면 저 또한 자답을 해보겠습니다.
이미 이해하고 계신 멤버님이 계시다면 답변을 부탁 드립니다.
혹은 저와는 다른 관점에서 의문을 가지고 계신 멤버님이 계시다면 같이 공유 했으면 하는 바람입니다.
댓글 12
-
홍문화
2011.07.22 23:15
-
홍문화
2011.07.26 21:03
저희 ARM 교재에 보면 FCFE(Fast Context Switch Extension)라는 기법이 나옵니다.
간단히 설명하면 수정된 가상 주소를 사용하여 다수의 유저 태스크가
4GB 동일 가상 주소 공간에 겹치지 않고 존재하게 합니다.
이를 통해 cash miss, TLB miss를 줄여 더 빠른 문맥 전환을 하게 한다고 합니다.
과연 쓰일 일이 있을지 모르겠지만 쓰고 싶으면 쓰라고 만들었겠죠.
마찬가지로 x86에서도 세그멘테이션만 사용해서 바로 물리 주소로 변환하고 싶으면
CR0의 페이징 비트를 끄고 원하는대로 사용하라는 의미일 수도 있겠네요.
이를 구지 유연하다고 표현한다면 틀린말은 아니겠죠.
워낙 역사가 오래된 녀석이라 제대로 이해하려면 역사부터 공부 해야 될거 같네요. ^^;
-
방철호
2011.07.23 01:40
저는 아키텍쳐의 하위 호환성을 유지하기 위해서 이러한 좀 기형적인 구조가 나왔다는 생각이 듭니다.
즉, X86의 역사때문에 그런것 같네요. 윈도우나 리눅스는 실질적으로는 세그멘테이션을 사용하지 않는다고 봐야겠지만..
제가 느낀건 좀더 메모리 관리를 유연하게 여러가지로 할수 있는 구조 라고 생각이 듭니다. 운영체제 구조도 다르게 설계할수 있는 여지가 많을수 있다랄까
-
pororo
2011.07.22 23:48
안녕하세요. x86의 천승환입니다.
궁금한게 있어서 질문 드립니다.
8086의 리얼모드 세그먼트 방식은 20비트 메모리 어드레스 억세스를 위한걸로 알고 있었는데요. 외부 단편화를 위한 측면이 이것보다 큰 의미를 가지고 있나요? -
pororo
2011.07.23 00:25
덕분에 궁금했던 의문들이 몇가지 풀렸습니다.
시간 내주셔서 감사합니다. -
홍문화
2011.07.23 00:22
1. arm은 세그먼트는 안쓰고 페이징 기법만을 쓰는 건가요?
=> 그렇게 알고 있습니다. 코드 분석에 들어가야 확실해질거 같습니다. ^^;
2. 책을 잠깐 봤는데 arm은 태생이 32비트인데 arm 모드와 thumb 모드가 있다고 들었습니다.
thumb모드에서도 메모리주소는 32비트로 억세스하게 되는건가요?
=> Thumb 모드를 사용하는 경우를 하나 들자면 메모리 버스가 16비트일 경우 32비트 ARM 모드에서는
명령어 하나를 읽어오기 위해서 메모리에 두번 접근을 해야 하지만 Thumb 모드에서는 한번만 접근하면 됩니다.
-
pororo
2011.07.23 00:06
os과목을 수강한적이 없어서 질문을 드렸습니다.
자세한 답변 감사드립니다.
질문한 김에 몇가지 더 여쭤보겠습니다.
1. arm은 세그먼트는 안쓰고 페이징 기법만을 쓰는 건가요?
2. 책을 잠깐 봤는데 arm은 태생이 32비트인데 arm 모드와 thumb 모드가 있다고 들었습니다. thumb모드에서도 메모리주소는 32비트로 억세스하게 되는건가요? -
홍문화
2011.07.22 23:59
세그멘테이션 기법은 메모리 액세스와는 전혀 관련 없는 내용입니다.
메모리를 효율적으로 사용하기 위한 방법중 하나입니다.
OS 과목을 수강 하셨다면 아시겠지만 메모리 관리 기법에는 크게 세그멘테이션과 페이징 기법이 있습니다.
세그멘테이션 기법은 가변 분할 메모리 관리 방식이고 (내부 단편화를 해소하기 위함.)
페이징 기법은 고정 분할 메모리 관리 방식(외부 단편화를 해소하기 위함.) 입니다.
글 내용에서 외부 단편화를 내부 단편화로 수정 하였습니다.
매번 헷갈리네요. ㅋ
-
정한국
2011.07.26 17:54
x86 초급(요즘 좀처럼 출석을 하지 못하고 있습니다 ㅠㅠ)의 정한국 이라고 합니다.
부족한 부분은 뒤에 다른 훌륭한 분들이 지적해주시시라 생각하고 지극~히 개인적인 생각을 한번 올려봅니다.
여기서 질문1. 왜 8086은 ARM이나 MIPS 처럼 바로 페이징을 적용하지 않았나 하는 것입니다.
만약에 메모리가 1메가 이고 실제 사용하는 프로그램이 (최대)640k 만 필요하고 세그멘테이션이 적용되었다면 페이징이 필요할까 하는 생각을 한번 해봅니다.
페이징도 티나지 않을지 모르겠지만 결국에는 일을 한번 더 하는 것이죠?
여기서 질문2. 386, 486 등등 이후의 프로세서에서 8086과의 호환을 위해 리얼모드를 지원합니다.
하지만 현재 8086에서 돌아가던 프로그램을 사용하지도 않는데 아직까지 리얼모드를 지원하는 이유는 뭘까요?
하위호환성 이라는 말을 빼면 딱히 할말이 없습니다.
여기서 질문3. 만일 그러하다면 실질적으로 무의미한 세그멘테이션 과정을 없애고 페이징 기법만을
사용하는 것이 효율적이지 않을까요?
만약에 메모리가 8G 라면 오직 페이징 기법만으로 8G 를 제어할 수 있을까요?
개인적인 생각이지만 페이징이 내부단편화와 관련이 있다면, 세그멘테이션은 외부단편화(약간 의미가 다르긴 합니다만...)와 관련이 있다고 생각합니다.
여기서 질문4. 컴파일 타임에 생성되는 프로그램의 주소는 가상주소(선형주소)로 알고있습니다.
세그먼트는 0 ~ 4GB로 하나이고 실질적으로 의미있는 세그멘테이션 동작을 하지 않으므로 프로그램
에 할당되는 가상 주소와 매핑 되는 논리주소는 가상주소와 동일하거나 또는 없다고 봐야하지 않을까요?
너무 책에 집중하신듯 합니다.
VC 로 프로그램을 컴파일 합니다. 실행 파일을 복사를 두 번 하면 프로그램이 3개가 생기겠죠?
프로그램 3개를 실행합니다. 세그먼트가 다 같을까요? 아마 다르지 않을까 싶습니다.
-
홍문화
2011.07.26 18:43
정한국님 안녕하세요. 먼저 같이 고민 해주셔서 감사합니다. ^^;
만약에 메모리가 1메가 이고 실제 사용하는 프로그램이 (최대)640k 만 필요하고 세그멘테이션이
적용되었다면 페이징이 필요할까 하는 생각을 한번 해봅니다.
페이징도 티나지 않을지 모르겠지만 결국에는 일을 한번 더 하는 것이죠?
=> 제 질문은 세그멘테이션이 적용 된 상태에서 왜 페이징을 적용 하지 않았을까가 아니라
ARM, MIPS와 같이 페이징만을 적용 하는게 더 낫지 않았을까 하는 것이었습니다.
추측 하기로, 당시에는 메모리 용량이 매우 작았을 뿐만 아니라 가격도 상당히 비쌌을 것입니다.
기술적 문제는 둘째 치고라도 1MB 메모리 하나를 사용한다고 했을 때 OS를 제외한 나머지
물리 메모리 공간을 페이징 하기 위해 MMU와 같은 하드웨어를 장착 한다는 것은 배보다 배꼽이
더 큰 경우가 되는것이었을 것입니다. 또한 DOS는 싱글 태스크 시스템이었기 때문에 리얼모드
세그멘테이션 만으로도 충분했을거라 생각합니다.
하위호환성 이라는 말을 빼면 딱히 할말이 없습니다.
=> 하위 호환성의 의미가 정확히 어떻게 되나요? 정말로 몰라서 묻는 것입니다.
만일 하위 호환성이라는 의미가 어떤 제품을 만드는데 반드시 A는 있어야 하고 B,C는
선택이었다고 가정합니다. 시간이 흘러서 다음 버전 제품을 만들려고 하는데 반드시 A는 있어야
하므로 유지하고 E,F를 추가 하는 것이라고 이해 할 수 있을까요? 물론 A는 8086 아키텍쳐입니다.
만약에 메모리가 8G 라면 오직 페이징 기법만으로 8G 를 제어할 수 있을까요?
=> 32비트 시스템에서 최대 어드레싱 가능한 주소는 4GB 입니다.
개인적인 생각이지만 페이징이 내부단편화와 관련이 있다면, 세그멘테이션은 외부단편화
(약간 의미가 다르긴 합니다만...)와 관련이 있다고 생각합니다.
=> 네, 페이징은 내부 단편화 문제를 유발하고, 세그멘테이션은 외부 단편화 문제를 유발합니다.
VC 로 프로그램을 컴파일 합니다. 실행 파일을 복사를 두 번 하면 프로그램이 3개가 생기겠죠?
프로그램 3개를 실행합니다. 세그먼트가 다 같을까요? 아마 다르지 않을까 싶습니다.
=> 물리적 증거 내지는 논리적 근거가 없어서 어떻게 받아들여야 할지 난감하네요.
제가 이해한 바로는 3개의 프로그램은 논리주소도 같고 가상주소도 같습니다. 물리주소만 다릅니다.
논리적 근거는 이미 글에서 설명을 했습니다.
-
홍문화
2011.07.26 19:00
이것이 하위 호환성이군요.
http://hellonewworld.tistory.com/148
인텔과 MS의 정치적, 상업적 선택인것 같은데 자세한 내막을 알수 없으니...
아무래도 구글과 애플같은 회사는 끼어들 엄두도 내지 말라 이런 것일까요? ㅋ
리눅스는 끼어들었는데... 뭐지? ㅋㅋㅋ
-
장성민
2011.07.28 10:58
스터디 시간에 열심히 토론했던 사람입니다.
저도 정리가 안되어서 몇 일동안 고민하다가 적습니다.
제가 조사한 바로는 세그먼테이션에는 3가지 모델이 있습니다.
----------------------------------------------------------------------------------------------------------
1. 기본(basic) flat 모델 :
이 모델은 가장 간단한 방법으로 운영체제나 응용 프로그램 모두가 세그먼트화 되지 않고, 연속적인 주소 공간을 접근하는 방법이다. 이 모델을 구현하기 위해선 Intel에서는 최소 두개의 디스크립터가 있어야 하며, 하나는 코드를 하나는 데이터 세그먼트를 위한 것이다. 모든 세그먼트 레지스터는 기본 주소로 0을 가지며, 4 GBytes의 세그먼트 한계를 가진다.
2. 보호(protected) flat 모델 :이 모델은 기본 flat 모델과 유사하나 실제적인 메모리 공간(physical address space)을 포함하기 위해서 세그먼트의 한계가 설정된다는 점이 다르다. 이 경우에는 앞에서와는 다르게 실제로 존재하지 않는 메모리에 대한 접근은 general-protection exception을 발생 시킨다. 따라서, 최소한의 하드웨어 보호 기능을 제공한다. 이 경우에는 페이징이 사용자와 커널 코드를 분리하기 위해서 4개의 세그먼트를 필요로하게 되며, 코드와 데이터 세그먼트의 각각에 하나씩 해당한다. 이 모델은 효과적으로 응용 프로그램과 운영체제를 보호하며, 또한 각각의 프로세스에 분리된 페이징 구조를 제공해서 프로세스간에도 보호기능을 제공하기 때문에, 많은 운영체제가 이 모델을 사용하고 있다.
3. 멀티 세그먼트 모델 :이 모델은 모든 세그먼테이션의 기능을 다 쓰는 것으로 하드웨어에서 제공하는 모든 보호기능을 코드와 데이터, 그리고, 프로그램들과 프로세스들에 대해서 적용하는 것이다. 모든 세그먼트와, 시스템에서 실행중인 개개의 프로그램의 실행 환경에 대한 접근은 하드웨어에 의해서 제어된다.
-------------------------------------------------------------------------------------------------------------------------------이 중에서 리눅스에서 사용하는 모델은 2번 보호 Flat 모델입니다. 이 모델의 경우 4개 세그먼테이션이 서로 보호되는 것 이외에는 특별한 기능을 지원하지 않는 것으로 보입니다. 어쩌면 세그멘테이션 폴트와 같은 잘못된 접근을 막아주는 역할을 하는 것이 전부인 것 같네요.
아래의 코드를 보면 gdt_table에 4개의 세그먼테이션이 있습니다.
================================================
...
ENTRY(gdt_table)
.quad 0x0000000000000000 /* NULL descriptor */
.quad 0x0000000000000000 /* not used */
.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff /* 0x18 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff /* 0x23 user 4GB code at 0x00000000 */
.quad 0x00cff2000000ffff /* 0x2b user 4GB data at 0x00000000 */
.quad 0x0000000000000000 /* not used */
.quad 0x0000000000000000 /* not used */==========================================================
이 4개의 세그먼테이션은 모두 4G의 영역과 BASE 번지가 0x00000000 번지인 동일한 가상주소라고 보는 것이 맞는것 같습니다. 세그먼테이션은 8바이트씩 공간을 차지하기 때문에 gdt_table의 0x10, 0x18, 0x20, 0x28 번지에 위치합니다.
각 각의 세그먼테이션을 선택하기 위한 세그먼트 셀렉터에는 주소 + selector의 privilege 레벨(kernel은 0, user는 3) 값이 정해져서 아래와 같이
0x10, 0x18, 0x23, 0x2B 값을 가지고 세그먼트를 선택하게 되는 것입니다.
아래의 자료와 첨부된 자룔를 보면서 제 나름대로 정리한 것인데, 혹시 내용에 문제가 있으면 댓글로 정정해주세요.. ^^
.
퇴근하고 집에 오는 길에 곰곰히 생각을 하다가 질문4에 대한 나름의 해법을 내려봤습니다.
모든 유저 태스크는 동일한 CS, DS 세그먼트(논리주소 0 ~ 4GB)를 사용합니다.
다시 말해 모든 유저 태스크는 GDT에서 두개의 엔트리를 차지하는 유저모드 CS 디스크립터,
유저모드 DS 디스크립터로 접근이 됩니다. 동일한 세그먼트 디스크립터를 사용한다는 것은
곧 동일한 세그먼트 셀렉터 값을 사용한다는 의미가 됩니다.
(세그먼트 셀렉터의 상위 13비트 값 * 8 = GDT의 인덱스)
16비트 리얼모드에서 논리주소는 16비트 세그먼트 베이스 주소 + 16비트 옵셋으로 구성이 됩니다.
이 책에서 재미있는 것은 32비트 프로텍티드 모드에서 논리주소가 어떻게 구성이 되는지
명시적으로 기술이 되어 있지 않다는 것입니다. 다만 논리주소에서 선형주소로의 변환
과정만이 설명 되어 있습니다. 16비트 세그먼트 셀렉터 값과 32비트 옵셋 값이 논리주소 영역에서
튀어나옵니다. 세그먼트 셀렉터를 통해 세그먼트 디스립터에 접근을 해서 32비트 베이스 주소를 알아냅니다.
여기에 32비트 옵셋 값을 더하면 선형주소가 됩니다. 그런데 이게 왠걸 모든 유저 태스크는 모두 같은
유저모드 세그먼트 디스크립터를 사용하므로 변환 과정에서 사용되는 베이스 주소는 모두 0입니다.
이미 눈치 채신분은 아시겠지만 이는 곧 선형주소 = 논리주소 영역에서 튀어나온 옵셋값 이라는 얘기가 됩니다.
더구나 이 변환 과정에서는 MMU와 같은 하드웨어 장치가 개입 되지도 않습니다.
=> 질문 3을 하게된 배경 중의 하나입니다.
이제 이런 질문을 던질 수 밖에 없습니다. 과연 물리 메모리에 접근을 할때마다 이런 의미없는 뻘짓을
정말로 하는가?