13 분 소요


Cindy Sridharan 의 2017년 글인 The method to epoll’s madness 를 의역해서 정리한 페이지입니다.

epoll 은 이벤트 폴 을 나타내며 Linux 전용으로, 프로세스가 여러 파일 디스크립터를 모니터하고
I/O가 가능한 경우 알림을 받을 수 있습니다.
엣지 트리거 및 레벨 트리거 알림이 모두 가능합니다. epoll 의 내부를 살펴보기 전에 먼저 문법을 살펴 보겠습니다.




epoll 문법

poll 과 달리 epoll 자체는 시스템 콜이 아닙니다.
프로세스가 여러 파일 디스크립터들에서 I/O를 다중화(Multiplex)할 수 있는 커널 자료 구조입니다.


그림 1. Epoll 인스턴스.


이 자료 구조는 세 개의 시스템 호출로 생성, 수정, 삭제될 수 있습니다.


epoll_create

epoll_create 시스템 호출은 epoll 인스턴스를 생성하고 epoll 파일 디스크립터를 리턴합니다.
epoll_create 의 시그니쳐는 다음과 같습니다:

#include <sys/epoll.h>
int epoll_create(int size);


size 인자는 프로세스가 모니터링하려는 파일 디스크립터 수를 커널에 알려주어
커널이 epoll 인스턴스의 크기를 결정하는 데 도움이 됩니다.
Linux 2.6.8부터는 파일 디스크립터가 추가되거나 제거될 때
epoll 데이터 구조의 크기가 동적으로 조정되므로 이 인수는 무시됩니다.


epoll_create 시스템 호출은 새로 생성된 epoll 커널 데이터 구조의 파일 디스크립터를 리턴합니다.
호출한 프로세스는 epoll 디스크립터를 사용하여 epoll 인스턴스가 I/O를 모니터링하려는
다른 파일 디스크립터를 추가, 삭제 또는 수정할 수 있습니다.


그림 2. Epoll Create.


다음 정의와 같은 또 다른 시스템 콜 epoll_create1 이 있습니다.

int epoll_create1(int flags);


flags 인자는 0 또는 EPOLL_CLOEXEC 일 수 있습니다.
0으로 설정하면 epoll_create1epoll_create 와 동일한 방식으로 동작합니다.

EPOLL_CLOEXEC 플래그가 설정되면 현재 프로세스에서 분기(fork)된 하위 프로세스가
execs 되기 전에 epoll 디스크립터가 닫히므로 하위 프로세스는 더 이상 epoll 인스턴스에 접근할 수 없습니다.


epoll 인스턴스의 파일 디스크립터는 close() 시스템 호출로 해제해야 합니다.

예를 들어 EPOLL_CLOEXEC 플래그가 없는 분기는 하위 프로세스의 epoll 인스턴스에 디스크립터를 복제하므로
여러 프로세스가 동일한 epoll 인스턴스 디스크립터를 가질 수 있습니다.
이 모든 프로세스가 close() 를 호출하여 epoll 인스턴스를 종료하면, 커널은 epoll 인스턴스를 삭제합니다.


epoll_ctl

프로세스는 epoll_ctl 을 호출하여 모니터링할 파일 디스크립터를 epoll 인스턴스에 추가할 수 있습니다.
epoll 인스턴스에 등록 된 모든 파일 디스크립터를 epoll 세트(set) 또는 관심 목록(interest list) 이라고 합니다.


그림 3. Epoll Control #0.


위 그림에서, 프로세스 483은 파일 디스크립터 fd1, fd2, fd3, fd4fd5 를 epoll 인스턴스에 등록했습니다.
이것이 특정 epoll 인스턴스의 epoll 세트 또는 관심 목록 입니다.
등록된 파일 디스크립터 중 하나라도 I/O 준비가 되면 준비 목록(ready list) 에 있는 것으로 간주됩니다.

준비 목록관심 목록 의 하위 집합입니다.


