CSAPP(10) - System-Level I/O

업데이트:

1. Unix I/O

  • 리눅스 파일은 바이트의 시퀀스이다.
  • 모든 입출력 장치는 파일로 모델링되어 있다.
    • 모든 입출력은 파일에 읽거나 쓰는 동작이다.

파일 열기

  • 앱은 커널에 파일 열기를 요청
  • 커널은 descriptor라 하는 음수아닌 정수를 반환
    • 서술자는 파일을 식별한다.
  • 리눅스 셸로 시랭된 프로세스는 표준입력(서술자 0), 표준출력(서술자 1), 표준에러(서술자 2) 3가지 파일을 열면서 시작된다.

파일 위치를 변경

  • 커널의 파일 위치르 유지한다. 위치는 최초 0이다.
  • 파일 위치는 파일 시작점부터의 바이트 오프셋이다.
  • seek 연산

파일 읽고 쓰기

  • 파일에서 메모리로 n 바이트를 복제하는 연산
  • 현재 위치 k에서 시작해서 n만큼 증가시킨다.
  • m바이트 크기의 파일이 있을 때, k >= m 이면 end-of-life(EOF)이다.
    • 명시적인 EOF 문자는 없다.
  • 쓰기 연산도 유사하게 n바이트를 메모리에서 파일로 복제한다.

파일 닫기

  • 앱이 커널에 닫기 요청을 한다.
  • 커널은 파일 열때 생성한 자료구조를 해제하고 서술자를 사용 가능한 서술자 풀에 반납한다.
  • 프로세스가 어떤 이유든 종료되면, 커널은 열린 파일을 닫고 메모리 자원을 해제한다.

2. Files

리눅스 파일은 type이 있다.

  • regular file은 임의 데이터를 가진다.
    • text file은 아스키 또는 유니코드 문제로 이루어진 파일
      • 텍스트 라인으로 이루어져 있고, 각 라인은 개행문자로 끝난다. 개행문자 \n는 ascii line feed, 숫자 0x0a와 같다.
    • binary file은 나머지
    • 커널은 두개를 구분하지 않음
  • directory 파일은 link의 배열로 이루어진다.
    • 링크는 파일(다른 디렉토리)에 파일명을 매핑한다.
    • 최소 두개가 있다. ., ..
  • socket은 네트워크를 동해 다른 프로세스와 통신하는데 쓰이는 파일이다.
  • 이외에도 named pipes, symbolic links, charater, block devices 등이 있다.

리눅스의 모든 파일은 root 디렉토리로부터 시작한다.

  • 절대 경로 : root로부터의 경로
  • 상대 경로 : 현재 디렉토리로부터의 경로

3. Opening and Closing Files

open 함수를 통해 파일을 열거나 생성한다.

  • flag 인수로 읽기전용, 쓰기전용, 읽기쓰기 모드를 지정한다.
    • 쓰기의 경우 create, truncate, append 모드도 함께 지정한다.
  • mode 인수로 접근 권한 비트를 설정한다.
    • owner, group, other의 read, write, execute 권한.

4. Reading and Writing Files

read, write 함수를 통해 입출력을 수행한다.

요청보다 적은 바이트를 전송할 때도 있다.

  • 읽기 중 EOF에 도달했을 때
  • 터미널에서 텍스트 라인을 읽을 때
  • 네트워크 소켓에서 읽거나 쓸때
  • 리눅스 파이프를 통해 읽거나 쓸때

6. Reading File Metadata

stat, fstat 함수로 파일의 메타데이터를 가져올 수 있다.

7. Reading Directory Contents

  • opendir 함수는 경로이름을 받아서 디렉토리 스트림(디렉토리 리스트의 추상화)를 반환한다.
  • readdir은 디렉토리 스트림의 다음 디렉토리를 반환한다.
  • closedir은 스트림을 닫고 리소스를 해제한다.

각 디렉토리 엔트리의 구조

