fcntlでのファイルのロック

Linuxでのプログラムではオープンしているファイルに書き込みや読み込みを行う際にはロックを介さずに行われてします.
ファイルに対して排他アクセスを制御するために,fcntlまたはflockを用いて行われることが多い.

今回はfcntlを用いてファイルに対してロックを行うことを考えていく.
まずmanコマンドでのfcntlはこんな感じ.

#include 
#include
int fcntl(int fd, int cmd, ... /* arg */ );
fcntlはファイルに対してさまざまな操作を行うシステムコールであるため,引数cmdによって実行する操作を指定する.
今回のロックを行う場合には以下のパラメータを指定する.

  • F_GETLK
  • F_SETLK
  • F_SETLKW

これらのパラメータを指定する場合にはfcntlの3番目以降の引数も定まり,プロトタイプは以下の用になる.

int fcntl(int fd, int cmd, struct flock *flock_structure);

ここでflock構造体!?となる.
flock構造体は主に以下のメンバから構成されている.

struct flock{
short l_type;
short l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
};
このflock構造体の各メンバの値によってロックの状態やリージョンを指示していく.
まずメンバl__typeによって以下からロックの種類を指定する.

  • F_RDLCK : 共有ロック
  • F_WLCLK : 排他ロック
  • F_UNLCK : アンロック

l_whence,l_start,l_lenによってロックを行うファイルのリージョンを指定する.
l_whenceはfseekなどにも使われる,SEEK_SET,SEK_CUR,SEEK_ENDを指定.
それぞれの意味はfseek関数を参照.
l_startはl_whenceからのリージョンの相対オフセットを指定.
l_lenはリージョンの長さを指定する(バイト数).
l_pidはロックを保持しているプロセスを調べるために用いられる.

  • F_GETLK

F_GETLKパラメータをfcntlに対して理容すると,flock構造体を用いて指定してたロックの種類とリージョンに対しての情報を得ることが出来る.
具体的には,ロックの種類とリージョンに対して競合するロックが既にされている場合には,flock構造体のメンバl_pidにロックを行っているプロセスのプロセスIDが格納されて返ってくる.

  • F_SETLK

F_SETLKはfcntlの第1引数でしていいたファイルディスクリプタのたいるに対してロックまたはアンロックを試みる.
l_typeでのロック(アンロック)をflock構造体のリージョンに対して試みて,ロックが成功した場合にはfcntlは-1以外の値を返し,失敗下端会いには-1を返す.
F_SETLKはロックに失敗した場合は直ちに関数から戻る.
またl_pidは用いらない.

  • F_SETLKW

F_SETLKWはF_SETLK同じだが,ロックの取得に失敗した場合はロックが取得できるまで,またはシグナルが発生するまで待機を行う.

またロックは対象のディスクリプタをクローズした時に自動的に解除される.

以下はロックのサンプル.
時際にロックがかかっているかは複数のプロセスを実行する必要がある.

#include
#include
#include
#include

const char *file = "test_lock";

int main()
{
int file_desc;
int byte_count;
struct flock region_1;
struct flock region_2;
int res;

file_desc = open(file, O_RDWR | O_CREAT, 0666);
if(!file_desc){
fprintf(stderr, "Unable to open %s for read/write\n", file);
exit(EXIT_FAILURE);
}
region_1.l_type = F_RDLCK;
region_1.l_whence = SEEK_SET;
region_1.l_start = 0;
region_1.l_len = 20;

region_2.l_type = F_WRLCK;
region_2.l_whence = SEEK_SET;
region_2.l_start = 30;
region_2.l_len = 20;

printf("Process %d locking file\n", getpid());
res = fcntl(file_desc, F_SETLK, ®ion_1);
if(res == -1)fprintf(stderr, "Failed to lock region 1\n");
res = fcntl(file_desc, F_SETLK, ®ion_2);
if(res == -1)fprintf(stderr, "Failed to lock region 2\n");

printf("Process %d closing file\n", getpid());
close(file_desc);
exit(EXIT_SUCCESS);
}

最後にfcntlの注意点としてWikipediaに以下のことが記述されている.

プロセスはひとつのファイルに対応するファイル記述子を複数個持つことができる。
このうちのいずれかを使ってfcntlでロックをかけていたとする。
このとき、同じファイルに対応する別のファイル記述子をクローズすると、そのファイル上にそのプロセスが設定した全ロックが解除されてしまう。
また、fcntlのロックは子プロセスに受け継がれない。
このfcntlのクローズに関連した動作は、ライブラリのサブルーチン内でファイルにアクセスすることが多いアプリケーションなどで問題を発生することが多い。

