Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
Шрифт:
Но на самом деле роль функции io_open гораздо значительнее. Она отвечает не только за проверку, может клиент открыть ресурс или нет, но также за следующее:
• инициализацию внутренних параметров библиотеки;
• привязку к запросу контекстного блока;
• привязку к контекстному блоку атрибутной записи.
Первые две операции выполняются с помощью функции базового уровня resmgr_open_bind, а привязка атрибутной записи сводится к простому присваиванию.
Будучи однажды вызвана, io_open выпадает из рассмотрения. Клиент может либо прислать сообщение ввода/вывода, либо нет, но в любом случае
Тут есть один интересный момент, который вы, может быть, для себя уже отметили. Прототип клиентской функции chown имеет вид:
Вспомните: сообщение об установлении соединения всегда содержит имя пути и является либо однократным, либо устанавливает контекст для дальнейших сообщений ввода/ вывода.
Так почему же сообщение, соответствующее клиентской функции chown, не является сообщением установления соединения? К чему здесь сообщение ввода/вывода, когда в прототипе даже дескриптора файла нет?!
Ответ простой — чтобы облегчить вам жизнь.
Представьте себе, что было бы, если бы функции типа chown, chmod, stat и им подобные требовали от администратора ресурсов, чтобы он сначала анализировал имя пути, а затем уже выполнял нужные действия. (Именно так, кстати, все реализовано в QNX4.) Типичные проблемы этого подхода:
• Каждой функции приходится вызывать процедуру поиска.
• Для функций, у которых есть также версия, ориентированная на файловый дескриптор, драйвер должен обеспечить две отдельные точки входа: одну для версии с именем пути, и еще одну — версии с дескриптором файла.
В QNX/Neutrino же происходит следующее. Клиент создает составное сообщение — реально это одно сообщение, но оно включает в себя несколько сообщений администратору ресурсов. Без составных сообщений мы могли бы смоделировать функцию chown чем-то таким:
где функция fchown — это версия функции chown, ориентированная на файловые дескрипторы. Проблема здесь в том, что мы в этом случае используем три вызова функций (а значит, и три отдельных транзакции передачи сообщений) и привносим дополнительные накладные расходы применением функций open
При использовании составных сообщений в QNX/Neutrino непосредственно клиентским вызовом chown создается одиночное сообщение, выглядящее примерно так:
Составное сообщение.
Сообщение состоит из двух частей. Первая часть посвящена установлению соединения (подобно сообщению, которое сгенерировала бы функция open), вторая — вводу/выводу (эквивалент сообщения, генерируемого функцией fchown). Никакого эквивалента функции close здесь нет, поскольку мы выбрали сообщение типа _IO_CONNECT_COMBINE_CLOSE, которое гласит: «Открой указанное имя пути, используй полученный дескриптор файла для обработки остальной части сообщения, а когда закончишь дела или столкнешься с ошибкой, закрой дескриптор».
Написанный вами администратор ресурса даже не заметит, вызвал ли клиент функцию chown или сначала сделал open, а потом вызвал fchown и далее close. Все это скрыто базовым уровнем библиотеки.
Как выясняется, концепция составных сообщений полезна не только для экономии ресурсов вследствие уменьшения числа сообщений (как в случае с chown, см. выше). Она также критически важна для обеспечения атомарности операций.
Предположим, что в клиентском процессе есть два или более потоков, работающих с одним дескриптором файла. Один из потоков в клиенте вызывает функцию lseek, за которой следует read. Все так, как мы и предполагаем. А вот если другой клиента попробует выполнить ту же самую последовательность операций с тем же самым дескриптором файла, вот тут у нас начнутся проблемы. Поскольку функции lseek и read друг о друге ничего не знают, то возможно, например, что первый поток выполнит lseek, а затем будет вытеснен вторым потоком. Второй поток выполнит свои lseek и read, после чего освободит процессор. Проблема здесь состоит в том, что поскольку эти два потока разделяют один и тот же дескриптор файла, у первого потока теперь получается неправильное смещение lseek, поскольку оно было изменено функциями lseek и read второго потока! Эта проблема проявляется также с дескрипторами файлов, которые дублируются (dup) между процессами, не говоря уже о сети.
Очевидным решением здесь является заключение lseek и read в пределы действия мутекса — когда первый поток захватит мутекс, мы будем знать, что он имеет эксклюзивный доступ к дескриптору. Второй поток должен будет ждать освобождения мутекса, прежде чем он сможет творить свое безобразие с позиционированием дескриптора.
К сожалению, если кто-то вдруг забудет захватить мутекс перед каждой и любой операцией с дескриптором файла, всегда остается возможность того, что такой «незащищенный» доступ может привести поток к чтению или записи данных не туда, куда надо.