[ はじめに | 排他処理 | 共有メモリ | 書式指定 | リクエストの処理 | C++ | デバッグ | 参考文献 ]
このページでは,私が mod_uploader の開発の過程で得た Tips について紹介しています.
他のプロセス及びスレッドに対して排他処理を行うには apr_global_lock_t が,他のスレッドに対して排他処理を行うには apr_thread_mutex_t や apr_thread_rwlock_t が利用できます.
基本的には,次の手順で使用します.
クリティカルセクションの開始/終了の詳細については,上記の関数を呼ぶ だけなので,apr_global_mutex.h のドキュメントを参照してください.関 数の戻り値のチェックだけ気をつけておけば,特につまずく点はないと思い ます.また,apr_global_mutex_t のリソースは Apache の終了時に自動的 に解放されるので,明示的 に apr_global_mutex_destroy を呼ぶ必要はあ りません.
post_config ステージにおい て unixd_set_global_mutex_perms を実行すべ きかどうかは AP_NEED_SET_MUTEX_PERMS が define されているかどうかで判断すれば良いのですが,このマクロは Apache 2.0 系には用意されていません.Apache 2.0 系のモジュールを作成 する場合は次のようにしてモジュール側で define してやる必要があります. (AP_SERVER_MAJORVERSION_NUMBER が Apache 2.1 系のみに存在することを 利用しています)
/* Apache 2.0 系の場合は,AP_NEED_SET_MUTEX_PERMS の define を試みる. */
#ifndef AP_SERVER_MAJORVERSION_NUMBER
#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
#define AP_NEED_SET_MUTEX_PERMS
#endif
#endif
以上をふまえると,モジュールのコードは次のようになります.
#ifndef AP_SERVER_MAJORVERSION_NUMBER
#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
#define AP_NEED_SET_MUTEX_PERMS
#endif
#endif
#include "apr_global_mutex.h"
#ifdef AP_NEED_SET_MUTEX_PERMS
#include "unixd.h"
#endif
typedef struct ServerConfig {
char *mutex_path;
apr_global_mutex_t *mutex;
} sconfig;
static void *create_server_config(apr_pool_t *p, server_rec *s)
{
sconfig *config;
config = (sconfig *)(apr_palloc(p, sizeof(sconfig)));
/* ここで指定されるファイルは,必要に応じて自動的に生成されます. */
/* あらかじめ作成しておく必要はありません. */
config->mutex_path = "/path/to/global/mutex/file";
return config;
}
static int post_config(apr_pool_t *pconf, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
sconfig *config;
void *user_data;
apr_status_t status;
/* post_config は Apache の起動時に二度呼ばれるので,ダミーの */
/* user_data を使って一度目では apr_global_mutex_t を生成しない */
/* ようにする. */
apr_pool_userdata_get(&user_data, USER_DATA_KEY, s->process->pool);
if (user_data == NULL) {
apr_pool_userdata_set((const void *)(1), USER_DATA_KEY,
apr_pool_cleanup_null, s->process->pool);
return OK;
}
config = (sconfig *)(ap_get_module_config(s->module_config, &sample_module));
status = apr_global_mutex_create(&(config->glock), config->glock_path,
APR_LOCK_DEFAULT, pconf);
if (status != APR_SUCCESS) {
return HTTP_INTERNAL_SERVER_ERROR;
}
#ifdef AP_NEED_SET_MUTEX_PERMS
status = unixd_set_global_mutex_perms(config->glock);
if (status != APR_SUCCESS) {
return HTTP_INTERNAL_SERVER_ERROR;
}
#endif
return OK;
}
static void child_init(apr_pool_t *p, server_rec *s)
{
sconfig *config;
config = (sconfig *)(ap_get_module_config(s->module_config, &sample_module));
if (apr_global_mutex_child_init(&(config->glock),
config->glock_path, p) != APR_SUCCESS) {
SERROR("Can not attach to global mutex (%s).", config->glock_path);
return;
}
}
static void register_hooks(apr_pool_t *pool)
{
ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
ap_hook_child_init(child_init, NULL, NULL, APR_HOOK_REALLY_FIRST);
/* (略)*/
}
module AP_MODULE_DECLARE_DATA uploader_module = {
STANDARD20_MODULE_STUFF,
NULL,
NULL,
create_server_config,
NULL,
NULL,
register_hooks
};
次のようにして使用します.
環境によってはスレッドを扱えない場合があるので,マク ロ APR_HAS_THREADS が 0 以外の時だ け apr_thread_mutex_t を使うようにすると良いです.
#include "apr.h"
#if APR_HAS_THREADS
#include "apr_thread_mutex.h"
#endif
/* (略)*/
apr_status_t lock()
{
#if APR_HAS_THREADS
return apr_thread_mutex_lock(mutex);
#else
return APR_SUCCESS;
#endif
}
使用に関しては特につまずく点は無いと思います.apr_thread_mutex.h の ドキュメントを参照してください.
変更はあまりされないけど,頻繁に参照されるデータの保護に使用します. ロックの方法は増えてますが,基本的な使用方法 は apr_thread_mutex_t と同じです.
他のプロセスとメモリの共有を行うには apr_shm_t を利用します.実際に 動くコンパクトなサンプルとしては, mod_shm_counter がお薦めです.
基本的には,次の手順で使用します.
モジュールのコードは次のようになります.
#include "apr_shm.h"
typedef struct ServerConfig {
char *shm_path;
/* 共有メモリ */
apr_shm_t *shm_data;
/* プロセスが実際に読み書きするメモリ */
char *data;
} sconfig;
static void *create_server_config(apr_pool_t *p, server_rec *s)
{
sconfig *config;
config = (sconfig *)(apr_palloc(p, sizeof(sconfig)));
/* ここで指定されるファイルは,必要に応じて自動的に生成されます. */
/* あらかじめ作成しておく必要はありません. */
config->shm_path = "/path/to/shm/file";
config->data_shm = NULL;
return config;
}
static int post_config(apr_pool_t *pconf, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
sconfig *config;
void *user_data;
apr_status_t status;
/* post_config は Apache の起動時に二度呼ばれるので,ダミーの */
/* user_data を使って一度目では apr_shm_t を生成しない */
/* ようにする. */
apr_pool_userdata_get(&user_data, USER_DATA_KEY, s->process->pool);
if (user_data == NULL) {
apr_pool_userdata_set((const void *)(1), USER_DATA_KEY,
apr_pool_cleanup_null, s->process->pool);
return OK;
}
config = (sconfig *)(ap_get_module_config(s->module_config, &sample_module));
/* 1024 バイトの共有メモリを設定 */
status = apr_shm_create(&(config->shm_data), sizeof(char)*1024,
config->shm_path, pconf);
if (status != APR_SUCCESS) {
return HTTP_INTERNAL_SERVER_ERROR;
}
config->data = (char *)(apr_shm_baseaddr_get(config->shm_data);
/* 必要に応じて初期化 */
memset(config->shm_data, 0, sizeof(char)*1024);
return OK;
}
static void child_init(apr_pool_t *p, server_rec *s)
{
sconfig *config;
config = (sconfig *)(ap_get_module_config(s->module_config, &sample_module));
/* この条件が成り立つときのみ attach する. */
if (!config->data_shm) {
if (apr_shm_attach(&(config->data_shm), config->shm_path,
p) != APR_SUCCESS) {
return;
}
}
config->data = (char *)(apr_shm_baseaddr_get(config->data_shm));
}
Apache および APR には,stdio.h や stdarg.h の *printf 関数の代わり に使える次の関数が用意されています.
これらに渡す書式指定 format には,通常の *printf 関数と同じものが使 えますが,数値に関しては注意が必要です.通常,数値に対しては「%d」や 「%lu」等の変換指定を用いますが,多くのアーキテクチャで動作させるこ とを考えた場合,これはあまり望ましくありません.あるアーキテクチャで 正常に動いていても別のアーキテクチャでは,違った値が出力されなかった り,不正なメモリアクセスが発生したりします. APR では,この問題を解決するために apr.h で次のような変換指定文字マ クロが定義されています.
これらを用いることで,特定のアーキテクチャに依存しない安全なコードに することができます. たとえば, apr_size_t, apr_off_t や apt_time_t を出力する際は,次のようにし ます.
apr_size_t size;
apr_off_t offset;
apr_time_t time;
const char *str;
/* (略)*/
str = apr_psprintf(pool,
"size = %" APR_SIZE_T_FMT ", "
"offset = %" APR_OFF_T_FMT ", "
"time = %" APR_UINT64_T_FMT,
size, offset, time);
Apache 2.x のコンテンツハンドラで POST されたデータを読み込むには次 の二つの方法があります.
原因は調べ切れていないのですが,後者の方法を使った場合,POST された データのサイズに比例した(大体 1/40)のメモリをApache 内部で消費してし まうようです.巨大なデータを扱うモジュールを作成する場合は注意してく ださい.
Apche のモジュールを C++ で開発する場合,いくつか注意点があります.基本的な物については, Apache API C++ Cookbook を参照してください.Apache 1.x 向けに書かれた物ですが, ほとんどの項目はそのまま Apache 2.x にも使えます. 以下では,その他の注意点について説明します.
C++ でモジュールを書いていて,「INT64_C が未定義です.」みたいなエラー が出た場合は,マク ロ __STDC_CONSTANT_MACROS を define してや りましょう.
参考: /usr/include/stdint.h
/* The ISO C99 standard specifies that in C++ implementations these
should only be defined if explicitly requested. */
#if !defined __cplusplus || defined __STDC_CONSTANT_MACROS
... (INT64_C の define)
#endif
Apache のモジュールを開発する際の便利なデバッグ方法を紹介します.
モジュールを開発する場合,APR Pool のデバッグ機能を有効にした Apache を用いることで,メモリ関連のバグを早い段階で見つけることがで きます.Unix の場合は次のようにすることで Debug ビルドが行えます. (参考: APR_POOL_DEBUG is functioning again WAS: RE: Don't use APR_POOL_DEBUG )
$ CPPFLAGS=-DAPR_POOL_DEBUG ./configure ...
$ make
WIndows の場合は,次のようにします. (参考: Compiling Apache for Microsoft Windows )
> vsvar32.bat
> nmake /f Makefile.win _apached
メモリ関連のバグの場合,デバッグオプション(-g) をつけてコンパイルし, Valgrind 上で Apache を実行するのが手軽です.Apache にデバッグモード (-X) を指定するのがポイントです.
$ valgrind -v --leak-check=yes --show-reachable=yes --tool=memcheck /usr/sbin/apache2 -X -f /path/to/httpd.conf 2>&1 | tee valgrind.log
Apache をデバッグモードで実行した場合,標準出力への出力がコンソール に表示されるので,いつもの(?) printf デバッグも行えます.
普通の Windows アプリケーションの場合と同様,デバッグオプション(/Od /MDd /GS /RTCsu /Zi) をつけてコンパイルして,VisualStudio を使うのが お薦めです.