가상 메모리 짧게.

Mr.뉴트리아·2020년 11월 20일
1

이 글은 Fundamental C++의 내용을 정리한 것입니다.

가끔, 가상 메모리를 실제 메모리보다 메모리를 더 많이 쓸 수 있도록 가상으로 늘려주는 기술 정도로 아는 개발자가 있다. 일반인이 그렇게 알고있다면 이해할 수 있지만, 확실한 개념을 알아두는 것이 좋다.

일반적으로 한 시스템의 여러 프로세스들은 하나의 CPU와 메인 메모리를 공유한다. 만약, 프로세스들이 어떤 작동에 의해(예를 들어 지나치게 큰 메모리를 요구한다던가)다른 메모리를 침범할 경우, 프로그램의 로직과는 별개로 오류가 날 수 있다. 바로 이를 사전에 방지하기 위한 기술이 가상메모리이다.

가상 메모리의 핵심은, 각각의 프로세스에게 메모리 공간이 독립적으로 부여된다는 것이다.
할당된 메모리영역은 해당 프로세스만이 접근할 수 있돌고 보호된다. 즉, 메모리 영역이 프로세스에게 독립적으로 부여되기 때문에
타 프로세스가 해당 메모리 영역을 침범할 수 없다는 것이다. 이런 점으로 인해 가상 메모리는 프로그램의 안정성을 크게 향상시킬 수 있다.

우리가 흔히 쓰고있는 x86 = 32비트 시스템

? : 왜 32비트라 안하고 x86인가?
->인텔의 cpu시리즈 이름이자, cpu의 아키텍쳐 이름 ,길게 풀어쓰면 32비트는 x86-IA32이다.

32비트 시스템 = 4GB 메모리를 부여받는다. 윈도우의 경우, 받은 메모리의 절반은 운영체제가 사용하며, 나머지를 프로세스가 사용한다. 즉, 실질적으로 2GB의 메모리 공간을 사용한다는 것이다.

64비트시스템 - 2의 64승의 메모리 주소를 사용할 수 있지만, 그렇게 큰 메모리 공간이 필요치 않기 때문에, 보통 8TB의 공간을 부여하도록 제한한다. 위와 같이 운영체제와 프로세스가 절반의 메모리 공간을 나눠 사용한다.

방이 1024개인 병원이 존재한다. 이 병원에는 소아병동와 청소년병동가 따로 존재하는데, 1호실 방이 소아병동로 배정되어 있다면, 2호실 방은 청소년병동로 지정해두었다. 이런 복잡성으로 인해 가끔 끔 1호실 병동을 소아병동과 청소년병동 양쪽에 부여하는 실수도 생겼으며, 가끔 호실을 잘못 안 방문객들이 다른 병동으로 가는일도 생기게 되었다. 이에 병원장이 해결책을 냈는데,
각 병동마다 가상으로 4096호실을 배정해 놓고, 병실이 필요할 때마다 실제 병실을 배정하고, 병실의 문 앞에는 배정된 각 병동의 이름과 호수를 팻말로 부착하는 방식을 취한 것이다. 예를 들어서 소아병동에 환자가 입원해서 새로운 병실이 필요할 경우, 실제 707병실에 소아환자를 입실시키고, 해당 병실 문 앞에는 '소아병동 303호실'이란 팻말을 부착하는 것이었다. 또한 병원 원무과에서는 병문안 오는 손님들은 안내하기 위해서 임시로 배정된 소아병동 303호실과 실제 병실 번호인 707호를 매칭시켜놓은 정보를 통해서 쉽게 소아병동 303호실의 위치를 알려줄 수 있는 것이다. 이런 방식의 병실 배정은 독특한 특징을 나타낼 수 있다. 소아병동 303호실과 청소년병동 303은 같은 303호실이지만 완전히 별개의 병실이라는 점이다.

독자는 위 병원에서 부여할 수 있는 병실의 수(4096)가 어떻게 실제 병실의 수(1024)를 넘어설 수 있는지 궁금할 수 있는데 방법은 다음과 같다.
실제로 거의 다 완치가 되어서 더 이상 진료가 필요하지 않은 환자들의 경우 임시로 이웃 호텔의 방을 빌려서 이동시킨 후 비워진 병실은 다시 각 병동에 부여하는 것이다. 결국 호텔의 방까지 이용하여 각 병동의 병실 수가 많아지게 보일 수 있다.

a병원 = 운영체제
각 병동 : 하나의 프로세스
1024의 호실 : 물리적 메모리
가상으로 부여된 4096개의 병실 : 프로세스의 가상 메모리, 부여된 병실 번호가 가상 메모리의 주소이다.
호텔 : ? 2차 저장영역에 해당되는 SSD나 하드디스크를 의미함.

페이징

프로세스의 구조.

