Red de conocimiento informático - Consumibles informáticos - La tecnología Epoll que se debe preguntar en las entrevistas, comprender a fondo epoll desde el código fuente del kernel

La tecnología Epoll que se debe preguntar en las entrevistas, comprender a fondo epoll desde el código fuente del kernel

epoll es un mecanismo para la multiplexación de E/S en Linux. La multiplexación de E/S es un mecanismo a través del cual un proceso puede monitorear múltiples descriptores. Una vez que un determinado descriptor está listo (generalmente (listo para leer o escribir), está listo). Puede notificar al programa que realice las operaciones de lectura y escritura correspondientes. Por supuesto, la multiplexación IO en Linux no es solo epoll. Otros mecanismos de multiplexación incluyen seleccionar y sondear. Pero a continuación, presentaremos la implementación del kernel de epoll.

Los eventos pueden ser una colección de las siguientes macros:

Ventajas de epoll en comparación con select/poll:

El código del kernel relacionado con epoll está en fs/ eventpoll.c, a continuación se analiza la implementación de las tres funciones epoll_create, epoll_ctl y epoll_wait en el kernel. El código fuente del kernel de Linux utilizado en el análisis es la versión 4.1.2.

epoll_create se usa para crear un identificador de epoll. Su implementación en el sistema del kernel es la siguiente:

sys_epoll_create:

Se puede ver que cuando llamamos. epoll_create, pasamos El parámetro de tamaño solo se usa para determinar si es menor o igual a 0 y no tendrá ningún otro uso a partir de entonces.

La función completa tiene solo 3 líneas de código y el trabajo real aún se ubica en la función sys_epoll_create1.

sys_epoll_create -> sys_epoll_create1:

El flujo de la función sys_epoll_create1 es el siguiente:

sys_epoll_create -> sys_epoll_create1 -> ep_alloc:

sys_epoll_create -> sys_epoll_create1 -> ep_alloc -> get_unused_fd_flags:

En el kernel de Linux, current es una macro y devuelve una variable de una estructura task_struct (lo llamamos descriptor de proceso), que representa Para el proceso actual, los recursos de archivos abiertos por el proceso se almacenan en el miembro de archivos del descriptor del proceso, por lo que current->files devuelve los recursos de archivos abiertos por el proceso actual. La función rlimit(RLIMIT_NOFILE) obtiene el número máximo de descriptores de archivos que puede abrir el proceso actual. Este valor se puede establecer y el valor predeterminado es 1024.

Recomendaciones de videos relacionados:

Revelación práctica de epoll, la piedra angular subyacente que admite IO de mil millones de niveles

Principios de red tcp/udp, programación de red epoll/reactor Entrevistas serias "Ensayo de ocho partes"

Dirección de aprendizaje: desarrollo de servidores Linux C/C++/arquitecto backend Lingsheng Education-Video tutorial de aprendizaje-Tencent Classroom

Necesito más C/C++ Linux Los materiales de aprendizaje de arquitectos de servidores agregan el grupo 812855908 para obtenerlos (los materiales incluyen C/C++, Linux, tecnología golang, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, streaming media, CDN, P2P, K8S, Docker, TCP/IP, Coroutine, DPDK, ffmpeg, etc.), uso compartido gratuito

El trabajo de __alloc_fd es proporcionar al proceso el proceso entre [inicio, fin) (nota: aquí el inicio es 0, el final es el archivo más grande descriptor que el proceso puede abrir Número) asigne un descriptor de archivo disponible, no entraremos en detalles aquí, el código es el siguiente:

sys_epoll_create -> sys_epoll_create1 -> ep_alloc -> get_unused_fd_flags -> __alloc_fd:

Luego, epoll_create1 llamará a anon_inode_getfile para crear una estructura de archivos, de la siguiente manera:

sys_epoll_create -> sys_epoll_create1 -> anon_inode_getfile:

La función anon_inode_getfile primero asignará un estructura de archivos y una estructura dentry, y luego conecte la estructura de archivos con un nodo de inodo anónimo anon_inode_inode. Lo que debe tenerse en cuenta aquí es que cuando se llama a la función anon_inode_getfile para solicitar la estructura de archivos, se pasa la variable ep de la estructura eventpoll aplicada anteriormente. en. El archivo aplicado->private_data apuntará a esta variable ep. Al mismo tiempo, después de que regrese la función anon_inode_getfile, ep->file apuntará a la variable de estructura del archivo solicitada por la función.

