본문 바로가기
정글 2기/OS 운영체제

[Pintos] Project 1, 2_Argument parsing, User Program_system call_code analysis

by Dean30 2021. 10. 14.
728x90

[Pintos] Project 1, 2_Argument parsing, User Program_system call_code analysis

 

1 ARGUMENT PASSING

*목표

명령어와 함께 들어온 인자들을 user program의 user stack에 정해진 순서에 따라 넣어준다.

*예시

  • 'bin/ls -l foo bar'이 커맨드라인에 들어오면 argv[0] = 'bin/ls\0', 'argv[1] = -l\0', argv[2] = 'foo\0', argv[3] = 'bar\0'으로 parsing(문자열 분리)한 뒤에 아래와 같이 스택에 넣어준다.
  • | argv[3] | ... | argv[0] | padding for 8 배수 | NULL pointer for argv[argc=4] | pointer for argv[3] | ... | pointer for argv[0] | return address = NULL |
  • 그림 첨부해라!*코드1 file_name을 parsing하는 코드
    char *argv[30];
    int argc = 0;
    char *token, *save_ptr;  // argc = N+1; argv = {filename, arg1, ... argN}로 parsing
    token = strtok_r(file_name, " ", &save_ptr);
    while (token != NULL)
    {
      argv[argc] = token;
      token = strtok_r(NULL, " ", &save_ptr);    // 두번째 parsing부터는 첫번째 인자를 NULL로 한다
      argc++;
    }
    2 strtok_r 함수: 문자열 파싱하는 함수
    char *
    strtok_r (char *s, const char *delimiters, char **save_ptr) {
      char *token;
      ASSERT (delimiters != NULL);
      ASSERT (save_ptr != NULL);
      /* If S is nonnull, start from it.
         If S is null, start from saved position. */
      if (s == NULL)
          s = *save_ptr;
      ASSERT (s != NULL);
      /* Skip any DELIMITERS at our current position. */
      while (strchr (delimiters, *s) != NULL) {
          /* strchr() will always return nonnull if we're searching
             for a null byte, because every string contains a null
             byte (at the end). */
          if (*s == '\0') {
              *save_ptr = s;
              return NULL;
          }
          s++;
      }
      /* Skip any non-DELIMITERS up to the end of the string. */
      token = s;
      while (strchr (delimiters, *s) == NULL)
          s++;
      if (*s != '\0') {
          *s = '\0';
          *save_ptr = s + 1;
      } else
          *save_ptr = s;
      return token;
    }
    2 parsed arguments를 user stack에 넣어주는 함수
    void load_userStack(char **argv, int argc, void **rspp)
    {
      // 1. Save argument strings (character by character)
      for (int i = argc - 1; i >= 0; i--)
      {
          int N = strlen(argv[i]);
          for (int j = N; j >= 0; j--)
          {
              char individual_character = argv[i][j];    // argv - i번째 스트링의 j번째 character; '\0'부터 거꾸로 시작
              (*rspp)--;        // (*rspp) = _if.rsp (unsigned long int: 8 byte 정수로 주소값 표현?) --> _if.rsp 1 감소 (1 byte 전 주소값으로 이동?)
              **(char **)rspp = individual_character; // rspp를 (char **)로 캐스팅하면, **(char **)rspp = *(char *)rsp = (char)(메모리)
          }
          // *(char **)rspp 는 (char *)형 포인터!
          argv[i] = *(char **)rspp; // (char *) 포인터..push this address too; 3번에 사용할 것
      }
      // 2. Word-align padding
      int pad = (int)*rspp % 8;        // (*rspp): _if.rsp 주소값 --> 8로 나눈 나머지
      for (int k = 0; k < pad; k++)    // pad만큼 주소값--; 주소 번지를 8의 배수 맞추기 위해
      {
          (*rspp)--;                    // _if.rsp 주소값 -1 이동
          **(uint8_t **)rspp = (uint8_t)0; // _if.rsp가 가리키는 내용물으 0으로 채움(1 byte)
      }
      // 3. Pointers to the argument strings
      size_t PTR_SIZE = sizeof(char *);
      (*rspp) -= PTR_SIZE;            // _if.rsp를 8byte 내림
      **(char ***)rspp = (char *)0;    // NULL포인터로 채움 argv[argc]
      for (int i = argc - 1; i >= 0; i--)
      {
          (*rspp) -= PTR_SIZE;        // 8byte 이동하면서 각 argv[i]가 저장된 스택의 주소값(char *) 기록(위-위 for문 마지막에 저장한 것)
          // 하고싶은 것: _if.rsp가 가리키는 곳에 argv[i] (char * 자료형) 을 기입하고 싶음 --> *(char **)_if.rsp = argv[i] 를 해주고 싶은 것! <-- 캐스팅하는 이유는 (char *)를 기록해야 하므로
          **(char ***)rspp = argv[i];    // argv[i]는 (char *)이고, *rspp가 _if.rsp --> (char ***)rspp 로 캐스팅하면
                                      //                            rspp는 (char ***), *rsp == _if.rsp는 (char **)형으로 캐스팅된 것..!: 원하는 결과
                                      // 즉, *(char **)_if.rsp = argv[i]   <==>  **(char ***)rspp = argv[i]
      }
      // 4. Return address
      (*rspp) -= PTR_SIZE;
      **(void ***)rspp = (void *)0;    // 마지막 return add도 void형 포인터 NULL 기입
    }
    2 SYSTEM CALLS각 system call 함수의 흐름을 정리했다- halt()- exit()- fork()```
    (부모 스레드)
    process_fork(thread_name, intr_frame)
  • exit() --> thread_exit() --> do_schedule(THREAD_DYING)
  • halt() --> power_off()
  • -> memcpy(&cur->parent_if, if_,): to pass this intr_frame down to child
  • -> thread_create(, __do_fork, cur)
       --> (자식 스레드)
       --> do_fork(cur) 
               --> 1. 부모 스레드의 cpu context 복사(intr_frame)
                       --> 2. page table 복사
                       --> sema_up(&current->fork_sema): wake up parent process (child loaded successfully)
                       --> do_iret(): switch to newly created process
                       --> 종료
  • -> sema_down(&child->fork_se ma): child thread가 실행되어 __do_fork에서 sema_up()하기까지 기다림
  • -> return tid
    #### - exec()
    fn_copy = palloc_get_page(): 파일 이름 동적할당하여 복사할 공간 확보
    strlcpy(): 복사 후 넘겨주기(process_cleanup()에서 다 날라가나?)
    process_exec(fn_copy)
       --> intr_frame 생성
       --> process_cleanup(): kill the current context
       --> argument parsing(게시글 맨 위 argument pasging 부분 참고)
       --> load(file_name, &_if): 실행파일을 메모리에 로딩
       --> load_userStack(): parsed arguments를 userstack에 올림
       --> do_iret(): switch process
    #### - process_wait()
    get_child_with_pid()
    sema_down(&child->wait_sema): 자식 스레드 process_exit() 호출할때까지 멈춤
       --> (자식 스레드) process_exit()
       --> ... process_cleanup()
       --> sema_up(&cur->wait_sema): process_wait()으로 잠든 부모 스레드 깨움
       --> sema_down(&cur->free_sema): 부모 스레드가 자식 리스트 지울 때까지 기다림
       <--
    exit_status = child->exit_status : 자식 스레드의 종료 status 기록(child->exit_status는 자식 스레드가 syscall.c/exit()호출할때 변경)
    list_remove(): 자식 스레드 리스트에서 제거
    sema_up(&child->free_sema): 자고 있는 자식 스레드 깨움
       --> 자식 스레드 process_exit() 종료
    #### - create()
    filesys_create(file, initial_size)
  • -> 1. dir_open_root(): 루트 디렉토리 inode를 메모리로 로딩
       --> inode_oped(ROOT_DIR_SECTOR): 루트 섹터에서 inode를 읽어들임. ROOT_D_S = 1
               --> 이미 열려있는 inode인지 확인 후 reopen(): open_inodes 리스트 순회
               --> 아닌 경우, inode 메모리 동적할당
               --> inode 초기화: open_inodes 리스트에 추가, 기본 멤버 초기화
               --> disk_read(): 디스크 SECTOR에 저장된 정보를 inode->data에 저장
               --> return inode
       --> dir_open(inode): dir 자료구조 메모리 할당 및 inode 연결
  • -> 2. free_map_allocate(1, &inode_sector): 디스크에서 1 sector 할당(생성할 파일 inode 기록하기 위한 공간?)
  • -> 3. inode_create(inode_sector, initial_size): file의 inode를 작성하고(메모리에서) 디스크(inode_sector)에 기록
       --> disk_inode 동적할당(디스크에 쓸 자료구조)
       --> disk_inode멤버 초기화: 파일 크기 크기, 등
       --> free_map_allocate(): 파일 크기에 해당하는 sector 수만큼 디스크 할당 + 시작 pointer update
       --> disk_write(): 디스크에 disk_inode 기록
       --> disk_inode 동적 해제
  • -> 4. dir_add(): 열려 있는 루트 디렉토리에 새로 만든 파일 아이노드 엔트리 추가하고 디스크에 기록
       --> lookup(): DIR 내 동일 이름 파일 있는지 검사
       --> dir->inode에서 비어있는 엔트리 찾는다: 해당 ofs(오프셋)값을 찾는다
       --> dir->inode 해당 엔트리에 생성된 파일 inode정보(멤버) 기록: in_use, name, sector 등
       --> inode_write_at(): 디스크에 저장
  • -> 5. dir_close(): 루트 디렉토리 inode메모리 해제
       --> inode_close(dir->inode): inode.open_cnt를 하나 내리고 0이 되면 비로소 메모리에서 지운다
               --> list_remove(): open_inodes 리스트에서 지운다
               --> 디스크에서 해당 섹터를 할당 해제한다(inode->sector: inode자체, inode->data: inode에 해당하는 파일)
               --> free(inode)
       --> free(dir)
    
    

- remove()

filesys_remove()

--> dir_open_root(): root 디렉토리 연다
--> dir_remove(): 디렉토리에서 해당 엔트를 사용안함처리하고, 아이노드 removed=true 처리
        --> lookup(): 디렉토리에서 파일 이름을 가진 엔트리 찾는다(offset)
        --> inode_open(): 엔트리에 해당하는 아이노드를 연다
        --> e.in_use=true: 해당 엔트리 사용안함처리
        --> inode_write_at(): 디렉토리의 해당 엔트리 갱신(사용안함처리했으므로)
        --> inode_remove(inode): 해당 아이노드를 deleted 처리
                --> inode->removed=true
--> inode_close(inode): inode->opencnt를 하나 내리고 0이 되면 메모리에서 내린다
--> dir_close(): 루트 디렉토리를 닫는다

- open()

file을 찾으려면 dir를 이용해야 한다. 이 과정에서 dir를 열어서 찾고 닫아야 한다.

filesys_open(): 파일을 연다
        --> dir_open_root(): 루트 디렉토리 연다
        --> dir_lookup(): 디렉토리 엔트리 검색 -> 찾은 파일의 inode를 open_inodes리스트에 추가
        --> dir_close(): 루트 디렉토리 닫는다
        --> file_open(inode): 메모리에 file 자료구조 할당 후 해당 파일의 inode 포인팅 및 초기화
                --> file 구조체 동적 할당
                --> 파일 멤버 초기화: inode와 연결
add_file_to_fdt(): 현재 실행 스레드의 FDT(file descriptor table)의 비어있는 인덱스에 파일 할당

- filesize()

find_file_by_fd(): 현재 실행 스레드의 fdt에서 fd번째 파일 찾는다
file_length(): 해당 파일의 길이(파일 -> inode -> inode->data.length)
             --> inode_length(file->inode) --> inode->data.length

- seek()

find_file_by_fd(): 현재 실행 스레드의 fdt에서 fd번째 파일 찾는다
fileobj->pos = position: 다음에 읽을 곳(pos)을 변경한다. 참고로 file 시작점이 0이다.

- tell()

find_file_by_fd(): 현재 실행 스레드의 fdt에서 fd번째 파일 찾는다
file_tell(): 현재 파일에서 다음에 읽을 곳 반환
        --> file->pos

- close()

find_file_by_fd(): 현재 실행 스레드의 fdt에서 fd번째 파일 찾는다
fd = 0 || fileobj == STDIN : 표준 입력인 경우, stdin_count -1
fd = 1 || fileobj == STDOUT: 표준 출력인 경우, stdout_count -1
remove_file_from_fdt(): fdTable[fd] 를 NULL로 만들어서 fdTable에서 없앰
CASE1: dupCount == 0 --> file_close
CASE2: dupCount != 0 --> dupCount -1

- read()

find_file_by_fd(): 현재 실행 스레드의 fdt에서 fd번째 파일 찾는다
CASE1: STDIN  --> input_getc()로 한글자씩 버퍼에 복사
CASE2: STDOUT --> X
ELSE : 
        lock_acquire(rw_lock): rw 접근 하나의 스레드만?
        file_read() -> inode_read_at(): read SIZE byes from INODE into BUFFER; 어렵다
        lock_release(rw_lock)

- write()

find_file_by_fd(): 현재 실행 스레드의 fdt에서 fd번째 파일 찾는다
CASE1: STDIN  --> X
CASE2: STDOUT --> putbuf()로 출력?
ELSE : 
        lock_acquire(rw_lock): rw 접근 하나의 스레드만?
        file_write() -> inode_write_at(): write SIZE bytes from BUFFER into INODE; 어렵다
        lock_release(rw_lock)

- dup2()

Creates 'copy' of oldfd into newfd

find_file_by_fd(oldfd): 현재 실행 스레드의 fdt에서 oldfd번째 파일 찾는다
find_file_by_fd(newfd): 현재 실행 스레드의 fdt에서 newfd번째 파일 찾는다
fileobj (oldfd 해당 파일)이 STDIN, STDOUT, 이외 경우 처리
close(): newfd번째 파일 닫기
fdt[newfd] 에 fileobj (oldfd 해당 파일) 넣기

 

 

 

참조 - special thanks to 노군

728x90

댓글