본문 바로가기

컴퓨터

[펌]Segment : Offset (세그먼트 옵셋)

세그먼트 주소 지정 방식 이라고 검색해봐라

http://www.pcguide.com/ref/ram/logicAddressing-c.html

http://thestarman.pcministry.com/asm/debug/Segments.html

엠파스에 잘 나오네

IBM 에서 컴퓨터를 처음 만들때 자체적인 RAM을 디자인하면서 1MByte 정도로 고정시켰습니다. ( 프로그램의 크기가 별로 크지않았기때문에) 1MByte는 IBM에서 디자인한 RAM의 최대 저장능력이 되었던거죠.

1MByte= 1024Byte * 1024Byte= 1,048,576Byte
약 백만개의 주소를 지정할수있는 공간이 있어야겠죠.
2의20승 정도가 약 100만이니까 20Bit 가사용되어야겠죠.

20 BIt :1111 1111 1111 1111 1111
             F     F     F     F     F

20자리 이진수라면 컴퓨터에서는 20비트가 사용됩니다 . 이 20비트를 16진수로 표현하면 5자리만 사용하면 됩니다. 하지만 컴퓨터는 20자리 이진수로 메모리 주소를 표현하는데 20비트라는 숫자를 컴퓨터는 표현하지 못합니다 .

CPU 가 16비트 32비트 64비트 숫자를 다루기에 적합하도록 설계되었기 때문에 컴퓨터가 메모리 주소에 부여 할수 있는 숫자도 16비트 32 비트64 비트 단위로 저장되는 것이 좋겠죠. 그래서 최초 16비트 컴퓨터가 20비트를 인식할수 없었기 때문에 4비트를 더필요하게되었죠. 그래서 세그먼트 와 옵셋이란 주소체계가 나온것입니다.

FFFF : 0000 =FFFF0

(세그먼트 : 옵셋 )

(세그먼트 * 10Hexa) + 옵셋 =실제메모리 주소

FFFF0
+ 0000
---------
FFFF0 (실제주소) = FFFF : 0000 가 되는것이죠.

치명적인 오류에서도 세그먼트 : 옵셋주소로 뜨죠? 컴퓨터를 사용하다 볼수있는 4525:A8E9 같은 숫자들은 바로 세그먼트와 옵셋주소로 표현된 메모리 주소입니다.


8086 호환모드 CPU의 주소체계는,
아시다시피 segment:offset의 구조로 되어 있습니다.
하지만, 이는 CPU 내부적으로 사용하는, 일종의 논리 주소이고,
CPU가 메모리를 억세스할 때 사용하는 주소는 실제로는 20비트죠.
따라서 논리주소와 물리주소 사이에 변환이 필요합니다.
segment주소는 어떤 세그먼트가 물리메모리에서 차지하고 있는 영역의 시작점을 나타내는 주소입니다.
여기에 offset을 더하여 실제 물리주소를 계산하게 되죠.
따라서 segment:offset 구조로 되어 있는 논리주소는 segment주소를 4비트 쉬프트하고,
offset을 더해주어서 물리주소로 변환하여 억세스합니다.
따라서 두 세그먼트 사이의 간격이 64k바이트 이하라면 두 세그먼트가 차지하는 주소공간이 겹칠 수도 있습니다
(세그먼트 주소로는 4096차이).
하지만, 모든 세그먼트가 64kb로 동일한 크기를 갖는 것은 아니란 점을 분명히 하시면 크게 문제는 없을 겁니다.
즉, D라는 세그먼트의 (데이터 세그먼트라고 해 보죠) 세그먼트 주소가 0x1000 이고,
C라는 세그먼트의 (코드 세그먼트라고 해도 무방합니다) 세그먼트 주소가 0x1040 이라면,
이론상 데이터 세그먼트의 오프셋 1024, 즉 0x1000:0x0400는 코드세그먼트의 시작점 0x1040:0000과 일치하게 되죠
(물리주소는 0x10400).
하지만, 이는 데이터 세그먼트의 크기가 1024바이트임을 나타내는 것으로,
프로그램 로직상에서 데이터 세그먼트는 오프셋 1023 이상을 억세스하지 않는다는 것을 가리키는 것 뿐입니다.
(즉, 데이터 세그먼트를 통해서 접근하는 가장 큰 옵셋이 1023이므로,
세그먼트 할당시에 데이터 세그먼트의 경계가 64kb보다 더 작게 할당되었다는 것이죠)

8086이 16비트 CPU라고 하여도, 모든 라인이 16비트인 것이 아니라,
내부 데이터패스가 16비트의 폭을 갖고 있다는 것이며,
메모리 어드레스는 20비트의 폭을 갖고 있다는 것입니다.
다른 말로는, 범용레지스터가 아닌, MAR에 해당하는 레지스터는 실제로는 20비트 크기를 갖는 레지스터가 된다는 것이죠.

