Проблемы при создании модуля для Apache 2
Проблема 1: Освобождение ресурсов в момент выгрузки модуля
Можно указать сравнительно большое число прикладных задач, при решении которых разработчик модулей сервера Apache так или иначе сталкивается с необходимостью выделения ресурсов системы и их использования на протяжении всего цикла работы модуля (примером такой задачи вполне может стать необходимость установить соединение с СУБД). В общем случае, при реализации, разработчику может потребоваться способ, позволяющий:
- Провести выделение ресурсов и инициализацию данных в момент загрузки модуля;
- Иметь возможность получать доступ к данным из процедур и функций модуля;
- Корректно провести освобождение ресурсов в момент выгрузки модуля.
В целом, такой способ оказывается сравнительно простым в понимании, но отдельные моменты, связанные с его реализацией, требуют особого внимания.
Выбор пула памяти
Согласно документации, рекомендуемым способом хранения данных и доступа к ним является механизм пулов памяти, поддерживаемый сервером Apache, поэтому из всех возможных вариантов предпочтительно использовать именно этот механизм. Идея, которой следует реализация, весьма проста и заключается в том, чтобы в момент загрузки модуля выделить системные ресурсы и область памяти в пуле, разместить в этой области все необходимые данные, а в момент выгрузки модуля освободить все ресурсы и область памяти пула. При этом нет необходимости создавать дополнительные пулы памяти, поскольку в рамках сервера Apache уже имеется несколько пулов, среди них может быть выбран пул, подходящий для размещения данных модуля с точки зрения следующих очевидных условий:
- Пул обязательно должен существовать в момент загрузки модуля;
- Пул не должен освобождаться во время работы модуля, так как это неизбежно приведет к потере данных;
- Освобождение пула должно производиться до момента выгрузки модуля, в противном случае процедура освобождения ресурсов, являющаяся одной из процедур модуля, к моменту освобождения пула окажется выгруженной из памяти вместе с остальными процедурами и функциями модуля и освобождение ресурсов системы не произойдет (более того, попытка передачи управления по “не действительному” адресу процедуры освобождения ресурсов приведет к некорректному завершению всего процесса сервера Apache).
Вполне возможно, что выбор подходящего пула может оказаться не однозначным в существующих и последующих версиях сервера Apache, поэтому вопрос выбора остается на усмотрение разработчика.
Проблема 2: Двукратная загрузка модулей Apache
Существенной особенностью работы сервера Apache с модулями является их двукратная загрузка. При первоначальном запуске, сервер Apache загружает все модули (имеются в виду модули, указанные в файле конфигурации с помощью соответствующей директивы LoadModule), при этом производятся все необходимые вызовы функций и процедур, начиная с процедуры регистрации хуков вплоть до вызова самих функций хуков. Далее следует выгрузка всех успешно загруженных модулей, опять же сопровождаемая вызовами всех необходимых процедур и функций. Именно таким образом сервер Apache осуществляет проверку того, что все модули корректно выполняют загрузку и выгрузку, после чего повторно выполняет загрузку всех модулей и только после этого продолжает работу.
Очевидным является то, что выделение ресурсов во время первой загрузки модуля лишено всякого смысла, поскольку в скором времени произойдет выгрузка модуля. В момент первой загрузки следует лишь сохранить информацию о том, что она имела место, с тем, чтобы выделение ресурсов и инициализацию данных провести при второй загрузке. Реализовать сохранение информации о первой загрузке можно различными способами, в том числе и с помощью пулов памяти следующим образом: при первой загрузке модуля в одном из пулов памяти сервера Apache разместить некоторый флаг инициализации, наличие которого при второй загрузке будет свидетельствовать о том, первая загрузка уже имела место и данная загрузка является второй по счету. При этом следует быть аккуратным в выборе пула для размещения флага, и, прежде всего, убедиться в том, что он не освобождается вплоть до момента второй загрузки модуля, в противном случае, флаг инициализации будет утерян. Следует так же отметить, пул размещения флага, не обязательно должен совпадает с пулом, который будет использоваться для хранения данных модуля.
Решение. Модуль mod_time
В качестве реализации, рассмотрим простейший модуль (исходный код модуля), представляющий собой выходной фильтр, на примере которого проиллюстрируем приемы, изложенные в двух предыдущих разделах. Из соображений простоты изложения максимально упростим работу модуля, единственной задачей которого станет определение системных даты и времени в момент загрузки и вывод некоторого информационного сообщения, содержащего полученные дату и время, в конце всех html-страниц.
Объявление структуры модуля является стандартным, причем существенной в данном случае является только поле функции регистрации хуков.
// объявляем модуль
module AP_MODULE_DECLARE_DATA time_module =
{
STANDARD20_MODULE_STUFF,
NULL, /* создание конфигурации для директории */
NULL, /* слияние конфигурации для директорий */
NULL, /* создание конфигурации сервера */
NULL, /* слияние конфигураций сервера */
NULL, /* cmds */
hook_registration /* функция регистрации hook’ов */
};
module AP_MODULE_DECLARE_DATA time_module =
{
STANDARD20_MODULE_STUFF,
NULL, /* создание конфигурации для директории */
NULL, /* слияние конфигурации для директорий */
NULL, /* создание конфигурации сервера */
NULL, /* слияние конфигураций сервера */
NULL, /* cmds */
hook_registration /* функция регистрации hook’ов */
};
В рамках функции регистрации хуков hook_registration, выполняется регистрация выходного фильтра, который будет осуществлять вывод информационного сообщения,
// регистрация выходного фильтра
ap_register_output_filter(“time_output_filter”,
time_output_filter, NULL,
AP_FTYPE_RESOURCE);
ap_register_output_filter(“time_output_filter”,
time_output_filter, NULL,
AP_FTYPE_RESOURCE);
и регистрация хука, выполняемого после завершения обработки файла конфигурации
// регистрация post config hook
ap_hook_post_config(post_config_hook, NULL, NULL, APR_HOOK_LAST);
ap_hook_post_config(post_config_hook, NULL, NULL, APR_HOOK_LAST);
Предположим, что объявлены следующие константы
// ключ для флага инициализации
#define MODULE_INITIALIZATION_FLAG_KEY “mod_time:initialization_flag_key”
// указатель для ассоциации с ключом флага
#define MODULE_INITIALIZATION_POINTER 0xFFFFFFFF
#define MODULE_INITIALIZATION_FLAG_KEY “mod_time:initialization_flag_key”
// указатель для ассоциации с ключом флага
#define MODULE_INITIALIZATION_POINTER 0xFFFFFFFF
Особо отметим две функции работы с пулами памяти, которые будут использоваться далее и представляют особый интерес:
- apr_pool_userdata_set - позволяет ассоциировать строку (ключ) с указателем;
- apr_pool_userdata_get - позволяет получить указатель по заданной строке (ключу).
Фактически, совокупность этих двух функций реализует функциональность хеша.
Идея использования этих функций такова: при каждой загрузке модуля, с помощью функции apr_pool_userdata_get производиться попытка получить указатель для ключа MODULE_INITIALIZATION_FLAG_KEY, далее
- Если такого ключа не существует - данная загрузка является первой по счету, и необходимо с помощью функции apr_pool_userdata_set произвести ассоциацию ключа MODULE_INITIALIZATION_FLAG_KEY с указателем MODULE_INITALIZATION_POINTER, значение которого не существенно и может быть выбрано произвольным образом;
- Если такой ключ существует - ассоциация была произведена и данная загрузка является второй по счету.
В данном случае, наличие и отсутствие ключа MODULE_INITIALIZATION_FLAG_KEY позволяет отличить первую загрузку модуля от второй (ассоциация этого ключа выступает в роли флага первой загрузки, о котором упоминалось в предыдущем разделе).
Таким образом, в функции post_config_hook, которая выполняется при каждой загрузке модуля, прежде всего, необходимо проверить наличие ключа флага
// извлечение указателя для ключа флага инициализации
apr_pool_userdata_get(&pData, MODULE_INITIALIZATION_FLAG_KEY,
s->process->pool);
apr_pool_userdata_get(&pData, MODULE_INITIALIZATION_FLAG_KEY,
s->process->pool);
Если ключа не существует - необходимо произвести ассоциацию ключа
// ассоциация указателя с ключом флага инициализации
apr_pool_userdata_set((LPVOID)MODULE_INITIALIZATION_POINTER,
MODULE_INITIALIZATION_FLAG_KEY,
apr_pool_cleanup_null,
s->process->pool);
apr_pool_userdata_set((LPVOID)MODULE_INITIALIZATION_POINTER,
MODULE_INITIALIZATION_FLAG_KEY,
apr_pool_cleanup_null,
s->process->pool);
(в данной реализации для хранения флага первой загрузки используется пул s->process->pool).
Если ключ существует - необходимо выделить память в подходящем пуле
// выделение памяти под структуру данных модуля
pData = apr_palloc(s->process->pconf, sizeof(_MODULE_INTERNAL_DATA));
pData = apr_palloc(s->process->pconf, sizeof(_MODULE_INTERNAL_DATA));
и произвести ассоциацию ключа MODULE_INTERNAL_DATA_KEY
// ключ для данных
#define MODULE_INTERNAL_DATA_KEY “mod_time:internal_data_key”
#define MODULE_INTERNAL_DATA_KEY “mod_time:internal_data_key”
c указателем выделенной памяти
// ассоциация указателя с ключом данных
apr_pool_userdata_set(pData, MODULE_INTERNAL_DATA_KEY,
module_internal_data_cleanup,
s->process->pconf);
apr_pool_userdata_set(pData, MODULE_INTERNAL_DATA_KEY,
module_internal_data_cleanup,
s->process->pconf);
В данном случае для хранения данных модуля (структуры типа _MODULE_INTERNAL_DATA) выбран пул s->process->pconf и он отличен от пула хранения флага первой загрузки. Если ассоциация выполнена успешно, при необходимости можно так же зарегистрировать функцию освобождения ресурсов
// регистрация функции освобождения ресурсов
apr_pool_cleanup_register(s->process->pconf, (LPVOID)pData,
module_internal_data_cleanup,
apr_pool_cleanup_null);
apr_pool_cleanup_register(s->process->pconf, (LPVOID)pData,
module_internal_data_cleanup,
apr_pool_cleanup_null);
которая будет вызвана при освобождении пула s->process->pconf с указанным при регистрации аргументом pData
apr_status_t module_internal_data_cleanup(void* pData)
{
…
return APR_SUCCESS;
}
{
…
return APR_SUCCESS;
}
Последним шагом в приготовлении к работе модуля является выполнение функции инициализации данных системными датой и временем в процедуре инициализации
initialize_module_internal_data((_MODULE_INTERNAL_DATA*) pData);
Во время работы модуля, периодически будут происходить обращения к зарегистрированной ранее функции фильтра time_output_filter. Для того чтобы иметь возможность получить доступ к данным модуля из функции time_output_filter, следует воспользоваться упомянутой ранее функцией apr_pool_userdata_get:
apr_status_t time_output_filter(ap_filter_t *filter,
apr_bucket_brigade *brigade_of_buckets)
{
…
apr_pool_userdata_get(&pData, MODULE_INTERNAL_DATA_KEY,
filter->r->server->process->pconf);
…
}
apr_bucket_brigade *brigade_of_buckets)
{
…
apr_pool_userdata_get(&pData, MODULE_INTERNAL_DATA_KEY,
filter->r->server->process->pconf);
…
}
Комментарий от Akiv — Март 24, 2006 @ 11:07 am
Комментарий от Marat — Ноябрь 23, 2007 @ 5:25 pm
Комментарий от организация праздника — Май 3, 2008 @ 1:28 am
__________________
http://footballnotes.ru/
Комментарий от андрюха — Август 29, 2008 @ 1:45 pm
Комментарий от Тимур — Октябрь 21, 2008 @ 3:39 pm
Комментарий от Тимур — Октябрь 21, 2008 @ 3:40 pm
Комментарий от Мария — Октябрь 21, 2008 @ 3:40 pm
Комментарий от Мартин — Октябрь 21, 2008 @ 3:41 pm
Комментарий от http://danetnavern0.narod.ru — Октябрь 21, 2008 @ 4:14 pm
Комментарий от kaa — Ноябрь 28, 2008 @ 7:42 pm
Комментарий от Heap — Январь 17, 2009 @ 3:08 am