[ はじめに | 排他処理 | 共有メモリ | リクエストの処理 | C++ | デバッグ | 参考文献 ]
このページでは,私が mod_uploader の開発の過程で得た Tips について紹介しています.
他のプロセス及びスレッドに対して排他処理を行うには apr_global_lock_t が,他のスレッドに対して排他処理を行うには apr_thread_mutex_t が利用できます.
基本的には,次の手順で使用します.
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_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_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));
/* 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 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 を使うのが お薦めです.