Содержание


Apache 2

Часть 8. Apache Portable Runtime (APR)

Comments

Серия контента:

Этот контент является частью # из серии # статей: Apache 2

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Apache 2

Следите за выходом новых статей этой серии.

APR - это автономная библиотека, используемая ядром. Она разработана отдельно от ядра и ее назначение более широкое, чем применение только в контексте веб-сервера. В частности, на базе APR построен проект Subversion. В этом документе будет обсуждаться применение APR в модулях Apache.

Основная цель APR - создание кросс-платформенного слоя для приложений. Работа с файловой системой, сетевое программирование, управление процессами/потоками, управление памятью реализовано на низком уровне. Модули Apache используют APR и не обращаются напрямую к системным функциям, независимо от используемой платформы.

1. Обзор APR

Ядром APR являются пулы (pool), управляющие ресурсами Apache. Полный список включает более 30 модулей APR, среди которых:

	apr_allocator
	apr_atomic
	apr_dso
	apr_env      
	apr_errno
	apr_file_info
	apr_file_io
	apr_fnmatch
	apr_general
	apr_getopt
	apr_global_mutex
	apr_hash
	apr_inherit
	apr_lib
	apr_mmap
	apr_network_io
	apr_poll
	apr_pools
	apr_portable
	apr_proc_mutex
	apr_random
	apr_ring          
	apr_shm           
	apr_signal        
	apr_strings       
	apr_support       
	apr_tables        
	apr_thread_cond   
	apr_thread_mutex  
	apr_thread_proc   
	apr_thread_rwlock 
	apr_time          
	apr_user          
	apr_version       
	apr_want

APR-UTIL - или APU - это набор утилит, включающий модули:

	apr_anylock        
	apr_base64         
	apr_buckets        
	apr_date           
	apr_dbd            
	apr_dbm            
	apr_hooks          
	apr_ldap           
	apr_ldap_init      
	apr_ldap_option    
	apr_ldap_url       
	apr_md4            
	apr_md5            
	apr_optional       
	apr_optional_hooks 
	apr_queue          
	apr_reslist        
	apr_rmm

Все публичные интерфейсы имеют префикс apr_ - это внешний namespace. Внутри модуля может быть вложенный, например, для модуля apr_dbd префикс будет иметь вид apr_dbd_.

Публичные функции декларируются с помощью макроса APR_DECLARE:

	APR_DECLARE(apr_status_t) apr_initialize(void);

Функция возвращает тип apr_status_t, например APR_SUCCESS:

apr_status_t rv;
...
rv = apr_do_something(... args ...);
if (rv != APR_SUCCESS) 
{
    /* log an error */
    return rv;
}

Функции могут возвращать не только целые значения, но также строки (char* или const char*), а также void* или void.

В силу разнородности операционных систем в APR по умолчанию отключены некоторые особенности, такие, например, как многопоточность. Для их явного включения на этапе компиляции необходимо включить макрос APR_HAS_:

#if APR_HAS_THREADS
    rv = apr_thread_mutex_lock(mutex);
    if (rv != APR_SUCCESS) {
        /* Log an error */
        /* Abandon critical operation */
    }
#endif

2. Управление ресурсами

Пул (pool) - фундаментальный блок, который является сердцевиной APR, основой для управления ресурсами. Пул может выделить память напрямую или косвенно, гарантируя ее освобождение в нужный момент. Пул может управлять не только памятью, но также файловыми дескрипторами и мьютексами. В обычной практике программирования, когда вы выделяете ресурс, вы должны позаботиться о его закрытии после того, как он будет использован, например:

char* buf = malloc(n);
... check buf is non null ...
... do something with buf ...
free(buf);

или

FILE* f = fopen(path, "r");
... check f is non null ...
... read from f ....
fclose(f);

Это простой пример. В реальной жизни сервера, когда не определен момент выделения ресурса, область видимости ресурса, его очистка - дело нетривиальное.

Модель с концепцией конструкторов/деструкторов в данной архитектуре не проходит. Приходится учитывать множество факторов: когда выделяются ресурсы, как они распределяются между объектами и т.д.

Для таких высокоуровневых языков, как lisp и java, основным механизмом управления ресурсов является т. н. garbage collector (gc). Он снимает с программиста ответственность за управление ресурсами и перекладывает ее на сам язык. У gc есть, конечно, и негативные моменты, такие как, например, невозможность управлять временем жизни выделенных объектов.

Пулы в APR реализуют альтернативную модель управления ресурсами. Помимо того, что они освобождают программиста от заботы про очистку ресурсов, они дают возможность контролировать время жизни ресурсов.

Основная парадигма звучит так: где бы вы не выделили ресурс, регистрируйте его с помощью пула. Этот пул будет сам очищать все свои ресурсы, когда истечет его собственное время жизни. Программисту остается только выбрать правильный пул.

Вместо традиционного

	mytype* myvar = malloc(sizeof(mytype));

