ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [시스템 프로그래밍] File I/O
    개발 이야기/운영체제(OS)&시스템프로그래밍 2024. 10. 21. 00:34
    본 내용은 한양대학교 시스템 프로그래밍 강의, 조인휘 교수님의 자료를 바탕으로 하고 있습니다. 
    간단한 함수 및 명령어 설명만 작성되었으며 버전에 따라 틀린 내용이 있을 수도 있습니다.

     

    File Descriptors

    이전 내용에서 말한 것과 같이 non negative integer로 구성된 File을 구분하기 위한 개념이다. 즉, 파일 디스크립터는 운영 체제가 각 프로세스마다 관리하는 테이블의 인덱스이다. 이 테이블에는 파일 또는 입출력 자원에 관한 메타데이터가 저장되어있다. 

    파일을 이용 시 운영 체제는 해당 자원을 식별하는 파일 디스크립터를 반환하며 이를 통해 read, write, close 작업을 진행한다.

     

    기본적으로 모든 프로세스에는 0,1,2는 이미 할당되어있다. 

     

    • 표준 입력 (stdin): 파일 디스크립터 0
    • 표준 출력 (stdout): 파일 디스크립터 1
    • 표준 오류 (stderr): 파일 디스크립터 2

     

     

    File System

    Linux File system에서 각 file은 inode를 가지고 있다. 이는 distk에 저장되어있으며 meta data를 담고 있다. 

    • v-node: filesystem independent portion of the i-node
    • i-node: file owner, size, dev, ptrs to data blocks, etc -> meta data를 저장하는 자료구조

    v node의 경우 각 os에서 서로 다른 inode 형태 혹은 형식을 가지기에 OS에서 파일을 추샇와 하여 일곤된 방식으로 저장할 수 있게 해준다. 

    대부분의 Unix 계열 파일 시스템에서 vnodeinode를 래핑(wrapping)하는 방식으로 동작한다. 하지만 네트워크 파일 시스템(NFS)이나 다른 비전통적 파일 시스템에서는 inode 없이 vnode만 존재할 수 있다.

    open()

    #include <fcntl.h>
    int open(const char *pathname, int oflag, … /* , mode_t mode */);

     

    open은 파일을 열때 사용하는 함수로 return value는 미사용 파일디스크립터의 가장 작은 값을 반환한다. 즉, 3 이상의 값을 반환할 것이다. 

    creat(), close()

    #include <fcntl.h>
    int creat(const char *pathname, mode_t mode);

     

    creat은 파일을 생성하는 함수이다. 다만,  open에서도 아래와 같이 파일을 creat 할 수 있다. 

    open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

     

    close는 open된 파일을 닫는다. 다만, 프로세스가 종료 시 모든 파일은 커널에 의해 닫혀진다. 

    #include <unistd.h>
    int close(int filedes);

     

    read / write

    read()함수는 파일 디스크립터를 통해 파일 디스크립터에서 데이터를 읽어드리는 함수이다. 

    # include <unistd.h>
    ssize_t read(int filedes, void *buf, size_t nbytes);

     

    write()함수는 파일을 통해 데이터를 쓰는 함수이다.

    #include <unistd.h>
    ssize_t write(int filedes, const void *buf, size_t nbytes);

     

    이때, buf는 System Buffer와 다른 버퍼인 Appliation level Buffer이다. 

    시스템 버퍼는 운영체제에서 관리하는 버퍼로, 디스크에서 읽거나 기록하기 전에 거치는 버퍼이다. 자원을 더 효율적으로 관리하기 위해 사용하는 버퍼이다. 

    Appliation level Buffer는 프로그램이 직접 관리하는 메모리 공간이다. 이 또한 데이터를 임시로 저장하는 중간 저장소이다. 

    특징 시스템 버퍼 어플리케이션 버퍼
    관리 주체 운영 체제가 관리 프로그램에서 명시적으로 관리
    버퍼 사용 목적 디스크/네트워크 장치와 메모리 간의 성능 최적화 파일 디스크립터와 데이터를 주고받기 위한 임시 저장소
    버퍼 위치 운영 체제의 커널 메모리 영역 어플리케이션 메모리 영역
    자동 버퍼링 OS가 자동으로 버퍼를 관리 사용자가 명시적으로 버퍼를 할당하고 관리
    입출력 연산 호출 수  최소화 사용자가 직접 관리, 호출 될 때마다 실행

     

    lseek Function

    lseek() 함수는 파일 디스크립터에 연결된 파일 포인터(혹은 오프셋)을 이동시켜주는 함수이다.  

    #include <unistd.h>
    off_t lseek(int filedes, off_t offset, int whence);

     

    offest은 파일 위치 포인터를 얼마나 이동시킬지 지정한다. 

    이때 whence 옵션은 아래와 같다. 

    • SEEK_SET: 파일의 시작점에서부터 offset만큼 이동. 예를 들어, lseek(fd, 10, SEEK_SET)는 파일의 시작점에서 10바이트 떨어진 위치로 파일 포인터를 이동
    • SEEK_CUR: 현재 파일 위치에서 offset만큼 이동. 만약 offset이 0이면 파일 위치는 그대로 유지
    • SEEK_END: 파일의 끝에서부터 offset만큼 이동. offset이 음수일 경우, 파일 끝에서 앞으로 이동

    Atomic Operation: o_append, pread(), pwrite()

    파일에 여러 프로세스 혹은 스레드가 동시에 접근하려고 할 때 중요한 것은 automicity를 파일이 보장해야한다는 것이다. 즉 전체 작업이 순차적으로 기록되고 파일 시스템에 의해 데이터가 섞이거나 순서가 바뀌는 일이 없도록 해야한다. 

     

    이때 open에서 O_APPEND 플래그를 통해 파일을 열 때 원자성이 보장하도록 한다. 즉, 이를 통해 쓰기를 수행할 때마다 항상 파일의 끝에 데이터를 추가하게 한다. 

    fd = open("example.txt", O_WRONLY | O_APPEND | O_CREAT, 0644)

     

     pread()함수는 lseek() 함수와  read() 함수의 기능을 합한 것이고,  pwrite() 함수는 lseek() 함수와 write() 함수의 기능을 합한 것이다. 즉, 파일 디스크립터에서 지정한 offset 위치부터 데이터를 처리하며, 파일 위치 포인터를 이동하지 않고 파일에서 임의의 위치로부터 작동한다. 

    이 두 함수는 pos에 있는 데이터를 프로세스가 완료되기 전까지 연산이 도중에 인터럽트 걸리진 않는다. 

    #include <unistd.h>
    ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
    ssize_t pwrite(int filedes, void *buf, size_t nbytes, off_t offset);

     

    dup / dup2 

    두 system clal 모두 파일 디스크립터를 복제하는데 사용한다. 죽 이미 열린 파일을 복제하여 사용한다. 

    이때, dup()은 가능한 가장 낮은 번호의 사용하지 않은 파일 디스크립터를 반환하고, dup2는 파일디스크립터 번호를 지정 가능하다. 

    또한 dup()는 기존의 nuwfd와 동일한 파일디스크립터를 복제하고, dup2()는 열린 파일을 먼저 닫고 복제한다. 

    #include <unistd.h>
    int dup(int filedes);
    int dup2(int filedes, int filedes2);

     

    sync, fsync, fdatasync

    3 함수 모두 버퍼의 내용을 디스크에 동기화 하는 역할을 한다. 특히 write의 경우 버퍼와 디스크가 동기화 되어있지 않아 만약 데이터를 바로 디스크에 적용해야 하는 경우라면 이를 해결하기 위한 방법이 필요하고 이때 사용되는 API가 sync() 계열이다.

    #include <unistd.h>
    int fsync(int filedes);
    int fdatasync(int filedes);
    void sync(void);

     

     sync(): 메모리에 버퍼링된 모든 파일 시스템의 변경 사항을 디스크에 기록하도록 요청. 쓰기가 완료될 때까지 기다리지 않고 반환

    fysnc(): 특정 파일의 **데이터와 메타데이터(파일 속성 등)**를 디스크에 쓰고, 디스크 쓰기가 완료될 때까지 기다렸다가 반환

    fdatasync(): 파일의 데이터 부분만 디스크에 쓰고, 파일의 메타데이터(예: 파일 크기, 타임스탬프 등)는 동기화하지 않는다

    fcntl

    file의 속성 정보, meta 데이터 변경

    #include <fcntl.h>
    int fcntl(int filedes, int cmd, … /* int arg */);

     

    /dev /fd

    UNIX 및 Linux와 같은 시스템에서 파일 디스크립터를 처리할 때 사용하는 특수한 가상 디렉토리이다. 각 파일의 디슼크립터를 파일로 접근 가능할 수 있게 해준다. 이러한 접근 방식은 Unix/Linux 시스템에서 파일 디스크립터를 파일처럼 다룰 수 있게 한다. 

    즉 /dev/fd/n를 여는 것은 디스크립터 n을 복제하는 것과 동일하다. 

     

    예를 들어 아래의 두 코드는 동일한 역할을 한다. 

    fd = open("/dev/fd/0", mode);
    fd = dup(0);

     

    /dev/fd/n는 시스템에서 열려 있는 파일 디스크립터를 참조하는 파일이다. **파일 디스크립터 n**이 이미 열려 있다면, 이를 통해 디스크립터를 복제할 수 있기에. 즉, **open("/dev/fd/n", mode)**를 호출하는 것은 **dup(n)**을 호출하는 것과 동일한 효과를 낼 수 있느 것이다. 

Designed by Tistory.