시스템프로그래밍_2-File IO

안수빈·2025년 4월 18일

시스템프로그래밍

목록 보기
2/7
post-thumbnail

<Library>

  • 재사용을 위한 컴파일된 객체 함수들의 집합

  • 어플리케이션 개발을 할 때 이미 만들어져 있는 함수들을 편리하게 사용할 수 있도록 모아 놓은

  • ex) 그래픽 라이브러리, 수학 라이브러리 등

  • Linux에서는 일반적으로 /lib 또는 /usr/lib 디렉토리에 위치

  • 사용자 프로그램에서 필요한 함수(객체)만 링크(link) 됨


컴파일 & 링크 과정 요약


<라이브러리 종류>

Shared Library (공유 라이브러리) *.so, *.dll

  • 함수의 복사본은 메모리에 하나만 있다 → 여러 프로세스간의 공유 → 메모리 절약

  • 함수의 주소는 실행 시(런타임 run-time)에 결정 ➡︎ 이것을 동적 링크(dynamic linking) or 바인딩(binding)이라고 함

  • 동적 링크를 위한 심볼 테이블이 메모리에 존재 → 메모리 오버헤드 발생
    심볼 테이블 (symbol table): OS는 각각의 라이브러리를 위한 정보들이 필요한데 이것을 심볼테이블이라 함, ex) printf는 몇 번째에 있고, scanf는 몇 번째에 있는지 ...

  • 서버 시스템(server systems)에 유용

Static Library (정적 라이브러리).a

  • 필요한 함수가 각 바이너리 프로그램에 직접 포함(link) 됨
    바이너리 프로그램 (binary program): 내가 만든 실행 파일

  • 동일한 함수 코드가 여러 번 메모리에 중복 저장됨 → 메모리 오버헤드

  • 임베디드 시스템(embedded systems)에 유용


<Standard I/O Library (표준 입출력 라이브러리)>

: 입출력을 지원하는 라이브러리

#include <stdio.h>

표준 입출력(콘솔 & 파일)을 위한 기호(Symbol) 와 API 함수를 정의한 헤더 파일
API: 응용 프로그램에서 사용할 수 있는 인터페이스와 사용하는 여러가지 심볼등을 정의

File I/O with the standard I/O library

I/O 장치는 특별한 파일로 매핑됨
➡︎ 콘솔(터미널), 프린터, 키보드 같은 I/O 장치들이 실제로는 파일처럼 다뤄진다는 의미

  • 콘솔 터미널의 기본 파일: stdin , stdout, stderr

  • 실행 시 자동으로 열려 있음

    파일 시스템: 파일을 저장하고 관리하는 OS의 핵심 기능


<FILE object in C>

FILE 포인터

  • 표준 I/O 라이브러리에 의해 생성

  • FILE * 포인터를 통해 파일 접근

-포인터 안에는 파일에 대한 시스템 정보가 포함

stdin, stdout, stderr

  • 셸(shell)에 의해 자동으로 열려있는 FILE 포인터

  • 사용자 프로그램이 상속받아 바로 사용 가능


<File Descriptor (파일 디스크립터)>

시스템 콜 기반 I/O

  • 운영체제는 FILE 포인터가 ❌, file descriptor(정수) 를 사용

  • 디스크립터는 정수 값이고,
    ex)
    0: stdin, 1: stdout, 2: stderr

  • 사용자가 여는 파일의 디스크립터는 3부터 순차적으로 할당

  • 보통 최대 1024개까지 열 수 있음

Descriptor (파일 디스크립터): 운영체제가 사용하는 정수형 ID(번호)로,
파일, 소켓, 파이프 등 I/O 자원(Resource) 을 구분하고 관리하는 데 사용됨

표준 I/O 함수도 결국 시스템 콜 호출함

  • ex) printf, fprintf, puts → 내부적으로 write() 시스템 콜 호출

✅ 표준 I/O 라이브러리를 사용하는 이유

  • 시스템 콜보다 편리하고 직관적

  • 버퍼링, 형식 지정 출력(formatting) 등의 기능 제공


<File stream & File Descriptor 연결>

  • 파일 스트림과 디스크립터는 1:1로 매핑

✅ 변환 함수 2개 암기

int fileno(FILE *stream);

