epoll
. Aqui, falaremos sobre como ele epoll
transfere 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 eventpoll
do 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
timeout
e 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 eventpool
em sua variável local. Em seguida, ele define o campo de ovflist
estrutura eventpool
como NULL
(e seu padrão é EP_UNACTIVE_PTR
).
Por que os autores
epoll
usamovflist
? 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 ovflist
como 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 ovflist
função, não ep_scan_ready_list()
há necessidade de manter um bloqueio ep->lock
ao 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 epoll
verificar 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 EPOLLOUT
enquanto 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. Epoll
você precisa lidar com situações como essa corretamente. Caso contrário, o usuário receberá EPOLLOUT
no 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 é ovflist
novamente 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?

