Existem muitos detalhes "inferiores".
Primeiro, considere que o kernel tem uma lista de processos e, a qualquer momento, alguns desses processos estão em execução e outros não. O kernel permite a cada processo em execução uma fatia do tempo da CPU, depois a interrompe e passa para a próxima. Se não houver processos executáveis, então o kernel provavelmente emitirá uma instrução como HLT para a CPU que suspende a CPU até lá é uma interrupção de hardware.
Em algum lugar no servidor, há uma chamada de sistema que diz "me dê algo para fazer". Existem duas grandes categorias de maneiras que isso pode ser feito. No caso do Apache, ele chama accept
em um soquete que o Apache abriu anteriormente, provavelmente escutando na porta 80 O kernel mantém uma fila de tentativas de conexão, e adiciona a essa fila toda vez que um TCP SYN é recebido. Como o kernel sabe que um TCP SYN foi recebido depende do driver do dispositivo; para muitos NICs, provavelmente há uma interrupção de hardware quando os dados de rede são recebidos.
accept
pede que o kernel retorne para mim o próximo início de conexão. Se a fila não estava vazia, então accept
apenas retorna imediatamente. Se a fila estiver vazia, o processo (Apache) será removido da lista de processos em execução. Quando uma conexão é iniciada mais tarde, o processo é retomado. Isso é chamado de "bloqueio", porque para o processo que o chama, accept()
parece uma função que não retorna até que tenha um resultado, o que pode demorar algum tempo a partir de agora. Durante esse tempo, o processo não pode fazer mais nada.
Quando o accept
for retornado, o Apache saberá que alguém está tentando iniciar uma conexão. Em seguida, chama fork para dividir o processo do Apache em dois processos idênticos. Um desses processos passa a processar a solicitação HTTP, as outras chamadas accept
novamente para obter a próxima conexão. Assim, há sempre um processo mestre que não faz nada além de chamar accept
e gerar subprocessos, e então há um subprocesso para cada solicitação.
Isso é uma simplificação: é possível fazer isso com encadeamentos em vez de processos, e também é possível fork
, portanto, há um processo de trabalho pronto para ser processado quando uma solicitação é recebida, reduzindo assim a sobrecarga de inicialização. Dependendo de como o Apache está configurado, pode fazer qualquer uma dessas coisas.
Essa é a primeira ampla categoria de como fazer isso, e é chamada de bloqueio de E / S porque as chamadas do sistema como accept
e read
e write
que operam em sockets suspenderão o processo até que eles tenham algo para devolver.
A outra maneira ampla de fazer isso é chamada de não-bloqueio ou baseada em evento ou E / S assíncrona . Isso é implementado com chamadas de sistema como select
ou epoll
. Cada um faz a mesma coisa: você dá a eles uma lista de sockets (ou, em geral, descritores de arquivos) e o que você quer fazer com eles, e o kernel bloqueia até que esteja pronto para fazer uma dessas coisas.
Com este modelo, você pode dizer ao kernel (com epoll
), "Diga-me quando houver uma nova conexão na porta 80 ou novos dados para ler em qualquer uma dessas 9471 outras conexões abertas". epoll
bloqueia até que uma dessas coisas esteja pronta, então você faz isso. Então você repete. Chamadas de sistema como accept
e read
e write
nunca bloqueiam, em parte porque, quando você as chama, epoll
acabou de informar que elas estão prontas, então não há razão para bloquear, e também porque quando você abra o soquete ou o arquivo que você especificar que deseja no modo de não bloqueio, para que essas chamadas falharão com EWOULDBLOCK
em vez de bloquear.
A vantagem deste modelo é que você precisa apenas de um processo. Isso significa que você não precisa alocar uma pilha e estruturas de kernel para cada solicitação. Nginx e HAProxy usam este modelo, e é um grande razão eles podem lidar com tantas conexões mais do que o Apache em um hardware similar.