개요
파일 읽기
read() 시스템 콜
1 2 3 | #include <unistd.h> ssize_t read(int fd, void *buf, size_t len); | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { int fd; fd = open("TEXT", O_RDONLY); if(fd == -1) { fprintf(stderr, "Failed to open file.\n"); exit(0); } else printf("Successfully opened file.\n"); unsigned long word; ssize_t nr; /* fd에서 몇 바이트를 읽어 'word'에 저장한다. */ nr = read(fd, &word, sizeof(unsigned long)); if(nr == -1) { fprintf(stderr, "Failed to read file.\n"); exit(0); } else printf("Successed reading file.\n"); return 0; } | cs |
반환값
read()가 len보다 작은 양수값을 반환하는 일은 파일에서 len보다 적은 바이트만 사용 가능하거나, Signal이 시스템 콜을 중단시키거나, 파일 디스크립터가 파이프라면 파이프에 문제가 생기는 등 다양한 원인이 있다. 그렇다면 반환값이 0일 때는 어떤 경우일까? read() 시스템 콜이 EOF를 알려줄 때이다. 즉, 읽을 바이트가 남아 있지 않다는 뜻과 같다. -1을 반환할 때와 다른 점이 있다면 EOF는 에러로 취급되지 않고, 단순히 file offset 이 파일에서 마지막으로 유효한 offset 을 넘어갔다는 상태를 나타내기 때문에 그저 '더 이상 읽을 게 없다'는 정보만을 알려줄 뿐이다.
EOF와는 달리, len바이트만큼 읽으라고 요청했지만 더 읽을 게 없다면 그건 에러로 취급된다. 이 때 read()는 읽을 바이트가 생길 때까지 block된다. 다시 말해 사용 가능한 데이터가 없는 것과 파일 끝에 도착했다는 것에는 차이가 있다.
자신이 설계한 코드에서 에러나 버그가 발생한다면 프로그래머 입장에서는 굉장히 착잡할 것이다. 하지면 몇몇 에러는 복구가 가능하니 너무 상심하지는 말자. 예를 들어 읽기도 전에 시그널이 read()를 중단시켰을 경우에도 read()는 -1을 반환하고, errno를 EINTR로 설정하는데, 이 경우에는 한 번 더 읽기 요청을 하는 것이 가능하다. read()는 다양한 가능성을 가지고 있는 시스템 콜이니, 다양한 경우를 고려하여 에러의 원인을 유추할 수 있다. 1
전체 바이트 읽기
1 2 3 4 5 6 7 8 9 10 11 12 | ssize_t ret; while(len != 0 && (ret = read(fd, buf, len)) != 0) { if(ret == -1) { if(errno == EINTR) continue; perror("Reading Error"); break; } len -= ret; buf += ret; } | cs |
논블록 읽기
read() 호출이 블록되지 않았으면 좋겠는데, 어떻게 하지?
블록되는 대신 읽을 데이터가 없으면 알려주기 위해 호출이 그 즉시 반환되는 상황을 원할 때도 있다. 이러한 것을 논블록 입출력이라고 한다. 이러한 입출력 방식은 애플리케이션이 잠재적으로 다중 파일 입출력을 수행하도록 한다. 따라서 어떤 특정한 파일에서 read() 호출이 블록되면서 다른 파일에서 사용할 수도 있는 데이터를 못 쓰게 되는 현상을 방지해준다는 장점이 있다.
이 때에는 errno값 EAGAIN을 추가로 점검할 필요가 있다. 파일 디스크립터를 논블록 모드로 열었는데, 읽을 데이터가 없을 때 read() 호출은 블록되는 대신 -1을 반환하며 errno를 EAGAIN으로 설정한다. 논블록 읽기에서 EAGAIN을 점검하지 않는다면 데이터 부족이라는 그냥 간단한 오류가 심각한 에러로 처리되어 알려질 수도 있다. 다음 예제를 살펴보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 | char buf[255]; ssize_t nr; start: nr = read(fd, buf, 255); if(nr == -1) { if(errno = EINTR) goto start; else if(errno = EAGAIN) else { fprintf("error"); exit(0); } } | cs |
그 외 에러 값
에러 코드 |
설명 |
EBADF |
주어진 파일 디스크립터가 유효하지 않거나 읽기 가능한 모드로 열리지 않았다. |
EFAULT |
buf로 전달된 포인터가 호출하는 프로세스의 주소 공간 밖에 존재한다. |
EINVAL |
파일 디스크립터가 읽기를 허용하지 않는 객체에 맵핑되어 있다. |
EIO |
저수준 입출력 에러 발생 |
read() 크기 제약
앞에서 몇 번 코드를 짚어보다 보면, 궁금증이 생겼을 것이다. 도대체 size_t는 무엇이고, ssize_t는 뭐지? 자료형인 것 같은데... 이들은 POSIX에서 지원하는 자료형(타입)이다. size_t 타입은 바이트 단위로 크기를 측정하기 위해 사용되는 값을 저장한다. ssize_t는 signed size_t를 뜻하며, 부호가 있는 size_t 값이다. ssize_t에서 음수 값은 에러를 표현하기 위해 사용된다. size_t와 ssize_t는 종종 둘이 함께 사용되기 때문에 잠재적으로 범위가 좀 더 작은 ssize_t가 size_t의 범위를 제한한다.
size_t의 최댓값은 SIZE_MAX, ssize_t의 최댓값은 SSIZE_MAX라는 상수로 정의되어 있다. len이 SSIZE_MAX보다 큰 경우에서 read() 시스템 콜 호출 결과는 정의되어 있지 않다. 대부분의 리눅스 시스템에서 SSIZE_MAX = LONG_MAXd이며, 32bit에서는 0x7ffffffff이다. 한 번 읽기에는 비교적 큰 값이다...
앞에서 언급했던 읽기 루프를 조금 더 개선해서 범용 읽기 루틴으로 만들고 싶다면 다음과 같이 수정해 보자. len을 0으로 둔 상태에서 read()를 호출하면 즉시 0을 반환한다.
1 | if(len > SSIZE_MAX) len = SIZE_MAX | cs |
'System > System Programming' 카테고리의 다른 글
6. 파일 닫기 - close() (0) | 2019.04.24 |
---|---|
5. 직접 입출력 (0) | 2019.04.24 |
4. 동기식 입출력 - fsync(), fdatasync() (2) | 2019.04.22 |
3. 파일 쓰기 - write() (0) | 2019.04.16 |
1. 파일 열기 - open(), creat() (0) | 2019.04.11 |