A. initd 에서 새 프로세스(1프로세스 = 1쓰레드) 생성시.
이때, 맨 처음에 생성된 thread(thread_init() 함수 안의 init_thread()) 의 주소값을 가지고 rsp를 지정한다.
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 *); /
.
.
.
이때, tf->rsp에 커널 스택 포인터를 저장한다
B. 프로세스를 fork할 시 do_fork 실행
이때, do_fork를 실행하는 쓰레드 또한 마찬가지로thread_create() -> init_thread() 를 통해 커널 영역을 잡고 생성된다.
kpage = palloc_get_page (PAL_USER | PAL_ZERO);
if (kpage != NULL) {
success = install_page (((uint8_t *) USER_STACK) - PGSIZE, kpage, true);
if (success)
if_->rsp = USER_STACK;
else
palloc_free_page (kpage);
}
load 안에서 인터럽트 프레임의 rip 값도 ELF바이너리의 e_entry 값으로 설정함
if_->rip = ehdr.e_entry;
4번에 대한 자세한 설명
1. 하나의 프로세스가 시스템 콜 호출
2. lib/user/syscall.c의 시스템 콜 호출
3. 이때, syscall 함수의 inline 어셈블리 안에서 syscall\n으로 시스템 콜을 호출
__attribute__((always_inline))
static __inline int64_t syscall (uint64_t num_, uint64_t a1_, uint64_t a2_,
uint64_t a3_, uint64_t a4_, uint64_t a5_, uint64_t a6_) {
int64_t ret;
// 각 레지스터에 인자값 저장
register uint64_t *num asm ("rax") = (uint64_t *) num_;
register uint64_t *a1 asm ("rdi") = (uint64_t *) a1_;
register uint64_t *a2 asm ("rsi") = (uint64_t *) a2_;
register uint64_t *a3 asm ("rdx") = (uint64_t *) a3_;
register uint64_t *a4 asm ("r10") = (uint64_t *) a4_;
register uint64_t *a5 asm ("r8") = (uint64_t *) a5_;
register uint64_t *a6 asm ("r9") = (uint64_t *) a6_;
// __ams: 인라인 어셈블리, __volatile : 컴파일러의 최적화 방지
__asm __volatile(
/* 어셈블리어들: %0, %1 등을 사용해서 input, output 오퍼랜드를 나타냄.
output부터 시작해 input에 나열된 변수의 순서대로 %0, %1 ... 순으로 번호가 매겨짐 */
/* input의 파라미터들을 rax, rdi.... 등 레지스터들에 옮김 */
"mov %1, %%rax\n"
"mov %2, %%rdi\n"
"mov %3, %%rsi\n"
"mov %4, %%rdx\n"
"mov %5, %%r10\n"
"mov %6, %%r8\n"
"mov %7, %%r9\n"
"syscall\n" // 시스템콜 호출 -> syscall-entry.s 로 진입아니고, syscall자체를 호출하는건데, 커널단에서 시스템콜을 다루는건 syscall_handler
/* output */
: "=a" (ret) // 결과값을 출력하는 변수가 ret, 이를 a라는 오퍼랜드에 넣는데, = 을 사용해서 쓰기 전용 오퍼랜드임을 나타냄
/* input : 인라인 어셈블리에 넘겨주는 파라미터를 적는다 */
: "g" (num), "g" (a1), "g" (a2), "g" (a3), "g" (a4), "g" (a5), "g" (a6) // g는 일반레지스터, 메모리 혹은, immediate 정수 중 아무것이나 나타내는 오퍼랜드
/* clobber : output, input에 명시돼 있지는 않지만, asms를 실행해서 값이 변하는 것을 적어준다 */
: "cc", "memory"); // memory clobber는 GCC가 메모리가 임의적으로 asm블럭에 의해 읽히거나 쓰일것이라고 가정하게 함. 따라서 컴파일러가 reorder하는 것을 막음
// cc 클로버는 모든 asm()안에 implicit하게 들어있음, 얘는 flag와 condition 코드를 컴파일러가 미리 고려하게 함
return ret;
}
여기서 프로세스의 rsp를 커널 영역으로 바꿔주고, 시스템 콜 호출 순간의 레지스터 값들을 intr_frame을 만들어 백업
이때 syscall\n 을 호출했을때, syscall-entry 로 넘어갈 수 있는 이유는, syscall_init에서 MSR레지스터에 syscall_entry 함수의 위치 (함수 포인터)를 등록하기 때문.
(이 syscall_init은 init.c에서 호출)
void syscall_init (void) {
// MSR: Machine_specific Register - syscall은 MSR의 값을 읽어서 작동한다
write_msr(MSR_STAR, ((uint64_t)SEL_UCSEG - 0x10) << 48 | ((uint64_t)SEL_KCSEG) << 32); // code segment와 stack segment를 로드할때쓴느 레지스터
write_msr(MSR_LSTAR, (uint64_t) syscall_entry); // MSR_LSTAR는 시스템 콜 핸들러 함수의 주소를 저장 -> syscall\n 호출시 syscall-entry로 넘어갈 수 있는 방법!
/* The interrupt service rountine should not serve any interrupts
* until the syscall_entry swaps the userland stack to the kernel
* mode stack. Therefore, we masked the FLAG_FL.
* MSR_SYSCALL_MASK에 인터럽트 처리시 레지스터 상태를 저장
* 시스템콜 호출시 유저모드 -> 커널 모드 전환 과정에서 스택이 변경되는데, 커널모드에서 실행되야 하는 중요한 작업이 수행전까지는
* 인터럽트를 처리하지 않기 위해서, 인터럽트 서비스 루틴은 syscall_entry가 유저 모드 스택을 커널 모드 스택으로 교체 전까지
* 어떠한 인터럽트도 처리하지 않게 FLAG_FL을 마스킹함
* */
write_msr(MSR_SYSCALL_MASK, FLAG_IF | FLAG_TF | FLAG_DF | FLAG_IOPL | FLAG_AC | FLAG_NT);
// ******************************LINE ADDED****************************** //
// Project 2-2-2 : User Programs - System Call - File Descriptor
/* 파일 사용 시 lock 초기화 */
lock_init(&filesys_lock);
// *************************ADDED LINE ENDS HERE************************* //
}