레지스터 레벨에서 살펴본다면, 프로그램에서 할당되는 전역변수는 데이터 영역에 생성되죠.
이때 데이터 세그먼트의 시작주소는 (8086에서라면) 프로그램 시작시에 DS레지스터에 저장되고,
이 변수를 억세스 할 때는 BX등의 레지스터에 저장된 옵셋을 사용하게 되는 것이죠.
즉, 어셈블리로 mov ax, [bx]와 같은 명령이 수행 될 때,
CPU는 DS레지스터의 세그먼트 주소와 BX레지스터의 오프셋 주소로 20비트 폭의 물리주소를 만들어서 MAR에 저장하고,
이 물리주소로 메모리에서 값을 가져오게 됩니다.
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
그럼 생각을 해보시면 세그먼트가 CS,DS,ES,SS이렇게 4개가 있게 되는데 옵셋이 없다면 모두 같은 주소가 되겠지요.
즉, 메모리의 실제(물리적) 주소가 0x00000 ~ 0xfffff 까지 있다고 생각하시면 CS의 주소가 0x00000 ~ 0x0ffff 까지 이면
DS도 0x00000 ~ 0x0ffff 이렇게 되면 서로의 세그먼트 주소가 실제(물리적)주소에서 중복이 되어버리죠....
그래서 각각을 따로따로 분리하기 위해 옵셋을 두는 겁니다.
그래서 상대적 주소라는 개념이 생긴것입니다.
8086프로세서는 20bit의 어드레스 라인이 있습니다.
이뜻은 실제(물리적)주소가 0x00000 ~ 0xfffff 까지 사용할수가 있다는 뜻입니다.
그런데 아쉽게도 8086의 프로세서내부의 IC(instruction counter)는 16bits짜리입니다,
그래서 4bits를 앞에 더 붙여줘서 20bits의 주소를 만들어 내는것입니다.




세그먼트와 오프셋이 개발된 이유

인텔에서 8086계열의 CPU를 개발할 때 메모리크기를 1MB로 정했다. 1MB를 접근하기 위해서는 어드레스버스가 20비트가 필요하나(1MB = 220 Byte)그 당시 기술로는 16비트(어드레스 버스)밖에 되지 않았다. 그렇다고 20비트 CPU를 개발하기에는 투자비용이 너무 많이 들고, 16비트 CPU와의 호환성에도 문제가 생긴다. 그래서 16비트 레지스터 2개를 이용하여 20비트 메모리에 접근하는 방식을 개발한 것이다.


물리주소(실효주소) 계산법

어드레스 버스를 제어하기 위해 20비트의 물리주소를 만드는 방법은 세그먼트 × 0x10 + 오프셋을 하면 된다.

예를 들어 세그먼트가 0x0600이고 오프셋이 0x0123 라고 한다면

        (0x0600 << 4) + 0x0123

----> 0x6000 + 0x0123              : 먼저 세그먼트를 좌측 4번 쉬프트 연산

----> 0x06123                       : 세그먼트와 오프셋을 더하면 물리주소가 나옴

어셈블리어로 코딩을 해보면

MOV   DS, 0600H                     ; 데이터세그먼트에 0x0600을 넣고

MOV   BX, 0123H                     ; BX(오프셋이 됨)에 0x0123를 넣고

MOV   AL, DS:[BX]                   ; AL에 0600:0123의 데이터를 넣는다.

                            ; 일반적으로 세그먼트:오프셋의 형태로 물리주소를 표현한다.


그리고 세그먼트:오프셋의 방식은 하나의 물리주소를 접근할 때 여러 가지로 표현될 수도 있다. 예를 들어 아래와 같이 0x06123의 주소에 접근할 때 세그먼트와 오프셋 값은 정해진 것이 아니라 변할 수 있다.


세그먼트           오프셋    물리주소

(0x0600 << 4) + 0x0123 = 0x06123

(0x0000 << 4) + 0x6123 = 0x06123

(0x0612 << 4) + 0x0003 = 0x06123

(0x05FF << 4) + 0x0133 = 0x06123


1) Intel 8086 (1978)
- 괄호안의 숫자는 제작년도를 뜻한다. 이름부터가 왠지 굉장히 오래된 프로세서임을 시사해준다. Intel의 8086은 8bit 대신 16비트 데이터 버스를 사용한다는 것만 제외하면 8088과 거의 똑같이 제작되었다. 사실 8088보다 8086이 먼저 개발되었지만(숫자만 봐도 알 수 가있다..) 이 Chip은 최초의 PC에는 탑재되지 않았고, 얼마의 시간이 흐른뒤에야 사용되기 시작했다. 그 이유는 당시의 PC 제조업체들이 생산품의 가격을 낮추기 위해서 16bit 대신 8비트 데이터 버스를 원했기 때문이다. 또한 8086은 기존의 8비트 프로세서(8080, 8085)와는 다르게 64KB가 아닌 1MB까지의 메모리에 접근하도록 설계되었다. 8086으로부터 시작된 또하나의 주목할만한 점은 세그먼트:오프셋(Segment:Offset) 방식의 메모리 접근이다. 1MB까지의 메모리에 엑세스하기 위해서는 기존의 방식에 따른 16비트로는 주소접근이 불가능하였다. 따라서 8086은 어떠한 한 지점(세그먼트)로부터 떨어져있는 거리(오프셋)로 그 주소에 접근하였다.


