[Linux] readline 구현
readline은 Command Line Interface (CLI)에서 줄 편집 및 입력 기록 저장 등의 역할을 하는 library다. 크게 두 부분 (client, server)로 나눠진다.
1 Byte 단위로 Data Read
Example
// server.c #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> ssize_t readline(int fd, void *vptr, size_t maxlen) { int n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = read(fd, &c, 1)) == 1) { *ptr++ = c; if (c == '\n') break; /* newline is stored, like fgets() */ } else if (rc == 0) { if (n == 1) return(0); /* EOF, no data read */ else break; /* EOF, some data was read */ } else return(-1); /* error, errno set by read() */ } *ptr = 0; /* null terminate like fgets() */ return(n); } int main() { int fd, ret; char buff[1024]; fd = open("myfifo", O_RDWR ); while( ret=readline( fd, buff, sizeof buff) ) write(1, buff, ret ); return 0; }
// client.c #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main() { int fd; char buff[1024]; fd = open("myfifo", O_RDWR ); getchar(); write( fd, "he", 2 ); getchar(); write( fd, "ll", 2 ); getchar(); write( fd, "o\n", 2 ); return 0; }
# Make fifo $ mkfifo mkfifo $ ./server ... $ ./client
위 코드를 빌드해서 각 세션에서 실행하게 되면 실제로 client에서 보내는 문자인 "hello"
가 마지막 '\n'
를 만날 때 전달되는 것을 볼 수 있다. 그러나 위 readline
의 함수의 문제가 있다.
Kernel 내 buffer는 4KB 단위로 읽게 되는데, readline()
함수를 보면 1 byte씩 읽는 것을 볼 수 있다.
if ( (rc = read(fd, &c, 1)) == 1)
따라서 처음 data를 읽을 때 4KB 단위로 읽도록 수정이 필요하다.
4 KB 단위로 Data Read
Example
#define MAXLINE 4096 static ssize_t my_read(int fd, char *ptr) { static int read_cnt = 0; static char *read_ptr; static char read_buf[MAXLINE]; if (read_cnt <= 0) { if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { return(-1); } else if (read_cnt == 0) return(0); read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return(1); } ssize_t readline(int fd, void *vptr, size_t maxlen) { int n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1) { *ptr++ = c; if (c == '\n') break; /* newline is stored, like fgets() */ } else if (rc == 0) { if (n == 1) return(0); /* EOF, no data read */ else break; /* EOF, some data was read */ } else return(-1); /* error, errno set by read() */ } *ptr = 0; /* null terminate like fgets() */ return(n); }
위 코드를 적용하게 되면 처음 my_read()를 호출하게 되면 4KB 단위로 읽기 때문에 buffer 읽은 위치, count 등을 계속 가지고 있어야 하기 때문에 static을 사용했다.
static int read_cnt = 0; static char *read_ptr; static char read_buf[MAXLINE];
이렇게 되면 전역변수가 사용되는데, 전역변수가 사용되면서 다른 thread가 수정할 수 있는 가능성이 존재한다. 따라서 이 부분이 buffer를 활용해서 reentrent한 코드로 수정이 돼야한다.
외부 Buffer를 사용
Example
typedef struct { int read_cnt; char *read_ptr; char read_buf[MAXLINE]; } Rline; static ssize_t my_read(int fd, char *ptr, Rline *buff) { if (buff->read_cnt <= 0) { if ( (buff->read_cnt = read(fd, buff->read_buf, sizeof(buff->read_buf))) < 0) { return(-1); } else if (buff->read_cnt == 0) return(0); buff->read_ptr = buff->read_buf; } buff->read_cnt--; *ptr = *buff->read_ptr++; return(1); } ssize_t readline(int fd, void *vptr, size_t maxlen) { int n, rc; char c, *ptr; Rline buff = {0,}; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c, &buff)) == 1) { *ptr++ = c; if (c == '\n') break; } else if (rc == 0) { if (n == 1) return(0); else break; } else return(-1); } *ptr = 0; return(n); }
나머지 함수는 그대로 사용하고, server.c에 readline()
함수 부분을 위와 같이 수정해주면 된다.