Code(Text)영역 : 소스코드 그 자체가 저장되는 영역. 함수나 상수, 전역배열 등이 저장된다. 만약 이 영역에 쓰기가 가능해지면,
타인이 악의적인 목적으로 실행 코드 자체를 변조시킬 위험이 있다. 때문에 코드의 안정성을 위해 읽기만 가능하다.

Data&BSS : 전역변수와 전역 배열이 저장되는 영역. 전역 변수에는 static도 포함된다. static을 정적 변수라고 이해하고 있어, 전역 변수와는 별개로 취급하는 사람도 있지만,
정확히 얘기하면 static 은 '전역 정적 변수'이다. 전역배열이 생소하게 들릴 수 있는데, 배열은 변수가 아니기에 따로 표현한 것이다. 보통 배열도 변수라고 통칭하는 경향이 있긴 하지만,
엄밀히 말해서 배열은 변수가 아니라 상수 포인터이다. 그래서 필자는 보통 전역 변수나 전역 배열 등을 통칭해서 전역 객체라고 표현한다.

데이터 : 초기화된 전역 객체가 저장되는곳

BSS : 초기화되지 않은 전역 객체가 저장되는 곳 쓰레기 값이 들어있는게 아니라, 메모리가 모두 0으로 채워져있다.

왜 데이터 영역과 BSS가 나뉘어져 있는가?

이를 알려면 처음에 가상 메모리가 어떻게 구성되는지를 알아야만 한다.

컴파일과 링크를 거치면 실행 파일이 생성된다. 이 프로세스는 실행파일을 실행할 때 시작되며, 완전히 일치하지는 않지만 실행 파일의 내용은 가상 메모리의 비슷한 구조로 올라가게 된다.
가상 메모리의 프로세스 영역이 code,data,bss 영역으로 나누어져 있듯이, 실행 파일의 구조도 비슷하게 여려 영역으로 분할되어 있다. 즉, 실행파일에서도 Code,Data,BSS에 대응되는 영역이 이미 존재한다는 의미이다. 당연히 같은 영역은 그대로 복사되면 될 것이다. 실제로 Code나 Data영역은 실행 파일에서 가상 메모리로 비슷하게 올라간다. 그러나 BSS는 이에 해당하지 않는다.

왜? : 초기화된 전역 객체 : 사용된다.
초기화되지 않은 전역 객체 : 사용되지 않는다.

위에 글에서 설명했듯이, 초기화되지 않은 전역 객체는 메모리가 0으로 채워져있다고 했다.
만약 초기화되지 않은 객체 1000개가 있다고 한다면, 이 메모리들을 일일히 0으로 초기화하는것은 상당히 비효율적이다.
아마도 1000개의 객체들은 한 영역에 몰아넣고 통째로 해당 영역을 0으로 초기화하는것이 더욱 효율적일 것이다. 그래서 초기화되지 않은 전역 객체에 대해서는
하나의 영역을 만들게 된 것이고, 그것이 바로 가상 메모리의 BSS 영역이다.

옛날 구조에서나 힙과 스택이 분리되어 표현되었지, 실제 구조에서는 서로 메모리 주소가 뒤섞여 있는 채로 존재한다.

스택은 스레드당 하나씩 생성된다 (스레드란? 프로세스 내에서 실행되는 흐름의 단위) 보통 스레드당 1~4MB정도가 스레드 스택 공간으로 자리한다.

스택은 함수가실행되면서 크기가 자라나는데, 함수가 종료되면서 다시 크기가 원래대로 돌아가게 된다.

힙은 동적 메모리가 저장되는 곳이다. 보통 malloc new등을 사용하여 할당되는 메모리들이 바로 힙 영역이다.

c++에서는 malloc 나 new로 메모리를 할당받을 수 있다. 하지만 생각해야할 점이 있다. 메모리를 관리하는 것은 전적으로 운영체제의 몫이다. 윈도우나 리눅스에 따라서 그리고 각각의 버전에 따라서
메모리를 관리하는 방법은 달라질 수 있다. 그럼에도 멀록이나 뉴를 사용하게 되면 운영체제의 차이를 뛰어넘어 메모리를 아주 쉽게 할당받을 수 있게 된다.

C++의 경우, CRT에서 사용하는 crt heap이 프로그램 시작 시 기본 생성된다. crt는 자신의 힙을 사용해서 malloc new 등을 처리한다.

profile
뉴트리아는 가시쥐과에 속하는 설치류의 일종이다. 오랫동안 뉴트리아과의 유일종으로 분류했지만, 현재는 가시쥐과에 포함시킨다. 늪너구리, 해리서 또는 코이푸라고도 한다. 뉴트리아는 스페인어로 수달을 의미하고, 출생지 남미에서는 이 종류를 코이푸라고 부른다.

0개의 댓글