_RESMGR_ERRNO(errno)
(Устаревшее.) Данное возвращаемое значение использовалось для «инкапсуляции» значения errno в возвращаемое сообщением значение. Например, если бы клиент выдал запрос open() (по записи — прим. ред.) устройству, доступному только для чтения, корректно было бы возвратить код ошибки EROFS. Поскольку данный способ сделать это считается устаревшим, вы можете возвратить код ошибки непосредственно (например, при помощи return (EROFS); вместо громоздкого _RESMGR_ERRNO(EROFS);).
_RESMGR_PTR(ctp, addr, len)
Это макрос для удобства. Он берет указатель на контекст ctp и заполняет его первый элемент IOV адресом addr и длиной len, а затем возвращает библиотеке эквивалент _RESMGR_NPARTS(1). Это может быть полезно для функций, возвращающих одноэлементные IOV.
Блокировки, разблокировки и обработка составных сообщений
Мы видели клиентский взгляд на составные сообщения, когда рассматривали функцию readblock() (в параграфе «Составные сообщения»). Клиент мог атомарно создать сообщение, которое содержало бы несколько «подсообщений» администратору ресурсов — в нашем примере это были сообщения, соответствующие функциям lseek() и read(). С точки зрения клиента две (или более) функций были как минимум атомарно переданы (и, вследствие самой сути обмена сообщениями, будут атомарно приняты администратором ресурсов). О чем мы еще не говорили, так это о том, как мы сможем гарантированно обеспечить атомарность обработки этих сообщений.
Данные рассуждения применимы не только к составным сообщениям, но и ко всем сообщениям, принимаемым библиотекой администратора ресурсов. Первое, что делает библиотека администратора ресурсов, — она блокирует атрибутную запись, соответствующую ресурсу, используемому полученным сообщением. Затем она обрабатывает одно или более «подсообщений», содержащихся в полученном сообщении. Затем она снова разблокирует атрибутную запись.
Это гарантирует, что поступающие сообщения обрабатываются атомарно, поскольку никакой другой поток администратора ресурсов (в случае многопоточного администратора, конечно) не может «влезть» и изменить ресурс, пока наш поток этот ресурс использует. Без блокировок два клиентских потока могли бы оба выдать то, что, по их мнению, являлось бы атомарным составным сообщением (скажем, пару «lseek() — read()»). Поскольку администратор ресурсов мог выделить на обработку этих сообщений два различных потока, эти потоки могли бы в произвольном порядке вытеснять друг друга, и их lseek() могли бы друг другу помешать. Блокировки же позволяют это предотвратить, потому что каждое сообщение, получающее доступ к ресурсу, обрабатывается целиком и атомарно.
Блокировка и разблокировка ресурса выполняются вспомогательными функциями по умолчанию (iofunc_lock_ocb_default() и iofunc_unlock_ocb_default()), которые размещаются в таблице функций ввода/вывода в полях lock_ocb и unlock_ocb соответственно. Вы можете, конечно, переназначить эти функции, если хотите выполнить в процессе блокировки и разблокировки какие-либо дополнительные действия.
Заметьте, что ресурс разблокируется перед вызовом io_close(). Это необходимо, поскольку функция io_close() освободит OCB, что автоматически сделает недействительным указатель на атрибутную запись, а блокировка хранится именно там!
Замечания о функциях установления соединения
Перед тем как углубиться в отдельные сообщения, однако, есть смысл подчеркнуть, что для всех функции установления соединения структура сообщений идентична (взято из <sys/iomsg.h>, с небольшими изменениями):
struct _io_connect {
// Для внутреннего использования
uint16_t type;
uint16_t subtype;
uint32_t file_type;
uint16_t reply_max;
uint16_t entry_max;
uint32_t key;
uint32_t handle;
uint32_t ioflag;
uint32_t mode;
uint16_t sflag;
uint16_t access;
uint16_t zero;
uint8_t eflag;
// Для конечного пользователя
uint16_t path_len;
uint8_t extra_type;
uint16_t extra_len;
char path[1];
};
Вы заметите, что я разделил структуру struct _io_connect на две части, часть «Для внутреннего использования» и часть «Для конечного пользователя».
Поля для внутреннего использования
Первая часть состоит из полей, которые библиотека администратора ресурсов использует для:
• определения типа сообщения, полученного от клиента;
• проверки сообщения на достоверность (не является ли оно дезинформацией);
• отслеживания режима доступа (используется вспомогательными функциями).
Для простоты я бы рекомендовал вам всегда применять вспомогательные функции (из семейства iofunc_*_default()) во всех функциях установления соединения. Эти функции возвратят вам признак успешного/неудачного завершения, после чего вы сможете использовать в функции установления соединения «поля для конечного пользователя».
Поля для конечного пользователя
Вторая половина полей непосредственно относится к вашей реализации функции установления соединения:
path_len и path
Имя пути (и его длина), которые являются операндом (то есть это имя пути, над которым производится действие).
extra_type и extra_len
Дополнительные параметры (имена путей, например), соответствующее данной функции установления соединения.
Чтобы получить представление о том, как поле path используется в качестве «имени пути, над которым производится действие», давайте рассмотрим что-нибудь типа функции rename(). Эта функция принимает два имени пути: «изначальное» имя пути и «новое» имя пути. Изначальное имя пути передается в path, потому что именно над ним производится операция (это имя файла, подлежащего переименованию). Новое имя пути — аргумент данной операции. Вы увидите, что параметр extra, который передается функции установления соединения, как раз содержит указатель на аргумент операции, в данном случае — на новое имя пути.
(В принятой реализации новое имя пути располагается в памяти непосредственно следом за изначальным (на которое указывает path) с учетом выравнивания, но вам делать на этот счет ничего не надо — параметр extra уже содержит правильный указатель.)
Алфавитный список функций установления соединения и ввода/вывода
В данном разделе в алфавитном порядке приведен список точек входа в функции установления соединения и ввода/вывода, которые вы можете заполнять самостоятельно (эти две таблицы затем передаются функции pathname_attach()). Помните, что если вы просто вызываете функцию iofunc_func_init(), все эти точки входа будут заполнены соответствующими значениями по умолчанию; вам следует переопределять конкретные точки входа только в том случае, если вы собираетесь обрабатывать данное конкретное сообщение самостоятельно. Ниже, в разделе «Примеры», вы увидите несколько примеров общеупотребительных функций.
Поначалу все это может показаться излишне запутанным, но отметим, что на самом деле существуют два разблокирующих вызова — один для функций установления соединения и один для функций ввода/вывода. Все вполне корректно, поскольку отражает то, когда происходит разблокирование. Вариант для функций установления соединения применяется тогда, когда ядро разблокирует клиента немедленно после отправки им сообщения установления соединения. Вариант для функций ввода/вывода применяется тогда, когда ядро разблокирует клиента немедленно после отправки им сообщения ввода/вывода.
Чтобы не путать точки входа в функции-обработчики сообщений с вызовами клиентской Си-библиотеки (например, open()), к именам всех приведенных здесь функций добавлен префикс «io_». Например, обработчик функции установления соединения open() будет называться io_open().
io_chmod()int io_chmod(resmgr_context_t *ctp, io_chmod_t *msg,
RESMGR_OCB_T *ocb)