[Linux_System_programming] 03 Buffer I/O

Linux System Programming (Robert Love) chapter 03 Buffer I/O summary

사용자 버퍼 입출력

  • 커널은 내부적으로 지연된 쓰기 연산, 미리읽기, 연속된 입출력 요청을 모아서 처리하는 방식으로 버퍼링을 구현
  • 그러나 일반 파일에 대해 잦은 입출력을 처리해야만 한다면 성능 개선을 위해 사용자 영역에서 애플리케이션, 라이브러리에 의한 버퍼링 처리 필요

블록크기

  • 실제로 블록크기는 보통 512, 1024, 2048, 4096, 8192로 정해짐
  • stat명령으로 블록크기을 알아낼 수 있지만 이런 경우는 드물며 블록크기의 정수배가 아닌 특이한 값으로 입출력 연산을 하여 정렬되지 않은 입출력을 피해야 한다.
  • 그러나 어플리케이션에서 데이터를 블록 단위가 아닌 행, 필드, 단일문자를 다루므로 사용자 버퍼를 사용하여 하나의 큰 버퍼에 저장되어 있다가 블록크기에 맞춰 파일 시스템으로 전달
  • 사용자가 직접 버퍼링을 구현할 수도 있지만 C의 표준 입출력 라이브러리나 C++의 iostream사용

표준 입출력

  • 이 책에서는 최신 리눅스 시스템에 포함된 glib에서 구현된 인터페이스와 동작방식 설명
  • C의 표준입출력은 파일 디스크립터를 직접 다루지 않고 파일포인터라는 독자적 식별자를 사용하며 파일포인터는 C라이브러리 내부에서 파일 디스크립터로 매핑
  • 표준 입출력에서 열린 파일은 스트림이라고 부르기도 한다.

파일 열기, 닫기

1
2
3
4
5
#include <stdio.h>

FILE* fopen(const char* path, const char* mode);
FILE* fdopen(int fd, const char* mode);
int fclose(FILE* stream);
  • fopen은 path를 mode에 따라 원하는 용도로 새로운 스트림 생성, 모드는 하기와 같음
    • r : 읽기 목적으로 파일을 염, 스트림은 파일 시작 지점
    • r+ : 읽기/쓰기, 스트림은 파일 시작지점
    • w : 쓰기, 파일 존재시 길이 0으로 만들고 존재하지 않으면 새로 만듬, 스트림은 파일 시작지점
    • w+ : 읽기/쓰기, 나머지는 w와 같음
    • a : 덧붙이기 쓰기, 파일 존재 시 길이 0으로 만들고 존재하지 않으면 새로 만듬, 스트림은 파일 끝지점
    • a+ : 덧붙이기 읽기/쓰기, 나머지는 a와 같음
  • fdopen은 파일 디스크립터를 통해 스트림을 만들며, 원래 파일 디스크립터 열 때의 모드와 호환성 유지 필요, 스트림 닫을 시 파일 디스크립터도 닫힘
  • fclose는 스트림을 닫음, fcloseall은 현재 프로세스와 관련된 모든 스트림 닫음

스트림에서 읽기

한번에 한문자씩 읽기

1
2
3
4
#include <stdio.h>

int fgetc(FILE* stream);
int ungetc(int c, FILE* stream);
  • fgetc()는 stream에서 다음 문자를 읽고 unsigned char 타입을 int 타입으로 변환 반환
    • 파일 끝이나 에러를 알려주기 위해 int로 반환,
  • ungetc()는 c값을 찔러 넣음, 성공 시 c를 반환, 성공후 fgetc로 읽으면 c값이 읽힘, 읽어보고 되돌려 넣기 위해 사용

한 줄씩 읽기

1
2
3
#include <stdio.h>

char* fgets(char* str, int size, FILE* stream);
  • fgets()는 size보다 하나 적은 내용을 읽어서 결과를 str에 저장, 마지막 바이트 읽고 난 후 버퍼 마지막에 null문자(\0) 저장
  • EOF나 개행문자를 만나면 읽기 중단

바이너리 데이터 읽기

1
2
3
#include <stdio.h>

size_t fread(void* buf, size_t size, size_t nr, FILE* stream);
  • fread()는 stream에서 크기가 size 바이트인 엘리멘트를 nr개 읽어서 buf에 저장, 읽어 들인 엘리멘트 갯수 반환(바이트 아님)
  • nr보다 적은 값을 반환하여 실패나 EOF를 알림, ferror()/feof()를 사용해야만 어느 에러에 해당되는지 알 수 있다.

스트림에 쓰기

1
2
3
4
5
#include <stdio.h>

int fputc(int c, FILE* stream);
int fputs(const char* str, FILE* stream);
size_t fwrite(void* buf, size_t size, size_t nr, FILE* stream);
  • fputc()는 c로 지정한 바이트를 stream에 쓰고 성공시 c를 반환, 실패시 EOF반환 후 errno를 설정
  • fputs()는 str이 가르키는 NULL로 끝나는 문자열 전부를 stream에 기록, 성공 시 음수가 아닌값 반환 실패 시 EOF 반환
  • fwrite()는 buf가 가리키는 데이터에서 size크기의 엘리먼트 nr개를 stream에 쓴다, 성공 시 엘리먼트 개수 반환