그림 4. Epoll Control #1.


epoll_ctl 의 시그니쳐는 다음과 같습니다:

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);


그림 5. Epoll Control #2.


epfdepoll_create 가 반환한 epoll 인스턴스 파일 디스크립터입니다.
fdepoll 세트 / 관심 목록 에 추가하려는 파일 디스크립터입니다.
op는 파일 디스크립터 fd 에서 수행할 작업을 나타냅니다. 일반적으로 다음 세 가지 작업이 지원됩니다.

  • ADD - fd 를 epoll 인스턴스에 등록(EPOLL_CTL_ADD)하고 발생하는 이벤트에 대한 알림을 받습니다.
  • DEL - fd 를 epoll 인스턴스에서 삭제/해제(EPOLL_CTL_DEL)합니다.
    삭제/해제 후에는 프로세스가 더 이상 해당 파일 디스크립터의 이벤트 알림을 받지 않습니다.
    파일 디스크립터가 여러 epoll 인스턴스에 추가된 경우 이를 닫으면 추가된 모든 epoll 관심 목록에서
    해당 디스크립터가 삭제/해제됩니다.
  • MOD - fd 가 모니터링 하는 이벤트를 수정(EPOLL_CTL_MOD)


그림 6. Epoll Control #3.


event 는 실제로 fd 를 모니터링하려는 이벤트를 저장하는 epoll_event 라는 구조체의 포인터입니다.

그림 7. Epoll Control #4.


typedef union epoll_data {
    void    *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;    /* Epoll events */
    epoll_data_t data;      /* User data variable */
};

epoll_event 구조체의 첫 번째 필드 eventsfd 가 모니터링되는 이벤트를 나타내는 비트 마스크입니다.


fd 가 소켓이면 소켓 버퍼에 새 데이터가 도착했는지(EPOLLIN) 모니터 할 수 있습니다.
EPOLLIN 을 사용하여 EPOLLET 를 OR 링하여 수행되는 Edge 트리거 알림에 대해 fd를 모니터링 할 수도 있습니다.
EPOLLETEPOLLIN 를 OR-ing 하여 fd 의 Edge 트리거 알림을 모니터링할 수도 있습니다.
등록된 이벤트의 발생에 대해서는 fd 를 모니터하지만 한 번만 fd를 모니터하고 해당 이벤트의 다음 발생에 대해서는
fd 모니터링을 중지할 수도 있습니다. 이것은 알림 전달자 EPOLLONESHOT 플래그를 다른 플래그 (EPOLLET, EPOLLIN)와
OR-ing 하면 됩니다.
사용할 수 있는 모든 플래그는 매뉴얼 페이지에서 찾을 수 있습니다.


epoll_event 구조체의 두 번째 필드는 공용체(union) 필드입니다.


epoll_wait

epoll_wait 시스템 호출을 호출하여 epoll 인스턴스의 epoll Set / 관심 세트 에서 발생한 이벤트를
스레드에 통지할 수 있습니다. epoll_wait 시스템 콜 호출은 모니터중인 디스크립터가 I/O 준비가 될 때까지 블록(block)됩니다.


epoll_wait 의 시그니쳐는 다음과 같습니다:

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);


epfdepoll_create 에서 반환한 epoll 인스턴스 디스크립터입니다.
evlistepoll_event 구조체의 배열입니다.
evlist 는 호출하는 프로세스에 의해 할당되며, epoll_wait 가 준비 상태에 있는 관심 목록(준비 목록 이라고도 함)의
배열을 리턴합니다.
maxeventsevlist 배열의 최대 길이입니다.
timeout — 이 인자는 poll 또는 select 와 동일한 방식으로 작동합니다. 이 값은 epoll_wait 시스템 콜이 블록(block)되는 기간을 지정합니다:

