우리는 C/C++ 언어에서 포인터란, 정수와 비슷한 형태이긴 하나 일반적인 숫자가 아니라 메모리 주소를 가리키는 특수한 자료형이라고 배운다. 포인터는 하드웨어 친화적이고(기계 입장에서 아주 직관적임) 매우 강력한 프로그래밍 요소이지만, 그만큼 길들여지지 않은 야생마처럼 위험 부담이 크다. 포인터 자체하고 포인터가 가리키는 메모리 영역이 따로 놀 가능성이 언제든지 있기 때문에 memory leak, dangling pointer 같은 위험에 노출되기가 매우 쉽다. 자유라는 약이 무질서라는 독으로 변질될 여지가 큰 것이다.

포인터의 값은 다른 지역/전역 변수의 주소로부터 얻어지거나 아니면 메모리 할당 함수를 통해 생성된다. 우리가 직접 상수 값을 대입하는 경우는 거의 없으며, 두 포인터의 값의 차이를 상대적으로 비교하는 경우는 있을지언정, 포인터가 나타내는 그 숫자 자체는 프로그래머에게 사실상 아무 의미가 없다.

사실 이 숫자가 무엇을 의미하는지는 C/C++ 언어의 영역이 아니라 그 언어를 돌리는 플랫폼 내지 운영체제의 영역으로 넘어가게 된다. 포인터란 그만큼 저수준인 존재이며, C++ 이후에 등장한 더 진보한 언어들은 포인터를 더 다루기 편하고 덜 위험한 형태로 어떻게든 감싸고 포장하고 더 상위 계층을 만들려고 애쓰고 있다.

우선 0은 NULL 값으로 유명하며, 윈도우 운영체제의 경우 0뿐만이 아니라 그 이후 0xFFFF 번지까지가 모두 오류로 간주된다. 즉, 이 영역은 운영체제가 어떤 경우에도 메모리를 가리키는 주소로 절대 인정하지 않으며, 오히려 응용 프로그램이 일반 숫자를 포인터로 착각하고 잘못 사용한 것으로 간주하여 무조건 오류를 일으켜 주겠다는 뜻이다. 이런 정책은 사실 프로그래머에게 굉장히 유용하다. 16비트 범위 안에 드는 작은 숫자는 메모리 주소보다는 인덱스 번호 같은 일반 숫자의 의미를 갖는 경우가 훨씬 더 많기 때문이다.

그 후 32비트 윈도우를 기준으로, 64KB부터 2GB까지는 응용 프로그램이 마음대로 사용할 수 있는 “사용자 영역”이다. 이 공간에 나의 EXE, DLL들이 로딩도 되고, 스택/힙 같은 메모리 공간이 할당되고 모든 일이 일어난다. 한 프로세스는 다른 프로세스의 이 영역을 넘볼 수 없다.

단 여기서 잠깐 예외가 있는데, 윈도우 9x는 앞부분의 64KB부터 4MB까지가 또 도스 및 16비트 윈도우 프로그램과의 호환성 유지를 위한 고정된 공용 주소로 예약이 되어 있다. 이런 이유로 인해 윈도우 9x는 4MB에 해당하는 0x400000보다 낮은 메모리 주소에다가는 32비트 바이너리를 불러올 수 없다. EXE/DLL의 preferred base가 이보다 더 낮은 주소인 데다가 재배치 정보까지 들어있지 않다면 그 바이너리는 윈도우 2000/XP 이상에서는 실행 가능하지만, 9x에서는 실행할 수 없게 된다. 비주얼 C++을 보면 EXE의 디폴트 기준 주소가 0x400000으로 잡혀 있는데, 이것은 윈도우 9x와의 호환성을 고려한 귀결이다.

NT급 윈도우는 0x80000000부터 커널이 사용하는 메모리 영역이 시작된다. 쉽게 말해 32비트 포인터로 가리킬 수 있는 4GB의 영역을 응용 프로그램이 2GB, 커널이 2GB로 반반씩 나눠 쓴다는 뜻이다. 물론 여기 부근에도 NT 계열과 9x 계열 윈도우는 메모리를 사용하는 방법이 대동소이한 차이가 존재한다.

NT급 윈도우에는 0x80000000 이전의 64KB 공간을 또 떼어서, 프로그래밍의 편의상 무조건 사용하지 않고 여기에 접근하는 것을 에러로 간주하는 영역을 또 두고 있다. 0~0xFFFF의 용도와 마찬가지이며, 말 그대로 사용자 영역과 커널 영역 사이에 안전을 위해 마련해 놓은 "메모리의 비무장 지대"인 셈이다.

