Skip to content

Robust I O

ChoYG edited this page Aug 4, 2016 · 1 revision

Robust I/O(RIO package)

기본 UNIX I/O함수 read, write 등을 사용하면서 불편한 점을 해결하기 위해 Computer Systems: A Programmer's Perspective 필자가 제시하는 방법이다. 기존 read나 write 함수는 다음과 같은 불편한 점이 있다고 한다.
(Files and I/O, CS61, Lecture 22, November 17, 2011 (Stephen Chong, Harvard School of Engineering and Applied Sciences) 참조)

  • 기존 UNIX I/O는 내가 원하는 만큼 받고 쓰는것을 보장하지 못한다.
    예를들어 read(fd, buffer,50); 을 한다 해도 읽을 내용이 부족하면 읽은 개수를 리턴하지, 50개를 체우려고 하지 않는다.
  • 터미널에서 ctrl + c를 하면 인터럽트 시그널이 발생하는데, 그런 경우에도 동작이 멈춘다.
    이 경우 문제가 되는 이유는, 몇몇 특수한 케이스에서 이런 시그널이 발생해도 데이터 읽기는 계속 해야 하는 경우가 있기 때문이다.
  • 매번 에러체크를 해줘야 한다.
    그리고 그것이 내 코드를 화나게~~(빡치게)~~ 한다.

이런 몇몇 불편한 점들을 해결하기 위해 제시된 I/O 가 RIO package다. 솔직히 처음에는 도데체 이게 왜 필요하며, 무슨 쓸모가 있고, 결론적으로 왜 이런 쓸때없어 보이는 동작을 해야하는지 이해하지 못했기 때문에,,, Harvard School of Engineering and Applied Sciences에서 과거 강의한 cs61 강좌를 좀 봤다. 그곳에서 위 문제점들을 언급했는데, 저게 문제인가보다(아래 두개는 이해가 되는데,,,).

RIO package 가 포함된 csapp.h, csapp.c 파일은 아래 사이트에서 받을 수 있다.

Instructor Site: Code Examples

rio_readn

rio_readn(파일 디스크립터, 저장할 버퍼, 저장할 버퍼 크기)

그냥보면 일반 read 함수와 인자는 크게 다를 게 없다. 일반 read 함수와 다른점은 다음 경우를 제외하고는 저장할 버퍼 크기만큼 read 하는것을 보장한다.

  • read 가 -1을 리턴하는 경우(파일을 열수 없거나 읽기권한이 없거나 등등,,,)
  • 파일을 끝까지 전부 읽었을 때

다음의 경우 동작을 멈추지 않음

  • sig handler 에 의한 중지(ctrl + c 같은)

rio_writen

rio_writen(파일 디스크립터, 쓰기 할 데이터 버퍼, 버퍼 크기) 

이 역시 일반 write와 크게 다른점은 없어보이지만, 다음과 같은 동작을 수행한다는 점이 있다.

  • 쓰기 수행시 언제나 버퍼 크기만큼 쓰기동작을 수행하려 한다.
  • 에러 발생시 -1, 정상적인 경우 언제나 리턴값은 버퍼 크기와 같다.(그만큼 쓰려고 하니까,,,)

rio_t 구조체

/* $begin rio_t */
#define RIO_BUFSIZE 8192
typedef struct {
    int rio_fd;                /* Descriptor for this internal buf */
    int rio_cnt;               /* Unread bytes in internal buf */
    char *rio_bufptr;          /* Next unread byte in internal buf */
    char rio_buf[RIO_BUFSIZE]; /* Internal buffer */
} rio_t;
/* $end rio_t */

Unix read, write 에서 파일 디스크립터를 인자로 받아 사용하였다면, RIO package 에서는 rio_t 라는 별도의 구조체가 존제한다. 이 구조체에는 파일 디스크립터 정보는 물론 내부적인 임시 버퍼와 관련된 정보들도 포함되어 있다.

  • rio_fd
    기존 파일 디스크립터 정보
  • rio_cnt
    내부 버퍼의 읽은 바이트 수
    (unread bytes 라고 하는데, 값이 0일때 비었다고 판단하는걸 보면,,, '읽지 않은 바이트 수' 가 아니라 '읽어드린 바이트 수' 라고 보는게 맞는 것 같다.)
  • rio_bufptr
    내부 버퍼에서 읽고 쓰는 위치를 지정하기 위한 포인터
  • rio_buf[RIO_BUFSIZE]
    내부 버퍼

