728x90
반응형
Pintos Part 2(User Program)
Background
thread_create() 함수 | - 쓰레드 구조체를 만들고 초기화 - 커널 스택을 할당받음 - 함수를 등록해 실행(명령어 포인터 및 인자를 넣음) - ready_list에 추가 |
로드(load) 함수 | - 디스크에서 바이너리 파일을 로드하고 메모리(스택, 데이터, BSS, TEXT 등) 초기화를 수행 - 성공적으로 로드가 되면 실행하고 아니면 exit() 호출 요구사항 1. Argument passing(인자 전달) 2. 레지스터에 agvc, argv 전달 |
프로세스 실행 함수 (process_exec) |
|
ELF 파일 로드 | - 2단계 페이지 테이블 생성 - 파일을 열어서 ELF 헤더를 읽음 - 파일을 파싱해서 데이터를 데이터 세그먼트로 로드 - 유저 스택을 만들고 초기화함 - setup_stack()함수에서 진행 |
Passing the arguments and creating a thread
개요 | 예시 명령 : echo x y z 1. 쓰레드 이름을 echo로 생성하고 2. 파일 이름이 echo인것을 찾아서 3. 유저 스택에 인자들(x, y, z)을 PUSH함 |
strtok_r() 함수 |
char *strtok_r(char *s, const char *delimiters, char **save_ptr)
char *s = 분할할 문자열 const char *delimiters = 문자열을 분할할 사용할 구분자 문자열 char **save_ptr = 사용 정보를 저장할 수 있는 포인터(분할하고 남은 문자열) ❗️주의사항❗️ 1. 원본 문자열을 보존하고 싶다면, 문자열의 사본을 만들어 사용해야 함 2. 각 호출에서 반환된 포인터는 원본 문자열의 부분을 가리키므로, 문자열의 메모리를 해제하거나 수정하면 안됨 3. strtok_r은 쓰레드 안전성을 제공하므로 멀티쓰레드 환경에서 문자열을 분할할 때 유용(strtok은 전역 상태를 사용하기 때문에 멀티쓰레드 환경에서는 안전하지 않음) |
인터럽트 프레임 | - 인터럽트가 발생했을 때, 현재 실행 중인 프로세스, 쓰레드의 상태를 저장(Context saving)하는 데이터 구조 - 시스템의 안정성과 일관성을 유지 저장 내용 1. 상태 레지스터(EFLAGS 등) 2. 명령 포인터(rip 등) 3. 범용 레지스터 4. 세그먼트 레지스터 5. 스택 포인터 과정 1. 인터럽트 발생 2. 현재 상태 저장 3. 인터럽트 서비스 루틴(ISR) 실행 4. 루틴이 종료되면 CPU가 인터럽트 프레임을 스택에서 복원 5. 상태 복구 6. 인터럽트 발생 전의 명령어 주소로 돌아가 프로그램 실행 |
PintOS KAIST Calling Convention |
Command Line : /bin/ls -l foo bar 1. 인자를 푸시 - 인자(/bin/ls, -l, foo, bar)를 오른쪽에서 왼쪽 순서로 PUSH됨 - 4byte 단위로 정렬 - 시작 주소를 푸시해야 함 2. argc(인자수), argv(시작 주소)를 레지스터에 지정 - rsi에 argv를 지정 - rdi에 argc를 지정 3. 최종적으로 리턴 주소(fake address) 유저 스택에 PUSH |
hex_dump() 함수 | - 16진수 맵을 덤프해서 스택에 제대로 PUSH 됐는지 확인 |
System Calls and Handlers
목표(Main goal) | 원본 : 시스템 호출 핸들러 테이블이 비어있음 목표 1. 시스템 호출 핸들러 작성 2. 시스템 호출 추가 - 프로세스 관련 : 중단, 종료, 실행, 대기 - 파일 관련 : 생성, 삭제, 열기, 파일 크기, 읽기, 쓰기, 찾기, 말하기, 닫기 |
시스템 콜(System call) | - 프로세스 생성 및 파일 저장 등 운영체제에게 시스템 콜을 요청 - 커널 영역에서 작업을 완료 후 사용자 영역으로 반환 - User Program의 권한이 올라감(User -> Kernel) |
시스템 콜 핸들러 요구사항 | 1. 사용자 프로그램이 보낸 인자의 포인터 유효성을 검사(커널 영역에서 수행) - 인자의 포인터는 사용자 영역을 가리켜야함(커널 영역을 가리키면 안됨) 2. 유효성 검사를 하고 커널의 유저 스택에 인자를 복사해서 넣음 3. 시스템 콜 작업을 마치고 반환값을 eax 레지스터에 저장 * 사용자는 유효한 주소(사용자 영역)를 제공해야 하고, 커널에서 작업이 수행되는 동안 사용자 주소공간에 접근이 있으면 안됨 |
유효한 주소 | - 시스템 콜을 통해서 유효하지 않은 포인터를 보낼 수도 있음 1. 널 포인터 / 가상 메모리에 맵핑되지 않은 포인터 2. PHYS_BASE를 넘어가는 포인터(커널 영역을 가리키는 포인터) 보호 방법 1. 페이지 테이블을 확인해 맵핑 여부를 확인 2. 물리 메모리 최대 주소보다 작은지 체크(Linux 같은 실제 운영체제에서 사용) |
유저 메모리 접근 | 아래와 같은 상황(lock이나 malloc)에서 page fault가 발생하게 되면 프로그램이 종료되어 메모리 누수가 발생 |
해결 방안 1. 간단한 방법 - 락이나 메모리 할당은 유효성 검사 이후에 이뤄짐 2. 어려운 방법 - 에러 코드를 반환받기 어렵고, 이러한 경우를 위해 함수를 사용해야함 |
|
프로세스 계층 (Process Hierarchy) |
- 프로세스간의 포인터가 있어야함(모든 자식에게 포인터를 주는 것은 비효율적임) 1. struct thread* : 부모 프로세스 2. struct list : 형제 프로세스 3. struct list_elem : 자식 프로세스 |
wait() 함수 | - 부모 프로세스는 자식 프로세스가 종료될 때까지 대기 - exit 콜 대신 커널에 의해 종료됐다면, -1을 반환 - 부모 프로세스는 자식 프로세스가 종료된 후에 프로세스 디스크립터를 해제해야함 - wait은 다음과 같은 상황에 실패하고 -1을 반환함 1. pid의 직접적인 자식(하위 항목)을 참조하지 않을때 2. 이미 종료된 자식에 대해 또 대기를 하려고 할 때 |
process_wait() 함수 | - child_tid를 사용해 자식 프로세스의 식별자를 검색 - 부모 프로세스는 자식 프로세스가 종료될 때까지 블록됨 - 자식 프로세스가 종료되면 자식 프로세스의 파일 디스크립터를 해제하고, 자식 프로세스의 종료 상태를 반환 |
세마포어 (Semaphore) |
- 쓰레드 구조체에 대기를 위한 세마포어를 추가함 - 쓰레드를 생성할 때 0으로 초기화 - 자식 프로세스 종료를 대기하는 동안 부모는 블록됨 - 자식 프로세스가 종료되면 부모 프로세스를 깨움 - 자식 프로세스의 실행파일이 성공적으로 로드될 때까지 기다리기 위해 sema_down을 호출 - 실행파일이 성공적으로 로드되면 sema_up을 호출 |
종료 상태 (Exit status) |
- 쓰레드 구조체에 exit status를 필드로 추가함 |
부모, 자식 프로세스 호출 과정 |
|
exec() 함수 | - Unix에서는 exec는 fork()+exec()와 동일한 의미를 지님 - 실행될 프로그램에 인자를 전달 - 새로 생성된 자식 프로세스의 pid를 반환 - 만약 프로세스 생성에 실패하거나 로드에 실패하면 -1을 반환 - 부모 프로세스는 자식이 생성되고 실행파일을 완전히 로드할 때까지 대기해야함 (자식은 생성되고 로드가 완료되면 부모에게 로드 완료 신호를 보냄) - 부모는 자식이 완전히 생성되고 바이너리 파일이 성공적으로 로드되었음을 알 때까지 대기해야함 |
적재 상태 (Load status) |
- 파일이 성공적으로 로드가 됐는지 안됐는지를 대표하는 필드를 쓰레드 구조체에 추가 |
부모, 자식의 호출과 흐름 |
부모 프로세스는 커널 영역, 자식 프로세스는 유저 영역에서 병렬적으로 수행 |
exit() 함수 | - 현재 사용자 프로그램을 종료하고 커널에게 상태를 반환 - 부모가 자식을 기다리고 있으면 자식 프로세스 종료 상태를 부모에게 반환해야함 - sema_up을 호출해 대기하던 프로세스를 깨움 - 스케줄(schedule())을 호출하면 뒤에 명령어까지 실행되지 못함(다음 준비된 쓰레드를 실행함) |
File Manipulation
파일 디스크립터 (File Descriptor) |
- 0(표준 입력), 1(표준 출력), 2(표준 에러)는 이미 할당되어 있음 - 각 프로세스는 각자 파일 디스크립터 테이블을 가지고 있음(최대 64개를 지님) - 파일 구조체를 가리키는 포인터의 배열(파일을 가리키는 포인터의 집합) - 번호는 3번부터 순차적으로 할당 - open() : 파일을 열고 파일 디스크립터(fd)를 반환 - close() : 파일 디스크립터를 닫고 파일 디스크립터 테이블에서 항목을 비움 |
Unix에서의 파일 식별자 |
|
할당하는 방식(2가지) | 1. 쓰레드 구조체 내에 정의하는 방법 - 장점 - 간단한 접근 - 캡슐환 - 단점 - 메모리 낭비 - 자원 공유 어려움 2. 커널 메모리 영역에 별도의 파일 디스크립터를 생성 - 장점 - 자원 공유 용이 - 효율적인 메모리 사용 - 단점 - 복잡한 접근 - 동기화 문제 |
파일 디스크립터 테이블 (File Descriptor Table) |
1. 쓰레드가 생성될 때 - 파일 디스크립터 테이블을 할당받음 - 파일 디스크립터를 가리키기 위한 포인터를 초기화 - 0, 1, 2는 이미 예약돼있음 2. 쓰레드가 종료될 때 - 모든 파일을 닫음 - 파일 디스크립터 테이블을 해제 3. 파일 디스크립터용 lock을 사용해 경쟁 상태를 해소 - lock을 정의해 동기화 문제를 해결 |
시스템 콜 관련 함수들 |
|
실행중인 파일 쓰기 거부하기 |
- 실행 파일이 열린 경우에는 수정되지 않도록 해야함 접근법 1. 파일 실행되는 동안 쓰기 작업을 거부하는 함수(file_deny_write()를 사용) 2. 파일 실행이 끝나면 쓰기 작업을 허용하는 함수(file_allow_write()를 사용) |
요약 |
728x90
반응형
'크래프톤 정글 - TIL' 카테고리의 다른 글
크래프톤 정글 5기 TIL - Day 67~69(Pintos Project 2) (0) | 2024.05.28 |
---|---|
크래프톤 정글 5기 TIL - Day 64~66(Pintos Project 2) (1) | 2024.05.24 |
크래프톤 정글 5기 TIL - Day 53 ~ 59 (0) | 2024.05.13 |
크래프톤 정글 5기 TIL - Day 52(알고리즘) (0) | 2024.05.13 |
크래프톤 정글 5기 TIL - Day 50(키워드 정리) (0) | 2024.05.09 |