struct dirent {
  ino_t d_ino;  /* inode number */
  char d_name[256]; /* Filename */
}

8. Sharing Files

커널은 열란 파일을 세가지 자료구조로 표현한다.

  • Descriptor table
    • 각 프로세스는 별도의 서술자 테이블을 가진다.
    • 엔트리들은 프로세스의 열린 파일 서술자에 의해 색인되어 있다.
    • 각 엔트리는 파일 테이블 엔트리를 가리킨다.
  • File table
    • 열린 파일의 집합이 저장되는 곳.
    • 모든 프로세스가 공유한다.
    • 각 엔트리는 현재 파일 위치, 참조하는 서술자 엔트리의 수, v-node 테이블의 엔트리를 가리키는 포인터로 구성되어 있다.
    • 서술자를 닫으면 참조수가 감소된다.
    • 커널은 참조수가 0이 될 때까지 파일 테이블을 지우지 않는다.
  • v-node table
    • 모든 프로세스가 공유한다.
    • 각 엔트리는 stat 구조의 대부분을 저장한다.(파일 메타데이터)

  • 파일 공유 시 각 서술자는 별도의 파일 위치를 가지고 있으므로 파일의 다른 위치를 읽을 수 있다.

  • fork시에 자식 프로세스는 부모의 서술자 테이블을 복제하며, 동일한 열린 파일 테이블을 공유한다.

9. I/O Redirection

리눅스 쉘의 입출력 리디렉션 연산자는 표춘입출력을 디스크 파일로 연관시킨다.

리디렉션은 dup2 함수를 통해 수행된다.

  • 서술자 oldfdnewfd로 복사하고 덮어쓴다.

  • 서술자 1(표준출력)은 fileA(터미널)과, 서술자4는 fileB(디스크파일)과 연관되어 있었다. 두 파일의 참조수는 1이다.
  • 리디렉트이후 두 서술자는 fileB와 연관된다. fileA는 닫히고 파일 엔트리와 v-node 엔트리는 제거된다. B의 참조수는2가된다.

리디렉트를 해도 표준출력 fd는 남아있으나, 터미널을 가리키던게 파일로 바뀌는 것.

10. Standard I/O

C언어는 고수준 표준 입출력 라이브러리가 있다.

  • 유닉스 입출력의 대안 표준 입출력 라이브러리는 열린 파일을 stream으로 모델한다.
  • 프로그래머에게 스트림은 FILE타입의 구조체를 가리키는 포인터.
  • FILE 스트림은 파일 서술자와 스트림 버퍼의 추상화.
    • 스트림 버퍼의 목적은 버퍼링을 통해 비싼 입출력 시스템 콜을 최소화하는 것.

11. Putting It Together: Which I/O Functions Should I Use?

  • 가능할 때마다 표준 입출력 합수를 사용하라.
  • scanf로 이진 파일을 읽지 마라.
  • 네트워크 입출력 시에는 Unix 입출력을 사용하라.
    • 표준 함수와 네트워크 파일은 상호 호환되지 않는다.

12. Summary

  • 시스템 콜을 직접 쓰는 대신 버퍼링 가능한 표준 라이브러리를 사용하자.
  • 커널은 열린 파일을 표현하는데 3개의 자료구조를 사용한다.
    • 서술자 테이블 엔트리
    • 열린 파일 테이블 엔트리
    • v-node 테이블 엔트리
  • 파일 공유는 v-node를 공유하며, 별도의 열린 파일을 가진다. (메타데이터 공유, pos 별도 유지)
  • 입출력 리디렉션은 각 서술자를 동일한 열린 파일로 연관시킨다.

too many open files 버그가 생기는 이유는 무엇일까?

  • 로깅 혹은 소켓?
  • 네트워크 트래픽과 연관이 없다면 로깅쪽일 가능성이 높겠다.

태그:

카테고리:

업데이트:

댓글남기기