Реализация select разделена на общий верхний уровень и множество специфических для устройства или сокета нижних частей. На верхнем уровне select или poll расшифровывает запрос пользователя, а затем вызывает соответствующие запрашивающие функции нижнего уровня. Системные вызовы select и poll имеют различные верхние уровни для определения наборов дескрипторов, которые следует опрашивать, но используют одни и те же специфические для устройства или сокета нижние части. Здесь будет описан лишь верхний уровень select. Верхний уровень poll реализован полностью сходным образом.
Верхний уровень select предпринимает следующие шаги.
2. Копирует и проверяет маски дескрипторов для чтения, записи и исключительных условий. Осуществление подтверждения правильности требует проверки того, чтобы каждый запрошенный дескриптор был в настоящее время открыт процессом.
3. Устанавливает для потока флаг selecting.
4. Для каждого дескриптора в каждой маске вызывает процедуру опроса устройства. Если дескриптор не может выполнить запрошенную операцию ввода/вывода, процедура опроса устройства отвечает за запись того, что поток хочет выполнить ввод/вывод. Когда для дескриптора становится возможным ввод/вывод, - обычно в результате прерывания нижележащего устройства - для выбирающего потока должно быть выдано уведомление.
5. Поскольку процесс выбора может занять много времени, ядро не хочет блокировать ввод/вывод в то время, пока оно осуществляет опрос всех запрошенных дескрипторов. Вместо этого ядро организует обнаружение ввода/вывода, который может повлиять на состояние опрашиваемых дескрипторов. Когда возникает такой ввод/вывод, процедура уведомления select, selwakeup(), сбрасывает флаг selecting. Если код select верхнего уровня обнаруживает, что флаг selecting для данного потока был сброшен, пока выполнялся опрос, и он не нашел каких-либо дескрипторов, которые готовы выполнить операцию, тогда верхний уровень знает, что результаты опроса неполные и должны быть повторены, начиная с шага 2. Другим условием, требующим повтора опроса, является коллизия. Коллизии возникают, когда несколько потоков пытаются опросить в одно и то же время один и тот же дескриптор. Поскольку у процедур опроса достаточно места для записи идентификатора лишь одного потока, они не могут отслеживать несколько потоков, которые необходимо пробудить, когда появилась возможность ввода/вывода. В таких редких случаях должны пробуждаться все ожидающие потоки.
6. Если ни один дескриптор не готов и select определила тайм-аут, ядро записывает тайм-аут на запрошенный промежуток времени. Поток входит в состояние сна, передав адрес глобальной переменной ядра selwait. Обычно дескриптор становится готовым, и поток будет уведомлен посредством selwakeupf). Когда поток пробуждается, он повторяет процесс опроса и возвращает доступные дескрипторы. Если ни один из дескрипторов не стал готовым до истечения срока таймера, поток возвращается с ошибкой тайм-аута и пустым списком доступных дескрипторов. Если приведено значение тайм-аута и дескриптор становится готовым до истечения указанного периода времени, время, которое select провел в ожидании готовности ввода/ вывода, вычитается из указанного времени.
Каждая из низкоуровневых процедур опроса в драйверах терминалов и сетевых протоколах следует примерно одному и тому же набору шагов. Часть кода процедуры опроса для драйвера терминала показана на рисунке. Шаги, связанные с процедурой опроса устройства, следующие.
1. Элемент запроса сокета или устройства вызывается с одним или несколькими флагами POLLIN, POLLOUT или POLLRDBAND (исключительное условие).
2. Процедура опроса устанавливает флаги, указывающие доступные операции. Кроме того, если носитель удален, можно получить ошибку чтения. Возвращение из select необязательно означает, что есть данные для чтения; скорее, оно означает, что чтение не будет блокировано.
3. Если запрошенная операция невозможна, с сокетом или устройством записывается идентификатор потока для последующего уведомления. Запись осуществляется процедурой selrecord(). Эта процедура сначала проверяет, записаны ли для этого сокета или устройства какие-нибудь потоки. Если ничего не записано, записывается текущий поток и структура selinfo добавляется в список событий, связанных с потоком. Второй оператор if проверяет наличие коллизий. Если записанный в структуру поток не является нашим собственным, возникла коллизия.
4. Вызывается selwakeup() с указателем наструктуру selinfo, использующейся selrecord() для записи идентификатора потока, и с флагом, указывающим на возникновение коллизии.
5. Если поток ожидает в selwait, он делается готовым к запуску (или помечается готовым, если он остановлен). Если поток ожидает какого-нибудь другого события помимо selwait, он не делается готовым к запуску. Может возникнуть ложный вызов selwakeup(), когда поток возвращается из select, чтобы начать обработку одного дескриптора, а затем другой дескриптор, который он выбирал, также становится готовым.
6. Если у потоков установлены их флаги selecting, они сбрасываются таким образом, чтобы ядро знало, что их результаты опроса недействительные и должны быть получены снова.
7. Если возникла коллизия, все ожидающие в selwait пробуждаются для повторного сканирования, не готов ли один из их дескрипторов. Пробуждение всех выбирающих потоков необходимо, поскольку процедура selrecord() не могла бы записать все потоки, которые необходимо пробудить. Поэтому ей приходится пробуждать все потоки, которые могут быть в этом заинтересованы. Как показывает опыт, коллизии возникают нечасто. Если бы они были частыми, стоило бы хранить в структуре selinfo несколько идентификаторов потоков.
- 15/11/2010 15:49 - Трансляция имен путей
- 14/11/2010 09:58 - Операции vnode
- 12/11/2010 12:38 - Содержимое vnode
- 10/11/2010 06:57 - Интерфейс виртуальной файловой системы
- 09/11/2010 19:30 - Перемещение данных внутри ядра
- 08/11/2010 02:22 - Мультиплексирование ввода/вывода для дескрипторов
- 04/11/2010 08:33 - Блокировка дескриптора файла
- 03/11/2010 00:01 - Асинхронный ввод/вывод
- 02/11/2010 10:14 - Управление дескрипторами
- 31/10/2010 09:00 - Открытые элементы файлов