мы делаем

	mytype* myvar = apr_palloc(pool, sizeof(mytype));

Причем второй вариант оказывается быстрее первого на большинстве платформ.

В качестве примера можно привести манипуляцию со строками - аналог для стандартной функции sprintf(), где даже не нужно знать размер строки:

	char* result = apr_psprintf(pool, fmt, ...);

APR включает базовые функции для управления памятью, файлами, сокетами, мьютексами. Можно даже их не использовать, как в данном случае, нужно только зарегистрировать объект в пуле для последующей очистки:

	mytype* myvar = malloc(sizeof(mytype));
	apr_pool_cleanup_register(pool, myvar, free,   apr_pool_cleanup_null);

Т. е. ресурс, выделенный стандартным образом, может быть, тем не менее, также очищен пулом, причем автоматически.

Очистка ресурса может быть явной или неявной, например, освободить какой-то ресурс прежде, чем будет обработан запрос, т. е раньше, чем это сделает соответствующий пул. Для этого есть функция - apr_pool_cleanup_kill. При ее вызове происходит раз-регистрация очистки ресурса в пуле. Рассмотрим следующий пример. Выделяем ресурсы:

	my_type* my_res = my_res_alloc(args);

Очищаем:

	apr_pool_cleanup_register(pool, my_res,  my_res_free, apr_pool_cleanup_null);
	rv = my_res_free(my_res);
	apr_pool_cleanup_kill(pool, my_res, my_res_free);

Последние 3 строки можно заменить на:

	apr_pool_cleanup_run(pool, my_res, my_res_free);

3. Пулы

Есть несколько типов пулов для разных ресурсов. Каждый такой пул привязан к определенной базовой структуре, и время жизни этой структуры есть время жизни такого пула. Основные базовые пулы:

  1. Пул запросов - время его жизни равно времени жизни запроса.
  2. Пул процессов - время его жизни равно времени жизни сервера.
  3. Пул коннектов.
  4. Конфигурационный пул.

Доступ к первым трем выполняется через request->pool, process->pool, connection->pool, к четвертому - через process->pconf. Пул процессов самый долгоживущий. Пул коннектов имеет время жизни коннекта, который в свою очередь может иметь несколько запросов. Он полезен в фильтрах, где объект запроса может быть не определен.

Все хуки имеют похожий шаблон:

int my_func(request_rec* r) 
{
	...
}

Можно использовать пул из r->pool. Это самое подходящее место для выделения ресурсов при обработке запроса. Пул процессов доступен из r->server->process->pool, он может быть использован для кеширования ресурсов, которые могут понадобиться при обработке запросов. Пул коннектов доступен через r->connection->pool.

У пулов есть свои ограничения:

  1. Если время жизни объекта не соответствует времени жизни стандартного объекта Apache, это требует дополнительных усилий.
  2. Выделение ресурсов через пул не есть thread safe. Это связано с тем, что речь идет о пулах коннектов и пулах запросов, которые обрабатываются потоками приватно.
  3. Пул не возвращает память операционной системе, он ее повторно использует после очистки. Поэтому, если речь идет о выделении очень больших кусков памяти, имеет смысл использовать стандартный malloc.

4. Встроенные типы данных

Обработка строк находится в apr_strings. Тут находятся простые строковые функции: сравнение, копирование, конкатенация, форматные функции типа sprintf, конвертация в другие типы данных. Управление строк привязано к пулам. Например, конкатенация произвольного числа строк:

	result = apr_pstrcat(pool, str1, str2, str3, ..., NULL);

В модуле apr_time реализован микро-секундный таймер. Базовый тип - 64-битное целое apr_time_t.

Есть макросы совместимости:

/** @return apr_time_t as a second */
#define apr_time_sec(time) ((time) / APR_USEC_PER_SEC)
/** @return a second as an apr_time_t */
#define apr_time_from_sec(sec) ((apr_time_t)(sec) * APR_USEC_PER_SEC)

Имеется набор стандартных временных функций типа sleep.

В APR имеется свой собственный набор типов данных:

  1. apr_table - таблицы и массивы.
  2. apr_hash - хеш-таблицы.
  3. apr_queue - FIFO очереди.
  4. apr_ring - кольцевые структуры.

Базовый тип для массивов - apr_array_header_t, который может хранить другие объекты или указатели. Массив может расти динамически. Операции с массивом:

/* Allocate an array of type my_type */
apr_array_header_t* arr = apr_array_make(pool, sz, sizeof(my_type));
/* Allocate an uninitialized element on the array*/
my_type* newelt = apr_array_push(arr);
/* Now fill in the values of elt */
newelt->foo = abc;
newelt->bar = "foo";
/* Pop the last-in element */
my_type* oldelt = apr_array_pop(arr);
/* Iterate over all elements */
for (i = 0; i < arr->nelts; i++) {
    /* A C++ reference is the clearest way to show this */
    my_type& elt = arr->elts[i];
}