timeout 이 0으로 설정되면, epoll_wait 는 블록(block)되지 않지만 epfd 에 대한 관심 목록 에서
어떤 파일 디스크립터가 준비되었는지 확인한 후 즉시 리턴합니다.
timeout 이 1로 설정되면, epoll_wait 는“영원히” 블록(block)됩니다.
epoll_wait 가 블록(block)되면 커널이 리턴될 때까지 프로세스를 휴면 상태가 됩니다.
epoll_wait 는 1) epfd 에 대한 관심 목록에 지정된 하나 이상의 디스크립터가 준비될 때 또는
2) 시그널 핸들러에 의해 호출이 interrupt될 때까지 블록(block)됩니다.
timeout 이 음이 아니고 0이 아닌 값으로 설정되면, epoll_wait
1) epfd 에 대한 관심 목록 에 지정된 하나 이상의 디스트립터가 준비될 때 또는
2) 시그널 핸들러 의해 호출이 interrupt될 때
3) 타임아웃으로 지정된 시간(milliseconds)으로 지정된 시간이 만료될 때까지 블록(block)됩니다.


epoll_wait 의 리턴 값은 다음과 같습니다:
— 에러(EBADF, EINTR, EFAULT 또는 EINVAL)가 발생하면, -1을 리턴합니다.
관심 목록 의 파일 디스크립터가 준비되기 전에 호출 시간이 초과되면 0을 리턴합니다.
관심 목록 에 있는 하나 이상의 파일 디스크립터가 준비되면 리턴 코드는 양의 정수이며
evlist 배열의 총 파일 디스크립터 수를 나타냅니다.
그 다음에는 evlist 를 검사하여 어떤 파일 디스크립터에서 어떤 이벤트가 발생했는지 판별할 수 있습니다.




Epoll 들여다보기(The gotchas of epoll)

epoll 을 완전히 이해하려면 파일 디스크립터가 실제로 어떻게 작동하는지 이해하는 것이 중요합니다.
이것은 이전 게시물(Nonblocking I/O)에서 살펴 보았지만 다시 복습하겠습니다.


프로세스는 디스크립터를 사용하여 I/O 스트림을 참조합니다.
모든 프로세스는 접근할 수 있는 파일 디스크립터 테이블을 유지합니다. 이 테이블의 모든 항목에는 두 개의 필드가 있습니다:
— 파일 디스크립터의 동작을 제어하는 플래그(유일한 플래그는 close on exec 입니다)
— 잠깐 살펴볼 내부 커널 데이터 구조에 대한 포인터


디스크립터는 open, pipe, socket 등과 같은 시스템 호출에 의해 명시적으로 생성되거나
fork 동안 상위 프로세스로부터 상속 됩니다.
디스크립터는 dup / dup2 시스템 호출로 “복제”됩니다.


그림 8. Epoll Gotchas #0.


다음의 경우 디스크립터가 해제됩니다:
— 프로세스 종료(exit)
close 시스템 콜을 호출
— 프로세스가 fork될 때, 모든 디스크립터들은 하위 프로세서에 “복제”됩니다. 디스크립터가 close-on-exec 로 표시되면
상위 프로세서 fork 후 하위 프로세서 실행 전에, 하위 프로세스에서 close-on-exec 로 표시된 디스크립터는 close 되고
더 이상 사용할 수 없습니다.
상위 프로세스는 여전히 디스크립터를 계속 사용할 수 있지만, 하위 프로세스는 exec-된 후에는 디스크립터를 사용할 수 없습니다.


위의 예제에서 프로세스 A에 close-on-exec 플래그로 표시된 디스크립터 3(fd3)이 있다고 가정합시다.
프로세스 A가 프로세스 B를 fork 하면, fork 직후, 프로세스 A와 프로세스 B는 동일하며
프로세스 B는 파일 디스크립터 f0, f1, f2f3 에 접근할 수 있습니다.
그러나 디스크립터 f3close-on-exec 플래그로 설정되어 프로세스 B 실행 전에
이 디스크립터는 “비활성”으로 표시되며 프로세스 B는 더 이상 접근할 수 없습니다.


