jun-wiki

View My GitHub Profile

Posts (Latest 10 updated) :
Read all
Contents:
  1. dup2
    1. 준비 과정
    2. 헬퍼 함수
    3. dup2
    4. 관련 손볼곳
      1. __do_fork
      2. process_exit
    5. 여담

dup2 let’s go~

extra 마지막 이라 볼 수 있다

stdin/stdout은…

#define STDIN_FD  ((struct file*)-1)
#define STDOUT_FD ((struct file*)-2)

이딴 거 만들어서 사실 코드 구조는 크게 안달라졌다

initd나 그런 거 살짝 건드리긴 했는데 이 정도는 알아서 할 수 있으리라 믿으니 넘어가겠다

dup2 해야해서 바쁘다


dup2

기존 duplication말고 다른 거라 dup2다

기능 추가하는 것인데

fd 테이블은 복사하되 파일은 똑같은 거 참조하게 하는 거다

아주 간단하다

단, fork 할 시에 dup2한 관계가 유지되어야 한다는게 어려운 점이다

그리고 이를 해결하기 위해 기존 시스템콜 로직들 손봤다

close나 오픈할 떄에 cnt라는 숫자 올리고 내리게 해 이 숫자가 0되면 진짜로 close하게 말이다

이거 기존에 동기화 할때 써먹었고 웹서버 구현때에도 써먹었던 유구한 전통이다

이는 수박도에도 그려진 엄연한 사실이다

준비 과정

아무래도 카운트 관련해서 다루려면 구조체가 적합하다

다른 거 써서 할 줄 모르긴 함 ㅇㅇ;;

struct file_ref {
  struct file *fp;
  int refcnt;
  struct list_elem elem;
};

static struct list file_ref_list;
static struct lock  file_ref_lock;

리스트도 만들고 락도 만들어줬다

기존 락을 쓰지 않은 이유도 존재한다

터지는지는 모르겠지만

일단 ref_list나 cnt만 살짝 건드릴때 락 필요한데

앞에 write니 read니 뭐니 하는애들이랑 락 공유시 꽤 많이 기다리거나 해야한다

그래서 걍 두개 만들었다

만들 수 있다면 많이 만드는 거 선호하는 편이라서 ㅇㅇ

기본 로직은 refcnt (참조 카운터라는 뜻) 가지고 올리고 내리며 참조 파일 열고 닫기 관리하는 거다

동기화 할때 몇번 써먹은 (그때는 1 아니면 0 수준이기는 했다) 방법이다


헬퍼 함수

하나하나 값 내리고 올리고 하다 보니 어지러워 따로 빼고

stdin/stdout도 관리 맡겼다

매크로 때문에 사소한 이슈도 있었지만 무식하게 해결했다

만든 리스트 뒤지는 ref_find 만들었고

선형 탐색이라 좀 걸리지만 미미하다

static struct
file_ref *ref_find(struct file *fp) {
  struct list_elem *e;
  for (e = list_begin(&file_ref_list); e != list_end(&file_ref_list); e = list_next(e)) {
    struct file_ref *r = list_entry(e, struct file_ref, elem);
    if (r->fp == fp) return r;
  }
  return NULL;
}

그리고 중요한 두 개 함수로

fdref_incfdref_dec만들었다

파일 디스크립터 참조 증가/감소 라는 뜻이다

void
fdref_inc(struct file *fp) {
  /* 제외를 이따구로 해놓은 이유는 매크로는 다른곳에 정의 안되어 있어서 무식하게 해놓음 */
  if (fp == (struct file*)-1 || fp == (struct file*)-2) return; 
  lock_acquire(&file_ref_lock);
  struct file_ref *r = ref_find(fp);
  if (!r) {
    r = malloc(sizeof *r);
    r->fp = fp;
    r->refcnt = 1;
    list_push_back(&file_ref_list, &r->elem);
  } else {
    r->refcnt++;
  }
  lock_release(&file_ref_lock);
}