GDBMを使ってみる4

GDBMを使ってみる3 - 日進月歩からの続き.

GDBMに関しては今回が最後かな.

  • データベースのエクスポートとインポート

データベースのバックアップをとるときや,異なるバージョンへ移行や移植する場合等にエクスポートとインポートを利用すれば便利.
だたし,計算機の間でintger型のビット数の違いであったり,バイトオーダーの違いがある場合には注意が必要.

まずエクスポートは以下の関数.

int gdbm_export (
GDBM FILE dbf,
const char *exportfile,
int flag ,
int mode )
第1引数ではエクスポートするデータベース,第2引数ではエクスポート先のファイル名をそれぞれ指定する.
第3引数では‘GDBM_WRCREAT’または‘GDBM_NEWDB’のいずれかを指定する.
GDBM_WRCREATの場合には指定したエクスポート先のファイル名
が既に存在知る場合には何もせず.GDBM_NEWDBの場合は上書きを行う.
第4引数はエクスポート先のファイルのパーミッションを指定.

インポート関数もほぼ同様.

int gdbm_import (
GDBM FILE dbf ,
const char *importfile ,
int flag )
第1引数はインポート先のデータベース,第2引数はインポートするファイルをそれぞれ指定.
第3引数はgdbm_storeのフラグと同じ(重複レコードの処理方法の指定).

  • データベースのファイル記述子の取得

gdbm_open関数を用いてデータベースをオープンした場合に‘GDBM_NOLOCK’を指定してしてデータベースのロックを自動的に行わない場合に,複数のwrite命令から整合性を維持するために,ユーザが独自のロック機構を利用することが考えられる.
このようなことをサポートする為に以下の関数が定義されている.

int gdbm_fdesc (GDBM FILE dbf )
gdbm_fdescを呼び出すことによって指定したデータベースのファイル記述子を戻り値から取得することが可能である.

  • データベースのオプション設定

以下の関数を用いて既にオープンされているデータベースに対して以下の関数によって,さまざまなオプションを設定できる.

int gdbm_setopt (
GDBM FILE dbf ,
int option ,
void *value ,
int size )
第1引数で設定対象のデータベースを指定し,第2引数でオプションの指定をする.
第3引数でオプション依存の値,第4引数で第3引数の値のサイズをそれぞれ指定する.

以下に指定するオプションについて説明していく.

  • GDBM SETCACHESIZE
  • GDBM CACHESIZE

 データベースに対して1度だけ設定することが出来る.
データベース内部のバケットキャッシュのサイズを第3引数(size_t型)で指定する.ちなみに最初は100に設定されている.

  • GDBM GETCACHESIZE

データベース内部のバケットキャッシュのサイズの値を第3引数(size_t型)にセットする.

  • GDBM GETFLAGS

データベースのフラグの情報を第3引数(int型)から得ることが可能.
第3引数はgdbm_openの時に指定するflagsと同じ扱い.

  • GDBM FASTMODE

データベースの同期を行わず高速化を目指すかどうかの指定.
第3引数(int型)によって'TRUE'を指定した場合には同期はぜずに高速に,'FALSE'を指定した場合には同期を行う.

  • GDBM SETSYNCMODE
  • GDBM SYNCMODE

GDBM FASTMODEオプションと逆の効果.
第3引数(int型)によって'TRUE'を指定した場合には同期を行う.

  • GDBM GETSYNCMODE

第3引数(int型)から同期の状態を得ることが出来る.

  • GDBM SETCENTFREE
  • GDBM CENTFREE

(検討中)
中央のフリーブロックプールを利用するかどうか.
利用することによってファイルスペースの再利用が迅速に行えるようになる.
デフォルトではオフとなっている.
第3引数(int型)によって'TRUE'か'FALSE'を指定.

  • GDBM SETCOALESCEBLKS
  • GDBM COALESCEBLKS

(検討中)
空きブロックをマージするかの指定.
設定すると隣接する秋ブロックがマージされるようになるが,
特にGDBM SETCENTFREEと併用して利用すると処理コストが大きくなる.
デフォルトではオフとなっている.
第3引数(int型)によって'TRUE'か'FALSE'を指定.

  • GDBM GETCOALESCEBLKS

第3引数(int型)からマージされている空きブロックの状態を得ることが出来る.

  • GDBM SETMAXMAPSIZE

メモリにmappingされる領域の最大サイズを第3引数(size_t型)で設定する.
実際の値はもっとも近いページの協会に設定される(ページサイズはsysconf(_SC_PAGESIZE)から得られる値).

  • GDBM GETMAXMAPSIZE