Hablemos brevemente sobre file/dentry/inode. Cuando un proceso abre un archivo, el kernel asignará una estructura de archivo al proceso, lo que indica que el archivo abierto está en el contexto del proceso, y luego la aplicación pasará un int Tipo de descriptor de archivo para acceder a esta estructura. De hecho, el proceso del kernel mantiene una matriz de estructuras de archivos y el descriptor de archivo es el subíndice de la estructura de archivos correspondiente en la matriz.

La estructura dentry (llamada "entrada de directorio") registra varios atributos del archivo, como el nombre del archivo, permisos de acceso, etc. Cada archivo tiene solo una estructura dentry y un proceso puede abrirlo varias veces. Un archivo o varios procesos también pueden abrir el mismo archivo. En estos casos, el kernel solicitará múltiples estructuras de archivos y establecerá múltiples contextos de archivos. Sin embargo, para el mismo archivo, no importa cuántas veces se abra, el kernel solo asignará un dentry al archivo. Por lo tanto, la relación entre la estructura del archivo y la estructura dentry es de muchos a uno.

Al mismo tiempo, además de una estructura de entrada de directorio dentry, cada archivo también tiene una estructura de nodo de índice, que registra la ubicación y distribución del archivo en el medio de almacenamiento. Cada archivo solo tiene Allocate. un inodo. Los objetivos descritos por dentry e inode son diferentes. Un archivo puede tener varios nombres de archivo (como archivos de enlace) y los permisos para acceder al mismo archivo a través de diferentes nombres de archivo también pueden ser diferentes.

El archivo dentry representa un archivo en un sentido lógico y registra sus atributos lógicos, mientras que la estructura de inodo representa un archivo en un sentido físico y registra sus atributos físicos. La relación entre dentry y estructura de inodo es una relación de muchos a uno.

sys_epoll_create -> sys_epoll_create1 -> fd_install:

Para resumir lo que hace la función epoll_create: después de llamar a epoll_create, asigne una estructura eventpoll y una estructura de archivos que represente el archivo epoll en el kernel. y Asocie estas dos estructuras y devuelva un descriptor de archivo epoll fd que también esté asociado con la estructura del archivo. Cuando una aplicación opera epoll, necesita pasar un descriptor de archivo epoll fd. Según este fd, el kernel encuentra la estructura del archivo de epoll y luego usa el archivo para obtener la variable de estructura eventpoll solicitada previamente por epoll_create. relacionado con epoll se almacena en esta estructura en. A continuación, todas las operaciones de la función de interfaz epoll se realizan en las variables de estructura de eventpoll.

Por lo tanto, la función de epoll_create es establecer un canal desde el descriptor del archivo epoll hasta la variable de estructura eventpoll en el kernel para el proceso.

La función de la interfaz epoll_ctl es agregar/modificar/eliminar eventos de escucha de archivos. El código del kernel es el siguiente:

sys_epoll_ctl:

Según el. Introducción anterior a la interfaz epoll_ctl, op es la acción para la operación epoll (agregar/modificar/eliminar eventos), ep_op_has_event(op) determina si no es una operación de eliminación, si op!= EPOLL_CTL_DEL es verdadero, debe llamar a copy_from_user función para copiar el evento pasado desde el espacio del usuario a la variable epds del kernel. Porque, solo para operaciones de eliminación, el kernel no necesita usar el evento pasado por el proceso.

Luego se llama a fdget dos veces seguidas para obtener las variables de estructura de archivo del archivo epoll y el archivo monitoreado (en lo sucesivo, el archivo de destino) (nota: esta función devuelve la variable de estructura fd y la La estructura fd contiene la estructura del archivo).

El siguiente paso es verificar los parámetros. Si ocurre la siguiente situación, se puede considerar que hay un problema con los parámetros pasados ​​y se devolverá un error directamente:

p> Por supuesto, hay algunas acciones de operación a continuación. No hay explicación aquí para agregar la operación. Es relativamente simple y puede leerlo usted mismo.

En ep, se mantiene un árbol rojo-negro. Cada vez que se agrega un evento de registro, se aplica una variable de la estructura epiitem para representar el elemento de escucha del evento y luego se inserta en el árbol rojo. árbol negro del ep. En epoll_ctl, se llamará a la función ep_find para encontrar el elemento de monitoreo representado por el archivo de destino del árbol rojo-negro de ep. El elemento de monitoreo devuelto puede estar vacío.