rio_t 구조체 초기화

void rio_readinitb(rio_t *rp, int fd) 
{
    rp->rio_fd = fd;  
    rp->rio_cnt = 0;  
    rp->rio_bufptr = rp->rio_buf;
}

그냥 rio_t 구조체에 파일 디스크립터 정보를 담고, 내부 버퍼를 빈 버퍼로 초기화 하는 간단한 함수다.

rio_read

rio_read(rio_t 구조체 변수, 저장할 버퍼, 저장할 버퍼 크기)

rio_readn와의 차이점은 파일 디스크립터 대신 rio_t 구조체를 이용한다는 것이다. 구조체를 이용함으로서 기존 read동작에 '내부 버퍼'로 저장 후 '저장할 버퍼'에 복사하도록 되어있다.

  • 내부 버퍼가 비었으면 일단 내부 버퍼부터 체운다.
  • read 함수가 -1 리턴시 그대로 -1 리턴가
  • 내부 버퍼를 저장할 버퍼에 복사한다.
  • 저장할 버퍼 크기보다 적으면 읽은 만큼만 리턴, 크기만큼 다 읽었으면 크기와 같은 값을 리턴턴

다음의 경우 동작을 멈추지 않음

  • sig handler 에 의한 중지(ctrl + c 같은)

쉽게 정리하면, '일반read + 내부 버퍼링 + sig handler 대처' 가 포함된, 업그레이드판 read다

rio_readn이 만드시 저장할 버퍼 크기만큼 읽어드리는것을 보장했다면, rio_read는 그러한 점은 보장하지 않되 내부 버퍼를 사용해서 갑작스런 sig handler 에도 대처할 수 있도록 조치되어있다.

rio_readnb

rio_readnb(rio_t 구조체, 저장할 버퍼, 저장할 버퍼 크기) 

다음 경우를 제외하고는 저장할 버퍼 크기만큼 read 하는것을 보장한다.

  • rio_read 가 -1을 리턴하는 경우(파일을 열수 없거나 읽기권한이 없거나 등등,,,)
  • 파일을 끝까지 전부 읽었을 때

다음의 경우 읽기 동작을 멈추지 않음

  • sig handler 에 의한 중지(ctrl + c 같은)

rio_readn과 다른점은, read 대신 rio_read를 이용하여 rio_t 구조체의 내부 버퍼를 같이 활용하는 점이다. 즉, rio_readnb 를 활용할 경우 다음과 같은 점이 보장된다.

  • 읽기 수행에 지장이 없고, 다 읽은게 아니면, 지정한 버퍼 크기만큼 읽는다.
  • sig handler 에 의한 갑작스런 중지를 방지한다.
  • rio_t 구조채의 내부 버퍼를 활용해 갑작스런 데이터 손실을 막는다.

rio_readlineb

rio_readlineb(rio_t 구조체, 저장할 버퍼, 저장할 버퍼 크기)

rio_readnb가 저장할 버퍼 크기만큼 읽는것을 보장했다면, rio_readlineb는 한 줄 단위('\n'으로 구분)로 읽어드린다.

rio_readlineb 는 다음과 같은 동작을 한다.

  • (개행문자)'\n'를 만날때까지 읽는다.
  • 읽을 데이터가 없을 경우 0을 리턴한다.
  • 읽다가 EOF를 만나면 읽은 만큼만 읽는다.

rio_read를 사용하므로 다음이 보장된다.

  • sig handler 에 의한 갑작스런 중지(ctrl + c 같은) 대처
  • rio_t 구조채의 내부 버퍼를 활용해 갑작스런 데이터 손실을 막는다.

그 이외에 에러처리를 겸한 래퍼 함수들도 포함되어 있으니 확인해보는 것이 좋을 것이다. (예로 rio_readnb의 에러처리 포함 함수는 Rio_readnb)

Clone this wiki locally