epoll. Aqui, falaremos sobre como ele epolltransfere eventos do espaço do kernel para o espaço do usuário e como os modos de disparo de borda e nível são implementados.
Este artigo foi escrito depois dos outros. Quando comecei a trabalhar no primeiro material, o kernel Linux estável mais recente era 3.16.1. E no momento da redação deste artigo, esta já é a versão 4.1. Este artigo é baseado no código desta versão do kernel. O código, entretanto, não mudou muito, então os leitores dos artigos anteriores não devem se preocupar com o fato de que algo na implementação mudou muito.
epoll
Interagindo com o espaço do usuário
Nos artigos anteriores, passei muito tempo explicando como funciona o sistema de tratamento de eventos no kernel. Mas, como você sabe, o kernel precisa passar informações sobre eventos para um programa em execução no espaço do usuário para que o programa use essas informações. Isso é feito principalmente com a chamada de sistema epoll_wait (2) .
O código para esta função pode ser encontrado na linha 1961 do arquivo
fs/eventpoll.c. A função em si é muito simples. Depois de verificações bastante normais, ele simplesmente obtém o ponteiro para eventpolldo descritor de arquivo e chama a seguinte função:
error = ep_poll(ep, events, maxevents, timeout);
Função Ep_poll ()
A função é
ep_poll()declarada na linha 1585 do mesmo arquivo. Ele começa verificando se o usuário definiu um valor timeout. Nesse caso, a função inicializa a fila de espera e define o tempo limite para o valor especificado pelo usuário. Se o usuário não quiser esperar, ou seja , timeout = 0, a função vai imediatamente para o bloco de código com uma etiqueta check_events:, que fica responsável por copiar o evento.
Se o usuário especificou um valor
timeoute não há eventos que possam ser relatados a ele (sua presença é determinada por meio de uma chamada ep_events_available(ep)), a função se ep_poll()adiciona à fila de espera ep->wq(lembre-se do que falamos no terceiro artigo desta série). Lá mencionamos que ep_poll_callback()no processo, ele ativa todos os processos que estão esperando na fila.ep->wq...
A função entra em espera ao chamar
schedule_hrtimeout_range(). Aqui estão as circunstâncias em que um processo "adormecido" pode "acordar":
- O tempo limite expirou.
- O processo recebeu um sinal.
- Um novo evento surgiu.
- Nada aconteceu, e o planejador apenas decidiu ativar o processo.
Nos cenários 1, 2 e 3, a função define os sinalizadores apropriados e sai do loop de espera. No último caso, a função simplesmente entra no modo de espera novamente.
Após esta parte do trabalho ser concluída, ele
ep_poll()continua a executar o código do bloco check_events:.
Neste bloco, primeiro é verificada a presença de eventos e, em seguida, é feita a próxima chamada, onde acontece o mais interessante.
ep_send_events(ep, events, maxevents)
Função
ep_send_events()declarada na linha 1546. Após a chamada, chama a função ep_scan_ready_list(), passando um callback ep_send_events_proc(),. A função ep_scan_ready_list()percorre a lista de descritores de arquivo prontos e chama ep_send_events_proc()cada evento pronto que encontra. Ficará claro a seguir que um mecanismo envolvendo o uso de um retorno de chamada é necessário para garantir a segurança e a reutilização do código.
A função
ep_send_events()primeiro coloca os dados da lista de descritores de arquivo prontos da estrutura eventpoolem sua variável local. Em seguida, ele define o campo de ovflistestrutura eventpoolcomo NULL(e seu padrão é EP_UNACTIVE_PTR).
Por que os autores
epollusamovflist? Isso é feito para garantir alta eficiência epoll! Você pode notar que depois que a lista de descritores de arquivo prontos foi retirada da estrutura eventpool, ela é ep_scan_ready_list()definida ovflistcomo NULL. Isso resulta em ep_poll_callback()não tentar anexar o evento que está sendo passado para o espaço do usuário de volta ep->rdllist, o que pode levar a grandes problemas. Ao usar a ovflistfunção, não ep_scan_ready_list()há necessidade de manter um bloqueio ep->lockao copiar eventos para o espaço do usuário. Como resultado, o desempenho geral da solução é aprimorado.
Depois disso, ele
ep_send_events_proc()ignorará a lista de descritores de arquivo prontos que possui e chamará seus métodos novamente.poll()para ter certeza de que o evento realmente aconteceu. Por que epollverificar os eventos aqui novamente? Isso é feito para garantir que o evento (ou eventos) registrado pelo usuário ainda esteja disponível. Considere uma situação em que um descritor de arquivo foi adicionado à lista de descritores prontos para arquivo por evento EPOLLOUTenquanto o programa do usuário está gravando nesse descritor. Depois que o programa termina de ser escrito, o descritor de arquivo pode não ser mais gravável. Epollvocê precisa lidar com situações como essa corretamente. Caso contrário, o usuário receberá EPOLLOUTno momento em que a operação de gravação for bloqueada.
Aqui, porém, vale a pena mencionar um detalhe. Função
ep_send_events_proc()faz todos os esforços para garantir que os programas de espaço do usuário recebam notificações de eventos precisas. É possível, embora improvável, que a disponibilidade de um conjunto de eventos mude após o ep_send_events_proc()acionamento poll(). Nesse caso, um programa de espaço do usuário pode receber notificação de um evento que não existe mais. É por isso que é considerado correto sempre usar soquetes sem bloqueio quando aplicados epoll. Isso evita que seu aplicativo seja bloqueado inesperadamente.
Depois de verificar a máscara de evento, ele
ep_send_events_proc()simplesmente copia a estrutura do evento para o buffer fornecido pelo programa de espaço do usuário.
Acionado por borda e por nível
Agora podemos finalmente discutir a diferença entre Edge Triggering (ET) e Level Triggering (LT) em termos de sua implementação.
else if (!(epi->event.events & EPOLLET)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
}
É muito fácil! A função
ep_send_events_proc()adiciona o evento de volta à lista de descritores de arquivo prontos. Como resultado, na próxima chamada, ep_poll()o mesmo descritor de arquivo será verificado novamente. Como ep_send_events_proc()sempre chama um arquivo poll()antes de devolvê-lo ao espaço do usuário, isso aumenta ligeiramente a carga do sistema (em comparação com ET) se o descritor de arquivo não estiver mais disponível. Mas o objetivo de tudo isso é, como mencionado acima, não relatar eventos que não estão mais disponíveis.
Após
ep_send_events_proc()terminar de copiar os eventos, a função retorna o número de eventos copiados para ela, mantendo o aplicativo do espaço do usuário atualizado.
Quando a função
ep_send_events_proc()termina, as funçõesep_scan_ready_list()precisa limpar um pouco. Primeiro, ele retorna à lista de descritores de arquivo prontos os eventos que não foram processados pela função ep_send_events_proc(). Isso pode acontecer se o número de eventos disponíveis exceder o tamanho do buffer fornecido pelo programa do usuário. Ele também ep_send_events_proc()anexa rapidamente todos os eventos de ovflist, se houver, de volta à lista de descritores de arquivo prontos. Além disso, in é ovflistnovamente registrado EP_UNACTIVE_PTR. Como resultado, novos eventos serão anexados à lista de espera principal ( rdllist). A função sai ativando qualquer outro processo "adormecido" no caso de haver outros eventos disponíveis.
Resultado
Isso conclui o quarto e último artigo da série de implementação
epoll. Enquanto escrevo esses artigos, fico impressionado com o tremendo trabalho mental que os autores do código do kernel do Linux realizaram para atingir o máximo de eficiência e escalabilidade. E sou grato a todos os autores do código Linux por compartilhar seu conhecimento com todos que precisam, compartilhando os resultados de seu trabalho.
Como você se sente em relação ao software livre?
