C언어에서 파일을 읽고 쓰는 것은 단순한 라이브러리 호출이 아니라 운영체제와 커널의 호출이 존재한다. 필자는 이번 글에서 파일 입출력의 동작 원리, 시스템 콜과의 관계, 최적화 전략까지 설명하도록 하겠다.
#include <stdio.h>
FILE* fp = fopen("data.txt", "r");
char buf[100];
fgets(buf, sizeof(buf), fp);
fclose(fp)
fopen, fgets, fclose 같은 함수들은 C 표준 라이브러리가 제공하는 고수준 인터페이스이다. 하지만 실제로 이 함수들은 운영체제 커널에 요청을 보내는 시스템 콜을 내부적으로 호출한다
사용자 프로그램이 커널에게 특정 작업을 요청하는 공식 통로.
대표적인 시스템 콜로는 open, read, write, close 등이 있다.
C 라이브러리를 사용하면 다음과 같은 과정을 거치게 된다.
[사용자 코드] -> [라이브러리] -> [시스템 콜] -> [커널] -> [사용자 코드]
즉 라이브러리 함수는 시스템 콜을 좀 더 편하게 포장한 고수준 인터페이스 이다.
시스템 콜을 직접 호출하면 더 낮은 수준으로 컴퓨터를 더 세밀하게 제어할 수 있다.
#include<fcntl.h>
#include<unistd.h>
int fd = open("data.tet", O_RDONLY); //읽기 전용 열기
char buf[100];
ssize_t n = read(fd, buf, sizeof(buf)); // 파일에서 읽기;
close(fd); //파일 닫기
open은 파일 디스크립터를 반환한다(정수형 ID)
read는 디스크립터를 통해 데이터를 읽는다.
close로 디스크립터를 반환한다.
구분 | FILE* | file descriptor |
---|---|---|
라이브러리 수준 | C 라이브러리 | 커널 시스템 콜 |
버퍼링 | 있음(Buffered) | 없음(Unbuffered) |
사용 용도 | 간편한 파일 읽기, 쓰기 | 고성능, 세밀한 제어 |
함수군 | fopen, fread, fwrite, fclose | open, read, write, close |
fgets는 내부 버퍼를 사용해 여러 바이트를 읽어놓고 한 줄씩 반환하지만 read는 호출할 때마다 직접 커널에 접근해서 읽는다.
시스템 콜 기반은 속도는 빠르지만, 직접 버퍼를 관리해야 한다는 부담이 있다.
파일 디스크립터는 리죽스에서 모든 것을 파일처럼 다루는 모델의 핵심이다.
파일 디스크립터는 번호마다 의미가 있다.
read나 write 호출이 완료될 때까지 기다리지 않고 작업이 완료되면 알림을 받는 방식이다. 고성능 네트워크 서버에 필수적이다.
#include <sys/mman.h>
void* addr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
파일을 직접 메모리에 매핑하여 read, write 호출 ㅇ벗이 메모리처럼 다룰 수 있다. 대용량 파일 처리에 최적화된 방법이다.
버퍼를 우회하고 디스크와 통신하여 데이터 무결성을 강화한다. 데이터베이스, 고성능 파일 시스템에서 주로 사용한다.
오늘은 파일 입출력과 시스템 콜에 대해서 알아보았다.
임베디드 시스템을 개발할때 한번씩 직접 사용해 보도록 하자.
그럼 필자는 다음 글로 다시 돌아오겠다.