[PintOS] Project 2 : User Program – 구현하다 생긴 의문들, 그리고 그 답을 따라가며

CorinBeom·2025년 5월 26일

PintOS

목록 보기
7/19
post-thumbnail

Project 2 유저 프로그램까지 구현하며 100퍼센트 이해했냐고 물어본다면 대답을 못할 것 같다.
그렇기에 구현 중에 느낀 이건 왜 이렇게 쓰였을까? 얘는 용도가 뭘까 생각이 난 부분들을
짧게라도 정리하면 좋을 것 같아서 작성하는 글이다.


process_create_initd에서 file_name을 스레드 이름으로 사용하는 이유

/* 이 코드를 넣어줘야 thread_name이 file name이 됩니다 */
char *save_ptr;
strtok_r(file_name, " ", &save_ptr);

이런 부분에서 file_name에 어떤 것이 들어가는거지? 라는 궁금증이 생겼다.
그래서 알아보니...

이 코드는 결국 thread_create()에 전달될 스레드 이름을 순수 파일 이름으로 설정하려는 목적.

이라고 한다.
이렇게만 이야기하면 헷갈릴 수 있으니 왜 필요한지 부터 알아보자

왜 필요할까?

thread_create()의 첫 번째 인자는 스레드 이름임.

이 이름은 thread_name() 등으로 출력될 때 사용. 예를 들어

printf("exit: %s: %d", thread_name(), exit_status); 에서.

근데 process_execute()나 process_create_initd()에 전달되는 file_name은 "echo hello world"처럼 인자까지 포함된 문자열.

그래서 "echo"만 따로 떼서 스레드 이름으로 지정.

결론

스레드 이름을 명확하게 실행 파일 이름으로 지정해 주기 위한 절차. 디버깅 메시지, 로그, thread_name() 호출 시 가독성과 추적성이 올라감.

결국 thread의 이름이 무슨 명령어인지 알려주는 이름이라고 쉽게 생각하면 좋을 것 같다.


thread 구조체 내에 각 sema 필드에 대한 설명

✅ 1. fork_sema부모와 자식 간의 초기 fork 동기화

✔ 목적:

  • 자식 프로세스가 초기화(__do_fork)를 마칠 때까지 부모가 대기하도록 함.

✔ 사용 위치:

  • 부모: process_fork()에서 sema_down(&parent->fork_sema);
  • 자식: __do_fork() 끝에서 sema_up(&parent->fork_sema);

✔ 시나리오:

// 부모 thread
tid = thread_create(...);         // 자식 스레드 시작
sema_down(&fork_sema);            // 자식 초기화 기다림

// 자식 thread (__do_fork 내부)
... fork 준비 ...
sema_up(&parent->fork_sema);      // 부모에게 "준비됨" 신호

✅ 2. wait_sema부모가 자식 종료를 기다릴 때 사용 (process_wait)

✔ 목적:

  • 부모가 특정 자식의 종료를 기다릴 때, 자식이 종료할 때까지 block 상태로 대기.

✔ 사용 위치:

  • 부모: process_wait()에서 sema_down(&child->wait_sema);
  • 자식: process_exit()에서 sema_up(&cur->wait_sema);

✔ 시나리오:

// 부모
exit_status = process_wait(child_tid); // 자식 종료까지 대기

// 자식
process_exit() {
	sema_up(&wait_sema);   // 부모를 깨움
}

✅ 3. exit_sema부모가 자식 리소스를 정리할 시간을 기다림

✔ 목적:

  • 자식이 exit할 때, 부모가 자식의 정보를 수거할 때까지 자식이 잠시 대기.
  • 좀비 프로세스 방지 역할과 유사한 개념.

✔ 사용 위치:

  • 자식: process_exit()에서 sema_down(&exit_sema); → 부모가 수거할 때까지 대기
  • 부모: process_wait()에서 sema_up(&child->exit_sema); → 자식 종료 후 정리 완료됨을 알림

