[운체] 오늘의 삽질 - 0724

방법이있지·2025년 7월 24일
post-thumbnail

아주 안 간단한 wait

아래 그림보단 훨씬 많은 걸 해야 하는데ㅣ... 이것도 조만간 글로 올리던가 해야지

사용자 vs 커널 가상주소

  • 가상 메모리는 페이지, 물리 주소 메모리는 프레임 단위로 구분

    • 핀토스 기준 약 4KB(PGSIZE = 4096 바이트)
    • 페이지 테이블은, 페이지와 프레임을 매핑하는 역할을 함

커널 가상주소

  • 커널 모드에서 사용하는 가상주소
  • 핀토스 기준 KERN_BASE = 0x8004000000 이상 주소
  • 물리 주소와 1:1 대응됨 (핀토스 기준 커널 가상주소에서 KERN_BASE를 빼면 물리 주소)
    • 직접적인 물리 주소 접근은 불가능
    • 커널은 커널 가상주소를 통해 프레임에 접근
  • 커널의 palloc_get_page 함수는, 실제 물리 주소가 아닌 페이지의 커널 가상주소를 반환
    • 물리 주소와 매핑되어 있으므로, 바로 프레임에 접근 가능

사용자 가상주소

  • 사용자 모드에서 사용하는 가상주소
  • 핀토스 기준 KERN_BASE = 0x8004000000 미만 주소
  • 사용자 프로세스는, 페이지 테이블(핀토스 기준 pml4 테이블)을 이용해 사용자 가상주소를 물리 주소로 변환해 프레임에 접근

커널 내 페이지 할당

  • palloc_get_page(): 물리 프레임 할당 후, 해당 물리 프레임의 물리 주소에 대응되는 커널 가상주소를 반환
    • 커널 내부에서만 호출되는 함수.
  • PAL_USER 플래그가 설정된 경우에는 user pool에서, 설정되지 않았으면 kernel pool 에서 할당함
    • user pool: 사용자 프로세스용 물리 페이지의 집합
    • kernel pool: 커널 내부에서 사용하는 물리 페이지의 집합
  • user pool에서 할당해도 커널 가상주소가 반환됨에 유의
    • 사용자 프로그램에서 이 페이지를 사용하려면, 이후에 볼 pml4_set_page를 통해 사용자 가상주소와 매핑해야 함.
  • cf. 커널에서 지원하는 malloc,calloc, realloc 은 무조건 kernel pool의 주소를 반환.

PML4 테이블

  • 사용자 가상주소를 물리주소에 매핑하는 역할
    • 하지만 핀토스에선 물리주소 대신 커널 가상 주소를 사용하므로
    • 사용자 가상 주소와, 커널 가상 주소를 매핑한다고도 볼 수 있음

  • 4단계로 이루어진 페이지 테이블인데, 중간과정보다는 변환을 한다는 점을 이해할 것

  • pml4_set_page(pml4, upage, kpage, rw)

    • 사용자 가상주소(upage)에 커널 가상주소 (kpage)를 읽기/쓰기 권한(rw)과 함께 매핑
    • rwtrue면 읽기/쓰기, false면 읽기 전용
  • pml4_get_page(pml4, uaddr)

    • PML4 테이블(pml4)에서 사용자 가상주소(uaddr)에 매핑된 물리 프레임이, 페이지 테이블에 존재하는지 확인
    • 존재하면, 해당 물리 프레임에 대응되는 커널 가상주소를 반환
    • 존재하지 않으면, NULL을 반환

핀토스 사용 예제

(1) 사용자 가상주소가 물리 메모리에 매핑되어 있는지 확인하기

pml4_get_page(thread_current() -> pml4, p) == NULL
  • thread_current() -> pml4: 현재 프로세스의 PML4 테이블
  • p: 사용자 가상주소
  • p가 물리 메모리에 매핑되어 있으면, 해당 물리 프레임에 대응되는 커널 가상 주소를 반환 -> 조건문 거짓
  • p가 매핑되어 있지 않으면 NULL을 반환 -> 조건문 참

(2) 페이지 테이블 엔트리 복사하기

  • fork 시스템 콜 구현 시, 부모의 페이지 테이블 내 엔트리를 자식의 페이지 테이블로 복사해야 함
