Pintos week02 User Programs

개요

  • 기간 : 2023년 4월 27일~ 5월 9일
  • 주제 : System call
  • 과제
    • Argument Passing
    • User Memory
    • System calls
    • Process Termination Messages
    • Denying Writes to Excutables

배경지식

1. INTERRUPT

  • 기본 개념

    • CPU의 명령 수행속도에 비해 I.O(입출력) 연산의 속도는 훨씬 느리다.
    • I.O 연산을 위해 CPU가 쉬면서 기다리는 것은 낭비다.
    • I.O 연산 중에 CPU는 다른 일을 하고 있는다.
    • I.O 연산 결과가 나오면 CPU에게 알린다. =>” interrupt
  • 정의

    • CPU가 프로그램을 실행하고 있을 때, 입출력 하드웨어 등의 장치나 예외상황이 발생하여 처리가 필요한 경우에 마이크로프로세서에서 알려 처리할 수 있도록 하는 것. 하드웨어 interrupt와 소프트웨어 interrupt로 나뉜다.
    • 하드웨어 인터럽트
      • 하드웨어가 발생시킴
      • CPU가 아닌 하드웨어 장치가 CPU에게 알려주가나, 서비스 요청할 경우 발생
    • 소프트웨어 인터럽트
      • 소프트웨어가 발생시킴
      • 사용자 프로그램(소프트웨어)가 스스로 인터럽트 라인을 세팅
      • 종류 : exception(예외), system call(syscall)
    • 두 종류 모두 CPU내의 인터럽트 라인을 세팅하여 인터럽트 발생시킴
    • CPU : 매 명령 수행 전 인터럽트 라인 검사
  • 인터럽트 과정

상황 : 프로세스 A 실행 중 디스크에서 어떤 데이터를 읽어오라는 명령을 받음

  1. 프로세스 A는 systeml call로 인터럽트 발생시킨다.
  2. 명령 받은 CPU는 현재 수행중인 기계어 코드 완료
  3. 수행중이었던 현재 상태를 해당 process의 PCB(Process Control Block)에 저장
  4. PC(Program Counter, IP)에 다음으로 실행할 명령의 주소 저장
  5. 인터럽트 벡터를 읽고 ISR 주소값을 얻어 ISR(Interrupt Service Rountine)로 점프하여 루틴 실행
  6. 해당 코드 실행
  7. 해당 작업 종료시, 레지스터 복원
  8. ISR의 끝에 있는 IRET 명령어에 의해 인터럽트 해제
  9. IRET 명령어 실행 후, 대피시킨 PC값 복원하여 복귀
  • 인터럽트와 특권 명령

명령어의 종류
  • 일반 명령 VS 특권 명령(previleged)
  • 일반 명령 : 모든 프로그램이 수행 가능
  • 특권 명령 : 운영체제(Operation system)만 수행 가능
Kernel Mode VS User Mode
  • kernel mode : 운영체제가 CPU의 제어권을 갖고 명령을 수행. 일반, previleged 명령 모두 가능
  • user mode : 일반 사용자 프로그램이 CPU의 제어권 가짐. 일반 명령만 가능

관련 용어

  • Interrupt handler(Interrupt service routin)

실제 인터럽트를 처리하기 위한 루틴. 운영체제 코드 영역내에 인터럽트별 처리할 내용 프로그래밍 되어있음.

  • Interrupt vector

인터럽트 발생 시 처리해야 할 Interrupt handler의 address를 인터럽트 별로 보관하는 테이블

  • PCB(Process Control Block)

커널의 데이터 영역에 존재. 각 프로세스는 고유의 PCB 가짐, 인터럽트 발생 시 수행중의 조건들 저장

2. Synchronization과 lock

Processor

  • 연산을 처리하는 하드웨어. CPU(Central Processing Unit), GPU(Graphics Processing Unit), DSP(Digital Signal Processor)가 예시.

CPU

  • processor의 종류 중 하나. 우리가 흔히 컴퓨터에서 생각하는 중앙처리장치.

Process와 Thread의 차이

  • Process 내에 여러 개의 Threads 존재 가능
  • Process는 자신만의 오픈 파일, 네트워크 소켓, 스택 등을 가진다.
  • Process는 다른 process의 메모리 공간이나 자원 접근 못함.
  • Thread는 프로세스에 속하는 가상 주소 공간을 다른 thread와 공유
  • Thread는 자신만의 스택이 있지만, 코드, 데이터 세그먼트는 공유한다.