➡︎ FILE 포인터로부터 file descriptor(정수) 를 반환

FILE *fdopen(int fd, const char *mode);

➡︎ file descriptor를 받아서 FILE 포인터를 생성


<Library Buffering (라이브러리 버퍼링)>

: 사용자 레벨에서 버퍼를 사용해 입출력을 효율적으로 처리하는 방식,
즉, 데이터를 바로 보내거나 받지 않고, 임시 메모리(버퍼)에 모아뒀다가 한꺼번에 처리.

버퍼(Buffer): 입출력(I/O) 데이터를 임시로 저장해두는 메모리 공간

  • 사용자 프로그램(user process) 수준에서 라이브러리 함수가 버퍼 관리
    ➡︎ 목적: 시스템 콜 호출 횟수를 줄임 → 성능 향상
    ➡︎ 예시: 키보드 입력 중 "DEL" 키를 누르면 커널까지 전달되기 전에 버퍼에서만 처리 가능

<버퍼링 종류>

✅ 1. Full Buffering

  • buffering를 모두 담는 경우

  • 주로 디스크 파일 등에 사용

  • 데이터가 일정량(수 KB 이상) 쌓이면 한꺼번에 시스템 콜 발생

  • 시스템 콜 횟수를 효과적으로 줄임

  • fflush() 함수로 커널과 강제 동기화 가능 ➡︎fflush()는 강제적으로 데이터를 내보내는 함수

동기화(Synchronization): 두 개 이상의 작업이 서로 간섭하지 않고 순서대로 제대로 작동하게 만드는 것


✅ 2. Line Buffering

  • 주로 콘솔 입출력에 사용

  • 엔터(newline)가 입력될 때 실제 I/O 발생

  • getchar() 문제(problem) ➡︎ 문자 하나를 눌러도 엔터 치기 전까지 전달되지 않음

✅ 3. Unbuffering (버퍼링 없음)

  • 버퍼 ❌ 즉시 시스템 콜 호출

  • 안정적 (전원 꺼져도 데이터 손실 적음)

  • 보통 에러 메시지 출력(stderr) 에 사용됨

  • 버퍼링 방식이 다르면 입출력 타이밍도 달라짐
  • fflush()를 호출하지 않으면, 버퍼가 비워지지 않아 출력 안 보이는 문제 발생 가능


(Linux에서의 기본 설정)


<버퍼링 유형 설정>

setbuf() 함수

void setbuf(FILE *stream, char *buf);
  • 사용자 지정 버퍼 주소를 설정

  • buf != NULL: 일반적인 버퍼링 (full or line)

  • buf == NULL: 버퍼 없이 처리 (Unbuffered)

  • 리턴값 ❌

setvbuf() 함수

int setvbuf(FILE *stream, char *buf, int type, size_t size);
  • 버퍼 주소뿐 아니라 버퍼링 유형과 크기까지 지정

  • type : 버퍼링 방식 설정

  • size : 버퍼 크기 (바이트 단위)

  • 리턴값:

    0: 성공

    != 0: 에러

setbuf() ➡︎ 간단한 버퍼 설정,
setvbuf() ➡︎ 버퍼링 방식 + 버퍼 크기까지 완벽 제어 가능!


<Kernel Buffering — 커널 버퍼링>

: 운영체제가 자체적으로 메모리에 I/O 데이터 캐싱하는 것
➡︎ 디스크와 직접 입출력하는 대신, 커널 메모리(buffer cache, page cache)에 잠깐 저장함

Cache (캐시): 자주 사용하는 데이터를 저장해두는 저장소 자체
Caching (캐싱): 자주 사용하는 데이터를 빠른 저장소에 미리 저장해두고 필요할 때 빠르게 꺼내 쓰는 기술

Page Cache (= Buffer Cache)

  • 자주 쓰는 디스크 블록들을 커널 메모리에 유지 ➡︎ 디스크 접근을 줄여 성능 향상

✅ 디스크에서 읽을 때

  • 페이지 캐시에 먼저 접근

  • 캐시에 없으면 ➡︎ 디스크에서 진짜 읽기 수행

✅ 디스크에 쓸 때

  • 데이터를 캐시에 먼저 쓰고, 디스크에는 나중에 반영
    ➡︎ 이걸 지연 쓰기(Delayed Write) 라고 부름