그림 9. Epoll Gotchas #1.


디스크립터란 실제로 프로세스 별로 파일 디스크립션(description, 설명) 이라고 하는
커널 내부 데이터 구조의 포인터입니다.
커널은 모든 open된 파일 디스크립션 의 테이블을 open 파일 테이블 에 유지합니다.


그림 10. Epoll Gotchas #2.


디스크립터 fd0 에 대한 dup 또는 fcntl 시스템 콜로 프로세스 A의 fd3 이 작성되었다고 가정합시다.
원래 디스크립터 fd0 과 “복제”된 디스크립터 fd3 은 모두 커널에서 동일한 파일 디스크립션을 가리킵니다.


프로세스 A가 프로세스 B를 fork 하고 fd3close-on-exec 플래그가 표시되면
하위 프로세스 B는 모든 상위 프로세스 A의 설명자를 상속받지만 fd3 은 사용할 수 없습니다.


하위 프로세스 B의 fd0 는 커널의 open 파일 테이블 에서 동일한 open 파일 디스크립션 을 가리킵니다.


그림 11. Epoll Gotchas #3.


프로세스 A에는 fd0fd3, 프로세스 B에는 fd0 이라는 세 개의 디스크립터가 있으며
모두 동일한 커널 내부의 open 파일 디스크립션 을 가리킵니다.
프로세스 A와 B의 다른 모든 파일 디스크립터도 open 파일 테이블의 항목을 가리키지만 그림에서는 생략되었습니다.

Note:
파일 디스크립션 은 한 프로세스가 다른 프로세스를 공유 할 때 두 프로세스에서만 공유되지 않습니다.
한 프로세스가 Unix 도메인 소켓 소켓을 통해 파일 디스크립터를 다른 프로세스로 전달한 경우
두 프로세스의 디스크립터는 동일한 커널 내부의 open 파일 디스크립션 을 가리킵니다.


마지막으로 파일 디스크립션inode 포인터 필드 를 설명하기 전에 inode 가 무엇인지 이해하는 것이 중요합니다.
inode 는 파일 또는 디렉토리와 같은 파일 시스템 객체에 대한 정보를 포함하는 파일 시스템 데이터 구조입니다.
이 정보에는 다음 내용이 포함됩니다:

— 파일 또는 디렉토리 데이터가 저장된 디스크 상의 블록 위치
— 파일 또는 디렉토리의 속성
— 파일 또는 디렉토리에 대한 추가 메타 데이터(예: 액세스 시간, 소유자, 권한 등).


파일 시스템의 모든 파일 (및 디렉토리)에는 파일을 나타내는 숫자인 inode 엔트리(entry) 가 있습니다.
이 숫자는 inode 번호 라고도 합니다.
많은 파일 시스템에서 최대 inode 수는 특정 값으로 제한됩니다. 즉, 시스템에 저장할 수 있는 총 파일 수가 제한됩니다.


디스크에는 inode 엔트리 를 실제 디스크의 inode data 구조체로 매핑하는 inode 테이블 엔트리 가 있습니다.
대부분의 파일시스템은 커널의 파일 시스템 드라이버를 통해 접근됩니다.
이 드라이버는 inode 엔트리 를 사용하여 inode에 저장된 정보에 접근합니다.
따라서 파일의 위치 또는 파일과 관련된 메타 데이터를 알기 위해서는 커널의 파일 시스템 드라이버가 inode 테이블에 접근해야 합니다.


프로세스 A가 프로세스 B를 fork 한 후 프로세스 A가 두 개의 파일 디스크립터 fd4fd5 를 생성했다고 가정합시다.
이들은 B 프로세스에서 복제되지 않습니다.


