ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • file descriptor (fd)
    운영체제 2025. 5. 3. 13:11

    file descriptor란? (fd)

    • 운영 체제에서 open()한 파일을 식별하는 정수값이다. fd로 줄여서 사용하기도 한다.
    • fd를 통해 해당 파일 읽기/쓰기/삭제/복제 등의 작업을 할 수 있다.
    • 기본적으로, fd값 0,1,2는 아래와같이 예약된 값이다. 세 값은 각각 표준 입출력 스트림들을 뜻한다.
      • 0: 표준 입력 (stdin)
      • 1: 표준 출력 (stdout)
      • 2: 표준 오류 (stderr)

     

    file descriptor를 받기까지의 과정

    user모드에서 open() 호출시 파일 경로와 read/write 여부를 인자에 넣고 fd를 리턴받는다.

    open 호출후 리턴받기까지 커널의 내부 작동 과정을 알아보자.

    int fd = open("sample.txt", O_RDONLY);

     

    - file descriptor 관련 내부 구조

    작동 과정을 알기 위해서는 내부 구조를 이해해야 한다.

    아래 그림은 open()을 실행하는데에 필요한 테이블들이다.

    오른쪽부분에서 왼쪽부분으로 갈수록 물리공간이 추상화된다.

    테이블은 크게 v-node table, open file table, descriptor table이 있다.

     

     

    v-node table : 파일 시스템 객체를 커널이 추상화해서 관리하는 구조체이다. 모든 파일 시스템에 대한 공통 구조가 저장되어있다.

    open file table : 프로세스가 파일을 어떻게 열었는지에 대한 정보를 저장한다. v-node 포인터, 파일 offset, read/write여부, ref count(파일을 open한 프로세스 수)를 기록한다.

    Descriptor table : fd와 open file table을 연결하는 정보를 담은 테이블이다.

     

    - 예약된값 0,1,2는 보통 터미널과 연결되어있다.

    위 그림을 보면, Descriptor table에서 fd가 1인 포인터가 최종적으로 모니터를 가리킨다는 것을 알수 있다.

    반면 fd가 4 이상인 경우 disk와 연결되어있는데, disk에 저장되어있는 어떤 파일을 가리킨다.

     

    - open()호출 후 fd 받기까지의 과정

    1. user가 open("sample.txt", O_RDONLY)을 호출한다

    2. 커널이 sample.txt의 경로에 맞는 v-node를 찾는다. 없으면 v-node를 새로 생성한다.

    3. open file table을 새로 생성한다.

    4. 생성한 open file table을 해당 프로세스의 descriptor table에 연결한다. 이때 fd를 할당하여 user에게 fd를 전달한다.


    file sharing : 서로 다른 fd가 같은 파일에 접근하기

    서로 다른 fd가 같은 디스크 파일을 공유하고싶을 수 있다. 그 예로 .so 라이브러리가 있다.

    이 경우 아래와 같은 구조가 된다.

     

    기존방식과 다르게 두개의 open file table이 하나의 v-node table을 공유하고 있다.


    부모-자식 프로세스와 file descriptor

    부모 프로세스에서 자식 프로세스를 만들때 fd를 중심으로 어떻게 작동하는지 알아보자.

     

    fork() 이전

    부모가 자식프로세스를 복제하기 전의 상태다. 부모프로세스가 fd 1과 4를 사용하고 있다.

     

     

     

    fork() 이후

    fork()를 호출하여 자식프로세스가 복사된 후의 상태다.

    부모의 descriptor table이 복사되어 자식은 별개의 테이블을 가진다. 자식도 부모와 같은 fd값을 갖게된다.

    자식의 fd들 또한 부모와 같은 open file table 엔트리를 가리킨다. 즉, 부모와 자식은 같은 파일을 공유하게 된다.

    만약 자식이 파일 write 위치를 변경하면 부모도 변경되며, 파일 모드도 변경하면 부모에게 영향을 준다.

    Open file table의 refcnt(해당 파일을 참조한 프로세스 수)가 1씩 늘어났다.

     

     


    관련 함수 : dup()

    dup() - descriptor table 참조 변경하기

    dup()함수는 어떤 fd가 특정 fd가 가리키는 open file table을 참조하도록 변경하는 함수다. 

    아래와 같이 fd의 참조가 변경된다. 본 그림은 아래에서 다시 설명한다.

     

    dup() 형태

    #include <unistd.h>
    
    // 기존fd가 가리키는 open file table을 참조하는 fd 생성
    int dup(int fd);
    
    // 기존oldfd가 가리키는 open file table을 참조하도록 newfd의 포인터 변경
    int dup2(int oldfd, int newfd);

     

    사용 예시

    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    int main() {
        int fd = open("output.txt", O_WRONLY | O_CREAT, 0644); 
    
        dup2(fd, 1); 
    
        printf("This will go to the file!\n");
    
        return 0;
    }

     

    실행 후 output.txt 파일 내용

    This will go to the file!

     

    터미널에 출력되어야 할 값이 output.txt에 저장되었다.

    이는 dup2() 때문인데,

    fd가 1이면 stdout을 뜻하기 때문에 dup2(fd, 1)를 호출하면 printf출력값은 output.txt에 저장된다.

     

    위 실행을 그림으로 나타내면 다음과 같다.

    dup2(4,1) 호출 전 기존 구조

    (위에서 받은 fd를 4라고 가정하자)

    터미널출력을 가리키는 fd 1과 output.txt를 가리키는 fd 4가 테이블에 적혀져있다.

     

    dup2(4,1) 호출시 변화

    fd 1이 가리키는 곳을 output.txt로 변경한다. 이로 인해 printf, 즉 stdout함수를 호출하면 그 값이 output.txt에 저장된다.

     


    활용하기 : cat input.txt | wc -l 작동 원리

    터미널 명령어 '|' 는 pipe 연산자다.

    해당 명령어는 input.txt 내용의 라인 수를 출력한다.

    cat input.txt | wc -l

     

    이 연산자는 pipe() + fork() + dup2() + exec() 조합으로 구현된다.

     

    - 세부 원리

    cat 프로세스의 fd 1과 wc 프로세스의 fd 0 두가지 모두 같은 pipe버퍼에 접근한다.

    cat input.txt를 통해 input.txt 파일 내용을 버퍼에 write하고, wc -l 를 통해 버퍼에 있는 것을 read하여 라인 수를 센다.

     

    - 대략적 순서

    1. pipe() : 파이프 생성

    int pipefd[2];
    pipe(pipefd);  // pipefd[0]: read end, pipefd[1]: write end

    2. fork() : 두 프로세스 생성

    3. dup2() : 버퍼 공유하도록 fd의 참조 변경

    cat input.txt

    if (fork() == 0) {
        dup2(pipefd[1], 1);  // stdout → pipe write end
        close(pipefd[0]); close(pipefd[1]);
        execlp("cat", "cat", "input.txt", NULL);
    }

    wc -l

    if (fork() == 0) {
        dup2(pipefd[0], 0);  // stdin → pipe read end
        close(pipefd[0]); close(pipefd[1]);
        execlp("wc", "wc", "-l", NULL);
    }

     

     

     

     

     

     

    '운영체제' 카테고리의 다른 글

    IPC - signal(), kill()  (0) 2025.05.24
    가상 메모리와, 공유메모리 / COW와 mmap()  (0) 2025.05.10
    IPC - pipe  (0) 2025.04.26
    library interpositioning  (0) 2025.04.18
    [OS] unix I/O와 standard I/O 함수  (0) 2025.04.12
Designed by Tistory.