
io_uring은 리눅스에서 비동기 I/O를 다루기 위한 전용 인터페이스입니다. 핵심은 애플리케이션과 커널이 submission queue와 completion queue를 공유하고, 사용자는 요청을 queue에 올린 뒤 완료 결과를 다시 queue에서 읽는다는 점입니다.
기준은 단순합니다. 파일, 네트워크, timeout, accept 같은 여러 I/O 작업을 한 이벤트 루프에서 많이 처리하고 syscall 횟수와 복사 비용이 병목이 되는 구조라면 io_uring을 검토할 수 있습니다. 반대로 요청 수가 적거나 이식성이 더 중요하거나 기존 epoll 기반 구조가 충분히 단순하다면 먼저 현재 구조를 유지하는 편이 안전합니다.

io_uring은 무엇을 바꾸나
man-pages 기준 io_uring은 Linux-specific asynchronous I/O API입니다. io_uring_setup()으로 ring context를 만들고, mmap()으로 submission queue와 completion queue를 사용자 공간에 매핑합니다. 애플리케이션은 submission queue entry, 즉 SQE에 작업 내용을 적어 넣고 커널은 완료 결과를 completion queue entry, 즉 CQE로 돌려줍니다.
이 구조가 기존 방식과 다른 지점은 요청 전달 방식입니다. 전통적인 I/O는 작업마다 시스템 호출을 반복하는 형태가 많습니다. io_uring은 여러 요청을 queue에 쌓고 한 번에 제출하거나, 일부 모드에서는 제출 syscall 자체를 줄이는 방향으로 설계되어 있습니다. 그래서 "비동기 파일 I/O API"로만 보면 좁고, 실제로는 다양한 I/O opcode를 queue 기반으로 다루는 인터페이스에 가깝습니다.
| 구성 요소 | 역할 | 확인할 점 |
|---|---|---|
| SQE | 사용자 공간에서 커널에 제출할 작업을 설명 | opcode, 파일 디스크립터, 버퍼, offset 같은 요청 정보를 정확히 채워야 함 |
| CQE | 커널이 완료된 작업의 결과를 사용자 공간에 전달 | res 값으로 성공 결과나 음수 오류 코드를 확인해야 함 |
| ring fd | io_uring_setup()이 반환하는 ring 인스턴스 참조 |
io_uring_enter(), io_uring_register() 호출에 사용됨 |
기본 흐름은 어떻게 돌아가나
저수준 시스템 호출 기준 흐름은 세 단계입니다. 먼저 io_uring_setup()으로 queue 크기와 옵션을 정해 ring을 만듭니다. 그다음 애플리케이션이 SQE를 준비하고 submission queue에 배치합니다. 마지막으로 io_uring_enter()를 호출해 요청을 제출하거나 완료 이벤트를 기다립니다.
완료 처리는 CQE를 읽는 방식입니다. io_uring_enter() 문서는 한 번의 호출이 새 I/O 제출과 완료 대기를 함께 수행할 수 있다고 설명합니다. 이 점 때문에 이벤트 루프에서는 여러 요청을 batch로 넣고, 필요한 수의 완료를 기다린 뒤, CQE의 결과를 보고 다음 작업을 이어가는 구조를 만들 수 있습니다.

setup ring
prepare SQE
submit SQE
wait or reap CQE
check CQE result
실제 애플리케이션에서는 시스템 호출을 직접 감싸기보다 liburing을 쓰는 경우가 많습니다. io_uring_queue_init()은 내부적으로 io_uring_setup()을 실행해 SQ와 CQ를 준비하고, io_uring_submit() 같은 helper는 ring 상태에 따라 필요한 io_uring_enter() 호출을 처리합니다. 다만 라이브러리를 쓰더라도 SQE를 만들고 CQE를 확인한다는 모델은 그대로입니다.
SQPOLL은 언제 조심해야 하나
IORING_SETUP_SQPOLL은 submission queue polling 모드입니다. 이 모드에서는 커널 thread가 submission queue를 poll해서, 특정 조건에서는 애플리케이션이 매번 io_uring_enter()로 제출하지 않아도 요청을 처리할 수 있습니다. 이름만 보면 자동 성능 개선 옵션처럼 보이지만, 문서는 workload별 평가가 필요하다고 설명합니다.
운영 기준은 보수적으로 잡는 편이 좋습니다. SQPOLL은 thread와 CPU 사용 방식, 커널 버전별 권한 조건, file 등록 조건의 변화가 얽혀 있습니다. 일반 애플리케이션에서는 먼저 기본 interrupt-driven 모드로 구조를 단순하게 만들고, syscall 제출 비용이 실제 병목으로 확인될 때만 SQPOLL을 별도로 실험하는 편이 안전합니다.
| 상황 | 우선 선택 | 이유 |
|---|---|---|
| 일반적인 비동기 I/O 구조 도입 | 기본 ring | 동작 조건이 단순하고 디버깅하기 쉬움 |
| 짧은 요청을 매우 자주 제출 | SQPOLL 실험 가능 | 제출 syscall 비용을 줄일 여지가 있음 |
| container, cgroup, 제한된 권한 환경 | 커널·권한 조건 먼저 확인 | SQPOLL thread와 CPU affinity 조건이 운영 환경에 영향을 받을 수 있음 |
등록 기능은 왜 있나
io_uring_register()는 파일, 사용자 버퍼, eventfd, personality, restriction 같은 자원을 ring에 등록하는 시스템 호출입니다. 문서 기준 파일이나 버퍼 등록은 커널이 내부 자료구조나 애플리케이션 메모리에 대한 장기 참조를 잡아 per-I/O overhead를 줄이기 위한 기능입니다.
다만 등록은 공짜가 아닙니다. 예를 들어 buffer 등록은 메모리를 lock하고 사용자의 RLIMIT_MEMLOCK 제한과 관련됩니다. 파일 등록도 고정된 file table을 설계에 포함해야 합니다. 따라서 "빠르다"는 이유만으로 모든 것을 등록하기보다, 반복적으로 같은 파일이나 버퍼를 쓰는 hot path인지 먼저 봐야 합니다.