fd5 프로세스 A에서 abc.txt 파일을 읽기 위해 open 하여 생성되었다고 가정합시다.
프로세스 B가 abc.txt 파일을 쓰기위해 open 으로 호출하여 fd10 파일 디스크립터를 생성한다고 가정합시다.


그런 다음 프로세스 A의 fd5 와 프로세스 B의 fd10open 파일 테이블 에서 다른 open 파일 디스크립션을 가리키지만
동일한 inode 테이블 항목(즉, 동일한 파일)을 가리킵니다.


그림 12. Epoll Gotchas #4.


여기에는 두 가지 중요한 의미가 있습니다.

— 프로세스 A와 프로세스 B 모두에서 fd0 은 같은 open 파일 디스크립션 을 참조하므로 파일 오프셋을 공유합니다.
즉, 프로세스 A가 read() 또는 write() 또는 lseek() 를 호출하여 파일 오프셋을 이동하면
프로세스 B에 대한 오프셋도 수정됩니다. fd3fd0 과 동일한 open 파일 디스크립션 을 참조하므로
이는 프로세스 A에 속하는 fd3 에도 적용됩니다.

— 이것은 한 프로세스에서 파일 디스크립터가 열린 파일 상태 플래그(O_ASYNC, O_NONBLOCK, O_APPEND)로
수정한 경우에도 적용됩니다.
따라서 프로세스 B가 fcntlsystem 콜을 통해 O_NONBLOCK flag를 설정하여 fd0 을 논블로킹(non-blocking) 모드로 설정하면
프로세스 A에 속하는 설명자 fd0fd3 도 논블로킹(non-blocking) 동작으로 관찰하게 됩니다.




Epoll 의 내부(The bowels of epoll)

프로세스 A의 두 개의 open 파일 디스크립터 fd0fd1 이 있고, open 파일 테이블
두 개의 open 파일 디스크립션 이 있다고 가정합시다.
파일 디스크립션 이 서로 다른 inode 를 가리킨다고 가정합니다.


그림 13. Epoll Bowels #0.


epoll_create 는 새로운 inode 엔트리 (epoll 인스턴스)와 커널에 open 파일 디스크립션 을 생성하고,
호출한 프로세스에 파일 디스크립터(fd9)를 리턴합니다.


그림 14. Epoll Bowels #1.


epoll_ctl 을 사용하여 파일 디스크립터(fd0)를 epoll 인스턴스의 관심 목록 에 추가하면,
실제로는 fd0 의 내부 파일 디스크립션을 epoll 인스턴스의 관심 목록 에 추가합니다.


그림 15. Epoll Bowels #2.


따라서 epoll 인스턴스는 실제로 프로세스 별 파일 디스크립터가 아닌 내부의 파일 디스크립션을 모니터합니다.
여기에는 몇 가지 흥미로운 의미가 있습니다.


— 프로세스 A가 하위 프로세스 B를 fork 하면 B는 epoll 디스크립터 fd9 를 포함하여 A의 모든 디스크립터를 상속합니다.
그러나 프로세스 B의 디스크립터 fd0, fd1fd9 는 여전히 동일한 내부 커널 데이터 구조를 나타냅니다.
프로세스 B의 epoll 디스크립터(fd9)는 프로세스 A와 동일한 관심 목록 을 공유합니다.


fork 후 프로세스 A가 epoll_ctl 을 통해 epoll 관심 리스트 에 새로운 디스크립터 fd8 (프로세스 B에서 복제되지 않은)을
작성하는 경우, epoll_wait() 를 호출할 때 fd8 의 이벤트에 대한 알림을 받는 것은 프로세스 A만이 아닙니다.


프로세스 B가 epoll_wait() 를 호출하면 프로세스 B는 fd8 (프로세스 A에 속하고 fork 중에 복제되지 않은)에 대한
알림도 받습니다.
이는 dup / dup2 에 대한 호출 또는 epoll 파일 디스크립터가 Unix 도메인 소켓을 통해
다른 프로세스로 전달되는 경우 epoll 파일 디스크립터가 중복된 경우에도 적용 가능합니다.


