

[구현 1-1] 무한루프 땜빵... 이건 쉽고
[구현 1-2] process_create_initd에서 쓰레드 만들 때, 쓰레드 이름 수정해야 함
thread_create의 file_name 매개변수로는 echo x y z가 전달됨echo x y z은 fn_copy에 백업한 뒤, thread_create의 쓰레드 이름으로는 echo만 전달해야 함.[구현 1-3] load에서 스택 초기화가 완료된 이후, argument passing하기
'echo x y z'를 echo, x, y, z로 알아서 잘 나눈 뒤, 사용자 스택에 전달해야 함그러면 도대체 (1) 문자열을 어떻게 파싱하고
(2) 파싱한 각 정보를 어디에 전달해야 하는지.... 를 공부해야 함.
strtok_r.split() 이랑 비슷// 경로: lib/string.c
char *strtok_r(char *s, const char *delimiters, char **save_ptr)
s를 delimiters로 나눔.s에 분리할 문자열을 전달. 이후 호출 시 NULL을 전달.save_ptr을 이용해 내부적으로 문자열을 기억#include <stdio.h>
#include <string.h>
int main(void){
char s[] = "사랑한다 나의 LG여 영원하라 무적 LG여";
char *token, *save_ptr;
for (token = strtok_r(s, " ", &save_ptr); token != NULL;
token = strtok_r(NULL, " ", &save_ptr)){
printf("'%s' ", token);
};
}
// [출력결과]
// '사랑한다' '나의' 'LG여' '영원하라' '무적' 'LG여'
process_create_initdfile_name은 'echo x y z'echo만 잘라내서 thread_create할 때 쓰레드명으로 전달할 것file_name은 미리 fn_copy에 복사해 뒀으니 괜찮음.initd 함수의 매개변수로 fn_copy가 전달됨tid_t process_create_initd (const char *file_name) {
char *fn_copy;
tid_t tid;
/* Make a copy of FILE_NAME.
* Otherwise there's a race between the caller and load(). */
fn_copy = palloc_get_page (0);
if (fn_copy == NULL)
return TID_ERROR;
strlcpy (fn_copy, file_name, PGSIZE);
// [구현 1-2] 현재 file_name은 "echo x y z"
// file_name은 "echo"만 전달해야 함
// "echo"를 이름으로 쓰레드 만들기.. strtok으로 나중에 구현해야 함
tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);
if (tid == TID_ERROR)
palloc_free_page (fn_copy);
return tid;
}
# 위->아래로 높은 주소 -> 낮은 주소
[ 커널 영역 (Kernel Space) ] ← OS가 관리하는 공간 (사용자 코드에서는 접근 불가)
├── PCB/TCB, 커널 내부 자료구조 등
├── 커널 스택 2 (쓰레드 2용)
├── 커널 스택 1 (쓰레드 1용) - 높은 주소에서 낮은 주소로 성장
├── 커널 코드/데이터
[ 사용자 영역 (User Space) ] ← 프로세스 입장에서 "내가 접근 가능한 공간"
├── 쓰레드 2
│ └── 사용자 스택 2 (개별) - 높은 주소에서 낮은 주소로 성장
├── 쓰레드 1
│ └── 사용자 스택 1 (개별)
├── 힙 영역 (공유) - 낮은 주ㅡ소에서 높은 주소로 성장
├── 데이터 영역 (공유)
├── 코드 영역 (공유)
struct intr_frame 구조체가 인터럽트 프레임 역할을 함rsp, 프로그램 카운터 rip) 등을 멤버로 저장int N 명령어로 사용자 프로그램 -> OS(커널)로 진입integer가 아니라 interrupt입니다... 이것도 시스템 콜이니까 일종의 인터럽트. N은 인터럽트 번호.rsp(스택포인터 레지스터) 이동.struct intr_frame 형태로, 사용자 프로그램의 레지스터 상태를 커널스택에 푸시.iret 명령어로 OS(커널) -> 사용자 프로그램으로 복귀iret 실행 전 struct intr_frame에 저장된 레지스터 상태를, 커널스택에서 팝해 복원iret 명령어는 복원된 레지스터 상태를 기반으로, 사용자 프로그램으로 점프process_execprocess_exec를 통해, OS -> 사용자 프로그램으로의 전환이 이루어짐intr_frame엔 저장된 게 있을 리가 없음. 따라서 우리가 입력받은 argument를 통해 알아서 인터럽트 프레임을 초기화해야 함.process_exec()을 통해 f_name을 실행f_name은 "echo x y z"와 같은 형태/* Switch the current execution context to the f_name.
* Returns -1 on fail. */
int
process_exec (void *f_name) {
char *file_name = f_name;
bool success;
// (1) 새로 실행할 사용자 프로그램의 인터럽트 프레임을 선언해 준다.
// 초기값 몇개를 할당해 주는데 이건 이해할 필요 없을 듯.
struct intr_frame _if;
_if.ds = _if.es = _if.ss = SEL_UDSEG;
_if.cs = SEL_UCSEG;
_if.eflags = FLAG_IF | FLAG_MBS;
/* We first kill the current context */
process_cleanup ();
// (2) 프로그램을 로딩하기.
// _if는 load 함수에 넘겨져, 인터럽트 프레임 및 사용자 스택 초기화에 사용된다.
// load에서 사용자 스택에 인자를 전달하는 과정도 이루어진다.
success = load (file_name, &_if);
/* If load failed, quit. */
palloc_free_page (file_name);
if (!success)
return -1;
// (3) 사용자 프로그램으로 점프할 때, _if에 저장된 정보를 사용한다.
do_iret (&_if);
NOT_REACHED ();
}
loadpml4_create ();), 파일 열고 (filesys_open();)ELF 헤더 읽음 (file_read (file, &ehdr, sizeof ehdr)).setup_stack이란 함수가 안에서 실행되며 사용자스택이 초기화됨.if_->rsp = USER_STACK;로, 사용자 스택의 top 주소 저장됨 (0x47480000임)if_->rip = ehdr.e_entry;로, 실행할 사용자 프로그램의 시작주소 저장됨.filesys_open에는 "echo x y z"'의 echo만 들어가야 할 것 같은데, 확실한진 모르겠다."echo", "x", "y", "z"는 setup_stack 이후, 따로 사용자 스택에 푸시해야 함static bool
load (const char *file_name, struct intr_frame *if_) {
struct thread *t = thread_current ();
// 생략
/* Open executable file. -> 기존 file_name에서 맨 앞 명령어만 파싱 (echo 등)*/
file = filesys_open (file_name);
// 생략
/* Set up stack. */
// setup_stack 내 `if_ -> rsp = USER_STACK` 코드 있음
if (!setup_stack (if_))
goto done;
/* Start address. */
if_->rip = ehdr.e_entry;
// 여기서 스택에 푸시하면서 ARGUMENT PASSING해야 함.
// 현재 스택 top 주소인 if_->rsp부터 시작
// 생략
}
setup_stackpalloc_get_page을 통해, 커널은 4KB(1페이지) 크기의 메모리를 할당install_page 함수.install_page(user_addr, kpage, writable)는 사용자 주소 공간의 user_addr 주소에, kpage가 가리키는 물리 페이지를 매핑USER_STACK - PGSIZE부터 1페이지만큼의 영역이, kpage에 해당되는 물리 페이지에 연결됨static bool
setup_stack (struct intr_frame *if_) {
uint8_t *kpage;
bool success = false;
// 실제 물리 프레임을 OS 물리 메모리 풀에서 할당
// 이 물리 프레임에 대응하는 커널 가상 주소를 반환
// kpage: 커널 공간에서, 할당된 물리 페이지에 접근할 수 있는 가상주소
kpage = palloc_get_page (PAL_USER | PAL_ZERO); // 사용자 페이지 할당, 모든 바이트는 0으로 초기화. 이때 한 페이지는 4KB.
if (kpage != NULL) {
// 사용자의 가상 주소 공간에 페이지 테이블을 설정
// 사용자 가상 주소 (USER_STACK - PGSIZE(4KB))에, 커널이 가진 물리 페이지 (kpage)를 매핑
// true: 사용자 process가 페이지에 쓰기 가능
success = install_page (((uint8_t *) USER_STACK) - PGSIZE, kpage, true);
if (success)
if_->rsp = USER_STACK;
else
palloc_free_page (kpage);
}
return success;
}
e.g., "/bin/ls -l foo bar"
(1) strtok_r로, "/bin/ls", -l, foo, bar로 파싱
(2) 스택 top에다 각 문자열을 푸시 (보통 역순으로)
푸시는 * 연산자로 직접 주소에 값을 할당하면 될 듯함.
e.g., if_ -> rsp(현재 스택포인터 top)의 초기값은 0x47480000일 때
스택은 높은 주소 -> 낮은 주소로 자람에 유의할 것.
cf. 각 포인터의 주소는 8의 배수여야 하므로, 패딩도 넣어야 할 수 있음.
| 주소 | 이름 | 데이터 | 자료형 | 크기 |
|---|---|---|---|---|
0x4747fffc | *argv[3] | "bar\0" | char[4] | 4바이트 |
0x4747fff8 | *argv[2] | "foo\0" | char[4] | 4바이트 |
0x4747fff5 | *argv[1] | "-l\0" | char[3] | 3바이트 |
0x4747ffed | *argv[0] | "/bin/ls\0" | char[8] | 8바이트 |
0x4747ffe8 | 8바이트 정렬용 | 0 | uint8_t[] | 5바이트 |
(3) 푸시한 각 문자열의 주소를 푸시. 이때 argv 배열 맨 끝의 널 포인터를 먼저 푸시해야 함.
포인터는 8바이트임에 유의할 것.
e.g., 위 예제에서 계속
| 주소 | 이름 | 데이터 | 자료형 | 크기 |
|---|---|---|---|---|
0x4747ffe0 | argv[4] | 0 (널포인터) | char * | 8바이트 |
0x4747ffd8 | argv[3] | 0x4747fffc | char * | 8바이트 |
0x4747ffd0 | argv[2] | "0x4747fff8" | char * | 8바이트 |
0x4747ffc8 | argv[1] | "0x4747fff5" | char * | 8바이트 |
0x4747ffc0 | argv[0] | "0x4747ffed" | char * | 8바이트 |
(4) fake return address를 푸시 (0)
e.g., 위 예제에서 계속
| 주소 | 이름 | 데이터 | 자료형 | 크기 |
|---|---|---|---|---|
0x4747ffb8 | 리턴 주소 | 0 | void * | 8바이트 |
이후 스택 포인터 (if_-> rsp)는 fake return address의 위치인 0x4747ffb8로 설정됨
(5) 레지스터 %rsi는 argv(즉 argv[0]의 주소)로, %rdi는 argc(여기선 4)로 설정
%rdi는 4, %rsi는 argv[0]의 주소인 0x4747ffc0으로 저장.if_ -> R -> rsi, if_ -> R -> rdi를 수정하면 될 듯함.do_iretstruct intr_frame *tf 구조체에서 레지스터 값들을 복원iretq 명령어로 CPU 상태를 전환해, 사용자 프로그램을 시작/* Use iretq to launch the thread */
void
do_iret (struct intr_frame *tf) {
// __volatile은 컴파일러의 최적화를 막음
__asm __volatile(
"movq %0, %%rsp\n" // 커널스택 포인터(rsp)를 tf가 가리키는 주소로 설정
// tf의 멤버들을 레지스터로 복원하는 과정
"movq 0(%%rsp),%%r15\n" // tf에 저장된 값을 꺼내, 일반 레지스터들에 복원
"movq 8(%%rsp),%%r14\n"
// 생략
// 사용자모드로 전환하며, 사용자 프로그램 시작
"iretq"
: : "g" ((uint64_t) tf) : "memory");
}