Synchronization

  • 여러 쓰레드들 혹은 여러 프로세스들의 임계 영역 이나 공유자원에 대한 동시적 접근, 실행으로 발생할 수 있는 부작용을 방지하기 위한 동기화
  • 공유 자원의 예시 : 전역 변수, 메모리, dynamic object, DB 등

  • 사용되는 기술에는 Lock, Condition Variable, Barriers 등이 있다.
  • Mutex, Semaphore, Monitor는 lock에 포함되는 개념이다.

Lock

  • 진입하기 전 lock을 얻고, 끝나면 lock을 반환한다.
  • 진입하는 쓰레드가 lock_acquire. 다른 쓰레드들이 접근하지 못하도록 한다.
  • 빠져나오며 쓰레드는 lock_release. 이제 다른 쓰레드들이 접근 가능하다.

Semaphore

  • Lock의 한 종류
  • 공유 자원에 대한 복수의 경쟁자(쓰레드나 프로세스)의 접근을 막는다.
  • sema_up 은 semaphore의 값을 1 증가시킨다. => 현재 공유 자원이 사용 가능함을 나타낸다.
  • sema_down 은 semaphore의 값을 1 감소시킨다. => 공유 자원이 사용되고 있음을 나타낸다.
  • sema_down이 semaphore의 값이 0일 때면, 1이 될때까지 기다렸다가 쓰레드를 호출한다.
  • 공유 자원 사용을 완료한 쓰레드가 sema_up 할 때, 다음 sema_down할 쓰레드를 구체적으로 지정할 수 없다.(Condition variable)

Mutex

  • Lock의 한 종류
  • 공유 자원에 대한 단일 경쟁자의 접근을 막는다.
  • binary semaphore라고도 하며, 0과 1 값만을 가진다(막을 대상이 하나 뿐이라).

Condition variable

  • Lock의 대체제가 아닌 보충하기 위함.
  • 같이 사용하여 더욱 동기화의 효과를 높인다.
  • Lock의 소유 유무 뿐만 아니라 특정 ‘조건’을 추가한 것.

3. Virtual Memory 와 Page

Virtual Memory 정의

  • 물리적으로 존재 하지 않는 메모리를 말하며, OS가 만들어낸 논리적인 형태의 메모리.
  • 가상으로 저장되는 것이 아니라 실제 물리메모리나 하드 디스크에 저장
  • OS가 1를 할당해준다.
  • 크기는 시스템의 bit수에 따라 정해져 있고 무조건적으로 정해즌 크기가 할당된다.

Virtual Memory에 관한 2가지 이슈

  1. Address Translation : 가상 메모리를 물리 메모리에 매핑
  2. 가상 공간에 대한 관리

Paging

  • Virtual Memory와 Physical memory의 매핑을 위한 기법
  • 컴퓨터가 메인 메모리에서 사용하기 위해 데이터를 저장하고 검색하는 메모리 관리 기법
  • 이 기법을 사용함으로써 물리적 메모리는 연속적으로 할당되어 존재할 필요 X
  • 반대로 연속적으로 존재하지 않는 물리적 메모리라도 페이징 기법을 통해 연속적으로 존재하는 것 처럼 이용될 수 있음.
  • 가상 메모리 상의 주소 공간을 일정한 크기로 분할
  • 가상 주소(Virtual Address) v = (p, d)
  • p : 페이지 번호, d : 오프셋(페이지 처음부터 얼마 떨어진 위치인가?)

Page와 Frame

  • 주소공간은 페이지 단위로 나누어지고, 실제 기억공간은 페이지 크기와 같은 프레임으로 나누어 사용
    • 페이지(Page): 가상 메모리를 일정한 크기로 나눈 블록
    • 프레임(Frame) : 물리 메모리를 일정한 크기로 나눈 블록
  • 페이지와 프레임의 크기는 동일하게 관리
  • 페이지가 하나의 프레임을 할당받으면, 물리 메모리에 위치