8086 호환모드 CPU의 주소체계는, 아시다시피 segment:offset의 구조로 되어 있습니다. 하지만, 이는 CPU 내부적으로 사용하는, 일종의 논리 주소이고, CPU가 메모리를 억세스할 때 사용하는 주소는 실제로는 20비트죠. 따라서 논리주소와 물리주소 사이에 변환이 필요합니다. segment주소는 어떤 세그먼트가 물리메모리에서 차지하고 있는 영역의 시작점을 나타내는 주소입니다. 여기에 offset을 더하여 실제 물리주소를 계산하게 되죠. 따라서 segment:offset 구조로 되어 있는 논리주소는 segment주소를 4비트 쉬프트하고, offset을 더해주어서 물리주소로 변환하여 억세스합니다. 따라서 두 세그먼트 사이의 간격이 64k바이트 이하라면 두 세그먼트가 차지하는 주소공간이 겹칠 수도 있습니다(세그먼트 주소로는 4096차이). 하지만, 모든 세그먼트가 64kb로 동일한 크기를 갖는 것은 아니란 점을 분명히 하시면 크게 문제는 없을 겁니다. 즉, D라는 세그먼트의 (데이터 세그먼트라고 해 보죠) 세그먼트 주소가 0x1000 이고, C라는 세그먼트의 (코드 세그먼트라고 해도 무방합니다) 세그먼트 주소가 0x1040 이라면, 이론상 데이터 세그먼트의 오프셋 1024, 즉 0x1000:0x0400는 코드세그먼트의 시작?
-->

오프셋 또는 옵셋이라고 부르는 명칭을 들어보신적 있으십니까?


저수준의 언어(어셈블리어 같은)에서나 쓰일만한 명칭쯤으로 기억하실 겁니다.


컴퓨터의 메모리를 흰 백지라 생각하고 컴퓨터가 처리하고 기억하는 모든 내용들을 CPU라는 펜으로 적을 수 있다 라는


가정을 생각해 봅시다.


사실 프로그래밍이 빌드되서 실행되는 것이 위의 내용과 많이 다르지 않습니다.


그렇다면 우리가 만드는 지역,전역,정적,포인터 변수들과 new,malloc 변수들, 그 많은 함수들은 대체 어디에 적히는 것일까요?


바로 세그먼트라는 영역에 나뉩니다.


세그먼트는 전체 메모리를 부분별로 나뉜 영역을 말합니다.


그 세그먼트에 따라 분류된 종이(메모리)에 적힌 그 수많은 변수들과 함수들의 문장들은 각자만의 주소를 가지고 있습니다.


하지만 그 주소를 그때그때 마다 지금까지 선언된 변수와 함수들을 일일이 계산해가며 주소를 부여하고 찾아가고 삭제하기에는 그 계산시간이 너무 걸리겠지요.


가령 종이 한장에 10줄 적을 수 있다고 치고 12장까지 적힌 코드로 작성된 프로그램이 있다고 치면 컴퓨터는 12장 5번째줄을 찾는데


110줄하고도 5줄을 한줄씩 counting(+1씩 숫자를더하가며)해서 주소를 찾아내는 셈인것입니다.


비효율적이지요.


그래서 컴퓨터(어셈블리어 기준)는 12페이지에 메모를 하기전에 지금 시작하는 줄은 110끝이다 라고 기억하고


12장 첫번째 줄에 메모를 할적에 1번줄이라고 기억하고 총페이지에서의 줄수를 찾아낼때


시작줄과begin(110)+찾고자하는줄의 상대적인 줄값wirte(1)으로 원하는 주소값 또는 전체줄번호를 찾아내는것 입니다.


여기서 상대적인 줄번호 값이 오프셋(OffSet)입니다.

즉 원하는 주소를 찾기위해 주소 절대값에 붙이는 주소 상대값인 것이죠.


FFFF : 0000(세그먼트 : 오프셋)이고

= FFFF0이다.

(∵세그먼트 × 10Hexa + 옵셋 = 메모리 주소)

쉽게 이해하기 위해서 이러한 문장을 쓰기도 한다.

“어떠한 한 지점(세그먼트)로부터 떨어져있는 거리(오프셋)로 그 주소에 접근하였다.”

실제로 수치를 넣어서 계산해보면

0x0600 <<4 +0x0123 = 0x06123