Archive for 3月, 2009

全国郵便番号XMLデータ

月曜日, 3月 30th, 2009

日本郵政の郵便番号データをXML形式に変換したものを用意する機会があったので、公開してみます。
文字コードはUTF-8です。

attachment filejp_zipcode (1.8MB)

フォーマットは以下のような形式になっています。

<?xml version="1.0"?>
<JapanAreaData>
  <Prefecture>
    <PrefectureKana>ホッカイドウ</PrefectureKana>
    <PrefectureName>北海道</PrefectureName>
    <City>
      <CityName>札幌市中央区</CityName>
      <CityNameKana>サッポロシチュウオウク</CityNameKana>
      <Area>
        <AreaKana>アサヒガオカ</AreaKana>
        <AreaName>旭ケ丘</AreaName>
        <ZipCode>0640941</ZipCode>
      </Area>
      <Area>
        <AreaKana>オオドオリヒガシ</AreaKana>
        <AreaName>大通東</AreaName>
        <ZipCode>0600041</ZipCode>
      </Area>
            :
    </City>
    <City>
      <CityName>札幌市北区</CityName>
      <CityNameKana>サッポロシキタク</CityNameKana>
              :
    </City>
  </Prefecture>
  <Prefecture>
    <PrefectureKana>アオモリケン</PrefectureKana>
    <PrefectureName>青森県</PrefectureName>
          :
  </Prefecture>
</JapanAreaData>

元データは日本郵政のサイトにおいてある、CSVファイルです。Perlで書いた変換スクリプトでXMLに変換しました。

郵便番号CSVデータをXMLに変換するスクリプト

#!/usr/bin/perl

use strict;
use XML::Simple;

my $hash;
my %tmp_hash;
my $i;

$hash->{'Prefecture'} = [];

my $current_pref;
my $current_city;
my $pref_prefix = -1;
my $city_prefix = -1;

while (<>) {
        chomp;
        s/"//g;
        my ($idnum, $zip5, $zip7, $pref_kana, $city_kana, $area_kana, $pref, $city, $area) = split(/,/);

        if ($area eq '以下に掲載がない場合') {
                next;
        }

        if ($current_pref ne $pref) {
                $pref_prefix++;
                $city_prefix = -1;

                $current_pref = $pref;
                $current_city = "";

                push(@{$hash->{'Prefecture'}}, { 'PrefectureName' => $pref, 'PrefectureKana' => $pref_kana, 'City' => [] });
        }

        if ($current_city ne $city) {
                $city_prefix++;
                $current_city = $city;

                push(@{$hash->{'Prefecture'}[$pref_prefix]->{'City'}}, { 'CityName' => $city, 'CityNameKana' => $city_kana, 'Area' => [] });
        }
        push(@{$hash->{'Prefecture'}[$pref_prefix]->{'City'}[$city_prefix]->{'Area'}}, { 'ZipCode' => $zip7, 'AreaName' => $area, 'AreaKana' => $area_kana });
}

print XMLout($hash,  NoAttr => 1, RootName => 'JapanAreaData');

プロファイラ gprof の使い方

木曜日, 3月 26th, 2009

gprof GNU Profiler とは

プロファイラあるプログラム中で使用される関数毎に、その実行回数や実行時間を分析して表示するツールです。プログラムが思ったようなパフォーマンスが出ない場合、ボトルネックになっている場所を特定するために利用します。

実行例

  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
  0.0       0.00     0.00        3     0.00     0.00  read [1] //read()が3回
  0.0       0.00     0.00        2     0.00     0.00  write [2] //write()が2回 呼ばれている
  0.0       0.00     0.00        1     0.00     0.00  ___sysctl [138]
  0.0       0.00     0.00        1     0.00     0.00  _close [139]
  0.0       0.00     0.00        1     0.00     0.00  _mcleanup (140)
  0.0       0.00     0.00        1     0.00     0.00  _profil [141]
  0.0       0.00     0.00        1     0.00     0.00  moncontrol [3]
  0.0       0.00     0.00        1     0.00     0.00  open [4]
  0.0       0.00     0.00        1     0.00     0.00  sysctl [5]

