Linux,  Programming

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

Leave a Reply

Your email address will not be published. Required fields are marked *