유저프로그램 - 유저모드
: 유저프로그램에서 fork(스레드이름);
으로 fork 호출
fork()
: syscall1(fork의 시스템콜 번호, 스레드이름);
호출
/* lib/user/syscall.c 파일 */
pid_t
fork (const char *thread_name){
return (pid_t) syscall1 (SYS_FORK, thread_name);
}
syscall
syscall1()
: syscall(fork의 시스템 콜 번호, 스레드 이름);
호출
#define syscall1(NUMBER, ARG0) ( \\
syscall(((uint64_t) NUMBER), \\
((uint64_t) ARG0), 0, 0, 0, 0, 0))
syscall()
: CPU 레지스터 %rax
에 시스템 콜 번호, %rdi
에 스레드 이름을 넣고, syscall
실행
__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_;
__asm __volatile(
"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 실행 */
: "=a" (ret)
: "g" (num), "g" (a1), "g" (a2), "g" (a3), "g" (a4), "g" (a5), "g" (a6)
: "cc", "memory");
return ret;
}
syscall
: syscall-entry
실행 (핀토스 init.c에서 부팅시 실행되는 syscall_init에서 syscall 호출시 syscall-entry가 실행되도록 이미 설정되어있기 때문)
#define MSR_STAR 0xc0000081 /* Segment selector msr */
#define MSR_LSTAR 0xc0000082 /* Long mode SYSCALL target */
#define MSR_SYSCALL_MASK 0xc0000084 /* Mask for the eflags */
void
syscall_init (void) {
write_msr(MSR_STAR, ((uint64_t)SEL_UCSEG - 0x10) << 48 |
((uint64_t)SEL_KCSEG) << 32);
write_msr(MSR_LSTAR, (uint64_t) syscall_entry); /* 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. */
write_msr(MSR_SYSCALL_MASK,
FLAG_IF | FLAG_TF | FLAG_DF | FLAG_IOPL | FLAG_AC | FLAG_NT);
}
syscall-entry.S
:
tss에 저장된 커널스택의 스택포인터를 rsp 로 저장 (이제 rsp는 유저 스택이 아닌 커널 스택을 가리킴)
실제 CPU의 rsp 값이 변경되는 것 → 커널모드
시작
/* syscall-entry.S 부분 */
movabs $tss, %r12
movq (%r12), %r12
movq 4(%r12), %rsp /* Read ring0 rsp from the tss */
CPU 레지스터 값들을 커널 스택에 push : push 되는 순서는 마지막 들어간 데이터부터 interrupt frame 구조체 순서가 되도록 하는 순서로 push💡 이때 SEL_UDSEG는 유저 데이터 세그먼트, SEL_UCSEG는 유저 코드 세그먼트를 의미한다.
/* syscall-entry.S 부분 */
push $(SEL_UDSEG) /* if->ss */
push %rbx /* if->rsp */
push %r11 /* if->eflags */
push $(SEL_UCSEG) /* if->cs */
push %rcx /* if->rip */
subq $16, %rsp /* skip error_code, vec_no */
push $(SEL_UDSEG) /* if->ds */
push $(SEL_UDSEG) /* if->es */
push %rax
movq temp1(%rip), %rbx
push %rbx
pushq $0
push %rdx
push %rbp
push %rdi
push %rsi
push %r8
push %r9
push %r10
pushq $0 /* skip r11 */
movq temp2(%rip), %r12
push %r12
push %r13
push %r14
push %r15
movq %rsp, %rdi