한편 9x 계열은 그런 것은 존재하지 않는다. 대신 0x80000000부터 0xC0000000 사이의 1GB를 “공유 메모리 전용 영역”으로 지정하여, 일부 운영체제 커널 DLL과, 응용 프로그램들이 생성하는 ‘공유 메모리’(memory mapped file)를 이 영역에다 따로 두고 있다. 물론 NT 계열은 그런 것들도 다 구분 없이 사용자 영역에 저장된다. 실제로는 같은 물리 메모리를 가리키더라도 이를 가리키는 포인터의 값은 프로세스마다 다 다를 수 있다는 것이다. 이런 이유로 인해, 공유 메모리를 생성해 보면 9x 계열은 메모리 위치가 0x80000000을 상회하는 높은 주소인 반면, NT 계열은 심지어 자기 EXE가 로딩된 0x400000보다도 낮은 위치에 매핑이 된 경우도 볼 수 있다.

본인 생각에, 이것은 안정성을 약간 희생하여 좀더 작고 빠른 저사양 친화형 OS를 추구한 9x 계열의 고육지책으로 보인다. 사용자 영역에는 진짜로 각 프로세스마다 따로 돌아가야 하는 메모리만 넣고, 조금이라도 프로세스들이 공유할 여지가 있는 메모리는 여기에다가 따로 옮겨 둔 것이다.
이런 구조상의 차이로 인해 윈도우 9x는 NT 계열만치 커다란 메모리 맵 파일 내지 공유 메모리를 생성할 수 없다. 모든 응용 프로그램들이 1GB짜리 공간 안에서 바둥대며 살아야 하기 때문이다. 하지만 NT 계열은 설령 공유 메모리라 할지라도 마치 자기 개인 메모리 다루듯이 얼추 2GB 안에서 자유롭게 그런 것들을 만들어 보호도 잘 받으면서 쓸 수 있다.

나머지 영역은 전부 커널이 사용한다. 프로세스, 스레드 같은 각종 커널 오브젝트를 생성하고 가상 메모리 내지 페이지 파일들을 관리하기 위한 메모리이다. 쉽게 말해 메모리를 관리하기 위한 메모리. 무슨 커널이 최하 1GB에 넉넉잡아 2GB까지씩이나 주소를 차지하고 있어야 하냐 질문할지 모르는데, 결론부터 말하면 그 정도 공간은 반드시 있어야 하며 사실 2GB조차도 부족한 감이 있다.

NT급 운영체제는 커널 영역의 주소가 사용자 응용 프로그램으로부터 완벽하게 보호받고 있으며 user가 커널 영역 메모리로 접근을 시도하면 즉시 에러가 난다. 둘 사이에 앞서 언급한 "비무장 지대"까지 존재한다. 그러나 9x 계열은 그렇지 못하다.

BYTE *pb = (BYTE *)0xC0001000;
int i;
for(i=0; i<4096; i++) {
        printf("%02X ", *pb), *pb=*pb; pb++;
}

이런 간뎅이-_-가 배 밖에 나온 코드를 실행하면 NT급에서는 당연히 즉시 Access violaton 에러가 나고 프로세스가 사살되는 반면,
9x 계열은 놀랍게도 잘 실행된다. *pb=*pb로 해 줬으니 망정이지 다 0으로 덮어쓴다거나 하면 무슨 일이 벌어질까? 9x 계열이 왜 불안정할 수밖에 없는지 답이 딱 나올 것이다.

같은 32비트 안에서 사용자:커널이 2G:2G가 아니라 사용자한테 좀더 메모리를 많이 줘서 더 대용량의 데이터를 처리할 수 있게 한 3G:1G 부팅 방식도 있긴 한다. 사실 9x 계열도 앞서 말한 구조의 차이 때문에 커널 메모리는 1G이다.
하지만 이 경우 운영체제가 관리할 수 있는 가상 메모리의 이론적 최대치가 크게 감소하고 생성 가능한 커널 오브젝트(프로세스, 스레드, 공유 메모리 등)의 수도 더 줄어든다는 것을 알아야 한다. 또한 응용 프로그램도 large-address-aware하게 빌드되었다는 별도의 플래그가 있어야 한다.

Posted by 사무엘

2010/01/11 00:43 2010/01/11 00:43
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/59

Trackback URL : http://moogi.new21.org/tc/trackback/59

Leave a comment
« Previous : 1 : ... 2151 : 2152 : 2153 : 2154 : 2155 : 2156 : 2157 : 2158 : 2159 : ... 2204 : Next »

블로그 이미지

그런즉 이제 애호박, 단호박, 늙은호박 이 셋은 항상 있으나, 그 중에 제일은 늙은호박이니라.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

Site Stats

Total hits:
3044511
Today:
1703
Yesterday:
2435