1. 파일과 파일디스크립터 (linux)
1-1. 파일이란
- 파일은 데이터의 집합으로, 디스크와 같은 저장 매체에서 정보를 저장하는 기본단위다.
- 리눅스에서는 기본적으로 모든 것을 파일로 취급하는 일관된 구조를 가지고 있다.
일반 파일 |
텍스트 파일, 바이너리 파일 등 사용자 데이터가 저장된 파일 |
디렉토리 |
파일과 다른 디렉토리를 포함하는 파일로, 파일 시스템의 구조를 형성 |
장치 파일 |
하드웨어 장치와의 인터페이스를 제공하는 파일로, /dev 디렉토리에 위치 |
FIFO |
프로세스 간 통신을 위한 특별한 파일 |
소켓 |
네트워크 통신을 위한 파일로, 프로세스 간의 데이터 전송을 가능하게 함 |
1-2. 파일 디스크립터 (file descriptor)
- 파일 디스크립터는 리눅스 및 UNIX 계열 운영 체제에서 프로세스가 파일이나 다른 I/O 자원(소켓, 파이프 등)에 접근하기 위해 사용하는 정수 값이다.
- 파일 디스크립터는 운영 체제가 내부적으로 관리하는 자료구조인 파일 테이블의 인덱스로 작용한다.
- 정수 값이 할당된다.
- 기본적으로 생성되는 파일 테이블은 아래 표와 같다.
0 |
표준 입력 (stdin) |
1 |
표준 출력 (stdout) |
2 |
표준 에러 (stderr) |
- 파일 디스크립터를 사용하여 read, write, close와 같은 시스템 콜을 통해 파일이나 I/O 자원에 접근할 수 있다. 예를 들어, 파일을 열면 운영 체제는 해당 파일에 대한 파일 디스크립터를 반환한다.
- 파일 디스크립터는 비유일성으로 열어 놓은 파일이나 자원에 대한 유일한 식별자 역할을 한다.
- 각 프로세스들은 독립적으로 파일 디스크립터를 관리한다.
- 파일 디스크립터도 열려있는 파일을 추적하고, 사용이 끝난 파일은 반드시 닫아야 한다. 그렇지 않으면 디스크립터도 누수로 이어진다.
1-3. 저수준 파일 입출력
- 운영 체제와 직접적으로 상호작용하여 파일이나 장치와 데이터를 읽고 쓰는 방식을 말한다.
- 위 방법을 가능하게 하는 것이 시스템 콜(system call) 이라고 불린다.
- ex) read, write, open, close
2. 파일 입출력 관련 시스템콜
2-1. open (파일 열기)
#include <fctnl.h>
int open(const char *path, int flag);
// path는 우리가 열 파일 경로, flag 는 파일을 열 때 줄 옵션
//example
// 1. 파일 열기 (읽기 및 쓰기 모드, 파일이 없으면 생성)
int fd = open("example.txt", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("파일 열기 실패");
return EXIT_FAILURE;
}
- 성공 시 fd (파일 디스크립터) 반환, 실패 시 -1 반환
- flag에 들어갈 옵션은 아래와 같다. 옵션들은 BIT 연산자 OR 로 여러가지를 동시에 줄 수 있다.
오픈 모드 |
의미 |
O_CREATE |
필요 시 파일 생성 |
O_TRUNC |
기존 데이터 삭제 |
O_APPEND |
기존 데이터 뒤에 이어 붙임 |
O_RDONLY |
읽기 전용으로 엶 |
O_WRONLY |
쓰기 전용으로 엶 |
O_RDWR |
읽기, 쓰기 겸용으로 엶 |
2-2. read (파일 읽기)
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
//fd는 우리가 읽을 파일의 디스크립터, buf는 읽은 데이터를 저장할 버퍼에 대한 포인터
//count는 읽어올 최대 바이트 수
//성공 시 실제로 읽은 바이트 수를 반환, 실패 시 -1을 반환
//example
// 4. 데이터 읽기
char buffer[14] = {0}; // null 문자로 초기화
ssize_t bytesRead = read(fd, buffer, 13);
if (bytesRead == -1) {
perror("파일 읽기 실패");
close(fd);
return EXIT_FAILURE;
}
2-3. write (파일 쓰기)
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
//fd는 데이터 전송대상을 나타내는 파일 디스크립터, buf는 전송할 데이터가 저장된 버퍼의 포인터
//nbytes는 전송할 데이터의 바이트 수
//성공 시 전달한 바이트 수, 실패 시 -1 반환
//example
// 2. 데이터 쓰기
const char *data = "Hello, World!";
ssize_t bytesWritten = write(fd, data, 13);
if (bytesWritten == -1) {
perror("파일 쓰기 실패");
close(fd);
return EXIT_FAILURE;
}
// 5. 읽은 데이터 출력 (write 사용)
write(STDOUT_FILENO, buffer, bytesRead); // 표준 출력에 쓰기
- 기본적으로 우리가 지정한 fd에 데이터를 쓰는 행위이다.
- 표준 출력으로 데이터를 쓴다는 것은 출력하는 행위를 의미한다. (실제로 printf 내부적으로 write를 호출한다.)
- 반환값이 ssize_t 인 이유는 문제가 생겼을 때, -1을 적절하게 반환받기 위함이다.
2-4. close (파일 닫기)
#include <unistd.h>
int close(int fd);
//fd는 우리가 닫아줄 파일 디스크립터
//성공 시 0, 실패 시 -1 반환
3. 저수준 파일 입출력 예제 코드
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
// 1. 파일 열기 (읽기 및 쓰기 모드, 파일이 없으면 생성)
int fd = open("example.txt", O_RDWR | O_CREAT | O_TRUNC);
if (fd == -1) {
perror("파일 열기 실패");
return EXIT_FAILURE;
}
// 2. 데이터 쓰기
const char *data = "Hello, World!";
ssize_t bytesWritten = write(fd, data, 13);
if (bytesWritten == -1) {
perror("파일 쓰기 실패");
close(fd);
return EXIT_FAILURE;
}
// 3. 파일을 닫고 다시 열기 (읽기 모드로)
close(fd); // 먼저 닫기
fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("파일 열기 실패");
return EXIT_FAILURE;
}
// 4. 데이터 읽기
char buffer[14] = {0}; // null 문자로 초기화
ssize_t bytesRead = read(fd, buffer, 13);
if (bytesRead == -1) {
perror("파일 읽기 실패");
close(fd);
return EXIT_FAILURE;
}
// 5. 읽은 데이터 출력 (write 사용)
// 위에 표에서 볼 수 있듯이 STDOUT_FILENO는 1을 의미
write(STDOUT_FILENO, buffer, bytesRead); // 표준 출력에 쓰기
// 6. 파일 닫기
close(fd);
return EXIT_SUCCESS;
}