El siguiente código en el área de cambio es el núcleo de toda la función epoll_ctl. Hay tres situaciones para agregar (EPOLL_CTL_ADD), eliminar (EPOLL_CTL_DEL) y modificar (EPOLL_CTL_MOD) al cambiar la operación. La adición se utiliza como ejemplo para explicar. Las otras dos situaciones son similares. Una vez que sepa cómo agregar un evento de escucha, también puede hacer inferencias sobre otros eventos de escucha que se eliminan y modifican.

Al agregar un evento de monitoreo para un archivo de destino, primero asegúrese de que el archivo de destino no haya sido monitoreado en el ep actual. Si existe (epi no está vacío), se devolverá un error -EEXIST.

De lo contrario, significa que los parámetros son normales. Luego, primero configure los eventos de monitoreo POLLERR y POLLHUP para el archivo de destino de forma predeterminada, y luego llame a la función ep_insert para insertar los eventos de monitoreo del archivo de destino en el árbol rojo-negro mantenido por ep. :

sys_epoll_ctl - > ep_insert:

Como se mencionó anteriormente, la supervisión del archivo de destino se mantiene mediante una variable de elemento de supervisión de la estructura epitem, por lo que en la función ep_insert, primero llame la función kmem_cache_alloc para asignar un epitem del asignador de losa. La estructura escucha los elementos y luego inicializa la estructura. No hay nada que decir aquí. Veamos a continuación la llamada a la función ep_item_poll:

sys_epoll_ctl -> ep_insert -> ep_item_poll:

En la función ep_item_poll, se llama a la función de encuesta del archivo de destino. Esta función es específica. a diferentes archivos de destino. Apunta a diferentes funciones. Si el archivo de destino es un socket, esta encuesta apunta a sock_poll. Si el archivo de destino es un socket tcp, esta encuesta es la función tcp_poll. Aunque la función señalada por la encuesta puede ser diferente, su función es la misma, que es obtener el bit de evento generado actualmente por el archivo de destino y vincular el elemento de escucha al gancho de encuesta del archivo de destino (lo más importante es para registrar la devolución de llamada de sondeo de la función de devolución de llamada ep_ptable_queue_proc), una vez completado este paso, se llamará a la función de devolución de llamada ep_ptable_queue_proc cuando ocurra un evento en el archivo de destino.

A continuación, llame a list_add_tail_rcu para agregar el elemento de monitoreo actual a la lista f_ep_links del archivo de destino. Esta lista de enlaces es la lista de enlaces epoll del archivo de destino. Se agregarán todos los elementos de monitoreo que monitorean el archivo de destino. a la lista en.

Luego llame a ep_rbtree_insert para agregar el elemento de monitoreo epi al árbol rojo-negro mantenido por ep. No hay explicación aquí. El código es el siguiente:

sys_epoll_ctl -> ep_insert -. > ep_rbtree_insert:

Como se mencionó anteriormente, ep_insert llama a ep_item_poll para obtener los bits de evento generados por el archivo de destino. Durante el período antes de llamar a epoll_ctl, puede haber eventos que el proceso relevante deba monitorear. monitorear eventos (revents & event->events es verdadero) y el elemento de monitoreo relacionado con el archivo de destino no está vinculado a la lista de preparación del ep rdlist, luego agregue el elemento de monitoreo a la lista de preparación de rdlist del ep y la rdlist está vinculada al descriptor de epoll monitoreando elementos de escucha para todos los archivos de destino listos. Y, si hay tareas esperando a que ocurra un evento, se llama a la función wake_up_locked para activar todas las tareas en espera y procesar los eventos correspondientes. Cuando un proceso llama a epoll_wait, el proceso aparece en la cola de espera wq de ep. A continuación, explicaremos la función epoll_wait.

Resumen de la función epoll_ctl: esta función se aplica a un elemento de monitoreo para el archivo de destino según el evento monitoreado y cuelga el elemento de monitoreo en el árbol rojo-negro de la estructura eventpoll.

El código del kernel para el evento de espera epoll_wait es el siguiente:

sys_epoll_wait:

El primero es verificar algunos parámetros pasados ​​por el proceso:

Después de verificar y calificar todos los parámetros, se llama a la función ep_poll para el procesamiento real:

