사용자 버퍼 입출력
- 커널은 내부적으로 지연된 쓰기 연산, 미리읽기, 연속된 입출력 요청을 모아서 처리하는 방식으로 버퍼링을 구현
- 그러나 일반 파일에 대해 잦은 입출력을 처리해야만 한다면 성능 개선을 위해 사용자 영역에서 애플리케이션, 라이브러리에 의한 버퍼링 처리 필요
블록크기
- 실제로 블록크기는 보통 512, 1024, 2048, 4096, 8192로 정해짐
- stat명령으로 블록크기을 알아낼 수 있지만 이런 경우는 드물며 블록크기의 정수배가 아닌 특이한 값으로 입출력 연산을 하여 정렬되지 않은 입출력을 피해야 한다.
- 그러나 어플리케이션에서 데이터를 블록 단위가 아닌 행, 필드, 단일문자를 다루므로 사용자 버퍼를 사용하여 하나의 큰 버퍼에 저장되어 있다가 블록크기에 맞춰 파일 시스템으로 전달
- 사용자가 직접 버퍼링을 구현할 수도 있지만 C의 표준 입출력 라이브러리나 C++의 iostream사용
표준 입출력
- 이 책에서는 최신 리눅스 시스템에 포함된 glib에서 구현된 인터페이스와 동작방식 설명
- C의 표준입출력은 파일 디스크립터를 직접 다루지 않고 파일포인터라는 독자적 식별자를 사용하며 파일포인터는 C라이브러리 내부에서 파일 디스크립터로 매핑
- 표준 입출력에서 열린 파일은 스트림이라고 부르기도 한다.
파일 열기, 닫기
|
|
- fopen은 path를 mode에 따라 원하는 용도로 새로운 스트림 생성, 모드는 하기와 같음
- r : 읽기 목적으로 파일을 염, 스트림은 파일 시작 지점
- r+ : 읽기/쓰기, 스트림은 파일 시작지점
- w : 쓰기, 파일 존재시 길이 0으로 만들고 존재하지 않으면 새로 만듬, 스트림은 파일 시작지점
- w+ : 읽기/쓰기, 나머지는 w와 같음
- a : 덧붙이기 쓰기, 파일 존재 시 길이 0으로 만들고 존재하지 않으면 새로 만듬, 스트림은 파일 끝지점
- a+ : 덧붙이기 읽기/쓰기, 나머지는 a와 같음
- fdopen은 파일 디스크립터를 통해 스트림을 만들며, 원래 파일 디스크립터 열 때의 모드와 호환성 유지 필요, 스트림 닫을 시 파일 디스크립터도 닫힘
- fclose는 스트림을 닫음, fcloseall은 현재 프로세스와 관련된 모든 스트림 닫음
스트림에서 읽기
한번에 한문자씩 읽기
|
|
- fgetc()는 stream에서 다음 문자를 읽고 unsigned char 타입을 int 타입으로 변환 반환
- 파일 끝이나 에러를 알려주기 위해 int로 반환,
- ungetc()는 c값을 찔러 넣음, 성공 시 c를 반환, 성공후 fgetc로 읽으면 c값이 읽힘, 읽어보고 되돌려 넣기 위해 사용
한 줄씩 읽기
|
|
- fgets()는 size보다 하나 적은 내용을 읽어서 결과를 str에 저장, 마지막 바이트 읽고 난 후 버퍼 마지막에 null문자(\0) 저장
- EOF나 개행문자를 만나면 읽기 중단
바이너리 데이터 읽기
|
|
- fread()는 stream에서 크기가 size 바이트인 엘리멘트를 nr개 읽어서 buf에 저장, 읽어 들인 엘리멘트 갯수 반환(바이트 아님)
- nr보다 적은 값을 반환하여 실패나 EOF를 알림, ferror()/feof()를 사용해야만 어느 에러에 해당되는지 알 수 있다.
스트림에 쓰기
|
|
- fputc()는 c로 지정한 바이트를 stream에 쓰고 성공시 c를 반환, 실패시 EOF반환 후 errno를 설정
- fputs()는 str이 가르키는 NULL로 끝나는 문자열 전부를 stream에 기록, 성공 시 음수가 아닌값 반환 실패 시 EOF 반환
- fwrite()는 buf가 가리키는 데이터에서 size크기의 엘리먼트 nr개를 stream에 쓴다, 성공 시 엘리먼트 개수 반환
스트림 탐색
|
|
- 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에 반환, 이기종 호환성을 위해 존재
스트림 비우기
|
|
- fflush()는 stream에 있는 쓰지 않은 데이터를 커널로 비움, stream이 NULL이면 프로세스의 모든 입력 스트림을 비움
- 성공시 0을 반환 실패시 EOF를 반환하고 errno를 적절한 값으로 설정
- 스트림(유저영역 존재)을 비우는 것이므로 물리매체에 기록하는 것을 보장하기 위해서는 fsync()를 호출해야 한다.
에러와 EOF
|
|
- fread()와 같은 몇몇 표준 입출력 인터페이스는 에러와 EOF를 구분하는 방벙을 제공하지 않는 등 에러를 알려주는 기능이 약함, 이를 위해 스트림 상태 확인 함수 제공
- ferror()는 stream에 에러 지시자가 설정되어 있을 경우 0이 아닌 값을 반환, 반대의 경우 0을 반환
- feof()는 stream에 EOF 지시자가 설정되어 있을 경우 0이 아닌 값을 반환, 반대의 경우 0을 반환
- clearerr()는 stream에서 에러/EOF 지시자 초기화, 항상 성공하므로 검사먼저 하고 이 함수를 호출 할 것
파일 디스크립터 얻어오기
- int fileno(FILE* stream)는 stream과 관련된 파일 디스크립터를 반환
- 표준 입출력 함수와 시스템콜을 섞어서 사용하는 방식은 권장하지 않음
버퍼링제어하기
|
|
- 표준 입출력 함수 setvbuf()은 유형의 사용자 버퍼링을 구현하고 버퍼의 유형과 크기를 다룰 수 있는 인터페이스 제공
- mode는 버퍼미사용, 행버퍼, 블록버퍼 3가지 설정, buf는 stream을 위한 버퍼로 사용(NULL이라면 glib이 메모리 할당)
스레드 세이프
- 표준 입출력 함수는 내부적으로 락 및 락 카운터를 가지고 있어 스레드 세이프 보장
- 더 넓은 수준의 원자성을 구현하기 위해 표준입출력은 스트림에 관련된 락을 개별적으로 조작하는 함수 제공
수동으로 파일락 걸기
|
|
- flockfile()은 stream의 락이 해제될 때까지 기다린 후 락 카운터를 올리고 락을 얻은 다음, 스레드가 스트림을 소유하도록 만든 후 반환
- funlockfile()은 stream과 연관된 락카운터를 하나 줄임, 락카운터가 0이 되면 스레드는 stream의 소유권을 포기후 다른 스레드가 락을 얻을 수 있게 함
- ftrylockfile()은 flockfile의 논블럭 버전
락을 사용하지 않는 스트림 연산
- 락을 수행하지 않는 버전의 표준 입출력 함수 제공(책에 함수 리스트 참조, 이름에 _ulocked 라고 뒤에 붙음)
- 락 오버헤드를 없애서 성능향상을 할 수 있음, 락이 필요하다면 개발자가 수동으로 락을 얻고 해제해야함
표준입출력 비평
- 표준 입출력의 가장 큰 문제는 이중복사 문제(read()시 커널에서 표준 입출력 버퍼로 복사 후 fgetc()를 통해서 어플리케이션 버퍼로 이동)
- 이중복사를 해결할 수 있는 고도로 최적화된 사용자 버퍼링 라이브러리가 존재