Hay bastantes detalles "inferiores".
Primero, considere que el núcleo tiene una lista de procesos y, en cualquier momento, algunos de estos procesos se están ejecutando y otros no. El kernel le permite a cada proceso en ejecución una porción de tiempo de CPU, luego lo interrumpe y pasa al siguiente. Si no hay procesos ejecutables, entonces el núcleo probablemente emitirá una instrucción como HLT a la CPU que suspende la CPU hasta que haya una interrupción de hardware.
En algún lugar del servidor hay una llamada al sistema que dice "dame algo que hacer". Hay dos amplias categorías de formas en que esto se puede hacer. En el caso de Apache, llama accept
a un socket que Apache ha abierto anteriormente, probablemente escuchando en el puerto 80. El núcleo mantiene una cola de intentos de conexión y se agrega a esa cola cada vez que se recibe un TCP SYN . La forma en que el núcleo sabe que se recibió un TCP SYN depende del controlador del dispositivo; Para muchas NIC probablemente haya una interrupción de hardware cuando se reciben datos de la red.
accept
le pide al kernel que me devuelva la próxima iniciación de conexión. Si la cola no estaba vacía, accept
solo regresa de inmediato. Si la cola está vacía, el proceso (Apache) se elimina de la lista de procesos en ejecución. Cuando se inicia una conexión más tarde, el proceso se reanuda. Esto se llama "bloqueo", porque para el proceso que lo llama, accept()
parece una función que no regresa hasta que tiene un resultado, lo que podría pasar en algún momento a partir de ahora. Durante ese tiempo, el proceso no puede hacer nada más.
Una vez que accept
regresa, Apache sabe que alguien está intentando iniciar una conexión. Luego llama a fork para dividir el proceso de Apache en dos procesos idénticos. Uno de estos procesos continúa para procesar la solicitud HTTP, el otro llama accept
nuevamente para obtener la siguiente conexión. Por lo tanto, siempre hay un proceso maestro que no hace más que llamar accept
y generar subprocesos, y luego hay un subproceso para cada solicitud.
Esta es una simplificación: es posible hacer esto con subprocesos en lugar de procesos, y también es posible hacerlo de fork
antemano para que haya un proceso de trabajo listo cuando se recibe una solicitud, lo que reduce la sobrecarga de inicio. Dependiendo de cómo esté configurado Apache, puede hacer cualquiera de estas cosas.
Esa es la primera categoría amplia de cómo hacerlo, y se llama bloqueo de E / S porque el sistema llama como accept
y read
y write
que operan en sockets suspenderá el proceso hasta que tengan algo que devolver.
La otra forma amplia de hacerlo se llama IO sin bloqueo o basada en eventos o asincrónica . Esto se implementa con llamadas al sistema como select
o epoll
. Cada uno de ellos hace lo mismo: les das una lista de sockets (o, en general, descriptores de archivos) y lo que quieres hacer con ellos, y el núcleo se bloquea hasta que esté listo para hacer una de esas cosas.
Con este modelo, puede decirle al núcleo (con epoll
): "Indíqueme cuándo hay una nueva conexión en el puerto 80 o nuevos datos para leer en cualquiera de estas 9471 otras conexiones que he abierto". epoll
bloquea hasta que una de esas cosas esté lista, entonces lo haces. Entonces repites. Las llamadas al sistema como accept
y read
y write
no cuadra, en parte porque cada vez que se llama a ellos, epoll
acabo de decir que están dispuestos de modo que no habría razón para bloquear, y también porque cuando se abre la toma o el archivo que especifica que ellos quieren en modo sin bloqueo, por lo que esas llamadas fallarán en EWOULDBLOCK
lugar de bloquearse.
La ventaja de este modelo es que solo necesita un proceso. Esto significa que no tiene que asignar una pila y estructuras de kernel para cada solicitud. Nginx y HAProxy usan este modelo, y es una gran razón por la que pueden manejar tantas más conexiones que Apache en hardware similar.