동적 적재(dynamic loading)와 가상 메모리(virtual memory)는 비슷한 개념으로 보일 수 있지만, 그들이 해결하려는 문제와 사용되는 기술은 매우 다릅니다.
동적 적재는 프로그램의 특정 부분(예: 함수 또는 모듈)이 실제로 필요할 때까지 그 부분을 메모리에 로드하지 않는 기법입니다. 이는 메모리 사용량을 최소화하고, 더 많은 프로그램을 동시에 실행하는 것을 가능하게 합니다. 하지만, 이 기법은 필요한 코드 또는 데이터가 실제로 필요한 시점에만 로드되므로, 프로그램의 실행 흐름에 따라 메모리에 로드되는 코드나 데이터가 바뀔 수 있습니다.
반면에, 가상 메모리는 물리적 메모리의 크기보다 큰 주소 공간을 프로그램에 제공하기 위해 사용됩니다. 이 기술은 메모리에 로드되지 않은 프로그램의 부분을 디스크 상의 '페이지' 또는 '세그먼트'에 저장하고, 프로그램이 그 부분을 필요로 할 때 그것을 메모리로 가져오는 '페이지 교체'를 수행합니다. 이로 인해 프로그램은 물리 메모리의 크기를 초과하는 메모리를 사용하는 것처럼 동작할 수 있습니다.
따라서 동적 적재와 가상 메모리 모두 필요한 시점에 데이터를 메모리에 로드하는 점에서 유사하지만, 그 목적과 작동 방식에서 차이가 있습니다. 동적 적재는 메모리 효율성과 프로그램 실행의 유연성을 증가시키는 데 초점을 맞추고, 가상 메모리는 프로그램이 물리 메모리의 제약 없이 더 큰 메모리 공간을 사용할 수 있게 하는 데 목표를 두고 있습니다.
fork()
시스템 콜은 Unix와 Linux 등의 운영 체제에서 사용되는 방법으로, 현재 실행 중인 프로세스의 복사본을 만드는 데 사용됩니다. 이 새로운 프로세스는 '자식 프로세스'라고 부르며, 원래 프로세스는 '부모 프로세스'라고 부릅니다. fork()
가 호출되면, 부모 프로세스의 메모리 공간이 자식 프로세스로 복사됩니다.
이러한 방식은 매우 효율적이지 않을 수 있습니다. 왜냐하면 복사해야 하는 데이터가 많을 경우, 프로세스 생성 시간이 증가하고, 메모리 사용량도 증가하기 때문입니다.
그러나, 가상 메모리 시스템에서는 '페이지 공유' 또는 '복사 시 쓰기(copy-on-write)'라는 기법을 사용하여 이 문제를 해결할 수 있습니다. 이 기법은 초기에는 부모 프로세스와 자식 프로세스가 같은 메모리 페이지를 공유하도록 하고, 프로세스 중 하나가 페이지를 변경하려 할 때만 해당 페이지의 복사본을 만들어서 변경하게 합니다.
이렇게 하면 fork()
를 사용하여 프로세스를 생성할 때 실제로 메모리를 복사할 필요가 없으므로, 프로세스 생성 시간을 크게 줄일 수 있습니다. 또한, 대부분의 경우에 프로세스는 복사된 메모리를 그대로 사용하므로 메모리 사용량도 줄일 수 있습니다. 이러한 방식은 효율성과 성능을 크게 향상시킬 수 있습니다.
두 기법 모두 '필요할 때에만 메모리에 로드한다'는 공통점이 있지만, 그 단위와 적용 범위에서 차이가 있습니다. 동적 적재는 일반적으로 프로그램 또는 모듈 단위에서 사용되며, 요구 페이징은 메모리 관리 단위인 '페이지'에 대해 적용되는 것이 일반적입니다.
"트랩을 건다"라는 표현은, 여기서는 특정 상황이 발생했을 때 운영체제에 그 정보를 알리는 것을 의미합니다. 이는 일종의 인터럽트로, 컴퓨터 하드웨어의 특정 상태나 오류 조건을 시스템 소프트웨어에 알리는 메커니즘입니다.
페이징 하드웨어가 페이지 테이블을 사용하여 주소 변환을 시도하는 과정에서 무효 비트(invalid bit)를 발견하면, 해당 메모리 접근이 유효하지 않음을 운영체제에 알립니다. 이를 통해 운영체제는 메모리 보호를 구현하거나 페이지 폴트를 처리하는 등의 적절한 동작을 수행할 수 있습니다.
즉, "트랩을 건다"는 것은 운영체제에게 제어를 넘겨서 적절한 조치를 취하도록 하는 것을 의미합니다. 이런 인터럽트를 통해 운영체제는 메모리를 안전하게 관리하고, 필요한 페이지를 메모리로 로드하거나, 무효한 메모리 접근에 대해 에러 메시지를 표시하는 등의 동작을 수행합니다.
익명 메모리와 스왑 공간의 연관성은 가상 메모리 시스템에서 메모리 관리를 어떻게 하는지에 대한 문제입니다.
익명 메모리는 파일 시스템에 연결되지 않은 메모리 영역을 나타냅니다. 이 메모리 영역은 일반적으로 프로세스의 힙(heap) 영역이나 스택(stack) 영역 등으로, 동적으로 생성되고 삭제되는 메모리를 포함합니다.
스왑 공간(swap space)은 디스크 상에 설정된 일정 영역으로, 메모리가 부족할 때 메모리의 일부를 임시로 저장하거나, 사용되지 않는 메모리 페이지를 내리는 역할을 합니다. 이를 통해 시스템은 더 많은 메모리를 사용하는 것처럼 작동할 수 있습니다.
익명 메모리는 주로 힙이나 스택과 같이 실행 시간 동안 동적으로 크기가 변경되는 메모리 영역에 사용되는데, 이런 메모리 영역이 필요 이상으로 커져서 실제 물리적 메모리를 초과하면, 운영체제는 메모리의 일부를 스왑 공간으로 옮겨야 할 수 있습니다. 이렇게 스왑 공간은 메모리 부족 상황에서 익명 메모리의 일부를 임시로 저장하는 역할을 하며, 시스템의 메모리 관리를 돕습니다.
fork()
와 exec()
시스템 콜의 조합을 사용하는 것이 운영체제 디자인과 프로세스 관리에 있어 유연성과 강력함을 제공합니다.
프로세스의 초기 상태 설정: fork()
를 통해 복제된 새로운 프로세스는 부모 프로세스의 상태, 즉 열린 파일 디스크립터, 환경 변수, 메모리 레이아웃 등을 복사하게 됩니다. 이는 새로운 프로세스가 기존 프로세스의 컨텍스트에서 시작할 수 있게 하고, 이를 통해 부모 프로세스와 새로운 프로세스 사이에서 데이터를 쉽게 공유할 수 있게 합니다.
새로운 프로그램 로딩: exec()
시스템 콜은 현재 프로세스의 메모리 공간을 새로운 프로그램으로 완전히 대체합니다. 이는 새로운 독립적인 프로그램을 실행하는 데 필요한 모든 정보를 로드하고 초기화합니다.
이 두 시스템 콜의 분리는 프로세스의 생성과 초기화를 구분하고, 이를 통해 프로세스 생성 이후와 새로운 프로그램 로드 이전에 부가적인 작업을 수행할 수 있는 유연성을 제공합니다. 예를 들어, 부모 프로세스는 fork()
와 exec()
사이에서 자식 프로세스의 자원 제한을 설정하거나, 입출력 리디렉션을 수행하거나, 신호 처리기를 설정할 수 있습니다.
따라서, 단순히 새로운 프로세스를 생성하는 것보다 fork()
와 exec()
를 사용하여 프로세스를 생성하고 초기화하는 것이 더 많은 제어와 유연성을 제공합니다. 이는 시스템 프로그래밍과 멀티프로세스 시스템에서 중요한 요구사항입니다.
아니요, 여기서 언급하는 "internal table"은 주로 프로세스 제어 블록 (PCB, Process Control Block)에 포함되어 있는 테이블로서, 특정 프로세스에 대한 메모리 접근 권한과 관련된 정보를 유지합니다.
이 테이블은 주로 페이지 테이블, 세그먼트 테이블 등과 같은 메모리 관리 데이터 구조를 포함하며, 이들은 운영 체제가 물리 메모리를 어떻게 관리하고 각 프로세스가 메모리 내에서 어디에 위치하고 있는지를 추적하는데 사용됩니다.
반면, 계층 페이지 테이블에서의 내부 테이블은 더 복잡한 페이지 테이블 구조의 한 부분을 나타냅니다. 여기서 내부 테이블은 실제 메모리 위치를 지정하는 엔트리를 가지는 반면, 외부 테이블은 내부 테이블에 대한 참조를 가집니다. 이는 복잡한 메모리 구조에서 효과적인 메모리 관리를 가능하게 합니다.
따라서, 위의 내용에서 언급하는 "internal table"은 프로세스 제어 블록에 있는 테이블을 나타내며, 이는 계층적 페이지 테이블의 내부 테이블과는 별개의 개념입니다.
"Raw 파티션"을 직접 사용하면 파일 시스템의 오버헤드를 피할 수 있기 때문에, 특정 유형의 응용 프로그램에서는 이를 통해 성능 향상을 이룰 수 있습니다.
예를 들어, 아래와 같은 시나리오에서 raw 파티션 사용이 효율적일 수 있습니다:
데이터베이스 시스템: 대규모 데이터베이스는 종종 raw 파티션을 사용하여 디스크 공간을 직접 관리합니다. 이를 통해 데이터베이스 관리 시스템(DBMS)은 파일 시스템이 제공하는 고급 기능을 건너뛰고, 디스크 I/O 작업을 더욱 세밀하게 제어할 수 있습니다. 이는 디스크 접근을 최적화하고, 성능을 향상시키며, 공간을 더 효율적으로 사용하는 데 도움이 됩니다.
스트리밍 미디어 서버: 스트리밍 미디어 서버는 오디오나 비디오 데이터를 순차적으로 읽고 쓰는 작업에 중점을 둡니다. 이런 경우에는 raw 파티션을 사용하여 파일 시스템의 오버헤드 없이 디스크에서 데이터를 직접 읽고 쓸 수 있어, 데이터 전송의 지연 시간을 줄일 수 있습니다.
클러스터 파일 시스템: 클러스터 파일 시스템은 여러 노드가 동시에 디스크를 공유할 수 있도록 설계되었습니다. 이런 경우에도 raw 파티션을 사용하여, 각 노드가 디스크에 직접 액세스하고, 파일 시스템의 복잡성을 제거할 수 있습니다.
그러나 이러한 이점에도 불구하고, 대부분의 일반적인 응용 프로그램은 파일 시스템의 고급 기능(예: 파일 보호, 공간 할당, 디렉토리 관리 등)을 활용하는 것이 더 효율적일 수 있습니다. 이러한 기능들은 파일의 조직화, 접근 제어, 안정성, 데이터 무결성 등을 쉽게 관리할 수 있도록 도와주기 때문입니다.
페이지 교체 알고리즘과 관련된 메모리 관리의 복잡성을 설명하고 있습니다. 여기서 예로 든 시나리오는 모든 메모리 참조 명령이 단 하나의 메모리 주소만을 참조할 수 있는 시스템입니다.
이러한 경우, 각 프로세스에는 최소한 세 개의 프레임이 필요하다는 것을 알 수 있습니다. 하나는 명령을 위한 프레임, 또 하나는 메모리 참조를 위한 프레임이며, 마지막으로 간접 주소 지정을 위한 프레임이 필요합니다.
간접 주소 지정이란, 한 명령이 메모리의 특정 위치를 가리키는데, 그 위치에는 실제 데이터가 아니라 다른 메모리 위치를 가리키는 주소가 들어있는 방식을 말합니다. 예를 들어, 프레임 16에 있는 명령은 프레임 0을 참조하며, 프레임 0은 실제 데이터가 있는 프레임 23을 간접적으로 가리키는 것입니다.
이런 상황에서 프로세스가 두 개의 프레임만 가지고 있다면 문제가 발생할 수 있습니다. 명령을 수행하는데 필요한 프레임 하나와 메모리 참조를 위한 프레임 하나가 이미 모두 차지되었기 때문에, 간접 주소 참조를 위한 추가적인 프레임이 필요하게 됩니다. 하지만, 이미 사용 가능한 모든 프레임이 소진되었기 때문에 이를 제공할 수 없는 상황이 발생하게 됩니다. 따라서, 각 프로세스에는 최소한 세 개의 프레임이 필요하다는 결론에 이르게 됩니다.
세그멘테이션(segmentation)과 페이징(paging)은 둘 다 메모리 관리를 위한 방법입니다. 각 방법은 메모리를 효율적으로 관리하고 프로세스 사이에 공유하는 방법에 차이점이 있습니다.
세그멘테이션은 메모리를 가변 크기의 블록으로 나누는 방법입니다. 이러한 블록은 세그먼트라고 불리며, 각 세그먼트는 프로그램의 로직에 따라서 별도로 관리됩니다. 예를 들어, 하나의 세그먼트는 함수를, 또 다른 세그먼트는 배열을, 또 다른 세그먼트는 스택을 관리할 수 있습니다. 세그멘테이션의 주요 장점 중 하나는 각 세그먼트가 메모리 내의 다른 위치에 자유롭게 배치될 수 있어 메모리를 유연하게 활용할 수 있다는 것입니다. 그러나 세그멘테이션으로 인해 발생하는 주요 단점은 메모리 단편화가 발생할 수 있다는 것입니다.
페이징은 메모리를 고정된 크기의 블록, 즉 페이지로 나누는 방법입니다. 각 페이지는 메모리의 연속적인 영역을 차지하며, 페이지 크기는 시스템에 따라 달라집니다. 프로세스는 이러한 페이지들의 집합으로 구성되며, 페이지는 메모리 내의 임의의 위치에 배치될 수 있습니다. 페이징의 주요 장점은 메모리를 효율적으로 관리하고 외부 단편화를 방지할 수 있다는 것입니다. 그러나 페이징의 단점은 내부 단편화가 발생할 수 있으며, 이는 페이지의 마지막 부분이 사용되지 않을 때 발생합니다.
따라서 세그멘테이션과 페이징은 각각 장단점이 있으며, 그 사용은 시스템의 특정 요구사항과 관련이 있습니다.
압축기술로 데이터를 압축해 더 적은 프레임만 사용하는 기술.
압축과 해제에 비용이 들기 때문에 보통 메모리가 부족한 경우에 사용한다.
하지만 메모리 압축이 SSD를 사용한 페이징보다도 빠르다는 테스트 결과도 있다.
모바일은 페이징을 지원하지 않아 메모리 압축을 많이 쓴다.
운영 체제의 커널 코드와 데이터는 일반적으로 메모리의 특정 영역에 영구적으로 위치합니다. 이 영역을 커널 공간이라고 하며, 이는 보통 페이징에서 제외됩니다. 이는 다음과 같은 이유 때문입니다:
효율성: 커널 코드와 데이터는 시스템의 모든 작업에 중요한 역할을 합니다. 이들을 페이징 아웃하면 다시 필요할 때 디스크에서 로드해야 하는데, 이는 시간이 많이 소요되는 작업입니다. 따라서 성능을 최적화하기 위해 커널은 보통 메모리에 상주합니다.
무결성과 안정성: 커널은 시스템의 안정성과 보안을 유지하는데 필요한 중요한 작업들을 처리합니다. 커널 데이터가 페이징 아웃되면, 이 데이터가 변경되거나 손상될 가능성이 있고, 이는 시스템의 안정성을 저해할 수 있습니다.
페이징 코드 실행: 페이징 자체는 커널에 의해 수행되는 작업이므로, 페이징 코드를 메모리에서 페이징 아웃하면 페이징 작업을 수행할 수 없게 됩니다. 즉, 커널 코드와 데이터는 페이징 메커니즘을 실행하는 데 필요하기 때문에 메모리에 항상 상주해야 합니다.
이러한 이유로 커널은 보통 '메모리에 상주(resident in memory)'하는 것으로 간주되며, 페이징에서 제외됩니다.
Buddy System
버디는 물리적으로 연속된 페이지들로 이루어진 세그먼트를 나누어 할당하는 시스템이다.
버디의 장점은 서로 인접한 버디들이 쉽게 합쳐질 수 있다는 점이다. (합병)
단점은 버디가 2의 거듭제곱 단위라 내부 단편화가 있다는 점이다.
Slab Allocation
슬랩을 쓰는 이유?
페이징 시스템이 효과적으로 실행되려면 페이지 교체 알고리즘과 할당 정책이 매우 중요하다.
이 두가지 외에도 고려할만한 사항들을 알아보자.
페이지를 올릴 때 그 페이지가 포함돼있는 working set의 모든 페이지를 함께 올린다.
페이지 테이블의 크기를 고려하면 큰 페이지가 좋다.
반면 메모리 사용 효율을 보면 작은 페이지가 좋다.
또한 작은 페이지는 정밀도가 좋아 지역성이 향상되어 I/O가 줄어든다.
하지만 또 이는 많은 페이지 폴트를 야기한다. (페이지 폴트는 오버헤드가 있다.)
시대적으로는 페이지의 크기가 점차 커지고 있다.
연관 메모리 : TLB에 쓰이는 특수 하드웨어(비쌈)
TLB reach : TLB가 커버하는 메모리 공간 크기. 프로세스의 워킹 셋을 담을 수 있으면 베스트다.
거대 페이지
연속비트
페이지가 메모리에 없을 때는 페이지가 디스크 어디에 있는지 알 수 없다.
-> 프로세스마다 확장된 페이지 테이블 필요.
이 테이블은 페이지 폴트 시에만 읽어 기존 페이지 테이블이랑은 차이가 있다.
사용자가 요구 페이징의 특성을 이해하면 성능을 크게 개선할 수도 있다.
또한 컴파일러와 로더도 페이징에 큰 영향을 미칠 수 있다.
읽기 전용 코드
각 루틴이 한 페이지 내에 들더가도록 묶으면 좋다.
이 문단의 내용은 페이지 교체가 발생할 때 생기는 문제와 이를 해결하기 위한 방법에 대해 설명하고 있습니다.
만약 I/O 작업을 위해 메모리를 할당받은 프로세스가 CPU를 다른 프로세스에게 양보하고, 그 사이에 페이지 폴트가 발생해 이 프로세스의 메모리가 다른 프로세스에게 할당되면 문제가 생깁니다. 이후 I/O 작업이 이루어질 때, 원래 할당받았던 메모리가 아닌, 현재 다른 프로세스에게 할당된 메모리에 I/O 작업이 이루어지게 됩니다.
이런 문제를 해결하기 위한 방법 중 하나는 사용자 메모리가 아닌 시스템 메모리로만 I/O 작업을 수행하는 것입니다. 즉, I/O 작업이 필요할 때마다 데이터를 사용자 메모리에서 시스템 메모리로 복사하고, 그 시스템 메모리에서 I/O 작업을 수행합니다. 하지만 이 방법은 데이터를 복사하는 데에 부담이 될 수 있습니다.
"시스템 메모리"라는 표현은 운영 체제가 관리하는 메모리를 일반적으로 가리킵니다. 이는 사용자 프로세스나 애플리케이션에 의해 사용되는 메모리(사용자 메모리)와는 별개로, 운영 체제 자체의 작업을 위해 사용되는 메모리를 의미합니다.
예를 들어, I/O 작업이나 인터럽트 처리, 커널이 실행되는 공간 등이 이에 해당합니다. 때로는 이 시스템 메모리는 물리적으로 보호되거나, 사용자 애플리케이션에 의해 직접 접근할 수 없도록 제한될 수 있습니다.
따라서 위에서 언급된 "시스템 메모리로만 I/O 작업을 수행하는 것"이란, 사용자 프로세스의 메모리 공간이 아닌 운영 체제가 관리하는 메모리 공간에서 I/O 작업을 수행하는 것을 의미합니다. 이를 통해 페이지 교체와 같은 문제를 예방할 수 있습니다.
Linux는 수요 페이징과 LRU 근사 클럭 알고리즘을 사용하는 전역 페이지 교체 정책을 사용하여 가상 메모리를 관리합니다. 이를 위해 Linux는 사용 중인 페이지를 포함하는 활성 목록과 재사용 가능한 페이지를 포함하는 비활성 목록, 이 두 가지 페이지 목록을 유지합니다. kswapd라는 데몬 프로세스가 주기적으로 깨어나 시스템의 남은 메모리를 확인하고, 만약 남은 메모리가 특정 임계값 이하로 떨어지면 비활성 목록에서 페이지를 회수하여 다시 사용할 수 있도록 합니다.
Windows 10은 클러스터링을 사용한 수요 페이징으로 가상 메모리를 구현하고, 이를 통해 메모리 참조의 지역성을 인식하고 페이지 결함을 처리합니다. Windows 10에서는 "작업 집합 관리"라는 가상 메모리 관리의 핵심 요소를 사용하여 프로세스에 최소와 최대 페이지를 할당합니다. 페이지 교체 시, Windows는 지역 및 전역 페이지 교체 정책과 함께 LRU 근사 클럭 알고리즘을 사용합니다.
Solaris는 페이지 결함이 발생할 때 커널이 유지하는 무료 페이지 목록에서 페이지를 할당합니다. 이 때문에 항상 충분한 양의 무료 메모리를 유지하는 것이 중요합니다. 또한 Solaris는 페이지를 스캔하는 속도를 제어하는 pageout 알고리즘을 사용하며, 이는 스캔률(scanrate)과는 무관하게 두 개의 시곗바늘을 이용해 페이지를 스캔합니다. 또한, 시스템이 minfree(최소한의 무료 메모리)를 유지할 수 없게 되면, 새 페이지 요청마다 pageout 과정이 호출됩니다.