epoll과 어떻게 나누어 보나
epoll은 준비 상태 readiness를 알려주는 이벤트 통지 모델입니다. 파일 디스크립터가 읽거나 쓸 준비가 되었는지 보고, 실제 read/write는 애플리케이션이 다시 수행합니다. io_uring은 작업 자체를 SQE로 제출하고 완료 결과를 CQE로 받는 completion 모델입니다.
네트워크 서버처럼 이미 epoll 기반 구조가 안정적이고 I/O 처리량이 충분하다면 io_uring 전환이 항상 이득이라고 볼 수 없습니다. 반대로 파일 I/O, timeout, socket 작업을 같은 completion queue로 묶고 싶거나 batch submit, registered buffer, fixed file 같은 기능을 활용할 수 있다면 io_uring이 더 직접적인 설계가 될 수 있습니다.
| 기준 | epoll |
io_uring |
|---|---|---|
| 모델 | 준비 상태 알림 | 작업 제출과 완료 통지 |
| 강점 | 전통적인 네트워크 이벤트 루프에 널리 사용 | 여러 I/O 작업을 queue 기반으로 batch 처리 가능 |
| 주의 | 실제 I/O 호출은 별도로 수행해야 함 | 커널 기능, opcode 지원, 운영 권한을 확인해야 함 |
실무 선택 기준
첫째, completion 모델이 코드 구조에 맞는지 봅니다. 단순히 "비동기"라는 이유만으로 바꾸면 오히려 SQE/CQE 관리, 오류 처리, cancellation, resource lifetime이 더 복잡해질 수 있습니다.
둘째, 배포 대상 커널과 라이브러리 경로를 확인합니다. io_uring 자체는 리눅스 전용이고, 세부 opcode와 flag는 커널 버전에 따라 달라질 수 있습니다. 서비스가 여러 배포판과 커널 버전에 걸쳐 배포된다면 기능 탐지와 fallback 경로가 필요합니다.
셋째, 병목을 측정한 뒤 최적화 옵션을 켭니다. SQPOLL, registered buffers, fixed files, registered ring fd 같은 기능은 특정 workload에서 overhead를 줄일 수 있지만, 운영 조건과 resource limit도 같이 봐야 합니다. 기본 ring으로 충분한지 먼저 확인하고, 병목이 있는 지점만 좁혀 실험하는 순서가 더 안정적입니다.
자주 묻는 질문
io_uring을 쓰면 모든 I/O가 자동으로 빨라지나?
아닙니다. queue 공유, batch 제출, 등록 자원 같은 구조가 이점이 될 수 있지만, 실제 효과는 workload와 커널, 저장장치, 네트워크 패턴, 라이브러리 사용 방식에 따라 달라집니다. 문서도 SQPOLL 같은 옵션은 case-by-case 평가가 필요하다고 설명합니다.
io_uring은 리눅스 밖에서도 쓸 수 있나?
아닙니다. man-pages는 io_uring을 Linux-specific API로 설명합니다. 이식성이 중요한 라이브러리라면 io_uring 경로와 다른 운영체제용 event loop 경로를 분리해야 합니다.
liburing을 쓰면 커널 조건을 몰라도 되나?
그렇지 않습니다. liburing은 사용을 편하게 해 주지만 커널이 제공하는 기능 범위를 없애지는 않습니다. 특정 flag, opcode, 등록 기능을 쓰는 코드는 대상 커널의 feature와 오류 처리를 확인해야 합니다.
정리하면 io_uring은 기존 I/O 호출을 단순히 다른 이름으로 바꾸는 API가 아니라, 요청 제출과 완료 처리를 queue 중심으로 재구성하는 리눅스 인터페이스입니다. 반복 I/O가 많고 completion 모델이 코드 구조에 맞으며 배포 커널 조건을 통제할 수 있다면 검토할 가치가 있습니다. 그렇지 않다면 기존 epoll이나 동기 I/O 구조가 더 단순한 선택일 수 있습니다.
'프로그래밍 > 리눅스 커널' 카테고리의 다른 글
| 리눅스 Device Tree clocks 기준 (0) | 2026.09.20 |
|---|---|
| 리눅스 pidfd 기준 (0) | 2026.09.19 |
| 리눅스 PSI 기준 (0) | 2026.09.16 |
| 리눅스 모듈 파라미터 기준 (0) | 2026.09.14 |
| 리눅스 Device Tree compatible 기준 (0) | 2026.09.14 |





