epoll
select의 대체자 epoll
epoll은 select의 단점을 보완하여 리눅스환경에서 사용할 수 있도록 만든 I/O 통지 기법이다. 전체 파일 디스크립터에 대한 반복문을 사용하지 않고, 커널에게 정보를 요청하는 함수(select 같은)를 호출할 때마다 전체 관찰 대상에 대한 정보를 넘기지도 않는다.
계속해서 정보를 넘기지 않기 위해서 관찰 대상인 fd들의 정보를 담은 저장소를 직접 운영체제가 담당한다. 운영체제에게 관찰대상의 저장소를 만들어달라고 요청하면 그 저장소에 해당하는 파일 디스크립터(이하 epoll_fd)를 리턴해준다. 관찰 영역이 변경되면(관찰대상 추가 삭제) epoll_fd를 통해 변경을 요청할 수 있다. 그리고 관찰 대상의 변경사항을 체크할때도 epoll_fd를 통해 확인을 한다. 따라서 전체 파일디스크립터를 순회하면서 FD_ISSET을 하는 문제는 더이상 발생하지 않는다.
계속해서 정보를 넘기지 않기 위해서 관찰 대상인 fd들의 정보를 담은 저장소를 직접 운영체제가 담당한다. 운영체제에게 관찰대상의 저장소를 만들어달라고 요청하면 그 저장소에 해당하는 파일 디스크립터(이하 epoll_fd)를 리턴해준다. 관찰 영역이 변경되면(관찰대상 추가 삭제) epoll_fd를 통해 변경을 요청할 수 있다. 그리고 관찰 대상의 변경사항을 체크할때도 epoll_fd를 통해 확인을 한다. 따라서 전체 파일디스크립터를 순회하면서 FD_ISSET을 하는 문제는 더이상 발생하지 않는다.
epoll
위의 동작을 코드상에 구현하려면 3가지 요청이 필요하다. 우선 epoll_fd를 만들어 주는 epoll_create 함수. 운영체제에 의해 만들어진 fd로 다른 fd와 같이 소멸시 close를 통한 반환이 필요하다.
int epoll_create(int size); //size는 epoll_fd의 크기정보를 전달한다. //반환 값 : 실패 시 -1, 일반적으로 epoll_fd의 값을 리턴
관찰 대상이 되는 파일 디스크립터들을 등록, 삭제하는데 사용되는 epoll_ctl
int epoll_ctl(int epoll_fd, //epoll_fd int operate_enum, //어떤 변경을 할지 결정하는 enum값 int enroll_fd, //등록할 fd struct epoll_event* event //관찰 대상의 관찰 이벤트 유형 ); //반환 값 : 실패 시 -1, 성공시 0
operate_enum 값은 EPOLL_CTL_ADD(새로운 fd를 등록). EPOLL_CTL_DEL (기존 fd 삭제), EPOLL_CTL_MOD (등록된 fd의 이벤트 발생상황을 변경) 으로 구성된다. 3번째가 조금 이해가 안될지도 모르지만 차차 알게 될 것이다. 문제는 앞으로 계속 사용될 epoll_event* 구조체의 정체이다.
struct epoll_event { __uint32_t events; epoll_data_t data; } typedef epoll_data { void* ptr; int fd; __uint32_t u32; __uint64_t u64; }epoll_data_t; enum Events { EPOLLIN, //수신할 데이터가 있다. EPOLLOUT, //송신 가능하다. EPOLLPRI, //중요한 데이터(OOB)가 발생. EPOLLRDHUD,//연결 종료 or Half-close 발생 EPOLLERR, //에러 발생 EPOLLET, //엣지 트리거 방식으로 설정 EPOLLONESHOT, //한번만 이벤트 받음 }
epoll_event는 파일 디스크립터와 event, 그리고 기타 정보를 묶어서 만든 구조체이다. 처음 fd를 설정하는 EPOLL_CTL_MOD에서도 epoll_event 구조체를 사용하여 초기화하며, select와 같은 역할을 하는 epoll_wait 함수에서도 비어있는 epoll_event의 배열을 넘겨서 반환값을 받는 구조체로 사용한다. 중간에 나오는 엣지 트리거에 대해서는 이후에 설명하도록 한다.
실제로 변경된 fd들의 집합을 요청하는 함수는 epoll_wait이다. select의 select와 같은 역할을 한다. 앞서 설명한 함수들은 이 함수를 위한 포석이다.
int epoll_wait( int epoll_fd, //epoll_fd struct epoll_event* event, //event 버퍼의 주소 int maxevents, //버퍼에 들어갈 수 있는 구조체 최대 개수 int timeout //select의 timeout과 동일 단위는 1/1000 ); //성공시 이벤트 발생한 파일 디스크립터 개수 반환, 실패시 -1 반환
두 번째 인자로 들어가는 포인터는 epoll_event 구조체의 배열을 넘긴다. 함수가 정상 반환시 배열에 이벤트가 발생한 fd와 이벤트의 종류가 묶여서 들어온다. 따라서 모든 fd에 대하여 순회하면서 체크할 필요가 없다. 이벤트가 있는 fd들이 배열에 담겨오고 그 개수를 알 수 있으니 꼭 필요한 event만 순회하면서 처리할 수 있다는 장점이 여기서 발생한다.
epoll 사용하기
epoll을 서버에서 사용한 실제 예
int epoll_fd = epoll_create(EPOLL_SIZE); struct epoll_event* events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE); struct epoll_event init_event; init_event.events = EPOLLIN; init_event.data.fd = server_socket; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &init_event); while(TRUE) { int event_count = epoll_wait(epoll_fd, events, EPOLL_SIZE, -1); if( event_count = -1 ) break; for( int i = 0 ; i < event_count; ++i ) { if(events[i].data.fd == server_socket) //서버 소켓에 이벤트 { //accept 처리 ... init_event.events = EPOLLIN; init_event.data.fd = new_client_socket; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_client_socket, &init_event); } else //이벤트가 도착한 소켓들 { //read, write, closesocket처리 } } } closesocket(server_socket); close(epoll_fd); return 0;
epoll의 정체성
epoll은 select의 단점을 많이 개선한 형태의 통지방식이다. FD_SET을 운영체제가 직접 관리하는 것으로 많은 부분이 개선되었다. 하지만 그 본질적인 동작 구조는 select와 크게 다르지 않다. 프로세스가 커널에게 지속적으로 I/O 상황을 체크하여 동기화 하는 개념은 여전히 유효하다. 따라서 epoll의 통지모델 역시 동기형 통지모델이다.
그리고 timeout개념이 select와 동일한 방식으로 동작하기 때문에 timeout에 들어온 인자가 어떠냐에 따라 blocking이기도 하고 non-blocking이기도 하다. 따라서 epoll의 전체적인 개념모델은 select와 같다고 생각한다.
출처: http://ozt88.tistory.com/21 [공부 모음]
Epoll의 기초 개념 및 사용 방법 (0) | 2019.01.05 |
---|---|
소켓의 우아한 연결 종료 (0) | 2017.10.09 |