Page table(Pt)

  • 페이지를 관리하는 자료구조 형태
  • 프로세스의 PCB(Process Control Block)에 Page Table 구조체를 가리키는 주소가 있다.
  • 프로세스의 페이지 정보를 저장(페이지 각각의 번호 별로 가상주소와 그에 해당하는 물리 메모리 주소를 매핑해놓은 정보)
  • 1 프로세스 : 1 페이지 테이블
  • Page table의 Key : Index
  • Page talbe의 Value : 해당 페이지에 할당된 메모리(Frame)의 시작 주소
  • 페이지 테이블은 사용을 위해서 메모리에 존재해야 하지만, 그렇게 되면 메모리 접근에 있어서 중복 호출이 일어난다.
  • 페이지 테이블에 한번, 페이지 테이블을 통한 실제 메모리에 한번 접근하기 때문이다.
  • 따라서 MMU(Memory Management Unit)의 지원을 받아 매핑시키게 된다.

한 프로세스가 특정 가상 주소에 접근한다

  1. 해당 프로세스 PCB에 있는 page table의 base 주소에 해당하는 가상 주소가 포함된 page 번호가 있는지 확인
  2. page 번호가 있으면 이 page가 매핑된 첫 물리 주소(p’)를 알아내면, p’ + d가 실제 물리 주소가 됨
  3. CPU가 가상 주소에 접근할 때 MMU 하드웨어 장치를 통해 물리 메모리에 접근한다.
  4. 프로세스가 맨 처음에 실행될 때, 페이지 테이블에 적재가 되면서 동시에 해당 페이지 테이블 base 주소가 별도 레지스터(CR3)에 저장되는 것.
  5. CPU가 프로세스 실행 위해 접근할 때, MMU가 CR3 레지스터를 가져와서 페이지 테이블 base 주소를 참조하여 물리주소로 변환을 한 다음, 해당 물리주소에 있는 데이터를 CPU에 가져다 준다.

Page table Entry(PTE)

  • 페이지 테이블의 레코드
  • PTE의 각 필드에 기록되는 내용은 다음과 같다.
    • 페이지 기본 주소(Page base address)
    • 플래그 비트(Flag bit)
      • Accessed bit : 페이지에 대한 접근이 있었는지
      • Dirty bit : 페이지의 내용에 변경이 있었는지
      • Present bit : 현재 페이지에 할당된 프레임이 있는지
      • Read/Write bit : 읽기/쓰기에 대한 권한이 있는지

동적 주소 변환

  1. 수행 중인 프로세스가 가상주소를 참조
  2. 페이징 기법을 통해 페이지 p가 페이지 프레임 f에 있음을 알아낸다.
  3. 실주소 r = f(페이지 프레임) + d([^오프셋])을 구해낸다.

[^오프셋]: offset, 배열이나 자료 구조 오브젝트 내의 오프셋은 일반적으로 동일 오브젝트 안에서 오브젝트 처음부터 주어진 요소나 지점까지의 변위차를 나타내는 정수형이다. 이를테면, 문자 A의 배열이 abcdef를 포함한다면 ‘c’문자는 A의 시작점엫서 2의 오프셋을 지닌다고 할 수 있다. 여기서는 페치지의 상대적인 위치를 말한다. 예를 들어, Virtual Page Number 0이 0 ~ 4095의 주소를 가진다면, Page table에 매핑된 Physical Page Number 1로 매핑된다. 페이지의 상대주소인 offset은 변하지 않고 매핑된다. 따라서 가상 주소 4를 참조했었다면, 물리 주소에서는 4096+4 의 위치를 참조하게 되는 것이다.

다중 단계 페이징 시스템

  • 32bit 시스템에서 4KB 페이지를 위한 페이징 시스템 : 오프셋(12bit) + 페이징 번호(20bit)
  • 페이징 정보를 단계를 나누어 페이지 디렉토리 생성

    • 필요없는 페이지는 페이지 테이블을 생성하지 않으면 공간 절약 가능
    • 페이지 디렉토리 중 정말 필요한 영역에 대해서만 페이지 테이블을 생성하는 것이 다중 단계 페이징 시스템.
  • 페이지 번호를 나타내는 bit를 구분해서 단계를 나눔(최근 4단계 : pml4)

4. Virtual Addresses(PintOS)

64비트 가상 주소의 구조

63 ~ 48 : Sign Extend(부호 확장)

47 ~ 39 : Page-Map Level-4 Dffset(9)

38 ~ 30 : Page-Directory Pointer(9)

29 ~ 21 : Page-Directory Offset(9)

20 ~ 12 : Page-Table Offset(9)

11 ~ 0 : Physical Offset(12)

