Poll і select

Підтримка будь-якого з цих викликів вимагає підтримки з боку драйвера пристрою. Ця підтримка (для всіх трьох викликів) забезпечується за допомогою методу драйвера poll (опитування). Цей метод має наступний прототип:

unsigned int (* poll) (struct file * filp, poll_table * wait);

Метод драйвера викликається всякий раз, коли програма робочих просторів перед виконує системний виклик poll. select або epoll за участю дескриптора файлу, пов'язаного з драйвером. Метод пристрою відповідає за ці два дії:

1. Викликати poll_wait для однієї або більше черг очікування, що може свідчити про зміну в статусі опитування. Якщо немає файлових дескрипторів, доступних в даний час для введення / виведення, ядро ​​змусить процес чекати в черзі очікування для всіх файлових дескрипторів, переданих системному виклику.

2. Повернення бітової маски, яка описує операції (якщо такі є), які можуть бути негайно виконані без блокування.

Обидві ці операції, як правило, прості і дуже схожі від одного драйвера до іншого. Передбачається, однак, що тільки драйвер може надати цю інформацію і, отже, вони повинні бути реалізовані індивідуально кожним драйвером.

void poll_wait (struct file * filp, wait_queue_head_t * wait_queue, poll_table * wait);

Друге завдання виконується методом poll поверненням бітової маски опису, які операції можуть бути завершені негайно; це теж просто. Наприклад, якщо в пристрої є дані, читання виповнилося б без сну; методу poll слід показати такий стан справ. Для позначення можливих операцій використовуються кілька прапорів (визначаються через ):

Цей біт необхідно встановити, якщо "нормальні" дані доступні для читання. Читане пристрій повертає (POLLIN | POLLRDNORM).

Цей біт показує, що для читання з пристрою доступні дані поза логічного каналу зв'язку (з додаткового допоміжного каналу). В даний час використовується тільки в одному місці в ядрі Linux (код DECnet) і, як правило, не застосуємо до драйверів пристроїв.

Коли процес, який читає цей пристрій, бачить кінець файлу, драйвер повинен встановити POLLHUP (зависання). Процес, що викликає select каже, що пристрій це читаемо, як це диктується функціональністю select.

У пристрої сталася помилка. При виклику poll про пристрій повідомляється як про читається і записуваному, а read і write повертають код помилки без блокування.

Цей біт встановлюється в повернутому значенні, якщо в цей пристрій можна записати без блокування.

Цей біт має таке ж значення, як POLLOUT і іноді він дійсно є тим же номером. Записується пристрій повертає (POLLOUT | POLLWRNORM).

Варто повторити, що POLLRDBAND і POLLWRBAND мають сенс тільки для файлових дескрипторів, пов'язаних з сокетами: драйвери пристроїв зазвичай не використовуватимуть ці прапори.

Опис poll займає багато місця для того, що є відносно простим для використання на практиці. Розглянемо реалізацію методу poll в scullpipe:

static unsigned int scull_p_poll (struct file * filp, poll_table * wait)

struct scull_pipe * dev = filp-> private_data;

unsigned int mask = 0;

Дайте відповідь на це буфер є круговим; він вважається повним

* Якщо "wp" знаходиться прямо позаду "rp" і порожнім, якщо

poll_wait (filp, dev-> inq, wait);

poll_wait (filp, dev-> outq, wait);

if (dev-> rp! = dev-> wp)

mask | = POLLIN | POLLRDNORM; / * Читаємо * /

mask | = POLLOUT | POLLWRNORM; / * Запісиваемо * /

Цей код просто додає дві черги очікування scullpipe до poll_table. потім встановлює відповідні бітові маски в залежності від того, чи можуть дані бути лічені або записані.

Показаний код poll опускає підтримку "кінця файлу", тому що scullpipe не підтримує умова "кінець файлу". Для більшості реальних пристроїв метод poll повинен повернути POLLHUP. якщо даних більше немає (або не буде). Якщо викликає використовував системний виклик select. про цей файл повідомляється як про читається. Незалежно від того, використовувався poll або select. додаток знає, що воно може викликати читання не чекаючи вічно і метод read повернеться, просигналізував 0 про кінець файлу.

