La diferencia entre seleccionar y epoll Zhihu
Primero hablemos del marco de este artículo, primero presentaremos el problema, luego resumiremos las diferencias y conexiones entre los dos mecanismos y finalmente presentaremos el uso de cada interfaz.
1. Diferencias en la obtención y conexión de problemas
El problema surge cuando es necesario leer más de dos E/S, si se utiliza el bloqueo de E/S, un descriptor puede quedar bloqueado durante mucho tiempo. Aunque otros descriptores tienen datos, no se pueden leer, por lo que el rendimiento en tiempo real no puede cumplir con los requisitos. Las soluciones aproximadas son las siguientes:
1. Complicará el programa e interferirá con el proceso. La creación y el mantenimiento de subprocesos también requieren una gran sobrecarga. (El servidor Apache utiliza un proceso secundario, que tiene la ventaja de aislar a los usuarios)
2. Utilice un proceso, pero utilice E/S sin bloqueo para leer datos, cuando una E/S sea ilegible. inmediatamente y verifique si el siguiente es legible. Esta forma de bucle es el sondeo. Este método desperdicia tiempo de CPU porque es ilegible la mayor parte del tiempo, pero aún lleva tiempo ejecutar repetidamente la llamada al sistema de lectura.
3. La E/S asíncrona utiliza una señal para indicarle al proceso cuándo un descriptor está listo. Sin embargo, debido al número limitado de señales, no es aplicable a varios descriptores.
4. Una mejor manera es la multiplexación de E/S (parece traducirse también a multiplexación). Primero construye una lista de descriptores relacionados (cola epoll), luego llama a una función que no regresa hasta uno de estos. Los descriptores están listos, indicando al proceso qué E/S está lista. Tanto el mecanismo select como el epoll son soluciones para el mecanismo de E/S multicanal. Select está en el estándar POSIX, mientras que epoll es exclusivo de Linux.
Hay tres diferencias principales (las ventajas de epoll sobre select):
1. El número de identificadores de selección es limitado. Existe una declaración de este tipo en linux/posix_types.h. archivo de encabezado: #define __FD_SETSIZE 1024 Indica que select puede monitorear hasta 1024 fds al mismo tiempo. Pero epoll no lo hace, su límite es el número máximo de identificadores de archivos abiertos.
2. El mayor beneficio de epoll es que la eficiencia no disminuirá a medida que aumenta el número de FD. El procesamiento de sondeo se utiliza en selec. La estructura de datos es similar a una estructura de datos de matriz, mientras que epoll mantiene una. Cola, solo verifique si la cola está vacía. epoll solo funcionará en sockets "activos"; esto se debe a que en la implementación del kernel, epoll se implementa en función de la función de devolución de llamada en cada fd. Luego, solo el socket "activo" llamará activamente a la función de devolución de llamada (agregue este identificador a la cola), pero otros identificadores de estado inactivo no lo harán. En este punto, epoll implementa una AIO "pseudo". Pero si la mayor parte de las E/S están "activas" y la tasa de uso de cada puerto de E/S es alta, la eficiencia de epoll no es necesariamente mayor que la de select (puede resultar complicado mantener la cola).
3. Utilice mmap para acelerar el paso de mensajes entre el kernel y el espacio del usuario. Ya sea select, poll o epoll, el kernel necesita notificar al espacio de usuario del mensaje FD. Cómo evitar copias de memoria innecesarias es muy importante. En este punto, mmap implementa epoll en la misma memoria en el espacio de usuario. núcleo.
2. Interfaz
1) seleccionar
1. int select(int maxfdp1, fd_set *restringir lecturas, fd_set *restringir escrituras, fd_set *restringir excepciones, struct timeval *restrict tvptr);
struct timeval{
long tv_sec;
long tv_usec;
}
Hay tres situaciones: tvptr == NULL, esperar para siempre; tvptr->tv_sec == 0 && tvptr->tv_usec == 0, no esperar en absoluto cuando no es igual a 0, es el tiempo de espera. Los tres punteros de selección pueden ser nulos. En este momento, seleccionar proporciona un temporizador más preciso que dormir. Tenga en cuenta que el primer parámetro de selección, maxfdp1, no es el número de descriptores, sino el descriptor más grande más 1. En primer lugar, actúa como un limitador para evitar errores. En segundo lugar, puede proporcionar una sesión previa para que el sondeo del núcleo mejore la eficiencia. . select devuelve -1 para un error, 0 para un tiempo de espera y el valor positivo devuelto es el número de todos los descriptores preparados (si el mismo descriptor está listo para lectura y escritura, el impacto en el resultado es +2).
2.int FD_ISSET(int fd, fd_set *fdset); fd no es cero en el conjunto de descriptores; de lo contrario, se devuelve 0
3.int FD_CLR(int fd, fd_set) *fd_set ); int FD_SET(int fd, fd_set *fdset);int FD_ZERO(fd_set *fdset);
Usando las palabras del hombre de Linux "FD_ZERO() borra un set.FD_SET() y FD_CLR( ) agregan y eliminan respectivamente un descriptor de archivo determinado de un conjunto. FD_ISSET() prueba para ver si un descriptor de archivo es parte del conjunto. Esto es útil después de que select() regrese". Estas funciones no tienen nada que ver con el 0 y el 1; del descriptor, simplemente agregue el descriptor de detección de eliminación para ver si está en el conjunto.
2) epoll
1.int epoll_create(int size);
Crea un identificador de epoll, el tamaño se usa para indicarle al núcleo el número de oyentes* **Qué grande. Este parámetro es diferente del primer parámetro en select(), dando el valor del fd+1 máximo monitoreado. Cabe señalar que después de crear el identificador de epoll, ocupará un valor fd. Si observa /proc/process id/fd/ en Linux, puede ver este fd, por lo que después de usar epoll, debe llamar a close(. ) para cerrar; de lo contrario, fd podría agotarse.
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
función de registro de eventos de epoll, que se diferencia de select() en que escucha para eventos Cuando le diga al kernel qué tipo de eventos escuchar, registre aquí primero el tipo de evento a escuchar.
El primer parámetro es el valor de retorno de epoll_create(), y el segundo parámetro representa la acción, representada por tres macros:
EPOLL_CTL_ADD: registra un nuevo fd en epfd;
EPOLL_CTL_MOD: Modificar el evento de escucha del fd registrado;
EPOLL_CTL_DEL: eliminar un fd de epfd;
El tercer parámetro es el fd que necesita ser monitoreado y el cuarto parámetro es la notificación. ¿Qué necesita monitorear el kernel? La estructura de struct epoll_event es la siguiente:
struct epoll_event {
__uint32_t events /* Epoll events */
epoll_data_t data; /* Variable de datos de usuario */
};
Los eventos pueden ser una colección de las siguientes macros:
EPOLLIN: Indica el descriptor de archivo correspondiente. se puede leer (incluido El SOCKET final está cerrado normalmente);
EPOLLOUT: indica que el descriptor de archivo correspondiente se puede escribir;
EPOLLPRI: indica que el descriptor de archivo correspondiente tiene datos de emergencia que se puede leer (esto debería indicar que hay datos fuera de banda que llegan);
EPOLLERR: Indica que el descriptor de archivo correspondiente tiene un error;
EPOLLHUP: Indica que el descriptor de archivo correspondiente está colgado;
EPOLLET: configure EPOLL en el modo activado por borde, que es relativo al activado por nivel.
EPOLLONESHOT: solo escucha el evento una vez. Después de escuchar este evento, si aún necesita continuar monitoreando el socket, debe agregar el socket a la cola EPOLL nuevamente.
Acerca del modo de trabajo de epoll ET, LT
LT (activado por nivel) es el modo de trabajo predeterminado y admite sockets con y sin bloqueo. En este enfoque, el kernel le indica si un descriptor de archivo está listo. y luego podrá realizar operaciones IO en este fd listo. Si no haces nada, el kernel seguirá notificándote, por lo que la posibilidad de errores de programación en este modo es menor. La selección/encuesta tradicional son representantes de este modelo.
ET (activado por flanco) es un modo de trabajo de alta velocidad y solo admite enchufes sin bloqueo. En este modo, el kernel le avisa mediante epoll cuando un descriptor cambia de no listo a listo. Luego asumirá que usted sabe que el descriptor de archivo está listo y no enviará más notificaciones de preparación para ese descriptor de archivo hasta que haga algo que cause que ese descriptor de archivo ya no esté listo, pero tenga en cuenta que si no hay ninguna operación IO en este fd (haciendo que no vuelva a estar listo), el kernel no enviará más notificaciones (solo una vez)
3.
Esperando que ocurran eventos, similar a una llamada select(). El parámetro events se utiliza para obtener una colección de eventos del kernel. maxevents le dice al kernel qué tan grandes son los eventos. El valor de maxevents no puede ser mayor que el tamaño al crear epoll_create(). El tiempo de espera del parámetro es el tiempo de espera (milisegundos). 0 volverá inmediatamente, -1 es un bloqueo permanente). Esta función devuelve la cantidad de eventos que deben procesarse. Si devuelve 0, significa que se agotó el tiempo de espera.