jun-wiki

View My GitHub Profile

Posts (Latest 10 updated) :
Read all
Contents:
  1. 실제 현재 구현된 흐름
    1. 전체 흐름
      1. 1) 커맨드 파싱 → 자식 생성 & 대기 (부모)
      2. 2) 자식 스레드에서 process_exec (ELF 로드 → 스택 구축 → 유저모드 진입)
      3. 3) 사용자 코드가 exit()/write() 호출 (유저→커널 트랩)
      4. 4) 커널 syscall_entry → syscall_handler() (디스패처)
      5. 5) process_exit()에서 자원정리 + “exit(…)” 출력 + sema_up
      6. 6) 부모 process_wait() 정상 반환 → run_task() 마무리

흐름 구상하고 구조체 만들 거 계획하며

예상으로 적어놓은 흐름이 있다

예상흐름 ☜(⌯•ᴗ<⌯)☜

이 흐름과 실제 구현이 어떻게 차이가 나고 동일한지 보여주겠다

실제 현재 구현된 흐름

사실 똑같다!!!

계획대로 잘 흘러갔다 d ( ܸܸ ì ·̫ í ⑅ )b

그치만 이렇게 끝내면 안되니까 어떻게 구현했길래 흐름이 동일한지 설명하겠다


전체 흐름

1) 커맨드 파싱 → 자식 생성 & 대기 (부모)

  • 파일: threads/init.c

  • 함수: run_task(const char *cmdline)

    • 쉘/테스트가 넘겨준 cmdline을 그대로 자식 프로세스 생성 API에 전달

    • 즉시 대기:

      int status = process_wait(tid);
      printf("Execution of '%s' complete.\n", cmdline);
      
    • -q 옵션이면 마지막에 power_off()


부모가 하는 일은 “만들고(wait) 끝까지 기다린다”.


2) 자식 스레드에서 process_exec (ELF 로드 → 스택 구축 → 유저모드 진입)

  • 파일: userprog/process.c

  • 함수: process_exec(void *f_name)

    1. 커맨드라인에서 프로그램 이름/인자 분리 (필요하면 strtok_r 등)

    2. struct intr_frame if_; memset(&if_,0,…)

    3. load(...) 호출

      • ELF 헤더 검증/세그먼트 매핑

      • 성공 시 if_.rip = ehdr.e_entry;

    4. 사용자 스택 빌드 (setup_stack(...) + 인자 적재)

      • 문자열 복사, argv[] 포인터 배열 푸시, argv[argc]=NULL, 워드 정렬

      • if_.R.rdi = argc; if_.R.rsi = argv; (System V AMD64 ABI)

      • if_.rsp = 새 스택 최종 위치

    5. 유저모드로 점프:

      do_iret(&if_);     // iretq로 CPL3 전이 (반환하지 않음)
      __builtin_unreachable();
      
    6. 실패 경로에서는 thread_exit();로 종료


여기서부터 사용자 코드 _start → main → exit()유저모드에서 돌아간다


3) 사용자 코드가 exit()/write() 호출 (유저→커널 트랩)

  • 파일: lib/user/syscall.c

  • 동작: 각 래퍼가

    • RAX ← 시스템콜 번호, 인자 RDI, RSI, RDX, R10, R8, R9에 적재

    • syscall 명령 실행 → CPU가 커널 진입점으로 점프



4) 커널 syscall_entrysyscall_handler() (디스패처)

  • 파일: userprog/syscall.c (+ intr-stubs.S)

  • 흐름

    • syscall_entry(어셈블리 스텁)가 레지스터/스택 저장 후 syscall_handler(struct intr_frame *f) 호출

    • 디스패처 예:

      switch (SC_NO(f)) {
        case SYS_EXIT: {
          int status = (int)ARG0(f);
          sys_exit(f, status);      // 아래 5번 흐름으로
          __builtin_unreachable();  // thread_exit()로 돌아오지 않음
        }
        case SYS_WRITE: {
          int fd = (int)ARG0(f);
          const void *ubuf = (const void*)ARG1(f);
          unsigned size = (unsigned)ARG2(f);
          if (fd == 1) { putbuf(ubuf, size); RETVAL(f) = size; }
          else { /* 파일FD면 파일시스템 경로 */ }
          break;
        }
        default:
          RETVAL(f) = -1;
      }
      
    • (포인터 인자 접근 전 유저 포인터 검증 / is_user_vaddr, get_user() 등 필수)



5) process_exit()에서 자원정리 + “exit(…)” 출력 + sema_up

  • 파일: userprog/process.c (또는 syscall.c에서 thread_exit() 전에 호출)

  • 호출 경로: sys_exit()thread_exit() → (USERPROG일 때) process_exit() (혹은 sys_exit()에서 직접 process_exit() 호출 후 thread_exit())

  • 해야 할 일(프로젝트2 필수)

    • 현재 쓰던 실행 파일 close(쓰기락 해제), 열린 파일/디스크립터 정리

    • 페이지 테이블/VM 해제

    • 종료 메시지 출력: printf("%s: exit(%d)\n", thread_name(), status);

    • 부모 대기 해제:

      struct child_status *cs = thread_current()->my_status;
      if (cs) {
        cs->exit_code = status;
        cs->exited = true;
        sema_up(&cs->sema);           // 부모의 process_wait() 깨움
        if (--cs->ref_cnt == 0) free(cs);
      }
      
    • 마지막에 thread_exit();로 스케줄러에 자신을 넘김



6) 부모 process_wait() 정상 반환 → run_task() 마무리

  • 파일: userprog/process.c

  • 함수: int process_wait(tid_t child)

    • current->children에서 child_status 선형 탐색

    • 없거나 이미 waited==true-1

    • cs->waited = true; sema_down(&cs->sema); // 자식 종료까지 블록

    • 깨어나면 status = cs->exit_code;

    • list_remove(&cs->elem); if (--cs->ref_cnt == 0) free(cs);

    • 반환: 자식 종료코드

  • 부모(run_task) 는 반환 코드로 진행, 테스트 드라이버가

    • Execution of '…' complete. 출력

    • -qpower_off()로 종료