bit mask

  • 비트마스크는 이진수를 사용하는 컴퓨터의 연산 방식을 이용하여, 정수의 이진수 표현을 자료 구조로 쓰는 기법
  • 이진수는 0 또는 1을 이용하므로 하나의 비트(bit)는 두 가지 표현이 가능
  • 비트가 1이면 “켜져있다.”, 0이면 “꺼져있다”고 말한다.

Round up/down

Round_down : 포인트가 가리키는 가상 페이지의 시작점을 반환

Round_up : 가장 가까운 페이지 경계(boundary)로 올림한 va 반


과제1 argument passing, User Memory

목표

커맨드 라인의 문자열을 토큰으로 분리

프로그램 이름과 인자를 구분하여 스택에 저장

인자를 프로그램에 전달

과정

process_create_initd() 함수 수정

  • 수정 파일 : userprog/process.c
  • 개선 방향
    • file_name 문자열을 parsing
    • 첫 번째 토큰을 thread_create() 함수에 스레드 이름으로 전달

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tid_t process_create_initd(const char *file_name)
{
    char *fn_copy;
	tid_t tid;
    char *save_ptr;

	fn_copy = palloc_get_page (0);
	if (fn_copy == NULL)
		return TID_ERROR;
	strlcpy (fn_copy, file_name, PGSIZE);

    file_name = strtok_r (file_name, " ", &save_ptr);
    tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);

	if (tid == TID_ERROR)
		palloc_free_page (fn_copy);
	return tid;
}

process_exec()함수 수정

  • 수정 파일 : userprog/process.c
  • 개선 방향
    • file_name 문자열 파싱
    • argument_stack() 함수를 이용해 스텍에 토큰들을 저장

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int process_exec (void *f_name) {
	char *file_name = f_name;
	bool success;
    char *arg_list[128];
    char *token, *save_ptr;
    int token_count = 0;

    token = strtok_r(file_name, " ", &save_ptr);
    arg_list[token_count] = token;

    while(token!=NULL){
        token = strtok_r(NULL, " ", &save_ptr);
        token_count++;
        arg_list[token_count] = token;
    }

	struct intr_frame _if;
	_if.ds = _if.es = _if.ss = SEL_UDSEG;
	_if.cs = SEL_UCSEG;
	_if.eflags = FLAG_IF | FLAG_MBS;

	process_cleanup ();

	success = load (file_name, &_if);

	if (!success) {
        palloc_free_page (file_name);
        return -1;
    }
    argument_stack(arg_list, token_count, &_if);
	do_iret (&_if);
	NOT_REACHED ();
}

argument_stack()함수 구현

  • 수정 파일 : userprog/process.c
  • 개선 방향
    • 프로그램 이름 및 인자(문자열) push
    • 프로그램 이름 및 인자 주소들 push
    • argv(문자열을 가리키는 주소들의 배열을 가리킴) push
    • argc(문자열의 갯수) push
    • fake address(0) 저장

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void argument_stack(char **argv, int argc, struct intr_frame *if_){
    char *arg_address[128];

    for(int i = argc - 1; i >= 0; i--){
        int argv_with_senti_len = strlen(argv[i]) + EOL;
        if_->rsp -= (argv_with_senti_len);
        memcpy(if_->rsp, argv[i], argv_with_senti_len);
        arg_address[i] = if_->rsp;
    }
    while(if_->rsp % SAU != 0){
        if_->rsp--;
        memset(if_->rsp, 0, sizeof(uint8_t));
    }

    for(int j = argc; j >= 0; j--){
        if_->rsp -= SAU;
        if(j == argc){
            memset(if_->rsp, 0, sizeof(char **));
        } else{
            memcpy(if_->rsp, &arg_address[j], sizeof(char **));
        }
    }

    if_->rsp -= sizeof(void *);
    memset(if_->rsp, 0, sizeof(void *));

    if_->R.rdi = argc;
    if_->R.rsi = if_->rsp + SAU;
}

과제2 System calls

목표

  • 시스템 콜 핸들러 및 시스템 콜 구현
  • 프로세스간의 부모와 자식관계를 구현하고, 부모가 자섹프로세스의 종료를 대기하는 기능 구

과정

check_address() 구현

  • 수정 파일 : userprog/syscall.c
  • 개선 방향
    • 포인터가 가리키는 주소가 유저영역의 주소인지 확인
    • 잘못된 접근일 경우 프로세스 종료

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
struct page* check_address(void *addr) {
    struct thread *curr = thread_current();