それぞれ、

  • 実行時間(全体に対する %)
  • 実行時間の累積(秒)
  • 実行時間(秒)
  • 関数の実行された回数
  • 1回の関数実行にかかる時間(ミリ秒)
  • 1回の関数実行にかかる時間の合計(ミリ秒)
  • 関数名

を表しています。

使い方

gprofを使うには「-pg」オプションをつけてコンパイルする必要があります。

$ gcc -pg -o test.o test.c

あとは、実行ファイルを普通に実行します。このとき、gprofのデータファイルが同じディレクトリに生成されます。

$ ./test.o

「gprof 実行ファイル データファイル」とすると、解析結果が出力されます。

$ gprof test.o test.o.gmon

参考文献

Debug Hacks -デバッグを極めるテクニック&ツール
Debug Hacks
GNU Make 第3版
GNU Make 第3版

参考URL

Makefile中の環境変数を無視する

木曜日, 3月 26th, 2009

Makefile中の環境変数を無視する

「-e」オプションでMakefile中で設定されている環境変数を上書きすることができる。

サンプルMakefile

CFLAGS = -O -Wall

a.out: main.c
$(CC) $(CFLAGS) main.c -o $@

make

$ make //普通にmake
cc -O -Wall main.c -o a.out

$ export CFLAGS=” //CFLAGSをなしに
$ touch main.c
$ make -e
cc main.c -o a.out

「-e」オプションの説明
Makeファイル中の変数を上書きする。

-e Specify that environment values override macro assignments within
makefiles for all variables.

参考文献

GNU Make 第3版
GNU Make
CとGNU開発ツールによる組み込みシステムプログラミング 第2版
CとGNU開発ツールによる組み込みシステムプログラミング
GNUソフトウェアプログラミング ―オープンソース開発の原点
GNUソフトウェアプログラミング

[C言語]共有メモリ(shm)を読み書きするサンプルコード

木曜日, 3月 19th, 2009

共有メモリとは

共有メモリはその名の通り、複数のプロセスで共有できるメモリである。
プロセス間通信(IPC)に利用される。
親プロセス、子プロセス、無関係なプロセス、どの関係であってもデータの共有が可能。

共有メモリの操作には下記のシステムコールを利用する。

システムコール 説明
shmget() 共有メモリ・セグメント識別子を獲得する
shmat() 自プロセスのデータセグメントにマップ(アタッチとも呼ぶ)する
shmdt() 共有メモリをアンマップ(デタッチとも呼ぶ)する
shmctl() 共有メモリをシステム上から削除する

サンプルプログラム

shm_receiver.c

shmを生成し、1秒置きに、shmの中身を画面に表示する。shmの中身が「end」になると、終了する。

#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* strcpy */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main(void)
{
    int  id;
    char *adr;

    /* IPC_PRIVATEを指定すると、新規にshmが生成される。*/
    if((id = shmget(IPC_PRIVATE, 512, IPC_CREAT|0666)) == -1){
        perror("shmget");
        exit(-1);
    }

    printf("共有メモリID = %d\n",id);

    /* char形で取得 */
    if(( adr = (char *)shmat(id, NULL, 0)) == (void *)-1){
        perror("shmat");
    } else {
        strcpy(adr,"Initial");
        /* 1秒ごとに出力 */
        while(1){
            printf("%s\n",adr);
            /* 「end」で終了 */
            if (strcmp(adr, "end") == 0) {
                break;
            }
            sleep(1);
        }

        if(shmdt(adr)==-1){
            perror("shmdt");
        }
    }

    /* shmの破壊をする */
    if(shmctl(id, IPC_RMID, 0)==-1){
        perror("shmctl");
        exit(EXIT_FAILURE);
    }

    return 0;
}

shm_writer.c

コマンドラインから与えられた文字列をshmにコピーする。

