-
파일은 의미 있는 정보를 담는 논리적인 단위로, bytes의 배열 형태로 관리된다.
파일은 일반 파일, 디렉토리 파일, 자명 파이프 파일, 드라이버 파일 등등 여러 개로 분류되고, 그에 따른 확장자가 붙는다.
UNIX 계열 운영체제에서 확장자는 크게 의미는 없고, 단지 해당 파일의 형식을 짐작하는 용도로 쓰이게 된다.
파일의 입출력은 kernel 단계에서 버퍼링 작업을 하게되며, 버퍼 덕분에 디스크에 여러 군데에 분산되어 저장된 데이터가
하나의 파일로 연결된 것 처럼 동작하게 된다.
응용 프로그램은 하드웨어에 직접 접근할 수 없다.
따라서 커널은 응용 프로그램에게 시스템 콜(system call) 함수를 제공하여, 파일에 접근하기 위한 인터페이스를 제공한다.
- 파일 지시자
응용 프로그램이 시스템 콜 함수를 호출할 때, 특정 파일을 어떻게 가리킬 수 있을까?
이 때 사용되는 개념이 파일 지시자(file descriptor)이다.
파일 지시자는 프로세스에서 관리하는 File Table에 연결되어 있고,
이 File Table은 또 시스템에서 관리하는 Inode Table에 연결되어 있는데,
응용 프로그램, 즉 프로세스가 파일에 접근하기 위해서는 파일 지시자를 시스템 콜 함수에 인자로 넘겨주게 되면,
시스템 콜 함수는 file table, inode table을 참조하여 파일에 대한 접근권한을 반환하는 식으로
프로세스가 파일 입출력을 할 수 있도록 해준다.
프로세스가 특정 파일에 대한 file descriptor를 얻으려고 시스템 콜 함수를 호출하게 되면,
kernel은 해당 프로세스가 관리하는 file descriptor table에서 빈 값 중 가장 작은 번호의 file descriptor을 반환하게 된다.
(참고로, 0, 1, 2는 프로세스 생성시 기본적으로 할당받는 파일 지시자로 사용된다)
- 파일 접근 프리미티브(File Access Primitive) 시스템 콜 함수
1. int creat(const char *pathname, mode_t mode);
2. int open(const char *pathname, int flags, [mode_t mode]);
(flags가 O_CREAT인 경우에만 mode_t mode를 사용하게 된다)
#define PERMS 0644 ... fd = creat(pathname, PERMS); fd = open(pathname, O_CREAT | O_RDWR, PERMS):
creat 함수의 경우 pathname에 저장된 경로에 PERMS에 지정한 권한으로 파일을 생성하고 그 파일의 파일 지시자를 반환합니다.
open의 경우에는 pathname에 저장된 경로에 파일을 flags에 해당한 권한으로 열고,
만약 flags에 O_CREAT 플래그를 사용한다면, 뒤에 PERMS를 지정하여 지정한 권한으로 파일을 만들고 열게 된다.
위 코드의 경우에는 0644 권한으로 pathname에 해당하는 파일을 만들고, 읽고 쓰기 모드로 파일을 여는 기능을 수행한다.
참고로, O_WRONLY, O_RDWR 모드로 열면 파일의 처음부터 데이터를 쓰게 되는데,
O_APPEND 플래그를 OR 연산하여 열게되면, 파일의 기존 내용 이후부터 쓰게 된다.
3. int close(int file_descriptor);
해당 파일 지시자에 해당하는 파일을 닫는다.
한 프로세스당 열 수 있는 파일의 개수가 제한되어 있기 때문에, 사용 후에 닫아주는 것이 바람직하다.
4. ssize_t write(int fd, void *buffer, size_t nbyte);
wsize = write(fd, (char *)msg, strlen(msg));
파일 지시자(fd)에 해당하는 파일에 msg 데이터를 strlen(msg) 길이만큼 데이터를 적게 됩니다.
파일을 쓰고 나서, file offset은 마지막으로 쓴 byte의 바로 뒤로 이동하게 된다.
(참고로, file offset은 프로세스에서 관리하는 file table에 저장되는 읽기-쓰기 포인터 값이다)
5. ssize_t read(int fd, void *buffer, size_t nbyte);
#define MAX_BUF_SIZE 1024 ... char buf[MAX_BUF_SIZE + 1] = { '\0', }; ... rSize = read(fd, buf, MAX_BUF_SIZE);
파일 지시자(fd)에 해당하는 파일에 데이터를 MAX_BUF_SIZE만큼 읽어 buf에 저장한다.
파일을 읽고 나서, file offest은 마지막으로 읽은 byte의 바로 뒤로 이동하게 된다.
6. off_t lseek(int fd, off_t offset, int flag);
위에서 언급을 안했지만, 파일을 읽거나 쓴 뒤에는 file offset이 다른 위치로 이동한 상태라,
파일의 처음부터 다시 읽고 쓰고 싶다면, 이 file offset의 위치를 옮겨줘야 한다.
이 함수가 이 기능을 수행하게 된다.
lseek(fd, (off_t)0, SEEK_END);
파일 지시자(fd)에 해당하는 파일의 file offset을 파일의 끝에서 0만큼 이동한 위치로 바꾼다.
참고로 lseek의 플래그는 다음과 같다.
SEEK_SET: 파일의 시작 (0번째 데이터 인덱스)
SEEK_CUR: 파일의 file offset의 현재 위치
SEEK_END: 파일의 끝 (마지막 데이터의 다음 인덱스)