メモリにmappingされる領域の最大サイズを第3引数(size_t型)から得る.

  • GDBM SETMMAP

メモリマッピングモードの設定.
第3引数(int型)によって'TRUE'か'FALSE'を設定.

  • GDBM GETMMAP

第3引数(int型)からメモリマッピングモードが設定されているかが確認できる.

  • GDBM GETDBNAME

データベースのディスクファイルの名前を第3引数(char **型)から取得する.
呼び出し元は第3引数に割り当てられた領域を不要になった際に解放しなければならない.



GDBMの使い方に関する記事は今回が最後になります.
いろいろ分かり辛いところはごめんなさい.
間違っているところは教えて下さい.
いつかGDBMのソースコードの解析が出来たらと思っています.

GDBMを使ってみる3

日進月歩の続き.

これまでに記述していない機能について.

  • シーケンシャルアクセス

格納データのキーに対してシーケンシャルアクセスを行う.
シーケンシャルアクセスを行うには以下の2つの関数を用いて行う.

datum gdbm_firstkey (GDBM FILE dbf )
datum gdbm_nextkey (GDBM FILE dbf , datum prev )
まずgdbm_firstkeyはデータベースdbfの最初のキーを取得する.
データベースにデータが無い場合は戻り値datumのdptrフィールドがNULLとなる.
gdbm_nextkeyはデータベースとgdbm_firstkeyやgdbm_nextkeyから得られたキーのdatum構造体を指定する.戻り値は指定したdatum構造体の次のキーを返す.
戻り値のdatum構造体のdptrフィールドがNULLであった場合,すべてのキーデータを読み取ったことを示す.
キーに対応するデータを取得するにはgdbm_fetch関数を利用する.

注意ポイントはgdbm_firstkeyとgdbm_nextkeyから得られたキーのdptrフィールドはmallocを利用して得られた領域であるため不要になった時には解放する必要がある.
以下はマニュアルの使用例.

key = gdbm_firstkey (dbf);
while (key.dptr)
{
datum nextkey;
/* do something with the key */
...
/* Obtain the next key */
nextkey = gdbm_nextkey (dbf, key);
/* Reclaim the memory used by the key */
free (key.dptr);
/* Use nextkey in the next iteration. */
key = nextkey;
}

  • データベースの再編成

データベースに対して削除を行うとデータに対して削除フラグを立てるだけでデータ自体の削除が行われない.
このため,データベースに空き領域が出来てしまう.
この空き領域を切り詰めてデータベースを再編成する関数が以下である.

int gdbm_reorganize (GDBM FILE dbf )
これはPostgreSQLのVACUUM命令と同じ働き.
処理が重いためか,The following function should be used very seldom.とめったに使わない方がいいと言われている.

エラーが起こると戻り値は負値を返し,成功すると'0'を返す.

  • データベースの同期

通常,データベースのような処理のボトルネックとなるのはディスクにアクセスである.
そのためデータベースをオープンする時はGDBM_SYNCフラグを指定しない限りは更新が行われてもメモリ上での更新のみで,すぐにディスクに反映されない.
これはクラッシュ事のデータの安全性が保証できないため,以下の関数で現在の状態をディスクに反映させる.

void gdbm_sync (GDBM FILE dbf )
ちなみにデータベースのクローズを行うときに,自動的に同期処理を行う.
なので処理ののちにデータベースのクローズを行う場合は,この処理は行わなくてよい.

  • エラー文字列表示

C言語のstrerrorと同様の機能.
GDBMのエラーに対しての関数が以下.

const char * gdbm_strerror (gdbm_error errno )
指定したGDBMのエラー番号に対して,エラー番号に対応するエラーメッセージを返す.
呼び出し元で返されたポインタを解放をしてはいけない.
引数として渡すgdbm_error型のerrnoは,通常グローバル変数で設定されているgdbm_errnoを渡す.


今回はこんなとこで...

GDBMを使ってみる2

GDBMを使ってみる1 - 日進月歩の続き.
今回はgdbmのデータベースに対してデータの挿入と読み込みについて考えていく.

まずデータベースとのやり取りにはdatum構造体を用いる.
定義は以下.

typedef struct {
char *dptr;
int dsize;
} datum;
keyとvalue共にこの構造体を用いてやりとりが行われる.

  • データの挿入

次にデータの挿入を考えていく.
データの挿入は以下の関数を通して行う.