#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* strcpy */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main(int argc, char *argv[])
{
    int   id;
    char  *adr;

    if( argc <= 2) {
        fprintf(stderr, "Usage: shm_writer shm_id string\n");
        exit(EXIT_FAILURE);
    }

    id = atoi(argv[1]);

    if(( adr = (char *)shmat(id,0,0)) == (void *)-1) {
        perror("shmat");
    } else {
        strcpy(adr, argv[2]);
        fprintf(stderr, "written.\n");
        if( shmdt(adr) == -1) {
            perror("shmdt");
        }
    }
}

実行例

$ gcc shm_receiver.c -o shm_receiver
$ gcc shm_writer.c -o shm_writer

$ shm_receiver
共有メモリID = 1234 ←IDを覚えておく
Initial

$ shm_writer 1234 hogehoge //shmに「hogehoge」を書き込む。
$ shm_writer 1234 end //shm_receverが終了する。

参考文献

詳解UNIXプログラミング
詳解UNIXプログラミング
例解UNIXプログラミング教室
例解UNIXプログラミング教室
C言語によるUNIXシステムプログラミング入門
C言語によるUNIXシステムプログラミング入門

参考URL

構築環境

FreeBSD 4.10-RELEASE

[C言語]正規表現 サンプルコード

月曜日, 3月 16th, 2009

regex.h

C言語で正規表現を使うにはregex.hをincludeします。
具体的に利用する関数はregcomp(), regexec(), regfree()関数の3つです。

regcomp() 関数

int regcomp(regex_t *preg, const char *regex, int cflags)
正規表現のコンパイルを行なう。コンパイル成功時には0を返す。cflags には以下に示す定数一つ以上のビットごとの OR (bitwise-or) を指定する。

REG_EXTENDED
regex に POSIX 拡張正規表現を使用する。もしこのフラグが設定されない場合、POSIX 標準正規表現が使われる。
REG_ICASE
大文字小文字の違いを無視する。
REG_NOSUB
このフラグを設定してコンパイルされたパターンバッファが regexec の引数に指定されると、パラメータ nmatch, pmatch が無視される。
REG_NEWLINE
全ての文字にマッチするオペレータに改行をマッチさせない。

regexec() 関数

int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags);
正規表現による検索を実行する。eflags には以下に示す定数一つ以上のビットごとの OR (bitwise-or) を指定する。

REG_NOTBOL
行頭にマッチするオペレータは、必ずマッチに失敗する (コンパイル時のフラグ REG_NEWLINE の項目も参照)。このフラグは、複数行にまたがる文字列を regexec() で検索する際に、文字列の先頭を行の先頭として解釈させない場合に用いる。
REG_NOTEOL
行末にマッチするオペレータは、必ずマッチに失敗する。

regfree()関数

void regfree(regex_t *preg)
正規表現パターンバッファを解放する。

正規表現サンプルコード

カンマ区切りの文字列から、数値を取り出す。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <regex.h>
 
int main() {
     char str[] = "123, 456, 789";
     regex_t preg;
     size_t nmatch = 5;
     regmatch_t pmatch[nmatch];
     int i, j;
 
     if (regcomp(&preg, "([[:digit:]]+), ([[:digit:]]+), ([[:digit:]]+)", REG_EXTENDED|REG_NEWLINE) != 0) {
         printf("regex compile failed.\n");
         exit(1);
     }
 
     printf("String = %s\n", str);
 
     if (regexec(&preg, str, nmatch, pmatch, 0) != 0) {
         printf("No match.\n");
     } else {
         for (i = 0; i < nmatch; i++) { /* nmatch にマッチした件数が入る */
             printf("Match position = %d, %d , str = ", (int)pmatch[i].rm_so, (int)pmatch[i].rm_eo);
             if (pmatch[i].rm_so >= 0 && pmatch[i].rm_eo >= 0) {
                 for (j = pmatch[i].rm_so ; j < pmatch[i].rm_eo; j++) {
                     putchar(str[j]);
                 }
             }
             printf("\n");
         }
     }
 
     regfree(&preg);
     return 0;
}

