Будучи однажды вызвана, io_open()
выпадает из рассмотрения. Клиент может либо прислать сообщение ввода/вывода, либо нет, но в любом случае должен будет однажды завершить «сеанс связи» с помощью сообщения, соответствующего функции close(). Заметьте, что если клиента вдруг постигает внезапная смерть (например, он получает SIGSEGV, или выходит из строя узел, на котором он работает), операционная система автоматически синтезирует сообщение close(), чтобы администратор ресурсов смог корректно завершить сессию. Поэтому вы гарантированно получите сообщение close()!Сообщения, которые должны быть сообщениями установления соединения, но таковыми не являются
Тут есть один интересный момент, который вы, может быть, для себя уже отметили. Прототип клиентской функции chown()
имеет вид:int chown(const char *path
, uid_t owner, gid_t group);Вспомните: сообщение об установлении соединения всегда содержит имя пути и является либо однократным, либо устанавливает контекст для дальнейших сообщений ввода/ вывода.
Так почему же сообщение, соответствующее клиентской функции chown()
, не является сообщением установления соединения? К чему здесь сообщение ввода/вывода, когда в прототипе даже дескриптора файла нет?!Ответ простой — чтобы облегчить вам жизнь.
Представьте себе, что было бы, если бы функции типа chown()
, chmod(), stat() и им подобные требовали от администратора ресурсов, чтобы он сначала анализировал имя пути, а затем уже выполнял нужные действия. (Именно так, кстати, все реализовано в QNX4.) Типичные проблемы этого подхода:• Каждой функции приходится вызывать процедуру поиска.
• Для функций, у которых есть также версия, ориентированная на файловый дескриптор, драйвер должен обеспечить две отдельные точки входа: одну для версии с именем пути, и еще одну — версии с дескриптором файла.
В QNX/Neutrino же происходит следующее. Клиент создает составное сообщение
— реально это одно сообщение, но оно включает в себя несколько сообщений администратору ресурсов. Без составных сообщений мы могли бы смоделировать функцию chown() чем-то таким:int chown(const char *path, uid_t owner, gid_t group) {
int fd, sts;
if ((fd = open(path, O_RDWR)) == -1) {
return (-1);
}
sts = fchown(fd, owner, group);
close(fd);
return (sts);
}
где функция fchown()
— это версия функции chown(), ориентированная на файловые дескрипторы. Проблема здесь в том, что мы в этом случае используем три вызова функций (а значит, и три отдельных транзакции передачи сообщений) и привносим дополнительные накладные расходы применением функций open() и close() на стороне клиента.При использовании составных сообщений в QNX/Neutrino непосредственно клиентским вызовом chown()
создается одиночное сообщение, выглядящее примерно так:Составное сообщение.
Сообщение состоит из двух частей. Первая часть посвящена установлению соединения (подобно сообщению, которое сгенерировала бы функция open()
), вторая — вводу/выводу (эквивалент сообщения, генерируемого функцией fchown()). Никакого эквивалента функции close() здесь нет, поскольку мы выбрали сообщение типа _IO_CONNECT_COMBINE_CLOSE, которое гласит: «Открой указанное имя пути, используй полученный дескриптор файла для обработки остальной части сообщения, а когда закончишь дела или столкнешься с ошибкой, закрой дескриптор».Написанный вами администратор ресурса даже не заметит, вызвал ли клиент функцию chown()
или сначала сделал open(), а потом вызвал fchown() и далее close(). Все это скрыто базовым уровнем библиотеки.Составные сообщения
Как выясняется, концепция составных сообщений полезна не только для экономии ресурсов вследствие уменьшения числа сообщений (как в случае с chown()
, см. выше). Она также критически важна для обеспечения атомарности операций.