Chapter 10 : Process Address Space
Linear Address Space: Overal
Kernel Memory Allocation
The kernel is the highest priority component of the OS, hence the kernel memory allocation should not be deffered
User Memory Allocation
- When allocating memory to user mode processes
- 사용자 모드 프로세스에 메모리를 할당할 때, 동적 메모리(즉, 물리적 페이지 프레임) 요청은 긴급하지 않은 것으로 간주됩니다. 예를 들어, malloc() 함수와 비교해 볼 때, 커널은 일반적으로 사용자 모드 프로세스에 페이지 프레임 할당을 지연시키려고 시도합니다.
- 사용자 프로그램은 신뢰할 수 없기 때문에, 커널은 사용자 모드에서 프로세스에 의해 발생되는 모든 주소 지정 오류를 포착할 준비가 되어 있어야 합니다.
- Characteristics of memory allocation for user mode process
- 사용자 모드 프로세스가 메모리 할당을 요청할 때 추가적인 페이지 프레임을 바로 받지 않습니다. 대신, 새로운 범위의 선형 주소 공간("메모리 영역")을 사용할 권리를 획득하게 되며, 이것은 해당 프로세스의 주소 공간의 일부가 됩니다.
간단히 말해서, 사용자 모드 프로세스 메모리 할당은 프로세스가 물리적 메모리 페이지를 직접 받는 것이 아니라 해당 페이지를 사용할 권리를 받는 것을 의미합니다. 이것은 프로세스에 실제 메모리 주소 대신 가상 메모리 주소를 제공하여, 커널이 메모리를 보다 유연하게 관리할 수 있게 하며, 메모리 보호와 프로세스 간 격리를 가능하게 합니다.
- User address space
- 이는 사용자 모드에서 프로세스가 사용할 수 있는 모든 선형 주소를 포함합니다. 커널은 사용자 주소 공간을 선형 주소의 간격을 추가하거나 제거함으로써 동적으로 수정할 수 있습니다.
- Memory region
- 이는 초기 선형 주소, 길이, 일부 접근 권한으로 특징지어지는 선형 주소의 간격을 의미합니다. 효율성을 위해, 초기 주소와 길이는 여러 개의 4KB(페이지 프레임에 적합한 크기)로 설정되어야 합니다.
요약하자면, 사용자 메모리 할당은 프로세스가 필요에 따라 사용할 수 있는 선형 주소 공간을 확보하는 것을 말하며, 이 공간은 메모리 페이지로 나누어져 있고, 커널은 이 영역을 관리하여 프로세스가 필요로 하는 메모리를 할당하고 회수합니다.
⭐️When Process Gets New Memory Regions?
- 새로운 프로세스 생성 시: 새로운 프로세스가 생성될 때, 일련의 메모리 영역이 새 프로세스에 할당됩니다. 이는 fork() 시스템 호출을 통해 이루어집니다.
- 완전히 다른 프로그램을 로딩할 때: 프로세스가 실행 컨텍스트를 새로운 실행 파일로 설명된 새로운 컨텍스트로 대체하려 할 때 (**exec()**와 같은 함수 사용), 새로운 메모리 영역이 할당합니다.
- 파일에 대한 메모리 매핑 수행 시: 프로세스가 mmap() 시스템 호출을 통해 파일에 대한 새로운 메모리 매핑을 생성할 때, 프로세스의 주소 공간이 확장됩니다.
- 프로세스의 사용자 모드 스택에 데이터를 추가할 때: 사용자 모드 스택의 크기를 확장하기 위해 새로운 메모리 영역이 할당됩니다.
- 다른 협력 프로세스와 데이터를 공유할 때: IPC(Inter-Process Communication) 공유 메모리를 사용하여 다른 프로세스와 데이터를 공유할 때 새로운 메모리 영역이 할당됩니다.
- 프로세스의 동적 영역(힙)을 확장할 때: 힙에 할당된 메모리 영역의 크기를 확장하기 위해 새로운 메모리 영역이 할당됩니다.
Handling Memory Regions
- 커널은 메모리 영역을 검색하는 빈번한 작업을 수행합니다. 대부분의 리눅스 프로세스는 매우 적은 수의 메모리 영역을 사용하지만, OODB와 같은 일부 대형 응용 프로그램은 수천 개의 영역을 가질 수 있으며, 이는 많은 malloc() 호출을 의미합니다. 이러한 상황에서의 성능은 비효율적이고 용납할 수 없을 정도로 나빠질 수 있습니다.
- 메모리 영역이 많을 때: 리눅스는 프로세스가 많은 수의 메모리 영역을 가질 때 이들의 기술자(descriptor)를 레드-블랙(RB) 트리에 저장합니다. RB 트리는 요소의 수가 적을 때 효율적이지 않습니다.
- 리눅스는 list와 RB 트리 구조를 모두 사용합니다. 리눅스 2.4.9까지는 AVL 트리가 사용되었다는 점을 주목해야 합니다.
간단히 말해, 커널은 프로세스의 메모리 영역을 관리하기 위해 데이터 구조를 사용하며, 이는 성능 최적화를 위해 다양한 자료 구조를 활용합니다. RB 트리는 메모리 영역의 기술자를 저장하는 데 효과적인 자료 구조로, 특히 메모리 영역의 수가 많을 때 유용합니다.
- 메모리 영역을 다루기 위해 mmap 필드에 의해 참조되는 연결 리스트(linked list)를 사용합니다. 프로세스의 메모리 기술자(descriptor)는 RB 트리를 가리키는 mm_rb라는 또 다른 필드를 포함합니다.
- RB 트리가 생성되어 메모리 영역을 처리하면 리눅스는 연결 리스트와 RB 트리를 모두 최신 상태로 유지합니다.
- ⭐️일반적으로 RB 트리는 특정 주소를 포함하는 영역을 찾는 데 사용되며, 연결 리스트는 모든 영역 세트를 스캔할 때 주로 유용합니다.
a. process에 새로운 memory region을 할당하는 상황을 모두 적고 설명하시오.
- 새로운 프로세스 생성 시: 새로운 프로세스가 생성될 때, 일련의 메모리 영역이 새 프로세스에 할당됩니다. 이는 fork() 시스템 호출을 통해 이루어집니다.
- 완전히 다른 프로그램을 로딩할 때: 프로세스가 실행 컨텍스트를 새로운 실행 파일로 설명된 새로운 컨텍스트로 대체하려 할 때 (**exec()**와 같은 함수 사용), 새로운 메모리 영역이 할당합니다.
- 파일에 대한 메모리 매핑 수행 시: 프로세스가 mmap() 시스템 호출을 통해 파일에 대한 새로운 메모리 매핑을 생성할 때, 프로세스의 주소 공간이 확장됩니다.
- 프로세스의 사용자 모드 스택에 데이터를 추가할 때: 사용자 모드 스택의 크기를 확장하기 위해 새로운 메모리 영역이 할당됩니다.
- 다른 협력 프로세스와 데이터를 공유할 때: IPC(Inter-Process Communication) 공유 메모리를 사용하여 다른 프로세스와 데이터를 공유할 때 새로운 메모리 영역이 할당됩니다.
- 프로세스의 동적 영역(힙)을 확장할 때: 힙에 할당된 메모리 영역의 크기를 확장하기 위해 새로운 메모리 영역이 할당됩니다.
b. memory region을 handling 할 때, RB tree와 linked-list를 사용하는 이유는 설명하시오
일반적으로 RB 트리는 특정 주소를 포함하는 영역을 찾는 데 사용되며, 연결 리스트는 모든 영역 세트를 스캔할 때 주로 유용합니다.
Managing the Heap
Heap is "Specific memory region", which is used to satisfy the process's dynamic memory requests.
Page Fault Exception Handling
페이지 폴트 예외는 주소 변환 과정에서 발생하며, 이는 MMU(메모리 관리 장치) 하드웨어에 의해 발생됩니다. 페이지 폴트는 프로세스의 주소 공간에 속하지만 아직 할당되지 않은 페이지를 참조할 때 발생합니다. 이 예외를 처리하기 위해 do_page_fault() 함수를 호출합니다.
do_page_fault() 함수는 페이지 폴트 예외에 대한 서비스 루틴으로, 아키텍처에 특정한 구현이 있습니다(예: arch/i386/mm/fault.c). 이 함수는 페이지 폴트를 유발한 선형 주소와 현재 프로세스의 메모리 영역을 비교하여 물리 메모리에서 페이지 프레임을 찾고, 누락된 페이지를 로드하며, 페이지 테이블을 업데이트합니다.
간단히 말해, 이는 운영 체제가 물리적 메모리에 없는 페이지에 접근하려 할 때 발생하는 예외 상황을 처리하는 메커니즘을 설명하고 있습니다. 이를 통해 운영 체제는 가상 메모리 관리를 수행하며, 필요한 페이지를 물리 메모리로 가져오거나 스왑 공간과 같은 보조 저장소를 사용하여 페이지를 교체하는 작업을 수행합니다.
do_page_fault()
기본적으로, do_page_fault()는 메모리 접근 위반을 관리하는 복잡한 함수로, 운영 체제가 잘못된 메모리 주소에 접근하려는 시도를 적절히 처리함으로써 안정성과 보안을 유지하도록 보장합니다.
Allocate a New Page Frame
- handle_pte_fault(): 이 함수는 프로세스에 새 페이지 프레임을 할당하는 방법을 결정합니다.
- 접근하려는 페이지가 현재 메모리에 없는 경우:
- 페이지가 아직 어떤 페이지 프레임에도 저장되지 않았다면, 커널은 새 페이지 프레임을 할당하고 적절히 초기화합니다.
- 이 과정은 "수요 페이징(Demand Paging)"이라고 불립니다.
- 접근하려는 페이지가 메모리에 존재하지만 "읽기 전용"으로 표시된 경우:
- 페이지가 이미 페이지 프레임에 저장되어 있다면, 커널은 새 페이지 프레임을 할당하고 기존 페이지 프레임 데이터를 복사하여 내용을 초기화합니다.
- 이는 "쓰기 시 복사(Copy On Write)" 방식으로 알려져 있습니다.
간단히 말해, handle_pte_fault() 함수는 프로세스가 필요로 하는 메모리 페이지가 물리 메모리에 없을 때 이를 해결하기 위해 새로운 페이지 프레임을 할당하고, 필요한 데이터를 그곳에 배치하는 역할을 합니다. 이는 메모리 관리의 중요한 부분으로, 시스템의 메모리 사용 효율성을 높이고, 필요한 메모리만을 사용하여 시스템 자원을 절약하는 방법입니다.
Demand Paging
수요 페이징은 동적 메모리 할당 기법으로, 프로세스가 RAM에 없는 페이지를 접근하려 할 때까지 페이지 프레임 할당을 미루는 방법입니다. 이는 "페이지 폴트" 예외를 발생시키게 됩니다. 이러한 방식의 동기는 모든 주소가 사용되지 않는다는 지역성 원리에 기초하고 있으며, 페이지 폴트 예외 처리에 따른 오버헤드를 포함합니다.
주소된 페이지가 메인 메모리에 존재하지 않을 수 있는데, 그 이유는 다음과 같습니다:
- do_no_page() 함수는 페이지 테이블 엔트리가 모두 0일 때 호출됩니다. 이는 해당 페이지가 프로세스에 의해 한 번도 접근되지 않았음을 의미합니다.
- do_swap_page() 함수는 페이지 테이블 엔트리가 0이 아닐 때 호출됩니다. 이는 해당 페이지가 이미 프로세스에 의해 접근되었지만, 그 내용이 디스크에 일시적으로 저장되었음(디스크로 스왑 아웃됨)을 의미합니다.
수요 페이징은 메모리를 효율적으로 사용하기 위한 중요한 기법으로, 실제로 필요할 때만 페이지를 메모리에 로드하여 시스템의 메모리를 절약할 수 있게 합니다. 페이지가 실제로 필요할 때까지 물리 메모리에 할당하지 않으므로, 메모리 사용량을 줄이고, 시스템의 전반적인 성능을 향상시킬 수 있습니다.
Copy on Write
COW는 UNIX 시스템에서 새로운 프로세스를 생성할 때 fork() 시스템 호출을 사용하여 부모 프로세스의 주소 공간을 자식 프로세스로 복제하는 전통적인 방식과 대조됩니다. 전통적인 방법은 비용이 많이 들고 시간이 오래 걸립니다. 왜냐하면 이는 부모 프로세스의 mm_struct, vm_area_struct 및 페이지 테이블을 모두 복사해야 하기 때문입니다. 따라서 COW 기법에서는 페이지 프레임을 복제하는 대신 부모와 자식 프로세스 간에 페이지 프레임을 공유하는 메모리 관리 기법입니다.
쓰기 가능한 영역의 페이지를 "읽기 전용"으로 설정하고, 이러한 페이지에 대한 어느 한쪽 프로세스의 쓰기 작업은 페이지 폴트를 유발합니다. 이때 폴트 핸들러는 COW를 인지하고 페이지의 복사본을 만든 후 쓰기 권한을 복원합니다. 즉, 페이지에 쓰기 작업이 발생할 때, 페이지 폴트 핸들러는 해당 페이지가 '읽기 전용'으로 표시되어 있는지 확인합니다. 페이지가 이미 페이지 프레임에 저장되어 있지만 '읽기 전용'으로 표시된 경우, 커널은 새 페이지 프레임을 할당하고 기존 페이지 프레임의 데이터를 복사하여 초기화합니다.
결과적으로 복사 작업은 반드시 필요한 경우 (즉, 두 프로세스 중 하나가 공유 페이지를 수정하려 할 때)까지 연기됩니다. 이 방법은 메모리 사용을 최적화하고 프로세스 생성 시간을 단축하는 데 도움이 됩니다.
⭐️Copy-On-Write에 대해 설명하고, page fault handler와 엮어서 구현 방식을 설명하시오.
Copy On Write (CoW)는 기존의 페이지 프레임을 복제하는 대신, 부모 프로세스와 자식 프로세스 간에 페이지 프레임을 공유하는 메모리 관리 기법입니다. 쓰기 가능한 영역의 페이지를 '읽기 전용'으로 만들어, 이 페이지들에 대한 쓰기 작업이 발생할 때 MMU 하드웨어 의해 페이지 폴트(page fault)가 발생하게 합니다. 폴트 핸들러는 복사-쓰기(copy-on-write)를 인식하고, 페이지의 복사본을 만든 후 쓰기 권한을 복원합니다. 이 방식은 프로세스가 공유 페이지를 수정하려고 할 때까지 실제 복사 작업을 연기합니다.
CoW는 페이지 폴트 핸들러와 밀접하게 연계되어 있습니다. 페이지 폴트는 주소 변환(즉, MMU 하드웨어)에 의해 발생하며, 프로세스 주소 공간에 속하지만 아직 할당되지 않은 페이지에 대한 참조로 인해 발생합니다. do_page_fault() 함수는 이러한 예외를 처리하는 데 사용됩니다. 이 함수는 발생한 페이지 폴트의 원인이 된 선형 주소를 현재 프로세스의 메모리 영역과 비교하여 물리 메모리에서 페이지 프레임을 찾고, 누락된 페이지를 로드하고 페이지 테이블을 업데이트합니다.
페이지에 쓰기 작업이 발생할 때, 페이지 폴트 핸들러는 해당 페이지가 '읽기 전용'으로 표시되어 있는지 확인합니다. 페이지가 이미 페이지 프레임에 저장되어 있지만 '읽기 전용'으로 표시된 경우, 커널은 새 페이지 프레임을 할당하고 기존 페이지 프레임의 데이터를 복사하여 초기화합니다. 이 과정은 '복사 시 쓰기(Copy On Write)'라고 하며, 페이지 폴트 핸들러는 이러한 상황을 인식하여 적절히 처리합니다.