実行結果

$ gcc regex-sample.c
$ ./a.out
String = 123, 456, 789
Match position = 0, 13 , str = 123, 456, 789
Match position = 0, 3 , str = 123
Match position = 5, 8 , str = 456
Match position = 10, 13 , str = 789
Match position = -1, -1 , str =

参考文献

反復学習ソフト付き 正規表現書き方ドリル (WEB+DB PRESS plus)
反復学習ソフト付き
正規表現書き方ドリル

詳説 正規表現 第3版
詳説 正規表現
プログラミング言語C 第2版 ANSI規格準拠
プログラミング言語C

参考URL

[C言語]アラーム サンプルコード

木曜日, 3月 12th, 2009

alarm()関数を利用してSIGALRMシグナルを一定時間後に送信することができる。
SIGALRMをキャッチしない場合は、プロセスは終了する。SIGALRM以外のシグナルについては[C言語]シグナルをキャッチする サンプルコードを参照。

alarm(int second);
second秒数後にSIGALRMを送る

サンプルコード

#include <stdio.h>
#include <stdib.h> /* exit */
#include <unistd.h>
#include <signal.h>
 
void sigcatch(int);
 
int main() {
 
    /* Set handler to SIGALRM */
    if (SIG_ERR == signal(SIGALRM, sigcatch)) {
        printf("failed to set signal handler.\n");
    }
 
    /* 5 seconds after, send signal */
    alarm(5);

    while (1) {
    }

    return 0;
}
 
void sigcatch(int sig) {
    printf("catch signal %d\n", sig);

    if (sig == SIGALRM) {
        printf("catch SIGALRM and exit.\n");
        exit(1);
    }
}

実行結果

$ gcc alarm-sample.c
$ ./a.out
catch signal 14
catch SIGALRM and exit.

参考文献

入門UNIXシェルプログラミング―シェルの基礎から学ぶUNIXの世界
入門UNIXシェルプログラミング
いますぐ始めるLinuxのC言語
いますぐ始めるLinuxのC言語
これならわかるC 入門の入門

これならわかるC 入門の入門

参考URL

実行環境

Linux Ubuntu 2.6.24, gcc version 4.2.3

[C言語]シグナルをキャッチする サンプルコード

月曜日, 3月 9th, 2009

シグナルとは

いわゆる「割り込み」の1つ。動いてるプロセスに対して、他のプロセスから信号を送ることが出来る。

代表的なシグナル

シグナル番号 シグナル名 説明
1 SIGHUP デーモンプロセスに設定の再読み込みをさせるのに良く利用される
2 SIGINT キーボードからの割り込み(Ctrl-C)
9 SIGKILL Kill シグナル。kill -kill [PID]
14 SIGALRM アラーム。一定時間後にSIGALRMを飛ばせる
15 SIGTERM 終了シグナル。kill -term [PID]

signal キャッチ

C言語で、signalをキャッチするには、signal()関数で、シグナルハンドラーを設定する。

signal(シグナルの種類, 受け取った時に実行する関数)

「signalを受け取った時に実行する関数」のことを、一般的に「signalハンドラー」と呼ぶ。
シグナルハンドラーは、1つのintを引数に、戻り値はvoidでなければならない。

typedef void (*sighandler_t)(int);

signal キャッチ サンプル

HUPをうけとると、無限ループしているプログラムを終了させる。
サーバのような、駐屯型のプログラムはこのようにシグナルで終了する仕組になっている。

#include <signal.h>
#include <stdio.h>

void sigcatch(int);

int main() {
    if (SIG_ERR == signal(SIGHUP, sigcatch)) {
        printf("failed to set signal handler.n");
        exit(1);
    }
	
    while (1) {
        sleep(1);
    }

    return 0;
}
 
void sigcatch(int sig) {
    printf("catch signal %dn", sig); 
    exit(1);
}

