Давайте сначала рассмотрим использование epoll.
В следующем коде сначала используйте epoll_create для создания дескриптора файла epoll epfd, затем используйте epoll_ctl для добавления отслеживаемого сокета в epfd и, наконец, вызовите epoll_wait для ожидания данных.
int s = socket(AF_INET, SOCK_STREAM, 0);
bind(s, ...);
listen(s, ...)
int epfd = epoll_create(...);
epoll_ctl(epfd, ...); // Все, что нужно контролировать socket добавить в epfd середина.
#define MAX_EVENTS 10
struct epoll_event events[MAX_EVENTS];
while(1) {
int n = epoll_wait(epfd, events, ...); // Возврат готов socket количество.
for(int i = 0; i < n; ++i) {
if (events[n].data.fd == listen_sock) {
// иметь дело с
}
}
}
Когда вы создаете сокет, операционная система создает объект сокета, которым управляет файловая система. Этот объект сокета содержит такие элементы, как буфер отправки, буфер приема и очередь ожидания. Очередь ожидания — очень важная структура, которая указывает на все процессы, которым необходимо дождаться события сокета.
Чтобы использовать epoll, сначала необходимо вызвать функцию epoll_create() для создания дескриптора файла epoll. Прототип функции выглядит следующим образом:
int epoll_create(int size);
Размер параметра является устаревшим по историческим причинам и не имеет значения, начиная с Linux 2.6.8, но должен быть больше нуля.
Когда пользователь вызывает функцию epoll_create(), он входит в пространство ядра и вызывает функцию ядра do_epoll_create() для создания дескриптора epoll. Код функции do_epoll_create() выглядит следующим образом:
/*
* Open an eventpoll file descriptor.
*/
static int do_epoll_create(int flags)
{
int error, fd;
struct eventpoll *ep = NULL;
struct file *file;
/* Check the EPOLL_* constant for consistency. */
BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);
if (flags & ~EPOLL_CLOEXEC)
return -EINVAL;
/*
* Create the internal data structure ("struct eventpoll").
*/
error = ep_alloc(&ep);
if (error < 0)
return error;
/*
* Creates all the items needed to setup an eventpoll file. That is,
* a file structure and a free file descriptor.
*/
fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
if (fd < 0) {
error = fd;
goto out_free_ep;
}
file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
O_RDWR | (flags & O_CLOEXEC));
if (IS_ERR(file)) {
error = PTR_ERR(file);
goto out_free_fd;
}
ep->file = file;
fd_install(fd, file);
return fd;
out_free_fd:
put_unused_fd(fd);
out_free_ep:
ep_free(ep);
return error;
}
sys_epoll_create() в основном делает две вещи:
Как видно из исходного кода do_epoll_create(), объект epoll на самом деле представляет собой опрос событий, определенный следующим образом:
struct eventpoll {
/* Protect the access to this structure */
spinlock_t lock;
/*
* This mutex is used to ensure that files are not removed
* while epoll is using them. This is held during the event
* collection loop, the file cleanup path, the epoll file exit
* code and the ctl operations.
*/
struct mutex mtx;
/* Wait queue used by sys_epoll_wait() */
wait_queue_head_t wq;
/* Wait queue used by file->poll() */
wait_queue_head_t poll_wait;
/* List of ready file descriptors */
struct list_head rdllist;
/* RB tree root used to store monitored fd structs */
struct rb_root rbr;
/*
* This is a single linked list that chains all the "struct epitem" that
* happened while transferring ready events to userspace w/out
* holding ->lock.
*/
struct epitem *ovflist;
/* wakeup_source used when ep_scan_ready_list is running */
struct wakeup_source *ws;
/* The user that created the eventpoll descriptor */
struct user_struct *user;
struct file *file;
/* used to optimize loop detection check */
int visited;
struct list_head visited_list_link;
}
Есть несколько участников, на которых необходимо сосредоточить внимание:
(1) Список контролируемых сокетов (rbr).
Добавьте отслеживаемый сокет в список, сохраненный в красно-черном дереве, с помощью epoll_ctl(2). Ключ узла красно-черного дерева представляет собой дескриптор файла, а значение — это информационный эпитемент, связанный с дескриптором файла.
(2) Очередь ожидания (wq).
и socket Точно так же у него также будет очередь ожидания при вызове epoll_wait(epfd, ...)
добавлю процесс в eventpoll Объект wq Ожидание в очереди.
(3) Готовый список сокетов (rdllist).
Сохраните список готовых дескрипторов файлов сокетов. Когда программа выполняет epoll_wait, если готовый сокет уже существует в списке rdlist, то epoll_wait возвращается напрямую. Если список rdlist пуст, процесс блокируется.
На следующем рисунке показана связь между объектом eventpoll и отслеживаемыми файлами:
Поскольку отслеживаемый файл передается epitem Объекты являются управляемыми, поэтому все узлы на рисунке выше основаны на epitem Он существует в форме Объекта. Почему использовать красно-черное дерево для управления отслеживаемыми файлами? Это для того, чтобы иметь возможность передавать дескриптор файлбыстро найти соответствующий epitem объект.
/*
* Each file descriptor added to the eventpoll interface will
* have an entry of this type linked to the "rbr" RB tree.
* Avoid increasing the size of this struct, there can be many thousands
* of these on a server and we do not want this to take another cache line.
*/
struct epitem {
union {
/* RB tree node links this structure to the eventpoll RB tree */
struct rb_node rbn;
/* Used to free the struct epitem */
struct rcu_head rcu;
};
/* List header used to link this structure to the eventpoll ready list */
struct list_head rdllink;
/*
* Works together "struct eventpoll"->ovflist in keeping the
* single linked chain of items.
*/
struct epitem *next;
/* The file descriptor information this item refers to */
struct epoll_filefd ffd;
/*
* Protected by file->f_lock, true for to-be-released epitem already
* removed from the "struct file" items list; together with
* eventpoll->refcount orchestrates "struct eventpoll" disposal
*/
bool dying;
/* List containing poll wait queues */
struct eppoll_entry *pwqlist;
/* The "container" of this item */
struct eventpoll *ep;
/* List header used to link this item to the "struct file" items list */
struct hlist_node fllink;
/* wakeup_source used when EPOLLWAKEUP is set */
struct wakeup_source __rcu *ws;
/* The structure that describe the interested events and the source fd */
struct epoll_event event;
};
epitem используется для представления информации о каждом отслеживаемом дескрипторе файла в epoll. Вот несколько важных полей:
Ранее мы рассказали, как создать epoll. Далее мы расскажем, как добавить отслеживаемый сокет в epoll.
Файлы, подлежащие мониторингу, можно добавить в epoll, вызвав функцию epoll_ctl(), прототип которой следующий:
long epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
Ниже объясняется функция каждого параметра:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
события могут представлять собой набор следующих макросов:
data используется для сохранения пользовательских данных.
Функция epoll_ctl() вызывает функцию ядра do_epoll_ctl(). Реализация do_epoll_ctl() следующая:
int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds, bool nonblock)
{
...
f = fdget(epfd);
if (!f.file)
goto error_return;
/* Get the "struct file *" for the target file */
tf = fdget(fd);
if (!tf.file)
goto error_fput;
...
/*
* At this point it is safe to assume that the "private_data" contains
* our own data structure.
*/
ep = f.file->private_data;
error = epoll_mutex_lock(&ep->mtx, 0, nonblock);
if (error)
goto error_tgt_fput;
...
/*
* Try to lookup the file inside our RB tree. Since we grabbed "mtx"
* above, we can be sure to be able to use the item looked up by
* ep_find() till we release the mutex.
*/
epi = ep_find(ep, tf.file, fd);
error = -EINVAL;
switch (op) {
case EPOLL_CTL_ADD:
if (!epi) {
epds->events |= EPOLLERR | EPOLLHUP;
error = ep_insert(ep, epds, tf.file, fd, full_check);
} else
error = -EEXIST;
break;
case EPOLL_CTL_DEL:
if (epi)
error = ep_remove(ep, epi);
else
error = -ENOENT;
break;
case EPOLL_CTL_MOD:
if (epi) {
if (!(epi->event.events & EPOLLEXCLUSIVE)) {
epds->events |= EPOLLERR | EPOLLHUP;
error = ep_modify(ep, epi, epds);
}
} else
error = -ENOENT;
break;
}
mutex_unlock(&ep->mtx);
error_tgt_fput:
if (full_check) {
clear_tfile_check_list();
loop_check_gen++;
mutex_unlock(&epmutex);
}
fdput(tf);
error_fput:
fdput(f);
error_return:
return error;
}
Функция sys_epoll_ctl() будет выполнять различные операции на основе значений различных переданных операций. Например, если EPOLL_CTL_ADD передается, чтобы указать, что должна быть выполнена операция добавления, тогда вызывается функция ep_insert() для выполнения операция добавления.
Продолжим анализ реализации функции ep_insert() операции добавления:
static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)
{
...
error = -ENOMEM;
// Подать заявку на один epitem объект
if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
goto error_return;
// инициализация epitem объект
INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
INIT_LIST_HEAD(&epi->pwqlist);
epi->ep = ep;
ep_set_ffd(&epi->ffd, tfile, fd);
epi->event = *event;
epi->nwait = 0;
epi->next = EP_UNACTIVE_PTR;
epq.epi = epi;
// Эквивалентно: epq.pt->qproc = ep_ptable_queue_proc
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
// вызвать отслеживаемый файл poll интерфейс.
// Этот интерфейс состоит из соответствующей файловой системы, нравиться socket из tcp_poll().
revents = tfile->f_op->poll(tfile, &epq.pt);
...
ep_rbtree_insert(ep, epi); // Пучок epitem объект добавлен вepollizУправление в красно-черных деревьях
spin_lock_irqsave(&ep->lock, flags);
// нравиться Если файл контролируется, можно выполнять соответствующие операции чтения и записи.
// Затем Пучок файла добавить в epoll очередь готовности rdllist середина, И вызов пробуждения epoll_wait() из процесса.
if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
if (waitqueue_active(&ep->poll_wait))
pwake++;
}
spin_unlock_irqrestore(&ep->lock, flags);
...
return 0;
...
}
Отслеживаемый файл передается epitem Объекты являются управляемыми, а это означает, что отслеживаемые файлы будут инкапсулированы в epitem объект, который затем будет добавлен в eventpoll Объект управление красно-черным деревом (нравиться приведенному выше коду из ep_rbtree_insert(ep, epi))。
tfile->f_op->poll(tfile, &epq.pt) Функция этой строки кода — вызвать отслеживаемый файл. poll() интерфейс, если прослушиваемый файл является socket дескриптор, то он будет вызываться tcp_poll(), давайте посмотрим tcp_poll() Что было сделано:
unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
{
struct sock *sk = sock->sk;
...
poll_wait(file, sk->sk_sleep, wait);
...
return mask;
}
Каждый объект сокета имеет очередь ожидания для хранения процессов, ожидающих изменения статуса сокета.
Из приведенного выше кода мы можем узнать, что tcp_poll() вызывает функцию poll_wait(), а poll_wait() в конечном итоге вызывает функцию ep_ptable_queue_proc(), которая реализована следующим образом:
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt)
{
struct epitem *epi = ep_item_from_epqueue(pt);
struct eppoll_entry *pwq;
if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
pwq->whead = whead;
pwq->base = epi;
add_wait_queue(whead, &pwq->wait);
list_add_tail(&pwq->llink, &epi->pwqlist);
epi->nwait++;
} else {
epi->nwait = -1;
}
}
ep_ptable_queue_proc() Основная задача функции — преобразовать текущий epitem объект добавлен в socket Объект ожидает в очереди и устанавливает функцию пробуждения на ep_poll_callback(), то есть, когда socket При изменении статуса будет инициирован звонок ep_poll_callback() функция.
Функция ep_poll_callback() реализована следующим образом:
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
...
// Пучокготовыйиздокументдобавить В очереди готовности
list_add_tail(&epi->rdllink, &ep->rdllist);
is_linked:
// Вызов пробуждения epoll_wait() и Заблокированный процесс.
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
...
return 1;
}
ep_poll_callback() Основная задача функции — добавить готовый дескриптор файла в eventepoll Очередь готовности объектасередина,Затем Вызов пробуждения epoll_wait() Заблокированный процесс.
После добавления отслеживаемого файлового дескриптора в epoll вы можете дождаться изменения статуса отслеживаемого файла, вызвав epoll_wait(). Вызов epoll_wait() заблокирует текущий процесс. Когда состояние отслеживаемого файла изменится, вызов epoll_wait() вернет управление.
Прототип системного вызова epoll_wait() выглядит следующим образом:
long epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
Описание параметра:
О возвращаемом значении: В случае успеха epoll_wait() возвращает количество готовых файловых дескрипторов или ноль, если ни один файловый дескриптор не готов в течение запрошенного таймаута в миллисекундах. При возникновении ошибки epoll_wait() возвращает -1 и соответствующим образом устанавливает errno.
Функция epoll_wait() вызовет функцию ядра sys_epoll_wait(), а функция sys_epoll_wait() в конечном итоге вызовет функцию ep_poll(). Давайте посмотрим на реализацию функции ep_poll():
static int ep_poll(struct eventpoll *ep,
struct epoll_event __user *events, int maxevents, long timeout)
{
...
// нравитьсяфруктыготовыйдокумент Список пуст
if (list_empty(&ep->rdllist)) {
// Пучоктекущий процессдобавить вepollизочередь ожиданиясередина
init_waitqueue_entry(&wait, current);
wait.flags |= WQ_FLAG_EXCLUSIVE;
__add_wait_queue(&ep->wq, &wait);
for (;;) {
set_current_state(TASK_INTERRUPTIBLE); // Пучоктекущий процессустановлен наспатьсостояние
if (!list_empty(&ep->rdllist) || !jtimeout) // нравитьсяфрукты有готовыйдокументили тайм-аут, выход из цикла
break;
if (signal_pending(current)) { // сигнал полученный тоже хочет бросить
res = -EINTR;
break;
}
spin_unlock_irqrestore(&ep->lock, flags);
jtimeout = schedule_timeout(jtimeout); // Откажитесь от процессора, Переключиться на другие процессы для выполнения
spin_lock_irqsave(&ep->lock, flags);
}
// Есть 3 ситуации, когда это произойдет:
// 1. В отслеживаемой коллекции файлов есть готовые файлы.
// 2. Тайм-аут был установлен и истек.
// 3. сигнал получен
__remove_wait_queue(&ep->wq, &wait);
set_current_state(TASK_RUNNING);
}
/* Естьготовыйиздокумент? */
eavail = !list_empty(&ep->rdllist);
spin_unlock_irqrestore(&ep->lock, flags);
if (!res && eavail
&& !(res = ep_send_events(ep, events, maxevents)) && jtimeout)
goto retry;
return res;
}
Функция ep_poll() в основном выполняет следующие действия:
Ниже приводится текстовое описание всего процесса реализации epoll мультиплексирования ввода-вывода:
epoll_create(2) - Linux manual page - man7.org Ядро LinuxEpoll Принцип реализации Linux source code (v6.0) - Elixir Bootlin Если эта статья не может объяснить суть эполла, то придите и задушите меня!