// 카운트 down
void
fdref_dec(struct file *fp) {
  /* 얘도 마찬가지 */
  if (fp == (struct file*)-1 || fp == (struct file*)-2) return;
  lock_acquire(&file_ref_lock);
  struct file_ref *r = ref_find(fp);
  ASSERT(r != NULL);
  if (--r->refcnt == 0) {
    list_remove(&r->elem);
    lock_release(&file_ref_lock);
    // 마지막 참조 해제 시 실제 close
    lock_acquire(&filesys_lock);
    file_close(fp);
    lock_release(&filesys_lock);
    free(r);
    return;
  }
  lock_release(&file_ref_lock);
}

inc의 경우 ref리스트 뒤져서 없으면 새로 만들어 리스트에 넣고

있으면 ref카운터 올려준다

dec는 리스트에서 ref 찾고 카운터 깎는다

만약 0이 되버리면 파일 닫고 free로 날려버리는 처리까지 담당한다

이거 두개로 기존 복사나 그런거 처리 ㄱㄴ

그리고 처음에는

fp == STDIN_FD || fp == STDOUT_FD

따위로 해놨는데

process.c에서 쓰거나 하려니까 매크로도 빼줘야 할 판이라

그냥

#define STDIN_FD  ((struct file*)-1)
#define STDOUT_FD ((struct file*)-2)

이거 그대로 떼왔다

참고로 -1, -2로 한 이유는 기존에 정수들 쓰려니 겹쳐서 말았다


아무튼, 사실 이거 헬퍼 함수 inc, dec 구현하면 끝이라고 보면 된다

다음 부턴 기존 open이나 참조만 살짝 만져주면 끝!

가장 중요한건 process.c에 다 있다


dup2

static int
system_dup2(int oldfd, int newfd) {
/* ---------- 조건문 및 설정 ------------- */
  struct thread *t = thread_current();
  if (oldfd < 0 || oldfd >= t->fd_cap) return -1;
  struct file *oldf = t->fd_table[oldfd];
  if (!oldf) return -1;

  if (newfd < 0) return -1;
  if (newfd >= t->fd_cap) return -1;
  if (oldfd == newfd) return newfd;
/* --------------------------------------- */

  // 만약 newfd가 열려있다면 슬쩍 닫아주기 (GitBOOK에 나와있는 조건)
  if (t->fd_table[newfd]) system_close(newfd);

  t->fd_table[newfd] = oldf;
  fdref_inc(oldf);
  return newfd;
}

뭐 많아 보이는데 솔직히 말하면 뭐 없다

범위 벗어났을 때랑 fd 두개 똑같은 거 주거나 하는 거 제외하는 조건문이랑

fd좀 선언하고 설정 해주는게 끝이다 핵심 동작은

  t->fd_table[newfd] = oldf;
  fdref_inc(oldf);
  return newfd;

이 3개가 끝이다

fd테이블 새꺼 기존에 파일 참조로 바꾸고

기존 파일 참조 ref_inc 올리고

새 파일 디스크립터 반환하면 끝~

아주 간단하다

cnt 이거이거 bb


관련 손볼곳

process.c꽤 손봐야 한다

__do_fork할 때랑 process_exit가 중요하다

__do_fork

원래는 그냥 다 duplicate 했는데 이렇게 하면 dup2의 경우는 파일 하나 참조가 아닌 각자 참조로 변경되는 불상사가 존재한다

그거 막으려고 inc, dec만들어 따로 전역 함수로 뺀거다


process_exit

원래는 그냥 file_close하고 가리키는 포인터 NULL 처리였는데

NULL 처리는 냅두고 file_close대신 fdref_dec로 대체해서

file_close랑 cnt–를 통해 dup2 완벽 처리~


대충 이정도 처리해주면 dup2 끝이다

물론, 실제 변경사항은 stdin/stdout때문에 좀 더 있긴 한데…

그건 사람마다 처리 방식이 달라 예외로 하겠다



여담

dup2 통과했는데

lock 함부로 걸어가지고 기존 케이스들 fail뜨는 거 잡기가 힘들었다…

그리고 97개 중에 갑자기 대다수가 박살나서 새로 디버깅하러가야한다 ㅅㄱ