그림 16. Epoll Bowels #3.


프로세스 B가 새로운 open 호출로 fd8이 가리키는 파일을 열고 그 결과로 새로운 파일 디스크립터(fd15)를
얻는다고 가정합니다.
이제 프로세스 A가 fd8close 한다고 가정하겠습니다. 프로세스 A가 fd8을 닫았으므로 더 이상 epoll_wait
호출할 때 fd8 의 이벤트에 대한 알림을 받지 못한다고 가정합니다.
그러나 관심 목록open 파일 디스크립션 을 모니터링하므로 그렇지 않습니다.
fd15fd8 과 같은 디스크립션을 가리키므로(둘 다 동일한 파일이므로) 프로세스 A는 fd15 의 이벤트에 대한
알림을 받습니다.
일단 파일 디스크립터가 epoll 인스턴스를 가진 프로세스에 의해 등록되면, 프로세스는 내부의 열린 파일 디스크립션이
여전히 다른 디스크립터(같거나 다른 프로세스에 속한)에 의해. 참조되는 한 디스크립터를 close 해도
디스크립터의 이벤트에 대한 알림을 계속받는다고 생각하는 것이 안전합니다.




epoll이 select 및 poll보다 성능이 뛰어난 이유

select/poll 의 비용은 O(N) 입니다.
이는 N이 매우 클 때(수만 명의 졸린 클라이언트를 처리하는 웹 서버를 생각할 때) select/poll 이 호출될 때마다,
실제로 발생한 이벤트 수가 적은 경우에도 커널은 목록의 모든 설명자를 스캔해야 합니다.


epoll은 내부의 파일 디스크립션을 모니터링하므로 커널은 프로세스가 epoll_wait 를 호출하여 처리하기를
기다리지 않고, open 파일 디스크립션 이 I/O 준비가 될 때마다 준비 목록에 추가합니다.
프로세스가 epoll_wait 를 호출할 때 커널은 단지 준비 목록 에 대한 모든 정보만들 반환합니다.

또한, select/poll 은 호출할 때마다 모니터링할 디스크립션에 대한 정보를 커널에 전달해야 합니다.
이것은 두 함수의 시그니쳐를 보면 명확합니다.
커널은 전달된 모든 파일 디스크립터에 대한 정보를 리턴하여 프로세스가 다시 모든 디스크립터를 스캔하여
I/O 준비가 된 디스크립터를 찾아야 합니다.

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);


epoll을 사용하면 epoll_ctl 콜을 사용하여 파일 디스크립터를 epoll 인스턴스의 관심 목록 에 추가한 후
나중에 epoll_wait 를 호출할 때 준비가 필요한 파일 디스크립터를 전달할 필요가 없습니다.
커널은 전달된 모든 디스크립터에 대한 정보를 리턴하는 select/poll 모델과 반대로
I/O 준비가 된 디스크립터에 대한 정보만 다시 리턴합니다.
결과적으로 epoll의 비용은 select/poll 의 경우와 같이 O(발생한 이벤트 수)이며 O(모니터되는 디스크립터 수)는 아닙니다.




Edge triggered epoll

기본적으로 epoll은 레벨 트리거(level-triggered) 알림을 제공합니다.
epoll_wait 에 대한 모든 호출은 관심 목록준비된 파일 디스크립터들만 리턴합니다.
따라서 4 개의 파일 디스크립터(fd1, fd2, fd3fd4)가 등록되어 있고
epoll_wait 를 호출 할 때 2 개(fd2fd3) 만 준비되어 있으면이 두 디스크립터에 대한 정보만 리턴됩니다.


그림 17. Epoll Edge triggered #0.


