Implementação de Epoll, parte 4

Este é o último de uma série de quatro artigos ( Parte 1 , Parte 2 , Parte 3 ) sobre implementação 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":



  1. O tempo limite expirou.
  2. O processo recebeu um sinal.
  3. Um novo evento surgiu.
  4. 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çãoep_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?










All Articles