int gdbm_store (
GDBM FILE dbf,
datum key,
datum content,
int flag )
引数dbfデータを挿入するデータベースを指定.
key, contentは挿入データのkeyとkeyに結びつけるvalueの値をdatum構造体を用いてそれぞれ指定する.
flagの値にはGDBM_REPLACEまたはGDBM_INSERTを指定する.
既にKey値が重複するデータがあった場合の動作が異なり,
GDBM_REPLACEを指定したときは上書きを行い,一方GDBM_INSERTを指定した場合には上書きは行わずエラーを返す.

また関数の戻り値は正常時には'0',GDBM_INSERTによるエラーの場合は'1',それ以外は'-1'を返す.

  • データの検索

次に,データベースから特定のkeyを元にデータを読み出すことを行う.
これは以下の関数で可能

datum gdbm_fetch (GDBM FILE dbf , datum key )
引数のdbfは検索対象のオープン済みのデータベースを指定.
keyには検索を行うキーの値を格納する.
検索を行い一致するデータご見つかった場合にはキーに対応するデータを戻り値として返す.
一方,データが存在しない場合には戻り値のdatum構造体のメンバーdptrにNULLがセットされる.

  • データの確認

指定したキーがデータベース内に存在するかの確認が以下の関数で行える.

int gdbm_exists (GDBM FILE dbf , datum key )
引数の対応はgdbm_fetch関数と同じ.
キーに対応するデータが存在する場合は'1'を,存在しないは'0'を,それぞれ戻り値として返す.

  • データの削除

データベースに格納されているデータの削除は以下の関数で行う.

int gdbm_delete (GDBM FILE dbf , datum key )
こちらも引数の対応はgdbm_fetch関数と同じである.
キーに一致するデータがある場合には削除し,'0'を戻り値として返す.
また一致するデータが存在しない場合や,その他のエラーは戻り値として'-1'を返す.

今回も最後にサンプルコードを記載.

Linuxプログラミング―例題で学ぶUNIXプログラミング環境のすべて

Linuxプログラミング―例題で学ぶUNIXプログラミング環境のすべて

のpp299-302の簡単なdbmの読み込みのサンプルソースをgdbm用に変更したものを記載する.
#include
#include
#include
#include
#include
#include

#define TEST_DB_FILE "dbm1_test"
#define ITEMS_USED 3
#define BLOCK_SIZE (4096)

struct test_data{
char misc_chars[15];
int any_integer;
char more_chars[21];
};

int main(){
struct test_data items_to_store[ITEMS_USED];
struct test_data item_retrieved;

char key_to_use[20];
int i, result;

datum key_datum;
datum data_datum;

GDBM_FILE dbm_ptr;

dbm_ptr = gdbm_open(TEST_DB_FILE, BLOCK_SIZE, GDBM_WRCREAT, 0666, NULL);
if(!dbm_ptr){
fprintf(stderr, "Failed to open database\n");
exit(EXIT_FAILURE);
}

memset(items_to_store, '\0', sizeof(items_to_store));
strcpy(items_to_store[0].misc_chars, "First!");
items_to_store[0].any_integer = 47;
strcpy(items_to_store[0].more_chars, "foo");

strcpy(items_to_store[1].misc_chars, "bar");
items_to_store[1].any_integer = 13;
strcpy(items_to_store[1].more_chars, "unlucky?");

strcpy(items_to_store[2].misc_chars, "Third");
items_to_store[2].any_integer = 3;
strcpy(items_to_store[2].more_chars, "baz");

for(i = 0; i < ITEMS_USED; i++){
sprintf(key_to_use, "%c%c%d",
items_to_store[i].misc_chars[0],
items_to_store[i].more_chars[0],
items_to_store[i].any_integer);
key_datum.dptr = (void *)key_to_use;
key_datum.dsize = strlen(key_to_use);
data_datum.dptr = (void *)&items_to_store[i];
data_datum.dsize = sizeof(struct test_data);

result = gdbm_store(dbm_ptr, key_datum, data_datum, GDBM_REPLACE);
if(result != 0){
fprintf(stderr, "gdbm_store failed on key %s", key_to_use);
exit(2);
}
}

sprintf(key_to_use, "bu%d", 13);
key_datum.dptr = key_to_use;
key_datum.dsize = strlen(key_to_use);

data_datum = gdbm_fetch(dbm_ptr, key_datum);
if(data_datum.dptr){
printf("Data retrieved\n");
memcpy(&item_retrieved, data_datum.dptr, data_datum.dsize);
printf("Retrieved tem - %s %d %s\n",
item_retrieved.misc_chars,
item_retrieved.any_integer,
item_retrieved.more_chars);
}
else{
printf("No data found for key %s\n", key_to_use);
}
gdbm_close(dbm_ptr);
exit(EXIT_SUCCESS);
}

