크래프톤정글/PintOS

크래프톤정글 8주차; WIL - PintOS Project 2 User Program

jamie-lee 2023. 1. 21. 19:23
  • 개인적으로 공부하면서 지속적으로 정보를 추가, 수정, 삭제합니다.
  • 정확하지 않은 부분 피드백 주시면 감사합니다.
  • 노란색 하이라이트는 블로그 주인의 생각 + 개인적으로 이해가 더 필요한 부분을 표시한 것입니다. 특별히 더 중요한 개념으로 표시한 것이 아닙니다.
  • TIL/WIL 노트의 일부를 정리해서 적합한 카테고리의 노트 항목에 추가합니다.

2022-12-26, 2022-12-27

C언어 - strtok_r() 함수 사용

참고: https://www.it-note.kr/86

왜?

argument passing 구현에서 명령행 인자 parsing을 위해 사용한 함수.

어떻게?

C언어 문법 & 개념 정리 노트에 추가

C언어 - memset 함수 사용법

참고: [C언어/C++] memset 함수 메모리 초기화 (tistory.com)
C언어 문법 & 개념 정리 노트에 추가

왜?

argument passing 구현에서 명령행 인자 parsing을 위해 사용한 함수.

어떻게?

#include <memory.h> 혹은 <string.h>

void* memset(void* ptr, int value, size_t num);

매개변수

ptr: 세팅하고자 하는 메모리의 시작 주소
value: 메모리에 세팅하고자 하는 값
num: 길이. ptr에서 value까지의 바이트 길이.

반환 값

성공하면 첫번째 인자로 들어간 ptr을 반환
실패한다면 NULL을 반환

2022-12-27, 2022-12-28

Pintos Project 2 System Call 카이스트 강의

참고: https://www.youtube.com/watch?v=sBFJwVeAwEk

왜?

시스템 콜이란?
시스템 콜을 pintos에서 어떻게 구현할 것인가

무엇?

시스템 콜

  1. OS에 의해 제공되는 서비스를 위한 프로그램밍 인터페이스
  2. 유저 모드의 프로그램들이 커널을 이용할 수 있게 해줌
  3. 커널 모드에서 동작(int n 명령 )하며, 유저 모드로 리턴함
  4. 중요한 것은; 유저 모드에서 커널 모드로 넘어갈 때 우선 순위가 스페셜 모드로 올라감 → 시스템 콜을 부르기 위해 하드웨어 인터럽트가 생성되었기 때문

★ 현재 핀토스 문제! syscall handler의 body가 비어있다! → syscall handler의 body를 채우자. 그러기 위해서는 system call을 구현해야 한다!

프로세스 계층 process hierarchy

  1. 부모 프로세스가 누군지, 자식 프로세스가 누군지 특정지어야 한다
  2. siblings를 가리키는 포인터를 가지고 있어야 한다

PintOS Project1 try-error 추가

참고:

무엇?

PintOS Project 1의 void test_max_priority(void) 함수가 제대로 동작하지 않는 에러 해결

왜?

PintOS Project 2하다가 스레드 부분에서 에러 나는 걸 발견해서

어떻게?

  • 되는 코드의 슈도코드
    1. 슬립 리스트가 비어있지 않고,
    2. 현재의 스레드의 우선순위 < 슬립 리스트의 첫번째 원소의 우선순위
    3. 인터럽트 처리중이 아닐 때 (!intr_context())
    4. 위의 조건을 모두 만족할 때 thread_yield()
  • 수정 & 확인 사항
    1. cur 을 thread_current로 바로 받음 (중간에 현재 쓰레드 바뀔까봐 -> 상관 없었다)★
    2. 리스트 엠티 확인
    3. 레디리스트의 첫째를 변수로 저장 안하고 바로 받음 (중간에 레디 리스트 첫째가 바뀔까봐 -> 상관 없었다)★
    4. cmp_priority 함수 사용 안함 (함수로 넘겨주면서 바뀌거나, 등호 때문으로 추측)★★★ 이놈이 원인
    5. begin -> front로 바꿔 써봄 (차이 없었다)
  • 내 코드와의 차이점
    결론부터 말하면, cmp_priority 함수를 사용하지 않고 직접 priority를 비교하는 코드로 수정하여 해결.
    우선순위를 비교함수 안에는 =가 true에 포함되지 않음 → 러닝 스레드가 우선순위가 같은 경우에서 차이가 있었음.
// 수정한 코드 
// P1 Priority: 러닝 스레드와 레디 리스트 첫 스레드의 우선순위와 비교 → 스케줄링

void test_max_priority(void)
{
	struct thread *cur = thread_current();
	struct list_elem *ready_begin = list_begin(&ready_list);

	if (list_empty(&ready_list))
		return;

	if (cur->priority >= list_entry(ready_begin, struct thread, elem)->priority)
		return;

	if (intr_context()) // P2 에러 방지를 위해 추가
		return;

	thread_yield();
}
// 기존 코드 
void test_max_priority(void)
{
struct list_elem *begin = list_begin(&ready_list);

if (list_empty(&ready_list))
	return;

bool cmp_val = cmp_priority(&thread_current()->elem, begin, NULL);

if (cmp_val == true)
	return;

if (!intr_context())
	thread_yield();
}

Syscall from gitbook

참고:

시스템 콜 from gitbook

  1. 시스템 콜 핸들러
    1. 시스템 콜 넘버 받기
    2. 시스템 콜 인수 받기
    3. 적절한 액션 수행하기
  2. 시스템 콜 디테일
    1. OS는 외부 인터럽트로 유저로부터 컨트롤을 되찾을 수 있다; timer와 I/O 기기로부터의 인터럽트
    2. 외부 인터럽트란 CPU 밖의 주체로부터 야기된 인터럽트임
    3. OS는 또한 소프트웨어 예외를 다룸; 프로그램 코드에서 발생하는 이벤트 e.g.; 페이지 폴트, 디비전 바이 제로
    4. 예외는 유저 프로그램이 OS에게 서비스(“system call”)를 요청하는 수단이다
    5. x86-64 아키텍쳐에서, 시스템 콜 핸들러를 호출하는 특별한 명령 syscall을 도입함 (기존에는 다른 소프트웨어 예외와 동일하게 다뤄짐)
  3. syscall 명령
    1. syscall은 시스템 콜을 호출하기 위해 현대에서 가장 흔히 사용되는 수단 (핀토스에서도 마찬가지)
    2. syscall 명령을 호출하기 전, 시스템 콜 넘버, 추가적인 시스템 콜 인수들은 다음 레지스터들에 저장되어야 한다;
      1. %rax는 시스템 콜 넘버
      2. 네 가지 인수는 %r10 (%rcx ❌)
      3. %rdi, %rsi, %rdx, %r10, %r8, and %r9

try-error “Page fault at 0xa: not present error reading page in kernel context.”

참고:

무엇?

접근되는 메모리의 페이지가 현재 물리 메모리에서 사용가능하지 않다. 예를 들어 페이지를 이제 현재 사용하지 않아서 이미 하드 드라이브같은 2차 저장기기로 스왑 아웃 되었거나, 그 페이지가 프로세스에게 아직 할당되지 않았거나.
OS가 커널 모드에서 실행 중일 때(in kernel context), 메모리 페이지에 엑세스하려고 시도하는 동안 오류가 발생했음을 나타냄.

어떻게?

readwrite 함수에서 STDIN, STDOUT의 경우 예외 처리 해주기

try-error exit(status) 테스트 케이스 통과 시 조심 할 점

참고:

무엇?

exit(-888) 처럼 나만 알아볼 수 있는 숫자를 이용하여 디버깅할 수 있음
but 테스트 케이스를 통과하려면 exit(-1)로 다시 고쳐놓아야 PASS 뜨므로 조심할 것.

close-twice 에러 메시지

참고:

무엇?

close-twice 에러 메시지
cur->fdt[fd] = 0 으로 엔트리 초기화를 할 때, f = 0 처럼 무심코 변수를 사용했다가 현기증나는 커널 패닉 🥹
이건 정말 주의해야 하는 실수다.
file을 0으로 만드는 것이 아니라, fdt의 엔트리를 0으로 만드는 것이기 때문에!

FAIL tests/userprog/close-twice
Kernel panic in run: PANIC at ../../filesys/inode.c:327 in inode_allow_write(): assertion `inode->deny_write_cnt > 0' failed.
Call stack: 0x8004217eb4 0x800421fb29 0x800421e980 0x800421e72d 0x800421d345 0x800421cc1a 0x800421c8ff 0x40019d 0x4001fb 0x400c9c
Translation of call stack:
0x0000008004217eb4: debug_panic (lib/kernel/debug.c:32)
0x000000800421fb29: inode_allow_write (filesys/inode.c:328)
0x000000800421e980: file_allow_write (filesys/file.c:144)
0x000000800421e72d: file_close (filesys/file.c:60)
0x000000800421d345: close (userprog/syscall.c:343)
0x000000800421cc1a: syscall_handler (userprog/syscall.c:109)
0x000000800421c8ff: no_sti (userprog/syscall-entry.o:?)
0x000000000040019d: (unknown)
0x00000000004001fb: (unknown)
0x0000000000400c9c: (unknown)

load 세마포어를 추가한 후 발생하는 에러

참고:

무엇?

load 세마포어를 추가한 후 발생하는 에러 // null인 elem이 리스트에 추가된 것 같다?!
아무래도 부모, 차일드 포인터 지정이 잘못 된 듯.

FAIL tests/userprog/args-none
Kernel panic in run: PANIC at ../../lib/kernel/list.c:362 in find_end_of_run(): assertion `a != NULL' failed.
Call stack: 0x8004217eb4 0x8004218b1d 0x8004218fd5 0x800420a146 0x800421b7fd 0x800421b554 0x800420744a
Translation of call stack:
0x0000008004217eb4: debug_panic (lib/kernel/debug.c:32)
0x0000008004218b1d: find_end_of_run (lib/kernel/list.c:363)
0x0000008004218fd5: list_sort (lib/kernel/list.c:427)
0x000000800420a146: sema_up (threads/synch.c:125)
0x000000800421b7fd: process_exec (userprog/process.c:220)
0x000000800421b554: initd (userprog/process.c:95)
0x000000800420744a: kernel_thread (threads/thread.c:449)

exec() 구현할 때 process_create_initd를 사용하면 어려운 이유

참고:

왜?

카이스트 핀토스 강의 & pdf에서 process_create_initd()를 사용하라고 언급하는데 아마 업데이트가 안된 듯 하다. 해당 함수를 사용하면 불편하다.

어떻게?

exec()을 구현할 때 process_exec() 함수를 이용하는 것이 편하다.
처음에는 process_create_initd 를 이용하여 구현했다가 바꾸었다.
이유는, exec-misssing 같은 테케를 통과하기 위해서 return -1을 받아야 하는 경우가 있는데,
process_create_initd -> initd -> process_exec로 이어질 때,
process_exec에서 return -1을 하면 initd가 바로 exit(-1) or 커널 패닉을 반환하면서 스레드가 종료되기 때문에,
exec() 함수가 아무것도 반환하지 못한다.
따라서 process_exec 을 사용하는 것이 편하다.