임베디드 관련 카테고리/Ubuntu

파일 디스크립터(fd)와 epoll 쉽게 이해하기

CBJH 2025. 2. 27.
728x90
반응형

리눅스에서 파일 디스크립터(fd)란?
epoll은 어떤 역할을 할까?
이 두 개념을 제대로 이해하면 고성능 네트워크 프로그래밍과 시스템 프로그래밍을 더 쉽게 다룰 수 있습니다.
오늘은 파일 디스크립터(fd)와 epoll이 어떻게 동작하는지 쉬운 예제와 함께 설명해보겠습니다.


🔹 파일 디스크립터(fd)란?

운영체제에서 파일, 디렉터리, 소켓, 파이프, 장치 파일 같은 모든 I/O 자원을 관리하는 번호(핸들)입니다.
**즉, fd는 "운영체제가 파일을 다루기 위해 부여하는 숫자"**라고 생각하면 됩니다.

📌 기본적인 fd 값

fd 값설명

0 표준 입력 (stdin, 키보드 입력)
1 표준 출력 (stdout, 터미널 출력)
2 표준 에러 (stderr, 오류 메시지)

우리가 프로그램을 실행할 때, 기본적으로 이 3개의 fd가 항상 열려 있습니다.
하지만 open()을 사용하면 추가적인 파일이나 디렉터리를 fd로 다룰 수 있습니다.


✅ 파일을 열고 fd를 확인하는 예제

#include <fcntl.h>  // open()
#include <unistd.h> // close()
#include <iostream>

int main() {
    int fd = open("test.txt", O_RDONLY); // 파일 열기
    if (fd == -1) {
        perror("파일 열기 실패");
        return 1;
    }

    std::cout << "파일 디스크립터: " << fd << std::endl;

    close(fd); // 파일 닫기
    return 0;
}

📌 실행하면 test.txt 파일을 열고 파일 디스크립터 번호가 출력됩니다.
예를 들어, fd = 3이 출력될 수 있습니다.
(fd 0~2는 이미 기본적으로 사용 중이기 때문에 3부터 할당됨)


✅ 디렉터리도 fd로 다룰 수 있음

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

int main() {
    int fd = open("/home/user", O_RDONLY); // 디렉터리 열기
    if (fd == -1) {
        perror("디렉터리 열기 실패");
        return 1;
    }

    std::cout << "디렉터리의 fd: " << fd << std::endl;

    close(fd); // 닫기
    return 0;
}

📌 디렉터리도 파일처럼 열어서 fd로 다룰 수 있습니다!
하지만 read()로 읽을 수는 없고, readdir() 같은 함수를 사용해야 합니다.


🔹 epoll이란?

리눅스에서 epoll은 다수의 파일 디스크립터를 효율적으로 감시하는 기술입니다.
파일뿐만 아니라 소켓, 파이프, 장치 파일 같은 다양한 fd를 감시할 수 있습니다.

출저 : yjseo01.log Velog

✅ epoll이 필요한 이유?

fd가 많아질수록 기존 select()나 poll() 방식은 성능이 떨어집니다.

  • 기존 방식 (select, poll)
    • 100개의 fd가 있다면, 모든 fd를 반복적으로 확인해야 함 → 비효율적
  • epoll 방식
    • **"이벤트가 생기면 알려줘!"**라고 운영체제에 요청
    • 이벤트 발생 시점에만 처리 → 성능 향상

✅ epoll을 활용한 FIFO 감지 예제

#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>  // mkfifo 함수 사용을 위한 헤더 추가
#include <iostream>

#define FIFO_PATH "./test_fifo"

int main() {
    // FIFO(이름 있는 파이프) 생성
    if (mkfifo(FIFO_PATH, 0666) == -1) {
        perror("mkfifo 실패");
        return 1;
    }

    int epfd = epoll_create1(0); // epoll 인스턴스 생성
    int fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); // 비동기 모드로 열기

    if (fd == -1) {
        perror("파일 열기 실패");
        return 1;
    }

    struct epoll_event ev;
    ev.events = EPOLLIN;  // 읽을 데이터가 있는지 감지
    ev.data.fd = fd;

    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); // epoll에 등록

    struct epoll_event events[5];

    while (true) {
        int n = epoll_wait(epfd, events, 5, -1); // 이벤트 대기

        if (n > 0) {
            std::cout << "데이터가 들어옴!" << std::endl;
            char buffer[128] = {0};
            read(fd, buffer, sizeof(buffer)); // 데이터 읽기
            std::cout << "읽은 데이터: " << buffer << std::endl;
        }
    }

    close(fd);
    close(epfd);
    return 0;
}

📌 코드 설명

  1. mkfifo(FIFO_PATH, 0666)
    → **이름 있는 파이프(FIFO)**를 생성
    (이미 존재하면 오류 발생하지 않음)
  2. open(FIFO_PATH, O_RDONLY | O_NONBLOCK)
    → FIFO를 읽기 전용 + 비동기 모드로 열기
  3. epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev)
    → epoll에 FIFO 감지 등록 (읽을 데이터가 생기면 감지)
  4. epoll_wait()
    새로운 데이터가 들어올 때까지 대기
    (데이터가 들어오면 read()로 읽고 출력)

✅ 실행 방법

1️⃣ 컴파일 & 실행

g++ -o epoll_fifo epoll_fifo.cpp
./epoll_fifo

2️⃣ 다른 터미널에서 데이터 입력

echo "Hello, epoll!" > test_fifo

🔹 FIFO로 데이터가 입력되면, epoll이 감지하고 출력됩니다.

 


🔹 fd와 epoll을 정리하면?

개념설명

파일 디스크립터(fd) 운영체제가 파일, 디렉터리, 소켓 등을 다룰 때 사용하는 번호(핸들)
epoll 여러 개의 fd에서 이벤트(데이터 읽기/쓰기 가능)를 감시하는 고성능 이벤트 감지 기술
epoll의 장점 select, poll보다 성능이 뛰어나며, 비동기 처리가 가능

epoll을 사용하면 다중 연결(소켓)이나 대량의 파일 이벤트를 더 효율적으로 감시 가능!
fd는 파일뿐만 아니라 디렉터리, 소켓, 파이프까지 다룰 수 있음


📌 마무리

  • **파일 디스크립터(fd)**는 운영체제가 파일, 디렉터리, 소켓 등을 다루기 위해 부여하는 고유한 번호입니다.
  • epoll은 fd를 효율적으로 감시하는 기술로, 다중 I/O 처리를 더 빠르게 할 수 있습니다.
  • open()으로 fd를 얻고, close()로 해제하는 것이 중요합니다.

댓글