큰 그림을 먼저 그려보자.
메모리는 물리메모리와 가상메모리 두가지가 존재한다.
똑같은 숫자 메모리를 하드웨어에 건네주었을 때, 하드웨어의 메모리 읽는 방법이 물리메모리라면 메모리에 직접 접근하고,
가상메모리라면 사전에 기재된 메모리 안에 적혀있는 메모리 주소에 접근해서 메모리주소를 가져온다.
여기서, 하드웨어가 어떤 방식으로 메모리를 접근할 지에 대한 내용과, 가상메모리접근시 사전 기재된 "메모리주소록(페이지테이블엔트리)의 주소" 를 설정하는 것은
하드웨어의 레지스터를 손봄으로써 이루어진다.
컴퓨터가 처음 부팅되었을 경우에 하드웨어는 물리메모리에 직접 접근해서 메모리를 접근하는데, 그 이유는 사전 기재된 메모리주소록이 없을 경우 가상메모리가 작동하지 않기 때문이며, 그 메모리 주소록은 운영체제마다 다 다르기에 하드웨어에서 미리 설정되어있을 수가 없기 때문이다.
즉, 운영체제의 가장 큰 역할 중 하나는 이 가상메모리 주소체계가 잘 작동하도록 셋팅을 마친 후, 하드웨어의 메모리 읽는 방법을 물리메모리방식에서 가상메모리 방식으로 변경해주는 것에 있다.
가상메모리를 사용하는 가장 큰 이유는, "격리" 다.
내가 C코드를 짰을때 나는 거대한 컴퓨터 위에서 다양한 객체들과 상호작용하면서 코딩하지 않고,
깨끗한 도화지 위에서 코딩한다는 것이다.
또한, 스케쥴링에 의해서 간섭받지도 않아야하고...... 이런 격리수준이 필요하기 때문에 가상메모리가 존재한다.
물론 물리메모리를 효율적으로 사용하기 위해서도 가상메모리는 필수적이다.
스케쥴링에 의해서 프로세스별로 CPU 시간이라는 자원을 배정해주듯이,
가상메모리에 의해서 프로세스별로 물리메모리 공간 이라는 자원을 효율적으로 배정할 수 있게 되는 것이다.
자 여기까지가 서론이다.
가상메모리는 일종의 포인터 개념이다.
32비트 주소를 기준으로, 32비트를 세개로 쪼갠다. 디테일은 기억도 안나고 찾아보기 귀찮아서 대충 10 10 12 라고 하겠다. (아마 10 10 12가 맞을거다)
그리고 첫 10 을 x, 두번째 10을 y, 12를 z라고 써보겠다.
CR3 은 앞서 설명한 하드웨어 레지스터에 기록되는 메모리주소록의 주소이다.
CR3에 적혀있는 주소 + x 를 한 공간에 쓰여있는 주소를 본다.
그 주소 + y 를 한 공간에 쓰여있는 주소를 본다. 이 주소를 addr 라고 하겟다.
addr 는 null일수도 있다. 이 경우, 페이지가 물리메모리에 올라와있지 않은 것이다.
여기에서 의문이 하나 들 것이다. CR3의 주소는 물리메모리에 직접 연결되어있으니 null 이 아닐거라고 확신할 수 있는데,
CR3 + x 를 한 공간에 들어있는 주소는 null 일 수도 있지 않나요??
그 부분까지도 운영체제가 전부 미리 공간을 할당해놓는다고 보면 된다.
즉, x 번지를 찾아가기 위한 y 주소록들은 전부다 세팅이 되어있고 공간이 다 배정되어있다. 운영체제를 실행시키기 위한 최소 메모리 조건안에서 이 공간들은 다 무리없이 배정 된다.
여하튼, CR3 + x >> + y >> null 인 경우,
물리메모리의 빈 공간에 2^12 바이트가 새로 배정되고, 해당 물리메모리의 주소가 CR3 + x >> + y 안에 기재된다.
xyz 라는 주소는 따라서 방금 배정된 새로운 페이지의 첫번째 바이트에서부터 z 바이트 만큼 떨어진 위치에 존재하게 되는데,
이는
CR3 + x >> + y >> +z 라고 표현된다.
CR3 + x >> + y >> 에서 물리메모리가 배정되어있는 경우는 그 공간을 찾아가서 z 만큼 오프셋을 떨구면 원하는 주소에 갈 수 있다.
이런 방식으로 가상메모리가 굴러간다.
자 그러면 이제 프로세스에 대한 이야기를 해야한다.
리눅스 커널 0.11은 존나 허접하다.
물리메모리는 16MB를 쓰는것으로 간주되고 (이는 24비트 공간이다.)
가상메모리는 4GB를 사용 (이는 32비트 공간)
그리고 여기가 핵심인데, 각 프로세스의 가상메모리 공간이 64MB 이며, 이는 4GB / 64 이다.
4GB / 64가 왜 중요하냐면,
리눅스 0.11는 4GB의 가상메모리를 64MB 씩 나누어서 64개의 칸막이를 설정해둔다는 점이다.
여기서 맨 앞의 64MB는 커널공간을 포함하고 있는데, 이 맨 앞의 공간은 프로세스 0 이라는 특수한 프로세스가 자리잡고 있어서
커널공간은 보호된다.
칸막이로 공간을 쪼개서 분할하면 생기는 문제가,
보호가 안된다는 점은 둘째 치고, 코드가 작동을 안한다.
가상메모리상에서 C코드는 0부터 4GB공간이 배정된다고 가정하는데 (리눅스 0.11의 경우는 프로세스당 할당된 공간이 64MB이니 이 부분이 어떻게 되었을지는 미지수. 워낙 시대가 시대인만큼, 저 시대는 C컴파일러가 가정하는 주소공간이 4GB보다 작았을것으로 예상됨)
여하튼, 가상메모리 상에선 0부터 시작하는데
운영체제에서는 0x40000000에서부터 시작된다고 하니까 문제가 발생하는거다.
물론 이거를 운영체제가 직접 주소체계를 소프트웨어로 변환해주고, 보호또한 소프트웨어로 직접 해줄 수 있는데,
매 메모리 접근마다 함수 호출할텐가?? 유효성체크하고??? 애초에 그걸 할거면 가상메모리는 왜 만든거지?? 가상메모리가 미완성이라는 얘기밖에 덜되는데??
답은, 하드웨어로 보호와 격리를 실행시킬 수 있다는 것이다.
여기서 이 문제를 해결하는 개념이
세그먼트와 TSS[ ] 다.
세그먼트는 하드웨어가 물리메모리 직접접속을 할 때 쓰는 레지스터다. 세그먼트 또한 사용하는 모드와 사용하지 않는 모드 두개로 나뉘는데,
세그먼트가 사용하는 주소록중 하나가 TSS다.
세그먼트는 가상메모리 모드에서도 사용이 가능한데,
base 주소를 설정하고, 주소의 유효범위를 설정한다.
TSS는 당연히 컨텍스트 스위치때마다 변경한다.
내가 TSS[ ] 로 리스트 형태로 쓴 이유기도 하다.
세그먼트 , 페이지테이블 이 두가지만 숙지하면 리눅스 0.11에 대한 가상메모리시스템은 이해가 가능하다.
아 그리고, 이 글 원래는 질문글이었는데
리눅스 상위버전도
CR3 은 고정함???? 0.11 이새끼는 0으로 고정해둠.
난 CR3을 고정한 결과물로 4GB를 쪼개서 쓰는 병신짓을 하고 있다고 생각하는데
일단 가상메모리를 쪼갠 다음에 세그먼트로 보호와 격리를 실행하는 이 구조는 진짜 쓰레기같다고 생각하고
아예 새로운 페이지테이블체계를 만들어서 CR3을 변경해야한다고 생각하고 있음.
근데 새로운 페이지테이블 체계를 만든다는게 좀 복잡해보이는게,
앞서 말했듯이, 리눅스 0.11은 PTE들이 페이지폴트가 이뤄지는 구조가 아님.
근데, 프로세스별로 페이지테이블을 생성해대면 페이지테이블또한 페이지폴트가 이뤄나야할거아님?? 스왑되어야할테니까
이 부분이 좀 빡세보임.
딱
딱
내가 지금 이거 보는데 정말 어렵다. 위에서는 가상메모리 주소만 얘기했을 뿐 실제로 어떤식으로 할당이 되고 관리를 하는지는 빠져있음 - dc App