シグナルを送る

# ps u
# kill -HUP [プロセス番号]

参考文献

ふつうのLinuxプログラミング Linuxの仕組みから学べるgccプログラミングの王道
ふつうのLinuxプログラミング
プログラミングテクニック―UNIXコマンドのソースコードにみる実践プログラミング手法 (UNIX MAGAZINE COLLECTION)
UNIXコマンドのソースコードにみる実践プログラミング手法
新版 明解C言語 入門編
新版 明解C言語 入門編

[C言語]POSIXスレッドを用いたechoサーバ

木曜日, 3月 5th, 2009

サーバの仕組みとスレッド

通常、Webサーバなどは親プロセスがポートをListenし、接続があったら、子プロセスを生成し、その後の(クライアントへのデータ送信等の)処理は、子プロセスに任せるというフローをとる。

そうすることで、親プロセスはクライアントへデータ送信が終わるのを待つ必要がなくなり、どんどん接続を受けることが可能になる。
接続数が多くなると、当然、子プロセスが多くなり、サーバへの過負荷となる。

そこで、子プロセスではなく、スレッドでクライアントの処理を行うと負荷低減につながる((スレッドの生成は子プロセスの生成より軽量))

echoサーバのPOSIXスレッド化

[C言語]Socket間通信 echoサーバを作るで作成したechoサーバにptheadを組み込み、複数クライアントに対して同時処理ができるように改良する。POSIXスレッドについて知りたければ、まずは[C言語]POSIXスレッドプログラミングを参照。

課題

  • 1回の通信が終わっても、Listenし続けるように変更する
  • threadで返答を返すよう変更する

コード

#include <sys/types.h>
#include <sys/socket.h> /* socket(), bind(), listen(), accept(), recv() */
#include <netinet/in.h> /* htons() */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>     /* exit() */
#include <pthread.h>    /* pthread_create(), pthread_detach() */

#define PORT    8823    /* Listenするポート */
#define MAXDATA 1024    /* 受信バッファサイズ */

void reply(void *);

