09:54 입실
정글 최대 위기, 핀토스 가상 메모리 😱😱😱😱😱
가상 메모리 공간에 page를 생성하고 초기화 함수를 설정한다.
실제 초기화 되는 건 아님.
이때 page타입(익명, 파일)에 따라 초기화할 함수 종류가 달라진다.
이 함수가 호출되면 초기화되지 않은 페이지가 먼저 세팅되고,
실제로 필요할 때 초기화가 되면서 프레임이 연결된다.
초기화되지 않은 페이지 구조체 초기 설정 제공.
익명 페이지를 초기화함.
익명 페이지를 초기화하는 함수. 실제 물리 메모리에 적재될 때 초기화 시킴.
프로세스 등이 실행되면 디스크의 파일을 페이지와 연결함.
페이지는 생성되지만 데이터가 로드되는 건 아님.
페이지 폴트가 발생하면, 이 함수가 동작하면서 load_segment에서 생성된 페이지를 프레임과 할당하고, 디스크의 데이터를 물리 메모리로 적재함.
auxiliary: 보조자
자동차에 꽂는 aux단자도 이거였네..?
가상 주소 공간을 할당받는 다는 의미는 물리 메모리 공간을 즉시 할당받는 것이 아님.
해당 가상 주소의 특정 범위에 접근할 수 있는 권한과 구조를 구조체로 설정하는 것.
실제 프레임이 연결되는 건 아님.
이 페이지 구조체는 가상 주소의 상태를 추적한다.
bool (*page_initializer)(struct page *, enum vm_type, void *);
page_initializer라는 함수 포인터를 선언
이때 매개 변수로 3개를 받음.
https://www.youtube.com/watch?v=hWnLq-dg7B4&t=446s
bool
vm_alloc_page_with_initializer (enum vm_type type, void *upage, bool writable,
vm_initializer *init, void *aux) {
ASSERT (VM_TYPE(type) != VM_UNINIT)
struct supplemental_page_table *spt = &thread_current ()->spt;
/* Check wheter the upage is already occupied or not. */
if (spt_find_page (spt, upage) == NULL) {
/* TODO: Create the page, fetch the initialier according to the VM type,
* TODO: and then create "uninit" page struct by calling uninit_new. You
* TODO: should modify the field after calling the uninit_new. */
struct page *p = (struct page *)malloc(sizeof(struct page));
bool (*page_initializer)(struct page *, enum vm_type, void *);
switch (VM_TYPE(type))
{
case VM_ANON:
page_initializer = anon_initializer;
break;
case VM_FILE:
page_initializer = file_backed_initializer;
break;
}
uninit_new(p, upage, init, type, aux, page_initializer);
p->writable = writable;
/* TODO: Insert the page into the spt. */
return spt_insert_page(spt, p);
}
err:
return false;
}
함수 포인터를 쓰면 변수에 함수를 할당하고, 조금 더 깔끔하게 코드를 작성할 수 있었음.
기존에 if문으로 처리하던 걸 함수 포인터와 스위치 조합으로 좀더 깔끔하게 씀.
You
스택 성장을 해당 스택에 추가로 페이지를 할당해서 가상 주소 공간을 확보하는 작업으로 이해할 수 있어?
ChatGPT
네, 맞습니다. 스택 성장(stack growth)은 프로세스의 스택에 추가로 페이지를 할당하여 가상 주소 공간을 확장하는 작업으로 이해할 수 있습니다. 이 과정은 프로세스 실행 중에 스택이 필요로 하는 메모리가 현재 할당된 스택의 크기를 초과할 때 발생합니다. 스택 성장의 주요 단계는 다음과 같습니다:
스택 메모리 요구 감지: 프로세스가 로컬 변수 할당, 함수 호출 등으로 더 많은 스택 메모리를 요구할 때, 현재 할당된 스택 영역이 이를 수용할 수 없는 경우가 발생합니다.
가상 주소 공간의 확장: 이때 운영 체제는 프로세스의 스택 영역을 확장하기 위해 새로운 가상 주소 공간에 페이지를 할당합니다. 이 페이지는 프로세스의 가상 주소 공간 내에서 스택의 끝 부분에 추가됩니다.
페이지 테이블 업데이트: 새로 할당된 페이지는 페이지 테이블에 등록되어, 가상 주소가 해당 페이지를 참조하게 됩니다.
물리 메모리 할당(선택적): 실제로 해당 가상 주소에 접근하는 순간(예: 페이지 폴트 발생 시) 운영 체제는 물리 메모리 내에 해당 페이지에 대응하는 프레임을 할당하고, 가상 주소와 물리 주소를 매핑합니다.
이렇게 스택 성장은 프로세스가 더 많은 스택 메모리를 요구할 때 자동으로 발생하며, 운영 체제는 가상 주소 공간을 관리하여 프로세스의 메모리 요구 사항을 충족시킵니다.
load_segment는 디스크의 파일을 가상 메모리의 페이지에 할당한다. 이때 실제 물리 메모리에 데이터가 올라오는 건 아님. 즉, 페이지를 생성하고 디스크와 연결하는 작업을 함.
load_segment 호출되면 내부에서 vm에 page를 할당하는데, 이때 초기화 함수로 lazy_load_segment가 넘어간다. 즉, lazy_load_segment는 페이지 폴트가 발생하면 실제로 디스크에서 데이터를 물리 메모리의 프레임으로 적재하고, 페이지와 프레임을 연결하는 작업을 함.
일반적으로 가상 메모리 시스템에서 커널 영역의 코드는 스왑 아웃되지 않습니다. 커널 영역과 관련된 몇 가지 중요한 사항을 설명하겠습니다:
커널 영역의 중요성:
커널 영역은 운영 체제의 핵심 부분을 포함하고 있으며, 시스템의 기본적인 서비스와 자원 관리 기능을 수행합니다.
이 영역은 시스템의 안정성과 보안을 위해 사용자 영역과 구분되며, 항상 빠르게 접근 가능해야 합니다.
스왑 아웃의 제한:
커널 코드나 중요한 커널 데이터 구조는 메모리에 상주해야 하는 중요한 구성요소입니다. 이들은 시스템 운영에 필수적이기 때문에 스왑 아웃되지 않습니다.
스왑 아웃은 주로 사용자 프로세스의 메모리 영역(예: 사용자 프로그램의 코드, 데이터, 힙, 스택)에 적용됩니다.
시스템의 안정성 및 성능 유지:
커널 영역의 코드와 데이터는 시스템의 안정적인 운영을 위해 언제나 빠르게 접근 가능해야 하며, 디스크로 스왑 아웃될 경우 시스템의 성능과 반응 시간에 부정적인 영향을 미칠 수 있습니다.
메모리 관리의 최적화:
가상 메모리 시스템은 사용자 영역의 메모리 사용을 최적화하는 데 중점을 두며, 필요에 따라 사용자 프로세스의 메모리 페이지를 스왑 영역으로 이동시킵니다.
결론적으로, 커널 영역의 코드와 중요한 데이터는 시스템의 핵심적인 부분으로, 메모리에서 항상 접근 가능한 상태를 유지하며 스왑 아웃되지 않습니다. 이는 시스템의 안정성과 성능을 보장하기 위한 중요한 설계 결정입니다.
커널 코드, 데이터, 스택은 메인 메모리에 상주한다.
더불어 페이지 디렉토리, 페이지 테이블도 메모리에 상주되며 일반적으로 스왑아웃 되지 않는다.
이 외에 중요한 시스템 테이블(프로세스, 테이블, 파일 디스크립터 테이블, 인터럽트 벡터 테이블), 디바이스 드라이버 등도 메인 메모리에 상주한다.
가상 메모리를 처음 부터 너무 많은 주소 공간을 할당하는 건 낭비다.
그래서 최소한의 공간을 할당하고, 만약 가상 주소 공간이 부족해지면 stack growth를 하는데, 이건 내부적으로 익명 페이지를 할당하는 개념이다!
가상 주소 공간을 추가함으로서 가상 메모리의 전체 영역을 늘리는 효과이다.
프로세스가 실행되면 가상 메모리 공간이 텍스트, 데이터(초기화, 비초기화), 스택, 힙 세그먼트로 나뉜다.
이때 파일도 여러 세그먼트로 나뉘며, 파일의 세그먼트가 가상 메모리의 세그먼트에 맞게 배치되는 것!
초기화되지 않고 선언만 된 변수는 bss 영역에 적재되는데 이곳은 일반적으로 일괄 0 등으로 초기화가 된 상태임.
그래서 다음의 static으로 선언된 변수는 bss영역에 적재되므로, 선언만 하고 초기화 하지 않아도 0으로 초기화된 상태이고, 출력하면 0이 출력됨.
#include <stdio.h>
static int a; // bss 영역에 적재
int main()
{
printf("%d", a); // 0이 출력됨
return 0;
}
초기화되지 않은 변수는 값을 저장하지 않고 해당 변수 존재 여부만 저장하면 되니까 실행 파일의 크기가 줄어드는 효과가 있음.
지역 변수로 선언된 변수는 bss가 아니라 스택에 할당되므로, 0으로 초기화되지 않고 이전의 쓰레기 값이 출력되는 점 참고하기!
claim은 실제로 페이지에 프레임을 매핑하는 과정이다.
claim함수는 가상주소에 해당하는 페이지를 찾아 do_claim을 호출하고, do_claim은 프레임에 데이터를 적재하고 페이지와 매핑한다.
자, 드디어.. 전체 흐름과 함수의 그림이 대충 그려졌다..