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 실행 중 디스크에서 어떤 데이터를 읽어오라는 명령을 받음
- 프로세스 A는 systeml call로 인터럽트 발생시킨다.
- 명령 받은 CPU는 현재 수행중인 기계어 코드 완료
- 수행중이었던 현재 상태를 해당 process의 PCB(Process Control Block)에 저장
- PC(Program Counter, IP)에 다음으로 실행할 명령의 주소 저장
- 인터럽트 벡터를 읽고 ISR 주소값을 얻어 ISR(Interrupt Service Rountine)로 점프하여 루틴 실행
- 해당 코드 실행
- 해당 작업 종료시, 레지스터 복원
- ISR의 끝에 있는 IRET 명령어에 의해 인터럽트 해제
- 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가지 이슈
- Address Translation : 가상 메모리를 물리 메모리에 매핑
- 가상 공간에 대한 관리
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)의 지원을 받아 매핑시키게 된다.
한 프로세스가 특정 가상 주소에 접근한다
- 해당 프로세스 PCB에 있는 page table의 base 주소에 해당하는 가상 주소가 포함된 page 번호가 있는지 확인
- page 번호가 있으면 이 page가 매핑된 첫 물리 주소(p’)를 알아내면, p’ + d가 실제 물리 주소가 됨
- CPU가 가상 주소에 접근할 때 MMU 하드웨어 장치를 통해 물리 메모리에 접근한다.
- 프로세스가 맨 처음에 실행될 때, 페이지 테이블에 적재가 되면서 동시에 해당 페이지 테이블 base 주소가 별도 레지스터(CR3)에 저장되는 것.
- CPU가 프로세스 실행 위해 접근할 때, MMU가 CR3 레지스터를 가져와서 페이지 테이블 base 주소를 참조하여 물리주소로 변환을 한 다음, 해당 물리주소에 있는 데이터를 CPU에 가져다 준다.
Page table Entry(PTE)
- 페이지 테이블의 레코드
- PTE의 각 필드에 기록되는 내용은 다음과 같다.
- 페이지 기본 주소(Page base address)
- 플래그 비트(Flag bit)
- Accessed bit : 페이지에 대한 접근이 있었는지
- Dirty bit : 페이지의 내용에 변경이 있었는지
- Present bit : 현재 페이지에 할당된 프레임이 있는지
- Read/Write bit : 읽기/쓰기에 대한 권한이 있는지
동적 주소 변환
- 수행 중인 프로세스가 가상주소를 참조
- 페이징 기법을 통해 페이지 p가 페이지 프레임 f에 있음을 알아낸다.
- 실주소 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);
}
-
Virtual Address Space의 약자. 가상 메모리에 할당되는 주소를 말하며 물리메모리 주소와 다른 주소이다. ↩