스트림 탐색

1
2
3
4
5
6
7
#include <stdio.h>

int fseek(FILE* stream, long offset, int whence);
int fsetpos(FILE* stream fpos_t* pos);
void rewind(FILE* stream);
long ftell(FILE* stream);
int fgetpos(FILE* stream fpos_t* pos);
  • fseek()는 stream에서 파일의 위치를 조작, 성공시 0을 반환, 실패 시 -1을 반환 errno를 적절한 값으로 설정
    • whence가 SEEK_SET이면 파일의 위치를 offset 값으로 설정
    • whence가 SEEK_CUR이면 파일의 위치를 현재 위치에서 offset 만큼 더한 값으로 설정
    • whence가 SEEK_END이면 파일의 위치를 파일 끝에서 offset 만큼 더한 값으로 설정
  • fsetpos()는 stream 위치를 pos로 설정, SEEK_SET를 사용한 fseek()와 동일하게 동작, 이기종 호환성 위해 존재, 리눅스 계열에서는 사용 불필요
  • frewind()는 stream을 시작위치로 되돌림, 반환 값이 없으므로 호출전 errno를 초기화 한후 호출 후 errno를 확인해야 함
  • ftell()를 현재 스트림을 빈환, fseek()는 갱신된 위치를 반환하지 않으므로 이 함수를 이용
  • fgetpos()는 현재 스트림을 pos에 반환, 이기종 호환성을 위해 존재

스트림 비우기

1
2
3
#include <stdio.h>

int fflush(FILE* stream);
  • fflush()는 stream에 있는 쓰지 않은 데이터를 커널로 비움, stream이 NULL이면 프로세스의 모든 입력 스트림을 비움
  • 성공시 0을 반환 실패시 EOF를 반환하고 errno를 적절한 값으로 설정
  • 스트림(유저영역 존재)을 비우는 것이므로 물리매체에 기록하는 것을 보장하기 위해서는 fsync()를 호출해야 한다.

에러와 EOF

1
2
3
4
5
#include <stdio.h>

int ferror(FILE* stream);
int feof(FILE* stream);
void clearerr(FILE* stream);
  • fread()와 같은 몇몇 표준 입출력 인터페이스는 에러와 EOF를 구분하는 방벙을 제공하지 않는 등 에러를 알려주는 기능이 약함, 이를 위해 스트림 상태 확인 함수 제공
  • ferror()는 stream에 에러 지시자가 설정되어 있을 경우 0이 아닌 값을 반환, 반대의 경우 0을 반환
  • feof()는 stream에 EOF 지시자가 설정되어 있을 경우 0이 아닌 값을 반환, 반대의 경우 0을 반환
  • clearerr()는 stream에서 에러/EOF 지시자 초기화, 항상 성공하므로 검사먼저 하고 이 함수를 호출 할 것

파일 디스크립터 얻어오기

  • int fileno(FILE* stream)는 stream과 관련된 파일 디스크립터를 반환
  • 표준 입출력 함수와 시스템콜을 섞어서 사용하는 방식은 권장하지 않음

버퍼링제어하기

1
2
3
#include <stdio.h>

int setvbuf(FILE* stream, char* buf, int mode, size_t size);
  • 표준 입출력 함수 setvbuf()은 유형의 사용자 버퍼링을 구현하고 버퍼의 유형과 크기를 다룰 수 있는 인터페이스 제공
  • mode는 버퍼미사용, 행버퍼, 블록버퍼 3가지 설정, buf는 stream을 위한 버퍼로 사용(NULL이라면 glib이 메모리 할당)

스레드 세이프

  • 표준 입출력 함수는 내부적으로 락 및 락 카운터를 가지고 있어 스레드 세이프 보장
  • 더 넓은 수준의 원자성을 구현하기 위해 표준입출력은 스트림에 관련된 락을 개별적으로 조작하는 함수 제공

수동으로 파일락 걸기

1
2
3
4
5
#include <stdio.h>

void flockfile(FILE* stream);
void funlockfile(FILE* stream);
int ftrylockfile(FILE* stream);
  • flockfile()은 stream의 락이 해제될 때까지 기다린 후 락 카운터를 올리고 락을 얻은 다음, 스레드가 스트림을 소유하도록 만든 후 반환
  • funlockfile()은 stream과 연관된 락카운터를 하나 줄임, 락카운터가 0이 되면 스레드는 stream의 소유권을 포기후 다른 스레드가 락을 얻을 수 있게 함
  • ftrylockfile()은 flockfile의 논블럭 버전

락을 사용하지 않는 스트림 연산

  • 락을 수행하지 않는 버전의 표준 입출력 함수 제공(책에 함수 리스트 참조, 이름에 _ulocked 라고 뒤에 붙음)
  • 락 오버헤드를 없애서 성능향상을 할 수 있음, 락이 필요하다면 개발자가 수동으로 락을 얻고 해제해야함

표준입출력 비평

  • 표준 입출력의 가장 큰 문제는 이중복사 문제(read()시 커널에서 표준 입출력 버퍼로 복사 후 fgetc()를 통해서 어플리케이션 버퍼로 이동)
  • 이중복사를 해결할 수 있는 고도로 최적화된 사용자 버퍼링 라이브러리가 존재
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy