리눅스 시스템 및 네트워크 프로그래밍/시스템 프로그래밍

비동기 I/O와 고급 파일 처리 기법: 리눅스에서 효율적인 데이터 처리하기

ROBL 2025. 2. 3.
728x90
반응형

리눅스에서 파일 I/O를 효율적으로 처리하는 것은 시스템 성능에 큰 영향을 미칩니다. 일반적인 파일 입출력은 동기식(Synchronous) 방식으로 동작하며, 데이터가 읽히거나 쓰일 때까지 프로세스가 대기해야 합니다. 그러나 비동기 I/O(Asynchronous I/O)를 활용하면 대기 시간을 줄이고 시스템의 전체적인 처리량을 증가시킬 수 있습니다.

이번 글에서는 비동기 I/O 모델과 고급 파일 처리 기법을 소개하고, 이를 활용하는 방법을 코드와 함께 설명하겠습니다.


1. 비동기 I/O란?

비동기 I/O(Asynchronous I/O, AIO)파일 읽기/쓰기가 완료될 때까지 프로세스가 블로킹되지 않는 입출력 방식입니다. 리눅스에서 제공하는 주요 비동기 I/O 모델은 다음과 같습니다.

I/O 방식 설명  예제 시스템 콜
블로킹 I/O I/O 작업이 끝날 때까지 대기 read(), write()
논블로킹 I/O 즉시 반환하며, 데이터가 없으면 0 또는 오류 코드 반환 fcntl(fd, F_SETFL, O_NONBLOCK), read(), write()
I/O 다중화 여러 개의 파일 디스크립터를 감시하여 준비된 것만 처리 select(), poll(), epoll()
비동기 I/O 커널이 I/O 작업을 완료하면 알림을 줌 io_submit(), io_getevents()

2. 논블로킹 I/O

기본적으로 파일 디스크립터는 블로킹 모드로 동작합니다. 그러나 O_NONBLOCK 옵션을 설정하면 데이터가 없을 때 즉시 반환하는 논블로킹(Non-blocking) I/O 모드로 변경할 수 있습니다.

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("example.txt", O_RDONLY | O_NONBLOCK);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    char buffer[100];
    ssize_t bytes = read(fd, buffer, sizeof(buffer));
    if (bytes == -1) {
        perror("read");
    } else {
        write(STDOUT_FILENO, buffer, bytes);
    }

    close(fd);
    return 0;
}

논블로킹 I/O는 데이터가 준비되지 않으면 즉시 반환하며, 네트워크 소켓과 같은 대기 시간이 긴 리소스에서 유용하게 활용됩니다.


3. I/O 다중화 (Multiplexed I/O)

여러 개의 파일 디스크립터를 동시에 감시하는 기법입니다. 대표적인 방법으로 select(), poll(), epoll()이 있습니다.

3.1 select()

select()는 여러 파일 디스크립터를 감시할 수 있지만, 성능이 좋지 않습니다.

#include <sys/select.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(STDIN_FILENO, &read_fds);

    struct timeval timeout = {5, 0};  // 5초 대기
    int result = select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout);

    if (result == -1) {
        perror("select");
    } else if (result == 0) {
        printf("타임아웃\n");
    } else {
        printf("입력 감지됨\n");
    }

    return 0;
}
  • select()는 최대 감시 가능한 파일 디스크립터 수(FD_SETSIZE)가 제한되므로 성능이 낮습니다.

3.2 epoll() – 고성능 이벤트 감시

epoll()은 리눅스에서 대규모 동시 연결을 처리할 때 효과적인 비동기 I/O 방식입니다.

#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int epfd = epoll_create1(0);
    struct epoll_event event, events[10];

    int fd = open("example.txt", O_RDONLY | O_NONBLOCK);
    event.data.fd = fd;
    event.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);

    int n = epoll_wait(epfd, events, 10, 5000);
    if (n == 0) {
        printf("타임아웃\n");
    } else {
        printf("파일 읽기 가능\n");
    }

    close(fd);
    close(epfd);
    return 0;
}
  • epoll()은 대규모 소켓 서버에서 활용됩니다.

4. 비동기 I/O (AIO)

비동기 I/O는 작업 요청 후 커널이 완료를 알리는 방식으로 동작합니다.

#include <aio.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd = open("example.txt", O_RDONLY);
    struct aiocb aio;
    memset(&aio, 0, sizeof(aio));

    char buffer[100];
    aio.aio_fildes = fd;
    aio.aio_buf = buffer;
    aio.aio_nbytes = sizeof(buffer);
    aio.aio_offset = 0;

    aio_read(&aio);
    while (aio_error(&aio) == EINPROGRESS) {
        printf("I/O 진행 중...\n");
        sleep(1);
    }

    int ret = aio_return(&aio);
    if (ret > 0) {
        write(STDOUT_FILENO, buffer, ret);
    } else {
        perror("aio_read");
    }

    close(fd);
    return 0;
}
  • aio_read()를 호출하면 프로세스는 즉시 반환되며, aio_error()를 통해 완료 여부를 확인합니다.
  • 이벤트 기반 비동기 I/O 방식이므로, CPU 자원을 효율적으로 사용할 수 있습니다.

5. 고급 파일 처리 기법

5.1 mmap()을 활용한 메모리 매핑 I/O

파일을 메모리에 직접 매핑하여 처리 속도를 높일 수 있습니다.

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    int fd = open("example.txt", O_RDONLY);
    char *data = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0);

    if (data == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    write(STDOUT_FILENO, data, 4096);
    munmap(data, 4096);
    close(fd);
    return 0;
}
  • mmap()은 대량의 파일을 빠르게 읽거나 수정할 때 유용합니다.

6. 정리

  • 논블로킹 I/O는 파일이 준비되지 않았을 때 즉시 반환합니다.
  • I/O 다중화(select, epoll)는 여러 파일 디스크립터를 감시할 수 있습니다.
  • 비동기 I/O(AIO)는 커널이 완료를 알리는 방식으로 동작합니다.
  • mmap()을 사용하면 파일을 메모리에 매핑하여 빠르게 처리할 수 있습니다.

비동기 I/O를 이해하면 네트워크 서버, 데이터베이스, 고성능 파일 처리 시스템을 효율적으로 설계할 수 있습니다.

728x90
반응형

댓글

💲 추천 글