NAME
eventfd - 이벤트 알림을 위한 파일 디스크립터 만들기
SYNOPSIS
#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);
DESCRIPTION
eventfd()
는 "eventfd 객체"를 생성한다. 이를 사용자 공간 응용에서 이벤트 대기/알림 메커니즘으로 사용하거나 커널에서 사용자 공간 응용으로 이벤트를 알리는 데 쓸 수 있다. 그 객체에는 커널에서 관리하는 부호 없는 64비트 정수 (uint64_t
) 카운터가 있다. 인자 initval
에 지정한 값으로 그 카운터가 초기화된다.
반환 값으로 eventfd()
는 새 파일 디스크립터를 반환하며 이를 이용해 eventfd 객체를 참조할 수 있다.
flags
에 다음 값들을 비트 OR 해서 eventfd()
의 동작 방식을 바꿀 수 있다.
EFD_CLOEXEC
(리눅스 2.6.27부터)- 새 파일 디스크립터에 exec에서 닫기(
FD_CLOEXEC
) 플래그를 설정한다. 이게 유용할 수 있는 이유에 대해선 open(2)의O_CLOEXEC
플래그 설명을 보라. EFD_NONBLOCK
(리눅스 2.6.27부터)- 새 파일 디스크립터가 가리키는 열린 파일 기술 항목(open(2) 참고)에
O_NONBLOCK
파일 상태 플래그를 설정한다. 이 플래그를 쓰면 같은 결과를 위해 fcntl(2)을 따로 호출할 필요가 없다. EFD_SEMAPHORE
(리눅스 2.6.30부터)- 새 파일 디스크립터에서 세마포어 같은 읽기 동작 방식을 제공한다. 아래 참고.
리눅스 버전 2.6.26까지는 flags
인자를 사용하지 않으며 0으로 지정해야 한다.
eventfd()
가 반환한 파일 디스크립터에 다음 작업을 수행할 수 있다.
- read(2)
-
read(2)가 성공할 때마다 8바이트 정수를 반환한다. 제공 버퍼의 크기가 8바이트보다 작으면 read(2)가
EINVAL
오류로 실패한다.read(2)가 반환하는 값은 호스트 바이트 순서, 즉 호스트 머신의 기본 정수 바이트 순서로 돼 있다.
read(2)의 동작 방식은 eventfd 카운터가 현재 0 아닌 값을 가지고 있는지 여부와 eventfd 파일 디스크립터 생성 시
EFD_SEMAPHORE
플래그를 지정했는지 여부에 따라 달라진다.-
EFD_SEMAPHORE
를 지정하지 않았고 eventfd 카운터의 값이 0이 아니면 read(2)가 그 값을 담은 8바이트를 반환하며 카운터 값이 0으로 재설정된다. -
EFD_SEMAPHORE
를 지정했으며 eventfd 카운터의 값이 0이 아니면 read(2)가 값 1을 담은 8바이트를 반환하며 카운터 값이 1만큼 줄어든다. -
read(2) 호출 시점에 eventfd 카운터가 0이면 카운터가 0이 아니게 될 때까지 블록한다. (그 다음에 read(2)가 위 설명처럼 진행한다.) 파일 디스크립터를 논블록으로 만들었다면
EAGAIN
오류로 실패한다.
-
- write(2)
-
write(2) 호출은 버퍼에 준 8바이트 정수 값을 카운터에 더한다. 카운터에 저장할 수 있는 최댓값은 가장 큰 부호 없는 64비트 정수에서 1을 뺀 것(즉 0xfffffffffffffffe)이다. 더한 결과가 그 최댓값을 초과하게 될 것 같으면 그 파일 디스크립터에 read(2)가 이뤄질 때까지 write(2)가 블록한다. 파일 디스크립터를 논블록으로 만들었다면
EAGAIN
오류로 실패한다.제공 버퍼의 크기가 8바이트보다 작거나 값 0xffffffffffffffff를 쓰려고 시도하면 write(2)가
EINVAL
오류로 실패한다. - poll(2), select(2) (기타 비슷한 함수)
-
반환되는 파일 디스크립터가 다음과 같이 poll(2)과 (마찬가지로 epoll(7)과) select(2)를 지원한다.
-
카운터 값이 0보다 크면 파일 디스크립터가 읽기 가능하다. (select(2)의
readfds
인자, poll(2)의POLLIN
플래그) -
블록되지 않고 적어도 "1" 값을 기록할 수 있으면 파일 디스크립터가 쓰기 가능하다. (select(2)의
writefds
인자, poll(2)의POLLOUT
플래그) -
카운터 값 넘침을 감지한 경우 select(2)는 파일 디스크립터가 읽기 가능하면서 동시에 쓰기 가능하다고 표시하며 poll(2)은
POLLERR
이벤트를 반환한다. 위에서 언급한 것처럼 write(2)가 절대 카운터를 넘치게 할 수 없다. 하지만 KAIO 서브시스템에서 eventfd "알림 발송"을 2^64번 수행한다면 넘침이 발생할 수 있다. (이론적으로 가능한 것이고 실제로는 가능성이 낮다.) 넘침이 발생했으면 read(2)가 가장 큰uint64_t
값(즉 0xffffffffffffffff)을 반환하게 된다.
eventfd 파일 디스크립터는 pselect(2)와 ppoll(2) 같은 다른 파일 디스크립터 다중화 API도 지원한다.
-
- close(2)
- 파일 디스크립터가 더는 필요하지 않으면 닫아야 한다. 같은 eventfd 객체에 연계된 모든 파일 디스크립터가 닫혔을 때 커널이 그 객체의 자원을 해제한다.
fork(2)로 생성된 자식이 eventfd()
로 만든 파일 디스크립터의 사본을 물려받는다. 복제된 파일 디스크립터는 같은 eventfd 객체에 연계돼 있다. exec에서 닫기 플래그를 설정하지 않았으면 execve(2)를 거치면서 eventfd()
로 만든 파일 디스크립터가 유지된다.
RETURN VALUE
성공 시 eventfd()
는 새 eventfd 파일 디스크립터를 반환한다. 오류 시 -1을 반환하며 오류를 나타내도록 errno
를 설정한다.
ERRORS
EINVAL
- 지원하지 않는 값을
flags
에 지정했다. EMFILE
- 열린 파일 디스크립터 개수에 대한 프로세스별 제한에 도달했다.
ENFILE
- 열린 파일 총개수에 대한 시스템 전역 제한에 도달했다.
ENODEV
- (내부적으로 쓰는) 익명 아이노드 장치를 마운트할 수 없었다.
ENOMEM
- 새 eventfd 파일 디스크립터를 생성하기에 메모리가 충분하지 않았다.
VERSIONS
리눅스 커널 2.6.22부터 eventfd()
를 이용할 수 있다. glibc 버전 2.8부터 잘 동작하는 지원을 제공한다. 리눅스 커널 2.6.27부터 eventfd2()
시스템 호출(NOTES 참고)을 이용할 수 있다. glibc 버전 2.9부터 커널에서 지원 시 eventfd()
래퍼에서 eventfd2()
시스템 호출을 이용한다.
ATTRIBUTES
이 절에서 사용하는 용어들에 대한 설명은 attributes(7)를 보라.
인터페이스 | 속성 | 값 |
---|---|---|
eventfd() |
스레드 안전성 | MT-Safe |
CONFORMING TO
eventfd()
와 eventfd2()
는 리눅스 전용이다.
NOTES
응용에서 파이프(pipe(2) 참고)를 이벤트 전달 용도로만 쓰는 모든 경우에서 파이프 대신 eventfd 파일 디스크립터를 쓸 수 있다. eventfd의 커널 오버헤드가 파이프보다 훨씬 낮으며 파일 디스크립터가 한 개만 있으면 된다. (파이프에선 두 개가 필요하다.)
커널에서 사용 시 eventfd 파일 디스크립터는 커널에서 사용자 공간으로의 가교가 될 수 있다. 그래서 예를 들어 KAIO(커널 AIO) 같은 기능에서 어떤 동작이 완료되었을 때 파일 디스크립터로 알릴 수 있다.
eventfd 파일 디스크립터의 핵심은 여느 파일 디스크립터들처럼 select(2)나 poll(2), epoll(7)을 이용해 상태를 확인할 수 있다는 점이다. 그래서 응용에서 "전통적" 파일의 준비 상태와 eventfd 인터페이스를 지원하는 커널 메커니즘의 준비 상태를 동시에 확인할 수 있다. (eventfd()
인터페이스가 없다면 그 메커니즘을 select(2)나 poll(2), epoll(7)을 통해 다중화할 수 없었을 것이다.)
프로세스의 /proc/[pid]/fdinfo
디렉터리의 대응하는 파일 디스크립터 항목을 통해 eventfd 카운터의 현재 값을 볼 수 있다. 자세한 내용은 proc(5) 참고.
C 라이브러리/커널 차이
기반 시스템 호출이 두 가지 있다. eventfd()
와 더 최신인 eventfd2()
이다. 앞쪽 시스템 호출에는 flags
인자가 구현돼 있지 않다. 뒤쪽 시스템 호출에는 위에 기술한 flags
값들이 구현돼 있다. glibc 래퍼 함수에서는 가능한 경우 eventfd2()
를 이용한다.
glibc 추가 기능
GNU C 라이브러리에서 새로운 타입 하나와 함수 두 개를 정의하고 있는데, 이는 eventfd 파일 디스크립터 읽기와 쓰기의 세부 사항을 좀 추상화해 보려는 것이다.
typedef uint64_t eventfd_t;
int eventfd_read(int fd, eventfd_t *value);
int eventfd_write(int fd, eventfd_t value);
이 함수들은 eventfd 파일 디스크립터에 읽기 및 쓰기 동작을 수행하며, 정확한 수의 바이트를 전송했으면 0을 반환하고 아니면 -1을 반환한다.
EXAMPLES
아래 프로그램은 eventfd 파일 디스크립터를 만든 다음 분기해서 자식 프로세스를 만든다. 부모가 잠시 잠드는 동안 자식이 프로그램 명령행 인자로 받은 정수들 각각을 eventfd 파일 디스크립터에 써넣는다. 그리고 잠에서 깬 부모가 eventfd 파일 디스크립터에서 읽기를 한다.
다음 셸 세션은 프로그램 실행 예를 보여 준다.
$ ./a.out 1 2 4 7 14
Child writing 1 to efd
Child writing 2 to efd
Child writing 4 to efd
Child writing 7 to efd
Child writing 14 to efd
Child completed write loop
Parent about to read
Parent read 28 (0x1c) from efd
프로그램 소스
#include <sys/eventfd.h>
#include <unistd.h>
#include <inttypes.h> /* PRIu64 및 PRIx64 정의 */
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h> /* uint64_t 정의 */
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int
main(int argc, char *argv[])
{
int efd;
uint64_t u;
ssize_t s;
if (argc < 2) {
fprintf(stderr, "Usage: %s <num>...\n", argv[0]);
exit(EXIT_FAILURE);
}
efd = eventfd(0, 0);
if (efd == -1)
handle_error("eventfd");
switch (fork()) {
case 0:
for (int j = 1; j < argc; j++) {
printf("Child writing %s to efd\n", argv[j]);
u = strtoull(argv[j], NULL, 0);
/* strtoull()에 다양한 기수 가능 */
s = write(efd, &u, sizeof(uint64_t));
if (s != sizeof(uint64_t))
handle_error("write");
}
printf("Child completed write loop\n");
exit(EXIT_SUCCESS);
default:
sleep(2);
printf("Parent about to read\n");
s = read(efd, &u, sizeof(uint64_t));
if (s != sizeof(uint64_t))
handle_error("read");
printf("Parent read %"PRIu64" (%#"PRIx64") from efd\n", u, u);
exit(EXIT_SUCCESS);
case -1:
handle_error("fork");
}
}
SEE ALSO
futex(2), pipe(2), poll(2), read(2), select(2), signalfd(2), timerfd_create(2), write(2), epoll(7), sem_overview(7)
2021-03-22