리눅스 시스템 프로그래밍_Ch 3

·2021년 1월 24일
1

리눅스

목록 보기
3/3
post-thumbnail

Ch 3 버퍼 입출력

3장에서는 표준 c 라이브러리를 통해 파일을 작업하는 것을 설명한다.


1. 사용자 버퍼 입출력

블록 크기

  • 파일 시스템의 최소 저장 단위는 블록임.
  • 모든 입출력 연산은 블록 크기의 정수배.

❗️하지만 보통 사용자 application에서는 필드나 문자열 등의 입출력 연산이 필요한 경우가 대부분임.
❗️시스템 콜을 이용하여 파일 입출력 작업을 하면 호출할 때마다 커널을 통해 입출력을 수행하여 시스템 효율이 떨어질 수 있음.
❗️따라서, 사용자 application code 레벨에서 인위적으로 버퍼링을 구현하는데, 이미 있는 사용자 버퍼링 구현체를 가져다 씀 (표준 입출력 라이브러리나 표준 c++ iostream).

2. 파일 열기

표준 입출력에서는 파일 디스크립터 대신 파일 포인터를 사용. 파일 포인터는 c 라이브러리 내부에서 파일 디스크립터로 맵핑됨.

fopen()

읽거나 쓰기 위해 fopen()을 사용해서 파일을 엶.

#include <stdio.h>

FILE * fopen (const char *path, const char *mode);
  • 파일 pathmode에 따라 원하는 용도로 새로운 스트림을 만듦.
  • mode
    (예시)
    - r: 읽기 목적으로 파일을 엶.
    - w: 쓰기 목적으로 파일을 엶.
    - a: 덧붙이기 상태에서 쓰기 목적으로 파일을 엶.
    ...

fdopen()

이미 열린 파일 디스크립터(fd)를 통해 스트림을 만듦.

#include <stdio.h>

FILE * fdopen (int fd, const char *mode);
  • mode는 fopen()과 동일.

3. 스트림 닫기

fclose()

#include <stdio.h>

int fclose (FILE *stream);
  • 버퍼에 쌓여있지만 아직 스트림에 쓰지 않은 데이터를 먼저 처리.

fcloseall()

#define _GNU_SOURCE
#include <stdio.h>

int fcloseall (void);
  • 표준 입력, 출력, 에러를 포함하여 현재 프로세스와 관련된 모든 스트림을 닫음.

4. 스트림에서 읽기

한 번에 한 문자씩 읽기: fgetc()

#include <stdio.h>

int fgetc (FILE *stream);
  • stream에서 다음 문자를 읽고 unsigned char 타입을 int 타입으로 변환해서(EOF나 에러를 알려주기 위해) 반환.

한 줄씩 읽기: fgets()

#include <stdio.h>

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

바이너리 데이터 읽기: fread()

c 구조체 같은 복잡한 바이너리 데이터를 읽을 때 사용.

#include <stdio.h>

size_t fread (void *buf, size_t size, size_t nr, FILE *stream);
  • stream에서 각각 크기가 size 바이트인 element를 nr개를 읽어서 buf가 가리키는 버퍼에 저장.

5. 스트림에 쓰기

한 번에 한 문자만 쓰기: fputc()

#include <stdio.h>

int fputc (int c, FILE *stream);
  • c로 지정한 바이트를 (unsigned char로 변환한 후에) stream이 가리키는 스트림에 씀.

문자열 기록하기: fputs()

#include <stdio.h>

int fputs (const char *str, FILE *stream);
  • str이 가리키는 NULL로 끝나는 문자열 전부를 stream이 가리키는 스트림에 기록.

바이너리 데이터 기록하기: fwrite()

#include <stdio.h>

size_t fwrite (void *buf, size_t size, size_t nr, FILE *stream);
  • buf가 가리키는 데이터에서 size 크기의 element nr개를 stream에 씀.

6. 스트림 탐색하기

fseek()

#include <stdio.h>

int fseek (FILE *stream, long offset, int whence);
  • lseek() 시스템 콜과 동일한 기능.
  • offsetwhence에 따라 stream에서 파일 위치를 조작.

7. 스트림 비우기

여기 3장에서 설명하는 함수들은 모두 c라이브러리가 관리하는 버퍼를 사용하는데 이 버퍼는 사용자 영역에 위치함. 커널이 유지하는 버퍼는 커널영역에 위치함.
fflush()는 사용자 버퍼를 커널로 비워서 스트림에 쓴 모든 데이터가 write()를 통해 실제로 디스크에 기록되도록 만드는 인터페이스.

fflush()

#include <stdio.h>

int fflush (FILE *stream);
  • stream에 있는 쓰지 않은 데이터를 커널로 비움.
  • 한마디로 사용자 버퍼에 있는 데이터를 커널 버퍼로 쓰는 것임. 이는 사용자 버퍼를 사용하지 않고 write() 를 직접 사용하는 효과와 동일.

8. 스레드 세이프

스레드 세이프(Thread-safe) 는 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다. _위키피디아

❗️멀티스레드 환경에서 동작해도 원래 의도한 대로 동작하는 것을 스레드세이프 하다고 할 수 있음.
❗️스레드를 지원하는 운영체제는 락 메커니즘을 지원하는데, 표준 입출력은 이를 활용해 단일 프로세스 내의 여러 스레드가 동시에, 심지어는 같은 스트림에 대해서 표준 입출력을 호출할 수 있도록 함.
❗️락을 걸면 critical section(다른 스레드의 간섭 없이 실행할 수 있는 코드)을 설정할 수 있음.

flockfile()

락을 거는 함수

#include <stdio.h>

void flockfile (FILE *stream);
  • stream의 락이 해제될 때까지 기다린 후에 락 counter를 올리고 락을 얻은 다음, 스레드가 stream을 소유하도록 만듦.

funlockfile()

락을 해제하는 함수

#include <stdio.h>

void funlockfile (FILE *stream);
  • stream과 연관된 락 counter를 하나 줄임.
  • 만일 락 counter가 0이 되면 현재 스레드는 stream의 소유권을 포기해서 다른 스레드가 락을 얻을 수 있도록 함.
profile
문과생 컴공초보👩🏻‍💻

0개의 댓글