командами UNIX, которые ничего не подозревают о существовании нового сервиса:
# cat /dev/prior
10
# nice -n-5 cat /dev/prior
15
# nice -n-19 cat /dev/prior
29
Вот здесь и проявляется исключительная мощь техники написания менеджера ресурса: созданная минимальными средствами серверная служба «камуфлирует» специфичный QNX-механизм передачи сообщений микроядра под стандартные POSIX-запросы к файловой системе (open()
, read()
и т.д.), и стандартные команды UNIX «не видят» отличий новой серверной службы от стандартных файлов (устройств) UNIX. Вот для достижения такой полной совместимости с «привычками» команд UNIX и созданы «на всякий случай» те особенности формата, возвращаемого запросами read()
, о которых упоминалось выше.
Теперь разработка, например драйвера некоторого специфичного устройства, перемещается из области шаманства «системного программиста» в область деятельности проблемного программиста, да и выполняется привычными высокоуровневыми инструментальными средствами, например С++.
Пользуясь случаем, именно здесь уместно на примере созданного менеджера ресурсов продемонстрировать гибкость микроядерной архитектуры и техники менеджера ресурса, а заодно убедиться, что наследование приоритетов (критически важное свойство для систем реального времени) сохраняется при запросе к удаленному менеджеру ресурса, запущенному на другом узле сети (имя узла — rtp
):
# on -frtp prior &
# ls -l /net/rtp/dev/pr*
nrw-rw--rw- 1 root root 0 Dec 18 17.09 /net/rtp/dev/prior
# nice -n-5 cat /net/rtp/dev/prior
15
# nice -n-19 cat /net/rtp/dev/prior
29
Многопоточный менеджер
Следующим шагом развития техники менеджера ресурсов является многопоточный менеджер. Фактически это объединение техники менеджера ресурсов с динамическим пулом потоков, рассмотренным выше.
Реальный работающий многопоточный менеджер с сопутствующим ему обстоятельным обсуждением приводился нами в книге [4] в главе «Драйверы». Мы не станем полностью приводить здесь этот достаточно объемный текст, поскольку он отличается от ранее показанного однопоточного менеджера только несколькими строками после вот этого оператора регистрации префикса имени менеджера:
// здесь создается путевое имя для менеджера
id = resmgr_attach(dpp, &resmgr_attr, '/dev/prior',
_FTYPE_ANY, 0, &connect_funcs, &io_funcs, &attr);
if (id == -1)
perror('attach name'), exit(EXIT_FAILURE);
Вот те несколько строк, которые, собственно, и превращают однопоточный менеджер в многопоточный:
...
thread_pool_attr_t pool_attr;
memset(&pool_attr, 0, sizeof pool_attr);
pool_attr.handle = dpp;
// это всегда остается так ...:
pool_attr.context_alloc = dispatch_context_alloc;
pool_attr.block_func = dispatch_block;
pool_attr.handler_func = dispatch_handler;
pool_attr.context_free = dispatch_context_free;
// численные параметры пула:
pool_attr.lo_water = 2;
pool_attr.hi_water = 6;
pool_attr.increment = 1;
pool_attr.maximum = 50;
thread_pool_t *tpp;
// флаг создания пула, который может принимать значения:
// POOL_FLAG_EXIT_SELF, POOL_FLAG_USE_SELF или,
// наконец, 0 и который определяет, что будет
// происходить дальше с вызывающим потоком...
if ((tpp = thread_pool_create(&pool_attr, POOL_FLAG_EXIT_SELF)) == NULL)
perror('create pool'), exit(EXIT_FAILURE);
thread_pool_start(tpp);
...
}
Но всю эту последовательность действий мы уже видели ранее при описании динамического пула потоков, и какого-то специфического отношения к созданию именно менеджера ресурса она не имеет.
Вот такими элементарными манипуляциями мы превращаем менеджер ресурса (практически любой менеджер!) в многопоточный. С другой стороны, простота трансформации одной формы в другую подсказывает простое и эффективное решение: вначале всегда пишите одно- поточный менеджер, поскольку в отладке и понимании он намного проще, и только потом при необходимости трансформируйте его в многопоточный.
Множественные каналы
Техника написания менеджеров ресурсов в QNX открывает перспективу для простого и ясного написания драйверов системы без необходимости «залезать» в специфические низкоуровневые детали. Тем не менее в описаниях технологии создания менеджеров ресурсов есть один аспект, который имеет непосредственное отношение к синхронизации параллельных ветвей, и нельзя сказать, что этот вопрос не освещен в технической документации, однако его составляющие детали «размазаны» по документации, и общую картину приходится восстанавливать.
Суть вопроса в следующем. Писать менеджер ресурсов как системный драйвер некоторого специфического аппаратного устройства — это удел единиц (на каждое устройство — по одному разработчику! … шутка), но менеджер ресурсов — это прекрасная альтернатива для описания чисто программных «псевдоустройств». Например, это могла бы быть некоторая оконная GUI-подсистема, в которой open()
создает прорисовку окна на экране, write()
вписывает некоторый текст в окно, a read()
считывает из окна текст, вводимый пользователем