[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() 함수 부분을 위와 같이 수정해주면 된다.