RElse

epoll

Tony Lim 2023. 8. 6. 15:13
728x90

https://copyconstruct.medium.com/the-method-to-epolls-madness-d9d2d6378642

 

The method to epoll’s madness

My previous post covered the fundamentals of file descriptors as well as some of the most commonly used forms on non-blocking I/O…

copyconstruct.medium.com

 

epoll 은 systemcall이 아니라 자료구조이다. 

다른 systemcall로 해당 자료구조를 제어하는 것이다.

 

1. epoll_create

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

size 는 kernel 로 하여금 현재 프로세스가 얼마만큼의 fd를 모니터링하고 싶은지 알려주고 커널은 이를 바탕으로 epoll의 사이즈를 가늠한다.

Linux2.6.8 이후로는 dynamic sizing이 가능해져서 무시당함

epoll_create 리턴 값으로 fd를 프로세스에게 전달해준다. 이 fd에 대고 modify 요청을 날리면 Epoll Instance를 조정할 수 있다.

 

int epoll_create1(int flags);

flag0이면 기본epoll_create로 동작 EPOLL_CLOEXEC 를 주게 되면 위의 pid 483이 fork해서 자식프로세스가 생기면 현 프로세스의 EPFD를 닫아준다.

제대로 EPFD를 close안해주면 커널이 epoll instance를 죽일수 없다한다.

 

2. epoll_ctl

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

epfd 는 어떤 epoll instance 를 가르키는 file descriptor  , there can be many epoll instances

fd 는 어떤 fd를 추가및 제거 할거냐?

op 는 추가? 제거? 등 어떤 operation을 할것인가

event 는 자료구조로써 어떤 event를 해당 fd로부터 모니터링 할것인가?

epoll instance가 관심을 가져야할 Interest list (fd set) 이 있고 그 중에

 

3. epoll_wait

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

thread 가 epoll_wait를 호출하면 interest fd들중 ready 된 애가 나올때까지 block된다.

evlist = ready list를 받을 껍데기

timeout = 0이면 ready인놈이 있는 체크 하고 즉시 리턴한다. -1인 경우 ready 나올때까지 forever blocking한다.

해당 함수는 리턴값으로 준비된 fd 수를 return 한다.

 

gotchas of epoll

모든 프로세스는 각각 fd table을 가지고 있다.

fd는 프로레스가 죽거나 , close systemcall을 호출하거나 , fork를 호출해서 B가 생성되었을때 fd3의 경우 Close on Exec 가 마킹되어있으므로 inactive로 마킹되어 B는 더이상 해당 fd에 접근못한다.

fork로 나온것이니 fd0,fd3(A) , fd0(B) 는open file table의 같은 open file dscription (offset이 같다)을 가르키고 있다.

fd3(A)는 fd0를 fcntl로 복붙한거다.

inode = 파일시스템 자료구조로 metatdata이다. OFT의 Inode ptr이 disk에 있는 inode를 가르키고 있다. 이걸 매핑하는 inode table이 따로 존재한다.

A에서 fd5를 "abc.txt"를 읽기 위해 열고 B에서 fd10을 "abc.txt"에 쓰기 위해 열었다.

이둘은 같은 disk 에 io 하려하니까 OPT는 달라도 Inode ptr은 같은 파일을 가르킬 것이다.

여기서 fd0가 좀 읽으면 offset이 이동 될것이다. 이 변화는 fd0(B) ,fd3(A)에게도 적용이 된다. fd0(A)에서 nonblocking mode를 키면 나머지 fd0,fd3에게도 적용이 된다.


The bowels of epoll

A 의 fd0 ,fd1 이 존재한다. epoll_create()의 return 값으로 fd9가 EPFD로 주어짐

epoll_ctl로 fd0를 추가함 

이떄 fork로 B를 생성하고 A에서 추가로 fd8을 interestlist에 추가함

B가 epoll_wait을 호출하면 fd8의 notification을 받게됨 fork할 당시 있지도 않았던 fd8인데 왜냐하면 process A, B 가 같은 epoll instance 를 공유하고 있고 거기에 fd8이 추가되었으니 알림이 가는것이다.

이떄 B가 fd8이 가르키고 있는 file 을 open으로 열어 fd15가 생성되어 fd8이 가르키고 있는 곳을 가르킬때 A가 fd8을 close한 상황

A 가 epoll_wait을 하면 fd15의 notification을 받게 된다. underlying open file description 이 다른 fd에게 참조되고 있기 때문이다. B에서 fd15로 참조하고 있기 때문이다. 

그냥 epoll_create1 호출할시 flag줘서 이런 일 애초에 안일어나게 하는게 좋을듯 = close on exec 를 주라는 의미같음

 

why epoll is more performant than select and poll

select ,poll 은 fd중 준비되어있는애 찾느라 O(N)이 걸림

epoll이 underlying file description을 모니터링 하면서 read list에 추가를 게속 하고 있기 때문에 실제 프로세스가 epoll_wait을 호출하면 그냥 준비된것을 리턴하면 된다.

select,poll은 O(N)을 시행하는 과정이 다음과 같다. fd를 통해 쭈욱 타고들어가 준비가되었는지 확인을 해줘야한다.

여러 fd가 같은 open file table row를 가르키고 있으면 epoll 이 앞도적으로 성능이 좋게된다.

 

Edge triggered epoll

default 는 level-triggered 로 epoll_wait시 read 된 놈을 돌려준다.

edge triggered의 경우에는 변화가 있을시에만 return 한다. 

       1. The file descriptor that represents the read side of a pipe
          (rfd) is registered on the epoll instance.

       2. A pipe writer writes 2 kB of data on the write side of the
          pipe.

       3. A call to epoll_wait(2) is done that will return rfd as a
          ready file descriptor.

       4. The pipe reader reads 1 kB of data from rfd.

       5. A call to epoll_wait(2) is done.

이 경우 5번째 epoll_wait에서 blocking된다. rfd에서는 2번 event는 3번 epoll_wait에서 먹히고 4번 은 전체를 안읽고 반만 읽었기에 event가 안날아간다함 

ready와 상관없이 정말 epoll_wait 호출 인터벌 사이에 변화가 있을 경우에만 리턴함

728x90

'RElse' 카테고리의 다른 글

network reset , oracle docker as root  (0) 2024.04.06