int main(void)
{
    struct sockaddr_in saddr;
    struct sockaddr_in caddr;

    int listen_fd;
    int conn_fd;

    int len = sizeof(struct sockaddr_in);

    pthread_t worker;

    /* ソケットの生成 */
    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    /*
     * saddrの中身を0にしておかないと、bind()でエラーが起こることがある
     */
    bzero((char *)&saddr, sizeof(saddr));

    /* ソケットにアドレスとポートを結びつける */
    saddr.sin_family        = PF_INET;
    saddr.sin_addr.s_addr   = INADDR_ANY;
    saddr.sin_port          = htons(PORT);
    if (bind(listen_fd, (struct sockaddr *)&saddr, len) < 0) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    /* ポートをListenする */
    if (listen(listen_fd, SOMAXCONN) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    printf("Start Listening Port : %d...\n", PORT);

    while (1) {
        /* 接続要求を受け付ける */
        if ((conn_fd = accept(listen_fd, (struct sockaddr *)&caddr, &len)) < 0) {
            perror("accept");
            exit(EXIT_FAILURE);
        }

        /* スレッドの生成 */
        if (pthread_create( &worker, NULL, (void *)reply, (void *)conn_fd) != 0) {
            perror("pthread_create");
            exit(EXIT_FAILURE);
        }
        pthread_detach(worker);
    }
    /* Listeningソケットを閉じる */
    close(listen_fd);

}

void reply(void *fd) {
    int conn_fd = (int)fd;

    int rsize;
    char buf[MAXDATA]; /* 受信バッファ */

    /* 送信されたデータの読み出し */
    do {
        rsize = recv(conn_fd, buf, MAXDATA, 0);

        if (rsize == 0) { /* クラアイントが接続を切ったとき */
                break;
        } else if (rsize == -1) {
                perror("recv");
                exit(EXIT_FAILURE);
        } else {
                write(conn_fd, buf, rsize);
        }
    } while (1);

    if ( close(conn_fd) < 0) {
        perror("close");
        exit(EXIT_FAILURE);
    }

    printf("Connection closed.\n");
}

ポイント

  • スレッドにする部分を関数にする
  • 生成したスレッドをdetach状態にする
  • while(1)ループでListenし続ける

参考文献

マスタリングTCP/IP 入門編 第4版
マスタリングTCP/IP 入門編
マスタリングTCP/IP 応用編
マスタリングTCP/IP 応用編
マルチコアCPUのための並列プログラミング―並列処理&マルチスレッド入門
マルチコアCPUのための並列プログラミング

参考URL

Pthreadによる複数クライアントに対するサービスの同時提供
http://www.coins.tsukuba.ac.jp/~yas/coins/syspro-2004/2004-05-10/echo-server-pthread.html
4.2 どうしてソケットがクローズしてくれないのでしょうか?
http://www.kt.rim.or.jp/~ksk/sock-faq/unix-socket-faq-ja-4.html#ss4.2

構築環境

FreeBSD 4.10-RELEASE

[C言語]POSIXスレッドプログラミング サンプル

月曜日, 3月 2nd, 2009

pthread ライブラリを使った、簡単なスレッドの実装例です。

POSIXスレッド とは

ポジックススレッドと読む。Pthreadと呼ばれることが多い。Pthreadを使うにはpthreadライブラリが必要である。
コンパイル時には「-pthread」オプションを使う((「-lpthread」の場合もある))。

# gcc -pthread source.c

スレッドのメリット/デメリット

スレッドと似た仕組みとして、fork()による子プロセスの生成があるが、スレッドを使うことによる利点がある。
かなりザックリとまとめると、
メリット

デメリット

  • スレッド間の同期処理が面倒
  • 利用するライブラリがスレッドに対応している必要がある

サンプルプログラム

こちらから引用。

POSIXスレッドでは、pthread_create()を使って、新しいスレッドを生成し、pthread_join()を使ってスレッドが終了するのを待つ。
終了を待つ必要がなければ、pthread_detach()を使って切り離せばよい。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *print_message_function( void *ptr );

main()
{
     pthread_t thread1, thread2;
     char *message1 = "Thread 1";
     char *message2 = "Thread 2";
     int  iret1, iret2;

    /* Create independant threads each of which will execute function */

     iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1);
     iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2);

     /* Wait till threads are complete before main continues. Unless we  */
     /* wait we run the risk of executing an exit which will terminate   */
     /* the process and all threads before the threads have completed.   */

     pthread_join( thread1, NULL);
     pthread_join( thread2, NULL); 

     printf("Thread 1 returns: %d\n",iret1);
     printf("Thread 2 returns: %d\n",iret2);
     exit(0);
}

void *print_message_function( void *ptr )
{
     char *message;
     message = (char *) ptr;
     printf("%s \n", message);
}

実行結果

$ gcc -pthread sample.c
$ ./a.out
Thread 1
Thread 2
Thread 1 returns: 0
Thread 2 returns: 0

参考文献

Pthreadsプログラミング
Pthreadsプログラミング
Pスレッドプログラミング
Pスレッドプログラミング

参考URL

スレッド・プログラミング
http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-1998/1999-01-19/pthread.html
YoLinux Tutorial: POSIX thread (pthread) libraries
http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html
スレッドを用いるメリット
http://mikilab.doshisha.ac.jp/dia/research/person/yoshiki/06.html
Manpage OF PTHREAD_CREATE
http://www.linux.or.jp/JM/html/glibc-linuxthreads/man3/pthread_create.3.html
Manpage OF PTHREAD_JOIN
http://www.linux.or.jp/JM/html/glibc-linuxthreads/man3/pthread_join.3.html
Manpage OF PTHREAD_DETACH
http://www.linux.or.jp/JM/html/glibc-linuxthreads/man3/pthread_detach.3.html

構築環境

FreeBSD 4.10-RELEASE