    if(!is_user_vaddr(addr) || addr == NULL){
		exit(-1);
	}

	#ifdef VM
	if(pml4_get_page (&thread_current()->pml4, addr) == NULL)
		if (spt_find_page(&thread_current()->spt, addr) == NULL)
			exit(-1);
	#endif
}

get_argument() 구현

  • 필요 없음
  • 이유 : 64bit로 바뀌면서 syscall_entry, syscall_handler코드가 추가되었다. 이 코드들 덕분에 시스템 콜 인자를 커널에 복사하는 함수를 구현할 필요가 없어졌다!

syscall_handler() 구현

  • 수정 파일 : userprog/syscall.c
  • 개선 방향
    • 유저 스택에 저장되어 있는 시스템 콜 넘버를 이용해 시스템 콜 핸들러 구현
    • 스택 포인터가 유저 영역인지 확인
    • 저장된 인자 값이 퐁니터일 경우 유저 영역의 주소인지 확인
    • 0 : halt
    • 1 : exit

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void syscall_handler (struct intr_frame *f UNUSED) {

    thread_current()->rsp_stack = f->rsp;

    int sys_number = f -> R.rax;
    switch(sys_number){
        case SYS_HALT:
            halt();
            break;
        case SYS_EXIT:
            exit(f -> R.rdi);
            break;
        case SYS_FORK:
            f -> R.rax = fork(f -> R.rdi, f);
            break;
        case SYS_EXEC:
            if(exec(f -> R.rdi) == -1){
                exit(-1);
            }
            break;
        case SYS_WAIT:
            f -> R.rax = wait(f -> R.rdi);
            break;
        case SYS_CREATE:
            f -> R.rax = create(f -> R.rdi, f -> R.rsi);
            break;
        case SYS_REMOVE:
            f -> R.rax = remove(f -> R.rdi);
            break;
        case SYS_OPEN:
            f -> R.rax = open(f -> R.rdi);
            break;
        case SYS_FILESIZE:
            f -> R.rax = filesize(f -> R.rdi);
            break;
        case SYS_READ:
            f -> R.rax = read(f -> R.rdi, f -> R.rsi, f -> R.rdx);
            break;
        case SYS_WRITE:
            f -> R.rax = write(f -> R.rdi, f -> R.rsi, f -> R.rdx);
            break;
        case SYS_SEEK:
            seek(f -> R.rdi, f -> R.rsi);
            break;
        case SYS_TELL:
            f -> R.rax = tell(f -> R.rdi);
            break;
        case SYS_CLOSE:
            close(f -> R.rdi);
            break;
        default:
            exit(-1);
            break;
    }
}

시스템 콜 구현 1

  • 수정 파일 : userprog/syscall.c
  • 개선 방향
    • halt
    • exit
    • create
    • remove

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void halt(void){
    power_off();
}