З реальними FIFO, наприклад, читач бачить кінець файлу, коли все письменники закривають цей файл, тоді як читач scullpipe ніколи не бачить кінця файлу. Поведінка відрізняється, тому що FIFO призначений, щоб бути каналом зв'язку між двома процесами, а scullpipe є Кошика, куди кожен може помістити дані, поки є хоча б один читач. Крім того, немає сенсу заново реалізовувати те, що вже є в ядрі, так що ми вибрали в нашому прикладі іншу поведінку для реалізації.

Реалізація "кінця файлу" так само, як в FIFO, означало б зробити перевірку dev-> nwriters в read і в poll і повідомляти про "кінець файлу" (як описано вище), якщо немає процесу, що має пристрій відкритим для запису. Однак, на жаль, при такій реалізації, якщо читач відкрив пристрій scullpipe перед письменником, він буде бачити "кінець файлу", не маючи шансу дочекатися даних. Кращим способом вирішити цю проблему буде здійснювати блокування в open. як це роблять реальні FIFO; це завдання залишається як вправа для читача.

Взаємодія з read і write

Метою викликів poll і select є визначити заздалегідь, чи буде операція введення / виводу блокована. В цьому відношенні вони доповнюють read і write. Що важливіше, poll і select корисні, тому що вони дозволяють з додатком одночасно очікувати кілька потоків даних, хоча ми і не використовуємо цю функцію в прикладах scull. Щоб зробити роботу коректної, необхідна правильна реалізація трьох викликів: хоча про такі правила вже більш-менш говорилося, ми підсумували їх тут.

Читання даних з пристрою

• Якщо є дані у вхідному буфері, виклик read повинен повернутися негайно, без будь-яких помітних затримок, навіть якщо є менше даних, ніж запитано додатком, і драйвер впевнений, що залишилися дані прибудуть найближчим часом. Ви завжди можете повернути менше даних, ніж запитано, принаймні один байт, якщо це зручно з будь-якої причини (ми робили це в scull). В цьому випадку poll повинен повернути POLLIN | POLLRDNORM.

• У разі відсутності даних у вхідному буфері, за замовчуванням read повинен блокуватися, поки немає принаймні одного байта. З іншого боку, якщо встановлено O_NONBLOCK. read відразу ж повертається зі значенням -EAGAIN (хоча в деяких старих версіях System V в даному випадку повертається 0). У цих випадках poll повинен повідомити, що пристрій не доступно для читання, поки не буде отримано принаймні один байт. Як тільки в буфері виявляються деякі дані, ми відкатуємося до попереднього випадку.

• Якщо ми в кінці файлу, read повинна негайно повернутися з повертається значенням 0, незалежно від O_NONBLOCK. poll в цьому випадку повинен повідомити POLLHUP.

Запис в пристрій

• Якщо є місце в вихідному буфері, write повинен повернутися без затримки. Він може прийняти менше даних, ніж запросив виклик, але він повинен прийняти як мінімум один байт. В цьому випадку poll повідомляє, що пристрій буде видимий для запису, повертаючи POLLOUT | POLLWRNORM.

• Якщо вихідний буфер заповнений, за замовчуванням write блокується, поки не звільняється деякий простір. Якщо встановлений O_NONBLOCK. write негайно повертається зі значенням -EAGAIN (старі System V повертали 0). У цих випадках poll повинен повідомити, що файл не доступний для запису. Якщо, з іншого боку, пристрій не може прийняти будь-які додаткові дані, write повертає -ENOSPC ( "No space left on device", "Немає місця на пристрої") незалежно від установки O_NONBLOCK.

• Ніколи не робіть очікування у виклику write для очікування передачі даних, перш ніж повернутися, навіть якщо O_NONBLOCK очищений. Багато додатків використовують select. щоб дізнатися, чи буде блокований write. Якщо про пристрій повідомляється як про доступне для запису, виклик не повинен блокуватися. Якщо програма, яка використовує пристрій, прагне до того, що дані з черги в вихідному буфері передавалися насправді, такий драйвер повинен надати метод fsync. Наприклад, знімне пристрій повинен мати точку входу fsync.

