파일 I/O: 범용 I/O 모델
System Program/I/O 2013. 10. 10. 07:281. 입출력 관련 시스템 호출
I/O를 수행하는 모든 시스템 호출은 파일 디스크립터라는 음이 아닌 정수를 통해 열려 있는 파일을 참조한다.
파일 디스크립터는 파이프, FIFO, 소켓, 터미널, 디바이스, 일반 파일 등 종류에 상관없이 모든 열려 있는 파일을 참조한다.
유닉스/리눅스의 I/O처리 철학이 모든 입출력 장치를 파일로 관주하는 맥락과 일치한다.
2. 표준 파일 디스트립터
파일 디스트립터 |
목적 |
POSIX 이름 |
STDIO STREAM |
0 |
표준 입력 |
STDIN_FILENO |
stdin |
1 |
표준 출력 |
STDOUT_FILENO |
stdout |
2 |
표준 에러 |
STDERR_FILENO |
stderr |
위의 세 디스트립터는 항상 열려 있는데, 이유는 쉘을 통해 실행되는 프로그램은 쉘로부터 항상 열려있는 세개의 디스트립터를 상속받기 때문이다. 이 개념은 매우 중요하다. 네트워크나 이와 유사한 프로그램에서 dup()함수를 통해서 열려 있는 소켓의 디스크립터의 번호를 재 정의할 때 이 개념이 정립되어 있어야지 이해할 수 있다.
또한 프로그램에서 파일 디스트립터의 숫자를 통해서 파일을 처리할 수 있으나, 가능하면 <unistd.h>에 정의된 POSIX 표준 이름을 쓰는 편이 좋다.
2.1. 파일 열기: open()
#include <fcntl.h>
int open(const char *pathname, int flags, ... /* mode_t mode */);
성공하면 파일 디스크립터를 리턴하고, 에러가 발생하면 -1을 리턴한다.
open()함수가 리턴하는 파일 디스크립터는 항상 프로세스에서 사용하지 않는 가장 작은 수를 리턴한다. 따라서 기본적으로 앞에서 설명했듯이 0 ~ 2는 사용하고 있으므로 상식적으로 3부터 차례로 리턴될 것이다.
이런 동작 방식에 대한 이해를 하는 것이 매우 중요하다.
open()함수에서 사용하는 flags에 대해서 아래와 같이 정리할 수 있다.
플래그 |
목적 |
표준 |
O_RDONLY |
읽기 전용으로 연다 |
v3 |
OWRONLY |
쓰기 전용으로 연다 |
v3 |
O_RDWR |
읽고 쓰기용으로 연다 |
v3 |
O_CLOEXEC |
실행 시 닫기 플래그를 설정한다 |
v4 |
O_CREAT |
파일이 이미 존재하면 새로 만든다 |
v3 |
O_DIRECT |
파일 I/O가 버퍼 캐시를 우회한다 |
|
O_DIRECTORY |
pathname이 디렉토리가 아니면 실패한다 |
v4 |
O_EXCL |
O_CREAT와 함께, 배타적으로 파일을 만든다 |
v3 |
O_LARGEFILE |
32비트 시스템에서 큰 파일을 열 때 쓴다 |
|
O_NOATIME |
read()시에 파일 최종 접근 시간을 갱신하지 않는다 |
|
O_NOCTTY |
pathname으로 제어 터미널이 되지 않게 한다 |
v3 |
O_NOFOLLOW |
심볼링크를 역참조하지 않는다 |
v4 |
O_TRUNC |
기존 파일의 길이를 0으로 설정한다 |
v3 |
O_APPEND |
언제나 파일 끝에 추가해서 쓴다 |
v3 |
O_ASYNC |
I/O가 가능해지면 시그널을 보낸다 |
|
O_DSYNC |
동기 I/O 데이터 무결성을 제공한다 |
리눅스 : 2.6.33부터 |
O_NONBLOCK |
비블로킹 모드로 연다 |
v3 |
O_SYNC |
파일 쓰기를 동기 모드로 설정한다 |
v3 |
■ open() error 값
에러가 발생하면 '-1'을 리턴하고, errno를 보면 에러의 원인을 알 수 있다. 발생 가능한 에러에 대해서 살펴본다.
EACCES |
호출하는 프로세스가 flags에 지정한 모드로 파일에 접근할 수 없다 |
EISDIR |
지정된 파일이 디렉토리이고, 호출자가 쓰기용으로 열려고 했다 |
EMFILE |
파일디스크립터의 개수가 프로세스의 한도를 넘었다 |
ENFILE |
열린 파일의 개수가 시스템 전체 한도에 도달했다 |
ENOENT |
지정된 파일이 존재하지 않고, O_CREAT가 지정되지 않았거나, 지정되었더라도 pathname으로 지정된 경로명이 존재하지 않는다 |
EROFS |
파일이 읽기전용인데 호출자 쓰기용으로 열려고 했다 |
ETXBSY |
지정된 파일이 실행 중에 있다. |
2.2. create() 시스템 호출
int create(const char *pathname, mode_t mode );
성공하면 파일 디스크립터를 리턴하고, 에러가 발생하면 -1을 리턴한다.
open()과 동일한 기능이지만 현재는 사용되지 않는다.
2.3. read() 시스템 호출
ssize_t read(int fd, void *buffer, size_t count);
읽은 바이트 수를 리턴한다. EOF인 경우에는 0을 에러가 발생하면 -1을 리턴
위의 함수에서 size_t count는 매개변수는 읽을 최대 바이트 수를 지정한다.
2.4. write() 시스템 호출
ssize_t write(int fd, void *buffer, size_t count);
쓴 바이트 수를 리턴한다. 에러가 발생하면 -1을 리턴
디스크에서 I/O를 수행할 때 write()가 성공하였다고해서 데이터가 디스크로 전송되었다고 보증할 수는 없다. 커널의 디스크 I/O 동작에 대해서 버퍼링을 수행하기 때문이다. 자세한 내용은 차 후에 보도록 한다.
2.5. close() 시스템 호출
int close(int fd);
성공하면 0을 에러가 발생하면 -1을 리턴
2.6. lseek() 시스템 호출
off_t lseek(int fd, off_t offset, int whence);
성공하면 새로운 파일 오프셋을 리턴하고, 에러가 발생하면 -1을 리턴
위에서 offset은 바이트 단위로 설정하여 파일 시작에서 몇 바이트 떨어져 있는 지점을 지정하기 위해서 사용된다.
뒤의 whence는 아래와 같은 의미를 갖는다. 이 함수는 모든 종류의 디바이스에 적용할 수 있는 것은 아니다. 일반적으로
파일에 적용될 수 있다고 보면 된다.
- SEEK_SET : 파일 오프셋 계산의 기준점이 파일의 시작점으로 설정
- SEEK_CUR : 파일 오프셋의 기준점을 현재 파일 오프셋으로 설정
- SEEK_END : 파일 오프셋의 기준점을 파일 끝으로 설정
2.7. ioctl() 시스템 호출
#include <sys/ioctl.h>
int ioctl(int fd, int request, ... /*argp */);
성공하면 request에 따라 다르다, 에러가 발생하면 -1을 리턴
위의 사용은 일반적으로 디바이스 드라이버 응용에서 많이 사용하는 방식이다. 커널쪽에 ioctl의 request에 대응하는 코드가 별도로 구현되야 한다.
즉 위의 함수는 앞서 설명한 범용 I/O 모델에서 벗어나는 파일과 디바이스 오퍼레이션을 위한 범용 메카리즘이다.
3. 기타
커널 2.6.22부터는 /proc/PID/fdinfo 디렉토리에서 각 프로세스에 대한 파일 디스크립터 정보를 얻을 수 있다.
자세한 내용은 참고서적 144Page 참조
'System Program > I/O' 카테고리의 다른 글
고급 I/O 모델 : poll() 함수 (0) | 2014.03.12 |
---|---|
고급 I/O 모델 : select() 시스템 호출 (0) | 2014.03.01 |
access() 함수 (0) | 2014.02.26 |
stat() 관련 함수 (0) | 2014.02.26 |
fcntl() 함수 (0) | 2014.02.21 |