sys_epoll_wait -> ep_poll:

Lo primero en ep_poll es el procesamiento del tiempo de espera, tiempo de espera El tiempo está en ms. El tiempo de espera es mayor que 0, lo que significa que se agota después de esperar el tiempo de espera. Si el tiempo de espera es igual a 0, la función no se bloquea y regresa directamente. 0, se bloquea permanentemente y no regresa hasta que ocurre un evento.

Cuando no se genera ningún evento ((!ep_events_available(ep)) es verdadero), llame a la función __add_wait_queue_exclusive para agregar el proceso actual a la cola de espera ep->wq, y luego en un bucle for infinito, primero Llame a set_current_state(TASK_INTERRUPTIBLE) para establecer el proceso actual en un estado de suspensión interrumpible. Luego, el proceso actual abandonará la CPU y se pondrá en suspensión. No ejecutará el siguiente paso hasta que otro proceso llame a wake_up o llegue una señal de interrupción. despierta el código.

Si el proceso se despierta, primero verifique si ocurre un evento, si ocurre un tiempo de espera o si fue despertado por otras señales. Si ocurren estas situaciones, salga del ciclo, elimine el proceso actual de la cola de espera de ep->wp y establezca el proceso actual en el estado listo TASK_RUNNING.

Si ocurre un evento, llame a la función ep_send_events para transferir los eventos al espacio del usuario.

sys_epoll_wait -> ep_poll -> ep_send_events:

ep_send_events no tiene trabajo, el verdadero trabajo está en la función ep_scan_ready_list:

sys_epoll_wait -> ep_poll -> ep_send_events -> ep_scan_ready_list:

ep_scan_ready_list primero vincula los datos en la lista ep ready a una txlist global, luego borra la lista ep ready y también establece la lista ep ovflist en NULL como una única lista vinculada. es una lista vinculada de respaldo que acepta eventos listos. Cuando el proceso del kernel copia eventos del kernel al espacio del usuario, es posible que se generen nuevos eventos en el archivo de destino durante este tiempo, y la nueva hora debe vincularse a ovlist. .

Solo entonces, llame a la función de devolución de llamada sproc (aquí se llamará a la función ep_send_events_proc) para copiar los datos del evento del kernel al espacio del usuario.

sys_epoll_wait -> ep_poll -> ep_send_events -> ep_scan_ready_list -> ep_send_events_proc:

La función de devolución de llamada ep_send_events_proc se repite para obtener los datos del evento del elemento de escucha. Para cada elemento de escucha, llame a ep_item_poll. para obtener el evento del elemento de escucha del archivo de destino. Si se obtiene el evento, se llama a la función __put_user para copiar los datos al espacio del usuario.

Regrese a la función ep_scan_ready_list. Como se mencionó anteriormente, durante la ejecución de la función de devolución de llamada sproc, el archivo de destino puede generar nuevos eventos y vincularlos a la lista vinculada de ovlist, por lo tanto, una vez finalizada la devolución de llamada. Es necesario volver a ingresar a la lista vinculada de ovlist. El evento se agrega a la lista de eventos listos para rdllist.

Al mismo tiempo, al final, si rdlist no está vacío (lo que indica si hay un evento listo) y el proceso está esperando el evento, se llama a wake_up_locked para reactivar el proceso del kernel nuevamente. para manejar la llegada del evento (el proceso es el mismo que antes, también es copiar el evento al espacio del usuario).

En este punto, el proceso epoll_wait ha finalizado, pero hay un problema, es decir, el proceso mencionado anteriormente se suspenderá después de llamar a epoll_wait, pero ¿cuándo se despertará este proceso? Al llamar a epoll_ctl para registrar un elemento de escucha para el archivo de destino, registre una función de devolución de llamada ep_ptable_queue_proc para el elemento de escucha del archivo de destino. La función de devolución de llamada ep_ptable_queue_proc agrega el proceso a la lista de activación del archivo de destino y registra la devolución de llamada ep_poll_callbak. el archivo de destino genera un evento, ep_poll_callbak La devolución de llamada activará el proceso en la cola de espera.

Para resumir la función epoll: La función epoll_wait hará que el proceso que la llama entre en suspensión (excepto cuando el tiempo de espera es 0). Si ocurre un evento monitoreado, el proceso se despertará y el evento se activará. expulsado del kernel. Copie al espacio del usuario y regrese al proceso.