✔ 시나리오:

// 자식
process_exit() {
	... 리소스 정리 준비 ...
	sema_down(&exit_sema);  // 부모가 수거할 때까지 대기
}

// 부모
process_wait(child_tid) {
	... 자식 상태 수거 완료 ...
	sema_up(&child->exit_sema);  // 자식에게 "끝났어" 알림
}

✅ 이 구조는 PintOS가 부모-자식 간 리소스를 깔끔하게 정리하고, race condition 없이 안정적으로 종료 처리를 하게 만든 설계.

                                                전체 흐름 요약


✅ 1. palloc_get_multiple() — 페이지 여러 개를 할당할 때 사용

✔ 목적:

  • 여러 페이지가 연속적으로 필요한 경우 (예: FDT, argument stack 등)를 위해 물리 메모리를 여러 개 한꺼번에 할당.

  • 일반적인 malloc()은 PintOS 커널에서는 사용할 수 없고, 페이지 단위 할당이 필요한 구조에서 사용.

✔ 사용 위치:

  • process_init() 에서 FDT 할당 시:
curr->FDT = palloc_get_multiple(PAL_ZERO, FDT_PAGES);
  • argument_stack()에서 사용자 인자 저장용 스택 설정 시 사용 가능.

  • 스레드 이름 복사나 ELF 로더 등에서도 일부 페이지 단위로 할당 필요 시 사용.

✔ 시나리오:

// FDT 2페이지 할당
#define FDT_PAGES 2
thread->FDT = palloc_get_multiple(PAL_ZERO, FDT_PAGES);
  • PAL_ZERO 플래그는 할당된 메모리를 0으로 초기화함.

  • 반환값은 페이지의 시작 주소이며, 연속된 페이지 메모리 영역이 확보됨.


✅ 2. palloc_free_multiple() — 연속된 여러 페이지를 해제할 때 사용

✔ 목적:

  • palloc_get_multiple()로 할당한 연속된 페이지를 해제.

  • FDT, 복사된 인자 메모리, 기타 여러 페이지를 사용하는 커널 자원을 반납.

✔ 사용 위치:

  • process_exit() 에서 FDT 반환 시:
palloc_free_multiple(cur->FDT, FDT_PAGES);

✔ 주의사항:

  • pg_ofs(addr) == 0, 즉 페이지 경계에 맞춰진 주소여야 함.

  • page_cnt 는 할당할 때 사용한 것과 동일하게 지정해야 메모리 누수나 패닉 방지.

✔ 시나리오:

// FDT 반납
palloc_free_multiple(thread->FDT, FDT_PAGES);

✅ 3. palloc_get_page() / palloc_free_page() — 단일 페이지 할당/해제

✔ 목적:

  • 단일 페이지가 필요할 경우 사용, 예: file_name 복사용, 임시 메모리, ELF 로더용 버퍼 등.

  • palloc_get_multiple()page_cnt = 1 인 버전이라고 생각하면 됨.

✔ 사용 위치:

  • process_create_initd()에서 file_name 복사할 때:
fn_copy = palloc_get_page(PAL_ZERO);

종료 시:

palloc_free_page(fn_copy);

✔ 시나리오:

// 실행 파일 이름 복사용 1 페이지 할당
char *fn_copy = palloc_get_page(PAL_ZERO);

// 해제
palloc_free_page(fn_copy);

✅ 이 구조는 PintOS가 커널 영역에서 malloc 없이 페이지 단위의 정적 메모리 관리 방식으로 각 자원(FDT, 파일명 복사 등)을 안전하게 할당/회수하도록 하기 위한 설계.
메모리 누수를 막고, 가상 메모리 확장 전까지는 매우 중요한 메모리 관리 수단임.
메모리 누수를 똑바로 못잡으면 마지막 테스트인 multi-oom 통과가 힘들 수 있음

profile
Before Sunrise

0개의 댓글