1. 과제 설명
시스템 콜을 구현하기 위해 유저의 가상 주소 공간에 접근할 수 있는 방법을 제시해줘야 한다.
인자를 받아올 때는 이 기능이 필요하지 않았다. 하지만 시스템 콜 인자에서 제공하는 값에 접근하기 위해서는 이 기능이 필요하다. 잘못된 포인터, 커널 메모리, 블록을 가리키는 포인터의 경우 자식 프로세스를 종료해 처리해야 한다.
ex) *ptr->value 형식으로 값을 넣어줄 때 큰일날 수 있기 때문에 이를 미리 확인하여 잘못된 경우 종료시킨다.
2. 개념 정리: User space memory access from the kernel
처음에는 이 파트가 그냥 넘어가도 되는 건 줄 알았는데, 보니까 이것도 하나의 과제가 맞는듯. 일단 위의 과제에서 말하는 것처럼 시스템 콜을 구현하기 위해서는 유저의 가상 주소 공간에 접근할 수 있는 방법을 제시해줘야 한다고 했다. 이때 주어가 없는데, 그럼 누가 유저의 가상 주소 공간에 접근할까? 바로 커널이다. 즉, 운영체제가 접근해야 한다. 개념 정리를 위해 Linux에서 커널이 유저 메모리 공간에 접근하는 방식에 및 운영체제 책 13장~ 대해 정리해보자.
리눅스 메모리
리눅스에서는 유저 메모리와 커널 메모리가 독립적이다. 이는 실제 주소 공간에도 분리되어 구현되어 있다.이 유저 메모리와 커널 메모리가 들어가는 주소 공간은 가상화되어 있는데, 여기 가상 주소들은 물리 주소로부터 추상화되어 있다. 이 주소 공간은 가상화되어 있기 때문에 실제 물리 공간에 비해 많은 공간이 존재할 수 있다. 실제로 커널은 하나의 주소 공간에 상주해있으며 각 프로세스는 각자의 주소공간에 상주해 있다. 이 주소 공간은 가상 메모리 주소로 구성되어 있어 독립적인 주소 공간을 갖는 많은 프로세스가 훨씬 더 작은 물리적 주소 공간을 참조한다.
이는 각 프로그램이 하나의 메모리를 갖게 하는 환상을 줄 뿐만 아니라 각 주소 공간이 독립되어 있기 때문에 운영체제를 보호하고(protection) 각 프로세스 간 보안에도 안전하다(isolation)는 장점이 있다. 하지만 각 구역을 독립하고 고립함으로써 보안을 확보할 수 있는 반면 그만큼 tradeoff가 생기는데, 각 프로세스 및 커널은 물리 메모리의 다른 영역을 참조하는 동일한 주소를 가질 수 있기 때문에 즉시 메모리를 공유할 수 없다.
- 여기서 잠시 책 내용을 정리하고 가자. (운영체제 아주 쉬운 세 가지 이야기 13.3장: 주소 공간) 이 파트에서 protection을 소개하면서 주소 공간에 대한 이야기가 나온다. 운영체제가 단일 프로그램만 돌리던 옛 시절에서 시분할 시스템을 이용해 여러 프로그램을 돌리면서(그림 16.2 참고), 운영체제에는 여러 프로그램이 메모리에 동시에 존재하려면 보호(protection) 기능이 중요하다는 것을 프로그래머들이 깨달았다. 이를 위해 주소 공간(address space) 개념이 필요해졌다.
주소 공간
주소 공간은 실행 중인 프로그램(=프로세스)이 가정하는 메모리의 모습이다. 운영체제에서 메모리 개념을 이해하는 것이 메모리를 어떻게 가상화할지를 이해하는 핵심과도 같다. 하나의 주소 공간에는 실행 프로그램의 모든 메모리 상태를 갖고 있다. 아래 이미지를 보자(그림 16.3). 메모리 내 각 세그먼트(메모리에서 특정 길이를 갖는 연속적인 주소 공간)가 나타나 있으며 각각 코드, 힙, 빈 공간, 스택 세그먼트로 구성되어 있다.
프로그램 코드 세그먼트는 명령어를 보관하는 곳이다. 코드는 정적이기 때문에 메모리에 저장하기 쉽다(코드 양이 변한다거나 하지 않으니 용량이 고정됨). 즉, 프로그램이 실행되면서 추가 메모리를 필요로 하지 않기 떄문에 주소 공간의 가장 상단에 배치한다.
다음으로 프로그램 실행과 더불어 확장 혹은 축소될 수 있는 두 종류의 주소 공간이 존재한다. 주소 공간의 상단에서 하방으로 커지는 힙과 주소 공간의 하단에서 시작해 상방으로 커지는 스택이다.힙은 동적으로 할당되는 메모리를 위해 사용하며, 마지막으로 스택은 함수 호출 체인상에서 현재 위치, 지역 변수, 함수 인자 및 반환 값등을 저장하는데 사용된다.
두 메모리 영역은 프로세스가 진행됨에 따라 확장할 여지가 있기 때문에 위 그림처럼 배치한다. 그래야 두 영역 모두 가운데 쪽으로 확장할 수 있기 때문이다. (참고: 이런 배치는 관례로 멀티 스레드 환경에서는 이런 식으로 주소 공간을 나누면 동작하지 않는다.)
위에서도 가상화에 대해 얘기했듯, 주소 공간을 얘기할 때 이 공간은 운영체제가 실행 중인 프로그램에게 제공하는 개념적인 공간이지 실제 프로그램이 물리 주소 공간에서 위 이미지와 같은 것을 가지는 게 아니다. 지금 우리가 계속 얘기하는 주소 공간은 가상 주소 공간임을 잊지 말자. 하지만 프로그램 입장에서는 착각을 하고 있는 상태이다. 즉, 자기는 하나의 물리 주소를 갖고 있다고 여기며 프로세스 A가 만약 주소 0으로부터 load 연산을 수행한다고 하면 운영체제는 하드웨어의 지원으로 가상 주소 0으로부터 실제 물리 주소 0이 아닌 A가 탑재된 실제 물리 주소 공간 내 주소에 접근할 것이다. 그럼 여기서 질문. 어떻게 메모리를 가상화할까?
메모리 가상화 목표 3가지: 투명성, 효율성, 보호
메모리 가상화로 만들어진 가상 메모리 시스템(VM, Virtual Memory)의 목표는 3가지이다.
1) 투명성(Transparency) - 운영체제는 실행 중인 프로그램이 가상 메모리의 존재를 인지하지 못하도록(자신이 실제 물리 메모리를 가진 것처럼) 가상 메모리 시스템을 구현해야 한다. 즉, 뒷단은 추상화의 영역이다.
2) 효율성(Efficiency) - 운영체제는 가상화가 시공간 측면에서 효율적이도록 해야 한다. 시간 - 너무 느리면 안되고 공간 - 너무 많은 메모리를 사용해서는 안된다. 이 시공간 효율적인 가상화를 구현할 때, 운영체제는 TLB 등의 하드웨어 기능을 지원받는다.
3) 보호(Protection) - VM의 세 번째 목표는 보호이다. 운영체제는 프로세스를 다른 프로세스로부터도 보호해야하고 자신도 프로세스로부터 보호해야 한다. 프로세스가 탑재, 저장, 혹은 명령어 반입 등을 실행할 때 어떤 방법으로든 다른 프로세스나 운영체제의 메모리(=커널!)에 접근하지 못하게 해야 한다. 이를 Isolation(위에서 말했다)이라 하는데, 이 보호 기능을 유지하면서 어떻게 유저 공간에 접근할 수 있을지 다음에서 마저 정리해보자.
참고: 우리가 보는 모든 주소는 가상 주소이다! C 프로그램 짤 때 주루룩 나오는 코드가 있는데 이는 모두 가상 주소임!