void exit(int status){
    struct thread *curr = thread_current();
    curr->exit_status = status;
    printf ("%s: exit(%d)\n", thread_name(), status);
    thread_exit();

bool create (const char *file, unsigned initial_size) {
    check_address(file);
    if (filesys_create(file, initial_size))
    {
    	return true;
    }
    else
    {
        return false;
    }
}

bool remove (const char *file) {
    check_address(file);
    return filesys_remove(file);

}

thread 구조체 수정

  • 수정 파일 : include/threads/thread.h

  • 개선 방향

    • 부모 프로세서의 디스크립터 추가
    • 자식 리스트 element
    • 자식 리스트
    • 프로세스의 프로그램 메모리 적재 유무
    • 프로세스가 종료 유무 확인
    • exit 세마포어
    • load 세마포어
    • exit 호출 시 종료 status

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
struct thread {

	tid_t tid;
	enum thread_status status;
	char name[16];
	int priority;

    int64_t wakeup_tick;

    struct list_elem elem;


    int init_priority;
    struct lock *wait_on_lock;
    struct list donations;
    struct list_elem donation_elem;

    int exit_status;

    struct intr_frame parent_if;

    struct list child_list;
    struct list_elem child_elem;

    struct semaphore wait_sema;
    struct semaphore fork_sema;
    struct semaphore free_sema;

    struct file **fd_table;
    int fd_idx;

    int stdin_count;
    int stdout_count;

    struct file *running;

#ifdef USERPROG
	uint64_t *pml4;
#endif
#ifdef VM
	struct supplemental_page_table spt;
#endif

	struct intr_frame tf;
	unsigned magic;
};

init_thread() 함수 수정

  • 수정 파일 : threads/thread.c

  • 개선 방향

    • 자식 리스트 초기화하는 코드 추가

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void init_thread(struct thread *t, const char *name, int priority) {
    ASSERT(t != NULL);
    ASSERT(PRI_MIN <= priority && priority <= PRI_MAX);
    ASSERT(name != NULL);

    memset(t, 0, sizeof *t);
    t->status = THREAD_BLOCKED;
    strlcpy(t->name, name, sizeof t->name);
    t->tf.rsp = (uint64_t) t + PGSIZE - sizeof(void *);
    t->priority = priority;
    t->magic = THREAD_MAGIC;

    t -> init_priority = priority;
    list_init(&t->donations);
    t -> wait_on_lock = NULL;

    list_init(&t->child_list);

    sema_init(&t->wait_sema, 0);
    sema_init(&t->fork_sema, 0);
    sema_init(&t->free_sema, 0);

    t->exit_status = 0;
}

thread_create() 구현

  • 수정 파일 : threads/thread.c
  • 개선 방향
    • 부모 프로세스 저장
    • 프로그램이 로드되지 않음
    • 프로세스가 종료되지 않음
    • exit 세마포어 0으로 초기화
    • load 세마포어 0으로 초기화
    • 자식 리스트에 추가

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
tid_t thread_create(const char *name, int priority, thread_func *function, void *aux) {
    struct thread *t;

    struct thread *now_running = thread_current();

    tid_t tid;

    ASSERT(function != NULL);

    t = palloc_get_page(PAL_ZERO);
    if (t == NULL)
        return TID_ERROR;

    init_thread(t, name, priority);
    tid = t->tid = allocate_tid();

    list_push_back(&now_running->child_list,&t->child_elem); // parent child 리스트에 생성한 child를 담는다

    t->fd_table = palloc_get_multiple(PAL_ZERO, FDT_PAGES);
    if(t->fd_table == NULL)
        return TID_ERROR;

    t->fd_table[0] = 1;
    t->fd_table[1] = 2;
    t->fd_idx = 2;

    t->stdin_count = 1;
    t->stdout_count = 1;

    t->tf.rip = (uintptr_t) kernel_thread;
    t->tf.R.rdi = (uint64_t) function;
    t->tf.R.rsi = (uint64_t) aux;
    t->tf.ds = SEL_KDSEG;
    t->tf.es = SEL_KDSEG;
    t->tf.ss = SEL_KDSEG;
    t->tf.cs = SEL_KCSEG;
    t->tf.eflags = FLAG_IF;

    thread_unblock(t);
    if (cmp_priority(&t->elem, &now_running->elem, 0)) {
            thread_yield();
    }
    return tid;
}

get_child 함수 구현

  • 수정 파일 : userprog/process.c
  • 개선 방향
    • 자식 리스트에 접근하여 프로세스 디스크립터 검색
    • 해당 pid가 존재하면 프로세스 디스크립터 반환
    • 리스트에 존재하지 않으면 NULL 반환

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct thread *get_child(int pid){

    struct thread *curr = thread_current();
    struct list *child_list = &curr->child_list;
    struct list_elem *e;
    if (list_empty(child_list)) return NULL;
    for (e = list_begin (child_list); e != list_end (child_list); e = list_next (e)){
        struct thread *t = list_entry(e, struct thread, child_elem);
        if (t->tid == pid){
            return t;
        }
    }
    return NULL;
}

remove_child 함수 구현

  • 수정 파일 : userprog/process.c
  • 개선 방향
    • 자식 리스트에서 제거
    • 프로세스 디스크립터 메모리 해제
1
2
3
4
5
void remove_child(struct thread *cp)
  {
  	list_remove(&cp->child_elem);
  	free(*cp);
  }

exec 함수 구현

  • 수정 파일 : userprog/syscall.c
  • 개선 방향
    • 커맨드 라인 parsing해서 file_name 추출해서 넣음
    • 생성된 자식 프로세스의 디스크립터 검색
    • 자식 프로세스의 프로그램 적재 대기

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 int exec(const *cmd_line)
  {
  	struct thread *curr = thread_current();
  	tid_t pid = process_create_initd(cmd_line);

  	struct thread *child = get_child_process(pid);
  	int result = process_wait(child);
  	list_push_back(&curr->children_list, &child->child_elem);

  	if (result = 1)
  	{
  		return pid;
  	}
  	else
  	{
  		return -1;
  	}
  }

wait함수 구현

  • 수정 파일 : userprog/syscall.c
  • 개선 방향
    • 자식 프로세스가 종료 될 때까지 대기
    • process_wait() 사용

정답 코드

1
2
3
int wait(tid_t pid){
    return process_wait(pid);
}

과제 3 File descriptor

목표

  • 파일 디스크립터 및 관련 시스템 콜 구현

과정

File Descriptor 테이블 추가

  • 수정 파일 : include/threads/thread.h
  • 개선 방향
    • 파일 디스크립터 테이블
    • 현재 테이블에 존재하는 fd값의 최대값 + 1

정답 코드

1
2
3
4
5
6
7
struct thread{
    ...
    struct file **fd_table;
    int fd_idx;
    ...
}

thread_create 함수 수정

  • 수정 파일 : threads/thread.c
  • 개선 방향
    • fd 값 초기화(0,1은 표준 입력,출력)
    • File Descriptor 테이블에 메모리 할당

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
tid_t thread_create(const char *name, int priority, thread_func *function, void *aux){

    ...

    t->fd_table = palloc_get_multiple(PAL_ZERO, FDT_PAGES);
    if(t->fd_table == NULL)
        return TID_ERROR;

    t->fd_table[0] = 1;
    t->fd_table[1] = 2;
    t->fd_idx = 2; // idx 2로 초기화

    //count 초기화
    t->stdin_count = 1;
    t->stdout_count = 1;

    ...
}

File descriptor 생성 함수 구현

  • 수정 파일 : userprog/syscall.c
  • 개선 방향
    • 파일 객체를 파일 디스크립터에 추가
    • 파일 디스크립터의 최댓값 1 증가
    • 파일 디스크립터 반환

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int add_file_to_fdt(struct file *file) {
    struct thread *curr = thread_current();
    struct file **fdt = curr->fd_table;

    while (curr->fd_idx < FDCOUNT_LIMIT && fdt[curr->fd_idx])
    {
        curr->fd_idx++;
    }

    if (curr->fd_idx >= FDCOUNT_LIMIT)
        return -1;

    fdt[curr->fd_idx] = file;
    return curr->fd_idx;
}

파일 객체 검색 함수 구현

  • 수정 파일 : userprog/syscall.c
  • 개선 방향
    • 파일 디스크립터에 해당하는 파일 객체를 리턴
    • 없을 시 NULL 리턴

정답 코드

1
2
3
4
5
6
7
8
9
static struct file *find_file_by_fd(int fd) {
    if (fd < 0 || fd >= FDCOUNT_LIMIT){
        return NULL;
    }

    struct thread *curr = thread_current();

    return curr->fd_table[fd];
}

파일 닫기 함수 구현

  • 수정 파일 : userprog/syscall.c

  • 개선 방향

    • 파일 디스크립터에 해당하는 파일을 닫음
    • 파일 디스크립터 테이블 해당 엔트리 초기화

정답 코드

1
2
3
4
5
6
7
void remove_file_from_fdt(int fd) {
    struct thread *cur = thread_current();
    if (fd < 0 || fd >= FDCOUNT_LIMIT){
        return;
    }
    cur->fd_table[fd] = NULL;
}

열린 모든 파일 닫기 구현

  • 수정 파일 : userprog/syscall.c
  • 개선 방향
    • 프로세스에 열린 모든 파일을 닫음
    • 파일 디스크립터 테이블의 최댓값을 이용해 파일 디스크립터의 최솟값인 2가 될 때까지 파일을 닫음
    • 파일 디스크립터 테이블 해제

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void process_exit (void) {
	struct thread *curr = thread_current ();

    for (int i=0; i<FDCOUNT_LIMIT; i++)
    {
        close(i);
    }
    palloc_free_multiple(curr->fd_table, FDT_PAGES);
    file_close(curr->running);

    sema_up(&curr->wait_sema);
    sema_down(&curr->free_sema);

    process_cleanup ();
}

시스템콜 함수 구현

  • 수정 파일 : userprog/syscall.c
  • 개선 방향
    • open
      • 파일 open
      • 해당 파일 객체에 파일 디스크립터 부여
      • 파일 디스크립터 리턴
      • 해당 파일이 존재하지 않으면 -1 리턴
    • read
      • 파일에 동시 접근이 일어날 수 있으므로 Lock 사용
      • 파일 디스크립터를 이용하여 파일객체 겁ㅁ색
      • 파일 디스크립터가 0일 경우 키보드에 입력을 버퍼에 저장 후 버퍼의 저장한 크기를 리턴(input_getc()함수 사용)
      • 파일 디스크립터가 0이 아닐 경우 파일의 데이터를 크기만큼 저장 후 읽은 바이트 수를 턴
    • filesize
      • 파일 디스크립터를 이용하여 파일 객체 검색
      • 해당 파일의 길이를 리턴
      • 해당 파일이 존재하지 않으면 -1 리턴
    • write
      • 파일에 동시 접근이 일어날 수 있으므로 Lcok 사용
      • 파일 디스크립터를 이용하여 파일 객체 검색
      • 파일 디스크립터가 1일 경우 버퍼에 저장된 값을 화면에 출력후 버퍼의 크기 리턴(putbuf()함수 사용)
      • 파일 디스크립터가 1이 아닐 경우 버퍼에 저장된 데이터를 크기만큼 파일에 기록 후 기록한 바이트 수를 리턴
    • fork
    • seek
      • 파일 디스크립터를 이용하여 파일 객체 검색
      • 해당 열린 파일의 위치(offset)를 position만큼 이동
    • tell
      • 파일 디스크립터를 이용하여 파일 객체 검색
      • 해당 열린 파일의 위치를 반환
    • close
      • 해당 파일 디스크립터에 해당하는 파일을 닫음
      • 파일 디스크립터 엔트리화

정답 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int open(const char *file){
    check_address(file);
    lock_acquire(&filesys_lock);

    struct file *file_obj = filesys_open(file);

    if (file_obj == NULL){
        return -1;
    }

    int fd = add_file_to_fdt(file_obj);

    if(fd == -1){
        file_close(file_obj);
    }

    lock_release(&filesys_lock);
    return fd;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int read(int fd, void *buffer, unsigned size){
    check_address(buffer);
    int read_count;

    struct file *file_obj = find_file_by_fd(fd);
    unsigned char *buf = buffer;

    if (file_obj == NULL){
        return -1;
    }

    if(file_obj == STDIN){
        char key;
        for (int read_count = 0; read_count < size; read_count++){
            key = input_getc();
            *buf++ = key;
            if (key == '\0'){
                break;
            }
        }
    }else if (file_obj == STDOUT){
        return -1;
    } else {
        lock_acquire(&filesys_lock);
        read_count = file_read(file_obj, buffer, size);
        lock_release(&filesys_lock);
    }

    return read_count;
}
1
2
3
4
5
6
7
8
int filesize(int fd){
    struct file *file_obj = find_file_by_fd(fd);

    if (file_obj == NULL){
        return -1;
    }
    return file_length(file_obj);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int write(int fd, void *buffer, unsigned size){
    check_address(buffer);
    int read_count;
    struct file *file_obj = find_file_by_fd(fd);

    if (file_obj == NULL){
        return -1;
    }

    if(file_obj == STDOUT){
        putbuf(buffer, size);
        read_count = size;
    } else if (file_obj == STDIN){
        return -1;
    } else{
        lock_acquire(&filesys_lock);
        read_count = file_write(file_obj, buffer, size);
        lock_release(&filesys_lock);
    }
    return read_count;
}
1
2
3
4
tid_t fork (const char *thread_name, struct intr_frame *f) {
    check_address(thread_name);
    return process_fork(thread_name, f);
}
1
2
3
4
5
6
7
void seek(int fd, unsigned position){
    struct file *file_obj = find_file_by_fd(fd);
    if (fd < 2){
        return;
    }
    file_seek(file_obj, position);
}
1
2
3
4
5
6
7
unsigned tell(int fd){
    struct file *file_obj = find_file_by_fd(fd);
    if (fd < 2){
        return;
    }
    return file_tell(file_obj);
}
1
2
3
4
5
6
7
8
9
void close(int fd){
    if (fd <= 1) return;
    struct file *file_obj = find_file_by_fd(fd);

    if (file_obj == NULL){
        return;
    }
    remove_file_from_fdt(fd);
}
  1. Virtual Address Space의 약자. 가상 메모리에 할당되는 주소를 말하며 물리메모리 주소와 다른 주소이다.