한 줄 정의
BSD/macOS 커널이 제공하는 이벤트 통지 메커니즘. 소켓뿐 아니라 파일 변경, 프로세스 종료, 시그널, 타이머 등 다양한 커널 이벤트를 하나의 인터페이스로 감시할 수 있다.
쉽게 말하면
epoll이 “네트워크 전용 알림 시스템”이라면, kqueue는 “건물 전체 통합 관제 시스템”입니다. 네트워크 이벤트는 물론이고 파일 변경, 프로세스 종료, 타이머 만료까지 하나의 대기열에서 모두 받아볼 수 있습니다.
왜 이걸 알아야 하는가?
- macOS/BSD 환경의 서버 개발: macOS에서 개발하고 Linux에 배포하는 경우, 로컬에서는 kqueue가 동작하고 프로덕션에서는 epoll이 동작합니다. Netty, libuv 같은 프레임워크가 이 차이를 추상화하지만, 디버깅이나 성능 분석 시 어떤 메커니즘이 동작하는지 알아야 합니다.
- epoll과의 비교를 통한 이해 심화: 같은 문제(select/poll의 O(n) 한계)를 Linux와 BSD가 서로 다른 방식으로 해결했습니다. 둘을 비교하면 I/O 다중화의 본질적인 설계 원칙을 더 깊이 이해할 수 있습니다.
왜 이렇게 설계했는가?
- 해결하는 문제: epoll과 동일하게 select/poll의 O(n) 한계를 해결합니다. 추가로 BSD 진영은 소켓 I/O뿐 아니라 모든 종류의 커널 이벤트를 하나의 통합 인터페이스로 감시하고 싶다는 설계 목표가 있었습니다.
- epoll보다 먼저 등장: kqueue는 2000년(FreeBSD 4.1)에, epoll은 2002년(Linux 2.5.44)에 등장했습니다. 두 프로젝트는 독립적으로 개발되었으며, kqueue가 범용 이벤트 시스템을 지향한 반면 epoll은 네트워크 I/O에 집중했습니다.
어떻게 동작하는가?
두 가지 시스템 콜
kqueue는 epoll의 세 개(create/ctl/wait)와 달리 두 개만으로 동작합니다.
1. kqueue() — kqueue 인스턴스 생성
int kq = kqueue();2. kevent() — 이벤트 등록과 대기를 하나의 호출로 처리
struct kevent changes[2]; // 등록할 이벤트
struct kevent events[10]; // 수신할 이벤트
// 소켓 읽기 감시 등록
EV_SET(&changes[0], sockfd, EVFILT_READ, EV_ADD, 0, 0, NULL);
// 파일 변경 감시 등록
EV_SET(&changes[1], filefd, EVFILT_VNODE, EV_ADD, NOTE_WRITE, 0, NULL);
// 등록 + 대기를 한 번에
int n = kevent(kq, changes, 2, events, 10, &timeout);
for (int i = 0; i < n; i++) {
handle(events[i]);
}epoll은 등록(epoll_ctl)과 대기(epoll_wait)가 분리되어 있지만, kqueue는 kevent() 한 번의 호출로 이벤트 등록과 수신을 동시에 처리할 수 있습니다.
struct kevent 구조
struct kevent {
uintptr_t ident; // 식별자 (fd, PID, 시그널 번호 등)
int16_t filter; // 이벤트 종류 (EVFILT_READ, EVFILT_VNODE 등)
uint16_t flags; // 동작 플래그 (EV_ADD, EV_DELETE, EV_ONESHOT 등)
uint32_t fflags; // 필터별 세부 플래그
intptr_t data; // 필터별 데이터 (읽기 가능 바이트 수 등)
void *udata; // 사용자 정의 데이터
};ident + filter 조합이 이벤트를 고유하게 식별합니다. 같은 fd에 대해 READ와 WRITE를 별도로 등록할 수 있습니다.
필터 (Filter) 시스템
kqueue의 가장 큰 특징은 다양한 커널 이벤트를 필터로 구분하여 하나의 인터페이스로 처리하는 것입니다.
| 필터 | 감시 대상 | 용도 |
|---|---|---|
EVFILT_READ | fd | 소켓/파이프에 읽을 데이터 도착 |
EVFILT_WRITE | fd | 소켓/파이프에 쓰기 가능 |
EVFILT_VNODE | fd | 파일/디렉토리 변경 (수정, 삭제, 이름변경) |
EVFILT_PROC | PID | 프로세스 이벤트 (종료, fork, exec) |
EVFILT_SIGNAL | 시그널 번호 | 시그널 수신 |
EVFILT_TIMER | 임의 ID | 타이머 만료 |
epoll은 기본적으로 fd 기반 I/O 이벤트만 처리하므로, 프로세스 감시는 signalfd, 타이머는 timerfd, 파일 변경은 inotify를 별도로 만들어서 epoll에 등록해야 합니다. kqueue는 이 모든 것이 하나의 필터 시스템에 통합되어 있습니다.
내부 동작
epoll과 마찬가지로 커널에 상태를 유지하는 stateful 방식입니다.
kqueue()로 커널에 이벤트 큐 인스턴스를 생성합니다.kevent()의 changelist로 감시 대상을 등록하면, 커널이 해당 객체(소켓, 파일, 프로세스)에 콜백을 등록합니다.- 이벤트가 발생하면 커널이 콜백을 통해 kqueue의 ready list에 이벤트를 추가합니다.
kevent()의 eventlist로 ready list에 쌓인 이벤트만 반환합니다.
시각화
epoll vs kqueue API 매핑
epoll kqueue
───────────────────── ─────────────────────
epoll_create() → kqueue()
epoll_ctl(ADD/MOD/DEL) → kevent(changelist)
epoll_wait() → kevent(eventlist)
epoll: 3개 시스템 콜 kqueue: 2개 시스템 콜
(등록과 대기 분리) (등록과 대기 통합 가능)
kqueue 동작 흐름
sequenceDiagram participant App as 애플리케이션 participant K as 커널 (kqueue) participant Net as 네트워크 participant FS as 파일시스템 App->>K: kqueue() → 인스턴스 생성 App->>K: kevent(changelist=[소켓 READ, 파일 VNODE]) Note over K: 소켓과 파일 모두 하나의 kqueue에 등록 Net-->>K: 소켓에 데이터 도착 → ready list에 추가 FS-->>K: 파일 수정 감지 → ready list에 추가 K-->>App: kevent() 리턴: [{소켓 READ}, {파일 VNODE}]
정리
| 기준 | epoll (Linux) | kqueue (BSD/macOS) |
|---|---|---|
| 등장 | 2002 (Linux 2.5.44) | 2000 (FreeBSD 4.1) |
| 시스템 콜 수 | 3개 (create, ctl, wait) | 2개 (kqueue, kevent) |
| 등록/대기 | 분리 (ctl ≠ wait) | 통합 가능 (kevent 하나로) |
| 감시 대상 | fd 기반 I/O | fd, 프로세스, 시그널, 타이머, 파일 변경 |
| 파일 변경 감시 | inotify 별도 필요 | EVFILT_VNODE 내장 |
| 타이머 | timerfd 별도 필요 | EVFILT_TIMER 내장 |
| ET/LT 모드 | 둘 다 지원 | 기본 ET와 유사, EV_CLEAR로 제어 |
| 배치 변경 | ctl 호출마다 1개씩 | changelist로 여러 개 한 번에 |
| 성능 | O(1) | O(1) |
실무에서 만난 사례
- Nginx:
Linux에서는 epoll, FreeBSD/macOS에서는 kqueue를 사용합니다.
ngx_kqueue_module.c와ngx_epoll_module.c가 각각 플랫폼별 구현이며, 상위 레이어에서는 동일한 이벤트 인터페이스로 추상화됩니다. - libuv (Node.js): Node.js의 이벤트 루프 라이브러리인 libuv가 Linux에서는 epoll, macOS에서는 kqueue, Windows에서는 IOCP를 내부적으로 선택합니다. 개발자는 플랫폼 차이를 신경 쓰지 않아도 됩니다.
- macOS에서의 개발:
로컬 개발 시
java.nio.channels.Selector는 macOS에서 kqueue 기반KQueueSelectorImpl을 사용합니다. Linux 프로덕션에서는EPollSelectorImpl로 바뀌므로, 플랫폼 간 미묘한 동작 차이가 있을 수 있습니다.
관련 개념
출처
- FreeBSD man pages: kqueue(2), kevent(2)
- Jonathan Lemon, “Kqueue: A generic and scalable event notification facility” (USENIX 2001)
- Apple Developer Documentation: kqueue