Тип apr_table_t лежит в основе таблиц: массивов для хранения пар ключ/значение. В таблицу можно добавлять элементы несколькими способами, удалять элементы (довольно медленно), искать, делать итерацию, удалять дубликаты. Таблицы нечувствительны к регистру (в отличие от APR hash table):

/* Allocate a new table */
apr_table_t* table = apr_table_make(pool, sz);
/* Set a key/value pair */
apr_table_setn(table, key, val);
/* Retrieve an entry */
val = apr_table_get(table, key);
/* Iterate over the table (see Chapter 5) */
apr_table_do(func, rec, table, NULL);
/* Clear the table */
apr_table_clear(table);
/* Merge tables */
newtable = apr_table_overlay(pool, table1, table2);
/* Prune duplicate entries */
apr_table_compress(table, flags);

Таблицы идеально подходят для манипуляций с такими объектами, как HTTP заголовки или переменные окружения.

Тип apr_hash_t аналогичен предыдущему, но он более низкоуровневый. У него есть два отличия:

  1. Ключи и значения могут быть любого типа, они чувствительны к регистру.
  2. Более эффективны в плане динамического роста.

Наиболее часто используемые операции для этого типа - вставка и поиск:

apr_hash_t* hash = apr_hash_make(pool);
/* key and value are pointers to arbitrary data types */
apr_hash_set(hash, key, sizeof(*key), value);
value = apr_hash_get(hash, key, sizeof(*key));

apr_queue_t применяется в многопоточном окружении и используется для кооперации работы нескольких тредов. Очередь может блокировать вставку/удаление элементов.

Тип APR_RING - это коллекция макросов, которая реализует циклический двойной связный список. Этот тип в частности используется в конструкции, называемой bucket brigade. bucket - это элемент кольца (корзина), brigade - само кольцо:

struct apr_bucket 
{
    /** Links to the rest of the brigade */
    APR_RING_ENTRY(apr_bucket) link;
    /** and, of course, the bucket's data fields */
};

/** список */
struct apr_bucket_brigade 
{
    /*  Кольцо прикрепляется к пулу.
         В случае, когда кольцо убивается раньше, чем это делает сам пул,
         нужно будет позаботиться о специальной kill-функции
     */
    apr_pool_t *p;
    APR_RING_HEAD(apr_bucket_list, apr_bucket) list;
    /** The freelist from which this bucket was allocated */
    apr_bucket_alloc_t *bucket_alloc;
};

Buckets Brigades используется в Apache для управления данными, I/O, в фильтрах. Этот двойной связный список не ограничен количеством элементов. В кольце могут храниться различные типы. Имеется ряд встроенных функций:

  1. read - возвращает адрес и размер данных в корзине. В случае, когда для каких-то данных не хватает размера одной корзины, оставшаяся часть будет помещена во вторую корзину.
  2. split - разбивает данные для размещения в две корзины.
  3. setaside - проверка жизненности хранимых данных.
  4. copy - копирует корзину.
  5. destroy - удаляет корзину.

Каждая такая функция имеет свой враппер - apr_bucket_read(), apr_bucket_destroy(). В APR имеется ряд встроенных типов для корзин:

  1. file
  2. pipe
  3. socket
  4. heap - память
  5. mmap - файл
  6. immortal - память
  7. pool
  8. transient
  9. flush - метаданные
  10. EOS - метаданные

Для работы с файловой системой имеется набор функций:

  1. apr_file_io - стандартные файловые операции.
  2. apr_file_info.
  3. apr_fnmatch.
  4. apr_mmap.

Имеется набор функций для управления процессами и потоками:

  1. apr_thread_proc - создание процессов/потоков, поддержка связи parent–child.
  2. apr_signal - управление сигналами.
  3. apr_global_mutex - управление потоковыми блокировками.
  4. apr_proc_mutex - управление блокировками процессов.
  5. apr_thread_mutex / apr_thread_rwlock.
  6. apr_thread_cond.

5. Процессы и потоки

Для управления процессами и потоками в APR есть набор функций, которые имеют специальный префикс:

  1. apr_thread_proc — создание, связи parent–child, wait и т. д.
  2. apr_signal — базовое управление сигналами.
  3. apr_global_mutex — управление глобальными блокировками.

Процессы:

  1. apr_proc_mutex — блокировки для процессов.
  2. apr_shm - управление глобальными сегментами памяти.

Потоки:

  1. apr_thread_mutex / apr_thread_rwlock — блокировки для потоков.
  2. apr_thread_cond - условные переменные.

Заключение

APR выполняет роль платформенно-независимого системного слоя, управляет выделенными ресурсами. Были рассмотрен стиль, пулы, управление ресурсами.

Если вы собираетесь программировать под Apache, вам, прежде всего, нужно смотреть в сторону APR.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=623795
ArticleTitle=Apache 2: Часть 8. Apache Portable Runtime (APR)
publish-date=02082011