// userprog/process.c
static bool duplicate_pte (uint64_t *pte, void *va, void *aux) {
	struct thread *current = thread_current ();
	struct thread *parent = (struct thread *) aux;
	void *parent_page;
	void *new_page;
	bool writable;

    // 계속
}
  • pte: 부모 프로세스의 pml4 테이블 내 각 엔트리의 주소
    • 테이블 내 모든 pte에 대해, duplicate_pte() 함수가 호출되어, 자식의 페이지 테이블로 복사하는 역할 수행
  • va: pml4 테이블 내, pte에 대응되는 사용자 가상 주소
    • 페이지 테이블에 저장된 커널 가상 주소를 뜻하는 게 아님에 유의할 것.
  • aux: 부모 쓰레드의 struct thread 포인터가 전달됨

1단계: kernel pool에 속한 프레임인지 검사

// duplicate_pte 내부
/* 1. pte에 저장된 주소가 kernel pool의 프레임을 가리키는 경우, 복사하지 않음. */
if (is_kern_pte(pte)){
return true;
}
[부모 pml4의 pte] -- 주소 --> 물리 프레임
         │
         ▼
  is_kern_pte(pte)?
      ├─ Yes → 복사하지 않고 넘어감 (return true)
      └─ No → 다음 단계 진행
  • PTE가 가리키는 물리 주소가 kernel pool에 속하는 경우, PTE를 복사하지 않아야 함
    • 커널 메모리는 프로세스별 복사 X, 모든 프로세스가 공유
  • 이 경우 true를 반환해 복제 과정을 생략해야 함
  • is_kernel_vaddr(va) 사용하면 안 됨.
    • 얘는 단순히 va의 사용자 주소 / 커널 주소 영역 판단용인데, va는 사용자 주소이므로 무조건 true 반환

2단계: 부모페이지 가져오기

/* 2. 부모의 pml4 테이블에서 va에 해당하는 페이지를 가져와, parent_page에 저장한다. */
parent_page = pml4_get_page (parent->pml4, va);
부모 프로세스 pml4 테이블
┌───────────┐
│ va -> 프레임 주소 ──────► 물리 페이지 (부모 페이지)
└───────────┘

parent_page = pml4_get_page(parent->pml4, va)
(va에 매핑된 물리 페이지를 커널 가상 주소로 변환해 저장)
  • 부모의 pml4 테이블에서, 사용자 가상주소 va에 매핑된 물리 프레임을 찾음
  • 프레임의 주소에 대응되는 커널 가상 주소를 parent_page에 저장함.

3단계: 자식을 위한 페이지 할당

/* 3. 자식 프로세스를 위해 PAL_USER (사용자영역) 옵션이 설정된 새 페이지를 할당하고, new_page에 저장한다 */
new_page = palloc_get_page(PAL_USER);
new_page = palloc_get_page(PAL_USER)

┌───────────────┐
│ 새로 할당된 4KB 페이지 │ <─ user pool에서 할당
└───────────────┘
  • 자식 프로세스에서 사용할 새로운 물리 프레임을 user pool에서 할당
    • 사용자가 쓸 거니까 user pool에서 할당해야 함
  • 프레임의 주소에 대응되는 커널 가상 주소를 new_page에 저장함.

4단계: 내용복사 및 권한확인

/* 4. 부모의 페이지 내용을 new_page에 복사한 뒤, 쓰기가능 여부를 writable 변수에 저장한다. */
memcpy(new_page, parent_page, PGSIZE);
if (is_writable(pte)){
    writable = 1;
    } else {
    writable = 0;
}
memcpy(new_page, parent_page, PGSIZE)
             │
             ▼
  부모 페이지 내용 → 새 페이지에 복사 완료

writable = is_writable(pte)
(읽기/쓰기 권한 확인)

5단계: 자식 페이지테이블에 삽입

  • memcpy를 이용해 현재 parent_page 주소의 내용을 new_page에 4kb(PGSIZE)만큼 복사
  • is_writable(pte)로 해당 pte에 저장된 프레임이 읽기 전용인지 확인 -> writable 변수 설정
/* 5.  자식의 페이지 테이블에 VA 주소로 new_page를 매핑하고, writable 권한과 함께 등록한다. */
if (!pml4_set_page (current->pml4, va, new_page, writable)) {
/* 6. 실패했을 경우 에러 처리를 수행한다. (일단 강제 종료되게 false를 반환) */
return false;
}
return true;
pml4_set_page(current->pml4, va, new_page, writable)

[자식 pml4 테이블]
 va ─────► new_page (writable)

성공 시 true 반환
  • 자식 프로세스의 pml4 테이블(current->pml4)에, 새로 할당한 new_page를 삽입
  • 부모 프로세스와 동일한 사용자 주소 vanew_page가 매핑되도록 함
  • pml4_set_page는 성공하면 true, 실패하면 false를 반환하므로, 이를 통해 에러 핸들링
profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글