Хоча це хороший набір загальних правил, слід також визнати, що кожен пристрій є унікальним, і що іноді правила повинні бути злегка змінені. Наприклад, пристрої, орієнтовані на запис (такі як стрічкові накопичувачі), не можуть виконувати часткові записи.

Скидання на диск в процесі виведення

Ми бачили, як метод write сам по собі не враховує всіх потреб передачі даних. Функція fsync. викликається системним викликом з тим же ім'ям, заповнює цю прогалину. Прототипом методу є

int (* fsync) (struct file * file, struct dentry * dentry, int datasync);

Якщо який-небудь додаток завжди має бути впевнена, що дані були відправлені в пристрій, метод fsync повинен виконуватися незалежно від того, чи встановлений O_NONBLOCK. Виклик fsync повинен повернутися тільки тоді, коли дані в пристрій були повністю передані (тобто, вихідний буфер порожній), навіть якщо це займає деякий час. Аргумент datasync використовується, щоб розрізняти системні виклики fsync і fdatasync; як такої, він представляє інтерес тільки для коду файлової системи і може бути проігнорований драйверами.

Нижележащих структура даних

Фактична реалізація системних викликів poll і select є досить простий для тих, хто зацікавився, як це працює; epoll є трохи більш складним, але побудований на тому ж механізмі. Всякий раз, коли користувальницький додаток викликає poll. select або epoll_ctl (* Це функція, яка створює внутрішню структуру даних для майбутніх викликів epoll_wait.). ядро викликає метод poll всіх файлів, на які посилається системний виклик, передаючи кожному з них ту ж poll_table. Структура poll_table є тільки обгорткою навколо функції, яка створює реальну структуру даних. Для poll і select ця структура є зв'язковим списком сторінок пам'яті, що містять структури poll_table_entry. Кожна poll_table_entry містить структуру file і покажчики wait_queue_head_t передаються в poll_wait поряд зі зв'язаним об'єктом черзі очікування. Виклик poll_wait іноді також додає цей процес до даної черги очікування. В цілому вся структура повинна обслуговуватися ядром так, щоб цей процес міг бути видалений з усіх цих черг до повернення poll або select.

Якщо жоден з опитаних драйверів не показує, що введення / виведення може відбуватися без блокування, виклик poll просто спить, поки одна з (може бути багатьох) черг очікування його не розбудити.

Цікавим в реалізації poll є те, що метод драйвера poll може бути викликаний з покажчиком NULL в якості аргументу poll_table. Ця ситуація може відбутися з кількох причин. Якщо додаток, що викликає poll. передало значення часу очікування 0 (що свідчить, що не повинно бути очікування), немає ніяких причин, щоб збирати чергу очікування, і система просто нічого не робить. Покажчик poll_table також відразу встановлюється в NULL після будь-якого опитування драйвера, що показує, що введення / виведення можливий. Так як з цього часу ядро ​​знає, що очікування не буде, воно не будує список черг очікування.

Після завершення виклику poll структура poll_table звільняється і всі об'єкти черзі очікування, раніше додані до таблиці опитування (якщо така є), будуть видалені з таблиці і їх черг очікування.

Ми постаралися показати на рисунку 6-1 структури даних, які беруть участь в опитуванні; цей малюнок є спрощеним уявленням реальної структури даних, оскільки він ігнорує багатосторінковий характер таблиці опитування і ігнорує файловий покажчик, який є частиною кожної poll_table_entry. Читачеві, зацікавленому в фактичної реалізації, настійно рекомендується заглянути в і fs / select.c.

Poll і select

Малюнок 6-1. Структура даних, що ховається за poll

На даний момент можна зрозуміти мотив для нового системного виклику epoll. У типовому випадку виклик poll або select включає в себе лише невелику кількість файлових дескрипторів, так що витрати на створення структури даних невеликі. Однак, існують додатки, які працюють з тисячами файлових дескрипторів. При цьому створення і видалення цієї структури даних між кожною операцією введення / виведення стає непомірно дорогим. Сімейство системного виклику epoll дозволяє додаткам такого виду створити внутрішню структуру даних ядра тільки один раз і використовувати її багато раз.

Схожі статті