GDBMを使ってみる1

GDBMは拡張可能ハッシュを使用するデータベース関数のライブラリである.UNIXのdbmに似たようなもの.
MySQLPostgreSQLのような関係データベースではなく,Key-Value格納の組み込み型のデータベースとして利用する.
今回はこのGDBMの使い方をGDBMから探っていく.

  • データベースのオープン

まずデータベースを使用するためにデータベースをオープンする必要がある.
これを行うのが以下の関数.

GDBM_FILE gdbm_open (
const char *name ,
int block_size ,
int flags ,
int mode ,
void (*fatal func)(const char *))
C言語のファイルポインタと同じような感覚でGDBM_FILEを用いてフデータベースのオープンを行えばよい.
関数の引数によってさまざまな指定が出来るので,次に引数に対する理解をしていく.

  • name

 オープンを行うデータベースの名前を記述する.

  • block_size

 ディスクからメモリへ1回に転送されるサイズを指定する.この値は初期化の時にのみ使われ,それ以外は無視される.最小のサイズは512になっており,512未満が設定されるとファイルシステムのブロックサイズが使用される.

  • flags

 ヘッダで定義されている値を使ってデータベースのフラグを設定する.
GDBM_READERを指定するとリードオンリーでデータベースを開く.
GDBM_WRITERを指定するとデータベースに対して読み書きが可能.
GDBM_WRCREATはGDBM_WRITERとほぼ同様であるが,指定したデータベースが存在しなければ新たにデータベースを作成する.
GDBM_NEWDBはGDBM_WRITERにおいてデータベースが既に存在するしないに関わらず新たに作成する.

これらのフラグのどれかを指定し,論理和により以下のフラグも設定可能.
GDBM_SYNCはデータベースに対する操作を常にディスクと同期させながら行う.
GDBM_NOLOCKデータベースファイルにt対するのロックを行わない.
GDBM_NOMMAPmmapの機能を無効にする.
またO_CLOEXECと同様の機能としてGDBM_CLOEXECが指定可能である.

  • mode

 ファイルのモードを指定する.

  • fatal_func

 致命的なエラーが発生した時に呼び出す関数を指定する.NULLを指定した場合はデフォルトの関数に設定される.

以上のオプションを利用してさまざまな条件でデータベースをオープンすることが出来る.

  • データベースのクローズ

次に開いたデータベースを閉じる関数は以下.

void gdbm_close(GDBM_FILE dbf)
引数として指定したデータベースを閉じることが出来る.

今回の最後にgdbmでデータベースを開いて閉じるサンプルソースを記載.データベースに対する書き込み読み込みは次回.

#include
#include
#include

#define TEST_DB_FILE "dbm_test"

int main(){
GDBM_FILE dbm_ptr;

dbm_ptr = gdbm_open(TEST_DB_FILE, 0, GDBM_WRCREAT, 0666, NULL);
if(!dbm_ptr){
fprintf(stderr, "Failed to open database\n");
exit(EXIT_FAILURE);
}

gdbm_close(dbm_ptr);
exit(EXIT_SUCCESS);
}

GDBMインストール

Ubuntu10.4にデータベースの一種であるGDBM(GNU dbm)をインストールする.
対象バージョンはgdbm-1.10.

まずGDBMよりtar.gzファイルをダウンロードする.
wgetを用いてのダウンロードは以下.

$wget ftp://ftp.gnu.org/gnu/gdbm/gdbm-1.10.tar.gz
続いてインストールを行っていく.
$tar zxvf gdbm-1.10.tar.gz
$cd gdbm-1.10
$./configure --enable-libdbm-compat
$make
$sudo make install
今回はdbmやndbm互換性のため"--enable-libdbm-compat"オプションを付けてインストールを行った.
またインストールにおけるデフォルトのライブラリ格納ディレクトリは"/usr/local/lib/".

C言語で利用する際には

#include
を記述し,ヘッダのインクルードを行い,コンパイル時には-lgdbmオプションを付ける.

PostgreSQLの文字化け

Ubuntu10.4にPostgreSQL8.4をインストールを行うとPostgreSQLのエラーメッセージが文字化けする.

そこでこの文字化けを解消.

$sudo vi /etc/postgresql/8.4/main/postgresql.conf
PostgreSQLの設定ファイルを以下のように編集前の行を編集後のように変更する.
lc_messages = 'ja_JP.utf8'  #編集前
lc_messages = 'C' #編集後
後はPostgreSQLの再起動
$sudo /etc/init.d/postgresql-8.4 restart