레벨 트리거의 경우 epoll이 관심을 갖는 디스크립터의 특성(블로킹 대 넌블로킹, blocking vs non-blocking)의 특성이
실제로 epoll_wait 호출 결과에 영향을 미치지 않는다는 점도 흥미 롭습니다.
epoll은 내부의 open 파일 디스크립션 이 준비될 때만 준비 목록 을 업데이트하기 때문입니다.


때로는 준비 여부에 관계없이 관심 목록에서 디스크립터(예를 들면, fd1)의 상태를 찾으려고 할 수도 있습니다.
epoll은 엣지 트리거(edge-triggered) 알림을 지원하여 특정 파일 디스크립터(epoll_wait 를 호출할 때
준비가 되어 있지 않은 경우에도)에서 I/O가 가능한지 여부를 확인할 수 있습니다.
epoll_wait 에 대한 이전 호출 이후(또는 디스크립터가 열린 이후, 프로세스가 호출한 이전 epoll_wait 콜이 없는 경우)
파일 디스크립터에서 I/O 활동이 있었는지 여부에 대한 정보가 필요한 경우, epoll 인스턴스에 파일 디스크립터를 등록하는
epoll_ctl 을 호출할 때 EPOLLET 플래그를 OR-ing 링하여 알림을 트리거할 수 있습니다.


아마 파일 디스크립터epoll_ctl 로 epoll 인스턴스에 등록되는 실제 프로젝트의 코드에서 EPOLLET 플래그가
다른 플래그와 함께 ORed 되는 것을 직접 보는 것이 더 도움이 될 것입니다.


function Poller:register(fd, r, w)
	local ev = self.ev[0]
	ev.events = bit.bor(C.EPOLLET, C.EPOLLERR, C.EPOLLHUP)
	if r then
		ev.events = bit.bor(ev.events, C.EPOLLIN)
	end
	if w then
		ev.events = bit.bor(ev.events, C.EPOLLOUT)
	end
	ev.data.u64 = fd
	local rc = C.epoll_ctl(self.fd, C.EPOLL_CTL_ADD, fd, ev)
	if rc < 0 then errors.get(rc):abort() end
end


실제 사례를 들면, 엣지 트리거 알림이 epoll과 작동하는 방식을 이해하는 데 도움이 될 것입니다.
프로세스가 epoll 인스턴스에 4 개의 디스크립터를 등록한 이전 예제를 사용하겠습니다.
fd3 이 소켓이라고 가정 해 봅시다.
시간 t1 에서, 입력 바이트 스트림이 fd3 가 참조하는 소켓에 도달한다고 가정합니다.


그림 18. t0 에, 소켓에 입력이 도착.


시간 t4 에서 프로세스가 epoll_wait() 를 호출했다고 가정해 봅시다.
시간 t4 에서 파일 디스크립터 fd2fd3 이 준비되면 epoll_wait 호출은 fd2fd3 이 준비되었다고 보고합니다.


그림 19. t4 에, 프로세스가 epoll_wait 호출.


시간 t6 에서 프로세스가 epoll_wait 를 다시 호출한다고 가정해 봅시다. fd1 이 준비되었다고 가정해 봅시다.
t4t6 사이에 fd3 이 참조하는 소켓에 입력이 도착하지 않았다고 가정해 봅시다.
레벨 트리거 된 경우, epoll_wait 에 대한 호출은 fd1 이 준비된 유일한 디스크립터이므로 프로세스에 fd1 을 리턴합니다.
그러나 t4t6 사이에 fd3 이 참조하는 소켓에 새 데이터가 도착하지 않았기 때문에 에지 트리거의 경우에는 호출이 블록(block)됩니다.


그림 20. t6 에, 프로세스가 다시 epoll_wait 호출.




결론

이 글은 “방법”을 설명하기 위해 작성되었습니다.
epoll을 이해하려면 다음 두 개의 블로그 게시물도 추천드립니다.

epoll is fundamentally broken — parts 1 and 2.




Glossary

  • call: 호출
  • entry: 항목
  • fork: 분기




참고자료

댓글남기기