<fflush() 함수 — 라이브러리 버퍼 강제 비우기>

int fflush(FILE *stream);

➡︎ 라이브러리 버퍼의 데이터를 커널로 강제 전송 (동기화 목적)

  • 리턴값:

    0: 성공

    EOF: 실패

EOF: End Of File, 파일의 끝을 의미하는 특별한 정수 상수값 (보통 -1로 정의됨)

(주요 특징)

printf("something");
fflush(stdout);  // 강제 출력!
  • printf()는 버퍼링 때문에 실제로 바로 출력되지 않을 수 있음

    ➡︎ 디버깅 중엔 fflush(stdout); 꼭 써야 출력이 즉시 반영됨


(디스크 I/O와 관련해서)

  • 디스크는 블록 단위로 전송 (수 KB 단위)

  • fflush()는 라이브러리 버퍼 → 커널 페이지 캐시로 전송

  • 디스크까지 동기화하려면?
    ➡︎ sync() 또는 fsync() 필요함

파일을 닫을 때(fclose() 호출 시), fflush()는 자동 호출됨


<I/O Buffering & Sync — 입출력 흐름과 동기화 구조>


<파일 열기: fopen()>

FILE *fopen(const char *filename, const char *type);
  • filename: 열 파일의 이름

  • type: 열기 모드 (r, w, a, …)

  • 성공 시: FILE * 반환

  • 실패 시: NULL 반환

    (파일 열기 정리표)

⚠️ 시스템마다 동시에 열 수 있는 파일 수 제한 있음


<파일 다시 열기: freopen()>

FILE *freopen(const char *filename, const char *type, FILE *stream);
  • stream으로 연결된 기존 파일을 닫고, 같은 스트림을 재사용하여 새 파일 열기

  • 반환값: 성공 시 새로운 FILE *, 실패 시 NULL

(동작 방식)

-stream에 연결된 파일을 먼저 닫음

새 파일을 열어 같은 stream(파일 포인터)로 재연결

기존 file descriptor도 그대로 재사용

(예제)

freopen("myfile.txt", "w", stdout);
printf("This sentence is redirected to a file.");
fclose(stdout);
  • stdout이 "myfile.txt"와 연결됨

  • printf() 출력이 화면 대신 파일로 저장됨


<파일 닫기: fclose()>

int fclose(FILE *stream);
  • 반환값:

    0: 정상 종료

    EOF: 에러 발생

  • 프로세스 정상 종료 ➡︎ 열려 있던 모든 파일 자동으로 닫힘
    강제 종료 시 ➡︎ fclose() 에러 확인 못함 / 버퍼에 있는 데이터가 손실될 수 있음



<File I/O functions (fread, fwrite)>





<File I/O 예제 코드 요약>





<Line I/O 예제 코드 요약>





<Array I/O example — 배열을 파일에 저장하기>



<Struct I/O example — 구조체를 파일에 저장하기>



  • fwrite()는 텍스트가 아닌 바이너리 파일 저장용 함수

  • 플랫폼 차이(ex. endian, padding) 때문에 저장한 구조체를 다른 시스템에서 읽을 땐 문제가 생길 수도 있어

<파일 복사 (Full buffering 방식)>

파일을 복사할 때 full buffering을 사용하는 예제이다. setvbuf() 함수를 사용하여 버퍼링 타입을 설정하고, fread()와 fwrite()를 통해 데이터를 블록 단위로 복사한다.





<File Offset>

  • 모든 열린 파일은 (읽기/쓰기) 오프셋을 가지고 있음 → 다음에 접근할 파일 위치를 나타냄

  • 파일을 읽기/쓰기 모드로 열면 오프셋은 파일 시작 지점으로 설정됨

  • 파일을 추가 모드(append)로 열면 오프셋은 파일 끝으로 설정됨

  • 읽기/쓰기 중에는 오프셋이 자동으로 증가


<File Access Methods (파일 접근 방식)>



<랜덤 접근 예제 요약 (frandom-ex.c)>






<I/O Types (입출력의 형태)>



<Formatted I/O example>


<File error check example>



<EOF check example>



<Error handling>

0개의 댓글