魔術師見習いのノート

プロフィール

魔術師見習い
Author魔術師見習い-_-.
Twitter魔術師見習い

コンピュータ関係のメモを主に書きます.

MENU

HTML 箇条書き(横書き)

投稿日:
タグ:

HTMLで,ニコニコ動画の上にある登録タグのようなものを書く(カンマやスペース等で区切って横に並べる)時,マークアップ言語的に<ul>を使って,見た目はCSSに任せるべきだと思い調べてみた.

見た目はこんな感じになる(Google Chrome19,Opera12, firefox12で確認).

  • hoge
  • fuga
  • piyo
  • hoge
  • fuga
  • piyo
以下が上のHTMLのCSSのサンプルコードである.
<style>
li {
  list-style:none;
  text-indent:0;
  margin:0;
  padding:0;
}
li:last-child {
  float:none;
}
li:not( :last-child ){
  float:left;
}
li:not( :last-child ):after{
  content:"・";
}
li:last-child:after{
  content:"";
}
</style>
contentの値を変えれば他の区切り記号になる.

また,Aタグを加えるとこういう見た目になる.

飛び先アドレスは全てgoogle

SESCのコンパイル

投稿日:
タグ:

Debian6-0.2.1でSESCをコンパイルする際にうまくいかなかったのでその時の苦労をメモ.コードを書き換えたりしているが,とりあえず実行できるものはできた.

作業環境

コンパイルに用いた環境はこんな感じである.

archx86_64
OSDebian6-0.2.1
pwd/home/user/00_workspace/sesc
GCCのバージョン情報※後述のものを参照
user% gcc -v
Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.4.5-8'
--with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr
--program-suffix=-4.4 --enable-shared --enable-multiarch
--enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib
--without-included-gettext --enable-threads=posix
--with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib
--enable-nls --enable-clocale=gnu --enable-libstdcxx-debug
--enable-objc-gc --with-arch-32=i586 --with-tune=generic
--enable-checking=release --build=x86_64-linux-gnu
--host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.4.5 (Debian 4.4.5-8) 

SESCの取得

SESCはの取得は, Hashim's Space を参考に行った.以下のコマンドを実行するとカレントディレクトリにsescディレクトリが生成される.

user% cvs -z3 -d:pserver:anonymous@sesc.cvs.sourceforge.net:/cvsroot/sesc co -P sesc 
コマンドの詳細については省略する.

コンパイル

流れとしては,"configure"によってシミュレータの種類を設定,"make"によってソースコードをコンパイルし,"make sesc.conf"でシミュレータの標準(オプションで指定しなかった場合のパラメータ)の設定ファイルを用意する."make sesc.conf"はconfsディレクトリからただコピーしてくるだけだが,シミュレータの種類に応じて適切なものをコピーしてくれる.

はじめにconfigureスクリプトを走らせる.今回は特にオプションを指定しない.

user% ./configure
configure: creating ./config.status
config.status: creating Makefile
config.status: creating Make.defs

次にMAKEコマンドを実行する.

user% make
g++  -O3 -momit-leaf-frame-pointer -march=i686 -funroll-loops
-fsched-interblock  -ffast-math -fno-strict-aliasing -freg-struct-return
-I/home/user/00_workspace/sesc/obj/Linux_obj
-I/home/user/00_workspace/sesc/./src/libapp
-I/home/user/00_workspace/sesc/./src/libsuc
-I/home/user/00_workspace/sesc/./src/libcore
-I/home/user/00_workspace/sesc/./src/libnet
-I/home/user/00_workspace/sesc/./src/libmem
-I/home/user/00_workspace/sesc/./src/libvmem
-I/home/user/00_workspace/sesc/./src/libll
-I/home/user/00_workspace/sesc/./src/libmint
-I/home/user/00_workspace/sesc/./src/libpint
-I/home/user/00_workspace/sesc/./src/libsmp -I.
-I/home/user/00_workspace/sesc/./src/librst -W -Wall
-Wno-unused -DPACKAGE_NAME=\"esesc\" -DPACKAGE_TARNAME=\"esesc\"
-DPACKAGE_VERSION=\"2\" -DPACKAGE_STRING=\"esesc\ 2\"
-DPACKAGE_BUGREPORT=\"renau@soe.ucsc.edu\ luisceze@cs.uiuc.edu\"
-DLINUX -DLENDIAN  -DSIMICS -DPOSIX_MEMALIGN -DNDEBUG -DDEBUG2=0 -o
/home/user/00_workspace/sesc/obj/Linux_obj/mkdep
/home/user/00_workspace/sesc/./src/misc/mkdep.cpp
/home/user/00_workspace/./src/misc/mkdep.cpp:1: error: CPU you selected
does not support x86-64 instruction set

開始して速攻で躓いた.

エラーの原因は選択したISAらしい.MAKEコマンドのログ情報を見てみると,コンパイラのクロスコンパイルなどをするための-marchオプションがi686になっているのが原因っぽい.GCCの設定ファイルってmake.confだったかなぁ(違うかも…)と思い探してみるが,見つからない.面倒臭いので他の方法が探してみた.

user% egrep i686 **/*
src/Makefile.defs.CYGWIN:COPTS  += -march=i686
src/Makefile.defs.Linux:# Default architecture is i686
src/Makefile.defs.Linux:OptArch := -march=i686
src/libsuc/Snippets.h:#if defined(__tune_i686__)
EGREPコマンドを使うと(GREPでも良いが),割と簡単にそれっぽいのを発見した.ファイルを見てみたところMakefile.defs.Linuxを書き換えれば良さそうである.ちなみに**/*はZSHが使用可能なパターンマッチングの方法である.VIコマンドで-march=より右をnoconaかcore2に変更する.
user% vi src/Makefile.defs.Linux 
Before:
     66 # Default architecture is i686
     67 OptArch := -march=i686
     68 # But now try to detect the real architecture
After:
     66 # Default architecture is i686
     67 #OptArch := -march=i686
     68 OptArch := -march=nocona
     69 # But now try to detect the real architecture
そして再びmakeコマンドを実行する.

──が,またしてもエラー.

/home/user/00_workspace/sesc/./src/libcore/FetchEngine.cpp:79:
error: ‘USHRT_MAX’ was not declared in this scope
FetchEngine.cppがlimits.hで定義されているUSHRT_MAXが読み込めてないらしい.面倒臭いので,読み込めてない原因は調べずコードを書き加えた.
Before:
     25 #include "SescConf.h"
     26 #include "FetchEngine.h"
     27 #include "OSSim.h"
     28 #include "LDSTBuffer.h"
     29 #include "GMemorySystem.h"
     30 #include "GMemoryOS.h"
     31 #include "GProcessor.h"
     32 #include "MemRequest.h"
     33 #include "Pipeline.h"
     34 
     35 
After:
     25 #include "SescConf.h"
     26 #include "FetchEngine.h"
     27 #include "OSSim.h"
     28 #include "LDSTBuffer.h"
     29 #include "GMemorySystem.h"
     30 #include "GMemoryOS.h"
     31 #include "GProcessor.h"
     32 #include "MemRequest.h"
     33 #include "Pipeline.h"
     34 
     35 #include <limits.h>
追加行は本来ならC++なので,#include <climits>とすべきかもしれないが,元々Cの書き方と混ざったソースコードだったので,気にせず<limits.h>とする.

FetchEngine.cppを解決すると,またしてもlimits.hが原因のエラー.

/home/user/00_workspace/sesc/./src/libcore/Cluster.cpp:161:
error: ‘INT_MAX’ was not declared in this scope
今度はCluster.cppでlimits.hで定義されたマクロがないと言われたので同じように追記する.
Before:
     23 #include "Cluster.h"
     24 
     25 #include "GEnergy.h"
     26 #include "GMemorySystem.h"
     27 #include "GProcessor.h"
     28 #include "Port.h"
     29 #include "Resource.h"
     30 #include "SescConf.h"
     31 
     32 #include "estl.h"
     33 
     34 // Begin: Fields used during constructions
After:
     23 #include "Cluster.h"
     24 
     25 #include "GEnergy.h"
     26 #include "GMemorySystem.h"
     27 #include "GProcessor.h"
     28 #include "Port.h"
     29 #include "Resource.h"
     30 #include "SescConf.h"
     31 
     32 #include "estl.h"
     33 
     34 #include <limits.h>
     35 
     36 // Begin: Fields used during constructions
ファイルの修正を終え,再度makeを叩く.

しかし前進はしているもののまたしてもエラー(結果からいえば次が最後の修正だった).

/home/user/00_workspace/sesc/./src/libsuc/Config.cpp:211:
error: ‘LONG_MAX’ was not declared in this scope
/home/user/00_workspace/sesc/./src/libsuc/Config.cpp: In
member function ‘bool Config::isPower2(const char*, const char*,
int32_t)’:
/home/user/00_workspace/sesc/./src/libsuc/Config.cpp:762:
error: ‘uint32_t’ was not declared in this scope
/home/user/00_workspace/sesc/./src/libsuc/Config.cpp:762:
error: expected ‘;’ before ‘v’
/home/user/00_workspace/sesc/./src/libsuc/Config.cpp:764:
error: ‘v’ was not declared in this scope
/home/user/00_workspace/sesc/./src/libsuc/Config.cpp:771:
error: ‘v’ was not declared in this scope
make[1]: ***
[/home/user/00_workspace/sesc/obj/Linux_obj/Config.o] エラー 1
今度はいろいろとエラーが出ているが,そのうち一つはまたしてもlimits.hが原因だった(結果としてはなんとかなったが,やはり読み込まない原因を最初に確認すべきだったかもしれない).LONG_MAXはlimits.hで定義されたマクロであるため,同様の文をConfig.cppに追加する.また,以降のエラーは恐らくuint32_tが原因で連鎖的に起きてそうな気がするので,そこだけ修正する.EGREPコマンドで確認したところ,uint32_tはplist/plist.hで定義されているのは分かったが,これだけ読み込んでも意味がなかった.ネットで調べたところstdint.hを読み込めば良いらしいので,これを読み込むように修正する.この型を使うこと自体C++では推奨されていないようだったが,typedef unsigned int uint32_t;のようなプロセッサに依存する書き方よりはマシかと思い,素直にstdint.hを取り込む方法をとる.
Before:
     28 #include 
     29 #include 
     30 
     31 #include "alloca.h"
     32 #include "Config.h"
     33 #include "ReportGen.h"
     34 
     35 Config::Record::~Record(void){
After:
     28 #include 
     29 #include 
     30 
     31 #include "alloca.h"
     32 #include "Config.h"
     33 #include "ReportGen.h"
     34 
     35 #include <limits.h>
     36 #include <stdint.h>
     37 
     38 Config::Record::~Record(void){
修正後,再びコンパイルし,ようやくmakeが無事終了した.

設定ファイルのコピー

以上まででプログラムは完成しているが,実際に実行するためには設定ファイルが必要である.設定ファイルは自分で書くか,コピーするか,MAKEコマンドによって自動で最適なものをコピーする方法がある.もちろん初めてなのでMAKEコマンドを使う.

make sesc.conf
Generating sesc.conf from:
/home/user/00_workspace/sesc/./confs/mem.conf
cp /home/user/00_workspace/sesc/./confs/mem.conf sesc.conf
cp /home/user/00_workspace/sesc/./confs/shared.conf .

実行確認

実行確認もまたMAKEコマンドで行える.オプションを指定することで,プログラムの呼び出しや引数指定を行ってくれるのだ.

user% make testsim
make[1]: `sesc.mem' は更新済みです
./sesc.mem -h0x800000 -csesc.conf
/home/user/00_workspace/sesc/./tests/crafty <
/home/user/00_workspace/sesc/./tests/tt.in
static[0x1008db40-0x101b3dd4] heap[0x101b4000-0x109b4000]
stack[0x109b4000-0x111ac000] -> [0x2b06b4000000-0x2b06b511e4c0]

Crafty v14.3

sesc_simulation_mark 0 (simulated) @30177037
White(1): sesc_simulation_mark 1 (simulated) @30226502
White(1): pondering disabled.
sesc_simulation_mark 2 (simulated) @30297741
White(1): noise level set to 0.
sesc_simulation_mark 3 (simulated) @30321294
White(1): search time set to 99999.00.
sesc_simulation_mark 4 (simulated) @30423503
White(1): verbosity set to 5.
sesc_simulation_mark 5 (simulated) @30448854
White(1): sesc_simulation_mark 6 (simulated) @30749824
White(1): search depth set to 2.
sesc_simulation_mark 7 (simulated) @30766129
White(1): 
              clearing hash tables

              depth   time  score   variation (1)
                1   ###.##  -0.67   axb5 c6xb5
                1   ###.##  -0.08   a4a5
sesc_simulation_mark 8 (simulated) @33088601
                1-> ###.##  -0.08   a4a5
                2   ###.##     --   a4a5
                2   ###.##  -0.65   a4a5 f6f5
                2   ###.##  -0.58   axb5 c6xb5 Ne4c5
                2   ###.##  -0.46   Rf1c1 f6f5
                2   ###.##  -0.43   Ra1c1 f6f5
sesc_simulation_mark 9 (simulated) @37835727
                2-> ###.##  -0.43   Ra1c1 f6f5
              time:###  cpu:###  mat:-1  n:833  nps: ####
              ext-> checks:18 recaps:4 pawns:0 1rep:6
              predicted:0  nodes:833  evals:372
              endgame tablebase-> probes done: 0  successful: 0
              hashing-> trans/ref:17%  pawn:83%  used:w0% b0%

White(1): Ra1c1 
              time used: ###.##
sesc_simulation_mark 10 (simulated) @38876482
Black(1): execution complete.

以上でオプションを指定しない場合のSESCのコンパイルは終了である.

sescutilsのコンパイル

投稿日:
タグ:

SESCのためのクロスコンパイラと他もろもろをコンパイル. 環境はSESCのコンパイルと同じ. 構築にはsescutilsという必要なソースとそれをコンパイルするためのスクリプトをまとめたものがあったので,それを利用した. 普通クロスコンパイラの構築には,シミュレートするISAなりエンディアンなりOSの情報なりが必要だが,そのあたりはスクリプトで設定されていたので,コンパイルを通すことだけに集中できた(一応事前にいろいろ調べたが). ただし私はこんなスクリプトを使ったのは初めてだったので,configureのエラーなのかmakeのエラーなのかが一瞬分かり辛かったりする弊害があった.

はじめに

説明は$HOME/workspace/に必要なものを解凍するところから始める.

uesr% tar jxf sescutils.tar.bz2
user% cd sescutils
全体の流れとしては,README.sescに書かれた通りに行う. ただしテキストで使用しているスクリプトの名前と実際のファイル名が微妙に違ったりする(というかテキストとスクリプトの名前が違うのはどうなんだろうか).
user% cd build-mipseb-linux/  
user% ls

build-1-binutils  build-3-glibc  build-5-gdb   build-gmp
build-2-gcc-core  build-4-gcc build-common
各スクリプトの名前には番号が降られており,基本的にこの順番に実行していけば良い. ただし今回はgdbのコンパイルは行わない. これらのスクリプトはsourceコマンドでbuild-commonにある変数を読み込み,それに従って実行する. そのため,README.sescの説明にもあるがbuild-commonの中身を自分用に書き換える必要がある.
Before:
      1 #!/bin/sh
      2 
      3 # common paths
      4 GNUSRC=$HOME/projs/gnu/src
      5 PREFIX=$HOME/projs/gnu/install
      6 PATH=$PREFIX/bin:$PATH
      7 KERNHEADERS=$GNUSRC/linux-headers
      8 
      9 # common parameters
     10 BUILD="`uname -p`-`uname -s | sed s/Linux/linux/ | sed
      s/Darwin/darwin/`"
After:
      1 #!/bin/sh
      2 
      3 # common paths
      4 GNUSRC=$HOME/workspace/sescutils/src
      5 PREFIX=$HOME/mypath
      6 PATH=$PREFIX/bin:$PATH
      7 KERNHEADERS=$GNUSRC/linux-headers
      8 
      9 # common parameters
     10 BUILD="`uname -p`-`uname -s | sed s/Linux/linux/ | sed
     s/Darwin/darwin/`"

binutils

1つ目はbinutilのコンパイル. 結論からいうとbinutilsは,コンパイル自体に手間はなかった. ただしconfigureやmakeを実行する前のスクリプトで躓いた.

user% ./build-1-binutils

./build-1-binutils: 3: source: not found
./build-1-binutils: 12: /binutils/configure: not found
./build-1-binutils: 13: all: not found
開始してすぐsourceがないと言われた. しかし,実行するシェルをZSHに変えたら即解決.
user% zsh ./build-1-binutils
スクリプト終了と共にprefixで指定した先にobjdumpなどのマニュアルやプログラムがインストールされる. 他のスクリプトも同様にsourceを使用しているので,zshで起動する.

gcc-core

2つ目はgcc-coreのコンパイル.

user% zsh ./build-2-gcc-core
	・
	・
	・
gcc -c   -g -O2 -DIN_GCC -DCROSS_COMPILE  -W -Wall -Wwrite-strings
-Wstrict-prototypes -Wmissing-prototypes -pedantic -Wno-long-long
-Wno-error  -DHAVE_CONFIG_H
-I. -I. -I/home/user/workspace/sescutils/src/gcc-3.4/gcc/gcc
-I/home/user/workspace/sescutils/src/gcc-3.4/gcc/gcc/. -I/home/user/workspace/sescutils/src/gcc-3.4/gcc/gcc/../include
c-parse.c -o c-parse.o
gcc: c-parse.c: そのようなファイルやディレクトリはありません
gcc: no input files
make[1]: *** [c-parse.o] エラー 1
make[1]: ディレクトリ
`/home/user/workspace/sescutils/build-mipseb-linux/obj/gcc-core-build/gcc'
から出ます
make: *** [all-gcc] エラー 2
スクリプトの出力を読んでいくと,無視されているエラーを発見(ここには載せてない部分). エラーはコンパイラコンパイラのBISONが原因だった. どうやらBISONの古いバージョンで読み取れていたものが新しいバージョンになって読み取れなくなったらしい(そうなった経緯は知らないが). BISONは前に扱う機会があったが,もう忘れているので素直にWEBで検索した. http://www.microchip.com/forums/m416697-print.aspx

BISONのソースコード(../src/gcc-3.4/gcc/gcc/c-parse.in)を書き換える.

Before:
   1731 structsp_attr:
   1732     struct_head identifier '{'
   1733     { $$ = start_struct (RECORD_TYPE, $2);
   1734       /* Start scope of tag before parsing components.  */
   1735     }
   1736     component_decl_list '}' maybe_attribute
   1737     { $$ = finish_struct ($<ttype>4, nreverse ($5),
   1738               chainon ($1, $7)); }
   1739   | struct_head '{' component_decl_list '}' maybe_attribute
   1740     { $$ = finish_struct (start_struct (RECORD_TYPE, NULL_TREE),
   1741               nreverse ($3), chainon ($1, $5));
   1742     }
   1743   | union_head identifier '{'
   1744     { $$ = start_struct (UNION_TYPE, $2); }
   1745     component_decl_list '}' maybe_attribute
   1746     { $$ = finish_struct ($<ttype>4, nreverse ($5),
   1747               chainon ($1, $7)); }
   1748   | union_head '{' component_decl_list '}' maybe_attribute
   1749     { $$ = finish_struct (start_struct (UNION_TYPE, NULL_TREE),
   1750               nreverse ($3), chainon ($1, $5));
   1751     }
   1752   | enum_head identifier '{'
   1753     { $$ = start_enum ($2); }
   1754     enumlist maybecomma_warn '}' maybe_attribute
   1755     { $$ = finish_enum ($<ttype>4, nreverse ($5),
   1756             chainon ($1, $8)); }
   1757   | enum_head '{'
   1758     { $$ = start_enum (NULL_TREE); }
   1759     enumlist maybecomma_warn '}' maybe_attribute
   1760     { $$ = finish_enum ($<ttype>3, nreverse ($4),
   1761             chainon ($1, $7)); }
After:
   1731 structsp_attr:
   1732     struct_head identifier '{'
   1733 //    { $$ = start_struct (RECORD_TYPE, $2);
   1734     { $<ttype>$ = start_struct (RECORD_TYPE, $2);
   1735       /* Start scope of tag before parsing components.  */
   1736     }
   1737     component_decl_list '}' maybe_attribute
   1738     { $$ = finish_struct ($<ttype>4, nreverse ($5),
   1739               chainon ($1, $7)); }
   1740   | struct_head '{' component_decl_list '}' maybe_attribute
   1741     { $$ = finish_struct (start_struct (RECORD_TYPE, NULL_TREE),
   1742               nreverse ($3), chainon ($1, $5));
   1743     }
   1744   | union_head identifier '{'
   1745 //    { $$ = start_struct (UNION_TYPE, $2); }
   1746     { $<ttype>$ = start_struct (UNION_TYPE, $2); }
   1747     component_decl_list '}' maybe_attribute
   1748     { $$ = finish_struct ($<ttype>4, nreverse ($5),
   1749               chainon ($1, $7)); }
   1750   | union_head '{' component_decl_list '}' maybe_attribute
   1751     { $$ = finish_struct (start_struct (UNION_TYPE, NULL_TREE),
   1752               nreverse ($3), chainon ($1, $5));
   1753     }
   1754   | enum_head identifier '{'
   1755 //    { $$ = start_enum ($2); }
   1756     { $<ttype>$ = start_enum ($2); }
   1757     enumlist maybecomma_warn '}' maybe_attribute
   1758     { $$ = finish_enum ($<ttype>4, nreverse ($5),
   1759             chainon ($1, $8)); }
   1760   | enum_head '{'
   1761 //    { $$ = start_enum (NULL_TREE); }
   1762     { $<ttype>$ = start_enum (NULL_TREE); }
   1763     enumlist maybecomma_warn '}' maybe_attribute
   1764     { $$ = finish_enum ($<ttype>3, nreverse ($4),
   1765             chainon ($1, $7)); }
書き換え後再びスクリプトを実行し,無事終了した.

glibc

3つ目はglibcのコンパイル. 先に言うとglibcの2箇所の修正を行う.

user% zsh build-3-glibc
checking build system type... Invalid configuration `unknown-linux':
machine `unknown' not recognized
configure: error: /bin/bash
/home/user/workspace/sescutils/src/glibc-2.3.2/scripts/config.sub
unknown-linux failed
make: *** ターゲット `all' を make するルールがありません.  中止.
構築対象のマシンが分からないと言われた. ぱっと見修正箇所が分からなかったので調べてみた. 原因はbuild-3-glibcのBUILD変数の値だった(World's Appreciated Kitsh!より). このページではBUILD変数をi686-linuxに書き換えていたが,自分の環境ではgccのvオプションでがx86_64-linux-gnuになっていたので,それに書き換えた.
Before:
      1 #!/bin/sh
      2 
      3 source ./build-common
      4 
      5 GLIBC=glibc-2.3.2
      6 GLIBC_THREADS=glibc-linuxthreads-2.2.5
      7 GLIBC_HEADERS=glibc-kernheaders-2.4.15pre9
      8 GLIBC_BUILD_DIR=obj/glibc-build
      9 
     10 rm -rf $GLIBC_BUILD_DIR
     11 mkdir -p $GLIBC_BUILD_DIR
     12 cd $GLIBC_BUILD_DIR
     13 export CFLAGS="-O2 -mips2 -mabi=32 -fno-PIC -mno-abicalls"
     14 export CZFLAGS="-mips2 -mabi=32 -fno-PIC -mno-abicalls"
     15 $GNUSRC/$GLIBC/configure --build=${BUILD} --host=${TARGET}
      --prefix=${PREFIX}/${TARGET} \
     16   --disable-shared --with-headers=$KERNHEADERS --enable-add-ons
      --enable-static-nss \
     17   --disable-profile 2>1 | tee ../glibc.conf.log
     18 $MAKE all install 2>1 | tee ../glibc.gmake.log
     19 cd ../..
     20 
After:
      1 #!/bin/sh
      2 
      3 source ./build-common
      4 
      5 GLIBC=glibc-2.3.2
      6 GLIBC_THREADS=glibc-linuxthreads-2.2.5
      7 GLIBC_HEADERS=glibc-kernheaders-2.4.15pre9
      8 GLIBC_BUILD_DIR=obj/glibc-build
      9 
     10 rm -rf $GLIBC_BUILD_DIR
     11 mkdir -p $GLIBC_BUILD_DIR
     12 cd $GLIBC_BUILD_DIR
     13 export CFLAGS="-O2 -mips2 -mabi=32 -fno-PIC -mno-abicalls"
     14 export CZFLAGS="-mips2 -mabi=32 -fno-PIC -mno-abicalls"
     15 $GNUSRC/$GLIBC/configure --build=x86_64-linux-gnu --host=${TARGET}
      --prefix=${PREFIX}/${TARGET} \
     16   --disable-shared --with-headers=$KERNHEADERS --enable-add-ons
      --enable-static-nss \
     17   --disable-profile 2>1 | tee ../glibc.conf.log
     18 $MAKE all install 2>1 | tee ../glibc.gmake.log
     19 cd ../..
     20 

修正して再びコンパイルするも,また失敗.

	・
	・
	・
In file included from version.c:33:
/home/user/workspace/sescutils/build-mipseb-linux/obj/glibc-build/csu/version-info.h:1:
	error: missing terminating " character
/home/user/workspace/sescutils/build-mipseb-linux/obj/glibc-build/csu/version-info.h:2:
	error: missing terminating " character
/home/user/workspace/sescutils/build-mipseb-linux/obj/glibc-build/csu/version-info.h:3:
	error: missing terminating " character
/home/user/workspace/sescutils/build-mipseb-linux/obj/glibc-build/csu/version-info.h:4:
	error: missing terminating " character
version.c:40: error: syntax error before string constant
make[2]: ***
	[/home/user/workspace/sescutils/build-mipseb-linux/obj/glibc-build/csu/version.o]
	エラー 1
make[2]: ディレクトリ
	`/home/user/workspace/sescutils/src/glibc-2.3.2/csu'
	から出ます
make[1]: *** [csu/subdir_lib] エラー 2
make[1]: ディレクトリ
	`/home/user/workspace/sescutils/src/glibc-2.3.2' か
	ら出ます
make: *** [all] エラー 2
verion-info.hが原因らしいが,今度もまた分かり辛いエラーだった. しかし,幸いこれも同様に先ほどのページに解決方法が書いてあった. ../src/glibc-2.3.2/csu/version.cのversion-info.hを取り込む部分をコメントアウトするだけで良いらしい(普通こんなことしたらコンパイルが通らなそうだが結果から言えばコンパイルが通った上に今のところ実行にも支障はない).
Before:
     32 Compiled by GNU CC version "__VERSION__".\n"
     33 #include "version-info.h"
     34 #ifdef GLIBC_OLDEST_ABI
     35 "The oldest ABI supported: " GLIBC_OLDEST_ABI ".\n"
     36 #endif
After:
     32 Compiled by GNU CC version "__VERSION__".\n"
     33 //#include "version-info.h"
     34 #ifdef GLIBC_OLDEST_ABI
     35 "The oldest ABI supported: " GLIBC_OLDEST_ABI ".\n"
     36 #endif

gcc

最後にgccのコンパイルを行ったが,これは何のトラブルもなかったので省略する.

実行確認

以上まででクロスコンパイラの構築は終わりで,次からはクロスコンパイラが正常かのテストである. 冗長に関数を使ったコードを書いてコンパイルし,それをsescで実行してみた. 実行したソースコードは以下の通りである.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char &argv[])
{
  write(1, "Hello, World\n", strlen("Hello, World\n"));
  exit(0);
  return 0;
}
PATHを通し,いざコンパイル. README.sescによると,gccに使用するオプションを加えたものが次の通りである.
user% mipseb-linux-gcc -mips2 -mabi=32 -Wa,-non_shared
-mno-abicalls --static
-Wl,--script=/home/user/mypath/mipseb-linux/lib/ldscripts/mint.x 
試しに実行すると無事コンパイルもsescでの実行もできたので,引数を受け取れるように変更して.zshrcに加えた.

twittering-modeの設定

投稿日:
タグ:

twittering-modeの設定. 以前に一度行ったが,後輩に聞かれたでメモ.

まずtwittering-modeを入手したら解凍し,任意のディレクトリに移動する(別に任意のディレクトリで解凍しても良い).

user% tar zxvf twittering-mode-2.0.0.tar.gz
user% mv twittering-mode-2.0.0.tar.gz /usr/local/share/.
前述の例は一般的とは思えないが,メモなのでそこは無視する.

twittering-modeの実行まで

次に,.emacsを開いて以下に2つの文を加える.

(add-to-list 'load-path "/usr/local/share/twittering-mode-2.0.0")
(require 'twittering-mode)

そして,emacs上でtwittering-modeを起動する. twittering-modeはtwitだけでも実行可能である. M-xから入力しても良いが,ここではコマンドラインから直接実行する.

user% emacs -f twit

実行すると,次のような入力画面が要求される.

Open authorization URL with browser? (using `browse-url')(y or n) 
私の場合firefoxに設定されているので,ここでyを押すとfirefoxが実行され,アプリケーションとの連携を許可するための画面が開く. この後の処理はWEBブラウザでログインしているかどうかなどで手順が変わる.とはいえ,ユーザ名やパスワードの入力や連携を承諾するかを聞かれる程度である. 前述の処理が終わるとPINコードが表示される. ちなみに文字ではないので,暗記するか見ながらemacsに入力する必要がある(emacsにウィンドウを切り替えてそのまま入力すれ良い).

twittering-modeの自動ログイン設定

twittering-modeは,自動ログイン設定しなければ前述のPINコードの入力を実行の度行わなければならない. 自動ログイン設定は,.emacsに変数の設定を書いてやれば良い. ログイン状態で,describe-variableを実行し,twittering-oauth-access-token-alistの値を調べる.

M-x describe-variable
Describe variable: twittering-oauth-access-token-alist
twittering-oauth-access-token-alist is a variable defined in
`twittering-mode.el'.
Its value is (("oauth_token"
. "##############################################")
 ("oauth_token_secret" . "#################################")
 ("user_id" . "#######################")
 ("screen_name" . "#############################"))


Documentation:
Not documented as a variable.
するとこのような画面が出力される(###の部分は実際は別の値).

これをコピーし,.emacsに次のような文を追加する.

(setq twittering-account-authorization 'authorized)
(setq twittering-oauth-access-token-alist 
'(("oauth_token"
. "##############################################")
 ("oauth_token_secret" . "#################################")
 ("user_id" . "#######################")
 ("screen_name" . "#############################")))
以上で次からはPINコードの入力が不要になり,実行するとすぐにログインされる.

patchとdiff

投稿日:
編集日:
タグ:

diffコマンドを使ったパッチファイルの作り方をメモ.

パッチ

以下の例はold.txtの中身をnew.txtの中身に変更するためのパッチファイルの作成.

パッチファイルの作成

パッチファイルは、以下のようにdiffコマンドで生成できる。 それぞれのファイルの中身は次の通り.

user% diff old.txt new.txt > hoge.patch 
old.txt:
hoge
fuga
piyo
new.txt:
hoge
foo
piyo

patchファイルを適応

指定したファイルにパッチを適応するには、以下のように行う。

user% patch old.txt < hoge.patch 

この方法では、オリジナルファイルが完全に上書きされてしまう。 もしオリジナルファイルを保存したい場合、"-b"オプションを使用すれば良い。

user% patch -b old.txt < hoge.patch 

unified形式

ファイルを指定せずに、予め決められたファイルを修正するには、"-u"オプションを使用すれば良い。

user% diff -u old.txt new.txt 
user% patch < hoge.patch 

SPEC CPU 2006

投稿日:
修正日:
タグ:

SPEC CPU 2006のプログラムをクロスコンパイルした時のことをメモ. ただし,fortranのクロスコンパイラとperlのライブラリの用意がまだなので,私が確認したのはそれ以外のプログラムだけ.

SPEC CPU 2006 インストール

メモといっても付属のドキュメントが分かりやすく,基本的にそれに従っていけば良い.ドキュメントは"Docs/install-guide-unix.html". 以降の処理も"Docs/"内のドキュメントを見れば良いが,今回はクロスコンパイルなのでコンパイラやライブラリを指定する必要がある.

まずスクリプトを使って,任意のディレクトリに作業環境を構築する.

user% ./install.sh -d $HOME/workspace/cpu2006
install.shは対話式に任意のディレクトリに必要なファイルをインストールすることができる. また,前述で使用しているdオプションを指定することで,インストール先のディレクトリを対話の前に設定することができる.

SPEC CPU 2006 (クロス)コンパイル

まずインストールした場所へ移動し,shrcかcshrcを読み込む.読み込み方は次に示す方法以外に,sourceコマンドを使用する方法などがある.

user% cd $HOME/workspace/cpu2006
user% . ./shrc

これによりパスが通りrunspecコマンドが使用可能になる.runspecの詳しい説明についてはドキュメントに詳しく書かれている.それに従うと,例えばbzip2をコンパイルしようとした場合,次のように指定する.

user% runspec --config=$PWD/config/Example-sgi_mips32.cfg --tune=base bzip2
configオプションは対象とするマシンの環境を指示するための設定ファイルを指定する.そのためクロスコンパイルする場合は,このファイルにコンパイラや使用する引数を指定すれば良いようである(CCやCXX,FCの部分や引数の部分).あるいは賢い方法とは思えないが,SPEC CPU 2006の各プログラムのコンパイルでは"benchspec/Makefile.defaults"が利用されるため,これを直接書き換えることでも可能なようだ.

SPEC CPU 2006では,CとC++,fortranのコードがある. また,perlはサードパーティのライブラリを使用しているためそれらのビルドも必要のようだ.

実行確認

実行方法はプログラムによってさまざまだが,前述のコマンドで無事コンパイルできたものは,"benchspec/CPU2006/"の各ディレクトリの中の"exe"ディレクトリ内に生成される. 例えば作成したbzip2をsescから実行すると,次のようになる.

user% ./sesc.mem
 $HOME/workspace/cpu2006/benchspec/CPU2006/401.bzip2/exe/bzip2_base.mips2-32
  $HOME/workspace/cpu2006/benchspec/CPU2006/401.bzip2/data/all/input/input.combined
各プログラムはそれぞれの特性を持つが,実行ファイルや入力ファイル,ドキュメントなどは皆"benchspec/CPU2006"の各ディレクトリ内の,"exe"や"data","Docs"というディレクトリに配置される.

追記:sescutilsのコンパイラでは,fortran90や95はコンパイルできない模様(少なくとも標準のままでは)。

SESCでのマルチコアプロセッサのシミュレーション

投稿日:
タグ:

SESCで複数のコアで複数のスレッド(またはプロセス)を同時実行しようとした時のことをメモ.結論からいうとこの記事は調査不足のまま書いているが,一応メモしておくことにした.今回の内容で主に参考にした情報源はDongrui's Homepageあるメール である.

マルチコアを使用するためのAPI

1つのプログラムで複数のスレッドを持つようなプログラムはSESCが提供するAPIを使用して記述することができる. そのAPIは"src/libapp/sesc_thread.c"で定義されており,"src/libapp/sescapi.h"を取り込み,先のCファイルから生成したオブジェクトとリンクすればAPIを利用できる. APIについては"docs/README.libapp"や"sescapi.h"で説明されている.

sescapi.hの一部(...は省略)
void sesc_init(void);
...
int32_t  sesc_spawn(void (*start_routine) (void *), void *arg, int32_t flags);
...
void sesc_exit(int32_t err);
これらのAPIは中でpthreadによって何か行われているらしく,提供される関数も同様なものだった.APIを使用したコードは次のようになる.
#include <stdio.h>
#include "sescapi.h"
#define THREADS_NUM        4
 
void *hello(void *arg)
{
  printf("Hello World!\n");
  sesc_exit(0);
  return NULL;
}
  
int main()
{
  int i;
  sesc_init();
  for(i=0; i<THREADS_NUM; i++){
    sesc_spawn((void*) *hello, NULL, SESC_FLAG_NOMIGRATE|SESC_FLAG_MAP|i);
  }
  sesc_exit(0);
  return 0;
}
これが書かれたファイルは"sescapi.h"と"sesc_thread.o"と同じディレクトリにあるものとする. コードの説明らは省略するが,これをクロスコンパイルしたものをSESCで実行した後,前と同様にレポートを出力する.
user% ./scripts/report.pl -last

# Bench : ./sesc.mem ./test/test2 
# File  : sesc_test2.yCPolo    :       Sun Aug 26 04:25:10 2012
      Exe Speed        Exe MHz         Exe Time         Sim Time
      (5000MHz)
    720.300 KIPS      7.3911 MHz       0.010 secs       0.015 msec
Proc  Avg.Time BPType       Total          RAS           BPred
BTB            BTAC
   0   252.198  hybrid       54.03% (100.00% of  10.63%)  48.56% (
   48.77% of  34.82%)   0.00% 
   1   239.483  hybrid       61.07% (100.00% of   9.06%)  57.20% (
   49.58% of  39.93%)   0.00% 
   2   239.069  hybrid       61.07% (100.00% of   9.06%)  57.20% (
   49.58% of  39.93%)   0.00% 
   3   238.655  hybrid       61.07% (100.00% of   9.06%)  57.20% (
   49.58% of  39.93%)   0.00% 
           nInst     BJ    Load   Store      INT      FP  : LD Forward ,
	   Replay : Worst Unit (clk)
   0        2925  19.93%  19.93%  12.41%  47.66%   0.07%  :     13.04%
   731 inst/repl  :  ALUIssueX 0.06 
   1        1426  20.90%  23.63%  13.88%  41.58%   0.00%  :     19.58%
   713 inst/repl  :  ALUIssueX 0.09 
   2        1426  20.90%  23.63%  13.88%  41.58%   0.00%  :     19.58%
   713 inst/repl  :  ALUIssueX 0.09 
   3        1426  20.90%  23.63%  13.88%  41.58%   0.00%  :     20.18%
   713 inst/repl  :  ALUIssueX 0.09 
Proc  IPC  Active       Cycles  Busy   LDQ   STQ  IWin   ROB  Regs Ports
TLB  maxBr MisBr Br4Clk  Other
   0  0.04  99.98        73898   1.3   0.0   0.0   0.1   0.0   0.0   0.0
   10.6   0.0   87.9    0.0    0.1 
   1  0.05  41.47        30651   1.6   0.0   0.0   0.1   0.0   0.0   0.0
   6.9   0.0   91.3    0.0    0.1 
   2  0.05  41.41        30604   1.6   0.0   0.0   0.1   0.0   0.0   0.0
   6.9   0.0   91.2    0.0    0.1 
   3  0.05  41.34        30556   1.6   0.0   0.0   0.1   0.0   0.0   0.0
   7.0   0.0   91.2    0.0    0.1 
################################################################################
Proc  Cache Occ MissRate (RD, WR) %DMemAcc MB/s : ... 
   0  DL1 0.0   6.24% ( 3.7%, 2.5%) 37.70%  0.26GB/s :  Bus 749.009 MB/s
   : L2 0.0  43.22% (43.2%, 0.0%) 18.50%  0.88GB/s :  MemBus 883.224
   MB/s : 
   1  DL1 0.0   7.02% ( 4.7%, 2.3%) 20.10%  0.16GB/s :  Bus 749.009 MB/s
   : L2 0.0  43.22% (43.2%, 0.0%) 18.50%  0.88GB/s :  MemBus 883.224
   MB/s : 
   2  DL1 0.0   7.21% ( 4.9%, 2.3%) 20.10%  0.16GB/s :  Bus 749.009 MB/s
   : L2 0.0  43.22% (43.2%, 0.0%) 18.50%  0.88GB/s :  MemBus 883.224
   MB/s : 
   3  DL1 0.0   7.11% ( 4.7%, 2.4%) 19.83%  0.16GB/s :  Bus 749.009 MB/s
   : L2 0.0  43.22% (43.2%, 0.0%) 18.50%  0.88GB/s :  MemBus 883.224
   MB/s : 
################################################################################
Proc  Cache Occ MissRate (RD, WR) %DMemAcc MB/s : ... 
   0  IL1 0.0  19.65% (19.7%, 0.0%) 26.92%  0.58GB/s : L2 0.0  43.22%
   (43.2%, 0.0%) 18.50%  0.88GB/s :  MemBus 883.224 MB/s : 
   1  IL1 0.0  16.67% (16.7%, 0.0%) 13.17%  0.24GB/s : L2 0.0  43.22%
   (43.2%, 0.0%) 18.50%  0.88GB/s :  MemBus 883.224 MB/s : 
   2  IL1 0.0  16.67% (16.7%, 0.0%) 13.17%  0.24GB/s : L2 0.0  43.22%
   (43.2%, 0.0%) 18.50%  0.88GB/s :  MemBus 883.224 MB/s : 
   3  IL1 0.0  16.67% (16.7%, 0.0%) 13.17%  0.24GB/s : L2 0.0  43.22%
   (43.2%, 0.0%) 18.50%  0.88GB/s :  MemBus 883.224 MB/s : 
このように複数のコアを動かすこと自体は成功した.

既存のプログラムに対して

元々あるマルチスレッドプログラムを使う場合はどうするか,または複数のプログラムをどうやって並列して動かすか(そもそもそれができるのか)は結局まだ分かっていない.そこで,とりあえず複数のプログラムのソースコードを持ってきてmain関数を別の関数名に置き換え,新しく用意したmain関数付きのオブジェクトとリンクさせることにしてみた.こうすれば,前述で示した方法で複数のコアを使用することができると思ったからだ.結果はここに載せないが,一応この方法を用いれば複数のプログラムをそれぞれのコアで実行することはできた(色々と気になることはあるが). ちなみにこの方法を用いる前にexec関数らを使った方法を試みたが,それらの関数はSESCで実装されていなかったため上手くいかなかった.

twittering-modeの使い方

投稿日:
タグ:

頻繁にtwittering-modeの基本的な使い方を探しにいくので,この際いろいろとメモ. ここでは「次」は下の方,「前」は上の方を意味する.

キー機能
vカーソルのタイムラインを表示
V人にのタイムラインを表示
Ctrl+cCtrl+m非公式RT
Ctrl+uCtrl+cCtrl+m公式RT
Ctrl+cCtrl+q検索
dダイレクトメッセージ
Ctrl+cD自分のツイート削除
Ctrl+m返信
Enter
Ctrl+cCtrl+lλかわいいよλをポスト(間違って押さないようにメモ)
Ctrl+cCtrl+u自分のタイムラインを表示
Ctrl+cCtrl+fフレンドタイムラインを表示
Ctrl+cCtrl+rリプライタイムラインを表示
iアイコン表示/非表示
g現在のタイムラインを更新
j次のツイートに移動
k前のツイートに移動
nカーソルのユーザの次のツイートに移動
pカーソルのユーザの前のツイートに移動
Ctrl+cCtrl+vリンクを開く
f次のバッファに移動
b前のバッファに移動
Tab次のリンクに移動
Shift+Tab前のリンクに移動
uツイート
G現在のバッファの最後に移動
q現在のバッファを終了
H現在のバッファの先頭に移動
フォローする
フォロー解除
twittering-modeのよく利用するキー

結局フォローや解除のコマンドが割り当てているのかどうかはよく分からなかったので,自分で割り当てた.コマンドはemacsのコマンド上でtwitteringまで打ってタブ押して調べたらそれっぽいのが簡単に見つかった.

  • twittering-follow
  • twittering-unfollow
試してみたがこれで正解だった.

"F"にtwittering-follow,"Ctrl-c F"にtwittering-unfollowを割り当てた. 割り当ては.emacsに次ようなコードを足すだけ.

(let ((km twittering-mode-map))
  (define-key km (kbd "F") 'twittering-follow)
  (define-key km (kbd "\C-cF") 'twittering-unfollow)
  nil)
これで終わり.

C メモ

投稿日:
タグ:

目次

よくCの開発者本の特定のページを開くので,今回まとめてみた.ついでによく質問される内容もメモ.

型変換(キャスト)

Cでは,型によって利用可能な演算やその振る舞いが異なる. 例えば次のようなint型のaに対しint型のbを加算する場合,aには5が加算される.

int a = 5;
int b = 10;
a += b;
しかし次のようにint型のポインタのaに対しint型のbを加算する場合,aには,aのポインタが指すint型のサイズに5を掛けた値が加算される.
int c;
int *a = &c;
int b = 5
a += b;
例えばint型が4バイトの時,aに20加算される.

また,Cでは関数の多重定義もできないため,関数の返り値やそれに与える引数の型も関数毎に固定される.

これらのような場合,Cでは,プログラマが型変換演算子を使うことによって明示的に,あるいはコンパイラによって暗黙的に型変換を行う. 型変換とは言葉通りある型のオブジェクトを別の型のオブジェクトに変換することである. 変換後のオブジェクトは,変換前のオブジェクトとはサイズも値も異なる. 例えば,次のようにfloat型の変数をint型の変数に型変換して代入する場合,float型は小数を切り捨てint型に格納できるように変換される.

int i;
float f = (float)1.5;
i = f;
この変換は見た目ほど単純ではない. なぜならプロセッサ内部でデータを保持するための表現方法が同じとは限らないからである. 例えば,プロセッサがint型を32ビットの2の補数表現,float型をIEEE754の32ビット表現で保持する場合, float型の1.5は「00111111110000000000000000000000」,1.0は「00111111100000000000000000000000」になる. これに対し,int型の1は「00000000000000000000000000000001」で表現される. そのためこの場合,見た目上の値の変換だけでなく,変換後の値を変換後の表現方法で表現しなければならない. また,この例ではintとfloatは共に同じサイズであるが,型変換は異なるサイズへの変換も行われる.

整数型から整数型への変換

char型やshort型,enum型のオブジェクトは,整数が利用可能な演算式で利用しても良い. その場合,int型で表現可能な時はint型に,そうでなければunsigned int型に変換される. これを整数への格上げと呼ぶ.

変換前の値が負でなく,かつ変換後の符号が無く型サイズが等しいか大きい場合,値は変わらない. 型サイズが小さい場合,余分な分だけ上位ビットを切り捨てる(変換後の型で表現可能な値+1で割った余り). 変換前の値が負の場合,変換後のサイズが小さければ,変換前のビット列を上位ビットを切り捨てられ,変換後のサイズが大きければ,ゼロ拡張か符号拡張される. このように計算されたビット列が変換後の値となるため,変換前後の値は大きく異なる.

変換後が符号付きの場合,変換後の型で表現可能であれば値は不変である. 例えば,「変換後の型サイズが大きい場合」や,「変換前後とも符号付きで型サイズが等しい場合」が挙げられる. また,型サイズが小さい場合であっても変換後で表現可能であれば,値は変わらないが,そうでない場合は処理系に依存する.

整数と浮動小数

浮動小数型から整数型へ変換する場合,小数部は無視される. この時,結果が整数型で表現できない場合のふるまいは不定である. 一例として,負の浮動小数値から符号無し整数値への変換の結果は処理系に依存する.

整数型から浮動小数型へ変換する場合,その値が表現可能な範囲であればその値か,処理系が決めるそれに最も近似な値になる. 表現可能な範囲でない時,ふるまいは不定である.

浮動小数型から浮動小数型への変換

精度の低い浮動小数型から高いまたは同じ浮動小数型へ変換される場合,その値は変わらない.

精度の高い浮動小数型から低い浮動小数型へ変換される場合,表現可能であれば表現し,表現可能な範囲であれば,処理系が決めるそれに最も近い値になる. 表現可能な範囲でない時,その結果は処理系に依存する(丸め).

ポインタと整数

ポインタは,それを保持するのに十分大きい整数型に明示的に変換して良い. 必要とされるサイズは,処理系に依存する.

整数型は,ポインタに明示的に変換できる.

ある型へのポインタは,別の型へのポインタへ変換できる. その結果できるポインタの先のオブジェクトが正しく整列されていない場合,処理系によってはアドレス割り込みを発生させる.

ある関数型へのポインタは,別の関数型へのポインタへ変換できる. ただし,変換されたポインタで指される関数の呼び出す場合の振る舞いは処理系に依存する.

void

voidから異なる型への変換は行うことができない.

また,ある型からvoidへの変換は可能であるが,それは変換後の値を捨てることを意味する.

void*

オブジェクトへの任意のポインタは,情報を失うことなくvoid*(voidポインタ)へ変換,または逆変換できる.

算術変換

特定の異なる型の演算では,暗黙的に型変換が行われる(算術変換). この時,それらの項の型から,全ての項の値を表現可能な型が選ばれる. 例えば,いずれかの項が浮動小数型の時,残りの型もその型と同じになる. また,整数型同士や浮動小数型同士の場合,大きい型に変換される. ただし,整数型同士か浮動小数型同士であり型のサイズも同じであるが符号の有無が異なる時,符号の有る項の型が,符号の無い項の値を表現できるならば,符号の有る項の型が選ばれる. 表現不可能な場合,符号のある項の型の符号を無しにした型に変換する.

型宣言

型宣言は左から右に結合され,次の優先順位(数字が低いほど優先順位が高い)に従って読む.

  1. ()
  2. []
  3. *
宣言された型を言葉にする際,英語では前述の法則に従った順に,日本語ではその逆に読む. 以下に具体例を示す.
C言語char *argv[]
英語argv is an array of pointers to char
日本語argvはchar型へのポインタの配列

また,複雑な宣言の例もいくつか示す.

C言語英語日本語
char (*p)[]p is pointer to an array of char type objectspはchar型のオブジェクトの配列へのポインタ
char p[5][10]p is an array of 5 arrays of 10 char type objectspはchar型のオブジェクトを10個持つ配列を5個持つ配列
int p()p is a function returning an int type objectpはint型のオブジェクトを返す関数
int *p()p is a function returning a pointer to an int type objectpはint型のオブジェクトへのポインタを返す関数
int (*p)()p is a pointer to a function returning an int type objectpはint型のオブジェクトを返す関数へのポインタ
int (*p[5])()p is an array of pointers to a function returning an int type objectpはint型のオブジェクトを返す関数へのポインタの配列
int (*p[5])[10]p is an array of 5 pointers to an array of 10 int type objectspは10個のint型のオブジェクトを持つ配列への5つのポインタの配列

整数

整数リテラルは,8進数表記は0で,16進数表記は0xで始める. 整数リテラルの型は,接尾子によって決定する. long型の場合,接尾子にlかL,unsignedの場合,接尾子にuかUが付く. また,接尾子なしの10進数の場合,次の候補の中の表現可能なものから,優先順位によって決定する(数字が低い方が優先度が高い).

  1. int
  2. long int
  3. unsigned long int
接尾なしの8進数か16進数の場合,次の候補と優先順位を使用する.
  1. int
  2. unsigned int
  3. long int
  4. unsigned long int

文字

文字リテラルは1つの文字を'で括って表す(例:'a'). 文字は整数型として保持される. そのため,処理系は文字リテラルを'で括った文字の文字コードとして扱う. Cの開発者本によると,その値は非負である. また,多くの場合文字セットはASCIIコードである. ただし言語依存ではないため,'A'を65(ASCIIコードの'A')と記述する書き方は,移植性を低下させる.

拡張文字の文字リテラルは,頭にLを付ける.

一般的に文字はchar型で保持する. char型はANSI C規格で1バイトと規定されており,処理系で実装されている文字セットの任意の文字を格納するだけの大きさを持つ. char型は整数型であるため,文字リテラル以外も格納できる. ただし格納された値が符号付きか否かは処理系に依存する. 明示的に符号を付ける場合はsigned修飾子,符号なしにする場合はunsigned修飾子を付ける.

文字列

文字列リテラルは,連続する文字を"で括って表される. また,文字列リテラルの実体は,char型オブジェクトの配列で,記憶クラスはstaticである. 同じ内容の文字列リテラルが区別されるかどうかは処理系に依存する. また,文字列リテラルを変更しようとした場合の振る舞いは不定である.

浮動小数

浮動小数リテラルの値は,接尾子がfかFならばfloat型,lかLならば,long double型,それ以外はdoubleを表す.また,eを付けることで指数を指定できる.例えば"1e2"は100,"1e-2"は0.01を表す.

なお浮動小数は2進数と10進数での誤差がある.それゆえ例えば"(0.1+0.1+0.1)==0.3"という比較演算は,10進数で考えると一致することを期待するが,2進数では一致しない.

演算子の意味

よく誤解される演算についてのメモ.
>, >=, ==, !=, <=, <

それぞれ比較しその条件に合えば1を返し,それ以外は0を返す.

&&, ||

&&は二項のいずれかが0の時0を,それ以外は1を返す. ||は二項のいずれかが0でない時1を,それ以外は0を返り値とする.

/, %

両方の項が正である時,余りは正で除数より小さい. それ以外の時,余りの絶対値が除数の絶対値より小さいことのみが保証される.

>>, <<

シフトにはいろいろな種類がある. 一般的なものには,算術シフトと論理シフトがある. これらは左シフトの場合同じ振る舞いだが,右シフトの時,項が負の時振る舞いが異なる. 論理シフトの場合指定された分だけ値を右にシフトし,ゼロ拡張するが,算術シフトの場合符号拡張する.

ANSI Cでは,符号無しや正の整数に対する右シフトの結果はどの環境でも同じである. しかし負の値をシフトする場合の振る舞いは,処理系に依存する.

また,左シフトか右シフトに関わらず,シフト量が負の場合の結果も処理系に依存する.

型や修飾子の省略

Cでは,特定の型や修飾子が省略できる.

  • 関数内の変数を自動(auto)変数にするか静的(static)変数にするか.省略した場合,自動変数として扱われる.
  • 符号の有無(signed/unsigned).省略した場合,符号有り変数として扱われる.
  • 変数や関数の型.省略した場合,int型として扱われる.ただし変数の場合全て省略すると文法エラーになるかもしれない.

名前

変数や関数に利用可能な名前は,1文字目が英字かアンダースコア(_)で,2文字目以降が英字かアンダースコア(_),数字のいずれかである1文字以上の文字列. 付けられる名前の字数に制限はないが,ANSI C規格では,区別されるのは最初の31文字までである.一般的に大文字と小文字は区別される.ただし実際には処理系によって区別される文字数や大文字小文字を区別するかは異なる.

型のサイズ

ANSI Cの規格では,char型が1バイトであることが定められており,他の型のサイズについては処理系依存である. また,バイトとは各プロセッサの処理の最低単位であり,具体的なビット数は決まっていない(多くのマシンで8ビットだが,明確な定義でない). intやchar型などの基本的な型の最大で表現できる値は,limits.hで定義されている.

その他

  • 関数に引数を与える際,引数が評価される順は不定である.
  • 各演算式の項も評価される順は不定である.
  • 構造体のサイズは各メンバの型のサイズの和になるとは限らない(コンパイラによっては,実行の最適化のため各メンバの間を整列しアドレスをある区切りでそろえる場合があるため).この時できる使われていない空間をパッディングと呼ぶ.
  • Cでは,真偽を表す型がなく,0が偽,それ以外が真として扱われる(というか真とか偽とは言わない).
  • Cに"else if"は存在しない.これは,単に2つのifを組み合わせているだけ.
  • 開発者本(ANSI C準拠)によると,コンパイラはifの中に複合文を使わず入れ子でifを書いたような場合,elseを(elseの手前の)一番後ろのifに関連付ける(曖昧なので普通は中括弧で括る).
  • switch文はswitch(条件)の後に文がくる.そのため別にラベル付き文が来なくても文法的に正しい(意味はないが).
  • const付きのポインタはポイント先の値を変更できない.
  • 修飾子volatileを付けた変数はコンパイラによる最適化を抑止される.volatileは組み込みやマルチスレッドのプログラミングなどで使用し,最適化によりアルゴリズムが変更されてしまうのを防ぐ.

MiniSat

投稿日:
タグ:

目次

SATソルバ

充足可能性問題を解決するためのツールとしてSATソルバというソフトウェアがある.SATソルバは与えられた論理式が充足可能かどうかを判断する.充足可能である場合,SATソルバは論理式が真になる時の論理変数の値を1つ返す.多くのSATソルバーでは,入力可能な論理式は乗法標準形のみである.全ての論理式は論理同値な乗法標準形に変換できる.

MiniSat

ここでは,MiniSatというSATソルバについて簡単に説明する.MiniSatは,SAT問題に関わる活動を始める研究者や開発者を助けるために開発された最小かつオープンソースのSATソルバである.MiniSatの最初のバージョンは,C++でコメント文を除くと600行ほど,バージョン2でさえ1500行ほどで記述されている.また,そのソースコードは,読みやすく,拡張がしやすい設計となっている.MiniSatは,MITライセンスの元で配布されており,多くの環境で動作する.MiniSatは,コマンドライン上で実行されるプログラムである.

実行方法は次のような感じである.

user% minisat2 input.cnf output.cnf
ここで,input.cnfは入力ファイル,output.cnfは出力ファイルである.入力ファイルは,DIMACS CNF形式である.

出力ファイルは,1行目に充足可能であるかどうかが記述されている.

SAT充足可能
UNSAT充足不能
SATの場合,更に2行目に全体の論理式が真になる場合の各命題変数の値が1パターン記述されている.各命題の真偽については,DIMACS CNF形式と同様の記述である.

DIMACS CNF形式

The Center for Discrete Mathematics & Theoretical Science(DIMACS)とは,離散数学と利論的コンピュータサイエンスのためのセンタのこと.ラトガース大学,プリンストン大学,AT&Tベル研究所とBellcore社の研究者らが,全米科学財団の科学技術センターとして運営している研究者の協会である. 彼らの挑戦の1つの提案は,共通のインスタンスと解析ツールのテストベッドを提供することによってアルゴリズムや発見手法の試験や比較に要求される労力を減らすことである.この労力を減らすため,標準フォーマットが必要だった.そのフォーマットの1つが,MiniSatの入力ファイル形式として使用されるDIMACS CNF形式である.研究者たちは論理式を変換するための翻訳プログラムをもっと手軽に,またはもっと簡潔に開発することを望んでいる.UNIXのawkの機能は,この作業に特に適しているとして推奨されている.

MiniSatを含むSATソルバの多くは,このフォーマットを入力対象とする.これは,毎年開催されるSATソルバの性能を競争するSAT competitionにおいて,そのフォーマットが指定されていることに起因する.各SATソルバの性能を競う上で入力インターフェースの統一は必要不可欠である.

DIMACS CNF形式のファイルは,ASCII文字から成る.このファイル形式は,以下で述べる種類のいくつかの行をもつ.そして,それらの行は改行文字で終わる.各行の欄は,最小1つのブランクスペースによって分けられる.このファイル形式の各行は,PreambleとClausesによって分類できる.

Preamble

Preambleでは,行の最初が行の種類を決める1つの文字(次にスペースが続く)から始まる.それらのタイプは,次の2つからなる.

Comments行
c This is a comment line.
Problem行

p FORMAT VARIABLES CLAUSES
FORMAT"cnf"が入る.
VARIABLES命題変数の数.
CLAUSES節数.

Clauses

Clausesは,problem行の次の行から開始される.そこで,使用される命題変数名は,1からVARIABLESの番号がつけられる.もちろん,すべての変数が論理式に登場する必要はない.各節は,空白やタブ,改行で区切られる数字の列で表される.変数をiとした時,論理否定するものを-iと表し,そうでないものをiと表す.各節は,0によって終わる.

(X1∨X2)∧X3∧(X4∨X5)∧¬X2に対応するDIMACS CNF形式の入力ファイルの中身と,それをMiniSatに与えた時の出力結果,出力ファイルの中身を示す.

input.cnf
p cnf 5 4
1 2 0 3
4 5 0
-2 0
実行結果
user% minisat input.cnf output.cnf
WARNING: for repeatability, setting FPU to use double precision
============================[ Problem Statistics
]=============================
|
|
WARNING! DIMACS header mismatch: wrong number of clauses.
|  Number of variables:             5
|
|  Number of clauses:               2
|
|  Parse time:                   0.00 s
|
|  Eliminated clauses:           0.00 Mb
|
|  Simplification time:          0.00 s
|
|
|
============================[ Search Statistics
]==============================
| Conflicts |          ORIGINAL         |          LEARNT          |
Progress |
|           |    Vars  Clauses Literals |    Limit  Clauses Lit/Cl |
|
===============================================================================
===============================================================================
restarts              : 1
conflicts             : 0              (-nan /sec)
decisions             : 1              (0.00 % random) (inf /sec)
propagations          : 2              (inf /sec)
conflict literals     : 0              (-nan % deleted)
Memory used           : 8.00 MB
CPU time              : 0 s

SATISFIABLE
output.cnf
SAT
1 -2 -3 -4 5 0

SATソルバによる数独の解法

投稿日:
修正日:
タグ:

注意:この記事はWEBブラウザがCSSに対応していることを前提に書かれています.

数独はSATソルバにより解法できる.とはいえ,充足可能性判定問題はNP完全なので,数独の規模やコンピュータの性能によっては解けない問題もある.

数独

数独は「数字は独身に限る」という名前に由来するペンシルパズルの1つである.

数独のルールはラテン方陣の応用である.ラテン方陣とは,各行と各列にそれぞれ1からnの整数が1回だけ出てくるn×nの行列である.数独はさらに小行列を持つ(必ずしも小行列とは限らないが).小行列はn個の要素を持ち,1からnの整数を1つずつ含む.

数独のルールは次の4種類から成る.この記事では,nの値を4として説明する.

数独の解は,これらの全てを満たす.

各マス(要素)のルール
  • 1つのマスに1から4の整数が入る.
  • 各行の各列の各マスには値が1つだけ入る.
各行のルール
各行各のマスには,各値は1度だけ現れる.
各列のルール
各列の各マスには,各値は1度だけ現れる.
小行列のルール
各小行列の各マスには,各値は1度だけ現れる.
実際の数独の問題は,このような共通ルールに加え,いくつかのマスの値が予め確定しているなどの非共通ルールを持つ.非共通ルールについては,後で言及する.

結論からいうと,各行と列,小行列のルールと,各マスのルールの後者は,皆似たような記述で表現できる.そこで,各マス(要素)のルールの2つを代表として説明し,残りのルールについては各マスのルールの後者との差異だけを紹介する.まずこのルールを命題を用いていくつかの言語で示す.

  • 日本語(自然言語)
  • 述語論理
  • 命題論理
  • 命題論理(CNF)
  • 命題論理(DIMACS CNF形式)
それぞれの表現方法については,ここでは説明しない.これらを用いた表現方法を説明した後,数独のルールの一部を出力するCNF式を出力するRubyプログラムを説明し,最終的に他のルールへの適応方法を紹介する.

日本語(自然言語)の場合

命題には,次のようなものを使用する.

命題: i行目のj列目の値がkである

1行目の1列目のマスを考えた時,それを表す命題は次のように表現できる.

  1. 1行目の1列目のマスの値は1である.
  2. 1行目の1列目のマスの値は2である.
  3. 1行目の1列目のマスの値は3である.
  4. 1行目の1列目のマスの値は4である.
これを参考にして,前述のルールを記述すると,次のように表現できる.
  • "1行目の1列目のマスの値は1"または"1行目の1列目のマスの値は2"または"1行目の1列目のマスの値は3"または"1行目の1列目のマスの値は4"である.
ここで,あえて冗長な表現で記述している理由は,他の言語に対応付ける際に都合が良いからである.

次に,もう1つの各マスのルール「各行の各列の各マスには値が1つだけ入る.」について説明する.そして「各行の各列の各マスには値が1つだけ入る.」というルールは,前述の各命題を参考にすると,次のように記述できる.

  • "1行目の1列目のマスの値は1"または"1行目の1列目のマスの値は2"または"1行目の1列目のマスの値は3"または"1行目の1列目のマスの値は4"である.
  • "1行目の1列目のマスの値が1"ならば,"1行目の1列目のマスの値は2"か"1行目の1列目のマスの値は3"か"1行目の1列目のマスの値は4"でない.
  • "1行目の1列目のマスの値が2"ならば,"1行目の1列目のマスの値は1"か"1行目の1列目のマスの値は3"か"1行目の1列目のマスの値は4"でない.
  • "1行目の1列目のマスの値が3"ならば,"1行目の1列目のマスの値は1"か"1行目の1列目のマスの値は2"か"1行目の1列目のマスの値は4"でない.
  • "1行目の1列目のマスの値が4"ならば,"1行目の1列目のマスの値は1"か"1行目の1列目のマスの値は2"か"1行目の1列目のマスの値は3"でない.
この表現は元のルールと対応する表現とは言い難いが,先に示したルールを満たす.元のルールは述語記号や量化記号,存在記号を用いることで表現できる.

述語論理の場合 Part.1

前述の日本語で示した表現を今度は述語論理で表現する. ここでは,次のような記号を使用する.

補助記号: ( ) ,
論理記号: ∨ ∧ ¬ ⇒
対象変数: i j k

Cell(i,j,k): i行目のj列目の値がkである.
これらの記号を用いた時,先ほどの表現はそれぞれ次のように記述できる.
「1つのマスに1から4の整数が入る.」
Cell(1,1,1)∨Cell(1,1,2)∨Cell(1,1,3)∨Cell(1,1,4)∧
「各マスには整数が1つだけ入る.」
(Cell(1,1,1)⇒¬(Cell(1,1,2)∨Cell(1,1,3)∨Cell(1,1,4)))∧
(Cell(1,1,2)⇒¬(Cell(1,1,1)∨Cell(1,1,3)∨Cell(1,1,4)))∧
(Cell(1,1,3)⇒¬(Cell(1,1,1)∨Cell(1,1,2)∨Cell(1,1,4)))∧
(Cell(1,1,4)⇒¬(Cell(1,1,1)∨Cell(1,1,2)∨Cell(1,1,3)))

命題論理の場合

Cell(1,1,1),Cell(1,1,2),Cell(1,1,3),Cell(1,1,4)をそれぞれa,b,c,dという命題変数とした時,前述の式は次のように記述できる.

「1つのマスに1から4の整数が入る.」
a∨b∨c∨d∧
「各マスには整数が1つだけ入る.」
(a⇒¬(b∨c∨d))∧
(b⇒¬(a∨c∨d))∧
(c⇒¬(a∨b∨d))∧
(d⇒¬(a∨b∨c))
同様に「"1行目の1列目のマスの値が2"ならば,"1行目の1列目のマスの値は1"か"1行目の1列目のマスの値は3"か"1行目の1列目のマスの値は4"でない」と表現できる.

命題論理(CNF)の場合

SATソルバは,CNF式のみを受け付ける.そのため,前述の式をCNF式に変換例を示す.ただし,ここでは「a⇒¬(b∨c∨d)」をCNF式に変換する例だけを紹介する.これは,いくつかのCNF式を論理積で繋いだ式がCNF式であることに起因する.この例に限らず,論理積で結合される各式がそれぞれCNF式に変換できれば,同様のやり方でCNF式に変換できる.

a⇒¬(b∨c∨d)⇔
¬a∨¬(b∨c∨d)⇔
¬a∨(¬b∧¬c∧¬d)⇔
(¬a∨¬b)∧(¬a∨¬c)∧(¬a∨¬d)
同じような変換を他の節にも行うことで,全てCNF式に変換できる.なお,「¬a∨¬b」と「¬b∨¬a」などは意味が重複しているため冗長であるが,これについてここでは考慮しない.

命題論理(DIMACS CNF形式)の場合

前述のCNF式をSATソルバが読み取り可能な形式(DIMACS CNF形式)に書き換えると,このようになる.DIMACS CNF形式では,命題変数を絶対値が1以上の整数,論理否定を"-",論理和をスペースや改行などの区切り,論理積を"0"で表す.DIMACS CNF形式の詳細については,ここでは言及しない.ここで命題変数はaを1,bを2,cを3,dを4で表す.

1 2 3 4 0
-1 -2 0
-1 -3 0
-1 -4 0
-2 -1 0
-2 -3 0
-2 -4 0
-3 -1 0
-3 -2 0
-3 -4 0
-4 -1 0
-4 -2 0
-4 -3 0

述語論理の場合 Part.2

ユーザがルール全てをタイプするのは困難である.そこで,SATソルバに与える入力ファイルを生成するプログラムを作成する.ここでは代表してマスのルールを述語論理で表す.その後,それに対応した入力データを作成するためのRubyプログラムを紹介する.

ここでは,述語記号Cellや主語を表すiとj,kは,前述と同じである.次の式は各マスのルールを表している.

補助記号: , ( ) { }
対象定数: 1 2 3 4
対象変数: i j k k'
述語記号: ∈ Cell = ≠

i∈{1,2,3,4}
j∈{1,2,3,4}
k∈{1,2,3,4}
k'∈{1,2,3,4}

∀i(∀j(∃k(Cell(i,j,k)∧∀k'((k≠k')⇒¬Cell(i,j,k')))))

命題論理(DIMACS CNF形式)式を出力するRubyプログラム

以下に前述で示した述語論理の式を出力するRubyプログラムを紹介する.ちなみにRubyである必要性はない.

s = [1, 2, 3, 4]

for i in s
  for j in s 
    for k in s
      print "#{Cell(i,j,k)} "
    end
  end
end
puts "0"

for i in s
  for j in s
    for k1 in s
      for k2 in s   
        if i != j then
          puts "#{-Cell(i,j,k1)} #{-Cell(i,j,k2)} 0"
        end
      end
    end
  end
end

その他のルール

行や列,小行列の場合も同じような考え方である.以下に,数独の全てのルールを述語論理で示す.なお,述語記号の意味は前述と同じである.ここで,「∃k(Cell(i,j,k)∧∀k'((k≠k')⇒¬Cell(i,j,k'))」を∃!と省略する.

補助記号: , ( ) { } |
対象定数: 1 2 3 4
対象変数: a b i j k l 
関数記号: ∃! + -
述語記号: ∈ Cell ≦ 


i,j,k∈{1,2,3,4}
a,b∈{1,2}
l∈{x∈Z|2a-1≦x≦2a}
c∈{x∈Z|2b-1≦x≦2b}

∀i∀j∃!k Cell(i,j,k)
∀i∀k∃!j Cell(i,j,k)
∀j∀k∃!i Cell(i,j,k)
∀a∀b∀l∀c∃!k Cell(l,c,k)

このように他のルールに関しても同じように記述できる.それゆえ,入力ファイルを生成するためのプログラムも要領は同じである.

非共通ルール

一般的な数独の問題では,いくつかのマスの値が始めから決まっている.これらを命題論理の式で示すのは難しくない.

1
1
例えば,前述の述語記号を使って"1行目の1列目が1"で"4行目の4列目が1"であることを表す場合,次のように書けば良い.
... ∧
Cell(1,1,1)∧
Cell(4,4,1)

この論理式はCNF式である.それゆえ共通ルールと非共通ルールの両方を満たす論理式もまたCNF式である.

命題変数と命題の相互変換

SATソルバで解法を行うためには,命題と命題変数を対応付ける必要がある.数独の場合,行番号と列番号,マスの値から命題変数へ命題変数から行番号と列番号,マスの値への変換ができれば,それが可能となる.

例えば,Cell(1,1,1)が命題変数1に,Cell(4,4,1)が命題変数61に対応する時(解があれば),次のようなファイルが出力される.

SAT
1 -2 -3 -4 ... 61 -62 -63 -64 0
この時,真である命題変数が各マスの値を指す.このような変換と逆変換は,ユーザが行うよりも,入力ファイルを作成したようにプログラムを使った方が効率が良い.そのためには,行と列,値に応じて命題変数を返す関数やその逆変換を行う関数を定義する必要がある.

その他のパズル

最後に特殊な数独と数独以外のパズルを紹介しているサイトを示す.

Mercurial 個人利用

投稿日:
タグ:

目次

個人で利用する場合のMercurialについてのまとめ.

用語

リポジトリ
Mercurialで管理された空間.
リビジョン
バージョン
チェンジセット
遡ることのできるポイントのこと.
コミット
新しいリビジョンを作ること.
tipリビジョン
tip
リポジトリにおいて最近追加されたリビジョン.もっとも最近修正されたヘッダ.
ヘッド
子リビジョンを持たないリビジョン
マージ
複数のリビジョンを合併.各リビジョンの異なるファイルに関する変更や同一ファイルの異なる変更を全て反映させる.
衝突
Mercurialが整合性を保ったまま複数のリビジョンを統合できない状態(例:複数のリビジョンで同じ箇所に異なる変更をした場合).
衝突の解消
解消
衝突している箇所に対し変更内容を指定すること.
タグ
リビジョンの名前のこと.1つのリビジョンにタグは複数付けられる.
ブランチ
2つの子リビジョンを持つ親リビジョンがいる時,その親リビジョン以降

基本コマンド

用途コマンド名別名注釈
リポジトリの初期化.init
管理対象として登録.add
ファイル名を変更.rename登録されたファイルの名前を変更.mvで変更するとMercurialで管理されなくなる.
管理対象から削除.removerm以前までの記録は残る
リビジョン情報を表示.loghistory"-v"で詳細な情報を表示.表示結果の見方は「ログ情報」で.
ファイルの状態を確認.statusst状態の種類
コミット.commitci
作業領域を任意のリビジョンのものに変更.update
  • up
  • checkout
  • co
リビジョンを指定しなければ最新のリビジョンへ
各コマンドのヘルプを表示.help何も指定しなければhgについて.コマンド名を指定すればコマンドについてのヘルプ."hg -v help"の方が詳細.

便利なコマンド

用途コマンド名別名詳細
パターンに一致する管理対象のファイルを表示.locate
  • パターンを指定しない場合,管理対象のファイル全て.
  • リビジョン番号を指定しない場合,現在の作業領域が対象.
あるファイルをあるリビジョン時点のものに更新.revertリビジョンを指定しない場合,親リビジョン("hg parent")が選ばれる.
リビジョンにタグを付ける.tag複数のタグを指定できる.
ファイルの複製.copy複製後のファイルは複製元のリビジョンの履歴を持つ.
2つのリビジョンのファイルの差分を表示.diff1つのリビジョンを指定した場合,指定リビジョンと作業領域間の差分が,何もリビジョンを指定いない場合, 作業領域とその親リビジョン間の差分を表示.
あるリビジョンのファイルの中身を表示.cat
あるパターンに一致するファイルやリビジョンを検索.grep特に指定がない場合,最初に合致したリビジョンのみ,"--all"を指定した場合,全てのリビジョンから検索.
リビジョンを圧縮して取り出し.archive圧縮形式は出力ファイル名から自動判定されるが,-tや--typeで指定可能.
1つ前のコミットを取り消し.rollback1段階限り取り消せる(commitやpushなど).Mercurialでは普通取り消さずに分岐させる.
ファイルの修正時期の確認.annotateblameバイナリには使用できない.

分岐関連コマンド

用途コマンド名別名注釈
  • 現在のブランチ名を表示.
  • ブランチ名の設定.
branch名前を指定した場合は,現在のブランチ名を設定,それ以外の場合は,現在のブランチ名を表示.
現在のブランチを閉鎖.現在のブランチを閉鎖する場合,commitコマンドの引数--close-branchを使用.
マージ.merge
  • マージ対象リビジョンの指定が無く,作業領域の親リビジョンがヘッドで,且つ現行ブランチがもう1つだけヘッドを持つ場合,そのヘッドがマージ対象.
  • 衝突未解消なファイルは "hg resolve"での衝突解消が必要です。
マージ状態の管理.resolve詳細は「衝突解消」で.

衝突解消

引数1引数2説明
-l--listマージの必要なファイルの解消状態一覧.
-m--mark当該ファイルを衝突解消済みに設定.
-u--unmark当該ファイルを衝突未解消に設定.

複数のリポジトリ

用途コマンド名別名注釈
リポジトリ全体の複製.clone
  • コピー先のリポジトリのdefault変数にコピー元のパスが代入される("hg paths"で確認).
  • コピー先に指定可能なパスは,ファイルシステム上のパスと"ssh://"形式のurl.
他リポジトリから自リポジトリへ差分複製.pull引数として複製元のリポジトリのパスやURL,パスのエイリアス名を与える.
自リポジトリから他リポジトリへ差分複製.push
  • 特に指定がなければ複数のヘッドが作成される処理は反映されない(-fで変更可能).
  • 引数として複製先のリポジトリのパスやURL,パスのエイリアス名を与える.

状態確認関連コマンド

用途コマンド名別名注釈
全てのヘッドのログを表示.headsパラレルワールドがあるか確認可能.
作業領域の概要を表示.summarysum
  • 現在のブランチ名を表示.
  • ブランチ名の設定.
branch
作業領域か任意のリビジョンの親リビジョンのログを表示.parents
リポジトリ内の全てのタグ名を表示.tags
tipリビジョンのログを表示.tip
リポジトリの整合性を検証.verify
リビジョンの識別情報を表示.identifyidリビジョンを指定しなかった場合,現在の作業領域.
リビジョン情報を表示.loghistory"-v"で詳細な情報を表示.表示結果の見方は「ログ情報」で.
ファイルの状態を確認.statusst状態の種類

ログ情報

user% hg log
チェンジセット:   2:c2ed88952439
タグ:             tip
ユーザ:           user
日付:             Sun Oct 28 23:21:54 2012 +0900
要約:             3rd check

チェンジセット:   1:06cc1925b446
ユーザ:           user
日付:             Sun Oct 28 23:21:13 2012 +0900
要約:             second check

チェンジセット:   0:2aa333d966d3
ユーザ:           user
日付:             Sun Oct 28 23:20:40 2012 +0900
要約:             first check

引数にファイル名やディレクトリ名を指定した場合,そのファイルやディレクトリ以下のファイル名のリビジョンを表示する.
フィールドfield説明
チェンジセットchangesetリビジョン番号とハッシュID
タグtagタグが付いていればタグ
ユーザuserユーザ名
日付dateコミットを実施した日付
ファイルfilesファイル一覧
要約summaryコミットメッセージの1行目
説明descriptionコミットメッセージ

ファイル状態

user% hg status -A
M hoge.html
? foo.html
C fuga.html
C piyo.html
状態マーク表示対象引数1引数2
A登録対象-a--added
?未登録-u--unknown
M修正
C無修正-c--clean
R除外-r--removed
!削除-d--deleted
I無視-i--ignored
(空白文字)複製元-C--copies

設定ファイル

Mercurialには,hgrcや.hgrcという名前の設定ファイルがある.詳細は,"hg help -v config"で表示できる.

設定ファイルは,セクション名([と]で囲われた文字列)とセクションから構成される.以下にセクションの例をいくつか示す.

ui
ユーザインタフェースに関する設定.項目は"hg help -v config"で確認できる.
username
ユーザ名
editor
エディタ名
ssh
sshコマンド
[ui]
username = user
editor = vim
ssh = ssh -p 10022
encode
ファイルパターンとそれに使用する圧縮コマンドの指定.
[encode]
*.tar.gz = gzip
*.tar.bz2 = bzip2
paths
リポジトリへのシンボリックな名前を設定.
[paths]
hoge = /home/user/work1
fuga = ssh://user@test:22/work2
いくつかのコマンドは,defaultやdefault-pushなどのエイリアス名を設定する.設定されたパスはpathsコマンドで確認可能.
alias
コマンドのエイリアス名を設定.
[alias]
ls = locate

mipsクロスコンパイラ[gcc-mips]

投稿日:
タグ:

目次

gcc4.4.0でのmips用クロスコンパイラを作成した時のメモ.基本的に次のページを参考にしている.というか一部を除いてほぼその通り.

基本情報

ソースファイル

使用するソフトウェアとバージョンは次の通り.リンク先は皆ダウンロードページであり,ここで使用するものと同じバージョンがダウンロードできない場合がある.

使用したソフトウェア名前説明
gcc-4.4.0GNU Compiler CollectionGNUのコンパイラ群で,CやC++,FORTRAN,Objective-C,Javaなど様々な言語のコンパイラやライブラリを提供する.以前はCコンパイラだけを提供していて,GNU C Compiler(GCC)だった.
glibc-2.11GNU C LibraryGNUの標準Cライブラリ.
glibc-ports-2.11glibcのソース本体に入っていないいくつかのアーキテクチャ用のファイルが入っている.
binutils-2.22GNU binutilsアセンブラやリンカなど機械語を扱ういくつかのソフトウェア群.
linux-2.6.27.62Linux KernelLinuxのカーネル.いくつかのアーキテクチャ用のヘッダを持つ.
gmp-5.0.5GNU Multi-Precision Library多倍長整数など任意の精度の算術ライブラリ.
mpfr-3.1.1GNU Multiple Precision Floating-Point ReliablyGMPライブラリを使用する任意の精度の浮動小数点演算のためのポータブルライブラリ.

システム環境

クロスコンパイラを構築するために,3種類のシステム環境を考慮する必要がある.これは作成するクロスコンパイラの用途や環境に応じて設定する.以下に今回使用する設定を示す.

binutilsgccglibc
ビルドマシンx86_64-linuxx86_64-linuxx86_64-linux
ホストマシンx86_64-linuxx86_64-linuxmips-linux
ターゲットマシンmips-linuxmips-linux×
ビルドマシン
クロスコンパイラを作るシステム環境
ホストマシン
作成したプログラムやライブラリなどを実行または使用するシステム環境
ターゲットマシン
作成したコンパイラやアセンブラなどが生成するコードのシステム環境

作業ディレクトリ

あまり意味はないが,環境変数を共用したりビルドするソフトウェアの数が多かったりするので,混乱を防ぐために作業環境を整理する.

user% pwd
/home/user/00_workspace/cross
user% ls *
build:


log:


scripts:
build-01-linux.sh  build-04-rmso.sh      build-07-glibc-header.sh  build-10-glibc.sh  build-common
build-02-gmp.sh    build-05-binutils.sh  build-08-glibc.sh         build-11-gcc.sh
build-03-mpfr.sh   build-06-gcc.sh       build-09-gcc.sh           build-12-glibc.sh


src:
binutils-2.22  gcc-4.4.0  glibc-2.11  gmp-5.0.5  linux-2.6.27.62  mpfr-3.1.1


src_original:
binutils-2.22.tar.gz  glibc-2.11.tar.gz        gmp-5.0.5.tar.bz2        mpfr-3.1.1.tar.bz2
gcc-4.4.0.tar.bz2     glibc-ports-2.11.tar.gz  linux-2.6.27.62.tar.bz2
ディレクトリ名目的
buildコンパイルに使用する場所.
scriptsconfigureやmakeを実行するスクリプトファイルを配置.
srcダウンロードしたファイルを解凍したものを配置
src_originalダウンロードしたファイルを配置.
※ glibc-portsはglibcの中に解凍して名前をportsに変更.
MIPS用のライブラリを作成するのに必要なものがportsの中にあり,configureの引数で--enable-add-ons=portsを指定することで,glibcはそれを認識する.

ビルド

環境変数: build-common

今回引数として使用するものの一部を環境変数として定義する.

export PREFIX=$HOME/90_mypath/cross
export TARGET=mips-linux
export PATH=$PREFIX/bin:$PATH
export LIB_HOST=$PREFIX/lib_host
export BUILD_DIR=/home/user/00_workspace/cross/build
export SRC_DIR=/home/user/00_workspace/cross/src
export LOG_DIR=/home/user/00_workspace/cross/log
変数の用途は以下の通り.
変数名用途
TARGETMIPSクロスコンパイラ作成用の引数
PATHコンパイラやライブラリのインストール先のパス
LIB_HOSTホスト用のライブラリのパス
BUILD_DIRコンパイルを行うディレクトリのパス
SRC_DIRソースコードを配置したディレクトリのパス
LOG_DIRconfigureやmakeの出力結果を記したファイルを配置したディレクトリのパス

カーネルヘッダ: build-01-linux.sh

インストール先のディレクトリを作成し,MIPS向けや全てのCPUで共通するカーネルヘッダをそこにコピーする.

#!/usr/bin/zsh

. ./build-common

mkdir $PREFIX
cd $SRC_DIR/linux-2.6.27.62
make include/linux/version.h
mkdir $PREFIX/$TARGET/
cp -a include $PREFIX/$TARGET/.
cd $PREFIX/$TARGET/include
ln -s asm-mips asm

gmp : build-02-gmp.sh

(クロス)コンパイラが内部で使用する多倍長整数など任意の精度の算術ライブラリをコンパイルする.

#!/usr/bin/zsh

. ./build-common

rm -rf $BUILD_DIR/*
cd $BUILD_DIR

$SRC_DIR/gmp-5.0.5/configure --prefix=$LIB_HOST |& tee $LOG_DIR/build_02_configure.log
make  |& tee $LOG_DIR/build_02_make.log
make install |& tee $LOG_DIR/build_02_make_install.log

mpfr : build-03-mpfr.sh

(クロス)コンパイラが内部で使用する任意の精度の浮動小数点演算のためのポータブルライブラリをコンパイルする.mpfrはgmpライブラリを使用するため,gmpの後にコンパイルしなければならない.

#!/usr/bin/zsh

. ./build-common

rm -rf $BUILD_DIR/*
cd $BUILD_DIR

$SRC_DIR/mpfr-3.1.1/configure --prefix=$LIB_HOST \
    --with-gmp=$LIB_HOST |& tee $LOG_DIR/build_03_configure.log

make  |& tee $LOG_DIR/build_03_make.log
make install |& tee $LOG_DIR/build_03_make_install.log

共有ライブラリの削除 : build-04-rmso.sh

ファイル名にbuildが付くが,処理内容はgmpとmpfrの共有ライブラリを削除するだけ.参考サイトによると共有ライブラリを使用しないように指定しても適用されないことがあるらしく,それを避けるために削除する.

#!/usr/bin/zsh

. ./build-common

rm $LIB_HOST/lib/*.so $LIB_HOST/lib/*.so.*

binutils : build-05-binutils.sh

MIPSクロスコンパイルのために使用するアセンブラやローダ,書庫作成ソフト,バイナリ解析などのソフトウェアをインストールする.

#!/usr/bin/zsh

. ./build-common
rm -rf $BUILD_DIR/*
cd $BUILD_DIR
$SRC_DIR/binutils-2.22/configure --target=$TARGET --prefix=$PREFIX |& tee $LOG_DIR/build_05_configure.log
make all |& tee $LOG_DIR/build_05_make.log
make install |& tee $LOG_DIR/build_05_make_install.log
mkdir $PREFIX/include
cp $SRC_DIR/binutils-2.22/include/libiberty.h $PREFIX/include/.

gccのコア部分のみ : build-06-gcc.sh

gccのコンパイラ部分のみをビルドする.ライブラリの生成などを無効にしないと,エラーが発生する.

#!/bin/sh

. ./build-common

rm -rf $BUILD_DIR/*
cd $BUILD_DIR

$SRC_DIR/gcc-4.4.0/configure --target=$TARGET --prefix=$PREFIX  \
	--without-headers --enable-languages=c \
	--disable-libmudflap --disable-libssp --disable-threads --disable-shared \
	--disable-multilib --disable-biarch \
	--with-gmp=$LIB_HOST --with-mpfr=$LIB_HOST |& tee $LOG_DIR/build_06_configure.log

make all-gcc |& tee $LOG_DIR/build_06_make.log
make install-gcc |& tee $LOG_DIR/build_06_make_install.log
出来上がったコンパイラは,ライブラリや必要なオブジェクトがないので,このままではまともにコンパイルできない.オプションについての説明は以下の通り.
オプション説明
--with-gmpgmpのパスを指定.--with-gmp=$PATHと指定した場合,--with-gmp-include=$PATH/includeと--with-gmp-lib=$PATH/libとして扱われる.
--with-mpfrmpfrのパスを指定.--with-mpfr=$PATHと指定した場合,--with-mpfr-include=$PATH/includeと--with-mpfr-lib=$PATH/libとして扱われる.
--without-headers標準Cヘッダを使用しない.
--enable-languagesビルドするコンパイラやライブラリのプログラミング言語を指定.
--disable-libmudflapmudflapライブラリをビルドしない.
--disable-libsspsspライブラリをビルドしない.
--disable-threadsスレッド処理のサポートを無効にする.
--disable-shared共有ライブラリをビルドしない
--disable-multilib32/64ビットアーキテクチャ向けのライブラリを共存するサポートを無効にする.
--disable-biarch32ビットと64ビットアーキテクチャのサポートらしいが詳細は不明.

glibc Cライブラリヘッダ : build-07-glibc-header.sh

ターゲットマシン用の標準Cライブラリのヘッダを生成する.

#!/usr/bin/zsh

. ./build-common
export ac_cv_type_long_double=no
export libc_cv_forced_unwind=yes
export libc_cv_c_cleanup=yes

rm -rf $BUILD_DIR/*

cd $BUILD_DIR
$SRC_DIR/glibc-2.11/configure --host=$TARGET --prefix=$PREFIX \
	--with-headers=$PREFIX/mips-linux/include --with-binutils=$PREFIX/mips-linux/bin \
	--enable-add-ons=ports --with-tls --disable-sanity-checks \
	--disable-versioning --disable-profile |& tee $LOG_DIR/build_07_configigure.log

make  install-headers |& tee build_make.log

cp -v bits/stdio_lim.h $PREFIX/include/bits/.
mkdir -p $PREFIX/include/gnu 
touch $PREFIX/include/gnu/stubs.h
configureのオプションの詳細は次の通り.
オプション説明
--with-headers[=DIR]システムヘッダが格納されているディレクトリのパスを指定.
--with-binutils[=DIR]binutils(asとld)が格納されているディレクトリのパスを指定.
--enable-add-ons[=DIRS...]指定したディレクトリらもビルド対象とする.
--with-tlsTLS(Thread local Storage)をサポートする.TLSとは,静的もしくは大域的なメモリをスレッドごとに局所的に使用するためのプログラミングの方法のこと.
--disable-sanity-checksgccとglibcのバージョンの依存関係を無視する.
--disable-versioningライブラリにバージョニング情報を入れない.
--disable-profileプロファイル情報を含めずにビルドする.
環境変数用途
ac_cv_type_long_doublelong double型に関する変数らしいが詳細は不明.ないとエラーになった.
libc_cv_forced_unwindyesの場合,unwindサポート機能のテストを省略し利用可能とする.
libc_cv_c_cleanupyesの場合,テストが省略されCのクリーンアップハンドリングのサポートを有効にする.

glibc ライブラリや必要なオブジェクト : build-08-glibc.sh

標準Cライブラリやスタートアップルーチンなどのオブジェクトをコンパイルする.作成するオブジェクトファイルは,プログラムの初期化やmain関数を呼び出す部分,プログラムを終了するコードを持つ.

#!/usr/bin/zsh

. ./build-common

export CC=mips-linux-gcc
export AR=mips-linux-ar
export RANLIB=mips-linux-ranlib
export ac_cv_type_long_double=no
export libc_cv_c_cleanup=yes

rm -rf $BUILD_DIR/*
cd $BUILD_DIR

$SRC_DIR/glibc-2.11/configure --host=$TARGET --prefix=$PREFIX \
	--disable-sanity-checks	--enable-kernel=2.6.27.62 --enable-add-ons=ports \
	--with-tls --with-_thread --enable-thread |& tee $LOG_DIR/build_08_configure.log 

make -k gnulib=-lgcc |& tee $LOG_DIR/build_08_make.log
make -k install |& tee $LOG_DIR/build_08_make_install.log
makeコマンドのkオプションを指定することで,可能な限りエラーが発生してもコンパイルを続ける.kオプションを指定せずにビルドを試みても良いが,私の環境では失敗した.うまくいけば$PATH/$TARGET/libにcrti.oやcrtn.o,crt0.oなどができているはずである.configureのオプションの詳細は次の通り.
オプション説明
--with-_threadTLSがサポートされている場合でもTLSを使用しない.
--enable-thread詳細は不明.名前からするとスレッド関係のオプションっぽい.
--enable-kernel互換性に関するオプション.指定したバージョン以降のカーネル向きにコンパイルする.
環境変数用途
CCコンパイルに使用するコンパイラを指定.
AR静的ライブラリを作るために使用する圧縮ソフトを指定.
RANLIB書庫のインデックスを作成するソフトを指定.

GCC Cコンパイラのみ : glibc-09-gcc.sh

Cコンパイラとlibgccを生成する.libgccはgccが自動的にコードに付加するライブラリである.

#!/bin/sh

. ./build-common
rm -rf $BUILD_DIR/*
rm -rf $PREFIX/mips-linux/lib 
rm -rf $PREFIX/mips-linux/sys-include
cd $BUILD_DIR


$SRC_DIR/gcc-4.4.0/configure --target=$TARGET --prefix=$PREFIX \
    --with-headers=$PREFIX/include --enable-languages=c \
    --disable-libmudflap --disable-libssp --disable-threads	\
    --disable-multilib --disable-biarch \
    --with-gmp=$LIB_HOST --with-mpfr=$LIB_HOST |& tee $LOG_DIR/build_09_configure.log

make all-gcc |& tee $LOG_DIR/build_09_make1.log
make all-target-libgcc |& tee $LOG_DIR/build_09_make2.log
make install-gcc |& tee $LOG_DIR/build_09_make_install1.log
make install-target-libgcc |& tee $LOG_DIR/build_09_make_install2.log
基本的に前回のビルドと同じオプションだが,disable-sharedを外す.disable-sharedを外しておかないとgcc_ehとエラーになる.

glibc gccが使用するCライブラリをglibcへ(1) : build-10-glibc.sh

出来上がったコンパイラを使って完全なglibcをコンパイルする.

#!/usr/bin/zsh

. ./build-common

export ac_cv_type_long_double=no
export libc_cv_forced_unwind=yes
export libc_cv_c_cleanup=yes

rm -rf $BUILD_DIR/*
cd $BUILD_DIR

$SRC_DIR/glibc-2.11/configure --host=$TARGET --prefix=$PREFIX \
	--disable-sanity-checks	|& tee $LOG_DIR/build_10_configure.log 
	
make |& tee $LOG_DIR/build_10_make.log
make install |& tee $LOG_DIR/build_10_make_install.log

gcc CとC++,fortranのコンパイラ : build-11-gcc.sh

完全なCコンパイラとglibcの標準Cライブラリを使用して,その他のライブラリやコンパイラを生成する.そのために,前回まで指定していた--disable-libから始まるオプションを外し,--enable-languagesに他の言語を追加する.

#!/bin/sh

. ./build-common
rm -rf $BUILD_DIR/*
rm -rf $PREFIX/mips-linux/lib 
rm -rf $PREFIX/mips-linux/sys-include
cd $BUILD_DIR

$SRC_DIR/gcc-4.4.0/configure --target=$TARGET  --prefix=$PREFIX \
    --with-headers=$PREFIX/include --with-libs=$PREFIX/lib \
    --enable-languages=c,c++,fortran  \
    --with-gmp=$LIB_HOST --with-mpfr=$LIB_HOST |& tee $LOG_DIR/build_11_configure.log

make |& tee $LOG_DIR/build_11_make.log
make install |& tee $LOG_DIR/build_11_make_install.log

glibc gccが使用するCライブラリをglibcへ(2) : build-12-glibc.sh

コンパイラが変更されたので,もう一度glibcをコンパイルする.これにより新しいコンパイラに合わせて各種設定が変更される.configureに与える引数は同じだが,

#!/usr/bin/zsh

. ./build-common

export ac_cv_type_long_double=no

rm -rf $BUILD_DIR/*
cd $BUILD_DIR

$SRC_DIR/glibc-2.11/configure --host=$TARGET --prefix=$PREFIX \
    --disable-sanity-checks	|& tee $LOG_DIR/build_12_configure.log 
	
make |& tee $LOG_DIR/build_12_make.log
make install |& tee $LOG_DIR/build_12_make_install.log

注意

作業中に実際に陥った問題を以下にメモ.

  • --hostと--build,--targetについて.glibcとgccで指定が異なるので注意.
  • gccのconfigureには--enable-threads,glibcのconfigureには--enable-threadという異なるオプションがある

make

投稿日:
修正日:
修正日:
タグ:

目次

makeの使い方に関するメモ.

make

makeコマンドとは

makeは,複数のソースファイルからコンパイルする際,それらの依存を解決するためによく使用される.makeはメイクファイルと呼ばれるテキストファイルに基づいて,依存を判断する.例えばhoge.cとfuga.c,piyo.cからfoo.exeを生成する場合,次のようにメイクファイルを記述できる.

foo.exe: hoge.o fuga.o piyo.o
	gcc -o foo.exe hoge.o fuga.o piyo.o

hoge.o: hoge.c
	gcc -c hoge.c

fuga.o: fuga.c
	gcc -c fuga.c

piyo.o: piyo.c
	gcc -c piyo.c
この時,makeは3つのソースファイルの内前回のコンパイル時から変更されたもののみをコンパイルし,3つのCソースファイルから生成されたオブジェクトファイルをリンクする.make自体には,コードを見て依存関係を判断する能力はなく,あくまでユーザが記したメイクファイルに従う.makeはターゲットを作成するために下位の構成要素から順に処理を行う.前述の例でfoo.exeをターゲットとした時,この文を実行するより先に必要な3つのコンポーネントの作成を行う.すなわちそのコンポーネント名がターゲット名である依存関係行から始まる文である.makeはこのような処理を再帰的に行い,最終的なターゲットを生成する.

makeの使い方は次の通りである.

user% make [ -f メイクファイル ] [オプション] [ターゲット] [マクロ定義]
メイクファイルを指定しなかった場合,makeはカレントディレクトリにあるmakefileやMakefileという名前のファイルをメイクファイルと見なす.標準のメイクファイルの名前は,makeの種類に依存する.

ターゲットを指定しなかった場合,メイクファイルで最も上のエントリを処理する.

通常,makeはコマンドがエラー終了(0以外の終了コードでの終了)の場合,そこで処理を中断する.

オプション

以下にオプションの例をいくつか示す.

オプション機能
-b古いmake用のメイクファイルを受付可能にする.
-dデバッグモードで実行.フラグやファイルの最終更新時刻などの詳細情報を表示する.
-iエラーを無視する.
-kエラーが発生すると現在のターゲットに関する作業は中止するが,他のターゲットの構築は続行する.
-nコマンドを実行せずに表示だけを行う.@で始まるコマンド行も表示する.
-pマクロ定義や有効なサフィックス,サフィックスルール,メイクファイルエントリを表示する.
-rデフォルトルールを使用しない.
-s実行するコマンドを表示しない.
-t構築のためにコマンドを実行せずにターゲットの最終変更時刻を最新のものに変更する.

メイクファイル

メイクファイルの名前には,関連としてmakefileやMakefile,または.mkのサフィックス(拡張子)を持つ名前を使用する.また,makeはfオプションを指定して複数のメイクファイルを指定することでそれらを結合して使用できる.

また,makeを次のように指定した場合,makeはファイルの代わりに標準入力から読み取る.

user% make -f - iomod
この場合ファイルの末尾(c-d)を読み取るまで処理を続ける.

構文と意味

ここでは,構文を示した後それらの詳細を説明する.ここでは,[]で囲われた部分は省略可能であることを指す.また,斜字の部分にはファイル名やコマンドなどの文字列が入る.ただし,タブはASCIIコードの9番のHTを意味する.

依存関係行
ターゲット:[:] [コンポーネント] [;[コマンド]]
コマンド行
タブ [-@] コマンド
  • コマンド行はそれぞれ別のシェルで実行するように扱われる.そのためifやforのようなコマンドを扱う場合は;や\を駆使する必要がある.
  • -がついている場合は,コマンド行でエラーが起きても,次のコマンド行を処理する。
  • 行末に\を付けることで,複数行に跨いだ記述ができる.
コメント
#から行末までがコメントとして扱われる.

ターゲット
  • 生成するファイルのこと.
  • ターゲットファイルが存在しない場合以降のコマンド行を実行する.
  • 空白で区切って複数指定可能.
  • 前にたぶを置いてはいけない
  • 英字,数字,ピリオド,アンダースコアが使用可能(それ以外はmakeコマンドに依存)
: (コロン)
  • コロンの両側には空白があってもなくても構わない
  • コロンを2つ使った場合,複数のコマンド行を伴った依存関係行に同一のターゲットを指定することができる(通常同じターゲットの依存関係行は記述できない).
コンポーネント
  • ターゲットを生成するために必要なファイルのこと.
  • コンポーネントがターゲットとして定義されていた場合,その処理を実行
  • コンポーネントのファイルが更新されていなかった場合,以降のコマンド行を無視する.
  • 空白で区切って複数指定可能.
セミコロン・コマンド
  • セミコロンの後にコマンドを記述可能.
  • タブの後に空白があってもなくても構わない.
  • -をつけるとコマンドからエラー(0意外の終了コード)が返されても無視する.
  • @をつけると実行の際にコマンドを表示しない.
  • コマンドには通常ターゲットを構築するためのコマンドを指定する.

サフィックスルール

サフィックスルールを使えばいくつかのメイクファイルのテキストが簡潔に記述できる.サフィックスルールを使用すると,前述のメイクファイルは次のように置き換えられる.

foo.exe: hoge.o fuga.o piyo.o
	gcc -o foo.exe hoge.o fuga.o piyo.o

.c.o:
	gcc -c $<
$<はマクロであり,現在処理中のターゲットよりも後で変更されたコンポーネント名を表す.サフィックスルールの文法は次の通りである.
サフィックスルール
.サフィックス1[.サフィックス2] :[:]
  • サフィックス1を持つファイルが,サフィックス以外が同じでかつサフィックス2を持つファイルがコンポーネントとなりうる.
  • サフィックス2が指定されていない場合,サフィックスを持たずサフィックス1以外が同じ名前のファイルを指す.
  • コロンは2つあっても意味がない
  • サフィックスには英字,数字,ピリオド,アンダースコアが使用可能(それ以外の文字はmakeコマンドに依存).
サフィックスルールは次の条件が成立する場合に適応される.
  • これより下位のコンポーネントが存在しない.
  • ターゲットがコンポーネントよりも古い.

マクロ

マクロの定義は次の文によって行う.

マクロ定義
マクロ名 = 文字列
  • マクロ名の前に空白を記述しても良い.
  • マクロ名の前にタブは記述してはいけない.
  • 等式の周りには空白やタブを置いて良いが,文字列を割り当てる際に文字列の一部として扱われない.
  • "や'は文字列の一部として扱われる.
マクロ参照
  • $(マクロ名)
  • ${マクロ名}
  • $1文字だけのマクロ名
  • 定義されていなマクロは空の文字列として扱われる.
マクロ名
  • 文字として英字,数字,下線などが使用可能(それ以外の文字はmakeコマンドに依存).
  • 慣例として大文字を使う.
VAR = hoge
echo $(VAR)
あらかじめ定義されているマクロ

makeが用意したマクロ
以下にいくつか例を示す.
変数名内容
CCCコンパイラ
CFLAGSCコンパイラのオプション
FCfortranコンパイラ
FFLAGSfortranコンパイラのオプション
ASアセンブラ
ASFLAGSアセンブラのオプション
LDリンカ
LDFLAGSリンカのオプション
MAKEFLAGSmakeコマンドに与えられたオプション.4.3BSDではMFLAGSというマクロ名.
$<現在処理中のターゲットよりも後で変更されたコンポーネント名.サフィックスルールと.DEFAULTエントリでのみ使用可能.
$*現在処理中のターゲットよりも後で変更されたコンポーネントのサフィックス除いた名前.
$@現在処理中のターゲット
$$@現在処理中のターゲット名.依存関係行の右側でのみ使用可能.
$?現在処理中のターゲットよりも後で変更されたコンポーネントのリスト
$%ターゲットがライブラリであった場合に対応する.oファイルの名前.
makeコマンド実行の際に定義したマクロ
前述のmakeコマンドの実行方法で示した通り,その際にマクロの定義を行うことができる.makeは与えられた引数がターゲットかマクロ定義かは=が付いているか否かで判断する.ただし,定義する文字列に空白を含む場合や=の前後を空白で区切る場合,"や'で囲わない限りシェルは正しく認識できない.
シェル変数
シェルに渡された環境変数をマクロとして利用できる.
特殊なマクロ
SHELL
コマンドを解釈するために使用するシェルの種類.

マクロの優先順位

マクロは前述で示した通り様々な方法で定義される.その際,同じ名前のマクロ名を参照する場合は優先順位が高いものが選ばれる.以下に優先順位が高い順に記す.

  1. makeを実行するコマンド上で,makeコマンド名よりも後で定義されたマクロ
  2. メイクファイル内で定義されたマクロ
  3. 環境変数
  4. makeによって予め内部定義されたマクロ

ただしmake実行の際にeオプションを指定した場合,次の優先順位が高い順に従う.

  1. makeを実行するコマンド上で,makeコマンド名よりも後で定義されたマクロ
  2. 環境変数
  3. メイクファイル内で定義されたマクロ
  4. makeによって予め内部定義されたマクロ
これは複数人のプロジェクトで一部それぞれ固有の設定を使用したい際などに便利である.

特殊なターゲット
.DEFAULTこれからつくろうとするファイルに関するメイクファイルエントリやサフィックスルールが存在しなかった場合に実行される.
.IGNORE実行したコマンドのエラーコードを無視する.
.PRECIOUSmakeを中断するためのシグナルやエラー終了する場合でも,ここで指定されたファイルは削除されない.
.SILENT実行するコマンドを表示しない.
.SUFFIXESこのターゲットとして指定されたものは,makeにとって意味を持つサフィックスとなり,サフィックスルールを持つことが出来るようになる.

さまざまなmake

makeは種類やバージョンによって異なる機能を持つ.以降の機能は全てのmakeが共通して持つ機能ではない.

特殊なマクロ
VPATH
カレントディレクトリにコンポーネントが存在しな場合にmakeが探しにいくディレクトリのリストを指定.
マクロ修飾子

いくつかのマクロは文字列からファイル名やディレクトリ部分を抽出できる.次のコードはhoge/fuga/にあるpiyo.cをコンパイルして,そこにpiyo.o生成するメイクファイルである.

all: hoge/fuga/piyo.o

.c.o:
	cd $(<D); \
	$(CC) -c $(<F)
D$?を除くマクロのディレクトリ部分
F$?を除くマクロのファイル名の部分

マクロ文字列置き換え

${value:s1=s2}
${value}を展開した文字列の空白またはタブの直前にあるs1や文字列の最後に位置するs1を全てs2で置き換える. 例えば次のように記述した場合,
SOURCES=hoge.c fuga.c piyo.c
	echo ${SOURCES:.c:.o}
echoは"hoge.o fuga.o piyo.o"と出力する.

サブディレクトリ

makeはディレクトリ毎に分けて管理する機能もサポートしている。

Makefile
サブディレクトリのMakefileを実行するには,「make -C サブディレクトリ」を使用する。サブディレクトリのmakeのターゲットを指定する場合の例も一緒に示す。
.PHONY: sub1 sub2
SUBDIRS := sub1 sub2

all: $(SUBDIRS)
	gcc -o a.out piyo.c -Isub1 -Isub2 -Lsub1 -Lsub2 -lhoge -lfuga

$(SUBDIRS):
	$(MAKE) -C $@

clean:
	rm a.out
	$(MAKE) clean -C sub1
	$(MAKE) clean -C sub2
.PHONYは擬似ターゲットと呼ばれるもので,後述するターゲットが存在する場合でも処理を行う場合に記述する。前述の例では,cleanやallというファイルがないことを前提に記述しているが,不安ならば.PHONYに書いた方がいいだろう。
sub1/Makefile
libhoge.a: hoge.o
	ar rv libhoge.a hoge.o

.c.o:
	$(CC) -c $< -o $@

clean:
	rm libhoge.a hoge.o
sub2/Makefile
libfuga.a: fuga.o
	ar rv libfuga.a fuga.o

.c.o:
	$(CC) -c $< -o $@

clean:
	rm libfuga.a fuga.o

前述で示したもの以外にもmakeはさまざまな機能を持つ.

include

複数のMakefileを使用する場合,一部のコードを共通化すると,便利である。

Makefile.common
CC=gcc
CFLAGS=-O3
Makefile
include Makefile.common

a.out: hoge.o
	$(CC) $(CFLAGS) -o a.out hoge.o

.c.o:
	$(CC) $(CFLAGS) -c $@ $<

Linux デバイスドライバ -Hello-

投稿日:
タグ:

目次

キーワード

  • カーネルメッセージ
  • ライセンス
  • ログレベル

本稿はLinuxデバイスドライバ(モジュール)プログラミングに関するメモ.第1弾.

Linuxは実行中にカーネルの機能の追加や削除できる. それらの処理には次のコマンドを使用する.

insmod
カーネルにモジュールを追加するコマンド.
rmmod
カーネルからモジュールを取り除くコマンド.
modprobe
モジュールをカーネルに追加したりカーネルから取り除いたりする高レベルインタフェースなコマンド.モジュールをロードする場合,多くの点でinsmodと同じだが,ロード対象のモジュールが必要とするモジュールもロードする.ただしmodprobeはインストール済みのモジュールを格納したディレクトリからしか探さないため,作業ディレクトリから依存するモジュールをロードする時はは,insmodを使わなければならない.
これらのコマンドはシステム管理用のコマンドであり,通常rootでなければ実行できない.

Linuxのデバイスやモジュールのクラス

キャラクタ型デバイス
バイトストリームとして扱われるデバイスで,その機能を提供するのがキャラクタ型デバイスドライバ.通常キャラクタ型デバイスドライバには以下のシステムコールが実装される.
  • open
  • close
  • read
  • write
テキストコンソールやシリアルポートはキャラクタ型デバイスの一種である.
ブロック型デバイス
ブロック単位でアクセスするデバイス.ブロックとは512バイトあるいはそれ以外の2のべき乗のデータのかたまりのことである.代表的な例にはディスクが挙げられる.
ネットワークインタフェース
ネットワークインタフェースはデータパケットの送受信を担当する.ネットワークインタフェースのほとんどはハードウェアを伴うデバイスであるが,ループバックインタフェースのようにソフトウェアだけのデバイスもある.ネットワークデバイスドライバの通信は他の2つのデバイスドライバとは異なり,ストリーム指向ではない.それゆえ対応するファイルノードを持たない.

デバイスドライバとアプリケーション

デバイスドライバとアプリケーションの違いについて,いくつか例を示す.

  • アプリケーションはリソースを大雑把に解放するか全く解放しないが,デバイスドライバは取り除かれる際に全ての資源を解放しないとシステムが再起動されるまでリソースが解放されない.
  • アプリケーション(Cのコード)はライブラリをリンクされるのでそこで定義された関数やオブジェクトを呼び出すことができる.しかしデバイスドライバはカーネルだけにリンクされるので,呼び出せる関数らはカーネルが公開しているものに限る.
  • 基本的にデバイスドライバはカーネル空間の中で実行され,アプリケーションはユーザ空間の中で実行される.
  • デバイスドライバは複数のシステムによって呼び出されることが珍しくない.それゆえデバイスドライバは再投入可能であるべき.
  • アプリケーションが利用するスタック領域に比べ,デバイスドライバが利用するスタック領域はわずか.そのため大きな自動変数を宣言するのは望ましくない.
__で始まる関数名はインタフェースの下位レベルのコンポーネントであり,使用には注意が必要.

Hello, World

学習に用いた本はオライリーの「LINUX デバイスドライバ」でLinux 2.6を対象としている.

実行環境

カレントディレクトリには後述するhello.cとMakefile,Linuxカーネル3.0.9のソースコードをビルドしたものを配置する.後述する実行中のカーネルはこのビルドしたものである.

ソフトウェア内容
OS(Linux)3.0.9
ディストリビューションUbuntu 12.04
makeGNU make 3.81
ブートローダgrub 2
デバイスドライバの作成にはカーネルソースが必要であり,そこに使用する関数 や変数などを宣言したヘッダがある.ただしビルドされていない状態では必要な ヘッダが不足していた.なおLinuxカーネルはThe Linux Kernel Archivesから取得できる.カーネルのコンパイル方法についてはさまざまあり,ここではそれらについて詳しく語ることはしないが,流れとしては次の通りである.
  1. 設定ファイル(.config)の作成と編集.
  2. カーネルのコンパイル
  3. コンパイルしたカーネルをインストール
  4. ブートローダの設定を書き換える.
今回は/bootにあるconfig-から始まる名前のファイル,つまり既存のカーネルの設定を基にして,コンパイルを行う."make"を叩くとカーネルの 設定について質問され,それを全部答え終わるとコンパイルを開始する.ncursesが入っていれば"make menuconfig"を使ってより簡単に設定ファイルの修正ができる.ただしその辺りについての詳細は省略する.ちなみにコンパイル時間は結構長い.コンパイルが完了すると"make install"でインストールし,それが終わるとブートローダの設定を書き換える.私の環境ではログインやカーネルの設定を選択する画面を省略しているので,/etc/default/grubのGRUB_DEFAULTを変更し,"update-grub"で更新することで,標準で起動されるカーネルを変更した.

hello.c

はじめにモジュールをロードした際と取り除いた際にカーネルメッセージを出力する例を示す.

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
  printk(KERN_ALERT "Hello, World\n");
  return 0;
}

static void hello_exit(void)
{
  printk(KERN_ALERT "Goodbye, World\n");
}

module_init(hello_init);
module_exit(hello_exit);
3行目はライセンスの宣言.本によるとコード的に必須な記述ではないらしいが,ないと,次のようなエラーが出力される.
[ 7092.616521] hello: module license 'unspecified' taints kernel.
[ 7092.616525] Disabling lock debugging due to kernel taint
カーネルによって認識されるライセンスは次の6種類.
文字列意味
"GPL"GNU General Public Licenseの任意のバージョン
"GPL v2"GPL バージョン2のみ
"GPL and additional rights"
"Dual BSD/GPL"
"Dual MPL/GPL"
"Proprietary"所有者のある

printkはカーネルメッセージを出力する関数である.Cの文法と一致しないが,デバイスドライバのコーディングではこのような特殊な記述が多いようだ.KERN_ALERTはログレベル,すなわち重要度を表している.これにより,printkはメッセージの深刻さを分類する.ログレベルを示す文字列は次の8つである.深刻度は次の数字が小さいほど高い.

深刻度定数用途
1KERN_EMERG緊急メッセージ.通常クラッシュを起こすような状態を表す.
2KERN_ALERTすぐに対応が必要な状況を表す.
3KERN_CRIT危機的状況を表す.
4KERN_ERRエラー状況(例:ハードウェアのトラブル)を通知する.
5KERN_WARNING疑わしい状況についてのワーニング.
6KERN_NOTICE状況としては正常だが,注意が必要な状態.
7KERN_INFO情報メッセージ.設定時に検出したハードウェアに関する情報などを表示する.
8KERN_DEBUGデバッグ用のメッセージ
module_initとmodule_exitの行は,2つの関数の役割を示すための特殊なカーネルマクロである.module_initはモジュールがロードされた際に実行する関数,module_exitはモジュールが取り除かれる際(取り除かれる直前)に呼び出される関数をセットする.なお両者がセットした関数の型が異なることに注意.

Makefile

前述のソースコードのコンパイルは一般的なCのコンパイルと異なる.以下にそのために必要な特別なMakefileの文を記述する.

obj-m := hello.o
この1行を記述したコンパイルを次のように実行することで,カーネルのビルドシステムが前述のコードをコンパイルする.
user% make -C $PWD/linux-3.0.9 M=$PWD modules
Cオプションを使用した場合,makeは指定したディレクトリに移動してから処理を開始する."M=パス"はmodulesターゲットを作成する前に,モジュールのソースディレクトリにあるMakefileに戻ることを表す.正常にコンパイルができたならば,カレントディレクトリにいくつかのファイルとモジュールhello.koができているはずである.

実行例

実行例は次の通りである.

user% sudo insmod hello.ko
user% dmesg | tail -1
[ 5947.430306] Hello, World
user% sudo rmmod hello.ko
user% dmesg | tail -1
[ 6088.515992] Goodbye, World
dmesgはカーネルのメッセージを出力したり制御したりするコマンドである.sudoやtailにのコマンドの詳細については省略する.

Linux デバイスドライバ -よりプログラムらしく-

投稿日:
タグ:

目次

本稿は「Linux デバイスドライバ -Hello-」の続きであり,よりプログラムらしいプログラムを作るためのコードをいくつか紹介する.

タグ

関数やオブジェクトはタグを付けることで特殊な扱いを受ける.以下にその例をいくつか示す.

__init
この宣言を行った関数は初期化時のみメモリを使用し,すぐに解放する.例を以下に示す.
static int __init hello_init(void)
{
  printk(KERN_ALERT "Hello, World\n");
  return 0;
}
__initdata
この宣言を行ったオブジェクトは__initでのみ使用されることを表す.
__exit
クリーンアップ用の関数であることを指定する.コンパイラはこの宣言が行われた関数を特殊なセクションに配置する.
static void __exit hello_init(void)
{
  printk(KERN_ALERT "Goodbye, World\n");
  return 0;
}
__exitdata
この宣言を行ったオブジェクトは__exitでのみ使用されることを表す.

モジュール引数

パラメータの値はロード時にinsmodかmodprobeによって割り当てられる.modprobeは設定ファイルからパラメータを読み込むことが可能.設定ファイルの名前は,/etc/modprobe.confや/etc/modprobe.d/内の拡張子.confのファイルである.マニュアル"man modprobe.conf"でフォーマットについて確認ができる.

パラメータの取得は,<linux/moduleparam.h>で定義される関数module_paramやmodule_param_arrayで行う.この関数もまたmodule_initらと同様関数の外で使用しないとエラーである.

module_param(variable, type, perm)
variable
代入する変数名を指定する.
type
型を指定する.指定可能な型は次の通り.
bool論理値
invboolboolの逆の値
charp文字列へのポインタ値.
short整数値符号付き
int
long
ushort符号なし
uint
ulong
perm
パーミッション値.誰がsysfsに現れるモジュールパラメータにアクセスできるかを制御する.この引数には<linux/stat.h>にある定義を使用する.例えば,"S_IRUGO|S_IWUSR"はルートがパラメータを変更することを許可する.
module_paramを利用した例を以下に示す.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>

static int i;

module_param(i, int, S_IRUGO);

static int __init init()
{
  printk(KERN_INFO "i=%d\n", i);
  return 0;
}
static void __exit fini(){}

module_init(init);
module_exit(fini);
コンパイルは依然の記事の通り.以下がコンパイルと実行結果である.
user% make -C ./linux-3.0.9 M=$PWD modules
user% sudo insmod param.ko i=100 array={1,2,3,4,5}
user% dmesg | tail -1
[41654.053664] name:insmod pid:27744
user% sudo rmmod param.ko
module_param_array(variable, type, num, perm)
モジュールが引数として配列を与えられた時にそれを取得する.variableには配列を記述する.
num
変数の数を指定する.

カレントプロセス

カーネルモジュールの処理はアプリケーションと異なり,順に実行される訳ではなく,個々のプロセスに関連付けられる.現在のプロセスに関する情報は,<asm/current.h>で定義されているcurrentによって取得できる.currentは<linux/sched.h>で定義されるstruct task_struct型へのポインタである.

#include <linux/init.h>
#include <linux/module.h>
#incldue <asm/current.h>
#incldue <linux/sched.h>
MODULE_LICENSE("Dual BSD/GPL")

static void info_proc(void)
{
  printk(KERN_INFO "name:%s pid:%i", 
    current->comm, current->pid);
}
static int __init init(void){ info_proc(); return 0; }
static void __exit fini(void){ info_proc(); }

module_init(init);
module_exit(fini);

エラー

Linuxカーネルのエラーコードは負数で,<linux/errno.h>で定義されている.例えば定数ENOMEMに関連付けたエラーを表す場合,関数は-ENOMEMを返す.

kvmによるライブマイグレーション

投稿日:
タグ:

目次

kvmのインストールからライブマイグレーションまでのメモ.

実行環境

ホストマシン
CPUIntel(R) Xeon(R) CPU E3-1280 V2 @ 3.60GHz
OSLinux 2.6.32-279.el6.x86_64
ディストリビュージョンCentOS-6.3-x86_64
メモリ容量32GB
HDD容量16TB
ゲストマシン
OSLinux 2.6.32-279.el6.x86_64
ディストリビュージョンCentOS-6.3-x86_64
debian 6.0.6 amd64
メモリ容量1GB
HDD容量40GB

KVMを使用するための準備

ハードウェアの設定

KVMはXenやVMwareとは異なり,仮想化支援機能と呼ばれるハードウェアサポートが必要不可欠なので,BIOSの設定で必ず有効化する.今回の環境は,Intelマシンなので,Intel VT-xを有効化する(AMDならAMD-Vかsvm).

必要なソフトウェアのインストール

今回のホストマシンの環境では,次のコマンドで必要なコマンドが揃う.

user# yum groupinstall kvm
user# yum install vnc
vncはクライアントさえあれば良い.

用語
KVM
Kernel-based Virtual Machineの略.ハイパーバイザー型の仮想マシンモニタでLinuxの機能の一部.
QEMU
エミュレーションを行う仮想化マシンソフトウェア.多彩な動的バイナリトランスレーションにより,さまざまな種類のバイナリコードをさまざまなアーキテクチャ上で実行できる.しかし処理速度は,KVMやXenのようなほとんどの命令を実際のマシンで処理するものと比べると遥かに遅い.
libvirt
KVMやXenなどののVMMを制御するための抽象化ライブラリ.Red Hat社が主体となって開発しており,オープンソース化している.後述するvirt-managerはPythonでこのライブラリを読み込んで使用している.

ゲストマシンの作成から実行まで

KVMで仮想マシンを管理するツールには次のようなものがある.

CUI
qemu-kvm
qemu
QEMUエミュレータを管理するためのツール.システムによってコマンド名が異なる.
virsh
libvirtのリファレンス実装として作られた対話型のシェルプログラム.
GUI
virt-manager
libvirtを使って仮想マシンを管理するGUIツール.
今回はCUIを使った方法でも仮想マシンを起動した後はGUIのツールでインストールを行う.

qemu-kvm(qemu)を使った方法

qemu-kvm(qemu)を使った場合の最初のOSの実行までの流れは次の通りである.

  1. 仮想HDDの作成.
  2. インストールCDのイメージをブート対象にしてVMを実行.
  3. 仮想HDDをブート対象にしてVMを実行.

仮想HDDの作成

仮想HDDはqemu-imgコマンドで作成できる.qemu-imgはQEMUの仮想イメージを扱うためのコマンドである. 実行方法は次のように行う.

user# qemu-img create -f qcow2 ./hda/test.img 40G
qemu-imgは第1引数でコマンド名,続けてオプションを指定する.
create
仮想HDDの作成.
-f qcow2
仮想HDDのフォーマット.qcow2は可変サイズの仮想HDD.
test.img
仮想HDDファイルの名前(パス).
40G
仮想HDDのサイズ.

インストールCDのイメージをブート対象にしてVMを実行

仮想HDDへのOSのインストールも仮想HDDからの起動も同じコマンドでできる.違いは,ブートの指定だけである.

user# qemu-kvm -cdrom ./img/CentOS-6.3-x86_64-bin-DVD1.iso -hda ./hda/test.img -boot d -m 1024 -usbdevice tablet -name test
オプションについては後で説明する.QEMUは,通常VGAの出力にSimple DirectMedia Layer(SDL)というグラフィックやサウンドを扱うライブラリを使用するが,qemu-kvmのオプションによりオフにしたりテキストモード(-curses)で入出力を行ったりすることができる.今回はVNCクライアントを使用する.qemu-kvmを実行すると次のようなものを出力する.
VNC server running on `::1:5900'
::1はlocalhostのことで:5900はポート番号を表す.これはVNCでアクセス可能なアドレスとポート番号である.ここにVNCクライアントでアクセスすれば,後は実際のマシンと同様インストールできる.
user# vncviewer localhost:5900

以下にqemu-kvmのオプションをいくつか紹介する.

オプション機能
-hda./hda/test.img使用する仮想HDDの指定.
-cdrom./img/CentOS-6.3-x86_64-bin-DVD1.iso使用する仮想cdrom(isoが指定可能)の指定.
-m1024メモリのサイズ(MB単位)の指定.
-bootd最初のcdromから起動.
c最初のhdaから起動.
order=dcブートの優先順位を指定.この例では最初のcdromを第1候補,最初のhdaを第2候補に指定.
-usbdevicetablet 本来の用途は不明だがVNCでアクセスした際にVMのマウスポインタとホストのマウスポインタのずれをなくすことができる.
-nametestゲストマシンの名前を指定.
-vncdisplay[, option, ..]vncの設定を変更.

インストールCDのイメージをブート対象にしてVMを実行.

基本的に先ほどのコマンドの-bootオプションのパラメータを変更しただけだが,-cdromのオプションが不要になるので,それも指定しない.

user# qemu-kvm --hda ./hda/test.img -boot c -m 1024 -usbdevice tablet


virt-managerを使った方法

libvirtを使ったGUIツールのvirt-managerを使えば,仕組みを知っているだけで簡単に仮想マシンの管理が行える.ただし私の環境では,標準では一般ユーザが扱えなかった.一般ユーザでも使用可能にするために取った策は「作業中に発生した問題」で説明する.

virt-managerでは,[New]によって新しい仮想マシンを作成することができ,基本的にそれに従っていけば仮想マシンの作成は容易である.しかしその前に接続について話す.

libvirtdへの接続

virshやvirt-managerは,別のマシンのlibvirtdに接続することで,リモートで管理することが可能である.上の画像では,接続はローカルマシン([localhost(QEMU)])だけである.virt-managerでは,接続を[ファイル]から[Add Connection]を選択することで,追加できる.ちなみに接続はssh経由で行うことができる.

ストレージ

[localhost[QEMU]]を右クリックして[詳細]を左クリックすると,次のような画面が出る(画像では一部隠しているが).

ここで,libvirtdに関する設定を行うことができる.私の環境では,標準で仮想HDDやisoファイルを管理するストレージプールに/var/lib/libvirt/imagesが設定されていた.

しかし今回はライブマイグレーションするためにNFSマウントした場所を使用するので,[ストレージ]タブを選んでそこからdefaultストレージプールをNFSでマウントした先のパスに変更する(というかGUIでの変更の仕方が分からないので削除してまたdefaultストレージプールを作成した).なお,試してはいないが,ストレージは/etc/libvirt/storage/でxml形式のファイルとして管理されており,それを修正しても変更はできるらしい.

新たにストレージプールを作る場合,タイプの指定を行う.その時,[netfs:Network Exported Directory]を指定してフォーマットをNFSに選択することで,NFSマウントすることも可能らしいが,今回は[dir:FileSystem Directory]を選んで既にNFSマウントしてあるパスを指定する.

仮想マシンの作成

libvirtdの設定を終わったところでvirt-managerでの仮想マシンの作成について触れるが,前述の通り[New]をクリックした後は簡単である.次のようなサブウィンドウが立ち上がり,基本的に指示に従うだけで良い.

なのでこれ以上の説明は行わない.

仮想HDDが作成される場所はストレージで設定した通りである.


libvirtを利用したCUIツールを使った方法

libvirtを利用したCUIアプリはさまざまある.管理のためにvirsh,インストールのためにvirt-install,GUIで仮想マシンをモニタリングするためにvirt-viewerらがある.virshだけでも仮想マシンの作成は可能そうだが,今回はvirt-installを使用する.

virt-installを使った仮想マシンの作成にはオプションを指定する方法と対話的に行う方法がある.使い方はそれぞれ次のような感じである.

  • uesr% virt-install \
    --connect qemu:///system --name test2 \
    --ram 1024 --disk path=./test2.img,size=40 \
    --cdrom /img/debian-6.0.6-amd64-DVD-1.iso
  • user# virt-install --prompt
    仮想マシンの名前は何ですか? test2
    どれだけの RAM を割り当てますか (メガバイト単位で)? 1024
    What would you like to use as the disk (file path)? ./test2.img
    どの位の大きさのディスク (./test2.img) にしたいですか(ギガバイト単位で)? 40
    インストール CD-ROM / ISO イメージ、もしくは URL の場所はどこですか? ./img/debian-6.0.6-amd64-DVD-1.iso
    
これらを設定した後,仮想マシンを起動させ,virt-viewerやVNCクライアント,テキストモードなどのインタフェースでインストールを行う.

ライブマイグレーション

ライブマイグレーションにはlibvirtを使用する(qemu-kvmでできるのかは未確認).当然だがライブマイグレーションには2つ以上の接続(localhostを含めても良い)が必要である.また,仮想マシンはNFSマウントした場所に格納されており,2つの接続のdefaultストレージは全く同じパスで全く同じNFSマウントを行なっている.この前提の基でライブマイグレーションは次のように行う.

virt-managerを使った方法

  1. 実行中の仮想マシンを選択して右クリック.
  2. [マイグレーション]を選択.
  3. サブウィンドウが立ち上がって通信に関するオプションや接続先ホストなどを選択.
  4. [マイグレーション]を選択.

virshを使った方法

virshコマンドを使った方法は次のような感じである.

user# virsh migrate --live test2 qemu+ssh://user@host/system
test2は仮想マシンの名前を,qemu+ssh://user@host/systemは移行先のホストマシンを指す.なお/systemは全面的アクセスが必要であることをlibvirtに伝えるために指定する.

作業中に発生した問題

最後に私が遭遇した問題(それほど大げさなものでもないが)を紹介する.

  • 仮想化支援機能が有効化されていない.
  • libvirtを利用した方法とqemu-kvmを利用した方法を併用すると管理が面倒.
  • qemu-kvmの場所
  • libvirtを利用したツールで接続ができない.

qemu-kvmの場所

私の環境では,qemu-kvmが/usr/libexec/にあった.

libvirtを利用したツールで接続ができない.

libvirtdが使用するソケットファイル(自分の環境では/var/run/libvirt/libvirt-sock)らは標準では一般ユーザがアクセスできない.そのためroot権限で使用する必要がある.ただし今回はssh経由の接続を行いたかったので,sshのrootログインを避けるために設定ファイルを修正し,補助グループを新たに追加した.変更後の設定ファイル(/etc/libvirt/libvirtd.conf)と変更前の設定ファイル(/etc/libvirt/libvirtd.conf.old)の差分は次の通りである.

user# diff /etc/libvirt/libvirtd.conf /etc/libvirt/libvirtd.conf.old
81c81
< unix_sock_group = "libvirt"
---
> #unix_sock_group = "libvirt"
88c88
< unix_sock_ro_perms = "0777"
---
> #unix_sock_ro_perms = "0777"
98c98
< unix_sock_rw_perms = "0770"
---
> #unix_sock_rw_perms = "0770"
101c101
< unix_sock_dir = "/var/run/libvirt"
---
> #unix_sock_dir = "/var/run/libvirt"
129c129
< auth_unix_ro = "none"
---
> #auth_unix_ro = "none"
138c138
< auth_unix_rw = "none"
---
> #auth_unix_rw = "none"
 
設定ファイルの81行目でソケットの所有グループをlibvirtに変更したので(標準ではroot),それに伴いそのグループの追加とユーザの所属するグループにlibvirtの追加を行う.2つの処理はそれぞれ次の通りである.
user# groupadd libvirt
user# usermod -aG libvirt user
最後にlibvirtdを再起動すれば設定が反映される.
user# /etc/init.d/libvirtd start

gmirror

投稿日:
編集日:
編集日:
タグ:

本稿はgmirrorの使い方に関するメモ.復旧と構築関連の使い方は次に使うまでの間隔が長すぎて良く忘れるので(そして調べた後にたったこれだけだったと嘆くので).ちなみにOSはfreeBSD8.2.

gmirrorとは

gmirrorとはGEOMのクラスの1つで,これによりソフトウェアraid1(ミラーリング)が可能となる.

GEOMとはデバイスドライバとカーネルの上位レイヤの間に位置するフレームワークのことである.ユーザはGEOMにクラスを追加することで新しい機能を簡単に追加することができる.参考までに以下に他のクラスの例をいくつか載せる.

  • gstripe
  • graid3
  • gjournal
  • gvinum


ソフトウェアraid1(ミラーリング)の構築

raidを組む補助記憶装置がマウントされていない必要がある.そのため,OSを書き込んだ補助記憶装置でraidを行うためには,それ以外からGEOMが使えるOSを起動するか、sysctlでkern.geom.debugflagsというパラメータを変更しなければならない.私の場合,インストールしたFreeBSDのディスクからOSをブートし,そこで処理を行った.

gmirrorでソフトウェアraid1を行う場合,大まかに次の手順で行う.

  1. GEOMのmirrorクラス(モジュール)を読み込む.
  2. 新しいミラーを作る.
  3. ミラーにコンポーネントを追加.
ミラーとはgmirrorによって仮想的に作成された補助記憶装置のことで,コンポーネントとはここでは補助記憶装置を指す.

GEOMのmirrorクラスの読み込み

GEOMのmirrorクラスを読み込む方法はさまざまある.

  • kldload
  • gmirror load
  • geom mirror load
それぞれの詳細はmanを見れば良いので省略し,ここではgmirrorを使った例だけ示す.
user% gmirror load
モジュールはkern.module_pathで設定されたパス群のいずれかから読み込まれる.パスは次のコマンドで確認できる.
user% sysctl kern.module_path
kern.module_path: /boot/kernel;/boot/modules
これらのパスの中を探すとgeomのmirrorモジュールが見つかるはずである.
ls /boot/kernel /boot/modules | grep geom_mirror
geom_mirror.ko
geom_mirror.ko.symbols

新しいミラーを作成

新しいミラーを作るための最も単純なコマンドは次の通りである.

user% gmirror label gm0 /dev/ad1
labelではさらにオプションを指定することでいくつかの設定を行える.例えば-bを使うことでバランス(実際に補助記憶装置にアクセスする際にどれにアクセスするかを決める方針)を指定できる.バランスに指定できる方針は次の4つである.
load最も負荷の低いコンポーネントを優先的に読み込む.
prefer優先度の高いコンポーネントを優先的に読み込む.
round-robinそれぞれのコンポーネントを順番に読み込む.
splitsliceサイズ以上の読み込み要求に対してアクティブなコンポーネントの数で分けて読み込む.sliceサイズはlistコマンドで確認できる.
sliceサイズはlabelのオプション-sで指定可能である.また,labelを含むgmirrorの多くのコマンドは-vを付けることで詳細な情報を出力してくれる. そのため-vを付ける人も少なくない.オプションを指定した場合次のような感じになる.
user% gmirror label -v -b round-robin gm0 /dev/ad1

ミラーにコンポーネントを追加

前述の方法で作成したミラーはコンポーネントが1つ登録されているだけの状態である.それゆえさらにコンポーネントを追加して冗長化しなければgmirrorを使う意味はない.コンポーネントの追加にはinsertを使用する.使い方は次の通りである.

user% gmirror insert gm0 ad6
以上まででミラーの設定は終了であるが,まだ冗長化された訳ではない.insert直後にstatusコマンドを使用すると,恐らく次のような結果が出力される.
user% gmirror status
      Name    Status  Components
mirror/gm0  DEGRADED  ad1
                      ad6(0%)
ad6(0%)は同期率を表している.それゆえこれが100%になるまではまだ冗長化は完了していない.しばらく放置して,次のような状態になれば同期完了である.
user% gmirror status
      Name    Status  Components
mirror/gm0  COMPLETE  ad1
                      ad6

OSを書き込む場合

OSの入ったコンポーネントでソフトウェアRAID1を構築する方法は2通りある。

  • 別のコンポーネントからOSを起動しそちらで設定を行う。
  • GEOMモジュールのデバッグフラグを設定し、起動中のHDDの書き換えを可能にする。
後者はkern.geom.debugflagsを0x10に設定すれば良い。設定は次のようにsysctlコマンドで行うことができる。
user% sysctl kern.geom.debugflags=16
パラメータの値は"=16"を省略することで確認可能である。

また、ソフトウェアRAID1を行ったコンポーネントからOS及びそれを格納したファイルシステムを読み込むには、次の2つの設定が必要である。

  • ローダの設定(/boot/loader.conf)をGEOMのmirrorクラスを読み込むように設定.
  • ファイルシステムのマウント設定(/etc/fstab)を修正.

GEOMのmirrorクラスは標準では自動的に読み込まれないので,ローダの設定を修正する必要がある.具体的には/boot/loader.confに以下の文を追加する.

geom_mirror_load="YES"

OSのマルチユーザモードでは/etc/fstabに記述されたファイルシステムをマウントするので,/etc/fstabを修正する必要がある./dev/ad1にOSをインストールした場合,ad1s1aやad1s1bなどのパスが使用されているはず.なので/dev/ad1の部分を全て作成したミラーのパス(/dev/mirror/gm0)に置き換えれば良い.以下に私の環境の例を示す.

# Device                Mountpoint      FStype  Options         Dump  Pass#
/dev/mirror/gm0s1b      none            swap    sw  		0       0
/dev/mirror/gm0s1a      /               ufs     rw 	 	1       1
/dev/mirror/gm0s1e      /tmp            ufs     rw  		2       2
/dev/mirror/gm0s1f      /usr            ufs     rw  		2       2
/dev/mirror/gm0s1d      /var            ufs     rw  		2       2


補助記憶装置が壊れた際の処理

あるコンポーネントでエラーが多発すると,gmirrorはそのコンポーネントをミラーへの接続から解除する.statusコマンドで確認すると,恐らく状態がDEGRADEDになり,コンポーネントの一覧からそれが消えているはずである.また,より詳細な情報を出力するlistコマンドを使用すると,正常なディスクはStateがACTIVEで,故障したディスクはSTALEとなっているはずである.故障してHDDを交換する場合,まずforgetコマンドを使用する.

user% gmirror forget gm0
これによりミラーに登録されたコンポーネントの中で接続されていないコンポーネントの情報を削除する.そして次に補助記憶装置を交換し,最後にinsertコマンドで新しいコンポーネントを追加すれば,あとは同期終了まで待つだけである.
user% gmirror insert gm0 ad1

また、もし以前のメンバと異なる場合、"Not all disks connected"というエラーメッセージが出力される。その場合、一度forgetを使用する必要がある。

user% gmirror forget gm0

再構成

gmirrorのメタ情報を全て削除するには、コンポーネントがbusyじゃない状態(例えば、モジュールをアンロードした状態やコンポーネントが停止した状態)で次のように行う。

user% gmirror clear /dev/ad0
そして、改めて新しいミラーの作成を行えば良い。コンポーネントの停止やモジュールのアンロードは次のように行える。
  • user% gmirror stop /dev/ad0
  • user% gmirror unload

gmirrorとgpart

gmirrorを使い、かつパーティションでHDDを区切る場合、次のような実装方法がある。

  • パーティション毎にミラーリングする方法
  • ミラーリングした仮想コンポーネントをパーティショニングする方法
本稿では、gpartを使用した後者の方法を紹介する。gpartを使用する上では「19.3. RAID1 - Mirroring」を参考にした。
  1. モジュールのロード。
    user% gmirror load
  2. ソフトウェアRAID1コンポーネントの構築
    user% gmirror label -v -b round-robin gm0 /dev/ada1
  3. GUIDパーティションを生成。
    user% gpart create -s MBR /dev/mirror/gm0
  4. /dev/mirror/gm0s1を作成。
    user% gpart add -t freebsd -a 4k /dev/mirror/gm0

  5. BSDの論理パーティションを生成。
    user% gpart create -s BSD mirror/gm0s1
  6. 200GBの/dev/gm0s1aを生成
    user% gpart -t freebsd-ufs -a 4k -s 200g /dev/mirror/gm0s1
  7. 250GBの/dev/gm0s1d(たぶんd)を生成
    user% gpart -t freebsd-ufs -a 4k -s 250g /dev/mirror/gm0s1

  8. gm0s1aのファイルシステムを構築。
    user% newfs -U /dev/mirror/gm0s1a
  9. gm0s1dのファイルシステムを構築。
    user% newfs -U /dev/mirror/gm0s1d
なおパーティション情報は次のようにshowコマンドで取得できる。
user% gmirror show /dev/mirror/gm0s1
試したところ、OSのインストール前であれば、インストール前にシェルを起動して、この処理を行ってからインストール先に作成したパーティションを指定することが可能だった。ただし、OSを起動する前に/boot/loader.confを書き換える必要がある。また、OSを格納したファイルシステムをブート可能にするには、次のような処理が必要である。ここで、カーネルはgm0s1aに格納されており、マウント位置は"/"とする。
user% gpart bootcode -b /boot/mbr /dev/mirror/gm0
user% gpart set -a active -i 1 /dev/mirror/gm0
user% gpart bootcode -b /boot/boot /dev/mirror/gm0s1

エラー

gpartやgmirrorの処理を行った後にインストーラでOSをした時、次のようなエラーが出力された(/boot/loader.confは変更済みだった)。

Mounting from ufs:/dev/mirror/gm0s1a failed with error 19.

Loader variables:
  vfs.root.mountfrom=ufs:/dev/mirror/gm0s1a
  vfs.root.mountfrom.options=rw

Manual root filesystem specification:
  <fstype>:<device> [options]
      Mount <device> using filesystem <fstype>
      and with the specified (optional) option list.

    eg. ufs:/dev/da0s1a
        zfs:tank
        cd9660:/dev/acd0 ro
          (which is equivalent to: mount -t cd9660 -o ro /dev/acd0 /)

  ?               List valid disk boot devices
  .               Yield 1 second (for background tasks)
  <empty line>    Abort manual input

mountroot>
私の場合、"ufs:/dev/gm0s1a"を指定して起動し、そこで再度前述の"gpart bootcode"の処理を行うことで解決できた。

OpenVPN for iPhone

投稿日:
タグ:

目次

先日iPhone用のOpenVPNのクライアントが出てることにようやく気づいたので,その設定をメモ.

インストール

クライアントのインストールにはApp Storeから検索して普通にインストール可能である.

設定

設定についてははじめに汎用マシンの方で設定ファイルや鍵,証明書などの必要なファイルを用意し,それをiPhone側に送ってそれをiPhoneのOpenVPNクライアントで開けば良い.

ovpnファイル

OpenVPNではいくつかの場合で設定ファイルの拡張子に.ovpnを使用する.ovpnファイルは基本的に汎用マシンのOpenVPNの設定ファイルとして使用するconfファイルと同じである.ただしアプリケーションの種類かファイルのフォーマットの違いにより,いくつかの書き方がある. ここでは,iPhone用のOpenVPNクライアントのためにクライアントの秘密鍵(key)と証明書(cert),CA証明書(ca)を埋め込んだものを作成し,それを利用する.例えば各ファイルの中身がそれぞれ次のような場合,

ca.crt(CA証明書) ca
-----BEGIN CERTIFICATE-----
$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$
-----END CERTIFICATE-----
client.csr(クライアントの証明書) cert
-----BEGIN CERTIFICATE REQUEST-----
%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%
-----END CERTIFICATE REQUEST-----
client.key(クライアントの秘密鍵) key
-----BEGIN RSA PRIVATE KEY-----
##################
##################
-----END RSA PRIVATE KEY-----
これらを組み込んだovpnファイルは次のような中身になる.
client
port 1194
proto tcp
dev tun
remote hoge.jp
keepalive 10 120
comp-lzo
persist-key
persist-tun
verb 3
mute 20

<ca>
-----BEGIN CERTIFICATE-----
$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN RSA PRIVATE KEY-----
##################
##################
-----END RSA PRIVATE KEY-----
</key>

設定ファイルが用意できたらそれをiPhone側に送る.方法はさまざまだが私は部屋のファイルサーバを利用し,iPhoneアプリ"FileExplorer"経由で行った.

  1. ovpnファイルが格納されているディレクトリまで移動し,ovpnファイルを選択.
  2. [Open In]を選択.
  3. [Open in OpenVPN]を選択.
  4. 新しい接続名(この例ではhoge.jp)の[+]を選択.
  5. □の部分でOpenVPNのON/OFFを変更.

OpenVPN実行中は画像のように"VPN"という文字が上に表示される.

Darwin Streaming Server

投稿日:
タグ:

ストリーミングサーバ「Darwin Streaming Server」の構築に関するメモ.ちなみに構築環境はSabayon 13.04 amd64版で,カーネルのバージョンは3.8.0である.

事前準備

はじめにDarwin Streaming Serverが使用するユーザとグループを追加する.

user% sudo groupadd qtss
user% sudo useradd -g qtss qtss

構築

次にソースコードを展開する.ソースコードはMacOS forgeのページから取得できる.

user% tar xvf DarwinStreamingSrvr6.0.3-Source.tar

パッチ

Darwin Streaming Serverにはいくつかのパッチがあり,必要に応じてパッチをダウンロードし,それをあてる.以下にいくつかパッチを紹介する.

Linux用パッチ
user% patch -p0 < dss-6.0.3.patch
Linux 64bit用パッチ
user% patch -p0 < dss-hh-20080728-1.patch

パッチは解凍を行ったディレクトリであて(注意:解凍したディレクトリではなく),その後解凍したディレクトリに移動し,コンパイルを行うコマンドを実行する.

user% cd DarwinStreamingSrvr6.0.3-Source/
user% ./buildtarball

インストール

前述のコマンドが無事修了すると,"Success!"という文字が出力される.その後,更に下位のディレクトリに移動し,インストールのためのコマンドを実行する.コマンドを走らせてしばらくすると後述のようなプロンプトが順番に出て,管理者の追加とそのパスワードの設定を行う(ここでは管理者名をadminとする).

user% cd DarwinStreamingSrvr-Linux
user% sudo ./Install
・
・
Please enter a new administrator user name: admin
・
・
Please enter a new administrator Password:      
インストールされるソフトウェアのうちユーザが知っていれば良いものは次の2つである.
DarwinStreamingServer
ストリーミングサーバ本体.
streamingadminserver.pl
ストリーミングサーバの管理用のWEBサーバ.
以降それぞれのサーバを起動させたい時はこれらのコマンドを実行する.OS起動時に実行した場合はrc.localなどを利用してこれを実行すれば良い.また,streamingadminserver.plは一般ユーザでも実行可能なようだったが,プレイリストやパラメータなどのファイルの権限の関係もあるので注意されたし.

streamingadminserver.pl

ストリーミングサーバの設定はWEBブラウザから行う.streamingadminserver.plを実行,または前述のInstallプログラムを実行すると,管理用のWEBサーバが実行される.管理用ページへのアクセスにはWEBブラウザのアドレスバーに"http://localhost:1220"と入力すれば良い.入力後次のようなログイン画面が表示されるはずである.

ログイン画面のユーザ名とパスワードは前述に設定したものを使用する.この例ではユーザ名がadminとなる.

管理用ページは詳細な説明が不要なぐらい簡単で基本的にページ内に書いている通りに行えば良い.

ただし私の環境では,権限が原因なのか文字コードが原因なのかは知らないが,ファイル名が表示されなかったり,プレイリストを作ろうとしてエラーになったりして,少し苦労した.

iptables

投稿日:
タグ:

目次

本稿はiptablesに関するメモ.iptablesとはLinuxカーネルのIPパケットフィルタ機能を管理するツールである.iptablesのコマンドを使用した例は次の通りである.

user% iptables -t filter -A INPUT -s 127.0.0.1 -j DROP
この例に関する説明は後述するが,用途によってこれより更に引数の数は増える.

iptablesとは

iptablesとはLinuxカーネルのIPパケットフィルタ機能を管理するためのツールのこと.IPパケットフィルタはテーブル内で定義されたルールに従ってパケットに対して検査や修正などさまざまな処理を行う.

IPパケットフィルタは目的ごとに異なる複数のテーブルに基づいて処理を行う.テーブルは更にチェインと呼ばれる複数のルールのリストを持つ.

キーワード

テーブル
チェインの集合.テーブルは用途毎に分けられており,それぞれ異なるチェインを持つ.異なるテーブルで同じチェインを持つ場合,テーブルによって適応される順番が決まる.
チェイン
IPパケットを検査するルールのリスト.検査するシチュエーション毎にさまざまなチェインがある.ルールはリストの順番に従ってチェックされ,適合した時点で以降のチェックは行われない.
ターゲット
条件にマッチしたパケットに対して行う処理.ターゲット名については「target」を参照.


テーブル

iptablesは標準でいくつかのテーブルを持つ.標準で利用可能なテーブルはバージョンに依存する.詳細はマニュアルで確認できる.ここで,代表的なテーブルを2つ紹介する.

filter
マシンが行う基本的なパケット処理を行う際に参照されるテーブル.iptablesはユーザがテーブルを指定しなかった場合にこのテーブルを利用する.
チェイン:
INPUT
パケットフィルタを行うマシンが宛先となる受信パケットに対するチェイン.
OUTPUT
パケットフィルタを行うマシンが送信元となる送信パケットに対するチェイン.
FORWARD
パケットフィルタを行うマシンを経由するパケットに対するチェイン.
nat
新しい接続を行うようなパケットを扱う際に参照されるテーブル.
チェイン:
OUTPUT
パケットフィルタを行うマシンが送信元となる送信パケットに対するチェイン.
PREROUTING
パケットが受信時に対するチェイン.
POSTROUTING
パケットが送信時に対するチェイン.
mangle
IPパケットヘッダの書き換えを行うために使用される特殊なテーブル.本稿では説明を省略.


オプション

テーブル指定を除くiptablesのオプションはいくつかのグループに分類される.

例えば,テーブル"filter"内のチェイン"INPUT"にルール"送信IPアドレス127.0.0.1からきたパケットを破棄"を追加する処理は次のように行う.
user% iptables -t filter -A INPUT -s 127.0.0.1 -j DROP
この時,これらの引数は次のように分類される.
テーブルを指定.
-t filter
コマンドチェインを指定.
-A INPUT
パラメータその他のオプションを指定.
-s 127.0.0.1 -j DROP

テーブル指定

よく使いそうなオプションをメモ.

オプション1 オプション2 引数 詳細
-t--tabletableテーブルを指定.このオプションを省略した場合は"filter"を使用.
コマンド

iptablesの処理を指定するためのオプションをメモ.

オプション1 オプション2 引数 詳細
-L--list[chain] 指定したテーブルの指定したチェインのルールを一覧表示する.chainを省略した場合,指定したテーブルの全てのチェインに対して行う
-A--appendchain rule-specification 指定したテーブルとチェインに対して,rule-specificationで指定した1つ以上のルールを追加する.
-I--insertchain [rulenum] rule-specification 指定したテーブルとチェインに対してrule-specificationで指定した1つ以上のルールを挿入する(-Aと違いルールの優先順位を指定可能).rulenumが1の場合は先頭に挿入.省略した場合も同様の処理.
-D--delete
  • chain rule-specification
  • chain rulenum
指定したチェインから1つ以上のルールを削除する. 削除するルールはrule-specificationとマッチするルールか,rulenumで指定した番号のルール.
-F--flush[chain] 指定したチェインのルールを全て削除する. chainを省略した場合,指定したテーブルの全てのチェインに対して行う.
rule-specification
[matches ...] [target]
match
-m matchname [per-match-options]
target
-j targetname [per-target-options]
パラメータ

ここでは次のコマンドで使用されるオプションをメモ.iptables1.4.17のマニュアルによると,この4つに--addを加えたもの(このコマンドの詳細は不明だが).

  • --append
  • --delete
  • --insert
  • --replace

オプション1 オプション2 引数 ターゲット 詳細
-s--source[!] address [/mask] 送信元アドレス,または!がついた場合これ以外の送信元アドレスを意味する.--srcという別名も.
-d--destination[!] address [/mask] 宛先アドレス,または!がついた場合これ以外の宛先アドレスを意味する.--dstという別名も.
-p--protocol[!] protocol 指定したプロトコル,または!が付いた場合は指定したプロトコル以外を意味する. protocolには/etc/protocolsで定義されたプロトコル名かallや,それに対応する数値を使用可能(例:tcp,udp).allは全てのプロトコルを意味する.
-i--in-interfacename
  • INPUT
  • PREROUTING
  • FORWARD
受信パケットの検出を行うインタフェースを指定.インタフェース名が+で終わる場合,その接頭辞で始まる任意のインタフェースを指す.
-o--out-interface name
  • OUTPUT
  • POSTROUTING
  • FORWARD
送信パケットの検出を行うインタフェースを指定.インタフェース名が+で終わる場合,その接頭辞で始まる任意のインタフェースを指す.
-j--jumptargetname [per-target-options] ターゲットを指定する.
-m--matchmatchname [per-match-options] 指定した拡張パケットフィルタリングモジュールをロードし,処理を指定する.
パラメータの拡張
マッチングの拡張(per-match-options)

iptablesは拡張パケットフィルタリングモジュールを利用できる.拡張パケットマッチングモジュールは-pで暗黙的にロードされるか,-mで明示的に指定することでロードできる.

例えば送信元のMACアドレスを制限する場合のサンプルは次の通りである.

user% iptables -t filter -A INPUT -m mac --mac-source 00:1d:09:d1:fb:d5 -j ACCEPT

オプション 引数 モジュール チェイン 詳細
--mac-source value[/mask] mac
  • PREROUTING
  • FORWARD
  • INPUT
送信元MACアドレスを表す.
--source-portsport[,port[,port...]] multiport 指定したポート番号郡のいずれかにマッチすることを意味する.--destination-portsは宛先,--source-portsは送信元のポート番号を検査する.
--destination-portsport[,port[,port...]]
--destination-port[!] port[:port] tcp --protocol tcpが指定された場合暗黙的にモジュールがロードされ,使用可能になるオプション.送信元や宛先のポート番号やポート範囲を指定できる.--destination-portは--dport,--source-portは--dportという別名あり.
--source-port
ターゲットの拡張(per-target-options)
オプション 引数 ターゲット チェイン 詳細
--to-destination ipaddr1 [-ipaddr2] [:port1-port2] DNAT
  • PREROUTING
  • OUTPUT
新しい宛先IPアドレスを指定する.ポートの指定は-p tcpや-p udpを指定している場合のみ有効.
--to-source ipaddr1 [-ipaddr2] [:port1-port2] SNAT
  • POSTROUTING
新しい送信元IPアドレスを指定する.ポートの指定は-p tcpや-p udpを指定している場合のみ有効.
--to-ports port1 [-port2] MASQUERADE
  • POSTROUTING
MASQUERADEの場合は送信元ポート,REDIRECTの場合は送信先ポートや範囲を指定.このオプションは-pでtcpかudpを指定した場合のみ有効.
REDIRECT
  • PREROUTING
  • OUTPUT
その他のオプション
オプション1 オプション2 引数 詳細
-v--verbose詳細な出力を行う.
-n--numeric標準ではIPアドレスやポート番号などを可能な限りドメイン名やプロトコル名で表示しようとするが,これを指定した場合数値で表示する.

ターゲット

jオプションで指定可能なtargetnameについていくつか紹介する.

ACCEPT
パケットの通過を許可.
DROP
パケットの通過を拒否.REJECTと異なりICMPパケットを返信しない.
SNAT
パケットの送信元アドレスを変更.
チェイン:
  • POSTROUTING
DNAT
パケットの宛先アドレスを変更.
チェイン:
  • PREROUTING
  • OUTPUT
LOG
カーネルログに記録.カーネルログはdmesgコマンドで閲覧可能.
REJECT
パケットを破棄し,ICMPパケットを返信.
チェイン:
  • INPUT
  • FORWARD
  • OUTPUT
REDIRECT
別のポートに転送.
チェイン:
  • PREROUTING
  • OUTPUT
MASQUERADE
パケットの送信元アドレスをパケットフィルタを行うインタフェースのIPアドレスに変更する.もしインターフェースが停止した場合,接続を忘れる.manによるとパケットフィルタを行うインタフェースのIPアドレスを固定割り当てする場合はSNAT,動的割り当てする場合はMASQUERADEを使うべきとのこと.
チェイン:
  • POSTROUTING
TCPMSS
-pでtcpを指定した際のみ指定可能なターゲット.SYNパケットのMSS値を設定可能.
TOS
IPパケットヘッダのType Of Serviceフィールドを設定可能.
ULOG
マッチしたパケットのログをユーザ空間で記録.


制御用スクリプト

LinuxのIPパケットフィルタのデーモンはスクリプトによって制御する.スクリプトの実行方法は環境によって異なる.例えば次のように直接指定する方法やserviceコマンドを経由して実行する方法がある.

  • user% /etc/init.d/iptables start
  • user% service iptables start
以下にパケットフィルタのデーモンを制御するスクリプトに関する引数をいくつかメモ.
start
IPパケットフィルタのデーモンを実行.
restart
IPパケットフィルタのデーモンを再実行.
stop
IPパケットフィルタのデーモンを停止.
status
IPパケットフィルタのデーモンの状態を確認.
save
IPパケットフィルタのデーモンの現在の設定を設定ファイルに書き込む.iptablesコマンドによるテーブルの変更は直接設定ファイルを書き換えない.それゆえOSを再実行にも設定維持するためには設定ファイルを書き換えなければならない.

自動実行

OS起動時にデーモンを自動的に起動する方法も環境に依存する.私の環境では次のようなコマンドを使用する.

user% chkconfig iptables on

設定ファイル

iptablesの設定ファイルは各行を上から順に記述され,次の5種類に分類される.

接頭辞表記詳細
##commentsコメントを記述.
**table テーブルの指定.
: :chain policy あるchainに対するポリシー(共通ルール)を設定.
:user_chain - ユーザ定義のチェインの場合はこちらのフォーマット.
rule あるchainに対する個別のルールを設定.
COMMITCOMMITファイルの先頭か最後にCOMMITしてから記述した分を反映.
設定ファイルは私の環境(CentOS)では,/etc/sysconfig/iptablesにあった.


おまけ

IPパケット

iptablesはIPパケットの構造について知らなくても利用可能だが一応メモ.

version
IPのバージョン.IPv4なら4が格納されている.
IHL(Internet Header Length)
IPヘッダの長さ.通常32ビット(4バイト)単位で表現される.例えばこのフィールドの値が15ならば,長さは480ビット(60バイト)を意味する.
TOS(Type of Service)
優先度などを表すために利用可能なフィールド.
Total Length
パケットの長さ.値の単位はバイトである.
ID
IPパケットの識別番号.このフィールドは断片化されたパケットが同一のパケットであることを表す.
VCF(Various Control Flags)
断片化の制御のために利用されるフィールド.
Fragment Offset
断片化されたIPパケットの相対位置を表すフィールド.
TTL(Total to Live)
パケットの生存時間.ルータはルーティングの度にTTLの値を1つ減らす.そして0になった時,パケットを破棄し,ICMPパケットを返信する.これにより,パケットが無限に巡回することを防ぐことができる.
protocol
トランスポート層のプロトコルを表す番号が格納されるフィールド.
Header Checksum
ヘッダの誤り検査に用いられるフィールド.
Source Address
送信元アドレス.
Destination Address
宛先アドレス.
Options
32ビット(4バイト)単位の可変長なフィールド.機能を拡張するために使用される.
data
IPパケットで送信されるデータ本体.

Ruby GTK+2 画像処理

投稿日:
修正日:
タグ:

本稿はRuby GTK+2の画像関係に関するメモである.

Ruby GTK+2で単に画像を使用する際Gtk::Imageクラスを使用する.

#!/usr/bin/ruby1.8

require 'gtk2'

win = Gtk::Window.new()
img = Gtk::Image.new("./img.png")
win.add(img)
win.show_all()
Gtk.main()
この時,表示される画像のサイズはオリジナルのサイズで使用される.しかし画像があまりにも大きい場合など,そのような表示が好ましくないことがある.そのような場合,次のようなの解決策がある.
  • スクロールバーを付けて表示される範囲を制限
  • 画像のサイズを変更
前者の場合,Gtk::ScrolledWindowを使用して解決できる.後者の場合,今から紹介する方法がある.

画像サイズ変更

次のコードは画像のサイズを倍にしたものを表示するプログラムのものである.

#!/usr/bin/ruby1.8

require 'gtk2'

win = Gtk::Window.new()
img = Gtk::Image.new()
src_pxb = Gdk::Pixbuf.new("./img.png") dst_pxb = src_pxb.scale(src_pxb.width*2, src_pxb.height*2, Gdk::Pixbuf::INTERP_HYPER) img.set_pixbuf(dst_pxb)
win.add(img) win.show_all() Gtk.main()
画像のサイズを変更する場合,Gdk::Pixbufを使用する.GtkではなくGdkであることに注意.scale関数など,Pixbufに関する詳細はここを参考にされたし.

scaleの第1引数は新しい画像の横幅,第2引数は新しい画像の高さ,第3引数は画像サイズの変更に伴う補完の方法を表す.第3引数はGdkInterpType型で,標準ではGdk::Pixbuf::INTERP_BILINEARである.GdkInterpType型の詳細はここを参照されたし.

画像合成

GTK+では2.8以降cairoを用いてwidgetの描画を行なっている.Ruby GTK2ではこれを使用することで複数の画像を合成することができる. 以下にサンプルコードを示す.

bg.png

img.png
#!/usr/bin/ruby1.8
# -*- coding: utf-8 -*-

require 'gtk2'
require 'cairo'

win = Gtk::Window.new()
img = Gtk::Image.new()

bg = Gdk::Pixbuf.new("./bg.png")
pict0 = Gdk::Pixbuf.new("./img.png")
pict1 = Gdk::Pixbuf.new("./img.png")
pxm = Gdk::Pixmap.new(nil, bg.width, bg.height, 24) ctx = pxm.create_cairo_context # bg ctx.set_source_pixbuf(bg) ctx.paint # pict0 ctx.set_source_pixbuf(pict0, -20, -25) ctx.paint # pict1 ctx.set_source_pixbuf(pict1, 0, -5) ctx.paint pxb = Gdk::Pixbuf.from_drawable(Gdk::Colormap.system, pxm, 0, 0, bg.width, bg.height)
img.set_pixbuf(pxb) win.add(img) win.show_all() Gtk.main()
合成した画像(Gdk::Pixbuf)はsave関数を使用することで保存することもできる.
pxb.save("output.png", "png")

透明化

paint関数は引数で透明度を指定できる. 以下はそのサンプルである.

#!/usr/bin/ruby1.8
	
require 'gtk2'
require 'cairo'
	
win = Gtk::Window.new()
img = Gtk::Image.new()
bg = Gdk::Pixbuf.new("./bg.png")
pict = Gdk::Pixbuf.new("./img.png")
pxm = Gdk::Pixmap.new(nil, bg.width, bg.height, 24)
ctx = pxm.create_cairo_context
	
ctx.set_source_pixbuf(bg, 0, 0)
ctx.paint()
	
ctx.set_source_pixbuf(pict, 0, 0)
ctx.paint(0.7)
	
pxb = Gdk::Pixbuf.from_drawable(Gdk::Colormap.system, pxm, 0, 0, pict.width, pict.height)
img.set_pixbuf(pxb)
	
win.add(img)
win.show_all()
Gtk.main()
与える引数が1に近いほど透明度が低く,0に近いほど透明度が高い.

gimpで背景を透明に

ここでは既存の画像の背景を透明にする方法としてGIMPを使った例を紹介する.本稿で使用するGIMPのバージョンは2.8.2である.透明化の流れは次のような手順で行う.

  1. 透明化の設定
    1. [レイヤー(L)]
    2. [透明部分(A)]
    3. [アルファチャンネルを追加(H)]
  2. 背景の切り取り
背景の切り取りには選択を使用するGIMPの選択には矩形選択,楕円選択,自由選択,ファジー選択などがある.最初の3つの選択の機能は言葉から想像できるとして,ファジー選択についてだけ紹介する.ファジー選択ではクリックした位置のピクセル近似色領域を判断して選択領域が決定する.それゆえ,単色の背景などの場合これで選択して切り取りを行えば,比較的簡単に背景を透明にできるだろう.

こちらのサイトを参考に.

参考サイト

ステガノグラフィ

投稿日:
タグ:

目次

最近NUMB3RSを見返してて,s2-8『隠された写真』に出てきたステガノグラフィが気になったので,少し調べてみた.

デジタル・ステガノグラフィ(digital steganography)

デジタル・ステガノグラフィとはデータ隠蔽技術の一つで,あるデータに対して別のデータを埋め込むことである(以降,単にステガノグラフィとだけ呼ぶ).ステガノグラフィでは,隠すために使用された元のファイルをカバーファイル,作成されたデータをステゴファイルという.

例えば,ある画像ファイルにテキストファイルを埋め込むことで,テキストの存在を秘匿できる.この時,ステゴファイルは,以前の画像と比べて変化しているが,その変化を人間が検知することは困難である.以下に実際のステゴファイルとカバーファイル,隠蔽した画像ファイルを示す.

カバーファイル


隠蔽したファイル


ステゴファイル

このステゴファイルは,後述するsteghideを使用して作成した.これから隠蔽したファイルを抽出するためには,ステゴファイルがあるディレクトリで以下のコマンドを入力すれば良い.
user% steghide extract -sf ./img3.jpg
抽出に必要なパスワードは『hogehoge』である.コマンドのオプションについては,後述のsteghideで示す.

ステガノグラフィの利用方法には,秘密情報の存在秘匿やメディアデータへのメタ情報の追加,サブリミナル広告などがある.

コマンド

outguess

画像ファイルにデータファイルを隠蔽するプログラム.サポートするファイルは以下の通りである.

  • PPM
  • PNM
  • JPEG
outguessコマンドの使い方はこの通りである.
user% outguess 引数 [入力ファイル [出力ファイル]]

以下に使用例を示す.

埋め込み
埋め込みには-dオプションで指定する.
user% outguess -d secret.txt cover.jpg stego.jpg
パスワードを設定したい場合,-kオプションで指定する.
user% outguess -k hogehoge -d secret.txt cover.jpg stego.jpg
抽出
抽出には-rオプションで指定する.
user% outguess -k hogehoge -r stego.jpg secret.txt

steghide

画像や音楽ファイルにデータファイルを隠蔽するためのプログラム.サポートするファイルは以下の通りである.

  • 画像
    • JPEG
    • BMP
  • 音楽
    • WAV
    • AU
steghideコマンドの使い方はこの通りである.
user% steghide コマンド 引数
-cf
カバーファイル
-ef
組み込むファイル
-sf
ステゴファイル

steghideのコマンド例
embed
ファイルを組み込む.
user% steghide embed -cf cover.jpg -ef secret.jpg -sf stego.jpg
Enter passphrase: 
Re-Enter passphrase: 
embedding "secret.jpg" in "cover.jpg"... done
writing stego file "stego.jpg"... done
extract
隠蔽ファイルを抽出.
-xf
抽出ファイル名.指定しない場合,embedの-efで指定したファイル名になる.
user% steghide extract -xf secret.jpg -sf stego.jpg wrote extracted data to "secret.jpg".
info
ファイル情報を表示
user% steghide info img3.jpg "img3.jpg": format: jpeg capacity: 2.1 KB Try to get information about embedded data ? (y/n) y Enter passphrase: embedded file "img2.jpg": size: 1.7 KB encrypted: rijndael-128, cbc compressed: yes

LaTeXではがき

投稿日:
タグ:

喪中はがきを書くことになったので,自分が普段よく使う環境(Linux)ではがきを書く方法をいろいろ調べてみた.

プリントマジック
はがきデザインキット
Adobe AIRランタイム上で動くアプリ.Linux版のAdobe AIRのサポートは既に終了している.
KreetingKard
KDEのはがき作成アプリ.最後に更新されたのは2005年.
Libre Office
オープンソフトのOfficeソフト.はがき専用のソフトではないが理論的には作成可能なはず.
LaTeX
テキストベースの組版処理システム.テンプレート次第ではがきも作成可能.リンク先はそのテンプレート.
結論として,はがきはLaTexで書くことにした.GUIではないため,はがき作成ソフトのような完成図を見ながら細かい位置調整するようなことはできないが,スクリプトを書くことで住所を書いたCSVファイルから自動生成するようなことができる.


基本的にここではLaTexに関する詳しい説明は行わず,自分が印刷に至るまでに行ったことを記述している.

latexとはがきというキーワードでググると,はがき用のクラスファイルとサンプルコードが見つかった.

バックアップ
さっそくコンパイルして,印刷してみると,郵便番号の記入欄にうまく番号が入 らなかった(普通紙で印刷し,はがきと重ねてみた).また,ウラ面はギリギリは がきの枠に収まっていたものの,やはり中心が真ん中になっていなかった.その ため,変数をいろいろ調整して再度印刷してみた.しかしどういう訳か一部与えたパラメータが適応されなかった.

原因を追求しようとしても,LaTeXの知識は新たに何かを定義したりするほど詳しい訳ではないので,邪道にもクラスファイルのコードを見ながら,それらしい部分を探して直接コードを書き換えた.変更前と後の差分をdiffで比べた結果が次の通りである.

53c53
<
\def\@hagakiSizes{height=14.8cm,topmargin=.58cm,width=10cm,leftmargin=.58cm}%
---
>
\def\@hagakiSizes{height=14.8cm,topmargin=-.78cm,width=10cm,leftmargin=.58cm}%-_-.
71c71
< \advance\oddsidemargin by -1in
---
> \advance\oddsidemargin by -3.5cm %-_-.
変更箇所ははがきサイズらと横の余白サイズの指定部分の二点.それぞれ縦の位置と横の位置を調整している. 53行目のtopmarginについては,サンプルコード2行目のtopmarginの値を書き換えるだけで調整できた.
\documentclass[height=14.8cm,topmargin=-.78cm,width=10cm,leftmargin=.58cm]{hagaki}
しかしleftmarginを調整しても横の位置の調整はできず,サンプルコードのコメントを見てもどれを変えれば良いのかよく分からなかったため,クラスファイルの71行目を直接書き換えた.この修正を施すパッチと使い方については以下の通りである.
patch < my_hagaki.patch

omote

オモテ面の宛名については,サンプルコードを見れば大体想像がつくはずである.

\omote{岡山}{太郎 & 様\cr ハナ子 & 様}{700-0000}{岡山市マンナカ町一--一\par 高級マンション}
--はコンパイルすると,-の縦向きになる.また,サンプルでは住所に使われる数字が漢数字であるが,半角数字を記述した場合横向きに縦書きされる.

とりあえず見た目を取り繕いたいだけであれば,--はーで,漢数字以外で数字を使いたい場合は全角数字を使用すれば,多少改善できた.

ura

ウラ面については,文章をどの位置に入れるかによって値を変更する.サンプルコードのうち,ウラ面の記述に関する部分がこちらである.

\ura{%
  \noalign{\vskip .7cm}%
  %\UraParBox{高さ}{行間}{文章}
  \hss\UraParBox{11cm}{2zw}{\Large\bf 謹んで新春のお慶びを申し上げます}\hss\cr
  \noalign{\vskip .5cm}%
  \hskip 5cm\UraParBox{6.5cm}{1.5zw}{%
    新春を迎え皆様のご健康とご多幸を心よりお祈り致します。\par
    本年もよろしくお願い申し上げます。\par
    \vskip .5zw
    \hskip 1zh\small 平成十二年\hskip 1zh 元旦
  }%
  \hss\cr
  \noalign{\vss}\cr
  %\UraJusho{差し出し人の郵便番号・住所・姓名印字領域の高さ}
  \hskip 5cm\UraJusho{8cm}\hss\cr
  \noalign{\vskip .7cm}\cr
}
そして実際に使ったコードの一部がこちらである.
\ura{%
  \noalign{\vskip .7cm}%
  %\UraParBox{高さ}{行間}{文章}
  \hss\UraParBox{11cm}{2zw}{\Large\bf 喪中につき年頭のご挨拶はご遠慮申し上げます}\hss\cr
  \noalign{\vskip .5cm}%
  \hskip 2.5cm\UraParBox{10.5cm}{1.5zw}{%
    ここに本年度中に賜りましたご厚情を深謝いたします\par
    明年も変わらぬご変誼のほどお願い申し上げます\par
    なお時節柄一層のご自愛のほどお祈り申し上げます\par
    \vskip .5zw
    \hskip 1zh\small 平成二十五年十二月\hskip 1zh
  }%
  \hss\cr
  \noalign{\vss}\cr
  %\UraJusho{差し出し人の郵便番号・住所・姓名印字領域の高さ}
  \hskip 6cm\UraJusho{3cm}\hss\cr
  \noalign{\vskip .7cm}\cr
}

参考サイト

imwheel

投稿日:
タグ:

本稿ではX11環境で多ボタンマウスを使用するのに使用するプログラムの一種,imwheelについて紹介する.

はじめに

一般的なマウス

多くのマウスでは,右ボタンと左ボタン,中ボタンがある.コンピュータのよっては中ボタンがないものもあるが,左右のボタンを同時に押すことで,中ボタンとして扱えるものがある.また,中ボタンはホイールになっているものが多く,そうでないノートPCの中にはタッチパネルで同様の操作をできるものがある.

多ボタンマウス

多ボタンマウスと呼ばれるマウスは,一般的なマウスよりも多くのボタンを持つ.imwheelはそのようなボタンやホイールをサポートするためのプログラムである.imwheelは動作するウィンドウとマウスのボタン(またはホイールの方向),修飾キー,入力するキーの組み合わせを結びつけることで,多ボタンマウスを利用可能にする.

imwheelを使用する場合,次の2つの設定が必要である.

  • imwheel
  • X11
imwheelは,X11の設定が標準のままであっても,正しく動作するかもしれないので,とりあえず「imwheelの設定(imwheelrc)」を行なって,上手くいかない場合に確認しても良いだろう.

imwheelの設定(imwheelrc)

imwheelの各要素は,次の4つの組み合わせからなる.

  1. フォーカスされたウィンドウ名
  2. 修飾キー
  3. 押したマウスボタンやホイールの向き
  4. 入力するキー
以下に実際の例を紹介する.
".*firefox"
None,		Thumb1, Control_L|Tab

「フォーカスされたウィンドウ名」を除く3つの要素は,","で繋いで記述する.それゆえ,このテキストではそれぞれ次のように対応される.
  1. 文字列の末尾が"firefox"のウィンドウ
  2. 修飾キーなし
  3. Thumb1
  4. Control_L+Tab
Thumb1とはボタンに付けられた名前である.X11では,ボタンは番号で管理される.そしてimwheelでは,ボタン番号に次のような名前が付けられている.
4Up
5Down
6Left
7Right
8Thumb1
9Thumb2
10ExtBt7
11ExtBt8
このような情報はimwheelのマニュアルを参照されたい.

「入力するキー」については,同時に入力するキーは"|"で繋ぐことで記述する.

また,1つの「フォーカスされたウィンドウ名」に対して,複数の「修飾キー」と「押したマウスボタンやホイールの向き」,「入力するキー」の組み合わせを記述する.以下に実際に私が使用している設定ファイルの一部を紹介する.

".*firefox"
None,		Thumb1, Control_L|Tab
None,		Thumb2, Control_L|Shift_R|Tab

".*ファイル・ブラウザ"
None,	 	Left, Control_L|Page_Up
None, 		Right, Control_L|Page_Down
None,		Thumb1, Alt_L|Right
None,		Thumb2, Alt_L|Left

".*Dolphin"
None, 		Left, Control_L|comma
None,		Right, Control_L|period
None,		Thumb1, Alt_L|Right
None,		Thumb2, Alt_L|Left

デスクトップ上の操作

デスクトップ環境やウィンドウマネージャの背景のことをルートウィンドウという.ルートウィンドウの設定を記述したい場合,imwheel起動時に"-r"オプションが必要である.また,「フォーカスされたウィンドウ名」には,"\(root\)"を指定する. 以下にサンプルを示す.

"\(root\)"
None,		UP,	h
None,		DOWN,	l
None,		Left,	Super_R|h
None,		Right,	Super_R|l
None,		Thumb1,	h
None,		Thumb2,	l


X11の設定

多ボタンマウスを使用する場合,X11の次の2種類の設定ファイルを変更が必要かもしれない.

  • xorg.conf
  • xmodmap

xorg.conf

X11はxorg.confという設定ファイルで基礎的な設定を行う.X11で一般的なマウスにないボタンを認識する場合,設定ファイルに次のような記述が必要である.

Section "InputDevice"
        Identifier  "Mouse0"
        Driver      "mouse"
        Option      "Protocol" "auto"
        Option      "Device" "/dev/input/mice"
        Option      "ZAxisMapping" "4 5 6 7"
EndSection
私の環境ではこの設定ファイルで8と9も認識されたが,これは実行環境に依存すると思われる.

xmodmap(ボタン番号)

マウスのボタンは,X11で認識してもユーザが意図したボタンとして認識されない場合がある.特に一般的なマウスにないボタンの場合は,なおさら期待できないだろう.

そのような場合,ボタン番号の割り当て(再割り当て)が必要である.X11では,xmodmapを使って"xmodmap -e"コマンドの引数に次の文字列を与えるか,設定ファイル(.Xmodmap)に次の行追加すれば良い.私の環境では,次のように指定した.

pointer = 1 2 3 4 5 6 7

Androidアプリ開発環境の構築

投稿日:
修正日:
タグ:

本稿はEclipseという統合開発環境を使ったAndroid用アプリの開発環境の構築についてのメモである.

はじめに

以下に実行環境を示す.

実行環境
CPUIntel(R) Celeron(R) M CPU 520 @ 1.60GHz
メモリ1024MB
OSDebian Linux7.1.0
Eclipse3.8
ADT(Android Developer Tools)22.3.0-887826
ハードウェアNexus 7
Android4.2
カーネル3.1.10-g22b4fcd

Android SDK

入手と解凍

Android開発用のEclipseは下記のサイトから入手できる.

Eclipseはzipで圧縮されている.

入手したzipファイルは解凍することで利用する.解凍するコマンドには次のようなコマンドがある.

user% unzip adt-bundle-linux-x86-20130917.zip
解凍したディレクトリには,Eclipseが含まれている.このプログラムは次のように起動すれば良い.
user% ./adt-bundle-linux-x86-20130917/eclipse/eclipse

Eclipse

ここでEclipseについて改めて紹介する.Eclipseとは統合開発環境(IDE)の一種である.統合開発環境とは,コンパイラやエディタ,デバッガなどを1つの1つの環境から利用できるようにしたもののことである.Eclipseは標準でJavaをサポートするが,プラグインを組み込むことで様々なプログラミング言語をサポートする.EclipseでAndroidアプリをサポートしたい場合,ユーザはEclipseにAndroid Development Tools(ADT)というプラグインを取り込めば良い.ちなみにEclipse自体はJavaで記述されているため,クロスプラットフォーム(さまざまなOSで実行可能)なアプリケーションである.

Eclipseの起動

Eclipseを起動すると,次のように作業環境のパスの指定を求められる.

Eclipseで作成されたファイルはここをルートとして作られていく.

ADTのインストール

入手したEclipseはこのままでは必要なプラグイン(ADT)が入っていないことがある.これをインストールするには,次のように行う.

  1. [Help]を選択.
  2. [Install new software...]を選択.
  3. "Work with"に"http://dl-sll.google.com/android/eclipse/"を指定.
  4. "Developper Tools"をチェック.
  5. [Next >]を選択して次のページへ.
  6. インストールするものの一覧を確認して[Next >]を選択して次のページへ.
  7. [I accept the terms of the license agreements]をチェック.
  8. [Finish]を選択してインストールを行う.

以上でエディタやコンパイラなどの必要なツールがインストールされる.

Androidマシン・エミュレータ

ADTにはテスト用のAndroidエミュレータを生成する機能が付いている.エミュレータを使いたい場合,ユーザはメニューバーの[Window]から[Android Virtual Device Manager]を選択する.

エミュレータは,[New...]を選択し,"AVD Name"と"Device"を指定することで生成できる."AVD Name"はエミュレータの名前,"Device"はエミュレーションするAndroidマシンのことである.

Androidマシンの設定

Javaで作られたエミュレータはCPUへの負担が大きい.それゆえ私は基本的に実際のハードウェアで直にテスト実行している.なお私が使っているAndroidマシン(Nexus 7)は,標準で開発システムのOSから認識できたが,Eclipseから認識できず,以下の設定が必要だった.

  1. [開発者向けオプション]を表示.
    1. [設定]を起動.
    2. [ビルド番号]を数回タップ.
  2. USB接続時にデバッグモードに設定.
    1. [開発者向けオプション]をタップ
    2. [USBデバッグ]をチェック

以上まででAndroidアプリを開発するための準備は完了である.

Androidアプリ生成 -Hello, World!-

投稿日:
タグ:

目次

本稿はEclipseでAndroidアプリンをクロス開発し,Android端末で動かすまでの話のメモである.

Androidアプリ プロジェクトの立ち上げ

名前と各種APIのバージョンの設定

はじめに,アプリケーション名とプロジェクト名,パッケージ名,各種APIのバージョンを指定.

プロジェクト名は標準ではアプリケーション名と同じ名前を付けられる.各種APIは作成するアプリケーションがどのAndroid OSを対象としたものかを指定する.これはAndroid OS毎に対応するAPIが違うためである.

次に進むとこのような画面が表示される.

この画面は標準設定のまま次に進むことができる.

ここではプロジェクトで使用するファイルのパスやアクティビティを生成するか否かを指定する.アクティビティについての詳細は後述する.

アイコンの設定

次に進むと,アプリケーションのアイコンの設定を行う.アイコンには画像やテキスト,マークなどを使用でき,背景色やテキストやマークの色を指定できる.

アクティビティの設定

最後にアクティビティの設定を行う.アクティビティとは画面の土台のようなものである(正しくは画面だけを管理する訳ではない).アプリケーションは1つ以上のアクティビティによって構成される.Androidアプリは基本的にアクティビティに対してボタンや画像などのウィジェットを追加してくことで作成する.

アクティビティの設定には2段階ある.まず1段階目はテンプレートの選択である.

ADTでは,いくつかのアクティビティのテンプレートが用意されている.テンプレートを使用しない場合,アクティビティを生成しないようにチェックを外すか,選択したテンプレートから余計なコードを消せば良い.
Blank Activity右上のメニューとテキストだけが表示されているテンプレート
Fullscreen Activityメイン画面にタッチするとメニューが隠れて全画面表示になるテンプレート
Master/Detail Flow左側に見出しがあり,それを選択すると右側の画面が変化するテンプレート

次に進むと,アクティビティの名前やレイアウトの名前を指定する.

"Blank Activity"の場合,さらに細かく設定できる.選択肢には次のようなものがある.
  • None
  • Fixed Tabs + Swipe
  • Scrollable Tabs + Swipe
  • Dropdown
これらの詳細については本稿では言及しない.

全ての設定が完了したら,[Finish]をクリックすることでプロジェクトのひな形が生成される.


アプリケーションの実行

本稿では内容の変更については言及しない.生成されたアプリケーションは次のようにして実行する.

  1. [Run]を選択.
  2. [Run]を選択.
  3. "Android APlication"を選択.
  4. [OK]を選択.
  5. 実行するデバイスを選択.
  6. [OK]を選択.
この例では実行するデバイスが1つしかないが,エミュレータを生成すると更に選択肢が増える.

実行画面を以下に示す.

Androidアプリ アクティビティとビュー

投稿日:
タグ:

目次

本稿では,ADTに生成されたばかりで何も機能を持たないAndroidアプリのソースコードの説明を行う.

Androidアプリ・プロジェクトの構成要素

プロジェクトを立ち上げると,生成されるファイルやディレクトリについて紹介する.とりあえず最初に理解しなければならない要素はこれらである(私見である).

AndroidManifest.xmlアプリケーションの情報や基本設定を記述したファイル
srcソースコードを格納するディレクトリ
res文字列やファイルなどの資源などを格納するディレクトリ.
layoutアクティビティの画面を表すファイルを格納するディレクトリ
values整数や文字列などの値を保持するファイルを格納するディレクトリ
menuメニュー画面を表すファイルを格納するディレクトリ
drawable 解像度毎に画像を格納するディレクトリ郡.
drawable-ldpi
低解像度なディスプレイ用の画像を格納するディレクトリ
drawable-mdpi
中解像度なディスプレイ用の画像を格納するディレクトリ
drawable-hdpi
高解像度なディスプレイ用の画像を格納するディレクトリ
drawable-xdpi
より高解像度なディスプレイ用の画像を格納するディレクトリ
drawable-xxdpi
最大限高解像度なディスプレイ用の画像を格納するディレクトリ
rawアプリケーション内で使用するファイル(例:テキストファイルやバイナリファイル)を格納するディレクトリ
本稿ではここで挙げたリソースのうち,見た目を司るもの(ビュー)をいくつかを紹介する.そして,それらとプログラムの対応付けについて説明する.

前述で紹介したもの以外にもさまざまなファイルやディレクトリが生成される.以下にその一部を紹介する.

genADTが使用するディレクトリ
libsライブラリを格納するディレクトリ
bin実行ファイルを格納するディレクトリ
これらやここで紹介しなかったディレクトリやファイルは,プログラマがあまり意識する必要がないものや,簡単なアプリケーションでは使用しないものである.

ソースコード

Androidアプリがプログラムである以上,ソースコードがあるのは当然である.AndroidアプリはJava言語で記述される.一般的なJavaの言語処理系は,純粋なマシン語ではなく,Java仮想マシンで動くコードにコンパイルする.ただし,Androidアプリを動かすDalvik仮想マシンは純粋なJava VM向けのバイナリコードは実行できない.Dalvik仮想マシンに関する詳細はここでは言及しない.また,Javaの言語仕様についてもここでは言及しない.

Androidアプリケーションを実行すると,AndroidManifest.xmlで設定したアクティビティクラス(あるいはそれを継承したクラス)のインスタンスを生成し,onCreate関数が呼び出す.

package com.example.test1;

public class MainActivity extends android.app.Activity {
        @Override
        protected void onCreate(android.os.Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
		/*
		  追加コード
		*/
        }
}

アクティビティの状態遷移図を以下に示す.

リソース

リソースには3種類ある.

  • リソースをXMLで表現したもの
  • 格納されたファイル自体がリソースであるもの
  • 2つを兼ね揃えたもの
valuesは1つ目で,drawableやrawは2つ目,layoutやmenuは3つ目に属する.全てのリソースには名前があり,それらはプログラム内で整数型のデータの変数名として扱われる.これらの変数はADTによって自動生成され,それぞれ一意に定まる値が割り当てられる.そのため,新たにリソースを使用する場合,Eclipseの機能で追加するべきである(そうでなければ整合性がとれなくなるかもしれない).また,ファイル自体がリソースの場合,ファイル名から拡張子を除いたものがリソース名,つまり変数名になる.

layout

アクティビティの画面を形成するためのリソースをレイアウトという.レイアウトを使用せずにアクティビティを生成することは可能であるが,特別な意図がなければレイアウトを使った方が簡潔である.

アクティビティとレイアウトを対応付けるには,setContentView関数を使用する.初期画面のレイアウトをアクティビティに対応づけるには,onCreate内で呼び出せば良い.

package com.example.test1;

public class MainActivity extends android.app.Activity {
        @Override
        protected void onCreate(android.os.Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
        }
}
レイアウトの変数はR.layoutに属する.なお変数の名前はレイアウトファイルのファイル名から拡張子を除いたものである.

ADTプラグインを含むEclipseでは,レイアウトの生成をテキストベースだけでなく,GUIで行うことができる.レイアウト生成ツールの画面は,次の4種類からなる.

  • アクティビティのレイアウト
  • アクティビティ上のビューの階層図
  • パレット ビューのテンプレート一覧
  • 各ビューのプロパティ(名前や色,サイズなどの情報)
用意されたビューのテンプレートはさまざまで,ビューの種類以上に多くのテンプレート用意されている(例えば文字付きボタンも文字の大きさでいくつか用意されている).また,ビューのプロパティのほとんどはプログラム内からも変更できる.

レイアウトはビュー(View)という見た目を司る要素の1つである.次に紹介するメニューもまたこの1つである.

menu

Androidアプリの右上にメニューを設置する場合,アクティビティの次の関数を定義すれば良い.

        @Override
        public boolean onCreateOptionsMenu(android.view.Menu menu) {
                getMenuInflater().inflate(R.menu.main, menu);
                return true;
        }
メニューはres/menu/内のXMLファイルで定義される.メニューの変数はR.menuで定義される.そして,getMenuInflater().inflate()でプログラムとメニューを対応づけることができる.

menuの作成は単純である.menuはいくつかの項目と項目のIDと表示される文字列からなる.新たに項目を追加したい場合,itemを追加しそのidと値を設定するだけで良い.

Androidアプリ ウィジェットとイベント

投稿日:
修正日:
タグ:

目次

ウィジェット

ウィジェットとは,簡単にいえばGUIを構成する要素のことである.Androidでは,ホーム画面から直接操作をできるものをウィジェットと呼ぶが,ここでいうウィジェットはそのウィジェットとは別のものである.

ウィジェットの追加はレイアウトのエディット画面で行える.そして,ウィジェットのidやサイズなどの情報もまたそこで編集できる.

以前紹介したTest1プロジェクトのレイアウトにボタンを追加した画面は次の通りである.

id役割
textandroid.widget.TextView文字列を表示するウィジェット
buttonandroid.widget.Buttonただのボタンのウィジェット

イベント

次のソースコードは,ボタンをクリックしたら(押してそのまま離したら)表示されている文字列"Hello, World!"が"hoge"に変更されるプログラムのものである.

package com.example.test1;

public class MainActivity extends android.app.Activity {
        private android.widget.TextView txt;
        private android.widget.Button btn;

        class MyClickHandler implements android.view.View.OnClickListener{
		@Override
		public void onClick(android.view.View v){
	        	txt.setText("hoge");
		}
	}

        @Override
        protected void onCreate(android.os.Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
		
                MyClickHandler my_chandler = new MyClickHandler();
                txt = (android.widget.TextView)findViewById(R.id.text);
                btn = (android.widget.Button)findViewById(R.id.button);
		btn.setOnClickListener( my_chandler );
        }
}
ウィジェットはレイアウトで定義できる.そして,これをプログラムで利用するにはfindViewById()を使用する.ウィジェットの変数はR.idに属する.

ウィジェットが押されたり触られたりすることをイベントという.イベントに応じて何らかの処理を行うには,イベントリスナーと呼ばれるものをセットすれば良い.前述のサンプルでは,ボタンウィジェットにクリックリスナー(あるいはそれを継承したクラスのインスタンス)を対応付けている.

ウィジェットのプロパティやイベントに対応する関数をセットするには,setから始まる名前の関数を使用する.また,それを取得する関数の多くはgetから始まる名前の関数である.これらの関数の詳細については,公式のAndroid APIのリファレンスマニュアルやEclipseの補完機能の情報を参照されたい.

もう1つイベントハンドラをセットする例を紹介する.

        class SettingMenuItem implements android.view.MenuItem.OnMenuItemClickListener {
                @Override
                public boolean onMenuItemClick(android.view.MenuItem item)
                {
        		txt.setText("setting");
		}
        }

        @Override
        public boolean onCreateOptionsMenu(android.view.Menu menu) {
                getMenuInflater().inflate(R.menu.main, menu);
		android.view.MenuItem item = menu.getItem(0); // 1番目の項目
                SettingMenuItem setting = new SettingMenuItem();
                item.setOnMenuItemClickListener(setting);
                return true;
        }

values,drawable

前述のサンプルでは"hoge"という文字列リテラルを記述しているが,Androidアプリは多言語に対応するための仕組みがある.Androidでは数や文字列をres/values内のXMLファイルで定義することで,それを利用できる.そして,対応する言語に応じてvaluesファイルを使い分けることで,複数の言語に対応したプログラムになる.また,valuesで定義された数や文字列は,他のXMLファイルで使用することができる.これにより,コーディングと同じでファイルの修正が容易になる.

XMLファイルでvaluesの値を使用したい場合,次のようにして記述すれば良い.

android:label="@string/app_name"
また,プログラム内で利用したい場合,次のようにする.
android.content.res.Resources r = MainActivity.this.getResources();
String str = r.getString(R.string.hoge);
txt.setText(str);
文字列に限らず整数や小数,配列,画像(drawable)などについても基本的には同じ考え方である.

Androidアプリ raw

投稿日:
修正日:
タグ:

raw

Androidアプリのリソースには,テキストやバイナリなどの生のデータがある.それらのデータはresのrawというディレクトリに格納される.rawへのファイルの追加はEclipseの機能を用いてコピーしたり生成したりすることが望ましい.これはEclipseがリソース情報を管理していることに起因する.

        String[] lines;
        try {
                int i = 0, cnt = 0;
		android.content.res.Resources resource = getResources();
                java.io.InputStream is = resource.openRawResource(R.raw.text);
                while (is.read()!=-1) cnt++;
                byte [] b = new byte[cnt+1];
                is.reset();
                is.read(b);
                String str = new String(b);
                lines = str.split(";");
                is.close();
        }catch(java.io.IOException e){
                
        }

Androidアプリ タイマとハンドラ

投稿日:
タグ:

本稿はAndroidアプリ開発でjava.util.Timerを使った際のメモである.

Android APIとタイマ

Javaではタイマ(java.util.Timer)を使って一定時間毎に特定の処理を行うことができる.しかしタイマ内で,直接アクティビティ内のウィジェットの情報を書き換えることはできない(私の環境では何故かProgressBarは上手くいったが…).このようなコードはコンパイル可能だが,タイマ内の処理でウィジェットの値を書き換えようとした瞬間,アプリがエラー終了する.これを解決するには,android.os.Handlerのインスタンスを使用すれば良い.これを含め,タイマには次の4つのクラスを使用する.

  • java.util.Timer
  • java.util.TimerTask
  • android.os.Handler
  • java.lang.Runnable

java.util.Timer

はじめにjava.util.Timerを使ったサンプルを紹介する.

class Hoge {
	private java.util.Timer timer;

	private class MyTimerTask implements java.util.TimerTask{
 	 	int time_cnt = 0;
                @Override
                public void run()
                {
                        time_cnt++;
                        System.out.println(time_cnt);
			if (time_cnt==10){
			        timer.cancel();
			}
                }
        }
        Hoge()
        {
                timer = new java.util.Timer(true);
                MyTimerTask ttask = new MyTimerTask();
                timer.schedule(ttask, 60000, 60000);
	}
}
java.util.Timerのインスタンスはschedule関数を使用して,ある処理を定期的に実行できる.scheduleの引数はそれぞれ次の通りである.
schedule(java.util.TimerTaskのインスタンス, 最初に実行されるまでの時間, どれぐらい間を置いて実行するか)
時間はミリ秒単位で指定する.

java.util.Timerのインスタンスはcancelを呼び出すことで終了する.

timer.cancel();
終了したタイマはもう一度scheudleを呼び出しても起動できず,再度処理を行うには新たにインスタンスを生成しなければならない.

java.util.Timerとandroid.os.Handler.

以下に実際のサンプルコードを示す.

public class MainActivity extends android.app.Activity {
	private android.widget.TextView text;
	private java.util.Timer timer;
	private final android.os.Handler HANDLER = new android.os.Handler();
	
	private class MyRunnable extends java.lang.Runnable {
	        private Integer time_cnt = 0;
                @Override
                public void run()
                {
			time_cnt++;
        	        text.setText(time_cnt.toString());
			if (time_cnt==10){
        			timer.cancel();
				finish(); // アクティビティの終了
			}
                }		
        }

	private class MyTimerTask extends java.util.TimerTask{
 	 	MyRunnable RUNNABLE = new MyRunnable();
                @Override
                public void run()
                {
                        HANDLER.post(RUNNABLE);
                }
        }
        protected void onCreate(android.os.Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);

                timer = new java.util.Timer(true);
                MyTimerTask ttask = new MyTimerTask();
                timer.schedule(ttask, 60000, 60000);
                }		
        }
このようにMyTimerTaskクラスのrun関数で,android.os.handlerインスタンスのpost関数を呼び出し,引数にjava.lang.Runnable(あるいはそれを継承した)クラスのインスタンスを与える.これにより,定期的にRunnable(あるいはそれを継承した)クラスのインスタンスのrun()が呼び出される.

Androidアプリ サブアクティビティ

投稿日:
タグ:

目次

ADTでプロジェクトを新たに作った時,デフォルトで1つのアクティビティが生成される.しかし,本格的なプログラムではいくつかのアクティビティを使用する.例えばスタート画面や設定画面,プレイ画面などがある.本稿では,複数のアクティビティを作り方のメモである.

アクティビティを追加するには次の3つのことを行わなければならない.

3つ目についてはなくても可能である.また,2つ目についてはファイルを分ける必要はない.しかし,本稿ではアクティビティ毎にファイルを分ける方針を取る.

アクティビティの追加

レイアウトファイルの生成

レイアウトの生成は次のような手順で行う.

  1. [File]を選択.
  2. [New]を選択.
  3. [Android XML File]を選択
  4. レイアウト名を指定.
  5. レイアウトの種類を指定.
  6. [Next >]を選択.
  7. [Finish]を選択.
本稿では,レイアウトの種類は"RelativeLayout"を選択する.また,レイアウト名にはactivity_sub.xmlと付ける.

レイアウトの編集については,ここでは言及しない.

新たなアクティビティの生成

クラスの追加は次のようにして行うことができる.

  1. [File]を選択.
  2. [New]を選択.
  3. [Class]を選択.
  4. クラス名を指定.
  5. スーパクラスをandroid.app.Activityに指定.
本稿ではクラス名にSubActivityという名前を使用する.

出来上がったソースコードには,次のようなコードを追加する必要がある.

public class SubActivity extends android.app.Activity {
        @Override
        protected void onCreate(android.os.Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_sub)
	}
}
setContentView()で指定するidには,新たに生成したレイアウトのものを使用する.

AndroidManifest.xmlへのアクティビティの登録

AndroidManifest.xmlへの登録もレイアウトと同様GUIで編集できる.手順は次の通りである.

  1. [Application]タブを選択.
  2. "Application Nodes"の[Add]を選択.
  3. "Activity"を選んで[OK]を選択.
  4. "Attributes for Activity"の"Name"を指定.
もちろんXMLファイルを直接書き換えても良い.なお,AndroidManifest.xmlの設定を忘れてもコンパイルは支障がなく行えるが,実行中にエラー終了する.


アクティビティの起動

アクティビティから別のアクティビティを実行するには,android.content.Intentを使用する.起動するアクティビティにデータを与えない場合,startActivity()を使用すれば良い.

android.content.Intent intent = new android.content.Intent(MainActivity.this, SubActivity.class);
startActivity(intent);

また,アクティビティの終了にはfinish()を使用する.

SubActivity.this.finish();
呼び出し先のアクティビティが終了すると,呼び出し元のアクティビティに制御が戻る.

データの受け渡し
呼び出し元アクティビティのデータ渡し

呼び出し先のアクティビティにデータを渡す時,startActivity()の代わりにstartActivityForResult()を使用する.そして,データを渡すにはputExtra()を使用する.putExtraは第1引数で指定した名前を鍵として,データを呼び出し先のアクティビティに渡す.また,第2引数はさまざまな型のものがある.

android.content.Intent intent = new android.content.Intent(MainActivity.this, SubActivity.class);
int i = 10;
intent.putExtra("integer", i);
startActivityForResult(intent, 0x01);

呼び出し先アクティビティのデータ受け取り

データを受け取るにはgetIntent()でandroid.content.Intentのデータを初期化する.そして,getExtra系の関数でデータを受け取ることができる.getExtra系の関数は"get + 型名 + Extra"の名前からなる.

android.content.Intent intent = getIntent();
int i = intent.getIntExtra("integer");
i *= 2;

呼び出し先アクティビティのデータ返却(渡し)

結果を呼び出し元返すには,setResult()で結果のタイプとandroid.app.Activityのインスタンスを指定する.

intent.putExtra("integer", i);
setResult(android.app.Activity.RESULT_OK, intent);
finish();

呼び出し元アクティビティのデータ受け取り

アクティビティを制御が戻った際に,onActivityResult()が呼び出される.onActivityResult()は,第1引数で呼び出し時にstartActivityForResult()で指定したアクティビティの識別コード,第2引数で結果のタイプ(呼び出し先アクティビティがsetResult()で指定した値),第3引数でandroid.content.Intentを受け取ることができる.

        @Override
        protected void onActivityResult(int request_code, int
	result_code, android.content.Intent sub)
        {
                int i;
                if (request_code==0x01 && result_code==MainActivity.this.RESULT_OK){
			i = sub.getIntExtra("integer");
                }
        }
第1引数と第2引数の値を判別した後は,呼び出し先アクティビティのデータ受け取り同様getExtra系の関数を使用する.

別のアプリの呼び出し

android.content.Intentはユーザが作成したアクティビティ以外にも別のアプリのアクティビティなどを起動できる.例えば別のアプリで画像を選択させたいような場合,次のようにアクティビティを起動する.

android.content.Intent intent = new android.content.Intent(android.content.Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, 0x02);

このサンプルでは,intent初期化にandroid.content.Intent.ACTION_GET_CONTENTを与えているが,他にもいろいろなタイプがある.ただし,本稿ではこれ以上言及しない.

選択した画像のパスを取得する例を以下に示す.

        private String path;

	@Override
	protected void onActivityResult(int request_code, int result_code, android.content.Intent sub)
	{
		super.onActivityResult(request_code, result_code, sub);
		if (request_code==0x02 & result_code==android.app.Activity.RESULT_OK){
			android.content.ContentResolver cr = MainActivity.this.getContentResolver();
			android.database.Cursor c = cr.query(sub.getData(), new String[]{MediaStore.Images.Media.DATA}, null, null, null);
			if (c!=null){
				c.moveToFirst();
				path = c.getString(0);
				c.close();
			}

		}
	}
単純にパスを取得するにはgetData()とgetPathを使うだけで良い.
path = data.getData().getPath();
しかし,これによって取得できるのはURIと呼ばれるパスである.一部の関数はそれだと都合が悪いので,本稿ではそれをディレクトリ階層のパスに変更する例を紹介した.

Androidアプリ TextViewでさまざまな表現

投稿日:
タグ:

android.widget.TextViewはandroid.text.Html.fromHtmlを使うことで,表示する文字列にさまざまな視覚的修飾を施すことができる.android.text.Html.fromHtmlは次のような返り値を引数を持つ.

CharSequence android.text.Html.fromHtml(String)

本来HTMLのタグは視覚的な効果はない.しかしandroid.text.Html.fromHtmlは,いくつかのWEBブラウザのようにタグを視覚的に修飾するような機能を持つ.ただし,利用可能なタグや属性はいくつかに限定される.

タグ視覚効果
<br>改行
<p>段落
<div>
<b>太字
<em>
<i>イタリック体
<cite>
<dfn>
<strong>---------
<big>大文字
<small>小文字
<font>colorやfaceの属性を使用して色やフォントの種類を指定可能.
<blockquote>引用文
<tt>等幅フォント
<a>hrefでリンクにできる.
<u>下線
<sup>上付き文字
<sub>下付き文字
<h1>見出し
<h2>
<h3>
<h4>
<h5>
<h6>
<img>画像(ただしそのままでは使用できない)

以下に利用例を示す.

android.widget.TextView text = (android.widget.TextView)findViewById(R.id.text);
String text = "次の下線部は何と読むか?<br><u>画面</u>を見る.";
text.setText( android.text.Html.fromHtml(text) );

コンピュータ・ネットワーク(1)

投稿日:
タグ:

目次

本稿はコンピュータネットワークの基礎についてメモである。現代でネットワークといった場合、暗黙的にコンピュータネットワークを指すことが多い。ネットワークとはいくつかのノード(例えばマシン)間が接続されてできる通信網のことである。

あるマシン間で行う通信は次のような階層モデルで表される。

アプリケーション層
トランスポート層
ネットワーク層
データリンク層
物理層
このモデルを5階層TCP/IPモデルという。このモデルがネットワークの階層を表す上で最適なモデルであるかはいろいろ議論はあるだろうが、本稿では基本的にこのモデルを基に説明する。

通信形態

通信には次の4つの形態がある。

ユニキャスト
1対1の通信のこと。
マルチキャスト
1対多の通信のこと。
エニーキャスト
ある集合に属する1つのものとの通信のこと。
ブロードキャスト
同じネットワーク上の全てのものとの通信のこと(1対不特定多数)。
本稿で扱う通信もまたこれらのいずれかに属する。

ネットワーク階層モデル

ネットワークを表すための階層モデルには、次のようなモデルがある。

アプリケーション層アプリケーション層アプリケーション層
プレゼンテーション層
セッション層
トランスポート層トランスポート層トランスポート層
ネットワーク層ネットワーク層ネットワーク層
物理層データリンク層データリンク層
物理層物理層
4階層TCP/IPモデル5階層TCP/IPモデルOSI参照モデル
どの階層が最適なモデルであるかは、定かではない。例えば、OSI参照モデルのアプリケーション層とプレゼンテーション層、セッション層は、5階層TCP/IPモデルのアプリケーション層に相当するが、それを分けるぐらいならば物理層やデータリンク層を細分化すべきという主張もある。本稿では、基本的に5階層TCP/IPモデルを使用し、階層についてこれ以上言及しない。

5階層TCP/IPモデル

5階層TCP/IPモデルの最上位のアプリケーション層は、それぞれユーザに対して、メールやWEBページの配信などさまざまな機能を提供する。そして、それから下位に行くほどユーザが認知しないベーシックな通信を扱う。

最上位のアプリケーション層を除く各層は、基本的にそれぞれ次の3つの要素からなる。

サービス
上のレイヤのエンティティ(通信するもの)に提供する機能。
インタフェース
サービスを受けるためのアクセス方法。
プロトコル
通信規約のこと。送信側と受信側が同じプロトコルを使用することで通信が成り立つ。例えばアプリケーション層でWEBページの配信を行う場合、送信者と受信者が共にHTTPに従う。

各層の要素についての説明は次の通りである。

レイヤ名サービスインタフェース代表的なプロトコルや技術
データ単位アドレス
アプリケーション層 HTTP, SMTP, POP3
トランスポート層 プロセスがサービスを行ったりそれを受けたりするための通信を可能にする。そしてそのためにフロー制御や輻輳制御、誤り訂正などを行う。 ソケット ポート番号 TCP, UDP
ネットワーク層 物理的なネットワークを跨いで全てのノード間の通信を可能にする。 パケット 論理アドレス IP, ICMP
データリンク層
  • 物理的に接続されたいくつかのノードの中(物理的なネットワーク)で、ユニキャストを可能にする。
  • 通信品質を向上のために、伝送エラーの検出やフロー制御などを行う。
フレーム 物理アドレス ARP, PPP, IEEE802.11
物理層 有線や無線、衛生などを使って物理的に通信を可能にする。 ビット なし(ブロードキャスト) 光ケーブル, ツイストペアケーブル, 10Base-T
基本的にアプリケーション層を除く4つのレイヤは、更に2つずつに分けられ、それぞれ異なるネットワークを構築する。物理層とデータリンク層は物理的なネットワーク、ネットワーク層とトランスポート層は論理的なネットワークを形成する。そして、次の表のようにそれぞれの組みの低い方のレイヤはノードの接続を、高い方のレイヤはその制御を司っている。
物理ネットワーク(MAC)仮想ネットワーク(IP)
接続物理層ネットワーク層
制御データリンク層トランスポート層

標準化

ネットワークを構成する技術や仕様は、いくつかの団体によって標準化が勧められている。

IETF(Internet Engineering Task Force)
インターネット技術の国際標準化をすすめる団体。インターネットで利用される多くのプロトコルはRFC(Request For Comments)というもので技術仕様が公開されている。
例)
RFC 959アプリケーション層のFTPの仕様
RFC 2616アプリケーション層のHTTP/1.1の仕様
IEEE(the Institute of Electrical and Electionics Engineers, inc)
アメリカに本部を持つ電気・電子技術学会。読み方はアイトリプルイー。通信工学・電子工学・情報工学などの分野を対象とし、標準化の活動を行なっている。
例)
IEEE 754浮動小数点の表記や演算に関する標準規格。
IEEE 802.3有線LANに関する標準規格。
IEEE 802.11無線LANに関する標準規格
ICANN(Internet Corporation for Assigned Names and Numbers)
IANA(Internet Assigned Number Authority)
インターネット上で利用されるアドレス資源の標準化や割り当てを行う団体。ICANNはIANAの後継にあたる。アドレス資源には、ポート番号やIPアドレス、ドメイン名などがある。

コンピュータ・ネットワーク(2) -物理層-

投稿日:
タグ:

目次

物理層は5階層TCP/IPモデルの下から1番下の層である.

アプリケーション層
トランスポート層
ネットワーク層
データリンク層
物理層

--編集中--

  • 線路
  • 無線
  • 衛生

双方向通信

全二重(full duplex)
半二重(half duplex)

ネットワーク・トポロジー

ネットワークとはいくつかのノード間が接続されてできる通信網のことである.ネットワークにはさまざまな幾何学的な形(トポロジー)がある.以下にいくつかの例を紹介する.以下の図では,○がノード,線が接続,□が接続を繋ぐ集線装置(例:ハブ)を表す.

デイジーチェイン型
ノードを数珠繋ぎで繋いだトポロジー.最もコストが低いが,冗長性も低く耐障害性が最も低い.いずれか1つのノードか経路が機能しなくなると,通信できなくなる可能性がある.
バス型
バスと呼ばれる伝送路を通じていくつものノードが接続されたトポロジー.他のノードによる通信の影響はないが,いずれか1つの経路が機能しなくなると,通信できなくなる可能性がある.ただし,バスのトポロジーによっては耐障害性も向上する.ただし,その分コストも上がる.
リング型
あるノードから別のノードへの経路が必ず2つあるトポロジー.いずれか1つの経路やそれに関わるノードが機能しなくても,通信可能である.
スター型
いずれかのノードや集線装置までの経路で問題が発生しても,通信可能である.ただし集線装置が停止した場合,全ての通信が不能になる.
ツリー型
巡回路(ループ)のないトポロジーのこと.デイジーチェイン型もまたツリー型の一種である.
フルコネクト型
耐障害性が最も高く,全てのノードに対して最短な経路があるトポロジー.あるノードから別のノードへの経路がいくつもあるので,いくつかのノードや経路が使えなくなっても通信が可能である.ただし費用も最も高い.

ネットワーク・トポロジーには次の2種類のトポロジーがある.

  • 物理的トポロジー
  • 論理的トポロジー
物理的トポロジーと論理的トポロジーのトポロジーが同じであるとは限らない.例えば,IEEE802.3の10BASE-Tの物理的トポロジーはスター型だが,論理的トポロジーはバス型になっている.

コンピュータ・ネットワーク(3) -データリンク層-

投稿日:
タグ:

目次

データリンク層は5階層TCP/IPモデルの下から2番目の層である.

アプリケーション層
トランスポート層
ネットワーク層
データリンク層
物理層

以下にデータリンク層が持つ3つの要素を表で示す.

サービス
  • 物理的に接続されたいくつかのノードの中(物理的なネットワーク)で、あるノードから指定したノードへの通信を可能にする。
  • 通信品質を向上のためにさまざまな処理を行う
インタフェース転送単位フレーム
アドレス物理(ハードウェア)アドレス(MACアドレス)
代表的なプロトコル
  • ARP
  • PPP
  • MAC(IEEE802やイーサネットなど)
多くのユーザはこのレイヤを意識することはないだろうが、スイッチングハブと呼ばれるハードウェアは,この物理層からデータリンク層までを扱うものである.

副層

データリンク層は更にいくつかの副層に分けて考える場合がある。例えば、物理的な制御と論理的な制御に分けることができる。

LLCLogical Link Control論理リンク制御副層 高品質な通信を可能にする。
  • 伝送エラーの検出
  • フロー制御
  • 送信の際の多重化と受信の際の非多重化
MACMedia Access Controlメディアアクセス制御副層 物理的に接続されたいくつかのノードの中(物理的なネットワーク)で、ユニキャストを可能にする。
  • MACアドレス
  • 競合制御(衝突対策)
物理層が1対1接続で、かつ全二重通信の場合、メディアアクセス制御副層は必要ない。

通信

物理層では、いくつかのノードを接続し、ネットワークを構築する。物理層の通信形態は、接続するノード数に関係なくブロードキャストである。データリンク層では、そのネットワーク上のあるノードに、宛先物理(ハードウェア)アドレスが自分のものであるフレームが届いた場合、それを受け取ることで、ユニキャストを実現する。

ARP

TCP/IPの通信では,各マシンはIPアドレスという論理的なアドレスを使って通信を行うが,最終的にはMACアドレスによってマシンを特定する.IPアドレスからMACアドレスを取得するには,ARP(Address Resolution Protocol)を利用する.ARPでは,物理的なネットワークでブロードキャストによって調べたいIPアドレスを伝える.受信したノードは,そのIPアドレスが自身のものである時,MACアドレスをその送信元ノードに返す.

フレーム

フレームとはデータリンク層で扱う転送単位で,ビット群のことである.ビット群は,さらにいくつかのフィールドに分類され,フィールドはそれぞれ必要な情報を保持する.フレームの構成にはいくつかの規定があり,以下にいくつか紹介する.なお,ここのフィールドの1セルは,ヘッダとデータを除いて1バイトを表しているで使用される.

DIX
プリアンブル MACヘッダ データ FCS
46〜1500bytes
世界中のLAN(Local Area Network)で最も使用されるイーサネットという規格では,DIXが主流に使用される.DIX規格はDecとIntel,Xeroxが共同開発した規格である.DIX規格の場合,MACヘッダのタイプ・長さのフィールドはタイプを表す.
IEEE802.3
プリアンブル SFD MACヘッダ データ FCS
46〜1500bytes
以下に各フィールドの用途を示す.
プリアンブル
SFD(Start Frame Delimiter)
同期を取り,フレームの開始を合図するためのフィールド.DIXでは8バイト(64ビット)で101010…と繰り返し,最後の2ビットを11,IEEE802.3では7バイトで101010…と繰り返し,最後の1バイト(8ビット)が10101011となる.
FCS(Frame Check Sequence)
エラー検出のためのフィールド.このフィールドにより,MACヘッダとデータが正しいかを判定する.エラー検出にはパリティや加算による方法など様々ある.イーサネットの場合,エラー検出の方法には巡回冗長検査(CRC:Cyclic Redundancy Check)方式を使用する.
MACヘッダ
宛先MACアドレス 送信元MACアドレス タイプ・長さ
宛先MACアドレス
送信元MACアドレス
MAC(Media Access Control)アドレスとは必ず一意に定まる48ビットの値のこと.物理ネットワークではこの値によってノードを識別する.UNIX系のifconfigやWindowsのipconfigでは,1バイト(8ビット)毎に区切って,"00:03:47:11:22:33"というように16進数で表記される.各ハードウェアベンダーは,利用可能なMACアドレスの番号の範囲を持つ.
タイプ
長さ
このフィールドが46から1500であればIEEE802.3規格の長さを表しており,1501以上であればDIX規格のタイプを表している.

衝突(コリジョン)

通信で使用される経路が送信でも受信でも使用される場合,いくつかのノードが同時に通信しようとして送信するデータが破損することがある.これを衝突(コリジョン)と呼ぶ.そして、いくつかのノードが同時に送信して衝突が発生する範囲のことをコリジョンドメインという。データリンク層では,これに備えるためのいくつかのプロトコルがある.

CSMA/CD(Carrier Sense Multiple Access/Collision Detection)プロトコル
CSMA/CD方式で衝突が発生した場合,ノードはランダムな間を置いて再度通信を行う.IE802.3ではCSMA/CDが採用されている.
CSMA/CA(Carrier Sense Multiple Access/Collision Avoidance)プロトコル
通信を開始する前に受信を試みることで、他に通信を行なっているノードがあるかを調べる。いずれかのノードが通信している場合、通信が終了してからランダムな間を置いて通信を行う。無線LAN規格のIEEE802.11aやIEEE802.11b、IEEE802.11gで利用されている。
FDDI(Fiber Distributed Data Interface)プロトコル
トークンリングプロトコルを拡張したプロトコル.トークンリングプロトコルとは衝突を回避するためのプロトコルで,ノードはトークンと呼ばれるものを保有する時のみ通信が許可される.トークンは1つの物理ネットワーク内のノードに1つだけ存在し,それをローテーションで渡していく.トークンリングプロトコルでは,衝突の発生を完全に防ぐことができるが,経路で問題が発生した場合,トークンが移動しなくなって全てのノードの通信ができなくなるという問題がある.FDDIプロトコルでは,トークンの受け渡しの経路を二重化することでこの問題を防ぐことができる.

リピータハブを用いたような物理層での接続の場合、それらの範囲はコリジョンドメインである。しかし、データリンク層で接続するスイッチングハブや、ネットワーク層で接続するルータを使用した場合、コリジョンドメインを分割できる。リピータハブはただ接続範囲を拡大するためのものだが、スイッチングハブはどこのポートに何のノード(MACアドレス)が接続されているか記憶することで,コリジョンドメインを分割する。これにより、衝突の頻度を減らすことができる。

データリンク層で接続された範囲のことをブロードキャストドメインという。ブロードキャストドメインは、フレームのブロードキャストが届く範囲のことである。本稿で述べる物理ネットワークとはこの範囲のことを指す。


関連コマンド

arp
システムが使用するARPのキャッシュを制御する.例えば次のようにして使用することで,キャッシュされたARPの情報を見ることができる.
user% arp 192.168.1.1
もし調べたいIPアドレスがあれば,pingのようなコマンドでそのアドレスへアクセスを行なってから,arpコマンドを使えば良い.以下にいくつかのオプションを紹介する.
-s
エントリの追加(IPアドレスとMACアドレスを対応付ける). IPアドレス"172.168.1.1"にMACアドレス"01:23:45:67:89:ab"を対応付けるには以下のように行う。
user% arp -s 172.168.1.1 01:23:45:67:89:ab -i wlan0
-d
エントリの削除
user% arp -d 172.168.1.1 -i wlan0
-a
ARPテーブルを出力.
-n
ホストやポート,ユーザーの名前を解決せずに生のまま出力.
-i
インタフェースを指定.
-v
詳細な情報を出力.
tcpdump
パケットスキャニングツール.-eオプションを指定することで,フレームのヘッダ情報を表示できる.
user% tcpdump -e -i eth0


補足

CRC(Cyclic Redundancy Check)

イーサネットの通信やZIP,PNGなどで利用されるエラー検査のアルゴリズムである.CRC方式では以下の方法で計算された値を比較することで,エラーを演出する.

  1. 入力と除数のビット列の左端を合わせる.
  2. 除数の左端のビットに対応する入力のビットを見て,1ならば,XOR演算を行う(除数の足りない桁は全て0とする).
  3. 解を入力とする.
  4. 除数の桁を右に1つずらす.
  5. 除数の右端の桁が入力の右端の桁より右にいかない間,1から4を繰り返す.
以下に例を示す.
10010010  入力
1011      除数
00100010
  1011
00001110
    1011
    0101  最終解

コンピュータ・ネットワーク(4) -ネットワーク層-

投稿日:
編集日:
タグ:

目次

ネットワーク層は5階層TCP/IPモデルの下から3番目の層である。

アプリケーション層
トランスポート層
ネットワーク層
データリンク層
物理層
ネットワーク層は、データリンク層で物理的に接続されたネットワークを跨いだノード間の通信を可能にする。

サービス
    物理的なネットワークを跨いで全てのノード間の通信を可能にする。
  • 誤り検出
インタフェースデータ単位パケット
アドレス論理アドレス
代表的なプロトコル
  • IPv4
  • IPv6
  • ICMP

IPパケット

IPでは、IPパケットというデータ単位で通信を行う。IPパケットは次のようなデータ構造である。ここで、各セルは基本的に1ビットを表す。

012345678910111213141516171819202122232425262728293031
Version IHL TOS Total Length
identification VCF Fragment Offset
TTL Protocol Header Checksum
Source Address
Destination Address
Options
Data
Version
IPのバージョン。IPv4ならば4。
IHL: Internet Header Length
IPヘッダの長さ。
Type Of Service
パケットが転送される際に重視するサービスを指定するフィールド。
01234567
優先度 遅延度 転送量 信頼性 予備
予備のフィールドは、RFC3168ではECN(輻輳通知機能)のために定義される。
67
ECTCE
各ビットはそれぞれ次のような名前である。
  • ECT(Explicit Capable Transport)
  • CE(Congestion Experience)
そしてそれらの値は次のような意味を持つ。
00ECNを未サポート。
01送信ホストはECNをサポート。
10
11輻輳はルータにより経験。
Total Length
パケットの全長。1バイトが8ビットとした時のバイト数。
identification
断片化した時にその断片がどのパケットのものかを表す。
VCF: Various Control Flags
断片化の制御に用いるフィールド。
012
予備禁止継続
Fragment Offset
パケットを断片化した時に、パケット全体のどこの位置にあたるのかを示すフィールド。
TTL: Time To Live
ルーティング可能な回数。パケットを転送する度に1減らし、これが0になったらそのパケットを破棄する。
Protocol
トランスポート層やネットワーク層の他のプロトコルなどでIPパケットを使用する場合にその種類を示すフィールド。
プロトコル番号プロトコル名
1ICMP
2IGMP
4IP
6TCP
7CBT
8EGP
9IGP
17UDP
41IPv6
43IPv6-Route
44IPv6-Flag
45IDRP
46RSVP
47GRE
50ESP
51AH
55MOBILE
58IPv6-ICMP
59IPV6-NoNxt
60IPv6-Opts
88EIGRP
89OSPF
94IPIP
103PIM
112VRRP
113PGM
115L2TP
Header Checksum
IPヘッダの誤り検査のためのフィールド。ルーティングの度にTTLの値が変化するため、この値も変化する。なおIPではデータ内容のチェックは行われない。
Source Address
送信元IPアドレス。
Destination Address
宛先IPアドレス。
Options
セキュリティやルーティングなどの拡張情報。可変長のフィールドで、ないこと、すなわち5であることが多い。


IPアドレス

IPアドレスとは32ビットの論理アドレスのことである。データリンク層で使用されるMACアドレスが各ネットワークデバイスにそれぞれ割り当てられているのに対し、IPアドレスはソフトウェアによってMACアドレスに対応づけられる(厳密には1対1関係とはいえないがそれについては後述する)。それゆえノードに割り当てられるIPアドレスは、常に同じであるとは限らない。

MACアドレスが隣接するノード間の通信に使われるのに、IPアドレスは接続されたノード間の通信のために使用される。例えば、送信元IPアドレス"192.168.1.2"から宛先IPアドレス"64.4.11.37"への通信を行う場合、経路中のパケットやフレームで使用される各アドレス情報は、それぞれ図のようになる(実際にはプライベートアドレスと呼ばれる"192.168.1.2"からグローバルアドレス"64.4.11.37"には直接通信できない)。

このように送信元・宛先IPアドレスが常にエンドのものであるのに対し、送信元・宛先MACアドレスはフレームを送受信するノードのものが使用される。

ネットワークインタフェース

IPアドレスはMACアドレスに対応付けられると述べたが、厳密にはこれは正しくない。前述の通り、MACアドレスはネットワークデバイス毎に割り当てられている。しかしソフトウェアレベルでは、ネットワークデバイスを抽象化したネットワークインタフェースという単位で扱われ、IPアドレスはネットワークインタフェースに割り当てられる。そして、ネットワークインタフェースにはMACアドレスがないがIPアドレスを持つものや、1つのネットワークインタフェースで複数のIPアドレスを持つもの、いくつかのネットワークインタフェースで同じMACアドレスが使用されるようなものなど様々なタイプがある。例えばUNIX系OSでは、ローカルループバックと呼ばれる自分自身と通信するためのIPアドレスが割り当てられたネットワークインタフェースがある。これは実際のネットワークデバイスの有無に関係なく存在し、かつMACアドレスがない。

ネットワーククラス -RFC791-

RFC791では、IPアドレスは3つの要素から構成される。各フィールドは、クラスとネットワークアドレス、ホストアドレスに分けられる。

クラス
ネットワークの規模。
ネットワークアドレス
ネットワークの識別番号。
ホストアドレス
あるネットワーク内のノードの識別番号。
クラス 0123456789 10111213141516171819 20212223242526272829 3031
A 0
B 1 0
C 1 1 0
1 1 1 拡張アドレスモード
IPアドレスは1バイト(8ビット)毎に区切って表示される(例:172.16.1.1)。IPアドレスにはいくつかの特別な番号のものがある。

サブネット -RFC950-

RFC950では、RFC791で出てきたネットワークを更に小さいネットワーク(サブネット)に分割できる。サブネットを指定するには、ネットワークマスクと呼ばれる32ビットの値を使用する。例えばクラスBのネットワークは次のようにしてサブネットを指定する。

0123456789 10111213141516171819 20212223242526272829 3031
B 1 0 ネットワーク ホスト
1 0 ネットワーク サブネット ホスト
1111111111 1111111111 000000000000
ネットワークマスクは255.255.0.0のように記述したり、IPアドレスと一緒に172.16.1.1/16のようにして表す。

プライベートアドレス -RFC1918-

インターネットで使用されるIPアドレスをグローバルアドレスと呼ぶ。そして、これとは別に局所的なネットワークで使用されるアドレスがある。これをプライベートアドレスという。RFC1918ではプライベートアドレスの範囲を次のように定めた。

10.0.0.0〜10.255.255.25510.0.0.0/8
172.16.0.0〜172.31.255.255172.16.0.0/12
192.168.0.0〜192.168.255.255192.168.0.0/16
プライベートアドレスとグローバルアドレスは、直接通信することができない。そのため、プライベートアドレスのマシンからグローバルアドレスのマシンにアクセスする場合、グローバルなネットワークに接続するルータによってアドレス変換が行われる。これをNAT(Network Address Translation)という。

このようなRFCで定められたグローバルアドレス以外の特別なアドレスのことを予約アドレスという。

予約アドレス

2014/02時点の予約アドレスは次の通りである。

アドレスネットワーク名RFC
0.0.0.0/8現在のネットワークRFC1122
10.0.0.0/8PRIVATE-ADDRESS-ABLK-RFC1918-IANA-RESERVEDRFC1918
127.0.0.0/8SPECIAL-IPV4-LOOPBACK-IANA-RESERVEDRFC6598
169.254.0.0/16SHARED-ADDRESS-SPACE-RFCTBD-IANA-RESERVEDRFC3927
172.16.0.0/12PRIVATE-ADDRESS-ABLK-RFC1918-IANA-RESERVEDRFC1918
192.0.0.0/24SPECIAL-IPV4-REGISTRY-IANA-RESERVEDRFC5736
192.0.2.0/24IANA Special UseRFC5737
192.88.99.0/246TO4-RELAY-ANYCAST-IANA-RESERVEDRFC1198
192.168.0.0/16PRIVATE-ADDRESS-ABLK-RFC1918-IANA-RESERVEDRFC1918
198.18.0.0/15SPECIAL-IPV4-BENCHMARK-TESTING-IANA-RESERVEDRFC2544
198.51.100.0/24TEST-NET-1RFC5737
203.0.113.0/24TEST-NET-3RFC5737
224.0.0.0/4マルチキャスト(クラスD)RFC3171
240.0.0.0/4クラスERFC1112
255.255.255.255ブロードキャストRFC919,RFC922
RFCは廃止される場合がある。すなわち予約アドレスが常に同じとは限らないし、それを規定するRFCも同じであるとは限らない。例えばプライベートアドレスは初めRFC1597で規定されたが、今は廃止され、現在はRFC1918で規定されている。

UNIX系OSでは、いくつかの例外を除いてIPアドレスの情報をwhoisコマンドで調べることができる。例えば127.0.0.1のIPアドレスについて調べたい時、次のようにすれば良い。

user% whois 127.0.0.1

ドメイン

人間がIPアドレスを指定する際、多くの場合はIPアドレスを指定する代わりにドメイン名といわれるコンピュータ名を指定する。ドメイン名はICANN(Internet Corporation for AssignedNames andNumbers)と呼ばれる団体によって管理される。ドメイン名の情報は、アプリケーション層のWHOISプロトコルを使用して取得できる。UNIX系では、そのためのクライアントアプリとしてwhoisがある。

user% whois google.co.jp

ドメインは、アプリケーション層のDNSという仕組みによって成り立つ。ドメイン名はDNSサーバによって管理され、DNSクライアントでアクセスすることで、ドメイン名からIPアドレスへ変換することができる。

user% nslookup google.co.jp


ルーティング

ルーティングとは、パケットの経路制御のことである。ルーティングはルーティングテーブルに基づいてルート(経路)を探索する。ルーティングは次の2種類に分類できる。

静的ルーティング
手動でルーティングテーブルの中身を設定する方法。
動的ルーティング
アルゴリズムに基づいて自動でルーティングテーブルの中身を設定する方法。経路決定アルゴリズムには次の2種類がある。
  • 距離ベクトルアルゴリズム(DVA:Distance Vector Algorithm)
  • リンク状態アルゴリズム(LSA:Link State Algorithm)

ここでルーティングの仕組みを紹介する。次のような3つのネットワークインタフェースを持つノードがあるとする。

lo
127.0.0.1
eth0
192.168.1.2/24
eth1
173.194.117.183/16
この時、ルーティングテーブルは次のような情報を持つ。
ネットワーク/ノードゲートウェイインタフェース
デフォルト192.168.1.1eth0
192.168.1.0/24*eth0
173.194.0.0/16*eth1
127.0.0.1/8*lo
ゲートウェイとは、あるパケットを別のネットワークに転送してくれるためのノードのことである。ここで、*はゲートウェイが設定されていないこと、すなわちそのネットワークに属するノードであることを示す。同じネットワーク内のノードであれば、データリンク層で接続されているため、直接通信可能である。また、全てのルーティングテーブルには、デフォルトルートと呼ばれるものがある。もしルーティングテーブルに宛先のネットワークが載っていない場合、デフォルトルートを使用する。

次のようなネットワークで前述のノードが通信を行うとする。

この時、送信元IPアドレスから宛先IPアドレス(64.4.11.37)までの経路は次のようになる。
  1. 宛先のネットワークが不明のため、デフォルトルートを使用。eth0のネットワークインタフェースを使用し、192.168.1.1に送信。
  2. 203.216.243.243に送信。
  3. 64.4.11.37に送信。
送信元ノードは、経路途中にあるノードのルーティングテーブルの情報が分からなくても問題ない。また、各ネットワーク内のノード間はデータリンク層で接続されており、識別にはMACアドレスが使用されている。データリンク層の通信については本稿では詳しく言及しない。IPアドレスはARPによってMACアドレスに変換することができる。

ネットワークインタフェースの情報は、UNIX系OSではifconfig、Windowsではipconfigコマンドで、ルーティングテーブルの情報は、UNIX系OSもWindowsも、routeコマンドや、netstatコマンドの-rオプションで表示したり設定したりできる。また、パケットが辿った経路については、tracerouteコマンドで確認することができる。


関連コマンド

以下にネットワーク層に関するコマンドをいくつか紹介する。なおここで紹介するコマンドは、主にUNIX系OSを想定している。

whois
IPアドレスやドメインの情報をRFC3912のデータベースより検索するコマンド。ドメインとはIPアドレスに対応づけた名前のことで、"google.co.jp"や"yahoo.co.jp"などがそれに該当する。whoisの使い方は次の通りである。
user% whois 173.194.117.166
これによりネットワークの大きさや名前、それを管理する団体の情報などを取得できる。また、ドメインを指定することでそのドメインの管理者や有効期限などの情報を取得できる。
user% whois google.co.jp
nslookup
dig
ドメインとIPアドレスを変換するにはnslookupやdigコマンドがある。nslookupコマンドはWindowsにもあり、基本的な使い方は同じである。nslookupコマンドの使い方は、コマンドの引数を指定する方法と対話する方法の2種類がある。
user% nslookup google.co.jp
user% nslookup
> google.co.jp
traceroute
パケットの経路を調査し、出力するコマンド。
user% traceroute google.co.jp
tracerouteのオプションをいくつか紹介する。
-p port
ポート番号を指定。
-i device
デバイスを指定。
ping
ICMP Echo Requestパケットを送信するコマンド。pingはUNIX系OSだけでなくWindowsでも使用可能である。以下にUNIX系OSのpingのオプションをいくつか紹介する。
-v
詳細な情報を出力。
-b
ブロードキャストで行う。
-R
経路を表示。
-i wait
個々の送信間隔をwait秒分間を置く。標準では1秒。
pingは次のようにして使用する。
user% ping google.co.jp
netstat
ネットワークに関する情報や状態を表示・設定するコマンド。以下にいくつか例を示す。UNIX系OSにもWindowsにもあるコマンドだが、使い方(オプション)は異なる。
ルーティングテーブル
user% netstat -r
UNIX系OS、Windows共に-rは同じである。
user% netstat --route
user% route
ネットワークインタフェース
user% netstat -i
user% netstat --interfaces
使用中のソケット情報を確認(つまりポートの状態も確認できる)
user% netstat -a
user% netstat --all
プロトコル毎のログの統計
user% netstat -s
user% netstat --statistics
なおオプション-nを指定することで、アドレスやポート番号をそのまま数字で表示する。
route
ルーティングテーブルの表示・設定を行うコマンド。routeは基本的に処理とそれに対する引数を指定する。処理を指定しなければ現在のルーティングテーブルを表示する。
add
ルーティングテーブルへの追加処理。
  • user% route add -net 192.168.1.0 netmask 255.255.255.0 dev eth0
  • user% route add -host 192.168.1.2 gw 192.168.1.1 dev eth0
  • user% route add -net default gw 192.168.1.1 dev eth0
del
ルーティングテーブルからの削除処理。
  • user% route del -net 192.168.1.0 netmask 255.255.255.0 dev eth0
  • user% route del -host 192.168.1.2 gw 192.168.1.1 dev eth0
  • user% route del -net default
ifconfig
ipconfig
ネットワークインタフェースの情報を表示・設定するコマンド。UNIX系OSはifconfig、Windowsはipconfigであり、使い方も異なる。
user% ifconfig -a
user% ipconfig /all
設定にはルート権限が必要である。 例えばIPアドレスとネットワークマスクは次のようにして設定する。
user% ifconfig eth0 192.168.1.100 netmask 255.255.255.128 
これ以外にもさまざまな設定がある。例えば通常ネットワークデバイスは自分のIPアドレス以外のパケットは破棄する(データリンク層でユニキャストを実現する)が、プロミスキャスモードというモードを有効にすれば、それを受信できる。
user% ifconfig eth0 promisc
DHCP
5階層TCP/IPモデルのアプリケーション層のDHCPによってIPアドレスを設定する場合、IPアドレスはサーバによって割り当てられる。そして多くのルータはDHCPサーバの機能を持つ。それゆえ多くのルータが使用される環境では、個々のノードが自身のIPアドレスを決めることはないだろう。UNIX系OSでは、DHCPクライアントにdhclientコマンドがある。以下に簡単な使い方を紹介する。
user% dhclient eth0
iwconfig
iwspy
無線のネットワークインタフェースのためのコマンド。
tcpdump
パケットスキャニングツール。5階層TCP/IPモデルのより上位の層のためにも活用できるコマンドで、指定したネットワークインタフェースが扱ったパケット情報を出力する。tcpdumpについてはアプリケーション層のところで詳述する。
user% tcpdump -i eth0
-i interface
ネットワークインタフェースを指定。
-e
イーサネットフレームのヘッダを表示。
-x
-X
パケットの中身を16進数で表示。-Xの場合、それに加えてASCII文字に変換したものも表示。
-n
アドレス(IPアドレスやポート番号)を名前に変換しない。
-N
ホストを名前に変換しない。
expression
パケットフィルタ機能。いかにいくつかの機能を紹介する。
proto
プロトコルを指定。
src host
送信元アドレスを指定。
src port port
送信元のポート番号を指定。
dst host
宛先アドレスを指定。
dst port port
宛先のポート番号を指定。
and
or
not
いくつかの条件を組み合わせる
iptables
PF
パケットフィルタツール。iptablesはLinux用の、pfは主にBSD系で使用されるパケットフィルタである。iptablesの詳細はここを参照されたい。

パケットプログラミング

IPパケットの送信

ネットワーク層のサービスを利用してデータを送信する例を紹介する。IPパケットを生成して送信するプログラムのソースコードは、次のように記述できる。なおこのコードはエラー処理のコードを記述していない。

#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char *argv[])
{
  const int ON=1;
  const char* DST_ADDR = "192.168.1.1";
  const unsigned char IP_PACKET[] = {
    0x45, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x40, 0x00,
    0xFF, 0x04, 0x00, 0x00,
    0xad, 0xc2, 0x7e, 0x0d, // src 173.194.126.13
    0xc0, 0xa8, 0x01, 0x01, // dst 192.168.1.1
    0x00, 0x01, 0x02, 0x03, 
    0x04, 0x05, 0x06, 0x07
  };
  int sock;
  struct sockaddr_in sin;
  
  memset(&sin, 0, sizeof(struct sockaddr_in));
  sin.sin_family = PF_INET;
  sin.sin_addr.s_addr = inet_addr(DST_ADDR);
  sin.sin_port = 0;
  
  sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);  
  setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &ON, sizeof(int));
  sendto(sock, IP_PACKET, sizeof(IP_PACKET), 0, (struct sockaddr*)&sin, sizeof(sin));
  
  return 0;
}
このコードは、IPパケットを送信するプログラムである。このプログラムを正常に実行するにはルート権限が必要である。

タイプがSOCK_RAWについては、マニュアル第7章のrawを参照されたい。

user% man 7 raw
マニュアルによると、IPヘッダの大きさやヘッダ・チェックサムは関数によって自動的に計算される。また、ソースアドレスとパケットIDは0の場合に変更される。 このプログラムを実行し、そのパケットをtcpdumpで見た結果を以下に示す。
15:43:32.732162 IP 173.194.126.13 > 192.168.1.1: IP0 [|ip]
     (ipip-proto-4)
     0x0000:  4500 001c 0000 4000 ff04 8e64 adc2 7e0d
     0x0010:  c0a8 0101 0001 0203 0405 0607

IPパケットの受信

ICMPパケットを受信するようなプログラムのコードは次のように記述される。このコードも同様にエラー処理を省略している。

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
  int i;
  int sock;
  unsigned char packet[0x54];
  
  sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
  while (1){
    recv(sock, packet, sizeof(packet), 0);
    for (i=0;i<0x54;i++){
      if (i%4==0) putchar('\n');
      printf("0x%.2x ", packet[i]);
    }
    putchar('\n');
  }
  close(sock);
  return 0;
}
このプログラムもまた正常に実行するにはルート権限が必要である。

このプログラムを起動中に、127.0.0.1宛にpingをすると、次のように画面に出力されるだろう。

0x45 0x00 0x00 0x54 
0x80 0x72 0x00 0x00 
0x40 0x01 0xfc 0x34 
0x7f 0x00 0x00 0x01 
0x7f 0x00 0x00 0x01 
0x00 0x00 0x19 0xed 
0x18 0xaa 0x00 0x01 
0xa9 0x65 0x07 0x53 
0x2c 0xac 0x05 0x00 
0x08 0x09 0x0a 0x0b 
0x0c 0x0d 0x0e 0x0f 
0x10 0x11 0x12 0x13 
0x14 0x15 0x16 0x17 
0x18 0x19 0x1a 0x1b 
0x1c 0x1d 0x1e 0x1f 
0x20 0x21 0x22 0x23 
0x24 0x25 0x26 0x27 
0x28 0x29 0x2a 0x2b 
0x2c 0x2d 0x2e 0x2f 
0x30 0x31 0x32 0x33 
0x34 0x35 0x36 0x37 

コンピュータ・ネットワーク(5) -トランスポート層-

投稿日:
修正日:
タグ:

目次

トランスポート層は5階層TCP/IPモデルの下から4番目であり、上位の層にサービスを行う最後の層である。

アプリケーション層
トランスポート層
ネットワーク層
データリンク層
物理層

トランスポート層は、アプリケーション層の各サービスに対し、通信を提供する。

サービス
    サービスを行ったり受けたりするために、そのプロセスに通信を提供。
  • フロー制御
  • 輻輳制御
  • 誤り検出
インタフェースデータ単位ソケット
アドレスポート番号
代表的なプロトコル
  • TCP
  • UDP
※UDPはフロー制御や輻輳制御などは行わず、アプリケーション層に任せているため、TCPだけといえなくもない
トランスポート層の代表的なプロトコルには、コネクション型のTCPと、コネクションレス型のUDPがある。TCPは電話のように互いにコンタクトを取りながらデータ通信を行うことで、品質の高い通信を提供する。UDPは手紙のように送信者が一方的に受信側にデータを送るようなものである。UDPの場合、送りつけられたデータが届いたかどうか、それが完全なものであるかの確認や、受信側やネットワークへの負荷管理などはアプリケーション層で確認しなければならない。
TCP(Transmission Control Protocol)
フロー制御や輻輳制御、誤り検出などを行うことで、高品質な通信をアプリケーション層に提供。
UDP(User Datagram Protocol)
TCPのような高品質な通信を行うための機能をアプリケーション層に任せることで、トランスポート層での高速通信を可能にする。
TCPの通信は、次の2種類の通信から成る。
  • データの送受信のための通信
  • 通信制御のための通信
本稿ではTCPを中心に説明する。

TCPの通信では、あるノードAがノードBに対してデータを送る場合、ノードAを送信側、ノードBを受信側と呼ぶ。ノードBからノードAに対して通信制御のための情報を送信することもあるが、多くの場合、その呼び方は変わらない。


3ウェイ・ハンドシェイク

TCPではコネクション型の通信を行う。そのために3ウェイ・ハンドシェイクと呼ばれる接続の確立を行う。接続の確立には、データ通信を開始するのを含め次の5つの工程が必要である。

  1. 送信者がSYNパケットを送る。
  2. 受信者がACKパケットを送る。
  3. 受信者がSYNパケットを送る。
  4. 送信者がACKパケットを送る。
  5. データを送信する。
SYNパケットは同期開始の合図であり、ACKパケットは確認の合図である。

TCPでは、これらの工程の次のように3つの工程にまとめている。

  1. 送信側がSYNパケットを送信する。
  2. 受信側がSYNパケットとACKパケットを送信する。
  3. ACKパケットと、データを送信する。

3ウェイ・ハンドシェイクで接続が確立されなければ、アプリケーション層はアクセスがあったかどうかも認識できない。それゆえ、SYNパケットを送り、SYN+ACKパケットが返ってくるかを待ち、返ってきても最後のACKを送信しないことで、相手に悟られることなくそのポートでTCPを使った通信を行なっているかを確認することができる。このような処理をポートスキャンという。これを防ぐには、1階層下のネットワーク層で、通信を監視する必要がある。このような機能や、それによって通信を防ぐ機能をファイアウォールという。

フロー制御

TCPのフロー制御では、受信相手の状況に応じて送信量を制御することで、受信側の負荷を小さくしようとする。そのために、受信側は受信可能な通信量、つまり受信者のバッファの空きサイズを送信側に知らせる。この通信量を制御する仕組みをスライディングウィンドウと、受信可能なサイズのことをウィンドウ・サイズという。受信側は,受信可能なウィンドウサイズを,ソケットのウィンドウ・サイズフィールドによって通知する。

輻輳制御

2つのノードが通信する場合,それが同一のネットワークであるとは限らない。そのような場合,パケットはノード間の転送速度は間にあるいくつかのネットワークやルータによって処理速度が変化する。それゆえ,それぞれのノードが好き放題な送信を行い,ルータがそれを処理しきれずにパケットをロスすれば結果として遅くなる。また,ネットワーク全体としての性能(スループット)も低下する。TCPにはこのような問題を防ぎ、ネットワークの利用効率を良くするための仕組み(輻輳制御の機能)がある。その仕組みがスロースタートアルゴリズムである。

送信側は、はじめに送出するサイズを小さく設定し、それから指数オーダ的に大きくしていく。もし輻輳が発生した場合,受信側はそれを送信側に知らせ,送出するサイズを小さくさせる。TCPでは送信側と受信側がそれぞれパケットを送りあうことでコネクションを確立するが,もし輻輳が発生した場合,受信側は送信側に送るパケットサイズを小さくすることで,それを知らせる。これはTCPには元々輻輳制御が備わっておらず,後に追加されたためにこのような通知手段をとっている。送出するサイズもまたウィンドウサイズという単位で扱われるが,輻輳制御の場合ウィンドウサイズはOSによって管理される。輻輳が発生してウィンドウサイズを減らした後,送信側は今度は線形的にウィンドウサイズを増やしていくことで,送出するサイズを理想的な状態に近づける。

ルータがパケットをロスするような場合,ルータはICMPパケットを使ってそれを送信側に知らせる。通信経路を特定するためのコマンドtracerouteは,この特性を利用して徐々にTTL(Time To Live)を増やしていく事で実装される。


ソケット

TCP通信で使用するソケットは次のような構造である。ここで各セルはビット単位を表す。

01234567 89101112131415 1617181920212223 2425262728293031
送信元ポート番号宛先ポート番号
シーケンス番号
ACK番号
データオフセット 予約領域 NS CWR ECE URG ACK PSH RST SYN FIN ウィンドウ・サイズ
チェックサム 緊急ポインタ
オプション(32ビット単位で可変長)
データ部分(可変長)
送信元ポート番号
サービスを行うトランスポート層のアドレス。
宛先ポート番号
サービスを受けるトランスポート層のアドレス。
シーケンス番号
データを分割して送信する場合、その順番を表す値。送信するデータには、1バイト毎に順にシーケンス番号が割り当てられている。シーケンス番号の値は、データの先頭が0とは限らず、最大値まで使用すると0から順に割り当てられる。シーケンス番号はTCPデータの送信側で管理される。例えば30バイトのデータを10バイトずつ送信する場合、ソケットのシーケンス番号は1つ目はn、2つ目はn+10、3つ目はn+20となる。
ACK(Acknowledge)番号
受信側がデータをどのシーケンス番号まで受信したか示す値。ACK番号は受信したデータのシーケンス番号+1である。例えば10バイトのデータを受信した場合、初期値+10の値になる。
データオフセット
ヘッダ長
TCPソケットのデータが始まる位置、またはTCPソケットのサイズを示す値。
予約領域
将来的に使用されるために予約されているフィールド。現在は未使用で値は常に0である。
フラグ
パケット(ソケット)の種類を表すフィールド群。各フィールドは1の時にオン、0の時にオフを表す。標準では全て0である。RFC793ではフラグが6つであったが、後に追加されていき、RFC3540までに3つ追加されて9ビットとなり、その分予約領域が縮小された。
URG
URGentフラグ。緊急データが含まれていることを表す。
ACK
ACKnowledgeフラグ。ACK番号が含まれていることを表す。
PSH
PuSHフラグ。受信したデータをすぐに上位レイヤに提供することを表す。
RST
ReSeTフラグ。TCP接続を中断、拒否することを表す。ある接続要求に対してACKならば接続許可、RSTならば接続拒否を表す。
SYN
SYNchronizeフラグ。接続要求を表し、ACK番号を同期する。
FIN
FINishフラグ。接続終了を表す。
後に追加されたフラグ
ECE
RFC3168で追加されたフラグで、輻輳が起こっていることや輻輳情報を通知するための機能が備わっていることを示す。ECN-Echoフラグ。具体的には、3ウェイハンドシェイク(SYNフラグがオン)の際、ECN(輻輳情報通知機能)対応であること、すなわちIPパケットのTOSの予備フィールドをECNのために使用できる(10か01、11の値かもしれない)ことを示す。それ以外(SYNフラグがオフ)の時、IPパケットのCEフラグが1だったことを示す。
CWR
RFC3168で追加されたフラグで、輻輳ウィンドウ・サイズを小さくしたことを示す。すなわち受信側がENEフラグがオンであるソケットを受信した場合、これをオンにしたソケットを送信することで、輻輳制御機構で対応したことを通知する。Congestion Window Reducedフラグ。
NS
RFC3540で追加されたENC Nonce Sumフラグ。
ウィンドウ・サイズ
受信側が受信可能なソケットのサイズ。
チェックサム
ヘッダやデータの誤りを検査するための値。
緊急ポインタ
URGフラグがオンの時に使用されるフィールド。 緊急データが シーケンス番号からのオフセットを示す。
オプション
拡張情報のためのフィールド。32ビット単位で可変長で、最小サイズは0バイトである。MSS(最大セグメントサイズ)やウィンドウ・スケーリングなどを設定するのに使用され、情報が32ビット単位にならない場合はその分0を詰められる(これをパディングと呼ぶ)。


関連コマンド

telnet
TELNETプロトコルのインタフェース。標準では23番が使われるが、ポート番号を指定することで他のポートにアクセスしてTCP通信が可能である。
user% telnet pied-piper.net 80
GET /note/note.cgi HTTP/1.1
Accept: image/gif, image/jpeg, */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (Compatible; MSIE 6.0; Windows NT 5.1;)
Host: pied-piper.net
Connection: Keep-Alive

nc
netcat
TCPかUDPで通信を行うためのユーティリティ。telnetと異なりtcpだけでなく、UDPも扱え、ポートスキャンやソケットの受け取り側としても起動可能。
-u
UDPモードで起動。
-l
リッスンモードで起動。
-p port
リッスンモードの際に開くポート番号を指定。
-s port
ソースポートを指定。
-z
スキャンのみでデータを送らない。
-v
詳細な情報を出力。
接続
user% nc pied-piper.net 80
GET /note/note.cgi HTTP/1.1
Accept: image/gif, image/jpeg, */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (Compatible; MSIE 6.0; Windows NT 5.1;)
Host: pied-piper.net
Connection: Keep-Alive

user% nc -u -z localhost 5555
リッスンモード
user% nc -p 5555 -l localhost
user% nc -p 5555 -u -l localhost
ポートスキャン
user% nc -z -v localhost 0-65535
user% nc -z -u -v localhost 0-65535
nmap
高性能ポートスキャニングツール。
user% nmap localhost -p1-1024 -sT
user% nmap localhost -p1-1024 -sT -sU
user% nmap 192.168.1.1,2 -p1-100,500-1024 -sT -sU
-p
ポート番号やポート番号の範囲を指定。
-T0
-T1
-T2
-T3
-T4
-T5
スキャンのタイミングを設定。数字が大きいほどスキャン速度は速い。
-r
検査するポートの順番は標準では無作為だが、順番に行うように設定。
-sT
TCP接続でスキャン。
-sU
UDPでスキャン
-sS
TCP SYNパケットでスキャン。
-sA
TCP ACKパケットでスキャン。
-sF
TCP FINパケットでスキャン。
-s0
IPパケットでスキャン。
-PE
ICMP Echoパケットでスキャン。
-PP
ICMP Timestampパケットでスキャン。
-PM
ICMP Address Maskパケットでスキャン。
-O
アクセス先マシンのOSを推測。ルート権限でのみ使用可能
--traceroute
tracerouteを行う。
tcpdump
パケットスキャニングツール.さまざまな機能を持ち、5階層TCP/IPモデルのいくつかの層で利用可能であり、プロトコルや宛先ポート番号、送信元ポート番号でフィルタリングすることができる。
user% tcpdump -X src port 80 -i eth0
proto
プロトコルを指定.
src port port
送信元のポート番号を指定.
dst port port
宛先のポート番号を指定.
and
or
not
いくつかの条件を組み合わせる


ソケットプログラミング

UDPやTCPソケット通信を利用したプログラム、すなわちアプリケーション層のプログラムは次のように書くことができる。

TCP

サーバ・プログラム

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>


int main(int argc, char *argv[])
{
  const char* SERVER_ADDR = "192.168.1.14";
  const int SERVER_PORT = 50000;
  int server_fd, client_fd;
  FILE *fp;
  struct sockaddr_in sin, pin;
  char buf[20];
  size_t size = sizeof(struct sockaddr_in);
  
  memset(&sin, 0, sizeof(struct sockaddr_in));
  sin.sin_family = PF_INET;
  sin.sin_addr.s_addr = inet_addr(SERVER_ADDR);
  sin.sin_port = htons(SERVER_PORT);
  
  server_fd = socket(PF_INET, SOCK_STREAM, 0);
  bind(server_fd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in));
  listen(server_fd, 50);
  
  client_fd = accept(server_fd, (struct sockaddr*)&pin, &size);
  fp = fdopen(client_fd, "w+");
  
  fprintf(fp, "Hello, Client\n");
  
  fgets(buf, 20, fp);
  printf("client ip:%s\n", inet_ntoa(pin.sin_addr));
  printf("Message: %s\n", buf);
  
  fclose(fp);
  return 0;
}

クライアント・プログラム

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>


int main(int argc, char *argv[])
{
  const char* SERVER_ADDR = "192.168.1.14";
  const int SERVER_PORT = 50000;
  int server_fd;
  FILE *fp;
  struct sockaddr_in sin;
  char buf[20];
  
  memset(&sin, 0, sizeof(struct sockaddr_in));
  sin.sin_family = PF_INET;
  sin.sin_addr.s_addr = inet_addr(SERVER_ADDR);
  sin.sin_port = htons(SERVER_PORT);
  
  server_fd = socket(PF_INET, SOCK_STREAM, 0);
  connect(server_fd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in));
  fp = fdopen(server_fd, "w+");
    
  fgets(buf, 20, fp);
  
  fprintf(fp, "Hello, Server\n");
  printf("Server ip:%s\n", inet_ntoa(sin.sin_addr));
  printf("Message: %s\n", buf);
  
  fclose(fp);
  return 0;
}


UDP

送信側プログラム

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>


int main(int argc, char *argv[])
{
  const char* SERVER_ADDR = "192.168.1.14";
  const int SERVER_PORT = 50000;
  const char HELLO[] = "Hello, Server";
  int sock;
  struct sockaddr_in sin;
  char buf[20];
  
  memset(&sin, 0, sizeof(struct sockaddr_in));
  sin.sin_family = PF_INET;
  sin.sin_port = htons(SERVER_PORT);
  sin.sin_addr.s_addr = inet_addr(SERVER_ADDR);
  
  sock = socket(PF_INET, SOCK_DGRAM, 0);
  
  sendto(sock, HELLO, strlen(HELLO), 0, (struct sockaddr*)&sin, sizeof(struct sockaddr_in));
  
  close(sock);
  return 0;
}

受信側プログラム

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>


int main(int argc, char *argv[])
{
  const char* SERVER_ADDR = "192.168.1.14";
  const int SERVER_PORT = 50000;
  const char HELLO[] = "Hello, Client";
  size_t len = sizeof(struct sockaddr_in);
  int sock;
  struct sockaddr_in sin, pin;
  char buf[20];
  
  memset(&sin, 0, sizeof(struct sockaddr_in));
  sin.sin_family = PF_INET;
  sin.sin_port = htons(SERVER_PORT);
  sin.sin_addr.s_addr = inet_addr(SERVER_ADDR);
  
  sock = socket(PF_INET, SOCK_DGRAM, 0);
  bind(sock, (struct sockaddr*)&sin, len);
  
  recvfrom(sock, (void*)buf, 13, 0, (struct sockaddr*)&pin, &len);
  buf[13] = '\0';
  printf("Message: %s\n", buf);
  
  close(sock);
  return 0;
}

コンピュータ・ネットワーク(6) -アプリケーション層-

投稿日:
修正日:
タグ:

目次

アプリケーション層は、OSI参照モデルや5階層TCP/IPモデル、4階層TCP/IPモデルなどで最も上位の層である。

アプリケーション層
トランスポート層
ネットワーク層
データリンク層
物理層
本稿では、アプリケーション層のいくつかのプロトコルによって実装されるWebや電子メールについて紹介する。

ポート番号

5階層TCP/IPモデルでは、アプリケーション層の各プロトコルはトランスポート層で提供されるポートを利用する。そして、いくつかの有名なプロトコルでは、サーバで利用するポート番号がICANNによって標準化されている。また、それ以外の多くのプロトコルも使用するポート番号が明示されている。

ウェルノウンポート番号(well-known ports)0-1023ICANNで登録されているポート番号の範囲のうち有名なもの
登録済みポート番号1024-49151ICANNで登録されているポート番号の範囲
プライベート(ダイナミック)ポート番号49152-65535 ユーザやアプリケーションが自由に利用できるポート番号の範囲。多くの場合、クライアントアプリが必要に応じて動的に使用する。

ウェルノウンポート番号(0-1023)

TCP/UDPポート番号プロトコル名機能サーバ例クライアント例
TCP,UDP7ECHO 基本的にテスト用途で使用される。送られたデータをそのまま返す
TCP20FTPFile Transfer Protocolファイル送受信のためのプロトコル。データ転送用
  • Webブラウザ
  • ftp
TCP21ファイル送受信のためのプロトコル。制御用
TCP22SSHSecure SHell暗号や認証技術を用いた安全な通信やリモートログインのためのプロトコル。
  • OpenSSH Server
  • OpenSSH Client
TCP,UDP25SMTPSimple Mail Transfer Protocol電子メールの送受信に使用するプロトコル。
  • qmail
  • sendmail
  • thunderbird
  • Microsoft Outlook
TCP,UDP42WINSWindows Internet Naming Service Microsoft Windowsネットワーク上のコンピュータ名をIPアドレスに変換するためのプロトコル。宛先不明のため、クライアントはブロードキャストで問い合わせを行う。
TCP43WHOISドメイン名やIPアドレスなどの所有者情報を取得するためのプロトコル。
  • whois
TCP,UDP53DNSDomain Name System IPアドレスとドメイン名の対応情報を取得するためのプロトコル。
  • BIND
  • Djbdns
  • nslookup
  • dig
TCP,UDP80HTTPHyperText Transfer Protocol HTMLや画像、テキストなどの送受信のためのプロトコル。 Webサーバ
  • apache
  • IIS
Webブラウザ
  • Internet Explorer
  • Google Chrome
UDP123NTPNetwork Time Protocolマシンの時間を同期するためのプロトコル。
  • ntpdate
TCP443HTTP over SSLSSL通信を用いたSMTP
TCP465SMTP over SSLSSL通信を用いたSMTP

登録済みポート番号(1024-49151)

TCP/UDPポート番号プロトコル名機能サーバ例クライアント例
TCP,UDP1194OpenVPNオープンソースのVirtual Private Networkソフトウェアのためのプロトコル。
TCP,UDP1293IPSecInternet Protocol SecurityIPパケット単位でのデータ改ざん防止や秘匿機能を提供するためのプロトコル。
TCP,UDP3389RDPRemote Desktop ProtocolMicrosoftが開発しているリモートデスクトップサービスのためのプロトコル。
  • リモートデスクトップ接続
TCP,UDP5900VNCVirtual Network Computingリモートデスクトップ方式のためのプロトコル。
TCP6000X11ネットワーク経由でXを利用するためのプロトコル。
UDP6001
TCP8080HTTPの代替HTTPのプロキシサーバやキャッシュサーバなどに使用される。

プライベート(ダイナミック)ポート番号(49152-65535)

多くのプロトコルでは、クライアントアプリケーションが使用するポート番号は、プライベートポート番号の中で使われていないもの無作為に動的に使用する。例えば、WebブラウザでHTTP(80番ポート)のサイトにアクセスする際のパケット解析した結果の一部を示す。

user% tcpdump -n -X dst port 443 -i wlan0
06:29:52.791584 IP 192.168.1.14.60063 > 183.77.202.242.80: Flags
[S], seq 1904628341, win 14600, options [mss 1460,sackOK,TS val
104657554 ecr 0,nop,wscale 4], length 0
この解析後すぐにサーバとクライアントのTCPポートを確認すると、それぞれ次のような結果となる。
サーバ
uesr% telnet 183.77.202.242 80
Trying 183.77.202.242...
Connected to 183.77.202.242.
Escape character is '^]'.
クライアント
uesr% telnet localhost 60063
Trying ::1...
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused
このようにクライアントのポートは、動的に使用される。

また、同じクライアントアプリを起動し直して再度同じページにアクセスしてもクライアントが使用するポート番号が同じであるとは限らない。

user% tcpdump -n -X dst port 443 -i wlan0
06:34:15.218947 IP 192.168.1.14.60082 > 183.77.202.242.80: Flags
[F.], seq 2386135769, ack 94445475, win 2641, options [nop,nop,TS val
104723161 ecr 2033111718], length 0
前の例では、クライアントアプリは60063番ポートを使用したが、今回は60082番ポートを使用している。このように、クライアントアプリは、49152番から65535番のポート番号を動的に使用する。

TCP

トランスポート層のTCPは、高品質なデータ通信を可能にする。アプリケーション層では、これを利用したものがいくつもある。それらはユーザに対して、さまざまなものを提供する。

  • テキスト
  • メッセージ
  • 音声
  • 動画

telnet

telnetコマンドを使用すると、ユーザは簡単にTCPのサービスを受けることができる。telnetは指定したIPアドレスのポート番号に対し、ユーザが入力した文字列を送信する。本稿では、これを利用してHTTPやSMTP、POP3などのプロトコルを説明する。


Web(HTTP)

WWW(World Wide Web)上でテキストや画像などのデータを通信するのに使用されている代表的なプロトコルが、HTTP(Hyper Text Transfer Protocol)である。HTTPサーバは基本的にTCPポートの80番を使用し、クライアントが要求したテキストや画像などを送信する。HTTPクライアントとして最も有名なアプリケーションがWebブラウザである。Webブラウザにはさまざまな種類がある。

  • Internet Explorer
  • Firefox
  • Google Chrome
  • Safari

ユーザの中にはWebブラウザが表示したWebページを、Web(HTTP)サーバが提供している画面だと認識しているかもしれない。しかしHTTPクライアントが取得するデータは、基本的にHTML(Hyper Text Markup Language)という形式のテキストである。HTTPのバージョン1.0では、クライアントは次のようにGETメソッドを使用してHTMLテキストを取得できる。

user% telnet pied-piper.net 80
Trying ::1...
Connected to pied-piper.net.
Escape character is '^]'.
GET /index.html HTTP/1.0

HTTP/1.1 200 OK
Date: Mon, 14 Apr 2014 15:34:24 GMT
Server: Apache/2.2.22 (Debian)
Last-Modified: Sat, 21 Sep 2013 17:22:33 GMT
ETag: "6a2c55-b1-4e6e8084eb7e3"
Accept-Ranges: bytes
Content-Length: 177
Vary: Accept-Encoding
Connection: close
Content-Type: text/html; charset=UTF-8

<html><body><h1>It works!</h1>
<p>This is the default web page for this server.</p>
<p>The web server software is running but no content has been added,
      yet.</p>
</body></html>
Connection closed by foreign host.
HTTPの仕様についてはRFCを参照されたし。
バージョンRFC
HTTP/1.0RFC 1945
HTTP/1.1RFC 2616

HTML(Hyper Text Markup Language)

HTTPは基本的にHTMLという形式のテキストを転送する。HTMLはマークアップ言語とよばれるコンピュータ言語の一種である。マークアップ言語はタグという文字列を使って、各文章を意味付けできる。例えば<h1>ハロー</h1>は"ハロー"という文字列が文中の最大レベルの見出しであることを示す。タグは基本的にマークしたい文字列を囲うことで意味付けする。以下にサンプルコードを示す。

<html><body><h1>It works!</h1>
<p>This is the default web page for this server.</p>
<p>The web server software is running but no content has been added,
      yet.</p>
</body></html>
<html>-</html>
HTMLの文
<body>-</body>
本文
<h1>-</h1>
見出し レベル1-6(1が最大)
<p>-</p>
段落

HTMLレンダリングエンジン

WebブラウザはHTMLを受け取るとそれを読み込み、解釈してユーザに分かりやすい形で表現する。前述で紹介したWebブラウザの場合、HTMLを視覚的に見やすい形で表現する。前述のサンプルを表現した例が次の通りである。

HTML Rendering Engine
ただし必ずしも視覚的な表現だけとは限らず、視覚障害者用の音声ブラウザのように音声で表現するものもある。このHTMLを取得し、それを解釈してユーザに分かりやすいよう表現するプログラム、または機能をHTMLレンダリングエンジンという。最近のHTMLレンダリングエンジンの多くはHTMLだけでなく、CSS(Cascading Style Sheets)JavaScriptなどを読み込む機能を持つ。
CSS(Cascading Style Sheets)
HTMLの視覚的情報を指定する情報。例えば文字の色や大きさなどがある。
JavaScript
動的なWebサイトを構築するためのプログラミング言語。HTTPのクライアントであるWebブラウザによって読み込まれ、処理される。
HTMLレンダリングエンジンには、次のようなものがある。
HTMLレンダリングエンジンWebブラウザ
Trident
  • Internet Explorer
  • Sleipnir
  • Craving Explorer
Gecko
  • Firefox
  • Epiphany(後にWebKitに移行)
WebKit
  • Google Chrome
  • Safari
HTMLには基本的に視覚的情報がない。また、HTMLレンダリングエンジンによって独自のCSSのプロパティがある。それゆえ、HTMLレンダリングエンジン毎に表示される画面が同じであるとは限らない。

WebKitライブラリ

オープンソースのHTMLレンダリングエンジンであるWebKitを使用したプログラミングのサンプルを以下に示す。

#!/usr/bin/python2.7

import gtk
import webkit

win = gtk.Window()
web = webkit.WebView()
web.open("http://www.google.co.jp")
win.add(web)
win.show_all()
Debian7.10では"python-webkit-dev"というパッケージをインストールすることで、環境を整えることができる。


電子メール(SMTP, POP3)

電子メールとは、コンピュータネットワークを使って、メッセージやファイルなどのデータの送受信を行う手段のこと。電子メールはメールやEメールなどと略される。本稿ではメールと記述する。

メールの機能は、メールサーバメールクライアントによって実装される。

メールクライアント
ユーザはメールクライアントによってメールサービスを受けることができる。メールクライアントの機能は、メールサーバに保存されているメールを受け取ったり、メールサーバに頼んでメールを送ってもらったりする。
メールサーバ
メールの転送は各メールサーバが行う。

メールの送信から受信までの流れ

メールの実装にはアプリケーション層のいくつかのプロトコルが使用される。

プロトコル機能ポート
POP3(Post Office Protocol version3)メールサーバからメールを受け取る(POPとIMAPそれぞれに利点と欠点がある)。tcp/110
IMAP(Internet Message Access Protocol)tcp/143, tcp/220
SMTP(Simple Mail Transfer Protocol)メールを転送する。tcp/25
あるユーザがメールを送信し、別のユーザがメールを確認するまでの流れは次の通りである。
メールサーバは、それぞれドメイン(例:pied-piper.net)を持つ。
  1. メールクライアントはSMTPクライアントとして、自身のドメインのメールサーバのSMTPサーバ機能に対して、送信要求を行う。
  2. SMTPサーバはメールの宛先が自身のドメインであれば、設定されたメールボックスにそれを追加する。それ以外の場合、その宛先ドメインのメールサーバのSMTPサーバ機能に対し、SMTPクライアントとして送信要求を行う。
  3. 受信するユーザは、POP3やIMAPクライアントでメールサーバにアクセスすることで、自身宛に届いたメールを受け取ることができる。

電子メールの仕組み

メールは5つのプログラム(機能)によって構成される。以下にそれらのプログラムとメールの流れを紹介する。

MUA(Mail User Agent)
いわゆるメールクライアントのこと。他にもメーラ、メールリーダなどと呼ばれる。
MTA(Mail Transfer Agent)
メールを別のメールサーバに送信したりMDAに渡したりするもの。
MDA(Mail Delivery Agent)
MTAから受け取ったメールをメールボックスに保存するもの。
MRA(Mail Retrieval Agent)
メールを受信するもの。
MSA(Mail Submission Agent)
MUAとMTAの間で認証などを行うもの。メールの仕組みができたばかりのころはなかったが、現在はセキュリティのためにMSAを間に置くのが主流。

メールクライアント

前述のHTTPのサンプルと同様に、telnetを使ってSMTPとPOP3のサーバにアクセスした例を以下に示す。

SMTP
user% telnet 192.168.1.101 25
Trying 192.168.1.101...
Connected to versus.
Escape character is '^]'.
220 pied-piper.net ESMTP
HELO pied-piper.net
250 pied-piper.net
MAIL FROM: me@pied-piper.net
250 ok
RCPT TO: user@pied-piper.net
250 ok
DATA
354 go ahead
From: me@pied-piper.net
Subject: test

Hello, User
.
250 ok 1397490184 qp 6800
QUIT
221 pied-piper.net
Connection closed by foreign host.
ちなみにSMTPの通信には,DATA以前に入力した情報を使用し,それ以降はメールヘッダのために使用する。 それゆえヘッダ部分は,偽装することも可能であるが,いくつかのSMTPサーバは,そのような怪しいメールの受け取りを拒否する。
POP3
user% telnet 192.168.1.101 110
Trying 192.168.1.101...
Connected to versus.
Escape character is '^]'.
+OK <6798.1397490101@pied-piper.net>
USER user
+OK
PASS hogehoge
+OK
STAT
+OK 125 3714019
LIST
+OK 
1 754
2 1846
3 782
RETR 1
+OK 
Return-Path: <user@pied-piper.net>
Delivered-To: user@pied-piper.net
Received: (qmail 6615 invoked by uid 0); 15 Apr 2014 00:22:33 +0900
Received: from unknown (HELO pied-piper.net) (192.168.1.111)
  by 192.168.1.101 with SMTP; 15 Apr 2014 00:22:33 +0900
From: me@pied-piper.net
Subject: test
Hello, User

.

QUIT
+OK 
Connection closed by foreign host.

SMTP,POP3プログラミング

SMTPやPOP3ライブラリを使用したサンプルコードを以下に示す。

SMTP
#!/usr/bin/python2.7

import smtplib,email.utils
from email.mime.text import MIMEText

FROM = "i@pied-piper.net"
TO = "you@example.pied-piper.net"

msg = MIMEText('This is the body of the message.')
msg['To'] = email.utils.formataddr(('You', TO))
msg['From'] = email.utils.formataddr(('O_Messiaen', FROM))
msg['Subject'] = 'Hello'

server = smtplib.SMTP('192.168.1.101', 25)
server.set_debuglevel(True) # show communication with the server
try:
    server.sendmail(FROM, [TO], msg.as_string())
finally:
    server.quit()
POP3
#!/usr/bin/python2.7

import poplib

pop3 = poplib.POP3("192.168.1.201", 110)
pop3.user("user")
pop3.pass_("hogehoge")
retr = pop3.retr(len(pop3.list()[1]))
"\n".join(retr[1])


セッション層

OSI参照モデル(7階層モデル)では,アプリケーション層とトランスポート層の間に,プレゼンテーション層とセッション層がある。セッション層では,通信の開始時や終了時などに送受信するデータの形式などを規定する。セッション層には例えばSSL(Secure Socket Layer)がある。SSLの詳細は省略するが,それは階層の上位のレイヤに対して暗号化や認証,完全性を提供する。昨今,httpやsmtp,pop3などのアプリケーション層のプロトコルを安全に利用するために,SSLを利用することは珍しくない。

以下にSMTP-SSLの利用例を示す。ここではopensslコマンドを使用する。opensslの機能はさまざまあり,引数としてコマンドを指定する。 以下の例では,s_clientを使用する。telnetやnc(netcat)がTCP(トランスポート層)での通信を提供するソフトウェアと考えるなら,opensslのs_clientコマンドはSSL(セッション層)での通信を提供するソフトウェアと考えれば良い。オプションの詳細については"man s_client"で閲覧できる。

user% openssl s_client -connect smtp.gmail.com:465 -crlf -ign_eof
CONNECTED(00000003)
depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
   i:/C=US/O=Google Inc/CN=Google Internet Authority G2
 1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2
   i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
 2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
   i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIEdjCCA16gAwIBAgIIGcMF7jeVMoAwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
   ー中略ー
    Verify return code: 20 (unable to get local issuer certificate)
---
220 mx.google.com ESMTP n7sm9349346pdl.90 - gsmtp
EHLO localhost
250-mx.google.com at your service, [153.198.XXX.XXX]
250-SIZE 35882577
250-8BITMIME
250-AUTH LOGIN PLAIN XOAUTH XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8
認証方法を入力する。
AUTH LOGIN
334 VXNlcm5hbWU6
AUTH LOGINで認証を行う場合,ユーザ名とパスワードをそれぞれbase64でエンコードしたものを使用する。ここでXXXXXXXXXXはユーザ名をエンコードしたもの,YYYYYYYYYYはパスワードをエンコードしたものである。
XXXXXXXXXX
334 UGFzc3dvcmQ6
YYYYYYYYYY
235 2.7.0 Accepted
エンコードは,次のようにbase64コマンドやopensslのencコマンドで行うことができる。また,opensslのencコマンドのマニュアルはs_clientと同様"man enc"で閲覧できる。
  • user% echo "me@gmail.com" | base64 
    bWVAZ21haWwuY29tCg=
  • user% echo "me@gmail.com" | openssl enc -e -base64
    bWVAZ21haWwuY29tCg=
パスワードのエンコードも同様である。XXXXXXXXXXやYYYYYYYYYYの部分にはこの結果を貼り付ければ良い。 これ以降は通常のSMTPと同じように行えば良いが,ここではあえてHTMLメールを送信する例を紹介する。
MAIL From: <me@gmail.com>
250 2.1.0 OK n7sm9349346pdl.90 - gsmtp
RCPT To: <you@gmail.com>
250 2.1.5 OK n7sm9349346pdl.90 - gsmtp
DATA
354  Go ahead n7sm9349346pdl.90 - gsmtp
Subject: html-mail
Mime-Version: 1.0;
Content-Type: text/html; charset="ISO-8859-1";
Content-Transfer-Encoding: 7bit;

<!DOCTYPE HTML>
<html lang="ja-JP">
<body>
<h1>こんにちわ</h1>
</body>
<html>
.
250 2.0.0 OK 1413388691 n7sm9349346pdl.90 - gsmtp
QUIT
221 2.0.0 closing connection n7sm9349346pdl.90 - gsmtp
read:errno=0

qmail

投稿日:
修正日:
修正日:
タグ:

本稿はqmail環境の構築に関するメモである。メールについては「コンピュータ・ネットワーク(6) -アプリケーション層-」を参照されたし。

qmail

qmailは、ダニエル・バーンスタインによって開発されたUNIX系OS向けのオープンソースのメールサーバである。本稿では、その中で現在の最新版であるnetqmail-1.06を使用する。

準備

はじめにqmailのソースコードやパッチを取得する。

ソースコード
netqmail-1.06
qmail-1.03にいくつかのドキュメントとパッチを加えたものである。
パッチ
qmail-103.patch512バイト以上のDNS応答のパケットを取り扱えるようにする。
qmail-date-localtime.patch表示されるDateタグをJST表示にする。
qmail-smtpd-relay-reject.patchユーザアカウント部分にいくつかの記号が入っている場合それを拒否する機能を追加。
圧縮されたqmailの解凍は次のようにして行う。
user% tar zxvf netqmail-1.06.tar.gz

インストール場所の用意

qmailでは、標準で/var/qmailに必要なファイルが配置される。そして、いくつかのユーザのホームディレクトリも/var/qmailに設定される。

user% sudo mkdir /var/qmail

ユーザとグループの追加

qmailでは複数のユーザやグループを使用する。そのためにユーザやグループの追加が必要である。

ユーザ名ホームディレクトリグループその他
alias/var/qmail/alias/nofilesエイリアス機能
qmaild/var/qmail/
qmaill
qmailp
qmailqqmail
qmailr
qmails
追加するユーザや追加方法は、展開されたファイルの1つINSTALL.idsを見れば良い。例えば、私の環境であるFreeBSDの場合、次のようにして行った。
user% pw groupadd nofiles
user% pw useradd alias -g nofiles -d /var/qmail/alias -s /sbin/nologin
user% pw useradd qmaild -g nofiles -d /var/qmail -s /sbin/nologin
user% pw useradd qmaill -g nofiles -d /var/qmail -s /sbin/nologin
user% pw useradd qmailp -g nofiles -d /var/qmail -s /sbin/nologin
user% pw groupadd qmail
user% pw useradd qmailq -g qmail -d /var/qmail -s /sbin/nologin
user% pw useradd qmailr -g qmail -d /var/qmail -s /sbin/nologin
user% pw useradd qmails -g qmail -d /var/qmail -s /sbin/nologin

インストール

もしパッチを当てる場合、コンパイルの前にパッチをソースがあるディレクトリに移動させ、以下のコマンドを実行する。

user% patch < ./qmail-date-localtime.patch
これによりソースコードを修正することができる。

そしてコンパイルとインストールには、makeコマンドを使用する。与える引数は次の通りである。

user% sudo make setup check
makeコマンドが正常に終了したならば、/var/qmailにさまざまなサブディレクトリが生成されているはずだ。

なおFreeBSD10ではutmp.hがないということでビルドエラーが発生した。これを解決するにはコードの修正が必要だが、誰かがパッチを作ったようだ。

設定

/var/qmail以下に生成されたいくつかのディレクトリについて説明する。

alias

エイリアス機能の設定ファイル群。標準では、/var/qmail/aliasはユーザaliasのホームディレクトリである。

設定ファイルは、.qmail-から始まるファイル名であり、メールを追加するファイルやディレクトリ、転送先アドレスが書いてある。

/var/qmail/mbox
mbox形式ファイルのパス。
./Maildir/
maildir形式で保存するディレクトリ
&user
転送先ユーザ。
|/bin/hoge
プログラムに与える場合のプログラムのパス。mbox形式のメールの中身が標準入力に入力される。

/var/qmail/aliasに届いたメールアドレスの設定ファイルがなく、かつユーザとして存在していれば、そのユーザのホームディレクトリにある.qmailの設定ファイルに従って処理を行う。また、"存在するユーザ名-"から始まる名前である場合、ホームディレクトリにある.qmail-から始まるファイルを検索し、あればその設定に従い処理を行う(例えば$HOME/.qmail-testならuser-test)。

必要不可欠なのかは未確認だが、いくつかのプログラムで以下の3つの設定ファイルが使用される。そのためこれらのファイルを作成し、設定を記述する必要がある。

  • /var/qmail/alias/.qmail-root
  • /var/qmail/alias/.qmail-postmaster
  • /var/qmail/alias/.qmail-mailder-daemon
なおこのファイルはaliasによって読み込まれるようである。

もし/var/qmail/alias/にmaildir形式のディレクトリを生成するならば、maildirmakeで生成できる。 ログイン可能な各ユーザが自分のホームディレクトリに生成するならば、次のように普通に使えば良い。

user% maildirmake ~/Maildir
しかし本稿ではaliasを非ログインユーザとして設定しているので、sudoを使用する。
user% sudo -u alias maildirmake ~/Maildir

control

制御ファイルの設定ファイル群。プログラムqmail-smtpdやqmail-send、qmail-inject、qmail-remoteなどによって使用される。以下にいくつかそれらのファイルを紹介する。

me
qmailが稼働するサーバのドメインの名前。
locals
qmailがローカル配送するドメインの名前。
defaultdomain
ドメインを省略した場合に付けられる標準のドメイン名。
plusdomain
メールアドレスに+と付けていた場合に置き換える文字列。
rcpthosts
qmail-smtpdが受信するSMTPのRCPT(宛先アドレス)のドメイン。

/var/qmail/controlを簡単に設定するには、添付されたスクリプトconfig-fastやconfigを使用する。例えばドメインpied-piper.netを前述の5つの設定ファイルに記述する場合、次のようにする。

user% sudo ./config-fast pied-piper.net
詳細については、ソースに添付されたINSTAL.ctlを参照されたい。

bin
プログラム群。以下にいくつかのプログラムを紹介する。
qmail-inject
メールを送信するためのコマンド。先頭から空行までがヘッダで、それからEOFまで本文である。
user% /var/qmail/bin/qmail-inject
To: user
Subject: hello

Hello, User
maildirmake
maildir形式のディレクトリを生成するコマンド。
user% /var/qmail/bin/maildirmake /etc/skel/Maildir
man
マニュアル群。
doc
ドキュメント群。
もし設定ファイルのバックアップを欲しいならば、少なくとも/var/qmail/aliasと/var/qmail/controlのバックアップが必要である。

起動

本稿では、qmailの起動にucspi-tcpを使用する。ucspi-tcpはqmailの開発者したサーバ制御ツールである。ucspi-tcpのtcpserverを使用すると不正中継の防止などを行うことで、セキュリティを向上させることができる。

インストールはqmailと同様このような感じで行うことができる。

user% tar zxvf ucspi-tcp-0.88.tar.gz
user% cd ucspi-tcp-0.88
user% sudo make setup check
tcpserverはtcprulesで生成したデータベースに基づいて処理を行う。

データベースの作成

tcpserverが使用するデータベースを生成するには、ソースファイルを生成し、それをコンパイルする必要がある。

データベースのソースは、各行に「左辺:右辺」といった感じで記述する。左辺にはアドレス、かネットワークを、右辺にはそれに対する設定を記述する。例えばアドレスの場合は"192.168.1.1"のように、ネットワークの場合"127.*.*.*"のネットワークを示したい場合、"127."と記述する。以下にサンプルを示す。

  • 192.168.:allow,RELAYCLIENT=""
    127.:allow,RELAYCLIENT=""
    :allow
    
  • 192.168.:allow,RELAYCLIENT=""
    192.169.:deny
    
もし環境変数RELAYCLIENTを設定した場合、controlのrcpthostsは無視され、RELAYCLIENTの値が使用される。

データベースのコンパイルは次のようにして行う。

user% tcprules tcp.smtp.cdb  tcp.smtp.tmp < tcp.smtp
以上まででtcp.smtp.cdbという名前のデータベースファイルが生成される。

サーバの起動

qmailサーバの起動には、次の3つを起動する。

  • qmail-start
  • qmail-smtpd
  • qmail-pop3d
これらを起動するためのスクリプトのサンプルは次の通りである。
#!/bin/sh

# Start qmail-start
exec env - PATH="$PATH:/var/qmail/bin" qmail-start ./Maildir/ \
splogger qmail &

# Start qmail-smtpd
/usr/local/bin/tcpserver -x /var/qmail/tcp.smtp.cdb -u qmaild \
-g nofiles 0 smtp /var/qmail/bin/qmail-smtpd &

# Start qmail-pop3d
/usr/local/bin/tcpserver -x /var/qmail/tcp.pop3.cdb \
0 pop3 /var/qmail/bin/qmail-popup \
pied-piper.net /bin/checkpassword /var/qmail/bin/qmail-pop3d Maildir &
本稿の構築方法ではログにはsyslogを使用している。もし何かトラブルが発生したならば、まず/var/log/maillogを見れば良いだろう。


トラブルシューティング(というか実際あったトラブル)

deferral: CNAME_lookup_failed_temporarily._(#4.4.3)/
  • ネットワークの構成を変えた際に/etc/resolv.confの内容に誤りがあった。

Pythonの特徴

投稿日:
修正日:
タグ:

以前少しだけPythonを触ったが、今回改めてちゃんと覚えようと勉強している。本稿はその学習のメモやまとめ、特に本稿ではPythonの特徴についてのまとめである。学習には主に次の2つの情報源を使用した。

Pythonの方針は次の通りである。


オンラインマニュアル

Pythonは、さまざまなバージョンの公式のオンラインマニュアルが用意されている(例えば2と3に互換性がない)。また、その内容もしっかりしており、いくつかのプログラミング言語で曖昧としているようないくつかの細かな仕様がしっかり規定されている。それゆえ非常に学習し易い。


開発者にとって便利な仕様

Pythonにはコードリーディングやコードライティングを助けるための仕様がいくつもある。以下にそれらをいくつか紹介する。

組み込み関数

Pythonには、コーディングやデバッギング助ける関数がある。また、Pythonではドキュメントの読み書きを行う共通のインタフェースがあり、ドキュメントを読むための関数がある。

  • locals関数
  • globals関数
  • dir関数
  • help関数

locals関数
globals関数
locals関数はローカルの名前空間の、globals関数はグローバルの名前空間の名前(識別子)の一覧を返す。これらの関数はデバッグの際に有効である。例えば次のような関数があったとする。
def func():
  v = 100
  w = "hoge"
  print(locals())
この時、ローカルの名前空間の名前一覧を画面に出力すると次のようになる。
{'x': 'hoge', 'w': 100}
次にglobals関数を使った対話的処理を以下に示す。
user% python3
>>> globals()
{'__builtins__': <module 'builtins' (built-in)>, '__name__': '__main__',
'__doc__': None, '__package__': None}
>>> import sys
>>> v = 0
>>> globals()
{'__builtins__': <module 'builtins' (built-in)>, '__package__': None,
'sys': <module 'sys' (built-in)>, 'v': 0, '__name__': '__main__',
'__doc__': None}
help関数
Pythonには、モジュールやクラス、関数などのドキュメントを読み書きするための共通のインタフェースがある。そしてドキュメントを読むのに使用するインタフェースがhelp関数である。help関数は、環境変数PAGERが設定されていればそのページャを使用する。全てのモジュールやクラスなどにドキュメントをしっかり設定していれば、help関数の使い方さえ覚えていれば、他の関数の使い方を知らなかったり忘れたりしていてもどうにかなるだろう(help関数自身もドキュメントはあるが)。ドキュメントの書き方については、「ドキュメンテーション文字列」で説明する。
help(dir)
Help on built-in function dir in module builtins:

dir(...)
    dir([object]) -> list of strings
    
    If called without an argument, return the names in the current
    scope.
    Else, return an alphabetized list of names comprising (some of) the
    attributes
    of the given object, and of attributes reachable from it.
    If the object supplies a method named __dir__, it will be used;
    otherwise
    the default dir() logic is used and returns:
      for a module object: the module's attributes.
      for a class object:  its attributes, and recursively the
    attributes
        of its bases.
      for any other object: its attributes, its class's attributes, and
        recursively the attributes of its class's base classes.
オブジェクトを指定した場合、その型のドキュメントが表示される。
dir関数
指定したモジュールやクラス、インスタンスなどの名前空間の名前一覧を返す関数である。もし引数を省略した場合は現在のスコープの名前一覧を返す。これを使用すれば、モジュールに含まれたクラスの名前や、クラス内のメンバの名前を調べることができる。そして、名前さえ分かればそれをhelp関数で詳細を調べることができる。例えばsysモジュール内の名前を調べるにはこのようにする。
>>> import sys
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__',
'__package__', '__stderr__', '__stdin__', '__stdout__',
'_clear_type_cache', '_current_frames', '_getframe', '_mercurial',
'_xoptions', 'abiflags', 'api_version', 'argv', 'builtin_module_names',
'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook',
'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix',
'executable', 'exit', 'flags', 'float_info', 'float_repr_style',
'getcheckinterval', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencoding', 'getprofile', 'getrecursionlimit',
'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace',
'hash_info', 'hexversion', 'int_info', 'intern', 'maxsize',
'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2',
'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit',
'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout',
'subversion', 'version', 'version_info', 'warnoptions']
class Cls:
  v = 100
  w = "hoge"
この時dir(Cls)は次のような値を返す。
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__', 'v', 'w']

ドキュメンテーション文字列

優れたコードには可読性を向上させるためのコメントが、そしてライブラリにはその使い方を正しく理解するためのコメントやマニュアルがある。前述の通り、Pythonにはそれらをサポートするための仕様が備わっている。ドキュメンテーション文字列は、そのようなドキュメントを書くためのインタフェースである。ユーザが関数やクラス、モジュールを定義する場合、ドキュメンテーション文字列を書くことで前述のhelp関数を使ってドキュメントを読むことができる。

関数アノテーション

Pythonでは、関数アノテーションを用いて関数の引数や返り値の説明を記述できる。例えば、次のコードは、関数sumの返り値と第1引数addee、第2引数adderの説明を記述している。

def sum(addee:"被加数", adder:"加数") -> "和":
  return addee + adder
この関数は次のような実行結果となる。
>>> sum(1, 4)
5
関数のドキュメントは、help関数で閲覧できる。
>>> help(sum)
Help on function sum in module __main__:

sum(addee: '被加数', adder: '加数') -> '和'

ドックストリング

Pythonでは、ドックストリングによって関数やクラス、モジュールの説明を記述できる。ドックストリングの記述するには、スイート内の文が始まる前に文字列リテラルを置く。

def sum(addee:"被加数", adder:"加数") -> "和":
  """
  2つの値を受け取ってその加算結果を返す
  """
  return addee + adder
Python文字列リテラルの書き方は4つある。
文字列引用符説明
'string'一重引用符
"string"二重引用符
'''string'''三連一重引用符エスケープなしで改行や引用符を書くことが可能。
"""string""" 三連二重引用符
Help on function sum in module __main__:

sum(addee: '被加数', adder: '加数') -> '和'
    2つの値を受け取ってその加算結果を返す

ドキュメンテーション文字列は、共通化されたコメントのようなもので省略可能である。しかし、省略してもあまりメリットはないだろう。

名前(識別子)

Pythonの名前(識別子)に使用できる文字は、次のいずれかである。

  • 大文字アルファベット
  • 小文字アルファベット
  • アンダースコア(_)
  • 数字
  • ASCII以外の文字(詳しくはPEP-3131を参照されたし)
Cでは、ASCII文字以外の文字を使用できないが、Pythonでは、日本語名の変数や関数などが定義できる。なおCと同様に先頭文字に数字は使用できない。

また、Cでは次の名前はいくつかのライブラリや言語処理系で予約されている。

  • _が接頭辞のグローバルな名前
  • _が接頭辞でそれに大文字の名前
  • __が接頭辞の名前
これは言語的な制約ではなく、名前の競合を防ぐための注意やマナーである。

他にも、Cでは、ユーザが定数を全て大文字にしたり、グローバル変数の接頭辞をg_にしたりすることで、定義した場所以外でもそれらの意図を分かりやすくなるような工夫がある。これはコードの可読性を上げ、コードリーディングを助ける。Pythonには言語仕様で特別な意味を持つ名前があり、その分可読性が高い。

  • _で始まる名前は、モジュールから*(ワイルドカード)でインポートする場合に除外される。
  • クラス内の__で始まるメンバの名前はクラス内でだけ有効である。
なお2つ目のルールは、実際には呼び出せない訳ではなく、Pythonによって名前が変更され、オリジナルの名前でアクセスできない状態となる(それゆえdir関数で名前を調べられる)。

これに加えて、Pythonでメンバ関数を定義する場合、インスタンス変数へのアクセスは、メンバ関数の第一引数を通じて行う。

class Cls:
  def set(self, val):
    self.v = val
  def get(self)
    return self.v
そのため、他の変数(ローカル変数やクラス変数など)との区別がしやすいだろう。

文法

Cでは、空白文字(スペースや改行、タブ)に字句の区切り以外の意味はない。そして、文は;(セミコロン)までを1文として、ブロックは{と}を使って表す。それゆえ"Hello, World"を画面に出力するためのプログラムは次のように記述できる。

int printf(const char *, ...);int func(){int
i=0;while(i<3){printf("Hello, World\n");i += 1;}return 0;}
しかし多くのユーザは、次のような工夫によってコードの可読性を挙げる。
  • 1文を1行に記述して文の区切りを分かりやすくする。
  • 同じブロックの文を同じサイズのインデントにしてブロック階層を分かりやすくする。
これを適用すると、前述のコードは次のように記述できる(改行の位置はユーザのスタイルによって異なる)。
int printf(const char *, ...);
int func()
{
  int i=0;
  while(i<3){
    puts("Hello, World");
    i += 1;
  }
  return 0;
}
Pythonでは、言語仕様によって改行やインデントで文やブロックの区切りを表すように定められている。それゆえ、自然と可読性の高いコードとなる。以下に前述のコードをPythonで記述したものを紹介する。
def func():
  i = 0
  while(i<3):
    print("Hello, World")
    i += 1
  return 0

いくつかの言語には、データのコンセプトを表すがある。よくある組み込み型には、整数や小数、文字を表すものがある。Pythonには、これらの基本的な型に加えて開発者向けのコンセプトを表す型がある。

NotImplemented

Pythonには、未実装を表す型としてNotImplementedがある。この型の値は1つだけで、かつこのオブジェクトは全て同一である。また、評価結果は常に真となる。

def func():
  return NotImplemented

Ellipsis

Pythonには、省略を表す型としてEllipsisがある。この型の値は1つだけで、かつこのオブジェクトは全て同一である。また、評価結果は常に真となる。

def func():
  return Ellipsis

None

Cのvoidに該当する型として、Pythonには値が存在しないことを表すNone型がある。Cのvoid型がオブジェクトも値も持っていないのに対し、None型のオブジェクトは1つだけあり、値も1つ(None)だけある。それゆえ、引数としてNoneを指定でき、返り値もなにも返さない訳ではなくNoneを返す。Pythonの関数がreturn文に到達しなかった場合、その関数はNoneの値を返す。

def func():
  pass

print(func())
Cでは、返り値を持たない関数が存在する。それらはサブルーチンのような意図で記述されるものだが、定義上それは関数である。しかし、Pythonには値がないというコンセプトのデータを返すことで、全ての関数が返り値を持つ。

Noneの値はCのNULLポインタに該当し、評価結果は常に偽となる。


最適化のために

多くのスクリプト言語では、書き方の柔軟性や多様性、利便性などに優れている。Pythonもその1つだが、Pythonはそれに加えてプログラムの処理速度を向上させるようないくつかの工夫がある。以下にいくつか紹介する。

tuple

Pythonには、組み込みシーケンス型のコンテナが2つある。

  • list
  • tuple
この2つの型は基本的にほとんど性質を持つが、tupleがイミュータブルで、listがミュータブルであるという違いがある。

tupleを使用する利点は以下の2つである。

  • ハッシュ(辞書型)のキーとして利用できる。
  • list型より速い。
v = {(1,1):0, (1,2):2, (3, 5):4}
v[(1, 2)]

比較演算

Pythonの比較演算子は以下の通りである。

  • <
  • <=
  • ==
  • !=
  • >=
  • >
  • is
  • is not
  • in
  • not in
Cとは異なり、比較演算子の優先順位は全て同じである。また、Cでは比較演算子は2つの項を受け取り1つの値を返す単純なものであるが、Pythonではブール演算のように左から右に連鎖する。例えばこのように記述する。
10 > func() >= 3
これがCのコードならば、まず"10 > func()"を評価し、その結果として1か0が返され、それと3を>=で比較する意味となる(すなわち常に0になる)。もし範囲を表したいコードを書きたいならば、次のようなコードとなる。
10 > func() && func() >= 3
しかしこのコードはfunc()が2回評価される。 それゆえ、呼び出し毎に返り値が異なる場合に問題となる。また、2回呼び出される分冗長である。これを避けるため、Cでは次のように記述する。
10 > (tmp=func()) && tmp >= 3
これに対してPythonはこのように記述するだけで良い。
10 > func() >= 3
この時、"10 > func()"の評価結果がFalseの場合は処理を終了してその結果を返し、Trueの場合、func()の結果と3を>=演算で比較し、その結果(TrueかFalse)を返す。それゆえコードの可読性が高いだけでなく、効率が良い。


注意点

Cっぽい言語としてPythonを使おうとすると,いくつかの落とし穴がある。前述に紹介した比較演算などがそれである。また,わかりづらい表現も1つでない。ここでは,それらのいくつかを紹介する。

除算

Pythonの除算には2種類がある。型を持つ多くの言語では,整数同士の除算の場合,結果は整数を返し,余りはモジュロ演算によって計算される。これに対し,Python3の場合は次のような除算結果となる。

>>> 5/2
2.5
>>> 5//2
2
なお前述の結果はPythonのバージョンが3の結果で,2の場合は以下のようになる。
>>> 5/2
2
>>> 5//2
2
>>> 5.0//2
2.0
>>> 5.0/2
2.5

また,計算機の世界では符号付き除算の結果は,言語や実行環境に依存する。Pythonの場合,それは言語の仕様として定義されている。ここで気をつけないといけないのは,数学などで習う結果とは異なる結果を返す点である。 Pythonのモジュロ演算の結果は、0か符号が常に除数(この場合y)と同じになる。モジュロ演算の結果の絶対値は、常に除数以下になる。また、Pythonでは、切り捨て除算演算結果と除数の積に剰余を加えた値は、元の値と常に等しい。 以下に例を示す。

切り捨て除算演算除算結果モジュロ演算剰余
5//225%21
-5//-22-5%-2-1
-5//2-3-5%21
5//-2-35%-2-1

2進数

Pythonで2進数を扱う場合,いくつかの注意が必要である。Pythonは負の値を表現するのに2の補数が使用される。それゆえ,ビット反転と加算を使うと次のように符号を反転できる。

>>> 5
5
>>> (~5)+1
-5
>>> ~5
-6
それにも関わらず,負の値の時,2の補数の文字列を返す関数は,次のような結果を返す。
>>> bin(5)
'0b101'
>>> bin(-5)
'-0b101'
同様に2進数のリテラルを記述する場合,マイナスを表現するには次のように表現する。
>>> 0b101
5
>>> -0b101
-5


デコレータ

いくつかの言語では、次のような関数やクラスのための関数やクラスを定義できる。

  • 関数やクラスを受け取りその振る舞いを変更する関数
  • クラス生成の振る舞いを変更するクラス
Pythonでは、このようなものを記述するための仕様がある。これはコードの意図を分かりやすくし、可読性を向上させる。

関数デコレータ

Pythonには、関数に機能を明示的に追加または変更するための機能がある。それが関数デコレータである。Pythonを含むいくつかの言語では、関数を別の関数に渡すことができる。これを利用すれば関数デコレータと同様のコードを記述できるが、関数デコレータを使用すればその意図を明示的に表すことができる。関数デコレータは次のように定義し、使用できる。

def decorator_called(f):
  def wrapper():
    print("called ", f.__name__)
    return f()
  return wrapper

@decorator_called
def hello():
  print("Hello, World")
hello関数を呼び出した結果を以下に示す。
called  hello
Hello, World
このように関数デコレータは、関数定義の際に生成する関数に対して機能の追加や変更ができる。

関数デコレータには以下のようなものが用意されている。
  • @staticmethod
  • @classmethod
  • @abstractmethod
ちなみにabstractmethodはabcモジュールで定義されている。

クラスデコレータ

デコレータは関数だけでなく、クラスにも適用できる。これをクラスデコレータという。関数デコレータのようにクラスデコレータは、クラス定義の際にクラス情報を受け取り処理を加えたクラスを生成できる。

import sys
def py_major_version(major):
  def wrap(cls):
    if sys.version_info[0] < major:
      raise RuntimeError("version error")
    return cls
  return wrap

@py_major_version(3)
class Hoge:
  pass
このコードは、Pythonのメジャーバージョンが2以下の場合に例外を発生させる。 Python2の場合このコードは問題なく実行できるが、python2で実行すると次のような結果となる。
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in wrap
RuntimeError: version error
クラスデコレータは、クラスが定義された(クラスオブジェクトが生成された)後で呼び出される。これに対し、クラスの生成方法そのものを変更する方法がある。それをメタクラスという。

メタクラス

メタクラスは、クラスを生成するためのクラスである。

def ham(self, arg):
  print(arg)

class MetaSpam(type): #組込み型typeを基に
  @classmethod
  def __prepare__(metacls, name, bases):
    print(name)
    print(bases)
    return {'ham':ham}

class Hoge(metaclass=MetaSpam):
  pass
メタクラスのメンバ関数
関数説明
__prepare__(metacls, name, bases)classブロックの実行前に呼び出されるクラスメソッド。ブロックに含まれる変数やメソッドの名前とオブジェクトの辞書を返す。
__init__(cls, bases, classdict)クラスオブジェクトの初期化
__new__(metacls, bases, classdict)クラスオブジェクトの生成
変数名説明
nameクラス名
bases基底クラスを格納したタプル
clsクラス
metaclsメタクラス
classdictクラスのメンバを格納した辞書
メタクラスは次のように指定する。
  • class クラス名(metaclass=メタクラス)
  • class クラス名(親クラス, metaclass=メタクラス)
  • class クラス名(metaclass=メタクラス, 引数名=引数)

用意されたメタクラスには次のようなものがある。

abc.ABCMeta仮想サブクラス実際には継承関係にないクラスの基底クラスとなれる


ライブラリ

Pythonには、標準や外部を含め、実にさまざまなライブラリがある。以下にいくつか紹介する。

モジュールサンプルコード
sys
import sys
print(sys.argv)
os
import sys
import os

r, w = os.pipe()
pid = os.fork()
if pid:
  os.close(w)
  out = os.read(r, 10)
  while True:
    buf = os.read(r, 10)
    if buf:
      out += buf
    else:
      break
  os.write(sys.stdout.fileno(), out)
  os.close(r)
else:
  os.close(r)
  os.dup2(w, sys.stdout.fileno())
  os.close(w)
  os.execlp("ls", "ls", "-l")
re
import re
m = re.match("^a(.*?)o", "aiueo")
print(m.group(1)) # iueを抽出
gtk
import gtk

win = gtk.Window()
win.show_all()
webkit
import gtk
import webkit

win = gtk.Window()
web = webkit.WebView()

web.load_uri("http://www.google.co.jp")
win.add(web)
win.show_all()
GTKとWebKitはPython2で行なっている。なお各ライブラリ(モジュール)の使い方は、help関数を使って調べることができる。

Pythonの基本

投稿日:
タグ:

本稿は私のPython学習のメモやまとめ、特にPythonの基本をまとめたものである。学習には主に次の2つの情報源を使用した。


単純文

単純文とは、単一の論理行内に収められる文のことである。

代入文

いくつかの言語では、代入や複合代入は演算の1つであり、式文として記述される。しかしPythonでは、それらは文として独立している。とはいえ、以下のような単純な代入ではその違いは感じないだろう。

v = 5
しかし、代入演算のように代入の演算結果を利用しようとすれば、エラーとなる。
v = (w = 5) 
とはいえ次のように代入した変数の値を別の変数に代入するのは、当然可能である。
v = w = 5
また、Pythonでは次のようにカンマで区切って複数の変数を1文で代入することが可能である。
v, w = 5, 10
この文では、vに5を、wに10を代入している。代入文は項数に限らず、まず右辺の式のリストを評価し、その後ターゲットのリストの左から右へ順番に代入していく。すなわち次の式はvにwの値を、wに元のvの値を代入する。
v, w = w, v

代入とは名前(識別子)をオブジェクトに束縛、または再束縛することである。代入文では既にその名前が束縛されている場合、オブジェクトの参照カウントを1つ減らす。もしオブジェクトの参照カウントが0になった場合、オブジェクトは解放され、そのデストラクタが呼び出される。

>>> class Hoge:
...     def __del__(self): # デストラクタ
...             print("release")
... 
>>> v = Hoge()
>>> v = 0
release
>>> x = y = Hoge()
>>> x = 1
>>> y = 2
release

del文

del文は指定した名前の束縛を解除し、オブジェクトの参照カウントを1つ減らす。もしオブジェクトの参照カウントが0になった場合、オブジェクトは解放され、そのデストラクタが呼び出される。

del x
もし名前が未束縛ならば、NameError例外が送出される。

また、del文はカンマで区切って複数の名前を指定できる。その際、ターゲットは左から右に順に再帰的に削除される。

式文

式文とは演算を行う文のことである。Pythonの演算子の種類と計算順序は以下を参照されたし。

演算子の優先順位

演算子の優先順位は表の上に行くほど高く、下に行くほど低い。同じ優先順位を持つ場合、結合/連鎖に従って評価順が決まる。

演算子機能結合/連鎖
  • (expressions...)
  • [expressions...]
  • {key:value...}
  • {expressions...}
  • x[y]
  • x[y:z]
  • x.y
  • func([arg ...])
→結合
  • x ** y
べき数←結合
  • +x
  • -x
  • ~x
単項演算子
  • x * y
  • x / y
  • x // y
  • x % y
/は除算演算で、//は切り捨て除算演算である。除算演算は小数まで計算する。モジュロ演算の結果は、0か符号が常に除数(この場合y)と同じになる。モジュロ演算の結果の絶対値は、常に除数以下になる。また、Pythonでは、切り捨て除算演算結果と除数の積に剰余を加えた値は、元の値と常に等しい。 →結合
  • x + y
  • x - y
  • x << y
  • x >> y
算術シフト演算
  • x & y
ビット単位AND
  • x ^ y
ビット単位XOR
  • x | y
ビット単位OR
  • x in S
  • x not in S
  • x is y
  • x is not y
  • x < y
  • x <= y
  • x == y
  • x != y
  • x >= y
  • >
比較演算。"is"や"is not"は同一性の比較で、"in"や"not in"はデータ構造の帰属のチェックの演算である。また、比較演算はオーバーロードされている。以下にいくつかの型の場合を紹介する。
機能
数値数学的に大小を比較。
bytes辞書順に大小を比較。
文字列各文字の文字コードの大小を順に比較。
リスト各要素を順に比較し要素の大小を比較。
setスーパセットやサブセットの判断。
→連鎖
  • not x
論理否定
  • x and y
論理積→連鎖
  • x or y
論理和
  • x if C else y
条件演算(3項演算)
lambda [x, ...] : expression ラムダ式。
v = lambda x, y : x + y
v(1, 2)


break文

break文はwhile文やfor文などのループを終了させる。もしelse節があってもそれは無視される。

continue文

continue文はwhile文やfor文などのループの次の周期の処理へ移動させる。


return文

return文は関数定義内で現れ、制御を関数呼び出し側に戻して指定した値を、式を指定した場合はその評価結果の値を返す。

def func():
  return 5
返り値は省略可能で省略した場合はNoneが返される。また、return文がなく関数の最後まで行った場合も同様である。return文はジェネレータ関数の中では記述できない。

yield文

ジェネレータ関数では、return文の代わりにyield文を使用する。というよりもyield文を使用した関数定義はジェネレータ関数となる。ジェネレータ関数でreturn文を使用するとエラーとなる。ジェネレータ関数はジェネレータオブジェクトを生成する。例えばこのようなジェネレータ関数を定義したとする。

def myGenerator():
  i = 0
  while i < 5:
    yield i
    i += 1
このコードはジェネレータオブジェクトの__next__関数で呼び出され、yield文までが処理される。そして次に__next__関数が呼び出されると、yield文の次の文から処理され、関数定義の末尾か次のyield文まで実行される。
>>> for x in myGenerator():
...   print(x)
... 
0
1
2
3
4
ジェネレータ関数の詳細は「ジェネレータ関数」で述べる。


global文

global文は指定した名前(識別子)をグローバル変数として解釈するようにする文である。global文にはカンマで区切って複数指定できる。

global文なしglobal文あり
str = "global"
class Cls:
  str = "instance"
  def func1(self):
    pass
    str = "func"
    print(str)
  def func2(self):
    print(str)
  def func3(self):
    print(self.str)
str = "global"
class Cls:
  str = "instance"
  def func1(self):
    global str
    str = "func"
    print(str)
  def func2(self):
    print(str)
  def func3(self):
    print(self.str)
>>> c = Cls()
>>> c.func1()
func
>>> c.func2()
global
>>> c.func3()
instance
>>> c = Cls()
>>> c.func1()
func
>>> c.func2()
func
>>> c.func3()
instance

nonlocal文

関数内の名前(識別子)のスコープのことをローカルスコープという。Pythonでは、関数内で別の関数を定義できるため、内側と外側の関数で名前の競合が発生する。nonlocal文は指定した名前(識別子)が最も近傍のローカルスコープで以前に束縛された変数を参照するようにする。

nonlocal文なしnonlocal文あり
def exFunc():
  str = "ex"
  def inFunc():
    pass
    str = "in"
    print(str)
  inFunc()
  print(str)
def exFunc():
  str = "ex"
  def inFunc():
    nonlocal str
    str = "in"
    print(str)
  inFunc()
  print(str)
>>> exFunc()
in
ex
>>> exFunc()
in
in


raise文

指定した例外を送出する。

def div(x, y):
  if y==0:
    raise ZeroDivisionError
  return x // y
何も指定しなかった場合、現在のスコープで最終的に有効になっている例外を再送出する。もしもそのような例外がない場合TypeError例外が送出される。

例外はtry文で受け取り処理を行うことができる。例外については「try文」で詳述する。

assert文

条件が偽だった場合にAssertionErrorを送出する文である。 例えばこのような関数を定義したとする。

def test(left, right):
  assert left < right
条件が真の場合と偽の場合の結果は以下のようになる。
>>> test(1, 3)
>>> test(3, 1)
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 2, in test
AssertionError


pass文

pass文は意味のない文であり、構文上何か必要な場合に記述する。例えば、関数定義で処理内容を記述しない場合はこのように書く。

def func(): pass


import文

import文はモジュールのロードに使用する文である。モジュールの読み込み先ディレクトリは、sysモジュールのpathで定義されている。

import文には、asやfromキーワードの組合せでいくつかのパターンがある。以下にいくつか例を示す。

  • import sys
    sys.path
  • import sys as hoge
    hoge.path
  • from sys import path
    path
  • from sys import *
    path
*を指定した場合、_を接頭辞とする名前を除く全てのメンバをロードする。ただし、モジュールに__all__という名前のメンバがあれば、__all__に含まれる名前のメンバのみがロードされる。

import文では、ロードするモジュールは、カンマで区切って複数記述できる。また、指定したモジュールがなかった場合、ImportError例外が送出される。

モジュール

Pythonスクリプトを記述し、ライブラリとして利用できるようにしたファイルをモジュールという。モジュールとモジュールを利用したコードのサンプルを以下に示す。 なおhoge.pyで記述したドキュメントを記述した文字列については、「ドキュメンテーション文字列」で後述する。

hoge.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"サンプルモジュールを定義"
class Hoge:
  "サンプルモジュールのクラス"
  def add(self, addee:"被加数", adder:"加数") -> "和":
    "サンプルモジュールのメンバ関数"
    return addee + adder
fuga.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import hoge
h = Hoge()
print(h.add(1, 2))
hoge.pyについて記述したドキュメントは、import後にhelp(hoge)と指定することで閲覧できる。

パッケージ

全ての機能を1つのモジュールに集約するのではなく、複数のモジュールを記述し、それを一括にロードする方法がある。それがパッケージである。パッケージはモジュールと同様にimport文でロードできる。パッケージとは普通のディレクトリであり、必ず__init__.pyというファイルが格納されている。__init__.pyは存在していれば中身が空でも構わない。パッケージの内容は__init__.pyの中身となる。すなわち、"hoge/"というディレクトリのパッケージがあるとする。この時、"import hoge"と行うとロード対象は"hoge/__init__.py"の中身となる。


複合文

複合文とは、2行以上の複数の論理行に収められる文のことである。

if文

if文は分岐文である。if文には次の3つのキーワードがある。

  • if
  • elif
  • else
Cではifとelseしかなく、複数の条件を書く場合はelseの中にifを書いていたが、Pythonではelif節というものが存在する。if文は1個のif節と、1個か0個のelse節、0個以上のelif節からなる。文法的に、はじめにif節、次にelif節、最後にelse節を書く。 elifはifが偽の時に処理され、複数のelifがある場合は上の方が先に評価される。
def func(i):
  if i < 10:
    print("10未満である")
  else:
    print("10以上である")

while文

while文は繰り返し文の1つである。

while 条件式:
  処理1
[else:
  処理2]
はじめに条件式が評価され、その結果が真であれば、処理を実行する。その後、処理が終了する度に条件式が評価され、偽になるまで処理を続ける。

また、Pythonのwhile文にはelse節がある。

for x in 1, 2, 3, 4:
  print(x)
else:
  print("loop end")
もしループの条件式の評価結果が偽となった場合、ループが終了した後に実行される。ただしbreak文で終了した場合、else節はスキップされる。

for文

for文は繰り返し文の1つである。Pythonでは、イテレータを使用し各要素に何らかの処理を行うような場合にfor文を使用する。

for ターゲットリスト in 式リスト :  
  処理1
[else:
 処理2]
ターゲットリストや式リストは次のように記述する。
>>> for x in 0,1,2,3:
  print(x)
また、以下のようにターゲットリストには複数の変数を指定できる。
>>> for x, y in [0,10], [5,20], [10,30]:
...     print(str(x) + "," + str(y))
... 
0,10
5,20
10,30
各式は最初に1度だけ評価され、イテレータが生成される。例えば式リストに組み込み関数rangeを使用し、"range(1, 5)"と記述した場合、"[1, 2, 3, 4]"となり、各要素が順に代入される。
>>> def func():
...     print("hoge")
...     return 1
... 
>>> for x in func(), func():
...     print("fuga")
... 
hoge
hoge
fuga
fuga

Pythonのfor文にはelse節がある。もしループの条件式の評価結果が偽となった場合、ループが終了した後に実行される。break文で終了した場合、else節はスキップされる。


with文

with文を使用すると、開始処理と終了処理があるような場合に、それを明示的に表現した簡潔なコードを記述できる。

with open("./write.txt", "w") as f:
  f.write("Hello\n")
  f.write("ハロー\n")
  f.write("はろー\n")
with文では、処理の最初に__enter__関数が、最後に__exit__関数が呼び出される。with文で__enter__関数の処理が正常に実行されたならば、__exit__関数が呼び出されることが保証される。 もしwithのスイート内で例外が送出したら、型と値、トレースバックが__exit__関数の引数に与えられる。それ以外の場合、各変数にはNoneが代入される。また、__exit__関数がTrueを返さなかった場合、with文は例外を送出する。__enter__関数と__exit__関数をオーバーライドする例を以下に示す。
class Cls:
  def __enter__(self):
    print("begin")
    return self
  def __exit__(self, type, value, traceback):
    print("end")
    return True
  def func(self, str):
    print(str)
実行結果は次の通りである。
>>> with Cls() as c:
...     c.func()
... 
begin
end
また、次のように複数の束縛を行う場合、カンマで区切って簡潔に記述できる。
with open("./write.txt", "w") as w
  with open("./read.txt", "r") as r:
    print(r.read(), file=w)
with open("./write.txt", "w") as w, open("./read.txt", "r") as r:
  print(r.read(), file=w)


関数定義

関数定義はdefキーワードを使用する。

def add(x, y):
  return x + y
定義した関数は関数呼び出し演算で呼び出すことができる。

引数
デフォルト引数
関数定義では、引数を省略した際に代入されるオブジェクトを指定できる。
>>> def func(str = "default"):
...     print(str)
... 
>>> func("hoge")
hoge
>>> func()
default
可変引数
Pythonでは、引数の数が任意個数の関数を定義できる。
*arg
タプル型で順に格納される。
>>> def func(arg1, *args):
...     print(args)
...
>>> func(0,1,2,3,4,5)
(1, 2, 3, 4, 5)
**arg
辞書型で格納される。
>>> def func(arg1, **args):
...     print(args)
... 
>>> func(0)
{}
>>> func(0, v=1, w=2)
{'w': 2, 'v': 1}
引数名指定
関数呼び出しで引数を渡す際、引数の名前を指定して渡すことができる。
>>> def add(addee, adder):
...     return addee + adder
... 
>>> add(adder=10,addee=5)
15
関数デコレータ

関数デコレータとは、関数に機能を明示的に追加または変更するための機能である。関数定義は1つ以上の関数デコレータでラップできる。

def decorator_called(f):
  def wrapper():
    print("called ", f.__name__)
    return f()
  return wrapper

@decorator_called
def hello():
  print("Hello, World")
hello関数を呼び出した結果を以下に示す。
called  hello
Hello, World
このように関数デコレータは、関数の生成の際に関数を受け取りそれに何らかの処理を加えて関数を呼び出す。

ジェネレータ関数

ジェネレータ関数はイテレータの一種で、ジェネレータオブジェクトを作成する関数である。Pythonでは、関数定義でreturn文の代わりにyield文を記述することでジェネレータ関数を定義できる。

def fib():
  x, y = 0, 1
  while True:
    yield x
    x, y = y, x + y
ジェネレータのコードは、はじめに関数のコードの頭からyield文まで処理し、次に呼び出した際はyieldの次の文からyield文まで処理する。ジェネレータのコードは、__next__関数を呼び出すことで処理される。
>>> f = fib()
>>> f.__next__()
0
>>> f.__next__()
1
>>> f.__next__()
1
>>> f.__next__()
2
>>> f.__next__()
3
>>> f.__next__()
5
このジェネレータオブジェクトをfor文で指定すると、無限に処理され続ける。これを改良したコードを以下に示す。
def fib(max):
  x, y = 0, 1
  while x < max:
    yield x
    x, y = y, x + y
これを実行すると次のようになる。
>>> f = fib(10)
>>> for x in f:
...     print(x)
... 
0
1
1
2
3
5
8


クラス定義

クラスは次のようにして定義する。

class Cls:
  def __init__(self):     # <コンストラクタ>
    self.str = "instance" # インスタンス変数
    print("called constructor")
  def __del__(self):      # <デストラクタ>
    print("called destructor")
  def func(self, arg0, arg1):
    str = "local"         # ローカル変数
    print(self.str)
    print(str)
    print(arg0)
    print(arg1)
コンストラクタやデストラクタ、インスタンス変数の定義もご覧の通りである。以下に実行例を示す。
>>> c = Cls()
called constructor
>>> c.func(0, 1)
instance
local
0
1
>>> del c
called destructor
また、Pythonにはコンストラクタ(__init__)とは別にインスタンスアロケータ(__new__)というものがある。 __new__は静的メソッドで、新しいインスタンスを生成する際に自動的に呼び出される。__init__の第1引数がインスタンスであるのに対し、__new__にはクラスが与えられる。__new__()がclsのインスタンスを返さない限り、インスタンスの__init__は呼び出されない。なお__init__と__new__の第2引数以降は同じものが与えられる。
class Hoge:
  def __new__(cls):
    print("new Parent")
    return super().__new__(cls)
  def __init__(self):
    print("init Parent")
このクラスを利用した例は次の通りとなる。
>>> h = Hoge()
new Parent
init Parent
__new__は変更不能な型 (int, str, tuple など) のサブクラスでインスタンス生成をカスタマイズするために使用される。それゆえ、基本的に__new__をオーバーライドする必要はない。

継承
継承は次のように記述する。
class Sub(Parent):
  pass
また、Pythonは多重継承をサポートする。多重継承では、基底クラスをカンマで区切って記述する。
class Sub(Parent1, Parent2):
  pass
オブジェクトの型を取得する場合はtype関数を使用する。また、オブジェクトの型が指定したクラスか、そのサブクラスであるかを調べたい場合は、isinstanceを使用する。
>>> class Parent: pass
... 
>>> class Child(Parent): pass
... 
>>> p, c = Parent(), Child()
>>> isinstance(p, Parent)
True
>>> isinstance(c, Parent)
True
カプセル化
Pythonでは、メンバへのアクセス制御の種類はあまり多くない。
  • 非公開(private)な名前を設定する場合、名前の接頭辞に__を付ける。
  • 前述以外の名前は全て公開(public)な名前となる。
なおPythonでは、非公開な名前はPythonによって名前を変更されるだけで、アクセスは可能である。
ポリモフィズム(多態化)
Pythonでは、メンバ関数は標準でオーバーライド可能である。
演算子の多重定義
Pythonでは、いくつかの特殊関数をオーバーロードすることで演算子の多重定義を実装できる。以下に例をいくつか紹介する。
関数名演算子サンプル
__add__(self, other) +
__sub__(self, other) -
__mul__(self, other) *
__truediv__(self, other) /
__floordiv__(self, other) //
__mod__(self, other) %
__pow__(self, other) **
__lshift__(self, other) <<
__rshift__(self, other) >>
__or__(self, other) |
__and__(self, other) &
__xor__(self, other) ^
__lt__(self, other) <
__le__(self, other) <=
__eq__(self, other) ==
__ne__(self, other) !=
__gt__(self, other) >
__ge__(self, other) >=
__neg__(self) -
__pos__(self) +
__call__(self, [args]) ()
この他にも累積代入文や型チェックなどをオーバーライドするために、さまざまな特殊関数が用意されている。
クラス関数
クラス変数
クラス関数やクラス変数は次のように関数デコレータで定義できる。
class Cls:
  @classmethod
  def set(cls, v):
    cls.v = v
  @classmethod
  def p(cls):
    print(cls.v)
以下に実行例を示す。
>>> o1, o2 = Cls(), Cls()
>>> o1.set("hoge")
>>> o2.p()
hoge

クラスデコレータ

クラスデコレータとは、クラス定義に処理を加える機能のことである。

import sys
def py_major_version(major):
  def wrap(cls):
    if sys.version_info[0] < major:
      raise RuntimeError("version error")
    return cls
  return wrap

@py_major_version(3)
class Hoge:
  pass
このコードは、Pythonのメジャーバージョンが2以下の場合に例外を発生させる。

メタクラス

メタクラスは、クラスを生成するためのクラスである。

def ham(self, arg):
  print(arg)

class MetaSpam(type): #組込み型typeを基に
  @classmethod
  def __prepare__(metacls, name, bases):
    print(name)
    print(bases)
    return {'ham':ham}

class Hoge(metaclass=MetaSpam):
  pass
メタクラスは次のように指定する。
  • class クラス名(metaclass=メタクラス)
  • class クラス名(親クラス, metaclass=メタクラス)
  • class クラス名(metaclass=メタクラス, 引数名=引数)


try文

try文は例外処理やクリーンアップ処理を行う文である。いくつかの言語では、エラー発生を通知するための手段として例外処理機構を持つ。例外処理機構は、呼び出し側へのエラー通知機能や、エラー処理とエラー通知の分離などさまざまな特徴を持つが、本稿ではそれらの詳細については言及しない。

try文は、以下のように例外が送出し得る文をtry節に記述し、送出するとexcept節で受け取ることができる。else節を記述した場合、制御がtry節の末尾までいった場合に実行される。これに対し、finally節を記述した場合、どの節を処理したかに関わらず必ず最後に呼び出される。

def div(x, y):
  try:
    result = x // y
  except ZeroDivisionError:
    result = "Error"
  finally:
    print(result)
except節とelse節、finally節は省略可能であり、except節は複数記述でいる。ただし、except節とfinally節の両方を省略することはできない。

また、except節は送出された例外オブジェクトをasで指定した名前に束縛することができる。

class myError(Exception):
  msg = ""

class Cls:
  def func(self):
    e = myError()
    e.msg = "error"
    raise e

c = Cls()
try:
  c.func()
except myError as my:
  print(my.msg) # 画面に"error"を出力
例外に使用するクラスは、BaseExceptionを例外していなければならない。しかし通常独自の例外を定義する場合、ExceptionというBaseExceptionのサブクラスを継承する。

コメント

Pythonには、コメントは#から行末までの1つしかない。

func() # 関数funcを呼び出す。

ドキュメンテーション文字列

Pythonでは、コメントとは別にドキュメントを読み書きする仕組みがある。関数のドキュメントはhelp関数で閲覧できる。

関数アノテーション
関数の引数と返り値を説明するドキュメント(関数アノテーション)は次のように記述できる。
def sum(addee:"被加数", adder:"加数") -> "和":
  return addee + adder
ドックストリング
関数やクラス、モジュールの説明(ドックストリング)は、スイート内の(Pythonの)文が始まる前に文字列リテラルを置くことで記述できる。
def sum(addee:"被加数", adder:"加数") -> "和":
  """
  2つの値を受け取ってその加算結果を返す
  """
  return addee + adder

組込み型

Pythonには、組み込みの型がいくつかある。以下にいくつか紹介する。

論理型(bool)
論理型はTrueかFalseの2つのいずれかの値を持つ。リテラルの書き方もまたTrueかFalseである。
数値型
数値型には次のようなものがある。
説明リテラルの例
整数型(int)メモリサイズの制限があるだけで無限の定義域を持つ。負の数は2の補数で表しており符号ビットが左に無限に続くような値となる。10, 0x1a, 017, 0b1011
浮動小数点型(float) 他の言語同様2進数で保持されるため、10進数との誤差が生じる。例えば"0.1+0.1+0.1"は"0.30000000000000004"となるため、"0.1+0.1+0.1==0.3"は偽となる。 3.14, 1e3
複素数型(complex)3j
数値型のための関数には、絶対値を求める関数absやべき数を求める関数pow、文字列の文字コードを取得する関数ordなどがある。
NotImplemented
Ellipsis
Python独特の型で、それぞれ未実装と省略を表す型。値はそれぞれ1種類しかなく、それぞれ型と同じ名前のリテラルである。評価結果は必ず真となる。
None
Cのvoidに該当する型として、Pythonには値が存在しないことを表すNone型がある。Cのvoid型がオブジェクトも値も持っていないのに対し、None型のオブジェクトは1つだけあり、値も1つだけある。NoneはCのnilにも該当し、Noneの評価結果は常に偽となる。また、Pythonでは関数の返り値を省略した場合はこのリテラルが返される。

データ構造

Pythonのデータ構造には次の3種類がある。

シーケンス型
シーケンス型はシーケンシャルに処理するためのデータ構造である。シーケンス型には以下のようなものがある。
説明リテラル例
tupleイミュレータブルな配列(0, 1, 2)
listミュータブルな配列[0, 1, 2]
string文字列"aiueo"
シーケンス型の詳細は「シーケンス型」で説明する。
集合型
集合型は非シーケンスなデータ構造である。
説明リテラル例
set格納順が不定なデータ構造{1, 2, 3}
辞書型
辞書型はキーでデータにアクセスするデータ構造である。
説明リテラル例
dictハッシュ{"key1":1, "key2":"hello"}

シーケンス型

シーケンス型はシーケンシャルに処理するためのデータ構造である。Pythonのシーケンス型には次の3種類のものがある。

  • コンテンナ
  • イテレータ
  • ジェネレータ
シーケンス型には次のような演算子や組み込み関数がある。
演算説明実行例
x in s帰属であることをチェック。
>>> "Hello" in "Hello, World"
True
x not in s帰属していないことをチェック。
>>> "Hello" not in "Hello, World"
False
s1 + s22つのシーケンスを結合。
>>> [0,1,2,3] + [4,5,6]
[0, 1, 2, 3, 4, 5, 6]
s * nsをn個分結合。
>>> [1,2,3]*3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
n * s
s[i]指定した番号の要素にアクセス。
>>>"Hello"[1]
e
s[i:j]指定した範囲を抽出したものを生成。
>>> (0,1,2,3,4,5,6,7,8)[1:-2]
(1, 2, 3, 4, 5, 6, 7)
s[i:j:k]指定した範囲でk毎に取り出したシーケンスを生成。
>>> [0,1,2,3,4,5,6,7,8][1:-2:2]
[1, 3, 5]
len(s) 要素数を返す。
>>> len([0,1,2,3])
4
min(s) 要素のうち最小のものを返す。
>>> min([0,1,2,3])
0
max(s) 要素のうち最大のものを返す。
>>> max([0,1,2,3])
3
s.index(x[, y[, z]]) xと最初に一致した要素の添字番号を返す。yは検索の開始位置を、zは検索の終了位置を表す。ValueError例外を送出する。
>>> [4,3,2,1,0].index(3)
1
s.count(x) 指定した値がいくつあるかを返す。
>>> [1,2,3,1,4,1].count(1)
3

CプログラマのPythonの落とし穴

投稿日:
タグ:

本稿は私のPython学習のメモやまとめ、特にPythonを学ぶ上でCプログラマから見た視点で気をつけなければいけないことや特徴をまとめたものである。学習には主に次の2つの情報源を使用した。


文法

Cでは、空白文字(スペースや改行、タブ)に字句の区切り以外の意味はない。そして、文は;(セミコロン)までを1文として、ブロックは{と}を使って表す。それゆえ"Hello, World"を画面に出力するためのプログラムは次のように記述できる。

int printf(const char *, ...);int func(){int i=0;while(i<3){printf("Hello, World\n");i += 1;}return 0;}
しかし多くのユーザは、次のような工夫によってコードの可読性を挙げる。
  • 1文を1行に記述して文の区切りを分かりやすくする。
  • 同じブロックの文を同じサイズのインデントにしてブロック階層を分かりやすくする。
これを適用すると、前述のコードは次のように記述できる(改行の位置はユーザのスタイルによって異なる)。
int printf(const char *, ...);
int func()
{
  int i=0;
  while(i<3){
    puts("Hello, World");
    i += 1;
  }
  return 0;
}
Pythonでは、言語仕様によって改行やインデントで文やブロックの区切りを表すように定められている。それゆえ、自然と可読性の高いコードとなる。以下に前述のコードをPythonで記述したものを紹介する。
def func():
  i = 0
  while(i<3):
    print("Hello, World")
    i += 1
  return 0
文の区切りはともかく、ブロックの終端字句がない仕様は、慣れないと違和感があるかもしれないが、デタラメなインデントのコードを読むよりは遥かに読み易いだろう。


命名規則

Pythonの名前(識別子)に使用できる文字は、次のいずれかである。

  • 大文字アルファベット
  • 小文字アルファベット
  • アンダースコア(_)
  • 数字
  • ASCII以外の文字(詳しくはPEP-3131を参照されたし)
Cでは、ASCII文字以外の文字を使用できないが、Pythonでは、日本語名の変数や関数などが定義できる。なおCと同様に先頭文字に数字は使用できない。

また、Cでは次の名前はいくつかのライブラリや言語処理系で予約されている。

  • _が接頭辞のグローバルな名前
  • _が接頭辞でそれに大文字の名前
  • __が接頭辞の名前
これは言語的な制約ではなく、名前の競合を防ぐための注意やマナーである。

他にも、Cでは、ユーザが定数を全て大文字にしたり、グローバル変数の接頭辞をg_にしたりすることで、定義した場所以外でもそれらの意図を分かりやすくなるような工夫がある。これはコードの可読性を上げ、コードリーディングを助ける。Pythonには言語仕様で特別な意味を持つ名前がある。

  • _で始まる名前は、モジュールから*(ワイルドカード)でインポートする場合に除外される。
  • クラス内の__で始まるメンバの名前はクラス内でだけ有効である。
それゆえ前述の名前をその意味に反する意図で使用することはできない。なお2つ目のルールは、実際には呼び出せない訳ではなく、Pythonによって名前が変更され、オリジナルの名前でアクセスできない状態となる(それゆえdir関数で名前を調べられる)。


代入文

Pythonでは代入は演算ではなく文である。Cの代入は演算であり、かつ代入した値を返すため、このように複合式で一時変数に代入するような記述ができた(後述の通りそもそもPythonではこのような一時変数は不要だが)。

if (1 < (tmp = func()) &&  tmp <  5) ) printf("hoge");
しかしPythonでは代入は式文ではなく1つの文であり、このような記述はできない。とはいえ、代入文の=の右辺に式を書くことは可能であるため、次のような記述は問題ない。
a = 1 + 2
なおCにあった+=や-=のような複合代入演算もPythonでは1つの文である。このような文は累算代入文(augmented assignement statement)という。


真偽

Cでは、0や0に変換される値(0.0や'\0'、NULLなど)が偽で、それ以外の全ての値は真である。これに対して、Pythonで偽とみなされる値は次のいずれかである。

  • False
  • None
  • 空文字列
  • 空のコンテナ
  • 全ての型の値の0
真はこれ以外である。ちなみにbool型はint型の部分型であり、Falseをint型に変換すると0となる。ただし整数型と異なり、文字列変換すると"True"や"False"になる。


None

Cのvoidに該当する型として、Pythonには値が存在しないことを表すNone型がある。Cのvoid型がオブジェクトも値も持っていないのに対し、None型のオブジェクトは1つだけあり、値も1つ(None)だけある。それゆえ、引数としてNoneを指定でき、返り値もなにも返さない訳ではなくNoneを返す。Pythonの関数がreturn文に到達しなかった場合、その関数はNoneの値を返す。

def func():
  pass

print(func())
Cでは、返り値を持たない関数が存在する。それらはサブルーチンのような意図で記述されるものだが、定義上それは関数である。しかし、Pythonには値がないというコンセプトのデータを返すことで、全ての関数が返り値を持つ。

Noneの値はCのNULLポインタに該当し、評価結果は常に偽となる。


演算

  • 条件演算(3項演算)
  • 除算演算とモジュロ演算
  • シフト演算
  • 比較演算
  • ブール演算

条件演算(3項演算子)
Pythonも条件演算をサポートするが、Cとは書き方が異なる。Cでは3項演算子を次のように記述する。
a<10? a : 0
これに対し、Pythonでは同様の式を次のように記述する。
a if a<10 else 0
ブール演算
PythonとCのブール演算では演算子の字句が異なる。字句はそれぞれこのように対応する。
PythonC
not!
and&&
or||
また、ANDブール演算やORブール演算は、Cと同様に左辺から右辺に向けて順に評価される。そして、andは偽になった時点で、orは真になった時点で評価を終了する。ただし、CとPythonでは返り値が異なる。
言語真偽返り値
C1
0
Python最後に評価した値
真偽については「真偽」を参照されたし。
除算演算(//と/)
モジュロ演算

/が除算演算、//が切り捨て除算という。除算演算が小数まで計算するのに対し、切り捨て除算演算が剰余を切り捨てる。除数が0の場合、ZeroDivisionError例外が発生する。除算演算は、Python2と3で異なる処理を行う。Python2では、除算演算や切り捨て除算演算に関わらず、項が2つとも整数型または長整数型であれば、整数型や長整数型の除算結果を返す。しかしPython3では、除算演算の場合は浮動小数点型を返し、切り捨て除算の場合は2つの項が整数型か長整数型であれば、整数型を返す。

x % y
符号付き除算及びモジュロの演算結果は言語処理系に依存する。Pythonの場合、モジュロ演算の結果は、0か符号が常に除数(この場合y)と同じになる。モジュロ演算の結果の絶対値は、常に除数以下になる。また、Pythonでは、切り捨て除算演算結果と除数の積に剰余を加えた値は、元の値と常に等しい。
x == (x // y)*y + (x % y)
すなわち、このような被除数と除数の絶対値が5と2の除算やモジュロ演算の結果は次のようになる。
切り捨て除算演算除算結果モジュロ演算剰余
5//225%21
-5//-22-5%-2-1
-5//2-3-5%21
5//-2-35%-2-1
ちなみにJavaの場合、除算結果は0方向に切り捨てられ、かつ"x == (x / y)*y + (x % y)"はtrueとなるポリシーである。

なおPythonの%演算子は、標準で文字列オブジェクト用にオーバーロードされている。

シフト演算
シフト演算には、論理シフトや算術シフトなどの種類があり、Cのシフト演算がどれであるかは、曖昧である。これに対し、Pythonのシフト演算は算術シフトである。すなわち演算結果は次の式と同値となる。
x >> nx // pow(2,n)
x << nx * pow(2,n)
右辺の項は、負の値を指定するとValueError例外が、sys.maxsizeより大きいとOverflowError例外が発生する。
比較演算

Cと異なり比較演算子は全て同じ優先順位である。比較演算には次の種類がある。

  • <
  • <=
  • ==
  • !=
  • >=
  • >
  • is
  • is not
  • in
  • not in
Pythonでは、比較演算子はCとはやや異なる振る舞いをする。Pythonの比較では2つ以上の項を記述できる。これは可読性に優れているだけでなく、いくつかの場合に性能(効率)的にも優れている。

例えばCでこのような関数があったとする。

int func(){
  int retval = 1;
  retval += 1;
  return retval;
}
この時、func()の値の範囲を評価するコードを書くには、数学っぽくこのように書きたくなるかもしれない。
3 > func() > 1
しかし、Cでは>演算子は2項演算子であり、これは別の意味となる。この式では、はじめに3>func()を評価し、>演算子が1か0を、この例では1を返す。そしてその値(1)と1を比較し最終的にこの式文は0を返す。それゆえ、範囲を表すにはこのように記述する。
3 > func() && func() > 1
これと同じ意味のコードをPythonで書くとすれば、次のように記述できる。
3 > func() and func() > 1
しかしながらこのようなコードはfunc()が2回呼び出されるため冗長である。また、関数の結果が呼び出し毎に変わる場合(例えばretvalがstaticなローカル変数の場合)、ユーザの意図したコードと異なる場合がある。これを防ぐため、Cではこのような一時変数を使用することで回避してきた。
3 > (tmp=func()) && tmp > 1
これに対してPythonの比較演算子はこのような記述だけで良い。
3 > func() > 1
Pythonの比較演算子はANDブール演算のように各項を左から右へ評価していき、Falseの式より右辺を評価しない。とはいえ、機械語的に3項の比較など考え難いため、恐らくコンピュータ内部では2項の比較を何度も行っているはずである。


繰り返し文

Cでは繰り返し文を次のように記述する。

for文while文
for (初期化式;条件式;更新式)
  処理
while (条件式)
  処理
Cのfor文は次のような流れで繰り返され、いずれかの条件式の評価で偽の結果となった際に終了する。
  1. 初期化式
  2. 条件式
  3. 処理
  4. 更新式
  5. 条件式
  6. 処理
  7. 更新式
  8. 条件式
  9. ……

これに対してPythonのfor文は次のように記述する。

for ターゲットリスト in 式リスト :  
  処理1
[else:
 処理2]
while 条件式:
  処理1
[else:
  処理2]
ターゲットリストや式リストは次のように記述する。
>>> for x, y in [0,10], [5,20], [10,30]:
...     print(str(x) + "," + str(y))
... 
0,10
5,20
10,30
式リストは1度だけ評価される。例えば式リストに組み込み関数rangeを使用し、"range(1, 5)"と記述した場合、"[1, 2, 3, 4]"となり、各要素が順に代入される。

Pythonのfor文やwhile文にはelse節がある。もしループの条件式の評価結果が偽となった場合、ループが終了した後に実行される。break文で終了した場合、else節はスキップされる。


整数

Cの整数型の詳細(サイズや表現可能な範囲など)は、言語処理系に依存する傾向が強く、かつ符号の有無や精度などの組合せでさまざまな種類が存在する。これに対して、Pythonの整数型はシンプルで、メモリサイズの制限があるだけで無限の定義域を持つ。また、負の数は2の補数で表しており、符号ビットが左に無限に続くような値となる。

Pythonの整数リテラルは、次の通りである。

表記接頭辞
16進数0x, 0X
10進数
8進数0
2進数0b, 0B

Cでは10と8,16進数をサポートしていたが、Pythonでは更に2進数もサポートする。

文字列

Cには、文字列型という組み込み型は存在せず、文字型配列を用いて文字列を表現する。これに対してPythonでは、str型という組み込み型を使用する。str型に関する詳細は、help関数を用いれば知ることができる。

Pythonでは、全ての型の基となるobject型に__str__や__repr__という関数が定義されており、それを上書きすることでstr型への変換の振る舞いを設定できる。

Cの文字列リテラルは、引用符に二重引用符を用いて表す。これに対し、Pythonではさまざまな表現方法がある。Pythonで使用される文字列リテラルの引用符は次の通りである。

文字列引用符説明
'string'一重引用符
"string"二重引用符
'''string'''三連一重引用符エスケープなしで改行や引用符を書くことが可能。
"""string""" 三連二重引用符

データ構造

複数のデータをまとめて扱う方法として、Cでは派生型の配列型やポインタ型を使用する。これに対し、C以降に登場した多くの言語では、そのためのさまざまな型がある。Pythonの場合、以下の3系統のデータ構造がある。

名前説明リテラル
シーケンス型listミュータブルな可変サイズの配列[0, 1, 2]
tupleイミュータブルな可変サイズの配列(0, 1, 2)
str文字列"abcd"
集合型set集合{0, 1, 2}
frozenset
辞書型dictハッシュ{"apple":1, "banana":3}
ここで紹介する型は、組込み型の一例である。

これらのデータ構造にはさまざまな演算子や関数が用意されている。


その他

変数
Cの変数が特定の型を持ちそのオブジェクトだけを格納だけたのに対し、Pythonの変数はどんな変数でも代入できる。
定数
Cではマクロを使って定数のように扱う記述やconst指定子による定数がある。これに対し、Pythonには定数がない。ただし、一部のユーザは、定数の意図で使用する変数を命名規則で全て大文字にしている。
コメント
  • Cではコード中に記述するユーザのための文字列はコメントだけだったが、Pythonにはドキュメントを表すドキュメンテーション文字列とコメントがある。ドキュメンテーション文字列はユーザのために記述するが、コード的に意味を持つ。
  • Cでは/*から*/までのブロックコメントと、//から行末までの2種類のコメントがある。これに対し、Pythonでは#から行末までのコメントしかない。
if文
Cではif節とelse節しかなく、複数の条件を書く場合はelse節の中にif節を書いていたが、Pythonではelif節というものが存在する。

C++プログラマのためのPython

投稿日:
修正日:
タグ:

クラス定義

Pythonはクラス定義や継承、多重定義をサポートする。クラス定義の文法は、大雑把に説明すると次の通りである。

C++
class クラス名 {
  メンバの定義
};
Python
class クラス名:
  メンバの定義

なおPythonではインデントでブロックを表すため、空のクラスや関数を定義する場合、pass文を使用する。

class Hoge: pass
pass文とは何もしない。


メンバ関数

メンバ関数はクラスに属する関数である。メンバ関数の定義はC++の演算子の定義と似ている。C++は演算子の多重定義をメンバで行う際次のように行う。

class T
{
public:
  void operator+(int arg2){ printf("%d\n", arg2); }
};
これは一見すると1つの引数しか取らないが、2つの引数を受け取る2項演算子である。Pythonのメンバ関数の定義はこれとよく似ている。Pythonでは、メンバ関数を定義する場合、第一引数にオブジェクトを受け取る変数を明示的に記述しなければならない。そして呼び出しの際は第二引数以降が1つずつずれる。
class Cls:
  def setAdd(self, addee, adder):
    self.ans = addee + adder

c = Cls()
c.set(1, 3)
第一引数の変数名は言語的な制約はない。
>>> class Hoge:
...     def func1(hogefugapiyo):
...             hogefugapiyo.v = 20
...     def func2(hahaha):
...             print(hahaha.v)
しかしselfという名前を用いることが多いようである。

インスタンス変数にアクセスする場合、例のように第1引数を使用してアクセスする。


コンストラクタとデストラクタ

Pythonではコンストラクタとデストラクタは次のように定義する。

class Hoge:
  def __init__(self):  # コンストラクタ
    pass
  def __del__(self):   # デストラクタ
    pass
コンストラクタとデストラクタは共に省略可能である。

C++ではdelete文で、Pythonではdel文でオブジェクトの削除を行うが、その振る舞いは異なる。C++のdelete文は、指定したアドレスに格納されたオブジェクトを削除し、そのデストラクタを呼び出す。これに対し、Pythonのdel文は参照カウントを1減らし、参照カウントが0になった時に同様の処理が行われる。

class Hoge {
public:
        Hoge(){std::cout << "Hello\n";}
        ~Hoge(){std::cout << "Bye\n";}
        void func(){std::cout << "test\n";}
};

int main()
{
        Hoge *h = new Hoge();
        Hoge *i = h;
        delete h;  // デストラクタが呼び出される

        return 0;
}

class Hoge:
  def __init__(self): print("Hello")
  def __del__(self): print("Bye")
  def func(self): print("test")




h = Hoge()
i = h;
del h  
del i # デストラクタが呼び出される


Pythonでは、基底クラスのコンストラクタ(__init__)は自動で呼び出されない。

インスタンスアロケータ

Pythonには__init__とは別にインスタンスを生成する際に呼び出される関数がある。それが__new__である。__new__は静的メソッドで、新しいインスタンスを生成する際に自動的に呼び出される。__init__の第1引数がインスタンスであるのに対し、__new__にはクラスが与えられる。__new__()がclsのインスタンスを返さない限り、インスタンスの__init__は呼び出されない。なお__init__と__new__の第2引数以降は同じものが与えられる。

class Hoge:
  def __new__(cls):
    print("new Parent")
    return super().__new__(cls)
  def __init__(self):
    print("init Parent")
このクラスを利用した例は次の通りとなる。
>>> h = Hoge()
new Parent
init Parent
__new__は変更不能な型 (int, str, tuple など) のサブクラスでインスタンス生成をカスタマイズするために使用される。それゆえ、基本的に__new__をオーバーライドする必要はない。


クラス関数

C++ではクラス変数とクラス関数はそれぞれ次のようにstaticキーワードを使って表現する。この時、クラス関数の宣言と定義を分ける場合、定義ではstaticを記述しない。また、C++ではクラス変数はグローバル変数の一種であり、外部にstaticなしの定義を記述しなければならない。

#include 

class Hoge {
private:
	static int i;  // クラス変数
	static int get();  // (1) 宣言だけ記述し、定義は外で記述
public:
	static void set(int v) // (2) まとめて書く
	{
		i = v;
	}
};

int Hoge::i; // 必須
int Hoge::get()
{
	return i;
}

int main()
{
	Hoge v1, v2;
	v1.set(10);
	std::cout << v2.get() << std::endl;
	return 0;
}
これに対し、Pythonでは、特殊な関数やクラスの定義にはデコレータという機能を使用する。デコレータは定義するクラスや関数に機能の追加や変更を施す。クラス関数を定義する場合、関数デコレータのclassmethodを使用する。
class Hoge:
  @classmethod
  def set(cls, v):
    cls.__v = v
  @classmethod
  def get(self):
    return cls.__v
以下に実行例を示す。
>>> o1, o2 = Hoge(), Hoge()
>>> o1.set(1)
>>> o2.get()
1
なおデコレータの定義については本稿では言及しない。以前の記事の『Pythonの特徴』を参照されたし。


抽象クラス

編集中


多態化

C++で多態化を実装するには、virtualキーワードを使用して関数をオーバーライドにする宣言が必要だった。これに対してPythonのメンバ関数は、標準ではメンバ関数が全てC++のpublicとvirtualな関数である。それゆえ、継承クラスで定義した同名の関数は自動的にオーバーライドされる。


継承

Pythonでは継承は次のように記述する。

class クラス名[(基底クラス [, 基底クラス, ...])]:
以下に実際の例を示す。
class Parent:
  def __func(self):
    print("Hello, World")

class Child(Parent):
  def func(self):
    super().func()
PythonではC++と異なり、継承の際にアクセス指定を指定しない。また、基底クラスのコンストラクタ(__init__)は自動で呼び出されない。

静的な型チェックを行うC++では、コンパイル時に継承関係を考慮した型チェックが行われる。これに対し、Pythonではisinstanceという組み込み関数を使用して型チェックを行う。

>>> class Parent: pass
... 
>>> class Child(Parent): pass
... 
>>> p, c = Parent(), Child()
>>> isinstance(p, Parent)
True
>>> isinstance(c, Parent)
True

多重継承

PythonはC++と同様多重継承が可能な仕様である。文法は以下の通りである。

C++
class クラス名 : [アクセス指定子] 基底クラス名 [, ...] {
  メンバの定義
};
アクセス指定子と基底クラスが1つのセットで「,」で区切っていくつも記述できる。アクセス指定子は省略可能で、その場合はprivateである。
Python
class クラス名(基底クラス [, ...]):
  メンバの定義
しかしいくつかの振る舞いが異なる。例えば、継承の際C++にはアクセス指定子を指定してアクセス制御を行うが、Pythonにはそのような機能はない(publicな状態)。
コード出力結果説明
C++
class A{
public:
  A(){printf("I am A\n");}
};

class B : public A {
public:
  B(){printf("I am B\n");}
};

class C : public A{
public:
  C(){printf("I am C\n");}
};

class D : public B, public C{
public:
  D(){printf("I am D\n");}
};
I am A.
I am B.
I am A.
I am C.
I am D.
C++では標準で全ての基底クラスのコンストラクタが自動的に呼び出される。
C++(仮想基底クラス)
class A{
public:
  A(){printf("I am A\n");}
};

class B : virtual public A {
public:
  B(){printf("I am B\n");}
};

class C : virtual public A{
public:
  C(){printf("I am C\n");}
};

class D : public B, public C{
public:
  D(){printf("I am D\n");}
};
I am A.
I am B.
I am C.
I am D.
仮想基底クラスの場合、基底クラスの情報は共有され、同じクラスのコンストラクタは1度しか呼び出されない。
Python(オーバーライド)
class A:
  def __init__(self):
    print("I am A.")

class B(A):
  def __init__(self):
    print("I am B.")

class C(A):
  def __init__(self):
    print("I am C.")

class D(B, C):
  def __init__(self):
    print("I am D.")
I am D.
Pythonでは、__init__をオーバーライドした場合、明示的に呼び出さない限り、基底クラスのコンストラクタは呼び出されない。
Python(非オーバーライド)
class A:
  def __init__(self):
    print("I am A.")

class B(A):
  def __init__(self):
    super().__init__()
    print("I am B.")

class C(A):
  def __init__(self):
    super().__init__()
    print("I am C.")

class D(B, C): pass
I am A.
I am C.
I am B.
Python
class A:
  def __init__(self):
    print("I am A.")

class B(A):
  def __init__(self):
    super().__init__()
    print("I am B.")

class C(A):
  def __init__(self):
    super().__init__()
    print("I am C.")

class D(B, C):
  def __init__(self):
    super().__init__()
    print("I am D.")
I am A.
I am C.
I am B.
I am D.


カプセル化

C++やJava、C#などの多くの言語では、文法は違えどpublic、private、protectedというキーワードでメンバのアクセス制御を行う。

class Hoge:
  v1 = 10            # クラス変数
  _v2 = 100          # プライベートで使用する意図のクラス変数
  __v3 = 1000        # プライベートクラス変数
  def func1(self):   # メンバ関数
    print(self.v4)   # インスタンス(オブジェクト)変数
    print(self.__v5) # プライベートインスタンス変数
  def __func2(self): # プライベートメンバ関数
    pass
  @classmethod
  def func3(cls):    # クラス関数
    print(cls.v1)    # クラス変数
C++ではメンバ関数の標準のアクセス状態は"private"であるが、Pythonでは"virtual public"である。

Pythonでメンバをプライベートとする場合、以下のように接頭辞が__から始まる名前にする。

class Hoge:
  def __func(self):
    print("Hello")
このプライベートメンバにアクセスしようとすると次のようにAttributeErrorが送出される。
>>> Hoge().__func()
AttributeError: Hoge instance has no attribute '__func'

Pythonでは、プライベートメンバの名前は、外部から参照できなくなっている訳ではなく、自動的に名前を変更される。これにより継承クラスで名前の競合を防いでいる。なおプライベートメンバの変更後の名前は、プライベートメンバの名前の接頭に「_クラス名」を追加したものである。前述のサンプルコードのHogeの名前空間を閲覧した結果を以下に示す。

>>> dir(Hoge)
['_Hoge__func', '__class__', '__delattr__', '__dict__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
'__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '__weakref__']
このように名前が変更されただけであるため、実はこのようにアクセスすることが可能である。
>>> h = Hoge()
>>> h._Hoge__func()
Hello

多様性 検索順

  1. D
  2. B
  3. C
  4. A
class A:
  def func(self):
    print("I am A\n")

class B(A):
  def func(self):
    print("I am B\n")

class C(A):
  def func(self):
    print("I am C\n")

class D(B, C): pass


変数

C++の変数は、スコープやライフタイム、ストレージクラス、アクセス制御、修飾子などの組合せによって非常にさまざまなものがある。Pythonの変数はC++程の種類はないが、それでもいくつかの種類がある。以下にC++の定義と比較した場合のPythonの変数定義を紹介する。

種類C++Python
グローバル変数(プログラム内で共有される変数)
int v = 1;
class Cls {
  void func()
  {
  }
};
なし。
グローバル変数(ファイルスコープの変数)
static int v = 1;
class Cls {
  void func()
  {
  }
};
v = 1
class Cls:
  def func():
    pass
ローカル変数
class Cls {
  void func()
  {
    int v = 1;
  }
};
class Cls:
  def func():
    v = 1
静的ローカル変数
class Cls {
  void func()
  {
    static int v = 1; 
  }
};
なし。
インスタンス(オブジェクト)変数
class Cls {
private:
  int v = 1;
  void func()
  {
  }
};
class Cls:
  v = 1
  def func(self):
    self.w = 1
クラス変数
class Cls {
public:
  static int v;
};
int Cls::v = 1;
class Fuga:
  @classmethod
  def func(cls):
    cls.w = 1
名前空間
namespace nspace {
  int v = 1;
};
class Cls {
  void func()
  {
  }
}
fileA.py
x = 10
fileB.py
import fileA
fileA.x
定数
const int VAL = 1;
class Cls {
  void func()
  {
  }
};
言語仕様としてはないが、定数の意図で使用する場合に名前を大文字にするマナーがある。
公開メンバ変数
class Cls {
public:
  int v = 1;
  void func()
  {
  }
}
class Cls:
  def func(self):
    self.v = 1
非公開メンバ変数
class Cls {
private:
  int v = 1;
  void func()
  {
  }
}
class Cls:
  def func(self):
    self.__v = 1
限定公開メンバ変数
class Cls {
protected:
  int v = 1;
  void func()
  {
  }
}
なし。


型変換

編集中


特殊関数

編集中


例外

Pythonではtry文を次のように記述する。

try:
  // 例外が発生しうる処理
except Exception:
  // 例外取得と処理
else:
  // try節が末尾までいった場合の処理
finally:
  // 最後に必ず行われる処理
Pythonではcatch節の代わりにexcept節がある。except節は送出された例外オブジェクトをasで指定した名前に束縛することができる。except節には、BaseExceptionのサブクラスを指定できる。しかし通常独自の例外を定義する場合、ExceptionというBaseExceptionのサブクラスを継承する。

また、tryとexcept節以外にもelse節とfinally節がある。else節を記述した場合、制御がtry節の末尾までいった場合に実行される。これに対し、finally節を記述した場合、どの節を処理したかに関わらず必ず最後に呼び出される。except節とelse節、finally節は省略可能であり、except節は複数記述でいる。ただし、except節とfinally節の両方を省略することはできない。

Pythonの覚えておきたい関数やクラス、モジュール

投稿日:
タグ:

本稿はPython3の言語仕様とは別に覚えておきたい関数やモジュールについてのメモ。

組み込み関数

map(func, iterable, ...)
第二引数以降で指定したコンテナから同じ添字番号のものを順に抜き出し、それをfuncに引数として与え、その結果を返すジェネレータ。funcに与える引数は、mapの第2引数のコンテナに属するオブジェクトがfuncの第1引数に、mapの第3引数のコンテナに属するオブジェクトがfuncの第2引数といった感じで、mapの第n引数のコンテナに属するオブジェクトがfuncの第n-1引数に与えられる。
def func(x, y, z):
  return x * y * z

print(list(map(func, [1, 2, 3], [2, 4, 6], [3, 6, 9])))
mapはPython2ではジェネレータではなく、コンテナを返す関数である。
filter(func, iterable)
iterableが返すオブジェクト群を順にfuncに引数として与えて評価し、真となるオブジェクトを返すジェネレータ。これを利用すると例えば次のように10から50の範囲の素数を検索するコードを記述することができる。
def isPrime(x):
  for y in range(2, x-1):
    if x%y==0:
      return False
  return True

print(list(filter(isPrime, range(10, 50))))
このコードはこのような結果を標準出力に出力する。
[11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
filterもまたPython2ではジェネレータではなく、コンテナを返す関数である。
zip(*iterable)
行列の行と列を交換するような関数。複数のコンテナを受け取り、同じ添字を1つのタプルを順に返すジェネレータ。 例えばこのようなコードがあったとする。
print(list(zip([1,2,3], [4,5,6])))
これは次のように標準出力に出力される。
[(1, 4), (2, 5), (3, 6)]
isinstance(object, class-or-type-or-tuple)
指定したオブジェクトが指定したクラスかその継承クラスである場合にTrueを、それ以外の場合にFalseを返す。また、クラスはタプルの中に記述することで複数指定できる。
isinstance(False, bool)
isinstance(False, (int, str))
id(object)
オブジェクトの識別値(ID)を返す。
算術
abs(number)
絶対値を返す関数

モジュール

以下にいくつか便利なモジュールの使い方を説明する。詳細は情報はhelpコマンドによるドキュメントを参照されたし。

re
正規表現を扱うためのモジュール。
一致
import re
r = re.match("h.*o", "hello")
if r is None:
  print("Not match")
else:
  print("match")
抽出
>>> import re
>>> r = re.match("^\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*$", "apple, 100, 5")
>>> if r is not None:
...   r.group(0)
...   r.group(1)
...   r.group(2)
...   r.group(3)
... 
'apple, 100, 5'
'apple'
'100'
'5'
置き換え
>>> re.sub("([^ ,]+)", "hoge", "foo, bar")
'hoge, hoge'
sys
システム系のモジュール。
コマンドライン引数の取得
import sys
print("program name: " + sys.argv[0])
print(" argument 1 : " + sys.argv[1])
print(" argument 2 : " + sys.argv[2])
標準出力
標準エラー出力
import sys
sys.stderr.write("error message\n")
これを利用すると次のようにリダイレクトができる。
import sys
out = sys.stdout
f = open("/tmp/test.txt", "w")
sys.stdout = f
cnt = sys.stdout.write("fuga\n")
print("hoge")
f.close()
exit
import sys
sys.exit(0)
os
システムコールのモジュール。
ディレクトリの生成
import os
os.mkdir("/tmp/hoge", 755)
exec系関数
dup
pipe
fork
import sys
import os

r, w = os.pipe()
pid = os.fork()
if pid:
  os.close(w)
  out = os.read(r, 10)
  while True:
    buf = os.read(r, 10)
    if buf:
      out += buf
    else:
      break
  os.write(sys.stdout.fileno(), out)
  os.close(r)
else:
  os.close(r)
  os.dup2(w, sys.stdout.fileno())
  os.close(w)
  os.execlp("ls", "ls", "-l")


特殊関数

編集中

Python2と3

投稿日:
タグ:

本稿はPython3と2の差異に関するメモ。

編集中

apply
basestring
buffer
callable
dict
except
exec
print
Python2ではexecやprintは文だったが、Python3では関数である。それゆえ、printの文法が異なる。
execfile
exitfunc
filter
map
zip
filterやmap、zipはPython2ではコンテナを返す関数だが、Python3ではジェネレータである。
2
>>> zip([1,2,3], [4,5,6])
[(1, 4), (2, 5), (3, 6)]
3
>>> zip([1,2,3], [4,5,6])
<zip object at 0xb731964c>
funcattrs
future
getcwdu
has_key
idioms
import
imports
imports2
input
intern
isinstance
itertools_imports
itertools
long
metaclass
methodattrs
ne
next
nonlocal文
Python3ではnonlocal文という文が追加された。
nonzero
numliterals
paren
raise
raw_input
reduce
renames
repr
set_literal
standard_error
sys_exc
throw
tuple_params
types
unicode
urllib
ws_comma
xrange
xreadlines

除算
除算(division)または切り捨て除算(floor division)演算は、はじめに型変換によって2つの項の型を統一する。そしてPython2では、除算演算や切り捨て除算演算に関わらず、項が2つとも整数型または長整数型であれば、整数型や長整数型の除算結果を返す。しかしPython3では、除算演算の場合は浮動小数点型を返し、切り捨て除算の場合は2つの項が整数型か長整数型であれば、整数型を返す。

PF メモ

投稿日:
編集日:
タグ:

本稿はBSD系OSのパケットフィルタリングツールPFのメモ。参考にしたのはmanと以下のページである。

実行環境
OSFreeBSD
バージョン10.0
アーキテクチャi386

はじめに

PFを利用するには以下の4つの要素がある。

カーネルの設定
PFやPFのログ機能などを利用するにはカーネルの設定が必要である。私の実行環境では標準でPFやログ機能は利用可能だが、ALTQといわれるキューイング制御のためのフレームワークは利用不能であった。
設定ファイル
PF関連の起動スクリプトの設定(/etc/rc.conf)
pf_enable="YES"	# pfを実行可能に
pflog_enable="YES"		# PF用のログインタフェース
pflog_logfile="/var/log/pflog"	# PF用のログファイルのパスを指定
pf_rules="/etc/pf.conf"		# pfの設定ファイルを指定
/etc/sysctl.conf
net.inet.ip.forwarding=1
PFでパケット転送機能を使用するには前述の設定が必要である。OS起動後にこの値を変更するには、次のような処理を行えば良い。
user% sysctl net.inet.ip.forwarding=1
また、現在の値については以下のようにして取得できる。
user% sysctl net.inet.ip.forwarding
PFの設定(/etc/pf.conf)
以下に例を示す。
# Tables
table <private> const { 127.0.0.1 192.168/16 }
table <hosts1> persist

# Options
set block-policy drop

# Filtering
block in all
pass in from any to <private>
起動スクリプトの実行
PF関連のスクリプトにはpfやpflog、pfsyncなどがある。
  • user% /etc/rc.d/pf start
/dev/pf制御用コマンド(pfctl)
PFはpfctlコマンドを利用して制御できる。以下にいくつか例を示す。
ルールテキストのロード
user% pfctl -f /etc/pf.conf
テーブル操作
user% pfctl -t hosts1 -T add 192.168.1.3
ルールの確認
user% pfctl -s rules
本稿では特にこの3つを重視する。


カーネルの設定

PFやログ機能を利用するには、カーネルがそれに対応していなければならない。

pf
device pf
pflog
device pflog
ALTQ
ALTQ(ALTernate Queueing of network packets)というBSD系OSのネットワークインタフェースでのキューイング制御のためのフレームワークのこと。ALTQには以下に記すようないくつかのオプションがあるが、その全てが必要な訳ではない。各オプションの機能はmanを参照されたし。
options ALTQ
options ALTQ_CBQ
options ALTQ_RED
options ALTQ_RIO
options ALTQ_HFSC
options ALTQ_CDNR
options ALTQ_PRIQ
options ALTQ_NOPCC
options ALTQ_DEBUG
私の実行環境では標準でPFやpflogは利用可能だが、ALTQは利用不能だった。


pf.conf

PFの設定はpf_rulesで設定したファイル、または/etc/pf.confに記述する。pf.confはいくつかの種類の文からなり、それらは次の2種類に分類できる。

  • 変数やアドレスリストの定義
    • Macros
    • Tables
  • ルール行
    1. Options
    2. Normalization
    3. Queueing
    4. Translation
    5. Filtering

pf.confのサンプルを以下に示す。

#Comments #から文末までコメント
#Macros 変数の定義 net_if="alc0"
#Tables アドレスリストの定義 table <default> const { 10/8, 172.16/12, 192.168/16 } table <goodhost> persist # pfctlで追加するリスト
#Options パラメータの設定 set block-policy drop set timeout { icmp.first 20 }
#Normalization パケットの正規化(分断化されたパケットを再構築すること)の制御 scrub in all scrub out all random-id max-mss 1414
#Queueing 帯域幅の制御 queue http on parent std bandwidth 100M default
#Translation NATや他のアドレスへのリダイレクトの制御 # nat|rdr|binat on 〜 rdr on $net_if proto tcp from 192.168.1.4 to 192.168.1.2 port 10080 -> 192.168.1.3 port 80
#Filtering ブロックや通過の設定 #block|pass in|out 〜 block in on $net_if all # ファイアウォールから入るのを防ぐ pass in on $net_if from <default> # default内のアドレスの通過を許可 pass in on $net_if from <goodhost> # goodhost内のアドレスの通過を許可
pf.confの作成や修正が終わったならば、起動スクリプトの再実行("/etc/rc.d/pf restart")や設定ファイルの再読み込み("/etc/rc.d/pf reload")を行えば、ルールが適応される。


変数やアドレスリストの定義

Macros
PFではマクロが利用可能である。
代入
var="string"
評価
$var
マクロはインタフェース、IPアドレスやポート番号などを記述するのに便利である。
myaddr="192.168.1.2"
nif="alc0"
mport="{ 25, 110 }"
また、マクロの機能という訳ではないがインタフェースを()で囲うことでそれに割り当てられたIPアドレスを指定できる。
($nif)
Tables
PFではいくつかのアドレスの集合を表す仕組みにテーブルがある。テーブルはアドレスのリストで、これによっていくつかのアドレスに対してまとめてブロックや通過などを指定できる。アドレスはIPアドレスやネットワークアドレスを指定できる。また、テーブルには、タイプがいくつかある。以下にいくつか例を紹介する。
const
不変なアドレスリスト。以下のように設定ファイルに記述できる。
table <private> const { 10/8,  172.16/12, 192.168/16 }
table <local> const { 127.0.0.1, 192.168.1.15 }
persist
可変なアドレスリスト。以下のように設定ファイルに記述し、
table <badhost> persist
pfctlによって次のように制御できる。
追加
user% pfctl -t badhost -T add 192.168.1.3
削除
user% pfctl -t badhost -T delete 192.168.1.3
コマンドの詳細は"man pfctl"を参照されたし。


ルール行(1)

ルール行は標準では次のような設定に従う。

  • PFはフィルタルールセット(/etc/pf.conf)の先頭から末尾へと順に評価される。
  • 最後に一致したルールが採用される。
  • quickキーワードが付いたルールと一致する場合、そのルールを最終的に一致するルールとなる。
  • デフォルトではパケットは通過するように設定されている。

また、ルール行は次の順番で記述しなければならない。

  1. Options
  2. Normalization
  3. Queueing
  4. Translation
  5. Filtering
ただし本稿ではこの順番ではないので注意されたし。また、はじめに以下の3つを、続けて残りの要素を記す。
  • Filtering
  • Translation
  • Options
(Packet) Filtering
ファイアウォールの中や外への通信の遮断(block)や通過(pass)などを制御。組合せは以下の4つである。
inout
blockblock inblock out
passpass inpass out
そして、それにネットワークインタフェースの指定や宛先や送信元の指定することで細かな制限が可能である。例えば、ファイアウォールの外部のネットワークの"183.77.202.242"から内部の"192.168.1.2"への通信を遮断するには次のように記述する。
block in from 183.77.202.242 to 192.168.1.2
Filteringの行の書き方は以下のような流れである。
block/pass [quick] [on interface] in/out [log] from 〜 to 〜
指定するIPアドレスは直接記述したりTablesで定義したテーブルを指定したりできる。また、全てのアドレスを指す場合は"any"というキーワードが使用でき、"from any to any"と記述する場合は"all"と書いても良い。
block block-policy
通信を遮断。blockはblock-policyの設定に基づいて動作が変化する。blockのblock-policyを省略した場合の設定はOptionsで設定できる。block-policyには次のいずれかを設定できる。
drop
パケットは黙って破棄。
return
ブロックされたTCPパケットに対してTCP RST パケットが返され、 他のすべてのパケットに対しては ICMP UNREACHABLE パケットが返されます。
block-policyは次のようにして指定する。
block drop on all
また、Optionsでデフォルトを設定する場合、次のように記述する。
set block-policy drop
pass
パケットを通過。
route-to
replay-to
dup-to
fastroute
キーワードには次のようなものがあり、基本的に他のルール行と共通である。
<キーワードの一例>
inファイアウォールの中への通信か外への通信
out
logログを記録(pflogは通常/var/log/pflogにtcpdumpのバイナリ形式で記録される。ログの保存先はrc.confのpflog_logfileで指定可能)
on interfaceネットワークインタフェース
proto protocolプロトコル名。/etc/protocolsに記述されているプロトコル。
inetIPv4/v6
inet6
from送信元
to宛先
any全てのアドレス
allfrom any to anyのこと
portポート番号(TCPやUDP通信などの場合)
quick一致する場合それを最終的に一致するルールとする
以下に実際のFilteringルールの例を紹介する。
<サンプルルール>
re0を通したファイアウォール内の192.168.1.21のTCPの110番(POP3)に対するアクセスをブロック
block in on re0 inet proto tcp from any to 192.168.1.21 port 110
または
block in on re0 inet proto tcp from any to 192.168.1.21 port = 110
ポートの指定には次の記号が利用可能。
port = xxと等しい
port != xxと等しくない
port < xx未満
port <= xx以下
port > xxより大きい
port >= xx以上
port x:yx以上y以下
port x >< yx未満でかつyより大きい
port x <> yx未満かyより大きい
また、IPアドレスと同様にリストが使用できる("{ 22, 80 }")。
re0を通したファイアウォール内の192.168.1.21のTCPかUDPの110番(POP3)に対するアクセスをブロック
block in on re0 inet proto {tcp, udp} from any to 192.168.1.21 port = 110
指定したアドレス(badhost)からファイアウォール内へのアクセスをブロック
table <badhost> persist
block in from <badhost>
テーブル<badhost>へのアドレスの追加は次のように行う。
user% pfctl -t badhost -T add 192.168.1.3
ファイアウォール内への全てのICMPアクセスをブロック
block in inet proto icmp all
ログを記録
block in log inet proto icmp all
ログはpflogによって制御され、通常/var/log/pflogにtcpdumpのバイナリ形式で記録される。icmpであれば、次のようなコマンドでログを取得できる。
user% sudo tcpdump -n -r  /var/log/pflog icmp
reading from file /var/log/pflog, link-type PFLOG (OpenBSD pflog file)
00:13:43.605085 IP 192.168.1.4 > 192.168.1.2: ICMP echo request, id 7202, seq 1, length 64
00:13:44.612563 IP 192.168.1.4 > 192.168.1.2: ICMP echo request, id 7202, seq 2, length 64
00:13:45.620305 IP 192.168.1.4 > 192.168.1.2: ICMP echo request, id 7202, seq 3, length 64
また、次のようなpop3をブロックするログがあるとする。
block in log on re0 inet proto tcp from any to 192.168.1.21 port 110
この時、このログは次のようにして取得できる。
user% sudo tcpdump -n -r /var/log/pflog port pop3
reading from file /var/log/pflog, link-type PFLOG (OpenBSD pflog file)
00:25:00.624451 IP 192.168.1.4.51868 > 192.168.1.2.110: Flags [S], seq
 1646204942, win 14600, options [mss 1460,sackOK,TS[|tcp]>
00:25:01.622446 IP 192.168.1.4.51868 > 192.168.1.2.110: Flags [S], seq
1646204942, win 14600, options [mss 1460,sackOK,TS[|tcp]>
00:25:03.627292 IP 192.168.1.4.51868 > 192.168.1.2.110: Flags [S], seq
1646204942, win 14600, options [mss 1460,sackOK,TS[|tcp]>
Translation
宛先や送信元のアドレスの変更やポート番号などを制御。
rdr
リダイレクトまたはポートフォワーディング。あるパケットを別のIPアドレスや別のポート番号に転送する。例えば"192.168.1.4"から"192.168.1.2"のポート番号10080へのTCP通信を"192.168.1.3"のポート番号80に転送する場合、次のように記述する。
rdr proto tcp from 192.168.1.4 to 192.168.1.2 port 10080 -> 192.168.1.3 port 80
また、直接IPアドレスを指定せずインタフェースを()で囲えば、そのインタフェースに割り当てられたIPアドレスを指定できる。
rdr proto tcp from any to ($nif) -> 192.168.1.5
nat
NATまたはNAPT。あるパケットのアドレスやポート番号を変更する。例えば"192.168.1.2"からの通信を"192.168.1.100"に変更する場合、次のように記述する。
nat on alc0 from 192.168.1.2 to any -> 192.168.1.100
->の右辺は以下のようにインタフェース名を指定することもできる。
nat on alc0 from 192.168.1.2 to any -> alc1
rdrとは異なり、->の右辺で指定するのは宛先ではなく送信元である。また、宛先ポート番号は指定できないが、送信元ポート番号を指定できる。NAPTは次のように行う。
nat on re0 proto tcp from 192.168.1.2 port 80 to any -> 192.168.1.100
natの状態はpfctlで確認可能である。
user% pfctl -s nat
なお変更後のアドレスには任意のアドレスを利用できるが、通信が成り立つためには相手が経路を確保できることとこちらのインタフェースがそれを取得できることが必要である。例えばローカルネットワーク"192.168.1.0"内で、"192.168.1.2"と"192.168.1.3"のマシンが通信を行い、"192.168.1.2"を"192.168.1.100"に変更する場合、それぞれのマシンで次のような処理が必要である。
192.168.1.2のマシン
pf.confに次の行を追加。
nat on re0 from 192.168.1.2 to any -> 192.168.1.100
インタフェースに"192.168.1.100"をエイリアスし、受信可能にする。
user% ifconfig re0 alias 192.168.1.100
192.168.1.3のマシン
192.168.1.2のMACアドレスが"01:23:45:67:89:ab"の時、192.168.1.100に対応するMACアドレスをそれに対応づける。
user% arp -s 192.168.1.100 01:23:45:67:89:ab -i eth0
binat
双方向マッピング。ある2つのアドレスやポート番号を対応させる。例えば192.168.1.1と192.168.1.5を対応付けるには次のように記述する。
binat on re0 from 192.168.1.1 to any -> 192.168.1.5
Translationの特定の通信だけを一致させないために、"no"というキーワードがある。例えば特定のアドレス("192.168.1.4")への通信の場合にNATを行わない場合、次のように記述する。
no nat on re0 from 192.168.1.2 to 192.168.1.4
nat on re0 from 192.168.1.2 to any -> 192.168.1.100
なおこのような場合最初に一致したルールが最終的な一致となる。
Options
PFや通信に関する基本的な情報を設定。以下にサンプルを示す。
set block-policy drop
set timeout { icmp.first 20 }
詳しくは"man pf.conf"を参照されたし。
timeout
loginterface
limitパケットフィルタで使用するメモリプールの限界値。
ruleset-optimization
optimization
block-policyFilterのblockでblock-policyを省略した場合のblock-policy。
state-policy
hostid
require-order
fingerrpints
skip on
debug


ルール行(2)

編集中

Traffic Normalization
分断化されたパケットを再構築(正規化)する際の制御。詳しくは"man pf.conf"を参照されたし。
scrub in all
scrub out all random-id max-mss 1414
Queueing


pfctl

pfctlの詳細は"man pfctl"で閲覧できる。以下にいくつかpfctlの機能を紹介する。

ルールテキストのロード
user% pfctl -f /etc/pf.conf
テーブル操作
user% pfctl -t hosts1 -T add 192.168.1.3
ルールの確認
user% pfctl -s rules
PFを有効に
user% pfctl -e
PFを無効に
user% pfctl -d


メモ

  • タグ

jail

投稿日:
修正日:
タグ:

FreeBSDのjailの構築の度に「【FreeBSD】FreeBSDサーバ上に仮想サーバを構築する(jail)」に依存しているので、それを回避するためのメモ。なおメモの途中で"man jail.conf"のEXAMPLESに手順が書いてあることに気づいた。

構築の手順は大雑把には次の通りである。

  1. カーネルのインストールと設定
  2. jailの設定

なお本稿ではパスや名前に以下の環境変数を使用する。

環境変数値の例用途
JAIL_NAMEmyjail1jailの名前
JROOT_PATH/usr/jail/myjailjailのルートディレクトリ
もし2つ以上のjailを生成するならば、これらの変数の値を変えれば良い。

カーネルのインストール

FreeBSDのカーネルのソースは/usr/srcにある。

user% cd /usr/src

カーネルのビルドにはmakeコマンドを使用する。makeに与えるターゲット名や変数はMakefileを参照されたし。特にターゲットについては丁寧に記述されている。

  1. カーネルのビルド
    user% make buildworld
  2. カーネルのインストール
    user% make installworld DESTDIR=${JROOT_PATH}
  3. カーネルの設定ファイルのインストール
    user% cd /usr/src/etc
    user% make distribution DESTDIR=${JROOT_PATH}
  4. jailのdevfsをマウント
    user% mount -t devfs devfs ${JROOT_PATH}/dev
DESTDIRで指定するディレクトリはあらかじめmkdirで生成しておく必要がある。

カーネルのビルドとインストールは以下のようにまとめて行うこともできる。

user% make world DESTDIR=${JROOT_PATH}
また、一度カーネルのビルドを行えば、2つ目以降のjailの生成では省略できる。なおビルドの際の設定はmake.confを使用する。詳細は"man make.conf"を参照されたし。


${JROOT_PATH}/etc以下のファイルの修正

"make distribution"でインストールした設定ファイルは、そのままではjail用でない。それでも実行するのに支障はないが、ホストと複数のjail間での競合によるトラブルを回避したいならば、いくつかの設定ファイルを修正する必要がある。以下にいくつか紹介する。

  • カーネルのログ出力を抑止。syslogdの設定ファイル(${JROOT_PATH}/etc/syslog.conf)を修正(コメントアウト)。
    before
    *.err;kern.warning;auth.notice;mail.crit /dev/console
    after
    #*.err;kern.warning;auth.notice;mail.crit /dev/console
  • カーネルの時間を制御するための時間同期(adjkerntz)を抑止。crontabの設定ファイル(${JROOT_PATH}/etc/crontab)を修正。
    before
    1,31    0-5     *       *       *       root    adjkerntz    -a
    after
    #1,31    0-5     *       *       *       root    adjkerntz    -a
  • OS全体で使用するユーザidとグループidを重複させないように設定。jailの設定ファイル(${JROOT_PATH}/etc/pw.conf)及びホストの設定ファイル(/etc/pw.conf)を修正(コメントアウト)。以下に記述例を示す。
    minuid  1000
    maxuid  1999
    mingid  1000
    maxgid  1999
    
    このように設定すれば、ユーザIDとグループIDは1000から1999の範囲に限定される。同じ要領で他の環境も設定すれば、重複しないようにできる。pw.confの詳細は"man pw.conf"を参照されたし。
  • ホスト名のローカルデータベースの設定。自分が使用するホスト情報が/etc/hostsにあるならば、それをコピーした方が早い。
    user% cp /etc/hosts ${JROOT_PATH}/etc/.

ネットワークインタフェース

ネットワークインタフェースがホストやjail間で共有している場合(例えばホストでエイリアスしたものを使用している場合)、意図した相手と異なる相手にアクセスしてしまうことがありうる。それを回避する方法をいくつか紹介する。

  • sshサーバが受信する宛先IPアドレスを設定(${JROOT_PATH}/etc/ssh/sshd_configを修正)。以下に例を示す。
    before
    #ListenAddress 0.0.0.0
    after
    ListenAddress 192.168.1.2

jailはデフォルトでは、raw socketを利用したプログラムが正常に機能しない。これを回避するためには/etc/sysctl.confに次の行を追加する必要がある。

security.jail.allow_raw_sockets=1


jailの設定

jailの構築が終わったら、jailのルート以下のファイルの設定を行う。なお以下で紹介する処理のほとんどは、jailを動かすために必要不可欠という訳ではない。以下のいくつかについては"man jail.conf"のConfiguring the Jailを参照されたし。

jailのルートディレクトリ以下で作業をする場合、chrootを使えばルートディレクトリを変更できる。

user% chroot ${JROOT_PATH}
ルートディレクトリを元に戻すにはexitで終了すれば良い。
  • DNSサーバの設定。エディタを起動して手動で設定するか、後述のようにホストのものをコピーするかすれば良い。
    user% vi /etc/resolv.conf
    ホストのresolv.confをコピーするには、ルートディレクトリを通常に戻った後に、次のように行えば良い。
    user% cp /etc/resolv.conf ${JROOT_PATH}/etc/.
  • jailのrootパスワードの設定
    passwd root
  • タイムゾーンの設定
    ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
  • 警告を防ぐために空の/etc/fstabを生成。
    touch /etc/fstab
  • adjkerntzは/etc/wall_cmos_clockがある場合、 CMOSクロックは(MS-DOSやMS-Windows互換モードの)ローカル時間を保持していることを意味する。
    touch /etc/wall_cmos_clock
    このファイルが無い場合、CMOSクロックはUTC時間を保持していることになる。
  • sendmailの警告を防ぐために次のコマンドを実行
    newaliases
  • 警告を防ぐためにインタフェースを無効化。 エディタを起動し、
    vi /etc/rc.conf
    次のパラメータを設定。
    network_interfaces=""
  • ポートマッパー(rpcbind)を無効化。rc.confに次のパラメータを設定。
    rpcbind_enable="NO"
  • デフォルトではrootしかいないので、必要であれば更にユーザの追加が必要。
    user% pw useradd -n user1
  • jailに導入したいパッケージをインストール

  • SSHサーバが受信する宛先IPアドレスを設定(前述の処理と同じようにすれば良い)。
  • FreeBSDではカーネルのようないくつかの重要なファイルのフラグがschg(システムの変更禁止フラグ)である。これがあると移動や削除ができない。これを解除するためには次のようなフラグの変更が必要。
    chflags noschg /sbin/init
  • dumpユーティリティというファイルシステムのバックアップのためのユーティリティがある。jailにとってファイルシステムの管理は不要であり、/etc/rmtのリンクを解除する。
    unlink /etc/rmt


jailの実行

jail機能を有効にするためには、/etc/rc.d/jailという起動スクリプトを使用する。しかしそのためには、rc.confの設定が必要である。

rc.conf

ホスト側のrc.confにはいくつかの設定が必要である。設定にはjail機能の設定と各jailの設定の2種類がある。 また、各jailが使用するネットワークインタフェースをホストのネットワークインタフェースのエイリアスしたものを使用するならばその設定も必要である。

使用するネットワークインタフェースが"re0"ならば次のようなコマンドを実行すれば良い。

user% ifconfig re0 alias 192.168.101 netmask 255.255.255.0
この処理を起動時に自動的に行うならば、以下のようにrc.confに記述すれば良い。
ifconfig_re0_alias0="inet 192.168.1.101 netmask 255.255.255.0"
ifconfig_re0_alias1="inet 192.168.1.102 netmask 255.255.255.0"

そしてjail機能や各jailの設定は次のように記述すれば良い。

jail_enable="YES"
jail_list="myjail1 myjail2"
jail_set_hostname_allow="NO"
jail_socket_unixiproute_only="YES"
jail_sysvipc_allow="YES"
jail_stop_jailer="NO"

jail_myjail1_rootdir="/home/jail/myjail1"
jail_myjail1_hostname="myjail1"
jail_myjail1_ip="192.168.1.101"
jail_myjail1_exec="/bin/sh /etc/rc"
jail_myjail1_devfs_enable="YES"
jail_myjail1_fdescfs_enable="NO"
jail_myjail1_procfs_enable="YES"

jail_myjail2_rootdir="/home/jail/myjail2"
jail_myjail2_hostname="myjail2"
jail_myjail2_ip="192.168.1.102"
jail_myjail2_exec="/bin/sh /etc/rc"
jail_myjail2_devfs_enable="YES"
jail_myjail2_fdescfs_enable="NO"
jail_myjail2_procfs_enable="YES"
各jailの設定は/etc/jail.confというものに書くことができるらしい(それを使った方が恐らく簡潔に記述できるはず)。 各jailの設定は/etc/jail.confというものに書くことができる。jail.confの記述例については後述する。

rc.confの設定が完了すれば、起動スクリプトで実行できる。

user% /etc/rc.d/jail start myjail1

jail.conf

jail.confで使用するパラメータ名はrc.confで使用したものと異なる場合が多い。それらの情報はmanで閲覧できる。

jail.confは次の2種類の文からなる。

  • 全てのjailの基となる設定
  • 各jail個別の設定
# shared rule
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
mount.devfs;

# myjail1's rule
myjail1 {
  path = "/home/jail/myjail1";
  host.hostname = "myjail1";
  ip4.addr = 192.168.1.101;
}

# myjail2's rule
myjail2 {
  path = "/home/jail/myjail2";
  host.hostname = "myjail2";
  ip4.addr = 192.168.1.102;
}

jail.confには変数が利用できる。変数には特別なものがありjail名などはnameという変数に格納される。それゆえ、前述のpathやhost.hostnameのように一部だけ違う場合は次のように記述できる。

# shared rule
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
mount.devfs;
path = "/home/jail/${name}";
host.hostname = "${name}";

# myjail1's rule
myjail1 {
  ip4.addr = 192.168.1.101;
}

# myjail2's rule
myjail2 {
  ip4.addr = 192.168.1.102;
}

jail関連コマンド

jailの制御には次のようなコマンドがある。

コマンド機能使用例
jlsjailの一覧を表示
user% jls
   JID  IP Address      Hostname     Path
     1  192.168.1.101    myjail1     /usr/jail/myjail1
     2  192.168.1.102    myjail2     /usr/jail/myjail2
jexecJIDを指定し、そのjailで指定したコマンドを実行 jexecでシェルを指定すればjail内で作業ができる。
user% jexec 2 sh
なおFreeBSD10では前述の引数ではエラーが返され、次のように名前を指定しなければならなかった。
user% jexec myjail2 sh

UML2.4(1)

投稿日:
タグ:

UML2.4

構造
ダイアグラム名特徴
オブジェクト図オブジェクト図とは、システムのある時点におけるオブジェクトの静的なスナップショットを表す図。
クラス図クラス同士の静的な関係を表現した図。オブジェクト図を抽象化して記述できる。
コンポジット構造図クラスやコンポーネントの内部構造(クラスやコンポーネント)を表す図。
コンポーネント図ソフトウェアを部品に分解した場合の全体構成を表現する図。
パッケージ図パッケージとパッケージ間の関係を表現する図。
配置図システムの物理的な構成を表現する図。
プロファイル図
動作
ダイアグラム名特徴
アクティビティ図
ユースケース図
シーケンス図
コミュニケーション図
ステートマシン図
タイミング図
相互作用概要図
プログラミング言語よりも抽象的な表現が可能であり、 プログラミング以外にも表現できる。

UML2.4(2) -オブジェクト図-

投稿日:
修正日:
タグ:

オブジェクト図

オブジェクト図とは、システムのある時点におけるオブジェクトの静的なスナップショットを表現するダイアグラムである。

名称意味
オブジェクト
オブジェクト名またはロール名 : クラス名
名前:型名=値
オブジェクト名
ロール名
オブジェクト(インスタンス)を表す要素。クラスを表現する場合もあるが、ロール名及びクラス名に下線がある場合はオブジェクトであることを表す。また、この項目はクラス名を書かず、オブジェクト名だけの場合もある。
スロット
ある時点でのオブジェクトの状態を表す要素
リンク
オブジェクト間に関係があることを表す。

例えば鈴木さんがメンバー加入届けを受付に提出するような場合、次のように表現できる。

鈴木さん メンバー加入届 : 申請書 :受付
  • 名前:文字列=鈴木太郎
  • 住所:文字列=ZZZZZ
  • 電話番号:数=012345
  • 職業:文字列=XXX
import java.util.ArrayList;

class Guest {
  /*- 属性 -*/
  private ApplicationForm memAddAF;

  /*- 操作 -*/
  public Guest(){
    memAddAF = new ApplicationForm("鈴木太郎", "ZZZZZ", 012345, "XXX");
  }
  public ApplicationForm getMemAddAF(){return memAddAF;}
}


class ApplicationForm {
  /*- 属性 -*/
  public String name;
  public String address;
  public int tel;
  public String work;

  /*- 操作 -*/
  public ApplicationForm(String name, String address, int tel, String work){
    this.name = name;
    this.address = address;
    this.tel = tel;
    this.work = work;
  }
}


class Receptionist {
  /*- 属性 -*/

  /*- 操作 -*/
  public void postApplicationForm(ApplicationForm memAddAF){
    // 受付処理
  }
}

class Sample {
  /*- 属性 -*/

  /*- 操作 -*/
  /** エントリポイント */
  public static void main(String args[]){
    Guest suzuki = new Guest();
    Receptionist receptionist = new Receptionist();
    receptionist.postApplicationForm(suzuki.getMemAddAF());
  }
}

UML2.4(3) -クラス図-

投稿日:
修正日:
タグ:

クラス図

クラス図とは、クラス同士の静的な関係を表現した図である。オブジェクト図が処理の流れのある瞬間の関係を表しているのに対し,クラス図は処理の流れ全体を通した関係を示している。クラス図で表現する関係は以下の通りである。

  • 汎化
  • 実現
  • 関連
    • 誘導可能性
    • 集約
    • コンポジション
  • 依存
名称意味
クラス
  • 属性
  • 操作
classA
  • + varA
  • - varB
  • # varC
  • ~ varD
  • + funcA()
  • - funcB()
  • # funcC()
  • ~ funcD()
クラス
オブジェクトの共通部分を抜き出して抽象化したもの。クラスを実体化させたもののことをオブジェクトまたはインスタンスという。クラス図では、クラスはクラスやそのインスタンスを表すのに使用される。
属性
クラスが持つ性質のこと。
操作
クラスが持つ振る舞いのこと。アクセッサ(ゲッタ/セッタ)やコンストラクタの記述は省略される場合がある。
可視性
UMLアクセス指定子(Java)意味
+public全てのクラスからアクセス可能
-private自分のクラスからだけアクセス可能
#protected自分のクラスと継承クラスからのみアクセス可能
~同一パッケージ内からアクセス可能(package)
ローカル可視性
メソッド内だけで繋がること。
属性可視性
オブジェクトが存在している間繋がる長期的な関係のこと。
パラメータ可視性
メソッドの引数として受け取った際だけに繋がる一時的な関係のこと。

インタフェース
Javaのインタフェースを表現する場合、クラス名を<<Interface>>のようにステレオタイプ(<<type>>)で表現。
操作や属性の型を表現する場合、:で区切って右辺に記述する。
  • + 山田太郎 : 人
  • - var : int
  • + func() : boolean
  • + func(arg1 : int) : boolean
初期値
初期値は次のように=を使って表す。
  • + りんごの数 : 非負数 = 5
集約集合-要素の関係。例えば"A has a B"の時,AはBを集約するという意味となる。関連の一種で,JavaではAの属性としてBがある場合にこの記号で表す。
コンポジション集約の中でも強い関係のこと。 例えば"A is part of B"の時,コンポジションで表す。Javaでは,集合クラスのインスタンスが消滅すると、要素クラスのインスタンスも消滅するような関係の時にコンポジションで表現する。
関連
2つのクラスのインスタンス間に長期的な繋がりがあること。Javaでは属性として相手のオブジェクトを持つような場合に関連とする。集約やコンポジションがどちらのクラスの属性なのか明示しているのに対して,関連はどちらかの属性である場合や,それぞれ対等な関係の場合に関連で表現する。
誘導可能性関連がある2つのクラスのインスタンスで一方のクラスがもう一方のクラスを参照(メッセージを送信)できるか否か。参照できる場合は矢印、できない場合は✕印、未定義の場合はそのどちらでもない。Javaで使用する場合,例えば"A has a B"の関係で,かつCがBを属性として保持しなければならない場合で,概念的にはCとBが集約関係でない場合は,誘導可能性で"C→B"のように表現する。これにより,開発者はどちらのクラスの属性かを判断できる。
関連端(ロール)名 属性を関連で表現した場合,属性名や可視性は関連端名で表現する。
多重度 あるクラスの複数のインスタンスと別のクラスのインスタンスに関連がある時、それをまとめることができる。数は次のように表現する。
11
*0以上
1..31以上3以下の範囲
2..*2以上
例えばJavaで配列やコレクションを使用する際に多重度でまとめて表現できる。
依存 2つのクラスが一時的な関係がある時に依存で表現する。Javaでは,引数として一時的にそのクラスを利用するような場合やローカル変数で利用する場合に依存で表現する。例えばクラスAのあるメソッドがクラスBを引数として使用する場合に,"A→B"の方向で記述する。
汎化抽象-具象の関係(class)。 Javaでは"class B extends A"の時,"A→B"の方向で記述する。
実現抽象-具象の関係(interface)。Javaでは"class B implements A"の時,"A→B"の方向で記述する。

以下のコードは記事『UML2.4(2) -オブジェクト図-』 と同じものである。

import java.util.ArrayList;

class Guest {
  /*- 属性 -*/
  private ApplicationForm memAddAF;

  /*- 操作 -*/
  public Guest(){
    memAddAF = new ApplicationForm("鈴木太郎", "ZZZZZ", 012345, "XXX");
  }
  public ApplicationForm getMemAddAF(){return memAddAF;}
}


class ApplicationForm {
  /*- 属性 -*/
  public String name;
  public String address;
  public int tel;
  public String work;

  /*- 操作 -*/
  public ApplicationForm(String name, String address, int tel, String work){
    this.name = name;
    this.address = address;
    this.tel = tel;
    this.work = work;
  }
}


class Receptionist {
  /*- 属性 -*/

  /*- 操作 -*/
  public void postApplicationForm(ApplicationForm memAddAF){
    // 受付処理
  }
}

class Sample {
  /*- 属性 -*/

  /*- 操作 -*/
  /** エントリポイント */
  public static void main(String args[]){
    Guest suzuki = new Guest();
    Receptionist receptionist = new Receptionist();
    receptionist.postApplicationForm(suzuki.getMemAddAF());
  }
}

これをクラス図で抽象的に表現すると次のようになる。

UML2.4(4) -コンポジット構造図-

投稿日:
タグ:

コンポジット構造図

コンポジット構造図とは、クラスやコンポーネントの内部構造を表現するダイアグラムである。コンポジット構造図は次のことを表現できる。

  • クラスやコンポーネントの内部と外部の境界を表現できる。
  • クラスやコンポーネント内のクラスやコンポーネントの結びつきや役割を表現できる。
名称意味
クラス
クラス名
内部構造を表現したいクラスやコンポーネントはこの図で表す。
クラス
オブジェクトを抽象化したもの。
コンポーネント
インタフェースを持つクラス。
コンポーネント
パート
部品
クラスやコンポーネントを構成する内部要素。部品のこと。パートもまたクラスやコンポーネントである。ロール名や多重度を表現可能。
コネクタ
パート間に関係があることを示す。
ポート
クラス(またはコンポーネント)の内部と外部の境界を表す。
インタフェース
提供インタフェース
クラスやコンポーネントが外部に公開している機能を表す。
要求インタフェース
クラスやコンポーネントが必要とする外部機能を表す。

例えばMP3とWMA形式、生の音楽ファイル(例えばWAV)を再生する音楽プレイヤーを表現したものが、以下のコンポジット図である。

/* 音楽プレイヤー */
class MusicPlayer {
  private ArrayList<Audio> playlist;
  private Controller my_controller;
  public GInterface my_interface;
  MusicPlayer(){
    playlist 
      = new ArrayList<Audio>(); 
    my_controller 
      = new Controller(playlist);
    my_interface
      = new GInterface(my_controller);
    my_interface.setVisible(true);
  }
}
前述のサンプルコードの全体像は次の通りである(曲の制御部分については省略)。
import java.util.ArrayList;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.filechooser.*;

/* 音楽 */
class Audio {
  public final String name;
  public final String path;
  Audio(String n, String p){
    name = n;
    path = p;
  }
}

/* コントローラ */
class Controller {
  private ArrayList<Audio> playlist;
  private int current_idx = 0;
  public Controller(ArrayList<Audio> pl){
    playlist = pl;
  }
  public void addAudio(Audio audio){
    playlist.add(audio);
  }
  public void start(){
    playlist.get(current_idx);
    /* 再生処理 */ 
  }
  public void stop(){
    /* 停止処理 */
  }
  public void next(){
    /* 次の曲へ */
  }
  public void prev(){
    /* 前の曲へ */
  }
}

/* インタフェース */
class GInterface extends JFrame {
  Controller controller;
  JButton start;
  JButton stop;
  JButton prev;
  JButton next;
  JButton adda;
  class StartListener implements ActionListener {
    public void actionPerformed(ActionEvent e){
      controller.start();
    }
  }
  class StopListener implements ActionListener {
    public void actionPerformed(ActionEvent e){
      controller.stop();	    
    }
  }
  class NextListener implements ActionListener {
    public void actionPerformed(ActionEvent e){
      controller.next();	    	    
    }
  }
  class PrevListener implements ActionListener {
    public void actionPerformed(ActionEvent e){
      controller.prev();	    	    	    
    }
  }
  class AddListener implements ActionListener {
    public void actionPerformed(ActionEvent e){
      JFileChooser filechooser = new JFileChooser();
      FileNameExtensionFilter filter = new FileNameExtensionFilter("mp3 & wma & wav audio file", "mp3", "wma", "wav");
      filechooser.setFileFilter(filter);
      if (filechooser.showOpenDialog(GInterface.this)==JFileChooser.APPROVE_OPTION)
        controller.addAudio(
          new Audio( filechooser.getSelectedFile().getName(),
               	     filechooser.getSelectedFile().getPath()));
    }
  }
  GInterface(Controller ctr){
    super();
    controller = ctr;
    start = new JButton("再生");
    start.addActionListener(new StartListener());
    stop = new JButton("停止");
    stop.addActionListener(new StopListener());
    prev = new JButton("前へ");
    prev.addActionListener(new PrevListener());
    next = new JButton("次へ");
    next.addActionListener(new NextListener());
    adda = new JButton("曲を追加");
    adda.addActionListener(new AddListener());
    Container c = this.getContentPane();
    c.setLayout(new GridLayout(1,5));
    c.add(prev);
    c.add(start);
    c.add(stop);
    c.add(next);
    c.add(adda);
    this.setSize(500, 100);
  }
}

/* 音楽プレイヤー */
class MusicPlayer {
  private ArrayList<Audio> playlist;
  private Controller my_controller;
  public GInterface my_interface;
  MusicPlayer(){
    playlist = new ArrayList<Audio>(); 
    my_controller = new Controller(playlist);
    my_interface = new GInterface(my_controller);
    my_interface.setVisible(true);
  }
}

UML2.4(5) -コンポーネント図-

投稿日:
タグ:

コンポーネント図

コンポーネント図とは、コンポーネントの構造を表現するダイアグラムであり、ソフトウェアを部品に分解した場合の全体構成を表現する。コンポーネント図は、各部品の内部構造には着目せず、外部に公開している機能、外部に要求している機能だけに着目する。

名称意味
コンポーネント
  • 《component》
  • コンポーネント名
コンポーネントは入れ子構造であり、内部にいくつかのコンポーネントを記述できる。
  • 《component》
  • コンポーネント名
インタフェース
提供インタフェース
外部に公開する機能を表す。白丸(ボール)で表記。
要求インタフェース
外部に要求する機能を表す。半円(ソケット)で表記。
実現 あるコンポーネントが別のコンポーネントを利用していることを表す。

UML2.4(6) -パッケージ図-

投稿日:
修正日:
タグ:

パッケージ図

パッケージ図は、パッケージとパッケージ間の関係を表現するダイアグラムである。

名称意味
パッケージ
パッケージ
さまざまなダイアグラムをグループ化するための要素のことであり、名前空間の管理も行う。パッケージ内の各要素の関係は、クラス図やコンポジット構造図のような他のダイアグラムで表現でき、パッケージ間の関係はインポートやマージで表現できる。
パッケージ
要素
インポート 別のパッケージに含まれている要素(クラスやインタフェースなど)を読み込むことを表す。矢印がA⇢BならばパッケージAがパッケージBを読み込んでいることを示す。
マージ 既存のパッケージを利用して別のパッケージを新規に作成することを表す。マージとインポートの違いはパッケージ間で名前の衝突が発生した時の解決方法である。
インポート
インポートするパッケージに元々あった要素が隠される。
マージ
両方のクラスの特徴を持つクラスが生成される。

次のようなパッケージ図があるとする。この時、パッケージP0でクラスAの操作opAという定義があり、かつそれをインポートしたパッケージP1でクラスAの操作Bを定義した場合、パッケージP0のクラスAは隠蔽され、P3で利用するクラスAは操作opBしかない。もしパッケージP0をマージしたパッケージP2でクラスAの操作Bを定義した場合、それをインポートしたパッケージP4では操作opAと操作opBの2つの機能を持つクラスとなる。


Javaのパッケージ

Javaでは、パッケージのインポートをimport文を用いて行う。パッケージの生成とimportは次のように行う。

./pkgtest/Hoge.java
パッケージを生成し、そのパッケージにファイル内で定義したクラスやインタフェースを追加する場合、package文を使用する。package文は必ずJavaの本文を記述する前に記述しなければならない。Javaでは,package文を省略した場合は無名パッケージとして扱われる。すなわちJavaのクラスは必ずいずれかのパッケージに属する。
package pkgtest; // 登録

/**
 * クラスHogeの説明
 * @author 魔術師見習い
 */
public class Hoge {
 /**
  * 加算関数
  * @param addee 被加数
  * @param adder 加数
  * @return 加算結果
  */
  public int add(int addee, int adder){
    return addee + adder;
  }
}
./pkgtest/package-info.java
javadocを使用する場合、パッケージの説明はpackage-info.javaというファイルに記述する。
/**
 * パッケージpkgtestの説明
 * 
 */
package pkgtest;
./Fuga.java
パッケージは次のようにimport文を使用して行う。
import pkgtest.Hoge;

public class Fuga {
  public static void main(String args[]){
    Hoge h = new Hoge();
    System.out.println(h.add(1, 3));
  }
}

UML2.4(7) -配置図-

投稿日:
タグ:

配置図

配置図は、システムの物理的な構成を表現するダイアグラムである。

名称意味
ノード ハードウェアや実行するソフトウェアなどの実行するモノの単位。
ステレオタイプ
device
ハードウェア
executionEnvironment
ソフトウェア実行環境
ノードは入れ子構造であり、ノード内にノードや成果物を記述できる。実際のハードウェアそのものを指している場合はノード名に下線を引く。
コミュニケーションパス
ノード間に物理的な繋がりがあること。関係に方向性がある場合、矢印でその方向を示す。
成果物 ソフトウェア開発の各工程で生成された物理的な情報の単位。ソースファイルや実行ファイル、データベースのテーブル定義などのような実際にノードに配置する配布単位を表現する(例:DBサーバ、hoge.exe)。
依存あるノードや成果物が別のノードや成果物を利用していること。依存する側から依存される側に向かって矢印を記述。

ワークステーションAの/usr/bin/javaでHogeというプログラムを実行し、それがワークステーションBのDBサーバを利用していることを表した図は次のように記述できる。

UML2.4(8) -プロファイル図-

投稿日:
タグ:

プロファイル図

UMLの内容を独自に拡張するためのダイアグラムで、メタモデルの構築が行える。

名称意味
メタクラス
《Metaclass》
メタクラス
クラスのためのクラスのこと。
ステレオタイプ
《stereotype》
ステレオタイプ
モデル要素に特別な意味をもたせ、特定の用途にモデル要素を拡張して使用することを表す。
プロファイル
《profile》
プロファイル
UMLを拡張するための特殊パッケージ。
《profile》
プロファイル
拡張 メタクラスの拡張。
プロファイル適用 外部のプロファイルの適用。

FreeBSDでルータ構築

投稿日:
タグ:

FreeBSD10でルータを構築した際のメモ。

最低限のWANとLANを接続するルータを作るには次の2つの機能が必要である。

FreeBSDのPPPoEクライアントであるpppは、NAT機能を備えている。それゆえ、OSのパケット転送機能を可能にし、pppの設定を行えば、最低限のルータとして動作する。

市販の多くのルータは前述の機能に加えて以下の機能も持つ。

これらの機能はなくてもインターネットにアクセス可能だが、あった方が安全だったりネットワークの設定が楽だったり通信が速かったりする。


基本設定

OSはFreeBSD10でカーネルはデフォルトのまま使用する。本ルータはDHCPサーバの機能を持ち、固定IPアドレスを使用する(もしDHCPサーバが別にあるならそれからIPアドレスを貰ってもいい)。 IPアドレスの割り当ては、次のようにifconfigコマンドで行うことができる。

user% ifconfig re0 192.168.10.1 netmask 255.255.255.0
また、OS起動時にこの設定を行うには、rc.confに次のような行を追加すれば良い。
/etc/rc.conf
ifconfig_re0="inet 192.168.10.1 netmask 255.255.255.0"
/etc/sysctl.conf
net.inet.ip.forwarding=1
NATを使用するには前述の設定が必要である。OS起動後にこの値を変更するには、次のような処理を行えば良い。
user% sysctl net.inet.ip.forwarding=1

pppの設定

私の環境ではPPPoEを使ってWANに接続する。PPPoEクライアントにはpppを使用する。設定は次のように行う。

/etc/ppp/ppp.conf
default:
  set log Phase Chat LCP IPCP CCP tun command
  set device PPPoE:re1
  set mru 1454
  set mtu 1454
  set authname XXXXXXX@YYYYYY.ZZZ
  set authkey WWWWWWWWWW
  set dial
  set login
  add default HISADDR
  nat enable yes
#  enable dns
#から行末まではコメント。authnameとauthkeyはプロバイダから渡されたアカウントとパスワードを使用する。
/etc/rc.conf
ppp_enable="YES"
ppp_mode="ddial" # モードについてはman pppを参照されたし
ppp_nat="YES"    # ppp.confの"nat enable yes"と同じ。
ppp_profile="default"
この設定は次のような処理と同じである。
user% ppp -ddial -nat default
ちなみにNATの設定はどちらかだけで良い(両方あっても問題なく動作するようだが)。
設定後、OSを再起動するか次のようにpppの起動スクリプトを実行すればWANに接続できる。
user% /etc/rc.d/ppp start
正常に接続できたならば、次のような仮想デバイスが生成されているはずである(番号が0でない可能性があるのでifconfig -aで確認した方が無難)。
user% ifconfig tun0
tun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> metric 0 mtu 1454
	options=80000<LINKSTATE>
	inet XXX.XXX.XXX.XXX --> YYY.YYY.YYY.YYY netmask 0xffffffff 
	nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
	Opened by PID 1101
XXX.XXX.XXX.XXXやYYY.YYY.YYY.YYYには実際のIPアドレスが入る。以降このような記述は同様の意図である。

これによりルータ、または内部ネットワークのノードが外部と通信した場合、その送信元IPアドレスはXXX.XXX.XXX.XXXとなる。


isc-dhcp-serverの設定

DHCPを使用しない場合、ノードのIPアドレス、使用するネームサーバ、ルーティングテーブルなどの設定を主導で設定しなければならない。以下に設定例を紹介する。

IPアドレスの割り当て
user% ifconfig eth0 192.168.10.3 netmask 255.255.255.0
使用するネームサーバ(/etc/resolv.conf)の設定ファイル
nameserver WWW.WWW.WWW.WWW
nameserver WWW.WWW.WWW.VVV
ルーティングテーブル
ルーティングテーブルの確認
user% netstat -r
ルーティングテーブルからの削除
デフォルトルートを削除する。
user% route del -net default
ルーティングテーブルへの追加
デフォルトルートeth0から192.168.10.1へのルートを追加する。
user% route add -net default gw 192.168.10.1 dev eth0
毎度毎度このような設定は面倒臭い。それゆえDHCPサーバを用意する。

本環境ではDHCPサーバはルータと同じマシンである。DHCPサーバにはisc-dhcp-serverを使用する。インストールはpkgを使用して以下のような感じでインストールする。

user% pkg install isc-dhcp43-server-4.3.0_1

isc-dhcp-serverの設定は以下のように行う。

/usr/local/etc/dhcpd.conf
option domain-name-servers WWW.WWW.WWW.WWW, WWW.WWW.WWW.VVV;
default-lease-time 600;
max-lease-time 7200;
log-facility local7;

subnet 192.168.10.0 netmask 255.255.255.0 {
        range 192.168.10.2 192.168.10.254;
        option routers 192.168.10.1;
        option broadcast-address 192.168.10.255;
}

# 個別設定

host my_router {
        hardware ethernet XX:XX:XX:XX:XX:XX;
        fixed-address 192.168.10.1;
        option host-name "my_router";
}

host my_client1 {
        hardware ethernet XX:XX:XX:XX:XX:XX;
        fixed-address 192.168.10.3;
        option host-name "my_client1";
}
/etc/rc.conf
dhcpd_enable="YES"                      # dhcpd enabled?
dhcpd_flags="-q"                        # command option(s)
dhcpd_conf="/usr/local/etc/dhcpd.conf"  # configuration file
dhcpd_ifaces="re0"                      # ethernet interface(s)
dhcpd_withumask="022"                   # file creation mask
#dhcpd_chuser_enable="YES"              # runs w/o privileges?
#dhcpd_withuser="dhcpd"                 # user name to run as
#dhcpd_withgroup="dhcpd"                # group name to run as
#dhcpd_chroot_enable="YES"              # runs chrooted?
#dhcpd_devfs_enable="YES"               # use devfs if available?
#dhcpd_rootdir="/var/db/dhcpd"          # directory to run in
#dhcpd_includedir="<some_dir>"		# directory with config-files to include
設定のサンプルは/usr/local/etc/dhcpd.conf.exampleを参照されたし。

isc-dhcp-serverの起動スクリプトは/usr/local/etc/rc.dにある。

/usr/local/etc/rc.d/isc-dhcpd start

pfの設定

PFとはBSD系のパケットフィルタである。PFの詳細については過去の記事を参照されたし。以下にサンプルコードを紹介する。

ext_if="tun0"
int_if="re0"

web_server="192.168.10.4"

rdr on $ext_if proto tcp from any to ($ext_if) port 80 -> $web_server # Webサーバへの通信を別のローカルマシンにリダイレクト
block drop in quick on $ext_if proto tcp from any to ($ext_if) port 22  # 外部からsshへのアクセスをブロック

Debianのlocaleトラブル

投稿日:
タグ:

本稿はDebianのlocaleを修正した際のメモ。基本的に見つけたページ(「debianでロケールのエラーが出る時の対処法」)通りにやっただけだが,以前もあるパッケージを無理矢理入れた際に発生したので,今後起きた時すぐ解決できるようにメモを残す(ツイッターだと字数が足りなかったので)。

症状

  • 日本語が表示されなかった(?????.pdfのように表示された)。
  • フォントが1文字1文字間隔を置いて表示された。

解決方法

とりあえずlocale-genがなかったので,Webでlocalesのdebファイルを探し,インストール。 あればこの過程は不要。

user% sudo dpkg -i locales_XXXXXX_all.deb
環境変数を設定(参考ページでは,以降でこの環境変数を指定しているが,この例だといらないかも)。
user% export LANG=ja_JP.UTF-8
user% export LC_ALL=ja_JP.UTF-8
ちなみに参考ページではja_JP.UTF-8ではなかった。

user% sudo locale-gen --purge ja_JP.UTF-8
user% sudo dpkg-reconfigure -f noninteractive locales 
user% sudo update-locale LANG=ja_JP.UTF-8 LC_ALL=ja_JP.UTF-8

HTMLタグとCSSによるデフォルメ日本地図

投稿日:
タグ:

tableタグとCSSで書くデフォルメ日本地図。「タグで作った日本地図素材-地方別色分けB(リンクあり)」「なぜ人はテーブルタグで日本地図を作るのか - 寄せては返す館主専用」を参考に,HTMLとCSSで記述。

<style>
<!--
table.jp td {
    text-align:center;
    padding:5px;
}
td.hokkaido{
    background-color:#87cefa;
}
td.tohoku {
    background-color:#40e0d0;
}
td.koshinetsu {
    background-color:#3cb371;
}
td.hokuriku {
    background-color:#2e8b57;
}
td.kanto {
    background-color:#66cdaa;
}
td.kinki {
    background-color:#bdb76b;
}
td.tokai {
    background-color:#9acd32;
}
td.chugoku {
    background-color:#deb887;
}
td.kyusyu {
    background-color:#ff8c00;
}
td.shikoku {
    background-color:#ff7f50;
}
td.okinawa {
    background-color:#ff6347;
}
-->
</style>
<table class="jp">
<tr>
 <td colspan="15"></td>
 <td colspan="3" class="hokkaido" style="height:80px;">
  北海道
 </td>
</tr>

<tr>
<td></td>
</tr>

<tr>
<td colspan="15"></td>
<td colspan="2" class="tohoku">
青森
</td>
<td style="min-width:10px;"></td>
</tr>

<tr>
<td colspan="15"></td>
<td class="tohoku">
秋田
</td>
<td class="tohoku">
岩手
</td>
</tr>

<tr>
<td colspan="15"></td>
<td class="tohoku">
山形
</td>
<td class="tohoku">
宮城
</td>
</tr>

<tr>
<td colspan="12"></td>
<td class="hokuriku">
石川
</td>
<td class="hokuriku">
富山
</td>
<td class="koshinetsu" colspan="2">
新潟
</td>
<td class="tohoku">
福島
</td>
</tr>

<tr>
<td colspan="2"></td>
<td class="kyusyu">
長崎
</td>
<td class="kyusyu">
佐賀
</td>
<td class="kyusyu">
福岡
</td>
<td></td>
<td class="chugoku" rowspan="2">
山口
</td>
<td class="chugoku">
島根
</td>
<td class="chugoku">
鳥取
</td>
<td class="kinki" rowspan="2">
兵庫
</td>
<td class="kinki">
京都
</td>
<td class="kinki">
滋賀
</td>
<td class="hokuriku">
福井
</td>
<td class="koshinetsu" rowspan="2">
長野
</td>
<td class="kanto">
群馬
</td>
<td class="kanto">
栃木
</td>
<td class="kanto">
茨城
</td>
</tr>

<tr>
<td colspan="3"></td>
<td class="kyusyu">
熊本
</td>
<td class="kyusyu">
大分
</td>
<td></td>
<td class="chugoku">
広島
</td>
<td class="chugoku">
岡山
</td>
<td class="kinki">
大阪
</td>
<td class="kinki">
奈良
</td>
<td class="tokai">
岐阜
</td>
<td class="koshinetsu">
山梨
</td>
<td class="kanto">
埼玉
</td>
<td class="kanto">
千葉
</td>
</tr>

<tr>
<td colspan="3"></td>
<td class="kyusyu">
鹿児島
</td>
<td class="kyusyu">
宮崎
</td>

<td colspan="5"></td>

<td class="kinki">
和歌山
</td>
<td class="tokai">
三重
</td>
<td class="tokai">
愛知
</td>
<td class="tokai">
静岡
</td>
<td class="kanto">
神奈川
</td>
<td class="kanto">
東京
</td>
</tr>

<tr>
<td colspan="7"></td>
<td class="shikoku">
愛媛
</td>
<td class="shikoku">
香川
</td>
</tr>

<tr>
<td class="okinawa">
沖縄
</td>
<td colspan="6"></td>
<td class="shikoku">
高知
</td>
<td class="shikoku">
徳島
</td></tr>
</table>

ZSHメモ

投稿日:
タグ:

ZSHの機能に関するメモ。

名前修飾子

**
再帰的。

ディレクトリhoge以下の全てのCファイル(.c)を検索。

ls hoge/**/*.c

ファイル名(.)
通常ファイル

カレントディレクトリ以下の通常ファイルから,文字列MAX_ORDERを含むファイルを検索。

egrep MAX_ORDER **/*(.)

ディレクトリ名(/)
ディレクトリ
ファイル名(L[kmp][+-]N)
ファイルサイズで絞り込む。

記号説明
kKB
mMB
pブロック
記号説明
 一致
-未満
+大きい
324バイトのCファイル
ls C*(L324)
3KB以上の全てのファイル
ls *(Lk+30)
ls *(L+30000)

ファイル名(m[Mwhms][-+]N)
修正時刻で絞り込む。

3日以内に修正したファイル
ls *(m-3)
カレントディレクトリ以下のhogeディレクトリ下の10時間より前に修正したファイル
ls **/hoge/*(mh+10)
M
w
h時間
m
s
記号説明
-指定した値以内(m-3なら3日以内)
+指定した値より前(m+3なら3日より前)

ファイル名(fSPEC)
アクセス権で絞り込み。SPECはchmodなどを参考に。
接頭辞がabcのアクセス権が755のファイル
abc*(f755)
abc*(f=755)
所有者はかけるが,グループは書けないCファイル
*.c(f/u+w,g-w/)
所有者が全ての権限のrbファイル
*.rb(f7??)
グループとその他のユーザが書き込みできないpyファイル
*.py(f-?22)
グループとその他のユーザが書き込み可能なpyファイル
*.py(f+?22)
?はいずれの値でも良い場合を表す。

記号説明
 完全一致
=
-そのビットがセットされている場合
+そのビットがセットされていない場合

ファイル名(D) 接頭辞がドットのファイルを含む

読み込み書き込み実行
userrwx
groupAIE
worldRWX
記号説明
ssetuid
Ssetgid
tsticky

ファイル名(a[Mwhms][-+]N)
アクセス時刻で絞り込み。
ファイル名(i[Mwhms][-+]N)
iノード修正時刻で絞り込み。

ファイル名(M)
一致したファイルがディレクトリの時,末尾に/を付ける。
echo *(M)
ファイル名(n)
ファイル名を整数とみなして小さい順にソート。
echo *
1 10 2 3 4 5 6 7 8 9
echo *(n)
1 2 3 4 5 6 7 8 9 10
ファイル名(onLlamcdN)
ソート
ファイル名(OnLlamcdN)
逆ソート
記号説明
nファイル名(標準)
Lファイルサイズ
lリンクカウント
aアクセス時刻
m修正時刻
ciノード修正時刻
dディレクトリ優先
N並べ替えなし
大きいサイズ順にファイル名を表示
echo *(OL)
ファイル名(N)
指定したファイル名が存在しない時エラーをおこkさない。
存在する場合
echo /tmp/(N/)
/tmp
存在しない場合
echo /tm(N/)
ファイル名(-)
ファイルがシンボリックの時,シンボリックファイルが指すものに。

ファイル名(@)
シンボリックリンク
ファイル名(*)
実行ファイル
ファイル名(=)
ソケットファイル
ファイル名(P)
名前付きパイプ(FIFO)
ファイル名(%)
デバイスファイル
ファイル名(%b)
ブロックデバイスファイル
ファイル名(%c)
キャラクタデバイスファイル
ファイル名(D)
接頭辞がドットのファイルを含む
ファイル名(U)
現在のUIDのユーザが所有するファイル。
ファイル名(G)
現在のGIDのユーザが所有するファイル。
ファイル名(uID)
UIDがIDのファイル。
userAのファイル
*.[hc](u:userA:)
userBのファイル
*.[hc](u/userB/)
ファイル名(gID)
GIDがIDのファイル。

NOT, AND, OR
NOT
ファイル名(^)
通常ファイル以外の全てのファイル
*(^.)
*(OL)と同じ
*(^oL)
AND
7日前から2日前までに修正されたファイル
*(m+2m-7)
1KBより大きく10KB未満のファイル
*(Lk+1Lk-10)
2年前の30KBより大きい通常ファイル
*(mM+24Lk+30.)
OR
カンマ(,)で区切るとORになる。
userAかuserBのCファイルかヘッダファイル
*.[ch](u:userA:, u/userB/)

変数

${変数名#接頭パターン}
接頭パターンに対応する部分を削除。
${変数名##接頭パターン}
接頭パターンに対応する部分を削除。最長一致
${変数名%接尾パターン}
接尾パターンに対応する部分を削除。
${変数名%%接尾パターン}
接尾パターンに対応する部分を削除。最長一致。
${変数名/パターン/置き換え文字列}
パターンに一致する部分を置き換え文字列で置き換える。
${変数名//パターン/置き換え文字列}
パターンに一致する全ての部分を置き換え文字列で置き換える。
${#変数名}
変数の値は文字数や要素数に置き換える。

数値範囲

<begin-end>
数値範囲内で一致するもの。
[begin-end]
[]は文字の一致。
user% ls <01-09>.jpg
1 2 3 8 9
{begin..end}
指定した範囲の数値の文字列に置き換え。
user% touch {1..10}
user% ls
1  10  2  3  4  5  6  7  8  9  
user% touch {01..10}
user% ls
01  02  03  04  05  06  07  08  09  10

OS 設計・実装 -メモリ管理-

投稿日:
タグ:

OSの設計と実装に関して勉強した際のメモ。テキストにはLinuxカーネル2.6解読室を主に使用。Operating Systems Design and Implementationも時々読んだ。本稿は特にプロセスやメモリに関連するメモである。前提として,タネンバアム先生のモダンオペレーティングシステムパタヘネ関係の知識が必要。

仮想メモリ

UNIX系OSでは,各プロセスに実際のメモリ(物理メモリ)を仮想化した仮想メモリを提供する。物理メモリがシステムで唯一の存在であるのに対し,仮想メモリはプロセス分だけ存在する。例えば伝統的な32ビットアーキテクチャ用のUNIX系OSでは,メモリアドレスが32ビット長であり,プロセスは4GBのメモリ空間(0から232-1の範囲のアドレス)を利用できる。しかし,実際にあるプロセスのアドレス0x00402310と,別のプロセスのアドレス0x00402310は,それぞれ別の物理アドレスを使用している。

仮想メモリは次のような特性や機能を持つ。

  • 実際のメモリサイズよりも大きなメモリ空間
  • マルチプログラミング
    • 各プロセスは独立したメモリ空間を持つ。
  • メモリ保護機能
    • カーネルを除くプロセスは基本的に他のプロセスのメモリ空間にアクセスできない。
    • セクションセグメントなどと呼ばれるメモリ領域毎に読み書きや実行などを制限。
仮想メモリを提供する単純な設計では,OSはメモリをページという一定のサイズごとにロードしたり追い出したり(これをスワップページングという)することで,物理メモリのサイズを隠蔽する。メモリアドレスは,ページ番号とページ内オフセットと呼ばれるフィールドから構成され,仮想ページ番号から物理ページ番号に変換することでアドレス変換が行われる。例えば仮想アドレスが32ビット長の場合,1ページサイズが4KBのシステムでは各フィールドは以下のようになる(4KBのページ内のアドレスを表現するのに必要なのが12ビットだから)。
3130292827 2625242322 2120191817 1615141312 1110987 65432 10
ページ番号 ページ内オフセット
この時,仮想アドレス0x00402310のアドレス変換は,仮想ページ番号0x00402に対応する物理ページ番号に変換される。例えば物理アドレス空間が64ビット長で,仮想ページ番号0x00402が物理ページ番号0x0123456789ABCDEに対応するとすれば,物理アドレスは0x0123456789ABCDE310となる。

仮想ページ番号から物理ページ番号への変換には,ページテーブルという対応表を使用する。ページテーブルはプロセス毎に1つ存在し,プロセスがメモリにアクセスするとMMU(Memory Management Unit)というハードウェアによって自動でアドレス変換が行われる。ページテーブルはメモリ空間上に存在し,OSによって生成される。MMUはページテーブルの先頭アドレス(物理アドレス)を指すレジスタを持ち,それを利用してページテーブルにアクセスする。ページテーブルは,仮想ページ毎に次のような情報を持つ。

  • 物理ページ番号
  • ディスク上の位置
  • 有効ビット(メモリ上にロード済かどうか)
  • 読み込み可能かどうか
  • 書き換え可能かどうか
  • 実行可能かどうか
  • ダーティビット(ページが書き換えられたかどうか)
  • 参照ビット(最近参照されたかどうか。ページを追い出す基準に使われる)
仮想ページ番号有効物理ページ番号読み込み可能書き込み可能実行可能
0x00000オン0xABCDEオンオンオフ
0x00001オン0xFF001オンオフオフ
0x00002オフ
32ビットアーキテクチャでページサイズが4KBの時のページテーブルの単純な実装をC++で表現するならば,このような感じである。
struct page_table_entry{
  paddr_t phisical_page;// 物理ページ番号かディスク上のアドレス
    // ここでpaddr_tは物理アドレスを表現できるサイズの符号なし整数型を表す
  bool valid;		// 有効ビット
  bool readable;	// 読み込み可能
  bool writable;	// 書き込み可能
  bool executable;	// 実行可能
  bool dirty;		// ダーティビット
  bool refference;	// 参照ビット
  page_table_entry() :valid(false) {}
} page_table[1<<20]; // ページテーブル 1<<20==2**20
仮想ページ番号をインデックスとしてアクセスすることで物理ページ番号を取得できる。
if (page_table[virtual_page].valid==true)
  page_table[virtual_page].phisical_page
ページテーブルは全ての仮想ページに対応するエントリ(1048576個)を持つが,実際にプロセスが全てのページを使用することはまずない(psコマンドやtopコマンドで使用中のプロセスの仮想メモリと物理メモリサイズを確認できる)。例えばLinuxで"ps -u"コマンドでpsが使用している物理メモリが1260KBの時,使用しているエントリは315,使用しているエントリは全体の0.03%である。それゆえ,実際のOSの実装でも全てのエントリをいきなり生成するようにはなっていない。


多段ページテーブル

Linuxは多段ページテーブルを使用する。多段ページでは,仮想アドレスは次のようにさらに細かいフィールドに分けられる(フィールドのサイズは不定)。

3130292827 2625242322 2120191817 1615141312 1110987 65432 10
ページグローバルディレクトリ ページミドルディレクトリ ページテーブル ページ内オフセット
0x0 0x04 0x02 0x310
多段ページテーブルは,2種類のテーブルからなる。
  • テーブルを指すテーブル
  • ページテーブル
そして次のように1つのテーブルを根とした木構造になる。
テーブルを指すテーブルのエントリが有効ビットがオフである時,それは複数のページのエントリの有効ビットがオフであることに相当する。例えば,ページグローバルディレクトリ(1番左のテーブル)の0xF番目のエントリの有効ビットがオフであれば,1階層のページテーブルの65536個のエントリの有効ビットがオフであることと同じ意味を持つ。

前述のようにC++で表現しようとすると次のようにな感じになる。

page_table_entry **page_table[1<<4]={NULL};

page_table[0x0] = new page_table_entry* [1<<8];

page_table[0x0][0x04] = new page_table_entry[1<<8];
page_table[0x0][0xF0] = new page_table_entry[1<<8];
1階層ページ
if (page_table[0x00402].valid==true)
page_table[0x00402].phisical_page
3階層ページ
if (page_table[0x0]!=NULL && page_table[0x04]!=NULL && 
      page_table[0x02].valid==true)
page_table[0x0][0x04][0x02].phisical_page
この例ではテーブルは全部で4つあるが,それらのエントリ数の合計は784個である。1階層の場合のエントリ数1048576個に対して遥かに少ないのが分かるだろう。このようにして,多段ページテーブルは無駄なエントリの生成を回避する。


Linuxのページテーブルの実装

Linuxの多段ページテーブルは,次のようなフィールドからなる。

バージョン2.6.12以降
ページグローバルディレクトリ ページアッパーディレクトリ ページミドルディレクトリ ページテーブル ページ内オフセット
バージョン2.6.11以前
ページグローバルディレクトリ ページミドルディレクトリ ページテーブル ページ内オフセット

linux-2.6.12.1/mm/memory.cの782-812行目
     pgd_t *pgd; // pdg_tはページグローバルディレクトリのエントリ
     pud_t *pud; // pdg_tはページアッパーディレクトリのエントリ
     pmd_t *pmd; // pdg_tはページミドルディレクトリのエントリ
     pte_t *ptep, pte;// pte_tはページテーブルのエントリ
     unsigned long pfn;
     struct page *page;

    page = follow_huge_addr(mm, address, write);
    if (! IS_ERR(page))
    return page;

    pgd = pgd_offset(mm, address);	// オフセット取得 
    if (pgd_none(*pgd) ||		// エントリの有無の確認
        unlikely(pgd_bad(*pgd)))	// エントリが不正かどうか確認
        goto out;

    pud = pud_offset(pgd, address);
    if (pud_none(*pud) || unlikely(pud_bad(*pud)))
        goto out;

    pmd = pmd_offset(pud, address);
    if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd)))
        goto out;
    if (pmd_huge(*pmd))
	return follow_huge_pmd(mm, address, pmd, write);

    ptep = pte_offset_map(pmd, address); // 仮想アドレスに対応するページテーブルエントリのアドレスを返す
    if (!ptep)
        goto out;

    pte = *ptep;
    pte_unmap(ptep); // pte_offset_map後に呼ぶ
linux-2.6.12.1/include/asm-*/pgtable.h
オフセットの取得やエントリの持つ情報は,アーキテクチャ依存であり,アーキテクチャごとに関数やマクロなどが定義されている。 例えばi386ならばasm-i386内の,sparcならばasm-sparc内のpgtable.hでマクロとして定義されていたり,関数の宣言があったりする。
関数/マクロ説明
pte_dirtyページが書き換えられたか判定。
pte_youngページがアクセスされたか判定。
pte_mkyoungページがアクセスされたという情報をクリア。
pte_mkexecページが実行不能にする。
以下にいくつかのマクロの実装例を示す(asm-arm/pgtable.h)。
#define L_PTE_PRESENT           (1 << 0)
#define L_PTE_FILE              (1 << 1)        /* only when !PRESENT */
#define L_PTE_YOUNG             (1 << 1)
#define L_PTE_BUFFERABLE        (1 << 2)        /* matches PTE */
#define L_PTE_CACHEABLE         (1 << 3)        /* matches PTE */
#define L_PTE_USER              (1 << 4)
#define L_PTE_WRITE             (1 << 5)
#define L_PTE_EXEC              (1 << 6)
#define L_PTE_DIRTY             (1 << 7)
#define pte_read(pte)           (pte_val(pte) &L_PTE_USER)
#define pte_write(pte)          (pte_val(pte) &L_PTE_WRITE)
#define pte_exec(pte)           (pte_val(pte) &L_PTE_EXEC)

#define pte_none(pte)           (!pte_val(pte))

#define pte_dirty(pte)          (pte_val(pte) &L_PTE_DIRTY)
これらの名前のマクロは,アーキテクチャによっては同名の関数の場合もある。
linux-2.6.12.1/include/asm-*/pgalloc.h
関数/マクロ説明
pgd_allocページグローバルディレクトリの作成。
pgd_freeページグローバルディレクトリの解放。
pgd_noneページグローバルディレクトリのエントリが空かどうかの判定。
pte_noneページテーブルのエントリが空かどうかの判定。
以下にマクロの実装例を示す(asm-arm/pgalloc.h)。
extern pgd_t *get_pgd_slow(struct mm_struct *mm);

#define pgd_alloc(mm)                   get_pgd_slow(mm)
#define pgd_free(pgd)                   free_pgd_slow(pgd)

#define pmd_alloc_one(mm,addr)          ({ BUG(); ((pmd_t *)2); })
#define pmd_free(pmd)                   do { } while (0)
これらの名前のマクロは,アーキテクチャによっては同名の関数の場合もある。


ページフォルト

アクセスしたページがメモリ上にロードされていなかった(有効ビットがオフだった)場合,MMUはページフォルト割り込み(例外)を発生する。ページフォールトが発生すると,ハードウェアかソフトウェアによってメモリにロードされる。Linuxの場合はOSがその処理を行う。ページフォルトが発生した場合のOSの処理は次のような流れである。

  1. 仮想アドレスを使用してページテーブルのエントリを参照し,参照された物理ページが格納されたディスクの位置を見つけ出す。
  2. ページ置き換えアルゴリズムにしたがって,置き換え対象の物理ページを選択し,選択したページのダーティビットがオンである場合,先にその物理ページをディスクに書き戻す。
  3. 参照されたページをディスクから選択した物理ページに入れる。
ディスクアクセスを待っている間,プロセッサは通常別のプロセスを実行する。

Linuxでは,ページフォルト例外の処理はdo_page_fault関数で実装され,そのコードはアーキテクチャに依存する。例えばarmならば,arch/arm/mm/fault.cで,x86_64ならばarch/x86_64/mm/fault.cで定義される。

TLB

MMUの処理を高速化するために,プロセッサは,頻繁にアクセスするページテーブルのエントリを格納するTLB(Transfer Lookaside Buffer)というキャッシュメモリを持つ。TLBの各エントリは基本的にページテーブルと同様の内容であるが,有効ビットだけは意味が異なる。TLBに格納されたページは必ずページテーブルにも存在し,かつメモリ上に存在する。もしそのページがディスクに追い出される場合,TLBのそのエントリはクリアされる。TLBの有効ビットは命令やデータを格納するキャッシュメモリ同様,そのエントリが使用されているかどうかを表す。

TLBはプロセスごとに生成されるページテーブルとは異なり,全てのプロセスで共通して使用される。そして,各プロセスはそれぞれ同じ仮想アドレスを使用する。それゆえ,プロセス切り替えの際にTLBのエントリをクリアしなければ,あるプロセスのメモリ空間に対し,他のプロセスがアクセスするような事態に陥る。TLBのエントリの追加はハードウェアによって自動的に行われるが,TLB情報のクリアはソフトウェア(OS)によって行われる。

include/asm-*/tlbflush.h
関数/マクロ説明
flush_tlb_allTLBのすべてのエントリをフラッシュ。
flush_tlb_mm指定した仮想アドレス空間に関連するエントリをフラッシュ。
flush_tlb_range指定した仮想アドレス空間の中の指定したアドレス範囲のエントリをフラッシュ。
flush_tlb_page指定した仮想アドレス空間の中の指定した1ページのエントリをフラッシュ。
以下にマクロの実装例を示す(asm-i386/tlbflush.h)。
#define __flush_tlb()							\
	do {								\
		unsigned int tmpreg;					\
									\
		__asm__ __volatile__(					\
			"movl %%cr3, %0;              \n"		\
			"movl %0, %%cr3;  # flush TLB \n"		\
			: "=r" (tmpreg)					\
			:: "memory");					\
	} while (0)

# define __flush_tlb_all()						\
	do {								\
		if (cpu_has_pge)					\
			__flush_tlb_global();				\
		else							\
			__flush_tlb();					\
	} while (0)


#define flush_tlb() __flush_tlb()
#define flush_tlb_all() __flush_tlb_all()
これらのマクロは,アーキテクチャによっては同名の関数かもしれないし,その逆かもしれない。

アーキテクチャによってはキャッシュメモリで使用されるアドレスは,仮想アドレスであり,そのようなアーキテクチャの場合,OSによるクリアが必要である。

include/asm-*/cacheflush.h
関数/マクロ説明
flush_cache_mm指定した仮想アドレス空間に関連するキャッシュをクリア。
flush_cache_range指定した仮想アドレス空間の中の指定したアドレス範囲のキャッシュをクリア。
flush_cache_page指定した仮想アドレス空間に関連するキャッシュをクリア。
以下にマクロの実装例を示す(asm-arm/cacheflush.h)。
static inline void flush_cache_mm(struct mm_struct *mm)
{
        if (cpu_isset(smp_processor_id(), mm->cpu_vm_mask))
                __cpuc_flush_user_all();
}

static inline void
flush_cache_range(struct vm_area_struct *vma, unsigned long start,
unsigned long end)
{
        if (cpu_isset(smp_processor_id(), vma->vm_mm->cpu_vm_mask))
                __cpuc_flush_user_range(start &PAGE_MASK,
		PAGE_ALIGN(end),
                                        vma->vm_flags);
}

static inline void
flush_cache_page(struct vm_area_struct *vma, unsigned long user_addr,
unsigned long pfn)
{
        if (cpu_isset(smp_processor_id(), vma->vm_mm->cpu_vm_mask)) {
                unsigned long addr = user_addr &PAGE_MASK;
                __cpuc_flush_user_range(addr, addr + PAGE_SIZE,
		vma->vm_flags);
        }
}
これらのマクロは,アーキテクチャによっては同名の関数かもしれないし,その逆かもしれない。ちなみにi386系は物理アドレスが使用されるため,何もしない実装となっている(共通コードの関係上マクロは呼び出されるが)。以下に"asm-i386/cacheflush.h"の一部を示す。
#define flush_cache_all()                       do { } while (0)
#define flush_cache_mm(mm)                      do { } while (0)
#define flush_cache_range(vma, start, end)      do { } while (0)
#define flush_cache_page(vma, vmaddr, pfn)      do { } while (0)


ユーザ空間(セグメント)

典型的なUNIX系OSのユーザ空間は下図のようなレイアウトである。

MIN
テキスト
データ
ヒープ
共有ライブラリテキスト
共有ライブラリデータ
スタック
MAX
テキスト領域
コードが格納される領域。
データ領域
静的なデータ(Cのグローバル変数やstatic変数)が格納される領域。静的なデータは初期値が0のものととそれ以外に分類され,それぞれメモリの初期化方法が異なる(0であればサイズだけ分かれば良いため)。前者の領域はBSSセクションやBSSセグメントと呼ばれる。アーキテクチャによっては書き換え不能なRODATA領域もある。
ヒープ領域
動的メモリ確保で使用される(Cのmallocで使用される)領域。アドレスの大きな方に向かって領域を広げる。
スタック領域
プログラム(主に関数)によって自動で確保される領域。Cではローカル変数や演算の返り値などの一時データを格納するのに使用される。ある関数が使用しているスタック領域の範囲をフレームという。静的変数が常に同じアドレスであるのに対し,ローカル変数はフレームの先頭位置か終了位置から相対的にアドレスを指す。それゆえ,ローカル変数は毎回アドレスが違う。
スタック
フレームA
フレームB
ちなみにこのような仕組みのおかげで関数の再帰呼び出しが可能である。
STACK領域はアドレスの小さい方に向かって領域を広げていき,その間のメモリ空間は動的ライブラリをリンクする際に使用される。なおプロセスはTEXT領域より上部やSTACKより下部の空間を使用できない。

プログラムが使用する仮想アドレスはリンカによって解決され,プログラムを実行する際にプログラムローダ(OSの機能)によってメモリ上に運ばれる。

ページテーブルは,forkシステムコールによって親のページテーブルを複製したものが使用される。

コピーオンライト

書き込み時にメモリコピーを行うことでメモリ管理の利用効率を高める方法。初期のUNIXでは,forkが呼ばれた度にページテーブルや使用するページの複製を生成し,ページテーブルが指すページを新たに生成したものに変更していた。

親プロセス
仮想メモリ物理メモリ読み込み可能書き込み可能
0x000000x0100Atruefalse
0x000010x03A08truefalse
...
0xFFFFE0x8FC07truetrue
0xFFFFF0x9F05Ctruetrue
子プロセス
仮想メモリ物理メモリ読み込み可能書き込み可能
0x000000x0208Atruefalse
0x000010x08531truefalse
...
0xFFFFE0x2031Btruetrue
0xFFFFF0x7F3B1truetrue
しかしこの方法では,実際に使用されないメモリ空間まで生成されてしまうため,無駄なメモリ割り当てやコピー処理,ページテーブルの修正が発生する。そこで現在は,まず親のページテーブルの書き込み権限をオフにし,そのページテーブルを単純に複製したものを子プロセスのページテーブルとする。
親プロセス
仮想メモリ物理メモリ読み込み可能書き込み可能
0x000000x0100Atruefalse
0x000010x03A08truefalse
...
0xFFFFE0x8FC07truefalse
0xFFFFF0x9F05Ctruefalse
子プロセス
仮想メモリ物理メモリ読み込み可能書き込み可能
0x000000x0100Atruefalse
0x000010x03A08truefalse
...
0xFFFFE0x8FC07truefalse
0xFFFFF0x9F05Ctruefalse
この方法では読み込みでは内容を共有し,親か子で書き込みが発生した場合に,例外を発生させ,そこではじめてメモリの割り当てやコピー,子プロセスのページテーブルの物理メモリ部分の修正,そして2つのページテーブルの書き込み権限の修正を行う。

fork

UNIX系OSでは,forkシステムコールを使用して親プロセスのコピーを作ることでプロセスを生成する。親プロセスの領域から子プロセスの領域へのコピーは,copy_mm関数で行われている。

kernel/fork.c
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
{
	struct mm_struct * mm, *oldmm;
	int retval;

	tsk->min_flt = tsk->maj_flt = 0;
	tsk->nvcsw = tsk->nivcsw = 0;

	tsk->mm = NULL;
	tsk->active_mm = NULL;

	/*
	 * Are we cloning a kernel thread?
	 *
	 * We need to steal a active VM for that..
	 */
	oldmm = current->mm;
	if (!oldmm)
		return 0;
プロセス空間を共有する場合は,mm_struct構造体を共有するため,親プロセスのmm_struct構造体のmm_usersメンバをインクリメントするだけで良い。

	if (clone_flags & CLONE_VM) {
		atomic_inc(&oldmm->mm_users);
		mm = oldmm;
		/*
		 * There are cases where the PTL is held to ensure no
		 * new threads start up in user mode using an mm, which
		 * allows optimizing out ipis; the tlb_gather_mmu code
		 * is an example.
		 */
		spin_unlock_wait(&oldmm->page_table_lock);
		goto good_mm;
	}
  1. mm_struct構造体の割り当て。mm_cachepスラブから割り当てを受ける。
  2. mm_structオブジェクトのメンバの初期化。start_codeなど,プログラムに関するメンバは親プロセスのものがコピーされる。
  3. ページグローバルディレクトリの割り当て。
  4. コンテキストの割り当て。
  5. プロセス空間のコピー。dup_mmap関数(kernel/fork.c)によって vm_area_structオブジェクトの割り当てと設定。コピーオンライトを行うため,vm_area_structにVM_MAYWRITEが設定されている場合,親も子も書き込み禁止にする。

	retval = -ENOMEM;
	mm = allocate_mm();
	if (!mm)
		goto fail_nomem;

	/* Copy the current MM stuff.. */
	memcpy(mm, oldmm, sizeof(*mm));
	if (!mm_init(mm))
		goto fail_nomem;

	if (init_new_context(tsk,mm))
		goto fail_nocontext;

	retval = dup_mmap(mm, oldmm);
	if (retval)
		goto free_pt;

	mm->hiwater_rss = get_mm_counter(mm,rss);
	mm->hiwater_vm = mm->total_vm;

good_mm:
	tsk->mm = mm;
	tsk->active_mm = mm;
	return 0;

free_pt:
	mmput(mm);
fail_nomem:
	return retval;

fail_nocontext:
	/*
	 * If init_new_context() failed, we cannot use mmput() to free the mm
	 * because it calls destroy_context()
	 */
	mm_free_pgd(mm);
	free_mm(mm);
	return retval;
}

mmap

UNIX系OSは,ファイルの一部,または全体をプロセス空間にマップしてメモリの一部のように使用するファイルマップ機能がある。ユーザはファイルマップをmmapシステムコールを使用して行うことができる。

通常,ファイルのアクセスはreadシステムコールやwriteシステムコールを使用して行う。この時,カーネルはファイルのデータをカーネル内のバッファ(ファイルキャッシュ)にコピーした上で,プロセス空間にコピーする。mmapはこのバッファをページ単位として,プロセス空間にマップすることで,ファイルマップを行う。mmapはdo_mmap関数として実装される。do_mmap関数はmmapシステムコールのためだけでなく,メモリ領域を確保する場合にも使用される。do_mmap関数はmm/mmap.cのdo_mmap_pgoff関数を呼び出し,これが実際の処理を行う。

exec

UNIX系OSでは,execシステムコールを使用することで,任意のプログラムを実行できる。execでは,新たなmm_struct構造体が割り当てられ,古いmm_struct構造体が解放される。新しいmm_struct構造体にはvm_area_struct構造体やプログラムに関するメンバも未設定で,これらを新しく設定する。execはプロセスのスタック領域とヒープ領域を確保し,テキスト領域やデータ領域は,カーネルのdo_mmap関数によってファイルマップされる。

brk

brkはヒープ領域の拡張を行うシステムコールであり,mm/mmap.cのdo_brk関数として実装される。do_brkは前述のdo_mmap_pgoff関数の簡略版である。

exit

exitはプロセスの終了時に呼び出されるシステムコールである。プロセス空間の領域はmm/mmap.cのexit_mmap関数で行われる。

  1. ページテーブルに書かれた実ページやスワップ領域の解放。
  2. ページテーブルの解放。
  3. ファイル構造体の参照カウントをデクリメントしたりvm_area_struct構造体の解放したりする。
mm/mmap.c
/* Release all mmaps. */
void exit_mmap(struct mm_struct *mm)
{
	struct mmu_gather *tlb;
	struct vm_area_struct *vma = mm->mmap;
	unsigned long nr_accounted = 0;
	unsigned long end;

	lru_add_drain();

	spin_lock(&mm->page_table_lock);

	flush_cache_mm(mm);
	tlb = tlb_gather_mmu(mm, 1);
	/* Use -1 here to ensure all VMAs in the mm are unmapped */
	end = unmap_vmas(&tlb, mm, vma, 0, -1, &nr_accounted, NULL); // 実ページやスワップ領域の解放
	vm_unacct_memory(nr_accounted);
	free_pgtables(&tlb, vma, FIRST_USER_ADDRESS, 0); // ページテーブルの解放
	tlb_finish_mmu(tlb, 0, end);

	mm->mmap = mm->mmap_cache = NULL;
	mm->mm_rb = RB_ROOT;
	set_mm_counter(mm, rss, 0);
	mm->total_vm = 0;
	mm->locked_vm = 0;

	spin_unlock(&mm->page_table_lock);

	/*
	 * Walk the list again, actually closing and freeing it
	 * without holding any MM locks.
	 */
	while (vma) {
		struct vm_area_struct *next = vma->vm_next;
		remove_vm_struct(vma);
		vma = next;
	}

	BUG_ON(mm->nr_ptes > (FIRST_USER_ADDRESS+PMD_SIZE-1)>>PMD_SHIFT);
}


カーネル空間

カーネルもまた基本的には仮想アドレス空間上で動作する。i386系のカーネル空間レイアウトは次の通りである。

MIN
ユーザ空間
カーネル空間ストレートマップ
カーネル仮想領域
HIGHMEMアクセス領域
固定マップ領域
MAX
ストレートマップ領域
物理メモリにアクセスするための領域。カーネルは全ての実メモリにアクセスできる必要がある。そしてそのためにストレートマップ領域では「仮想アドレス=実アドレス+PAGE_OFFSET」の関係でマッピングされている(PAGE_OFFSETはマクロで,ストレートマップ領域の先頭アドレスを表す)。i386系では最大896MBのサイズで,実メモリがそれ以上の場合,HIGHMEMアクセス領域にマッピングされる。
HIGHMEMアクセス領域
ストレートマップ領域にマッピングできなかった実メモリにアクセスするための領域。HIGHMEMアクセス領域は専用のインタフェースによってアクセスする。そのインタフェースは,指定したページがストレートマップ領域ならば仮想アドレスを,HIGHMEMであればページテーブルにマッピングして,その仮想アドレスを返す。
固定マップ領域
固定の仮想アドレスで任意の実アドレスにアクセスするための領域。固定マップ領域の用途はinclude/asm-*/fixmap.hの「enum fixed_addresses」を参照されたし。
include/asm-i386/fixmap.h
enum fixed_addresses {
	FIX_HOLE,
	FIX_VSYSCALL,
#ifdef CONFIG_X86_LOCAL_APIC
	FIX_APIC_BASE,	/* local (CPU) APIC) -- required for SMP or not */
#endif
#ifdef CONFIG_X86_IO_APIC
	FIX_IO_APIC_BASE_0,
	FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS-1,
#endif
#ifdef CONFIG_X86_VISWS_APIC
	FIX_CO_CPU,	/* Cobalt timer */
	FIX_CO_APIC,	/* Cobalt APIC Redirection Table */ 
	FIX_LI_PCIA,	/* Lithium PCI Bridge A */
	FIX_LI_PCIB,	/* Lithium PCI Bridge B */
#endif
#ifdef CONFIG_X86_F00F_BUG
	FIX_F00F_IDT,	/* Virtual mapping for IDT */
#endif
#ifdef CONFIG_X86_CYCLONE_TIMER
	FIX_CYCLONE_TIMER, /*cyclone timer register*/
#endif 
#ifdef CONFIG_HIGHMEM
	FIX_KMAP_BEGIN,	/* reserved pte's for temporary kernel mappings */
	FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,
#endif
#ifdef CONFIG_ACPI_BOOT
	FIX_ACPI_BEGIN,
	FIX_ACPI_END = FIX_ACPI_BEGIN + FIX_ACPI_PAGES - 1,
#endif
#ifdef CONFIG_PCI_MMCONFIG
	FIX_PCIE_MCFG,
#endif
	__end_of_permanent_fixed_addresses,
	/* temporary boot-time mappings, used before ioremap() is functional */
#define NR_FIX_BTMAPS	16
	FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
	FIX_BTMAP_BEGIN = FIX_BTMAP_END + NR_FIX_BTMAPS - 1,
	FIX_WP_TEST,
	__end_of_fixed_addresses
};
カーネル仮想領域
カーネルが自身の作業をするのに使用する領域。以下に示す関数などを使用して,必要に応じて仮想メモリを割り当て,そこで作業を行う。いくつかのアーキテクチャでは,カーネルモジュールをロードするために使用される。
include/linux/vmalloc.h
extern void *vmalloc(unsigned long size);
extern void vfree(void *addr);
extern void *vmap(struct page **pages, unsigned int count,
			unsigned long flags, pgprot_t prot);
extern void vunmap(void *addr);
ストレートマップ領域の後からHIGHMEMアクセス領域の前までにある。カーネル仮想領域の開始位置と終了位置は次のマクロで定義されている。
マクロ説明
VMALLOC_START開始位置
VMALLOC_END終了位置


実ページ管理

Linuxは実ページを階層的に管理する。

ノード
Linuxでは,同じメモリアクセス特性を持つ物理メモリ領域毎に管理することで,さまざまなアーキテクチャに対応させ,かつそれらを効率的に利用する仕組みがある。
NUMA(Non-Uniform Memory Access)システム
共有メモリ型マルチプロセッサのうち,全てのプロセッサとメモリの距離が一定でないもの。NUMAアーキテクチャでは,プロセッサとメモリの対のことをノードという。NUMAシステムは複数のノードから構成され,全てのノードはインターコネクトで接続されている。NUMAシステムでは,プロセッサとメモリの距離に応じてアクセス速度が異なる(すなわちメモリアクセス特性が異なる)。 例えば,あるノードのプロセッサが同じノードのメモリ(ローカルメモリ)にアクセスするのと,別のノードのメモリ(リモートメモリ)にアクセスするのではローカルメモリの方が速い。ページ管理では,アクセス頻度が高い領域をローカルメモリに,アクセス頻度が低い領域をリモートメモリに割り当てた方が高い性能が期待できる。
非連続メモリ領域
複数の物理メモリ領域を持つアーキテクチャの場合,各物理メモリ領域毎に管理することで対応する(物理的なメモリ数ではなくアドレス空間が複数ある場合の話)。
PC/AT互換機の場合,ノード数は1つである(一般的なPCはPC/AT互換機)。ノードはpglist_data構造体で管理される。
ゾーン
Linuxでは,実メモリはアドレスに応じてゾーンに分類される。ゾーンの種類はアーキテクチャによって異なる。例えばia32やx86_64では以下のようになる。
ゾーン領域ia32
ZONE_DMAストレートマップ領域16MB未満
ZONE_NORMAL16MB以上896MB未満
ZONE_HIGHMEMその他の領域896MB以上
linux-2.6.12.1/include/linux/mmzone.h
/*
 * On machines where it is needed (eg PCs) we divide physical memory
 * into multiple physical zones. On a PC we have 3 zones:
 *
 * ZONE_DMA       < 16 MB       ISA DMA capable memory
 * ZONE_NORMAL  16-896 MB       direct mapped by the kernel
 * ZONE_HIGHMEM  > 896 MB       only page cache and user processes
 */
#define ZONE_DMA                0
#define ZONE_NORMAL             1
#define ZONE_HIGHMEM            2
ゾーンはzone構造体で管理される。

カーネルは「struct pglist_data」のポインタであるpgdat_listという変数を持ち,これを使用して各ノードを管理する。

pglist_data構造体(include/linux/mmzone.h)
pglist_dataはノード毎にメモリを管理するための制御表である。メンバには次のようなものがある。
メンバ説明
node_zonesノードに属するゾーン
node_zonelists空きページ獲得時の検索順
nr_zonesノードに属するゾーン数
node_mem_mapノードに属するページ
node_idノード番号
pgdat_nextノードリスト用の次へのポインタ
typedef struct pglist_data {
	struct zone node_zones[MAX_NR_ZONES];
	struct zonelist node_zonelists[GFP_ZONETYPES];
	int nr_zones;
	struct page *node_mem_map;
	struct bootmem_data *bdata;
	unsigned long node_start_pfn;
	unsigned long node_present_pages; /* total number of physical pages */
	unsigned long node_spanned_pages; /* total size of physical page
					     range, including holes */
	int node_id;
	struct pglist_data *pgdat_next;
	wait_queue_head_t kswapd_wait;
	struct task_struct *kswapd;
	int kswapd_max_order;
} pg_data_t;
前述したZONE_DMAやZONE_NORMALなどのマクロは,node_zonesの添字として使用される。
zone構造体(include/linux/mmzone.h)
ゾーンはzone構造体で管理される。
メンバ説明
free_pagesフリーなページ数
pages_min空きページの確保量(min<low<high)
pages_low
pages_high
zone_mem_mapゾーンに属するページ
free_areaバディシステム管理
nameゾーン名
struct zone {
	/* Fields commonly accessed by the page allocator */
	unsigned long		free_pages;
	unsigned long		pages_min, pages_low, pages_high;
	/*
	 * We don't know if the memory that we're going to allocate will be freeable
	 * or/and it will be released eventually, so to avoid totally wasting several
	 * GB of ram we must reserve some of the lower zone memory (otherwise we risk
	 * to run OOM on the lower zones despite there's tons of freeable ram
	 * on the higher zones). This array is recalculated at runtime if the
	 * sysctl_lowmem_reserve_ratio sysctl changes.
	 */
	unsigned long		lowmem_reserve[MAX_NR_ZONES];

	struct per_cpu_pageset	pageset[NR_CPUS];

	/*
	 * free areas of different sizes
	 */
	spinlock_t		lock;
	struct free_area	free_area[MAX_ORDER];


	ZONE_PADDING(_pad1_)

	/* Fields commonly accessed by the page reclaim scanner */
	spinlock_t		lru_lock;	
	struct list_head	active_list;
	struct list_head	inactive_list;
	unsigned long		nr_scan_active;
	unsigned long		nr_scan_inactive;
	unsigned long		nr_active;
	unsigned long		nr_inactive;
	unsigned long		pages_scanned;	   /* since last reclaim */
	int			all_unreclaimable; /* All pages pinned */

	/*
	 * prev_priority holds the scanning priority for this zone.  It is
	 * defined as the scanning priority at which we achieved our reclaim
	 * target at the previous try_to_free_pages() or balance_pgdat()
	 * invokation.
	 *
	 * We use prev_priority as a measure of how much stress page reclaim is
	 * under - it drives the swappiness decision: whether to unmap mapped
	 * pages.
	 *
	 * temp_priority is used to remember the scanning priority at which
	 * this zone was successfully refilled to free_pages == pages_high.
	 *
	 * Access to both these fields is quite racy even on uniprocessor.  But
	 * it is expected to average out OK.
	 */
	int temp_priority;
	int prev_priority;


	ZONE_PADDING(_pad2_)
	/* Rarely used or read-mostly fields */

	/*
	 * wait_table		-- the array holding the hash table
	 * wait_table_size	-- the size of the hash table array
	 * wait_table_bits	-- wait_table_size == (1 << wait_table_bits)
	 *
	 * The purpose of all these is to keep track of the people
	 * waiting for a page to become available and make them
	 * runnable again when possible. The trouble is that this
	 * consumes a lot of space, especially when so few things
	 * wait on pages at a given time. So instead of using
	 * per-page waitqueues, we use a waitqueue hash table.
	 *
	 * The bucket discipline is to sleep on the same queue when
	 * colliding and wake all in that wait queue when removing.
	 * When something wakes, it must check to be sure its page is
	 * truly available, a la thundering herd. The cost of a
	 * collision is great, but given the expected load of the
	 * table, they should be so rare as to be outweighed by the
	 * benefits from the saved space.
	 *
	 * __wait_on_page_locked() and unlock_page() in mm/filemap.c, are the
	 * primary users of these fields, and in mm/page_alloc.c
	 * free_area_init_core() performs the initialization of them.
	 */
	wait_queue_head_t	* wait_table;
	unsigned long		wait_table_size;
	unsigned long		wait_table_bits;

	/*
	 * Discontig memory support fields.
	 */
	struct pglist_data	*zone_pgdat;
	struct page		*zone_mem_map;
	/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
	unsigned long		zone_start_pfn;

	unsigned long		spanned_pages;	/* total size, including holes */
	unsigned long		present_pages;	/* amount of memory (excluding holes) */

	/*
	 * rarely used fields:
	 */
	char			*name;
} ____cacheline_maxaligned_in_smp;
page構造体(include/linux/mm.h)
実メモリはpage構造体で管理される。 ページ管理に使用される構造体のメンバの一部を紹介する。
メンバ説明
flagsページの状態(フラグはinclude/linux/page-flags.hで定義されている)
_countページの参照カウント
_mapcountアドレス空間にマッピングされている数
mappingファイルキャッシュとして使用されているとき,そのアドレススペース構造体
indexページ単位のファイルオフセット,またはスワップ領域のインデックス(mappingがスワップキャッシュの場合)
lruLRUリスト用
virtualkmapされているとき,その仮想アドレス
以下に実際のstruct pageの定義部分を示す。
struct page {
	page_flags_t flags;		/* Atomic flags, some possibly
					 * updated asynchronously */
	atomic_t _count;		/* Usage count, see below. */
	atomic_t _mapcount;		/* Count of ptes mapped in mms,
					 * to show when page is mapped
					 * & limit reverse map searches.
					 */
	unsigned long private;		/* Mapping-private opaque data:
					 * usually used for buffer_heads
					 * if PagePrivate set; used for
					 * swp_entry_t if PageSwapCache
					 * When page is free, this indicates
					 * order in the buddy system.
					 */
	struct address_space *mapping;	/* If low bit clear, points to
					 * inode address_space, or NULL.
					 * If page mapped as anonymous
					 * memory, low bit is set, and
					 * it points to anon_vma object:
					 * see PAGE_MAPPING_ANON below.
					 */
	pgoff_t index;			/* Our offset within mapping. */
	struct list_head lru;		/* Pageout list, eg. active_list
					 * protected by zone->lru_lock !
					 */
	/*
	 * On machines where all RAM is mapped into kernel address space,
	 * we can simply calculate the virtual address. On machines with
	 * highmem some memory is mapped into kernel virtual memory
	 * dynamically, so we need a place to store that address.
	 * Note that this field could be 16 bits on x86 ... ;)
	 *
	 * Architectures with slow multiplication can define
	 * WANT_PAGE_VIRTUAL in asm/page.h
	 */
#if defined(WANT_PAGE_VIRTUAL)
	void *virtual;			/* Kernel virtual address (NULL if
					   not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
};
ページフラグ(include/linux/page-flags.h)
ページのフラグ(page構造体のflagsメンバ)で使用される値は次の通りである。
名前説明
PG_lockedロックされている
PG_errorIO中のエラー発生
PG_referenced参照された
PG_uptodate読み込みIO完了
PG_dirty書き込みがあった
PG_writebackライトバック中
PG_reclaimスワップ処理中
PG_lruLRUリストにつながっている
PG_checkedチェック済み(filesystemで使用)。
PG_swapcacheスワップキャッシュとして使用
以下に前述のマクロらの定義部分を示す。
#define PG_locked                0      /* Page is locked. Don't touch. */
#define PG_error                 1
#define PG_referenced            2
#define PG_uptodate              3

#define PG_dirty                 4
#define PG_lru                   5
#define PG_active                6
#define PG_slab                  7      /* slab debug (Suparna wants this) */

#define PG_highmem               8
#define PG_checked               9      /* kill me in 2.5.<early>. */
#define PG_arch_1               10
#define PG_reserved             11

#define PG_private              12      /* Has something at ->private */
#define PG_writeback            13      /* Page is under writeback */
#define PG_nosave               14      /* Used for system suspend/resume */
#define PG_compound             15      /* Part of a compound page */

#define PG_swapcache            16      /* Swap page: swp_entry_t in private */
#define PG_mappedtodisk         17      /* Has blocks allocated on-disk */
#define PG_reclaim              18      /* To be reclaimed asap */
#define PG_nosave_free          19      /* Free, should not be written */
#define PG_uncached             20      /* Page has been mapped as uncached */

空きページの管理(バディシステム)

バディシステムは空きページを2のべき乗の単位で管理する。

  • 20=1
  • 21=2
  • 22=4
  • 23=8
  • ...
  • 211=2048
空きページはzone構造体(include/linux/mmzone.h)のfree_areaメンバで管理される。
        struct free_area        free_area[MAX_ORDER];
MAX_ORDERもまたinclude/linux/mmzone.hで定義される。以下はバージョン2.6.12.1の場合の例である。
include/linux/mmzone.h
#define MAX_ORDER 11

struct free_area {
        struct list_head        free_list;
        unsigned long           nr_free;
};
include/linux/list.h
struct list_head {
        struct list_head *next, *prev; 
};
空きページフレームの管理は,バージョン2.6.10以前とそれより後では管理方法が異なる。前者はビットマップで,後者はリスト構造で管理される。free_listの各要素はそれぞれ2の添字番号ページの空きページセットである。

実ページの獲得,解放のインタフェースはpage構造体やアドレスを返す。これらの関数はmm/page_alloc.cやinclude/linux/gfp.hらで定義されている。

関数備考
unsigned long __get_free_pages(unsigned int __nocast gfp_mask, unsigned int order)
struct page *alloc_pages(unsigned int __nocast gfp_mask, unsigned int order)gfp_maskのフラグについてはinclude/linux/gfp.hの__GFP_やGFP_の接頭辞のマクロを参照。
fastcall void __free_pages(struct page *page, unsigned int order)
fastcall void free_pages(unsigned long addr, unsigned int order)
ページの獲得処理はmm/page_alloc.cの__alloc_pages関数で実装されている。


参考URL

SPEC CPU 2000を手動でコマンドライン実行

投稿日:
タグ:

通常SPEC CPUの各プログラムは,そのセットに付いたスクリプトで実行できるが,シミュレータなんかで実行する場合,コマンドラインで手動でそれを実行しなければならない。本稿はそのまとめ。各ベンチマークのSpec/object.pmやNew Page 1ソフトウェア/SimpleScalar/プログラム実行のシミュレーション方法SpecINT 2000 Commandlines | Ken Barrを参考にやり方だけをまとめた。

入力データにはrefを使用。なお以下で使用しているアプリケーション名は,実際のものとは異なる。

CINT2000

164.gzip
user% ./cpu2000/CINT2000/164.gzip/exe/gzip cpu2000/CINT2000/164.gzip/data/ref/input/input.source 60
175.vpr
user% ./cpu2000/CINT2000/175.vpr/exe/vpr ./cpu2000/CINT2000/175.vpr/data/ref/input/net.in ./cpu2000/CINT2000/175.vpr/data/ref/input/arch.in ./cpu2000/CINT2000/175.vpr/data/ref/input/place.in ./cpu2000/CINT2000/175.vpr/data/ref/output/route.out -nodisp -route_only -route_chan_width 15 -pres_fac_mult 2 -acc_fac 1 -first_iter_pres_fac 4 -initial_pres_fac 8
176.gcc
user% ./cpu2000/CINT2000/176.gcc/exe/gcc ./cpu2000/CINT2000/176.gcc/data/ref/input/200.i -o ./cpu2000/CINT2000/176.gcc/data/ref/output/200.s
181.mcf
user% ./cpu2000/CINT2000/181.mcf/exe/mcf ./cpu2000/CINT2000/181.mcf/data/ref/input/inp.in 
186.crafty
user% ./cpu2000/CINT2000/186.crafty/exe/crafty < ./cpu2000/CINT2000/186.crafty/data/ref/input/crafty.in
197.parser
必要なファイルをカレントディレクトリにコピー。
user% cp -r ./cpu2000/CINT2000/197.parser/data/all/input/work .
user% ./cpu2000/CINT2000/197.parser/exe/parser ./cpu2000/CINT2000/197.parser/data/all/input/2.1.dict -batch < ./cpu2000/CINT2000/197.parser/data/ref/input/ref.in 
252.eon
どれが必要かはわからないが,*.datやmaterialsが必要らしいので,カレントディレクトリにコピー。
user% cp ./cpu2000/CINT2000/252.eon/data/ref/input/* .
user% ./cpu2000/CINT2000/252.eon/exe/eon00.peak.ev6 ./cpu2000/CINT2000/252.eon/data/ref/input/chair.control.cook ./cpu2000/CINT2000/252.eon/data/ref/input/chair.camera ./cpu2000/CINT2000/252.eon/data/ref/input/chair.surfaces ./cpu2000/CINT2000/252.eon/data/ref/output/chair.cook.ppm ppm ./cpu2000/CINT2000/252.eon/data/ref/output/pixels_out.cook
253.perlbmk
どれが必要か分からないが,カレントディレクトリに入力ファイルが必要なようである。
user% cp ./cpu2000/CINT2000/253.perlbmk/data/ref/input/{benums,lenums,cpu2000_mhonarc.rc} .
user% ./cpu2000/CINT2000/253.perlbmk/exe/perlbmk -I./cpu2000/CINT2000/253.perlbmk/data/all/input/lib ./cpu2000/CINT2000/253.perlbmk/data/all/input/diffmail.pl 2 550 15 24 23 100
254.gap
user% ./cpu2000/CINT2000/254.gap/exe/gap -l ./cpu2000/CINT2000/254.gap/data/all/input/ -q -m 192M < ./cpu2000/CINT2000/254.gap/data/ref/input/ref.in
255.vortex
どれが必要かは分からないが,カレントディレクトリに入力ファイルが必要らしいので,それをコピー。
cp ./cpu2000/CINT2000/255.vortex/data/ref/input/* .
user% ./cpu2000/CINT2000/255.vortex/exe/vortex ./cpu2000/CINT2000/255.vortex/data/ref/input/lendian1.raw
256.bzip2
user% ./cpu2000/CINT2000/256.bzip2/exe/bzip2 ./cpu2000/CINT2000/256.bzip2/data/ref/input/input.source 58
300.twolf
user% ./cpu2000/CINT2000/300.twolf/exe/twolf ./cpu2000/CINT2000/300.twolf/data/ref/input/ref

CFP2000

168.wupwise
設定ファイルをコピー。
user% cp ./cpu2000/CFP2000/168.wupwise/data/ref/input/wupwise.in .
user% ./cpu2000/CFP2000/168.wupwise/exe/wupwise
171.swim
user% ./cpu2000/CFP2000/171.swim/exe/swim < ./cpu2000/CFP2000/171.swim/data/ref/input/swim.in
172.mgrid
user% ./cpu2000/CFP2000/172.mgrid/exe/mgrid < ./cpu2000/CFP2000/172.mgrid/data/ref/input/mgrid.in
173.applu
user% ./cpu2000/CFP2000/173.applu/exe/applu < ./cpu2000/CFP2000/173.applu/data/ref/input/applu.in
177.mesa
user% ./cpu2000/CFP2000/177.mesa/exe/mesa -frames 1000 -meshfile  ./cpu2000/CFP2000/177.mesa/data/ref/input/mesa.in -ppmfile ./cpu2000/CFP2000/177.mesa/data/ref/output/mesa.ppm
178.galgel
user% ./cpu2000/CFP2000/178.galgel/exe/galgel < ./cpu2000/CFP2000/178.galgel/data/ref/input/galgel.in
179.art
user% ./cpu2000/CFP2000/179.art/exe/art -scanfile ./cpu2000/CFP2000/179.art/data/ref/input/c756hel.in -trainfile1 ./cpu2000/CFP2000/179.art/data/ref/input/a10.img -trainfile2 ./cpu2000/CFP2000/179.art/data/ref/input/hc.img -stride 2 -startx 110 -starty 200 -endx 160 -endy 240 -objects 10
183.equake
user% ./cpu2000/CFP2000/183.equake/exe/equake < ./cpu2000/CFP2000/183.equake/data/ref/input/inp.in
187.facerec
どれが必要か分からないが,allかrefまたはその両方の入力ファイルがカレントディレクトリに必要らしいので,それをコピー。
user% cp cpu2000/CFP2000/187.facerec/data/all/input/* .
user% cp cpu2000/CFP2000/187.facerec/data/ref/input/* .
user% ./cpu2000/CFP2000/187.facerec/exe/facerec < ./cpu2000/CFP2000/187.facerec/data/ref/input/ref.in
188.ammp
ammp.inに書かれたファイルをカレントディレクトリにコピー。
user% cp ./cpu2000/CFP2000/188.ammp/data/ref/input/all.init.ammp ./cpu2000/CFP2000/188.ammp/data/ref/input/init_cond.run.* .
user% ./cpu2000/CFP2000/188.ammp/exe/ammp < ./cpu2000/CFP2000/188.ammp/data/ref/input/ammp.in
189.lucas
user% ./cpu2000/CFP2000/189.lucas/exe/lucas < ./cpu2000/CFP2000/189.lucas/data/ref/input/lucas2.in
191.fma3d
設定ファイルをコピー。
user% cp ./cpu2000/CFP2000/191.fma3d/data/ref/input/fma3d.in .
user% ./cpu2000/CFP2000/191.fma3d/exe/fma3d
200.sixtrack
入力データをカレントディレクトリにコピーする必要がある。allかref,またはその両方のデータをコピーする。
user% cp cpu2000/CFP2000/301.apsi/data/ref/input/fort.* .
user% cp cpu2000/CFP2000/301.apsi/data/all/input/fort.* .
user% ./cpu2000/CFP2000/200.sixtrack/exe/sixtrack < ./cpu2000/CFP2000/200.sixtrack/data/ref/input/inp.in
301.apsi
設定ファイルをコピー。
user% cp cpu2000/CFP2000/301.apsi/data/ref/input/apsi.in .
user% ./cpu2000/CFP2000/301.apsi/exe/apsi

videoタグとaudioタグ

投稿日:
編集日:
タグ:

本稿はvideoタグとaudioタグに関するメモ。 参考にしたものとしては,このページなどを利用した。

videoタグ

動画を扱うためのタグ。どうやらWebブラウザはちゃんとバッファリングしてくれるようだった。

動画はWebで検索して見つかった動画素材を使用。ソースコードはこのような感じである。
<video controls width="320" height="240">
<source src="./downloads/a63/test.mp4">
<p>ご利用のWebブラウザでは再生できません。</p>
</video>
videoタグやaudioタグで囲んだものはそのタグをサポートしていないブラウザ向けの内容を書く。

再生できる動画フォーマットはWebブラウザに依存するが,videoタグはsourceタグを使用することでいくつかの候補を用意することができる。

<video controls autoplay poster="firstframe.jpg" width="320"
height="240">
<source src="./sample.flv">
<source src="./sample.mp4">
<p>ご利用のWebブラウザでは再生できません。</p>
</video>
videタグの属性には次のようなものがある。
controls
再生やシーク,音量などのインタフェースを表示。
autoplay
読み込み次第再生。
poster
初期画面。
width
横のサイズ。
height
縦のサイズ。

CSSやJavascriptを利用したコメント付き動画のサンプル

videoタグやCSS,Javascriptを使用すればニコニコ動画のように動画にコメントが流れるようなものも生成できる。以下にサンプルを示す。なお停止やシークの移動によりコメントが戻るような機能はついていない。また,サンプルでは追加されたタグは削除されないので注意。

CSS
div#comments {
  position:absolute;
  width:420px;
  height:200px;
  color:black;
  z-index:1;
  font-size:20px;
  text-align:center;
  opacity:0.7;
}

video#comments_video {
    z-index:0;
    position:absolute;
    padding-left:100px;
}
Javascript
const ID = 0;
const COMMENT = 1;
const START = 2;
const LEFT = 3;
const TOP = 4;
const DEFAULT_LEFT = 420
const COMMENT_NUM = 11;
var comments = [
    // [element, comment, start_time, default_left, top]
    [null, "comment0", 0, DEFAULT_LEFT, 60],
    [null, "comment1", 1000, DEFAULT_LEFT, 40],
    [null, "comment2", 1000, DEFAULT_LEFT, 80],
    [null, "comment3", 2000, DEFAULT_LEFT, 40],
    [null, "comment4", 3000, DEFAULT_LEFT, 140],
    [null, "comment5", 5000, DEFAULT_LEFT, 40],
    [null, "comment6", 5000, DEFAULT_LEFT, 80],
    [null, "comment7", 5000, DEFAULT_LEFT, 100],
    [null, "comment8", 6000, DEFAULT_LEFT, 80],
    [null, "comment9", 6500, DEFAULT_LEFT, 100],
    [null, "comment10", 7000, DEFAULT_LEFT, 160]
];
var rtime;
var time=0;
var min = 0;
var index=0;

function controlComments()
{
    var child;
    
    time += 50;
    if (index < COMMENT_NUM)
	while (comments[index][START] < time){
	    comments[index][ID] = document.createElement("span");
	    child = comments[index][ID];
	    document.getElementById("comments").appendChild(child)
	    child.innerHTML = comments[index][COMMENT];
	    child.style.color = "black";
	    child.style.position = "absolute";
	    child.style.left = comments[index][LEFT] + "px";
	    child.style.top = comments[index][TOP] + "px";
	    document.getElementById("comments").appendChild(comments[index][ID])
	    index += 1;
	}
    
    
    for (i=min;i<index;i++){
	if (comments[i][LEFT] > 0){
	    comments[i][LEFT] -= 5;
	    comments[i][ID].style.left = comments[i][LEFT] + "px";
	}else{
   	    // TODO: spanタグの削除
	    if (i>min)
		min = i;
	}
    }
}

function doPlay()
{
    rtime = setInterval('controlComments()', 50);
}

function doPause()
{
    clearInterval(rtime);
}
html5
<div id="comments">
</div>

<video id="comments_video" controls poster="firstframe.jpg" onplay="doPlay();" onpause="doPause()" width="320" height="240">
<source src="./downloads/a63/test.mp4">
<p>動画を再生するにはvideoタグをサポートしたブラウザが必要です。</p>
</video>

audioタグ

音楽を扱うためのタグ。 audioタグもvideoタグ同様controls属性を使えるが,以下にjavascriptやhtml,cssでインタフェースを作成した例を示す。



曲はWebで検索したものから適当なページを選択。

css3
hr {
    display:none;
}

section#music_controller {
    background-color:black;
    text-align:center;
    width:20em;
    padding:1em;
}

select {
    background-color:#111111;
    color:white;
    width:20em;
}

#music_seek {
    height:2em;
    width:20em;
}

.cir_btn {
    color:red;
    background-color:black;
    text-align:center;
    width:3em;
    height:3em;
    margin:0;
    padding:2px;
    border-radius: 30px;
    -moz-border-radius: 30px;
    -webkit-border-radius: 30px;
}
javascript
var playing = false;	// 再生中か否か
var rtime;		// シークバーの管理
var idx=0;		// 音楽番号
var MUSIC_NUM=3;	// 曲数
var MUSIC_LIST = [	// 曲名一覧
    "http://pied-piper.net/note/downloads/a63/Brahms-Symphony-No1-1st.mp3",
    "http://pied-piper.net/note/downloads/a63/Ravel-Bolero.mp3",
    "http://pied-piper.net/note/downloads/a63/Beethoven-SymNo7-1.mp3"
];


/* 再生位置をシークバーの位置に設定 */
function jumpMusicSeek()
{
    document.getElementById("my_audio").currentTime = document.getElementById("music_seek").value;
}


/* 曲名を指定した番号に変更 */
function changeMusic(i)
{
    if (playing)
	stopMusic();
    
    idx = i;
    document.getElementById("my_audio").src = MUSIC_LIST[idx];
    document.getElementById("music_list").selectedIndex = idx;
}


/* 選択された曲を再生 */
function selectMusic()
{
    if (playing)
	stopMusic();
    
    idx = document.getElementById("music_list").selectedIndex;
    document.getElementById("my_audio").src = MUSIC_LIST[idx];
    
    playMusic();
}


/* シークバーを進める */
function advanceMusicSeek()
{
    var audio = document.getElementById("my_audio");
    if (audio.ended){
	idx++;
	if (idx<MUSIC_NUM){
	    changeMusic(idx);
	    playMusic();
	}else{
	    idx = 0;
	    changeMusic(idx);
	}
    }
    document.getElementById("music_seek").value = audio.currentTime;
}


/* 次の曲を再生 */
function nextMusic()
{
    idx++;
    if (idx >= MUSIC_NUM){
        idx = 0;
    }
    if (playing){
        changeMusic(idx);
        playMusic();
    }else{
        changeMusic(idx);
    }
}


/* 前の曲を再生 */
function prevMusic()
{
    idx--;
    if (idx < 0){
        idx = MUSIC_NUM-1;
    }
    if (playing){
        changeMusic(idx);
        playMusic();
    }else{
        changeMusic(idx);
    }
}


/* 曲を再生/一時停止 */
function playMusic()
{
    if (playing){
	document.getElementById("play_button").innerHTML = "▶";
	document.getElementById("my_audio").pause();
	playing = false;
	clearInterval(rtime);
    }else{
	var audio;
	var seek = document.getElementById("music_seek");
	audio = document.getElementById("my_audio");
	audio.play();
	document.getElementById("play_button").innerHTML = "||";
	playing = true;
	rtime = setInterval("advanceMusicSeek()", 500);
    }
}


/* 再生中の音楽を停止 & 再生位置を0に */
function stopMusic()
{
    var audio = document.getElementById("my_audio");
    if (playing){
	audio.pause();	
	document.getElementById("play_button").innerHTML = "▶";
	document.getElementById("my_audio").pause();
	playing=false;
	clearInterval(rtime);
    }
    audio.currentTime = 0.0;
    document.getElementById("music_seek").value = audio.currentTime;
}


/* 早送り */
function ffMusic()
{
    audio = document.getElementById("my_audio");
    audio.currentTime += 2.0;
    document.getElementById("music_seek").value = audio.currentTime;
}


/* 巻き戻し */
function rewindowMusic()
{
    audio = document.getElementById("my_audio");
    audio.currentTime -= 2.0;
    document.getElementById("music_seek").value = audio.currentTime;
}


/* 曲の再生時間を設定 */
function setMaxTime()
{
    document.getElementById("music_seek").max = document.getElementById("my_audio").duration;
}
html5
<div id="music_player">
<section id="music_controller">
<button id="prev_button" onClick="prevMusic()" class="cir_btn">|◀◀</button>
<button id="rewind_button" onClick="rewindMusic()" class="cir_btn">◀◀</button>
<button id="play_button" onClick="playMusic()" class="cir_btn">▶</button>
<button id="stop_button" onClick="stopMusic()" class="cir_btn">■</button>
<button id="ff_button" onClick="ffMusic()" class="cir_btn">▶▶</button>
<button id="next_button" onClick="nextMusic()" class="cir_btn">▶▶|</button>

<br>

<input id="music_seek" type="range" onChange="jumpMusicSeek()" min="0.0" value="0.0">

<br>

<select id="music_list" onChange="selectMusic()" size="3">




<option selected>ブラ1</option>
<option>ボレロ</option>
<option>ベト7</option>
</select>
</section>

<audio id="my_audio" src="http://www.sousound.com/music/jazz_fusion/jazz_01.mp3" onloadeddata="setMaxTime();">
<p>このブラウザはaudioタグをサポートしていません</p>
</audio>
</div>

メモリ

投稿日:
タグ:

メモリ関連のメモ。

信号(1か0か)の記憶。入力や出力は次のように実装できる。

S-Rラッチ(NOR型)
S
Set信号
R
Reset信号
SRQ¬Q
00Q¬Q
1010
0101
11
ラッチ
クロックがアサートされれば入力の変化に応じていつでも状態が変更される。
フリップフロップ
基本的にラッチと同じだが,クロックのエッジ(立ち上がりか立ち下がりのどちらか)でのみ状態が変更される。フリップフロップはラッチをベースに構成されることが多い。
DラッチやDフリップフロップは,データ入力信号の値を内部メモリに記憶する。
Dラッチ
D
Data
C
Clock
Dフリップフロップ


レジスタファイル
レジスタ群で構成され,レジスタ番号を指定して読み書きする。Dフリップフロップを並べたレジスタ配列と,読み込みと書き込みの各ポートのデコーダで実現できる。

ROM(Read Only Memory)
読み出し専用メモリ。
PROM(Programmable ROM)
プログラム可能なROM。電気的に内容を書き込むことができる。
消去及び書き換え可能なPROM(erasable PROM)
紫外線を使用して内容を消去して書き換えることができるPROM。それには時間がかかるので通常はROMとして使用。消去・書き換えは設計やデバッグのときだけ。
RAM(Random Access Memory)
読み書き可能なメモリ。

大容量の記憶媒体はSRAMまたはDRAMで構築される。

SRAM(Static RAM)
典型的なCMOS SRAMセル(記憶ビット)はトランジスタ6個で構成されるが,DRAMよりも高速アクセス可能なRAM。電源供給さえしていればずっと記憶情報が保持される。
DRAM(Dynamic RAM)

記憶ビットあたり1個のトランジスタで実装されるRAM。DRAMはキャパシタに電荷を蓄えることで情報を記録し,電源供給がなくなると記憶情報が失われる。また,時間の経過でも漏洩して失われるのでリフレッシュ(取り出して再び格納する機能)が必要であり,その分動的消費電力も高い。このリフレッシュ動作をもって,Dynamic RAMと呼ばれる。現在プロセッサではなくメモリコントローラがリフレッシュを行う。

DRAMでは,まず行アクセス,ついで列アクセスによってアドレスを指定する。また,そのための方法としてRAS(Row Access Strobe)とCAS(Column Access Strobe)という一対の信号が使用する。

ECC(Error Correcting Code:誤り訂正コード)

1ビットのパリティ・コードを持つ場合1ビットの誤り検出が可能であるが,ECC機能を持つ場合,2ビットの誤り検出や1ビットの誤り訂正ができる。

SSRAM(Synchronous SRAM)
SDRAM(Synchronous DRAM)
配列または行中の一連の順次アドレスからバースト(burst)を転送できる。シンクロナスRAMが高速性の利点を持つ理由は,アドレス・ビットを追加せずに,バースト内の一連のビットを転送できるため。アドレス・ビットを繰り返し指定する代わり,シンクロナスRAMでは,クロック周波数を使用してバースト内の一連のビットを転送する。アドレス・ビットを指定する必要がないため,データ・ブロック転送速度が大幅に改善された。
DDR-SDRAM(Double Data Rate SDRAM)
データ入出力をクロックの立ち上がりと立ち下がりの両方で行うことで,SDRAMの2倍のデータ転送速度となる。

MySQLの基礎

投稿日:
修正日:
タグ:

MySQLの使い方に関するメモ。試した環境はDebianやWindowsである。

SQLの構成

SQL(Structured Query Language)とはRDB(Relational Data Base)において,データの操作や定義,制御を行うためのデータベース言語である。RDBは日本語で関係データベースと呼ばれるもので,1つ以上の表から構成される。表の行はデータの基本単位であり,レコードとも呼ばれる。同じように表の列は属性とも呼ばれる。SQLの利用方法は次の3つがある。

  • 対話的に処理。
  • プログラムに埋め込む。
  • 自動生成ツールで使用。

本稿では「対話的に処理」する場合の方法を説明する。

また,SQLの文は次の3つに分類できる。

データ定義文(DDL: Data Definition Language)
データベースやテーブルの作成や削除など。
データ操作文(DML: Data Manupulation language)
テーブルの検索や変更など。
データ制御文(DCL: Data Control Language)
トランザクション関連の処理(コミットやロールバック)。

インストール

Debian7.10 amd64では,

user% sudo aptitude install mysql-server
または
user% sudo apt-get install mysql-server
で,WindowsではGUIのインタフェースでインストールする。また両者ともMySQLのためのルートパスワードの設定が求められる。

Windowsのコマンドプロンプトで実行する場合,更に環境変数Pathにmysql.exeがあるディレクトリの場所を追加しなければならない。環境変数の設定は,次の手順で行う。

  1. エクスプローラで[コンピュータ]を開く。
  2. [システム・プロパティ]を選択。
  3. [システムの詳細設定]を選択。
  4. [詳細]タブを選択。
  5. [環境変数]を選択。
  6. [編集]でPathを選択。
  7. ;で区切って追加するパス(mysql.exeが格納されたディレクトリの位置)を追加する。

インストールや初期の設定が終了後,

user% mysql -u root -p
とコマンドを打ち込み,パスワードが求められてそれを入れてMySQLのプロンプトが出ればインストール成功である。

日本語

文字列に日本語を使うには設定が必要である。私の環境では(Debian7.10 amd64),/etc/mysql/my.cnfの[〜]の各セクションに,それぞれ次の行を追加した。

[client]
default-character-set=utf8

[mysqld]
character-set-server=utf8

[mysqldump]
default-character-set=utf8

[mysql]
default-character-set=utf8

その後mysqlを再起動する。

/etc/init.d/mysql restart
SQLを使う際の注意点
  • 命令や名前の大文字小文字は区別しない(本稿では可読性のためSQLのキーワードは大文字で記述する)。
  • 文字列はシングルクォーテーション(')で囲う。
  • いくつかの変更するための処理(述語)は,変更処理だけ行い変更後の表を画面に表示しない。もしユーザが変更結果を知りたい場合,参照するための処理を指定する必要がある。
サンプルRDBの作成

本稿の説明では主に次の表を使って説明する。

社員表(sampledb.employee)
社員番号(id)名前(name)年齢(age)性別(sex)血液型(blood)
1001山田太郎40B
1002佐藤一郎29A
1003鈴木花子35AB
2001伊藤月子30A
2002齋藤次郎36O
2003高橋雪34A
3001後藤三郎19O

前述の表を生成する場合,以下のコードをコピーアンドペーストすれば良い。

CREATE DATABASE sampledb;
CREATE TABLE sampledb.employee
(id 	INTEGER	        PRIMARY KEY NOT NULL,
 name 	CHAR(30)        NOT NULL,
 age 	INTEGER	        ,
 sex 	CHAR(3)	        ,
 blood 	CHAR(2)	        );
INSERT INTO sampledb.employee VALUES (1001, '山田太郎', 40, '男', 'B');
INSERT INTO sampledb.employee VALUES (1002, '佐藤一郎', 29, '男', 'A');
INSERT INTO sampledb.employee VALUES (1003, '鈴木花子', 35, '女', 'AB');
INSERT INTO sampledb.employee VALUES (2001, '伊藤月子', 30, '女', 'A');
INSERT INTO sampledb.employee VALUES (2002, '齋藤次郎', 36, '男', 'O');
INSERT INTO sampledb.employee VALUES (2003, '高橋雪', 34, '女', 'A');
INSERT INTO sampledb.employee VALUES (3001, '後藤三郎', 19, '男', 'O');

ここでこれらの文の見方については言及しない。


データ操作文(DML: Data Manupulation language)

参照と変更

DMLは表に対する処理のことであり,主に参照系と変更系の2種類に分類できる。

参照系(select)
社員表から社員番号と名前,性別の列を表示。
SELECT id,name,sex FROM sampledb.employee;
この述語は次のように構成される。
SELECT 列 FROM 表
表はいずれかのデータベースに属し,"データベース.表"のように指定するか,USEコマンドで現在使用するデータベースを変更することで,表だけ記述できる。
USE sampledb;
SELECT id,name,sex FROM employee;
社員表の一覧(全ての列)を表示。
SELECT * FROM sampledb.employee;
社員表から年齢(age)が30歳以上のヒトを検索(出力)。
SELECT * FROM sampledb.employee WHERE age >= 30;
社員表から男性でかつ年齢(age)が30歳以上のヒトを検索(出力)。
SELECT * FROM sampledb.employee WHERE sex='男' && age >= 30;
社員表から女性または年齢(age)が30歳以上のヒトを検索(出力)。
SELECT * FROM sampledb.employee WHERE sex='女' || age >= 30;
社員表から男性で年齢(age)が20歳から30歳までのヒトを検索(出力)。
SELECT * FROM sampledb.employee WHERE age BETWEEN 20 AND 30;
または
SELECT * FROM sampledb.employee WHERE 20 <= age && age <= 30;
社員表から〜子で終わる名前のヒトを検索(出力)。
SELECT * FROM sampledb.employee WHERE name LIKE '%子';
または正規表現を使って
SELECT * FROM sampledb.employee WHERE name REGEXP '.*子';
社員表の一覧を年齢の昇順で表示
SELECT * FROM sampledb.employee ORDER BY age;
社員表の一覧を年齢の降順で表示
SELECT * FROM sampledb.employee ORDER BY age DESC;
社員表の一覧から血液型ごとに平均年齢を表示。
SELECT blood,AVG(age) FROM sampledb.employee GROUP BY blood;
社員表の一覧から血液型ごとに平均年齢(30歳以上)を表示。
SELECT blood,AVG(age) FROM sampledb.employee GROUP BY blood HAVING blood > 30;
変更系(update,insert,delete)
社員表に社員番号1004番の田中三郎,年齢18歳,血液型O型を追加。
INSERT INTO sampledb.employee VALUES (1004, '田中三郎', 18, 'O');
列を指定。
INSERT INTO sampledb.employee(id, name, age) VALUES (1005, '衛宮四郎', 18);
INSERTは
INSERT INTO 表[(列, ...)] VALUES (値, ...);
社員表の社員番号1001番の年齢を30歳に変更。
UPDATE sampledb.employee SET age=30 WHERE id=1001;
UPDATE 表 SET 列=値[,列=値];
ただしUPDATEやDELETEはWHERE句を指定しなければ全てのレコードに対して処理を行うので,必ずWHERE句を付けると考えた方が無難である。
社員表の社員番号1001番の年齢を30歳に,血液型をB型に変更。
UPDATE sampledb.employee SET age=30, blood='B' WHERE id=1001;
社員表から社員番号1002のヒトの項目を削除。
DELETE FROM sampledb.employee WHERE id=1002;
DELETEは表だけ指定すれば最低限成り立つが,前述の通りそれでは全てのレコードを対象にしてしまう。
DELETE FROM 表;
集合関数

SQLではSUM(総和)やMAX(最大値),MIN(最小値),AVG(平均),COUNT(総数)を求めるような関数が用意されている。以下にそれらの関数を使用した例を示す。

社員表から社員の平均年齢を検索
select AVG(age) FROM sampledb.employee;
副問い合わせ

SQLでは()を使用することで,ある検索結果で得た情報を別の検索のために使用することができる。これを副問い合わせという。以下に例を示す。

社員表から平均年齢よりも高い社員を検索
SELECT * FROM sampledb.employee WHERE age > ( SELECT AVG(age) FROM sampledb.employee );
結合

SQLではいくつかの表を組み合わせて検索する場合があり,そのための仕組みとして結合という方法が用意されている。結合にはいくつか種類があるが,ここでは以下の3つの結合を紹介する。

内部結合(INNER JOIN)
2つの表で一致する部分を抽出。
外部結合(OUTER JOIN)
片方の表で一致する部分を抽出。
交差結合(CROSS JOIN)
2つの表の組み合わせを生成。

以下でMySQLでのそれぞれの結合の方法を紹介する。

内部結合を説明するために新たに次の表を追加する。

顧客表(sampledb.customer)
顧客番号(cid)名前(name)担当社員番号(eid)
109001本田五郎1001
109002高倉六郎1002
109003豊田松代1001
109004松田竹子1003
CREATE TABLE sampledb.customer
(cid 	INTEGER	        PRIMARY KEY NOT NULL,
 name 	CHAR(30)        NOT NULL,
 eid    INTEGER         NOT NULL);
INSERT INTO sampledb.customer VALUES (109001, '本田五郎', 1001);
INSERT INTO sampledb.customer VALUES (109002, '高倉六郎', 1002);
INSERT INTO sampledb.customer VALUES (109003, '豊田松代', 1001);
INSERT INTO sampledb.customer VALUES (109004, '松田竹子', 1003);
内部結合

複数の表に分けて管理する場合,それらのデータが冗長にならないように設計した方がデータベースのサイズは小さい。例えば,前述のデータベースでは,顧客表は担当者の番号だけを記録し,担当者の名前は社員表を見れば良い。しかし顧客名と担当者名を対応させたい時,それでは不便である。そのような時に内部結合を使用する。

顧客名と担当する社員の社員番号,担当する社員の名前を表示。
SELECT customer.name,employee.id,employee.name FROM sampledb.employee INNER JOIN sampledb.customer on employee.id=customer.eid;

関連する2つの表がある時,それらは1側と多側に分けることができる。例えば前述の社員表と顧客表においては,社員表の社員番号と顧客表の担当社員番号で関連付けることができ,社員表の社員番号が各レコードで一意に定まるの対し,顧客表は同じ内容を指しているが一意に定まることはない。

SELECT 列 FROM 1側の表 INNER JOIN 多側の表 on 関連付ける列;
関連付ける列は"表1.列=表2.列"のように指定する。 なお表示する列を指定する場合は,1側(employee.id)と多側の列(customer.eid)を指定しても同じような結果となる。
  • SELECT customer.name,employee.id,employee.name FROM sampledb.employee INNER JOIN sampledb.customer on employee.id=customer.eid;
  • SELECT customer.name,customer.eid,employee.name FROM sampledb.employee INNER JOIN sampledb.customer on employee.id=customer.eid;
しかし,1側を指定した方が検索にかかる時間は短い。

また,表の名前はASを使うことで別名を付けて簡略化することができる。

SELECT c.name,e.id,e.name FROM sampledb.employee AS e INNER JOIN sampledb.customer AS c on e.id=c.eid;

ASは表の名前だけでなく列名などにも使用可能である。例えば前述の例では顧客名も担当者名もnameで分かりづらい。そういった場合,それぞれ別名を付けて表示することができる。

SELECT c.name AS 顧客名,e.id AS ID,e.name AS 担当者名 FROM sampledb.employee AS e INNER JOIN sampledb.customer AS c on e.id=c.eid;

外部結合
前述と同じような内容を知りたい時でも,「社員がそれぞれどの顧客を担当しているか」を知りたい場合がある。当然ながら社員とはいえ,さまざまな仕事内容があり,中には顧客と直接対応しない者もいる。そのような情報が欲しい場合,外部結合を使用して片方の表に乗っている場合はレコードを表示する。
SELECT c.name,e.id,e.name FROM sampledb.employee AS e LEFT OUTER JOIN sampledb.customer AS c on e.id=c.eid;
外部結合の場合,LEFTやRIGHTといったキーワードで,ベースとする表を指定する。関連付けた列でベースとしている方にレコードが存在する場合,そのレコードを生成する。今回の例では,社員をベースに考えているため,社員表をベースとしている。なお左表と右表というのは"左表 {LEFT|RIGHT} OUTER JOIN 右表"というように記述する。今回の例でいえば,"employee LEFT OUTER JOIN customer"と"customer RIGHT OUTER JOIN employee"は同じ結果となる。
交差結合

データ定義文(DDL: Data Definition Language)

SQLサーバは複数のデータベースを,データベースは複数の表を,表は複数のレコードを持つ。DMLがレコードの参照・変更・追加・削除などであったが,DDLはデータベースや表に対してそのような処理を行う。

MySQLで表を生成するまでの流れは次の通りである。

  1. データベースの作成。
    CREATE DATABASE sampledb
  2. テーブルの作成。
    ID id: 整数名前 name: char(20) 年齢 age: 整数血液型 blood: char(2)
    CREATE TABLE テーブル名 
    (属性名	データ型	値の制約,
     属性名	データ型	値の制約
     ...);
    

    テーブル名は現在使用しているデータベースのものであれば,直接テーブル名を指定できるが,それ以外の場合は「データベース名.テーブル名」とする。現在使用中のテーブルはUSE文で変更できる。

    USE データベース名;

    以下にテーブルの生成例を示す。

    CREATE TABLE sampledb.employee
    (id	INTEGER		PRIMARY KEY NOT NULL,
     name	CHAR(20)	NOT NULL,
     age	INTEGER		,
     blood	CHAR(2)		);
    

    CHARで指定する数字は,文字数ではなくバイト数である。英字であればその2つは同じ意味を持つが,その他の言語を利用する場合それらは文字コードによって使用するバイト数が異なる。

    生成したテーブルの列情報はDESCで確認できる。

    DESC テーブル名;

作成したデータベースやテーブルの一覧はSHOWで確認できる。

データベース
SHOW databases;
SHOW tables;
前述のテーブルの一覧表示は,現在設定されているデータベースのものが表示される。
制約

各レコードのデータ型を設定する場合,整数や文字列といったコンセプトとは別に,格納する際の条件(制約)が設定できる。制約には,ブランク(NULL)を禁止するものや,同じ列の他のレコードと値が同じになってはいけないものなどさまざまある。RDBを生成する場合,必ず1レコードだけを抽出するための列を用意するべきである。このような列を主キーという。主キーの制約には"PRIMARY KEY NOT NULL"の2つが必要である。

データベースや表の削除
  • データベースの削除。
    DROP DATABASE sampledb;
  • 表の削除。
    DROP TABLE sampledb.employee;


データ制御文(DCL: Data Control Language)

データベースには,複数のユーザが同時に処理を行うことで,データの整合性がとれない状況を防ぐ仕組みがある。代表的なものには,以下の2つがある。

排他的制御
あるユーザが処理を行う際に他のユーザが処理できないようにロックする方法。
トランザクション処理
いくつかのレコード変更処理をトランザクションという単位でまとめて扱い,最終的にそのデータを反映させる場合はコミット,取り消す場合はロールバックを行うことでデータの整合性を守る。なおMySQLではデフォルトでは自動コミットとなっており,変更系の文を記述するたびにデータの変更が行われる。

DCLはこのような制御を行う。

トランザクション処理

実際にデータベースを扱う場合,一部のデータだけ変更できれば良いわけではない。例えば購入処理では,購入者の所有数を増やすと同時に,在庫表のものを減らさなければならない。このような処理はいずれかだけできれば良い訳ではなく,必ず全て必要である。SQLでは,このような処理をトランザクションという単位でまとめて処理を行う。トランザクションの主な処理は次のような手順である。

  1. トランザクションの開始。
    START TRANSACTION;
  2. 変更処理をいろいろ(参照処理も可能だが参照処理は逐次行われる)。
  3. コミット。データを正式に反映する。
    COMMIT;
    ロールバック。トランザクション開始時からの変更処理を取り消す。
    ROLLBACK;

本稿を順番に読んだ場合,今まで見たレコードの変更系処理は,処理を記述する度にコミットされてきた。それゆえコミットのイメージは分かるものとし,ここではロールバックの例を示す。

  1. トランザクションの開始。
    START TRANSACTION;
  2. 参照。初期状態を確認(参照系)。
    SELECT * FROM sampledb.employee;
  3. 更新(変更系)。
    UPDATE sampledb.employee SET age=41 WHERE id=1001;
  4. 参照(参照系)。
    SELECT * FROM sampledb.employee;
  5. 新しいレコードを追加(変更系)。
    INSERT INTO sampledb.employee VALUES (1005, '幸田七郎', 25, '男', 'B');
  6. 参照(参照系)。
    SELECT * FROM sampledb.employee;
  7. ロールバック。前述の2つの変更系処理を取り消す。
    ROLLBACK;
  8. 参照。初期状態と同じ状態になっている。
    SELECT * FROM sampledb.employee;
自動コミットモード

UML2.4(11) シーケンス図

投稿日:
タグ:

シーケンス図

シーケンス図とは,オブジェクト(クラス)間の相互関係を時系列的に表現したダイアグラムのことである。 シーケンス図の構成要素は次の通りである。

実行指定
オブジェクトの操作が呼び出されている時間。
ライフライン
使用されていないがオブジェクトがまだ存在していることを表す。
メッセージ
同期メッセージ
呼び出し先の処理が終わる(返事が返ってくる)のを待って,処理を行う場合のメッセージパッシング。
リプライメッセージ
返事。処理が終わったことを伝えるためのメッセージ。
reply
非同期メッセージ
呼び出し先の処理を待たずに処理の継続や処理の終了を行う場合のメッセージパッシング。
複合フラグメント
種類説明
loop繰り返し文
alt条件文
ステレオタイプ

分析

概念クラス図
概念シーケンス図

設計

実装クラス図
実装シーケンス図

実装

Book.java
class Book {
  /*- 属性 -*/
  /** タイトル。 */
  private String title;
  /** 著者。 */
  private String author;

  /*- 操作 -*/
  /**
   * コンストラクタ。
   * @param title タイトル。
   * @param author 著者。
   */
  Book(String title, String author){
    this.title = title;
    this.author = author;
  }

  /**
   * 本のタイトルを取得。
   * @return 本のタイトル。
   */
   public String getTitle(){
     return this.title;
   }

  /**
   * 本の著者を取得。
   * @return 本の著者。
   */
   public String getAuthor(){
     return this.author;
   }
}
Library.java
import java.util.*;

class Library {
  /*- 属性 -*/
  /** 図書館名。 */
  private String name;
  /** 住所。 */
  private String address;
  /** 本リスト。 */
  private ArrayList<Book> book_list;


  /*- 操作 -*/
  /**
   * コンストラクタ。
   * @param name 図書館名。
   * @param address 住所。
   */
  public Library(String name, String address){
    this.name = name;
    this.address = address;
    this.book_list = new ArrayList<Book>();
  }

  /**
   * 本を借りる。
   * @param title 借りたい本のタイトル。
   */
  public Book lendBook(String title){
    Book book = searchBook(title);
    if (book!=null){
      this.book_list.remove(book);
      return book;
    }else{
      return null;
    }
  }
  
  /**
   * 本を追加する。
   * @param book 追加する本
   */
  public void addBook(Book book){
    this.book_list.add(book);
  }

  /**
   *
   */
  private Book searchBook(String title){
    Iterator<Book> it = this.book_list.iterator();
    while (it.hasNext()){
      Book book = it.next();
      if (book.getTitle()==title){
        return book;
      }
    }
    return null;
  }
}
Client.java
class Client {
  /*- 属性 -*/
  private Library lib;
  
  /*- 操作 -*/
  /** エントリポイント */
  public static void main(String args[]){
    Client client = new Client();
    client.init();
    client.exec();
  }
  /** デフォルトコンストラクタ。 */
  public Client(){
  }
  /** 1つ目の実行指定 */
  private void init(){
    final String[][] BOOKS = {{"たいとるA", "山田太郎"},
                              {"たいとるB", "鈴木次郎"},
                              {"たいとるC", "佐藤三郎"}};

    lib = new Library("市立図書館", "XXX-YYY ○○市△△町1-1-1");
    
    for (String[] book : BOOKS){
      lib.addBook(book[0], book[1]);
    }
  }
  /** 2つ目の実行指定 */
  private void exec(){
    lib.lendBook("たいとるB");
  }
};

EclipseとSubversion

投稿日:
タグ:

本稿はEclipseとSubversionを使ってグループ開発を行う際の環境構築の必要最低限のメモである。

Subversion

サーバ側の準備

サーバの環境はDebian7.1.0である(なお後述のクライアント環境も同様)。

  1. リポジトリに使用するディレクトリの設定。
    user% mkdir -p /var/svn/repos/project1
    私の環境では前述の場所はroot権限でなければ使用できないのでsudoコマンドを利用。以下の所有者の変更も同様である。
  2. ディレクトリの所有者や権限の設定。
    user% chown -R user:svn /var/svn/
    user% chmod -R 775 /var/svn/
    ここではSubversionを管理するユーザはuserで,使用するグループをsvnとして説明を行なっている。
  3. レポジトリの作成。
    user% svnadmin create /var/svn/repos/project1
クライアント側(svnコマンドの基本的な使い方)

Subversionはhttpやhttps,sshなどを使ってサーバとやり取りを行うことができる。本環境ではsshを使ったやり方で説明する。

はじめにコマンドを使ったやり方を紹介し,その後にEclipse環境での話しを行う。

  1. プロジェクトで使用するディレクトリ(project)をリポジトリにインポート。
    user% svn import ./project svn+ssh://user@server/var/svn/repos/project1
  2. 最新版を取得。
    作業スペースに移動する(以下は例)。
    user% cd ~/work
    最新版を取得する。
    user% svn co svn+ssh://user@server/var/svn/repos/project1
  3. 指定したファイルやディレクトリを登録。
    取得したディレクトリのところに移動する。
    user% cd project1
    新しいファイルを生成した場合,Subversionで管理できるようにファイルを登録する。
    user% svn add *
  4. 編集したファイルをコミット。
    user% svn commit
    または
    user% svn ci
  5. 最新版に更新。
    user% svn update
    任意のリビジョンに更新する場合はrオプションを使用する(リビジョン2に更新)。
    user% svn update -r 2
    今までのリビジョンについてはlogコマンドを使用してコメントを確認できる。
    user% svn log

Eclipse

以下にEclipseでSubversionを使う場合のプラグインの導入と実際の利用方法を紹介する。今回使用したEclipseのバージョンは3.8.0である。

プラグインの追加

はじめにEclipseからSubversionを利用するために必要なプラグインを追加する。

  1. メニューバーの[ヘルプ]から[新規ソフトウェアのインストール]を選択する。
    新規ソフトウェアのインストール
  2. もしも利用可能なサイトに,Subversion関連のものが入っていない場合,[追加]ボタンを押してサイトの追加を行う。
    インストール
    追加
    今回名前はなんでも良く,URLは次のものを利用した。
    http://download.eclipse.org/technology/subversive/0.7/update-site/
  3. Subversion関連のプラグインをインストールするためのサイトを登録したならば,続いて必要なパッケージを検索する。ここでは,「全ての利用可能なサイト」の中からSubversionに関連プラグインを全て選択している。
    インストール
    どうやら必要なのは「Subversion SVN コネクタ」らしいが,この際他のものもまとめてインストール。
  4. ライセンスの同意を行う。
  5. インストール終了後,Eclipseを再起動する。
プロジェクトの共有

作成したプロジェクトを共有するための手順は次の通りである。

  1. パッケージエクスプローラからプロジェクトを選択して右クリックする。
  2. [チーム]を選択する。
    共有0
  3. [プロジェクトの共有]を選択する。
    共有1
  4. SVNを選択する。
    共有2
  5. SubversionのサーバのURLと認証に使用するユーザ名やパスワードを入力する。
    共有3
    URLはSSH経由でSubversionを利用する場合は,"svn+ssh://domain/path"のように記述する。
  6. リポジトリに生成するディレクトリ名(URL)を指定する。
    共有4
  7. プロジェクトを共有するにあたりコメントを記述する。
    共有5
  8. 最初のコミットを行い,それに対するコメントを残す。
    共有6
インポート

共有したプロジェクトを他のユーザが利用するためにはインポートを行う。

  1. メニューバーの[ファイル]から[インポート]を選択する。
    インポート1
    またはパッケージエクスプローラから右クリックして[インポート]を選択する。
  2. インポート先のタイプとしてSubversionを選択する。
    インポート2
  3. プロジェクトを取得するために,リポジトリとユーザ名,パスワードを入力する。
    インポート3
  4. 取得するプロジェクトのパスを入力する。
    インポート4
  5. チェックアウトを行う(取得するプロジェクトのリビジョンや取得する際の名前を決める)。
    インポート5
  6. プロジェクトを生成するローカルパスを入力する。
    インポート6

以上の処理が上手くいけば,パッケージエクスプローラに新しいプロジェクトが追加されているはずである。

Subversionの利用方法

プロジェクトの共有に関係なくEclipseでSubversionを使用する場合は,パースペクティブの追加からSubversionを選択し,リポジトリブラウザを起動する。

  1. パースペクティブを追加する。
    パースペクティブ1
  2. Subversionを選択する。
    パースペクティブ2

なおこの方法については本稿ではこれ以上言及しない。

プロジェクトの共有処理

Subversionで共有されているプロジェクトの場合,プロジェクト名を選択して右クリックを押して,[チーム]を選ぶとそこからバージョン管理を行うことができる。

  1. [チーム]を選択する。
    チーム
  2. 用途に応じて選択する。
    SVNメニュー

JavaScriptコードレシピ

投稿日:
タグ:

本稿はJavascriptやHTML5,CSSを使ったコードレシピ集である(随時更新予定)。

ファイル生成

HTMLでボタンを押したら"Hello, World!"と記述されたテキストファイルを生成するには,次のようなコードを記述する。

<script>
function createFile(){
  blob = new Blob(["Hello, World!\n"]); 
  var anchor = document.createElement("a");
  var evt = document.createEvent("MouseEvent");

  anchor.download = "file.txt";
  anchor.href = (window.URL || window.webkitURL).createObjectURL(blob);
  evt.initEvent("click", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
  anchor.dispatchEvent(evt);
}
<script>
<input type="button" onClick="createFile();" value="button">
IE

IE相手にで同じような処理を行わせたい場合,msSaveBlobやmsSaveOrOpenBlobメソッドを使用する方法がある。前者は保存させたい場合に,後者は保存か開くかを選ばせたい場合に使用する。使い方は同じであるが,ここではmsSaveBlobメソッドを使用した例を示す。

function createCSV(){
  var str = "得意先コード,得意先名,電話番号,住所 \n\
AX0001,山田太郎,999-999-9999,ほげ県ふが市ぴよ町0-0 \n\
BX0002,鈴木次郎,000-000-0000,ほげ県ふが市ぴよ町1-1 \n\
CX0003,佐藤三郎,111-111-1111,ほげ県ふが市ぴよ町0-0 \n\
DX0004,衛宮四郎,222-222-2222,ほげ県ばあ市ぴよ町0-0\n";
  var blobObject = new Blob([str], { type: "text/csv" });
  window.navigator.msSaveBlob(blobObject, 'file.csv');
}

私の環境では生成されたファイルの文字コードがUTF-8になったが,それをShift_JISとして生成する方法は次の通りである。

function createSJIS(){
  var bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
  var str = "得意先コード,得意先名,電話番号,住所 \n\
AX0001,山田太郎,999-999-9999,ほげ県ふが市ぴよ町0-0 \n\
BX0002,鈴木次郎,000-000-0000,ほげ県ふが市ぴよ町1-1 \n\
CX0003,佐藤三郎,111-111-1111,ほげ県ふが市ぴよ町0-0 \n\
DX0004,衛宮四郎,222-222-2222,ほげ県ばあ市ぴよ町0-0\n";
  var blobObject = new Blob([bom, str], { type: "text/csv" });
  window.navigator.msSaveBlob(blobObject, 'hoge.csv');
}

ページ?

selectタグやinnerHTMLを使用してページを指定するようにして画面の表示を変更する例を以下に示す。

得意先コード得意先名電話番号住所
AX0001山田太郎999-999-9999ほげ県ふが市ぴよ町0-0
BX0002鈴木次郎000-000-0000ほげ県ふが市ぴよ町1-1

/2
<script>
var ary = ["AX0001山田太郎000-000-0000ほげ県ふが市ぴよ町0-0BX0001鈴木次郎000-000-0000ほげ県ふが市ぴよ町1-1","CX0001佐藤三郎000-000-0000ほげ県ふが市ぴよ町0-0DX0001衛宮四郎000-000-0000ほげ県ばあ市ぴよ町0-0"];
function changePage(){
  document.getElementById("cpage_tbody").innerHTML = ary[parseInt(this.cpage.value) - 1];
}
</script>

<div align="center">
  <table class="sep">
    <thead>
      <tr><th>得意先コード</th><th>得意先名</th><th>電話番号</th><th>住所</th></tr>
    </thead>

    <tbody id="cpage_tbody">
      <tr><td>AX0001</td><td>山田太郎</td><td>000-000-0000</td><td>ほげ県ふが市ぴよ町0-0</td></tr>
      <tr><td>BX0001</td><td>鈴木次郎</td><td>000-000-0000</td><td>ほげ県ふが市ぴよ町1-1</td></tr>
  </table>

  <br>

  <select id="cpage" name="cpage" onChange="changePage()">
    <option value="1" selected>1</option>
    <option value="2">2</option>
  </select>/2
</div>

jQueryの利用

投稿日:
修正日:
タグ:

jQueryとは,JavaScriptのコーディングを簡潔にしたり,強力にするためのライブラリである。本稿ではjQueryを使ったいろいろなサンプルを紹介する(随時更新予定)。

サンプル一覧

jQueryの基本

jQueryを使用する場合,はじめに通常のJavaScriptファイルと同様に,scriptタグを使ってファイルを読み込む。

<script type="text/javascript" src="〜">

指定するURLもまたローカルでもグローバルでも良い。

そしてjQueryを実際に利用したい場合は,$関数またはjQuery関数を使う。使い方は両者とも全く同じと考えても問題はないはず。

例えば後述するハッシュ値に変換する機能を利用したい場合,次のように記述する。

  • var str = "hoge";
    var hash = $.md5(str);
    
  • var str = "hoge";
    var hash = jQuery.md5(str);
    

また,タグに何らかの処理を行いたい場合は次のように記述する。

$("セレクタ").関数(引数)

補足すると,セレクタとは,CSSで記述しているidやクラス,擬似クラスなどのことである(hoge {color:red;}のhogeの部分)。この文では,セレクタで指定したタグに,指定した関数の処理を行う。

このような処理を行う場合,ページのDOMツリーが用意された状態でなければならない。そのため,そのような処理は次のような関数を使用する。

$(document).ready(実際に処理を行う関数);

これはwindow.onloadに似ているが,$(document).readyはwindow.onloadと異なり,HTML文書が完全にロードされなくてもHTMLをDOMツリーに変換した時点で処理される。ちなみにreadyは省略可能である。

$(function(){});

住所の補完

「郵便番号を入力すると住所を自動保管してくれるjQueryプラグイン・jquery.jpostal.js - かちびと.net」を参考。というかコードはほぼコピペ。

郵便番号 〒 -
都道府県
市区町村
町域
<div>
<script type="text/javascript" src="http://code.jquery.com/jquery-git2.js"></script>
<script type="text/javascript" src="http://jpostal.googlecode.com/svn/trunk/jquery.jpostal.js"></script>
<script type="text/javascript">
<!--
$(document).ready( function() {
  $('#postcode1').jpostal({
    postcode : [
      '#postcode1',
      '#postcode2'
    ],
    address : {
      '#prefecture'  : '%3',
      '#city'  : '%4',
      '#town'  : '%5'
    }
  });
});
-->
</script>
<form>


<span>郵便番号 〒</span>
<input id="postcode1" name="postcode1" maxlength="3">-<input id="postcode2" name="postcode2" maxlength="4"><br />

<span>都道府県</span>
<select id="prefecture" name="prefecture">
 <option value="北海道" selected>北海道</option>
 <option value="青森県">青森県</option>
 <option value="岩手県">岩手県</option>
 <option value="宮城県">宮城県</option>
 <option value="秋田県">秋田県</option>
 <option value="山形県">山形県</option>
 <option value="福島県">福島県</option>
 <option value="茨城県">茨城県</option>
 <option value="栃木県">栃木県</option>
 <option value="群馬県">群馬県</option>
 <option value="埼玉県">埼玉県</option>
 <option value="千葉県">千葉県</option>
 <option value="東京都">東京都</option>
 <option value="神奈川県">神奈川県</option>
 <option value="新潟県">新潟県</option>
 <option value="富山県">富山県</option>
 <option value="石川県">石川県</option>
 <option value="福井県">福井県</option>
 <option value="山梨県">山梨県</option>
 <option value="長野県">長野県</option>
 <option value="岐阜県">岐阜県</option>
 <option value="静岡県">静岡県</option>
 <option value="愛知県">愛知県</option>
 <option value="三重県">三重県</option>
 <option value="滋賀県">滋賀県</option>
 <option value="京都府">京都府</option>
 <option value="大阪府">大阪府</option>
 <option value="兵庫県">兵庫県</option>
 <option value="奈良県">奈良県</option>
 <option value="和歌山県">和歌山県</option>
 <option value="鳥取県">鳥取県</option>
 <option value="島根県">島根県</option>
 <option value="岡山県">岡山県</option>
 <option value="広島県">広島県</option>
 <option value="山口県">山口県</option>
 <option value="徳島県">徳島県</option>
 <option value="香川県">香川県</option>
 <option value="愛媛県">愛媛県</option>
 <option value="高知県">高知県</option>
 <option value="福岡県">福岡県</option>
 <option value="佐賀県">佐賀県</option>
 <option value="長崎県">長崎県</option>
 <option value="熊本県">熊本県</option>
 <option value="大分県">大分県</option>
 <option value="宮崎県">宮崎県</option>
 <option value="鹿児島県">鹿児島県</option>
 <option value="沖縄県">沖縄県 </option>
</select>
<br>

<span>市区町村</span>
<input type="text" id="city" name="city">
<br>

<span>町域</span>
<input type="text" id="town" name="town">
<br>

<input type="button" value="送信">
<input type="reset"  value="クリア">
</form>
</div>
住所の補完とinput:hidden

hiddenタイプの入力と組み合わせれば,前述の方法で住所を補完する場合でも1つの入力フォームの補完ができる。

郵便番号 〒 -

<script type="text/javascript">
$(function() {
  $('#postcode1').jpostal({
    postcode : [
      '#postcode1',
      '#postcode2'
    ],
    address : {
      '#prefecture'  : '%3',
      '#city'  : '%4',
      '#town'  : '%5'
    }
  });
});
</script>

<span>郵便番号 〒</span>
<input id="postcode1b" name="postcode1b" maxlength="3" style="width:3em">-<input id="postcode2b" name="postcode2b" maxlength="4" style="width:4em;">
<input type="button" value="補完" onClick="document.getElementById('addressb').value = document.getElementById('prefectureb').value + document.getElementById('cityb').value + document.getElementById('townb').value;">
<br>
<input type="text" id="addressb">
<input type="hidden" id="prefectureb" name="prefectureb">
<input type="hidden" id="cityb" name="cityb">
<input type="hidden" id="townb" name="townb">
<br>

<input type="reset"  value="クリア">
</form>
</div>

このサンプルでは,onClick属性にそのまま処理を記述しているが,当然ながら関数化した方が可読性は良い。


並び替え可能なウィジェット

タグを並び替えるjQueryには、Sortableを使用する。
  1. hoge
  2. fuga
  3. piyo
  4. foo
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
<script>
$(function() {
    $('#sortable').sortable();
});
</script>
<ol id="sortable">
 <li id="d01"><div>hoge</div></li>
 <li id="d02"><div>fuga</div></li>
 <li id="d03"><div>piyo</div></li>
 <li id="d04"><div>foo</div></li>
</ol>
説明
cursorドラッグ時のカーソルcursor: 'move'
opacityドラッグ時の透明度opacity: 0.6
updateドロップ時に実行する処理update: function(){〜}

ドラッグ可能なウィジェット

タグのドラッグ可能なjQueryには、Draggableを使用する。
ドラッグ可能
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
<script>
$(function() {
  $('#draggable').draggable({
    containment: 'parent'
  });
});
</script>
<div style="border:inset; width:100%; height: 3em;">
<div id="draggable" style="background-color:blue; width:6em;">
ドラッグ可能
</div>
</div>
説明
containmentドラッグ可能な範囲を制限containment: 'parent'
curssorドラッグ時のカーソルcursor:move
opacityドラッグ時の透明度opacity:0.8
revertドロップ後元の位置に戻すrevert:true
startドラッグ開始時の処理start: function(){〜}
dragドラッグ中の処理drag: function(){〜}
stopドラッグ終了時の処理stop: function(){〜}

Tomcat 入門

投稿日:
タグ:

本稿はtomcatの基本的な部分または最低限必要な部分に関するメモである。

Tomcatとは

Tomcatとは,Apacheソフトウェア財団謹製のJavaサーブレットコンテナである。要するにJavaでWebアプリを作成・実行するためのソフトウェアである。

Tomcat実行環境構築

私の環境(Debian)では,Tomcatは次のコマンド一発でインストール可能であった。

user% sudo aptitude install tomcat7

パッケージ管理を使わずともTomcatの公式ページからダウンロードし,それを解凍すればそれでも十分である。ただしその場合はいくつかの環境変数が必要だったり,起動するのに起動スクリプトではなくtomcatホームのbin/startup.shで起動したりする,といった違いがある。

Webアプリの作成

はじめに以下のディレクトリやファイルを作る。

ファイル(ディレクトリ)説明
hello/WEB-INF/web.xml設定ファイル
hello/WEB-INF/classes使用するクラス
hello/WEB-INF/lib使用するライブラリ(JAR)

web.xmlの中身は後述する。

次にWebアプリに必要なページを生成する。

hello/login.html
<!doctype html>
<html>
  <head>
    <title>login</title>
  </head>

  <body>
    <form action="/hello/Top" method="post">
      <input name="username" type="text">
      <input name="pass" type="password">
      <input type="submit">
    </form>
  </body>
</html>
TopServlet.java
import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;

public class TopServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse
    res) throws IOException,ServletException {
        PrintWriter pw = res.getWriter();

        res.setContentType("text/html; charset=UTF-8");

        pw.println("<html>");
        pw.println("<head><title>hoge</title></head>");
        pw.println("<body>");
        pw.println("<h1>" + req.getParameter("username") + "のページ</h1>");
        pw.println("</body>");
	pw.println("</html>");
    }
}
作成したソースファイルをコンパイルしたものをhello/WEB-INF/classes/の下に配置する。
user% javac -classpath /usr/share/tomcat7/lib/servlet-api.jar -d hello/WEB-INF/classes  TopServlet.java
classpathはライブラリのパスを指定するオプションで,-dはクラスファイルを配置するルートを指定するオプションである。-dはなくても良いが,パッケージをユーザが意識しなくて済むので使った方が便利である。
hello/WEB-INF/web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

<servlet>
  <servlet-name>Top</servlet-name>
  <servlet-class>TopServlet</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>Top</servlet-name>
  <url-pattern>/Top</url-pattern>
</servlet-mapping>

</web-app>

最後に生成したディレクトリを圧縮する。

user% cd hello/
user% jar -cvf hello.war *

これによって生成されたhello.warをTomcat実行環境のwebapps直下に配置する。

user% mv hello.war /var/lib/tomcat7/webapps/.

そして,Tomcatを起動すればWebアプリが実行される。

user% sudo service tomcat7 start
user% sudo /

特別にポート番号などを指定しない場合,デフォルトでは次のURLにアクセスすれば作成したページにアクセスできる。

http://localhost:8080/hello/login.html

おまけ

HttpServlet
GETとPOST

HTTPやHTMLのFORMタグに出てくるアクセス方法(メソッド)は,それぞれ以下のHttpServletの関数で処理する。

postdoPost
getdoGet
2つのメソッドは返却値を引数も同じであり,ほぼ同じように作成することができる。

initとdestroy

doGetやdoPostはページが開かれる度に処理される。 これに対して,Tomcatの起動時(正しくは最初のアクセス時)と終了時に処理される処理を書きたい場合,それぞれinitメソッドとdestroyメソッドをオーバーライドする。

@Override
public void init throws ServletException (){}

@Override
public void destroy(){}
forward

Webアプリでは,あるアクセスに対して,複数のサーブレットで処理を行いたい場合がある。 そのような時,次のようなコードを記述する。

FirstServlet.java(First)
public class FirstServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse
    res) throws IOException,ServletException {
        String username = req.getParameter("username");
	〜〜
	RequestDispatcher dispatch = request.getRequestDispatcher("/Second");
        req.setAttribute("username", username);
        dispatch.forward(req, res);
    }
}
SecondServlet.java(Second)
public class SecondServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse
    res) throws IOException,ServletException {
        String username = (String)req.getAttribute("username");
        〜〜
    }
}
スコープ
pageサーブレットに要求が来てから,フォワードするかレスポンスを返すまでの間
requestユーザにレスポンスを返すまでの間
session異なるリクエストを超えて保持したい場合に使用される。
applicationWebアプリケーション内
JSP

サーブレットを作成する場合,簡単な内容であればJSPを使用してより簡易に記述することができる。

<%@ page contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>hello</title>
<%
        ArrayList<String> ary = new ArrayList<String>();
        String tbody = "";
        ary.add("hoge");
        ary.add("fuga");        
        ary.add("piyo");        
        for (int i=0;i<3;i++){
          tbody += "<tr><td>" + i + "</td><td>" + ary.get(i) + "</td></tr>";
        }
%>
</head>

<body>
        <!-- 見出し -->
        <div align="center">
	<table>
	<tbody>
	<%= tbody %>
	</tbody>
	</table>
        </div>
</body>
</html>

このファイルが例えばhello/Hello.jspであれば,URLは"http://localhost:8080/hello/Hello"になる。

JSPは,以下のタグと,新たに拡張したタグを記述することで,簡単にサーブレットを作成できる。

<@ ディレクティブ名 属性="値" ... >
ディレクティブ設定を行う。例えばpageディレクティブはエンコード方式やインポートなどを行う。
<@! 〜 %>
宣言Webアプリが起動直後に1回だけ行う処理を記述する(ページを開き直しても処理されない)。
<% 〜; %>
スクリプトレットJavaのコードを記述する。
<%= 〜 %>
評価結果を出力する。
<jsp:〜 >
アクションタグ別ファイルの展開やページフォワードなどサーブレットの処理を行う
暗黙オブジェクト

JSPでは宣言せずに利用できるオブジェクトがいくつかある。以下にいくつか例を挙げる。

  • request
  • response
  • session

例えばrequestはrequest.getParameter("username")のようにして使用できる。

VPNゲートウェイ

投稿日:
タグ:

本稿はOpenVPNを使ったVPNゲートウェイ(ルータ的な意味)の設定についてのメモである。

OpenVPNの設定

OpenVPNのゲートウェイの設定を行う。

push "redirect-gateway def1"

Iptablesの設定(Linuxの場合)

OpenVPNサーバからルータへのフォワードの設定を行う。以下は、通信の許可やらフォワーディングの設定の例。デバイス名やネットワークは環境に応じたものを使用する。

iptables -A INPUT -i tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s 192.168.128.0/24 -o eth0 -j MASQUERADE

JAXB

投稿日:
タグ:

JavaでXMLを使用する際、JAXB(Java Architecture for XML Binding)を使用すると便利である。

JAXBの基本

ソース
import javax.xml.bind.JAXB;

class Bean {
    /** 属性 */
    private String val;

    /** 属性のセッター */
    public void setVal(String val){this.val = val;}
    /** 属性のゲッター */
    public String getVal(){return val;}
}


class Sample {
    public static void main(String args[]){
	Bean b = new Bean();
	
	b.setVal("hoge");
	JAXB.marshal(b, System.out);
    }
}
実行結果
user% java Sample
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bean>
<val>hoge</val>
</bean>
marshalがエンティティからXMLの文字列やXMLをストリームへ出力する際に使用する。

タグ名を指定

デフォルトではクラス名やメンバ名がタグに使用されるが、これらの値は以下のように指定することもできる。

ソース
import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlElement;

@XmlRootElement(name = "root")
class Bean {
    /** 属性 */
    private String val;


    /** 属性のセッター */
    public void setVal(String val){this.val = val;}
    /** 属性のゲッター */
    @XmlElement(name = "val1")
    public String getVal(){return val;}
}

class Sample {
    public static void main(String args[]){
	Bean b = new Bean();
	
	b.setVal("hoge");
	JAXB.marshal(b, System.out);
    }
}
実行結果
user% java Sample
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<val1>hoge</val1>
</root>

属性を設定

また、次のようにしてメンバを属性として扱うこともできる。

ソース
import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlAttribute;

@XmlRootElement(name = "root")
class Bean {
    /** 属性1 */
    private String val1;
    /** 属性2 */
    private String val2;

    /** 属性1のセッター */
    public void setVal1(String val1){this.val1 = val1;}
    /** 属性1のゲッター */
    @XmlElement(name = "val1")
    public String getVal1(){return val1;}
    /** 属性2のセッター */
    public void setVal2(String val2){this.val2 = val2;}
    /** 属性2のゲッター */
    @XmlAttribute(name = "val2")
    public String getVal2(){return val2;}
}

class Sample {
    public static void main(String args[]){
	Bean b = new Bean();
	
	b.setVal1("hoge");
	b.setVal2("fuga");
	JAXB.marshal(b, System.out);
    }
}
実行結果
user% java Sample
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root val2="fuga">
<val1>hoge</val1>
</root>

ゲッターの代わりに属性にアノテーションを付加

ソース
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.JAXB;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "root")
class Bean {
    @XmlElement(name = "val_1")
    private String val1;
    
    @XmlElement(name = "val_2")
    private String val2;
    
    public void setVal1(String val1){this.val1 = val1;}
    public String getVal1(){return val1;}
    public void setVal2(String val2){this.val2 = val2;}
    public String getVal2(){return val2;}
}


class Sample {
    public static void main(String args[]){
	Bean f = new Bean();
	
	f.setVal1("hoge");
	f.setVal2("fuga");
	JAXB.marshal(f, System.out);
    }
}
実行結果
user% java Sample
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<val_1>hoge</val_1>
<val_2>fuga</val_2>
</root>

XML読み込み

前述までと逆に、XMLの読み込みを行う場合はunmarshal()を使用する。
ソース
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.JAXB;
import java.io.StringReader;

@XmlRootElement(name = "root")
class Bean {
    @XmlElement(name = "val_1")
    private String val1;
    @XmlElement(name = "val_2")
    private String val2;
    
    public void setVal1(String val1){this.val1 = val1;}
    public String getVal1(){return val1;}
    public void setVal2(String val2){this.val2 = val2;}
    public String getVal2(){return val2;}
}

class Sample {
    public static void main(String args[]){
	String s = "hogefuga";
	System.out.println(s);
	
	Bean bean = (Bean)JAXB.unmarshal(new StringReader(s), Bean.class);
	System.out.println(bean.getVal1());
	System.out.println(bean.getVal2());
    }
}
実行結果
user% java Sample
<?xml version="1.0" encoding="UTF-8"
standalone="yes"?><root><val_1>hoge</val_1><val_2>fuga</val_2></root>
hoge
fuga

タグの順番を未指定

import javax.xml.bind.annotation.XmlAccessorOrder;
import javax.xml.bind.annotation.XmlAccessOrder;

@XmlAccessorOrder(XmlAccessOrder.UNDEFINED)
@XmlRootElement(name = "root")
class Bean {
  // 〜
}

Ruby便利機能 メモ

投稿日:
更新日:
タグ:

Rubyの便利なメソッドをメモ。 なお後述の実行結果は見やすいように形成している。

配列

  • product
  • combination
  • reject
  • select
  • collect/map
  • transpose
  • zip
  • each
  • each_with_index
  • join
  • uniq
  • split
  • inject
配列1.product(配列2, ...)
複数の配列から組み合わせて、順列を作成する。
ary1 = [1, 2, 3]
ary2 = ["a", "b"]
p ary1.product(ary2)
実行結果
[[1, "a"], [1, "b"],
 [2, "a"], [2, "b"],
 [3, "a"], [3, "b"]]
productは3つ以上の配列でも良い。
ary1 = [1, 2, 3]
ary2 = ["a", "b"]
ary3 = [-10, -20]
p ary1.product(ary2, ary3)
実行結果
[[1, "a", -10], [1, "a", -20],
 [1, "b", -10], [1, "b", -20],
 [2, "a", -10], [2, "a", -20],
 [2, "b", -10], [2, "b", -20],
 [3, "a", -10], [3, "a", -20],
 [3, "b", -10], [3, "b", -20]]
配列.combination(n){|要素, ...| block }
配列から組み合わせを生成する。要素はn個記述する。
[1, 2, 3].combination(2){|e1,e2|
  puts "#{e1} #{e2}"
}
実行結果
1 2
1 3
2 3
配列.reject{|要素| 削除条件}
配列から特定の要素を削除した配列を生成する。
奇数のみ
p [1, 2, 3, 4, 5, 6].reject{|x| x%2==0 }
実行結果
[1, 3, 5]
配列.select{|要素| 選択条件}
配列から特定の要素を選択した配列を生成する。
奇数のみ
p [1, 2, 3, 4, 5, 6].select{|x| x%2==1 }
実行結果
[1, 3, 5]
配列.collect{|要素| 新しい要素}
配列.map{|要素| 新しい要素}
各要素に対して何らかの処理を行い、新たな配列を生成する。
全ての要素を倍に
p [1, 2, 3, 4, 5, 6].collect{|x| x*2 }
実行結果
[2, 4, 6, 8, 10, 12]
配列.transpose()
配列を行列とみなし、行と列を交換する。
irb(main):002:0> [[1,2,3], [4,5,6]].transpose()
=> [[1, 4], [2, 5], [3, 6]]
配列.zip(other_array, ...)
配列の要素を引数として、other_arrayと組み合わせ、配列の配列を作成 する。
irb(main):002:0> [1,2,3].zip([4,5,6])
=> [[1, 4], [2, 5], [3, 6]]
irb(main):001:0> [1,2,3].zip([4,5,6],[7,8,9])
=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
irb(main):004:0> [1,2,3].zip([4,5,6],[7,8,9]).each{|x,y,z| puts
"#{x}-#{y}-#{z}" }
1-4-7
2-5-8
3-6-9
=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
配列.each{|要素| block}
各要素に対して何らかの処理を行う。
全ての要素を倍に
[1, 2, 3].each{|x| puts x*2 }
実行結果
2
4
6
配列.each_with_index{|要素, 添字番号| block}
eachに加えて、添字番号も取得する。
全ての要素を倍に
["hoge", "fuga", "piyo"].each{|v, i| puts "#{i} #{v}" }
実行結果
0 hoge
1 fuga
2 piyo
配列.join(区切り記号の文字列)
配列を結合して文字列を生成する。
2次元配列をCSV形式で出力
ary1 = [1, 2, 3]
ary2 = ["a", "b"]
ary3 = [-10, -20]
p ary1.product(ary2, ary3).each{|x|
 puts x.join(",")
}
実行結果
1,a,-10
1,a,-20
1,b,-10
1,b,-20
2,a,-10
2,a,-20
2,b,-10
2,b,-20
3,a,-10
3,a,-20
3,b,-10
3,b,-20
配列.uniq
配列から重複を削除する。
2次元配列をCSV形式で出力
ary1 = [1, 1, 2]
ary2 = [3, 4]
p ary1.product(ary2)
puts
p ary1.product(ary2).uniq
実行結果
[[1, 3], [1, 4],
 [1, 3], [1, 4], [2, 3], [2, 4]]

[[1, 3], [1, 4], [2, 3], [2, 4]]
文字列.split(区切り記号)
文字列を指定した文字列で区切って、配列を生成する。
  • irb(main):001:0> "hoge,fuga,piyo,,foo".split(/,+/)
    => ["hoge", "fuga", "piyo", "foo"]
    
  • irb(main):002:0> "hoge,fuga,piyo,,foo".split(",")
    => ["hoge", "fuga", "piyo", "", "foo"]
    
配列.inect{|合計, 要素| 合計}
配列.inect(初期値){|合計, 要素| 合計}
配列.inect(初期値, 合算関数)
配列.inect(合算関数)
各要素を順次処理して結果を合算していく。配列と書いたが実際にはEnumerable#inject。
ブロックの中で指定した結果の合計が次の周期の引数の合計になり、最終的な合計が返却値となる。
初期値を指定しない場合は配列の1つ目の要素が初期値で、2つ目の要素から始まる。
例1)
[1,2,3,4,5].inject{|s,v| p s; s+v}
実行結果
1
3
6
10
=> 15
例2)
[1,2,3,4,5].inject(0){|s,v| p s; s+v}
実行結果
0
1
3
6
10
=> 15
配列の合計や乗算などを扱う場合に便利。
例えば3~5(3,4,5)の整数の乗算結果は以下にように記載できる。
(3..5).inject(:*)

ハッシュ

ハッシュ.invert()
ハッシュのキーと値を入れ替える。
irb(main):001:0> h = {"a"=>1, "b"=>>, "c"=>3}
=> {"a"=>1, "b"=>2, "c"=>3}
irb(main):002:0> h.invert
=> {1=>"a", 2=>"b", 3=>"c"}
irb(main):003:0>

IO, File

IO.read(size)
IOからサイズ分文字列を読み込む。size省略で全て読み込む。
IO.readlines()
IOから全て読み込んで、行毎に配列にする。
IO.popen(コマンド, モード){|IO| 処理}
irb(main):001:0> IO.popen("date"){|io| puts io.read}
2016年  3月  5日 土曜日 15:30:54 JST
=> nil

String

文字列.scan(正規表現)
正規表現に一致する文字列の配列を返す。
irb(main):001:0> STDIN.read.scan(/[^ ]藤/)
さ藤 佐藤 田中 藤野 斎藤
佐々木 遠藤 鈴木
=> ["さ藤", "佐藤", "斎藤", "遠藤"]
irb(main):002:0>
sprintf(str, arg, ...)
str % arg
書式付き文字列を作成する。
  • puts "%.2d" % 2
  • puts "%.2d %s" % [2, "hoge"]
  • puts sprintf "%.2d %s", 2, "hoge"

Docker メモ

投稿日:
タグ:

Dockerとは

Linux用のオープンソースのソフトウェアコンテナ(コンテナ)のこと。類似したソフトにはLXCやFreeBSDのjailなどがある。


インストール(Debianの場合)

  1. APTのデータ取得元の設定リストにURLを追加 (以下jessieの場合)。
    /etc/apt/sources.list
    • deb https://apt.dockerproject.org/repo debian-jessie main
      
    • deb https://apt.dockerproject.org/repo debian-wheezy main
  2. パッケージを更新する。
    sudo apt-get update
  3. Dockerをインストールする。
    sudo apt-get install docker-engine
  4. デーモンが実行していない場合、dockerデーモンを起動する。
    sudo service docker start
apt-getでhttpsを利用する

apt-getはデフォルトではhttpsを利用できない(はず)。

E: メソッドドライバ /usr/lib/apt/methods/https が見つかりません。
apt-getで利用可能なプロトコルは、以下のようにして確認できる。
ls /usr/lib/apt/methods

対応してない場合、httpsのプロトコルと証明書を追加する。

sudo apt-get install apt-transport-https ca-certificates
APTキー管理ユーティリティで、公開鍵の設定を行う。
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

失敗談

はじめ、Dockerをインストールしようとして、

sudo apt-get install docker
とやって実行を試みた。
sudo docker run hello-world
すると結果は、
Segmentation Fault or Critical Error encountered. Dumping core and aborting.

このDockerはドッキングアプリケーションのことである。詳しくは"aptitude show docker"とかで確認できる。


とりあえず実行する

現在管理しているイメージはimagesで確認できる。

user% sudo docker images
デフォルトでは、Welcomメッセージを表示するhello-worldイメージが用意されている(はず)。

イメージは、runコマンドで指定したコンテナを実行できる。

user% sudo docker run hello-world
コンテナ名を指定しない場合、デフォルトで"nostalgic_almedia"というコンテナが使用される。

実行中のコンテナは、psで確認できる。

user% sudo docker ps -a
シェルのような対話的なコマンドをコンテナで実行するには、-itオプションを使用する。
user% sudo docker run -it ubuntu:14.04 /bin/bash
ubuntuのイメージがない場合、イメージをダウンロードしてから実行する。

また、--nameでコンテナに名前をつけることができる。

user% sudo docker run --name="test" ubuntu /bin/bash
user% sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
86f74cf40355        ubuntu              "/bin/bash"         18 seconds ago      Exited (0) 16 seconds ago                             test

Bashからexitで抜けて、ホストに戻った後、imagesをもう一度実行すれば、イメージが増えているのが分かるだろう。

user% sudo docker images


停止中のコンテナを再開する

  1. コンテナを確認する。
    user% sudo docker ps -a
    CONTAINER ID        IMAGE               COMMAND		CREATED              STATUS                      PORTS		NAMES
    9fec70d35bde        ubuntu              "/bin/bash"     About a minute ago   Exited (0) 14 seconds ago            	sleepy_williams
  2. (停止中の)コンテナを実行する。
    user% sudo docker start 9fec70d35bde
    startは以下のように使用する。
    user% sudo docker start [オプション] コンテナ名|コンテナID ...
  3. 実行中のコンテナでコマンドを実行する。
    user% sudo docker exec 9fec70d35bde ls
    user% sudo docker exec オプション コンテナ名|コンテナID コマンド
  4. 実行中のコンテナをアタッチする。
    user% sudo docker attach 9fec70d35bde

コンテナからイメージを作成

  1. コンテナを確認する。
    user% sudo docker ps -a
    CONTAINER ID        IMAGE               COMMAND		CREATED              STATUS                      PORTS		NAMES
          9fec70d35bde        ubuntu              "/bin/bash"     About a minute ago   Exited (0) 14 seconds ago            	sleepy_williams
  2. コンテナからイメージを作成する。
    user% sudo docker commit 9fec70d35bde hoge
  3. イメージを確認する。
    user% sudo docker images
    REPOSITORY          TAG                 IMAGE ID            CREATED		SIZE
    hoge                latest              8a2222fb559e        5 seconds ago       188 MB
    ubuntu              14.04               07c86167cdc4        4 days ago		188 MB
  4. 作成したイメージからコンテナを作成し、実行する。
    user% sudo docker run hoge
  5. コンテナを確認する。
    user% sudo docker ps -a
    CONTAINER ID        IMAGE               COMMAND             CREATED	STATUS                     PORTS               NAMES
    937b91f51708        hoge                "/bin/bash"         9 seconds ago       Exited (0) 7 seconds ago	               elegant_northcutt
    9fec70d35bde        ubuntu              "/bin/bash"         31 minutes ago      Up 13 minutes                                  sleepy_williams

基本コマンド

docker コマンド オプション
ps
実行中のコンテナの一覧を表示。
run
新しくコンテナを実行。
rm
コンテナを削除。
images
取得したイメージの一覧を表示。
rmi
取得したイメージを削除。
search
Docker Hubからイメージを検索する。
stop
実行中のコンテナを停止させる。
commit
コンテナからイメージを作成する。
docker run イメージ名
新しくコンテナを実行する。
docker images
ダウンロード済みのイメージの一覧を表示する。
docker ps -a
実行されたコンテナの情報を表示する。
docker save イメージ > tarファイル
docker save -o tarファイル
docker save --output tarファイル
イメージをtarファイルに保存する。saveとloadはセットで覚えた方が良い。xzやgzipと組み合わせれば、tar.gzやtar.xzなども作成可能。
docker load イメージ < tarファイル
docker load -i tarファイル
docker load --input tarファイル
保存したtarファイルからイメージを読み込む。

忘れた時は...

サブコマンドの確認..
sudo docker
サブコマンドの使い方の確認..
sudo docker サブコマンド --help

その他

管理者権限なしでdockerを使用する
sudo groupadd docker
sudo gpasswd -a ユーザ docker

メモ

  • 3.14以前のLinuxカーネルではPAMを利用できない。

RDBとSQL 応用

投稿日:
タグ:

DBやSQL関連の中級以上向け技術のメモ。

三層スキーマ

データベースの構造・スキーマを3階層に分けて、定義する方式がある。

三層スキーマ
外部スキーマ
概念スキーマ
内部スキーマ
内部スキーマ
DBの実態。データを効率的に管理するための物理設計。DBもデータである以上、ファイルとして管理される。DB開発者が考えるスキーマ。
概念スキーマ
テーブル。データを一意に効率的に管理するための設計。DB設計者が考えるスキーマ。
キーワード:正規化, ER図
外部スキーマ
テーブルやビュー、SELECTの結果。ユーザ視点のもので、実際にデータを使用する際に考えるスキーマ。

縦持ちと横持ち

生徒のテストの点数を管理する場合、エクセルのような表計算ソフトを使用するヒトは、以下のような表を作成するだろう。

名前
佐藤太郎5060567060
鈴木二郎6055705570
田中三郎4070806080
横持ち

このような表を横持ちテーブルという。1つのレコードに対して各フィールドに必ず1つの値が入る場合、有効な設計である。しかしRDBでは横持ちテーブルは、列間の独立性が高く、常に良いとは限らない。横持ちテーブルに対し、以下のようなテーブルを縦持ちテーブルという。

名前科目点数
佐藤太郎50
佐藤太郎60
佐藤太郎56
佐藤太郎70
佐藤太郎60
鈴木二郎60
鈴木二郎55
鈴木二郎70
鈴木二郎55
鈴木二郎70
田中三郎40
田中三郎70
田中三郎80
田中三郎60
田中三郎80
縦持ち

一見すると、縦持ちテーブルは横持ちテーブルに比べて、可読性が低く、かつデータが重複しているため、効率が悪いように見えるかもしれない。しかし、RDBでは縦持ちテーブルの方が効率が良い場合がある。

横持ちテーブルで科目数を増減したい場合、列を追加する必要がある。RDBでは列の追加は、ALTER文を使用してテーブル設計を変更する必要がある。これに対し、縦持ちテーブルはINSERT文だけで追加可能である。すなわち、テーブルを再設計するコストがない。

横持ちテーブル
ALTER TABLE テーブル ADD 美 int;
ALTER TABLE テーブル DROP 美;
縦持ちテーブル
INSERT INTO テーブル VALUES ('衛宮四郎', '美', 50);
DELETE FROM テーブル WHERE 科目='美';

更に、横持ちテーブルでこのような変更を行った場合、各フィールドにデータが入らないケースが発生する(すなわちnull)。そうなると、列ごとの何らかの処理を行う場合、nullを除外するような処理を記述しなければならなくなる(例:COALESCEを使う)。

また、データ処理においても、縦持ちテーブルは優れた性質を持つ。生徒ごとの合計点を計算する場合、縦持ちテーブルと横持ちテーブルではそれぞれ次のように記述する。

横持ちテーブル
SELECT 名前,国+数+社+理+英 FROM テーブル GROUP BY 名前;
縦持ちテーブル
SELECT SUM(点数) FROM テーブル WHERE 名前="佐藤太郎"

このように、縦持ちテーブルは、ある関連するデータの値が0個〜複数個ある場合に、横持ちテーブルよりも拡張性に優れている。

しかしその反面、縦持ちテーブルは横持ちテーブルに比べて、レコード数が増加しやすい。RDBにおいて、レコード数が多いと、検索や処理速度の低下を招くため、前述のようなケースでも必ず縦持ちテーブルが良いとは限らない。例えば前述の横持ちテーブルは、1つのUPDATE文やINSERT文で5教科の更新や追加をできるのに対し、縦持ちテーブルではそれぞれ5回発行しなければならない。

横持ち→縦持ち

以下のようなテーブルがあったとする。

名前ニックネーム1ニックネーム2ニックネーム3
佐藤太郎タローtaro3たろっち
鈴木二郎ジロー2ronull
このテーブルは1つのINSERT文やUPDATE文で1レコードを更新できるが、前述した通り何らかの処理をする場合は縦持ちの方が都合が良いことがある。このような場合、UNIONを使うと横持ちテーブルを縦持ちに変換できる。
SELECT ニックネーム1 AS ニックネーム FROM テーブル WHERE 名前 ='佐藤太郎' UNION
SELECT ニックネーム2 AS ニックネーム FROM テーブル WHERE 名前 ='佐藤太郎' UNION
SELECT ニックネーム3 AS ニックネーム FROM テーブル WHERE 名前 ='佐藤太郎';
ニックネーム
タロー
taro3
たろっち
※別にWHEREは結合後につけても結果自体は変わらないが、性能が同じとは限らない。

前述の三層スキーマでも述べたように、SQLでは実態のテーブル(概念スキーマ)と実際に扱うデータ構造(外部スキーマ)を分けることができる。

縦持ちから横持ちテーブルへの変換については後述する。


ビュー

頻繁に使用するSELECT文は、ビューを作成することで、擬似的なテーブルとして利用できる。 例えば前述のテーブルに以下のようなSQL文を発行する。
CREATE VIEW ニックネームテーブル (名前,ニックネーム) AS
SELECT 名前,ニックネーム1 FROM テーブル UNION
SELECT 名前,ニックネーム2 FROM テーブル UNION
SELECT 名前,ニックネーム3 FROM テーブル;
そうすると次のようなビューが作成される。
名前ニックネーム
佐藤太郎タロー
佐藤太郎taro3
佐藤太郎たろっち
鈴木二郎ジロー
鈴木二郎2ro
鈴木二郎null

CASE式

プログラミング経験者のSQL初心者は、はじめてSQLを使用するとSQLはただデータを格納するためだけに使い、データの整理はプログラミングでどうにかしようとしたくなる(少なくとも私はそうだった)。

例えば以下のような学校があったとする(Appendixに初期化用のSQL文を記載)。

クラス
1-A1512
1-B1410
2-A1120
2-B2110
3-A1516
3-B1415
この時、運動会で全校を対象に同じ組同士でチームを組む場合、各色のチーム人数を調べる場合、例えばRubyでは次のように記載できる。
db = func()
# func()は「select * from sample_table」の結果を受け取る。
# [
# {"クラス"=> "1-A", "男"=>15, "女"=>12}, 
# {"クラス"=> "1-B", "男"=>14, "女"=>10}, 
# {"クラス"=> "2-A", "男"=>11, "女"=>20}, 
# {"クラス"=> "2-B", "男"=>21, "女"=>10}, 
# {"クラス"=> "2-A", "男"=>15, "女"=>16}, 
# {"クラス"=> "3-B", "男"=>14, "女"=>15}
# ]

red = 0
white = 0
db.each do |x|
  case x["クラス"]
  when "1-A", "2-A", "3-A"
    red += x["男"] + x["女"]
  when "1-B", "2-B", "3-B"
    white += x["男"] + x["女"]
  end
end
puts "赤:#{red} ホワイト:#{white}"
この方法では、DBのデータを全て取得した後にアプリがもう一度全てのデータにアクセスしている。しかし、この程度の処理であれば、DBがデータにアクセスしながら値を整理し、アプリはその結果を出力するだけで良い。
SELECT
  CASE class WHEN '1-A' THEN '赤'
             WHEN '2-A' THEN '赤'
             WHEN '3-A' THEN '赤'
             WHEN '1-B' THEN '白'
             WHEN '2-B' THEN '白'
             WHEN '3-B' THEN '白'
             END,
  SUM(population)
from sample_table
  GROUP BY CASE class WHEN '1-A' THEN '赤'
             WHEN '2-A' THEN '赤'
             WHEN '3-A' THEN '赤'
             WHEN '1-B' THEN '白'
             WHEN '2-B' THEN '白'
             WHEN '3-B' THEN '白'
             END;
実行結果
168
178

CASE式を覚えることで、性能改善や利便性の向上を図ることができる。ただし、CASE式や結合処理はDBの負荷を上げるため、アプリ側で対処するケースもあるため、そこは状況に応じた使い分けが必要である。


縦持ち→横持ち
CASE式を使用すると、縦持ちテーブルを横持ちテーブルに変換できる。例えば以下のようなテーブルがあったとする。
クラス性別人数
1-A15
1-A12
1-B14
1-B10
2-A11
2-A20
2-B21
2-B10
3-A15
3-A16
3-B14
3-B15
縦持ち
この時、次のようなSQL文を実行することで横持ちテーブルに変換できる。
SELECT class,
       sum(CASE WHEN sex='男' THEN population ELSE 0 END) as '男',
       sum(CASE WHEN sex='女' THEN population ELSE 0 END) as '女'
FROM sample_table GROUP BY class;

制約

-編集中-
PRIMARY KEY制約
UNIQUE制約
NOT NULL制約
CHECK制約

正規化

データの重複をなくし、整合的にデータを取り扱えるデータベースを設計することを正規化という。 正規化は段階に合わせて6つに分類される。

正規化(データの整合性)とパフォーマンスは基本的にトレードオフの関係にある。そのため、必ずしも段階が高ければ良いとはいう訳ではなく、状況に応じて使い分けるのが望ましい。

なお、一般的には第3正規化まで行っている設計が多い。

第1正規化
以下の条件を満たしたテーブルにすること。
  • 1つのセルに1つのデータのみ。
  • レコードを一意に特定するキー(主キー:PK)がある。
非正規形のままではRDBで扱うことができない。 言ってみれば第1正規化とはRDBに格納可能な形にすることである。
1つのセルに1つのデータのみ。
名前子供
田中一郎太郎
花子
伊藤五郎null
名前子供
田中一郎太郎
田中一郎花子
伊藤五郎null
レコードを一意に特定するキー(主キー:PK)がある
例1)主キーが名前の場合
名前
佐藤太郎5060567060
鈴木二郎6055705570
田中三郎4070806080
横持ち
例2)主キーが名前と科目(複数)の場合
名前科目点数
佐藤太郎50
佐藤太郎60
佐藤太郎56
佐藤太郎70
佐藤太郎60
鈴木二郎60
鈴木二郎55
鈴木二郎70
鈴木二郎55
鈴木二郎70
田中三郎40
田中三郎70
田中三郎80
田中三郎60
田中三郎80
縦持ち
キー
キーには、ナチュラルキーサロゲートキーの2種類がある。前述したようなあるデータモデルから一意に特定できる要素を見出して、それをキーとしたものを、ナチュラルキー、データモデルにはないが、一意に特定するために新しく追加したキーをサロゲートキーという。例えば前述の縦持ちテーブルのPKをサロゲートキーにすると、次のようになる。
ID名前科目点数
01佐藤太郎50
02佐藤太郎60
03佐藤太郎56
04佐藤太郎70
05佐藤太郎60
06鈴木二郎60
07鈴木二郎55
08鈴木二郎70
09鈴木二郎55
10鈴木二郎70
11田中三郎40
12田中三郎70
13田中三郎80
14田中三郎60
15田中三郎80
サロゲートキーを使用するメリットには、次のようなものがある(ミックさん談)。
  1. そもそも主キーにできる項目がない場合(重複してる)
  2. 主キーの値が使いまわされる場合
  3. 主キーの体系が変化する場合
2と3の例には、商品コードと商品を扱う場合に、商品コードが再利用されるような場合がある。 商品コードをナチュラルキー(PK)とした場合、レコードを上書きするしかないが、別にキーを設定することで、新たに追加するだけで良い。
第2正規化

主キーの一部から非キーが一意に特定できる場合、テーブルが分けること。第2正規化とは、第1正規形から部分関数従属性を取り除くことである。

関数従属性
ある属性(列)の値が決まる時、別の属性が一意に決まること。
部分関数従属性
キーの一部に非キー属性が関数従属している状態のこと。

言ってみれば、第2正規化とは情報からエンティティ毎に整理することである。 大雑把に説明すると、例えばサッカーのグループリーグの情報を表すDBがあり、選手を起点に考える。

  • 選手の名前(キー)
  • 選手の身長
  • 選手の体重
  • 選手の所属チーム
  • 所属チームのホームグラウンド
  • 所属チームの勝利数
  • 所属チームの敗北数
第2正規形でない第1正規形は、選手と所属チームという2つのエンティティをひとまとめに管理しているが、これらを別のものとして分けるのが第2正規形である。前述の一覧でいえば、「〜の」でテーブルを分割ことである。

RDBではキーがなければレコードを作成できない。そのため、第2正規形でない第1正規形のように1つのテーブルにエンティティが混在する場合、全体の主キーが決まらなければレコードが作成できない。また、同一のエンティティを複数のレコードで管理するため、データの整合性を保持するのが難しい。これを解消したものが第2正規形である。

第3正規化

第2正規形から推移関数従属性を取り除くこと。非キーから別の非キーを一意に特定できる場合、それをテーブルに分けること。

言ってみれば、第3正規化とはエンティティの情報を更に整理することである。第2正規化で全体の情報をエンティティに分けるが、このエンティティの持つ情報から更に一意に特定できる情報を独立化させたものが第3正規形である。

ボイス・コッド正規化

第3正規形のうち非キーから主キーが一意に定まらないDBのこと。ボイス・コッド正規形とは、非キーから主キーへの関数従属をなくした状態。

第4正規形

ボイス・コッド正規化までが情報の整理を行うのに対し、第4・5正規化はエンティティ間の関係を整理する。

エンティティはキーによって一意に特定できる。そのため、キーだけのテーブル(エンティティ)を作成することで、エンティティの関係を表すエンティティを作成できる。

例えば「A→→B|C」の関係のエンティティがある場合、「|Aのキー|Bのキー|」と「|Aのキー|Cのキー|」のようにして関係を表す。

第5正規形

1つの関係エンティティにつき1つの関係を表すDBが第5正規形である。

A→→B、A→→Cに加えてB→→Cの関係があるでは、3つのエンティティの関係を表すテーブルができる。

この時に以下のようにして分割することで、全てのエンティティの関係を表すことができる。

  • |A|B|
  • |A|C|
  • |B|C|

Appendix

my.cnf
[client]
default-character-set=utf8

[mysqld]
character-set-server=utf8

[mysqldump]
default-character-set=utf8

[mysql]
default-character-set=utf8
初期化
START TRANSACTION;
CREATE DATABASE sampledb;
USE sampledb;
CREATE TABLE sample_table
(class  CHAR(3),
 sex CHAR(1),
 population  INTEGER);
INSERT INTO sample_table VALUES ('1-A', '男', 15);
INSERT INTO sample_table VALUES ('1-A', '女', 12);
INSERT INTO sample_table VALUES ('1-B', '男', 14);
INSERT INTO sample_table VALUES ('1-B', '女', 10);
INSERT INTO sample_table VALUES ('2-A', '男', 11);
INSERT INTO sample_table VALUES ('2-A', '女', 20);
INSERT INTO sample_table VALUES ('2-B', '男', 21);
INSERT INTO sample_table VALUES ('2-B', '女', 10);
INSERT INTO sample_table VALUES ('3-A', '男', 15);
INSERT INTO sample_table VALUES ('3-A', '女', 16);
INSERT INTO sample_table VALUES ('3-B', '男', 14);
INSERT INTO sample_table VALUES ('3-B', '女', 15);
COMMIT;

AngularJS1.5 メモ(1) 基本的な記載方法

投稿日:
タグ:

本稿はAngularJS1.5系の基本的な記載方法のサンプル集である。

コンポーネント化する場合
<!doctype html>
<html ng-app="sampleApp">
<head>
<meta charset="UTF-8">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
<script>
var app = angular.module("sampleApp", []);

app.component("sample", {
  template: '<div>hello</div>',
  controller: function() {},
  bindings: {}
});
</script>
<title>sample</title>
</head>
<body>
<sample></sample>
</body>
</html>
templateの代わりに、templateUrlを使用することで、テンプレートを別のファイルに記載することもできる。
パラメータを使用する場合
controllerAsで名前を指定すると、その名前でメンバを指定できる。
AngularJSのng-〜属性以外で、メンバの参照を行う場合、「{{〜}}」で囲う。
<!doctype html>
<html ng-app="sampleApp">
<head>
<meta charset="UTF-8">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
<script>
var app = angular.module("sampleApp", []);

app.component('sample', {
  template: '<div>hello, {{sample.value}}</div>',
  controller: function(){},
  controllerAs: 'sample',
  bindings: {
    value: '@'
  }
});
</script>
<title>sample</title>
</head>
<body>
<sample value="world"></sample>
</body>
</html>
バインディングの種類は以下の通りである。
バインディング説明
@文字列と連携。評価しない。
&メソッド連携。
<オブジェクト連携。単方向データバインディング
=オブジェクト連携。双方向データバインディング
メソッド定義
コントローラ内でメンバを指定する場合、thisを使って指定する。
<!doctype html>
<html ng-app="sampleApp">
<head>
<meta charset="UTF-8">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
<script>
ctrlFunc = function() {
  this.value = 'button';
  this.func = function(){
    this.value = 'pushed';
  }
};
var app = angular.module("sampleApp", []);

app.component('sample', {
  controller: ctrlFunc,
  controllerAs: 'sample',
  template: '<div>hello, {{sample.value}}</div><input type="button" value="button" ng-click="sample.func();">'
});
</script>
<title>sample</title>
</head>
<body>
<sample></sample>
</body>
</html>
サービスを使用する場合
以下は$windowサービスを使用したサンプル。
<!doctype html>
<html ng-app="sampleApp">
<head>
<meta charset="UTF-8">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
<script>
ctrlFunc = function($window) {
  this.func = function(){
    $window.alert("warn");
  }
};
var app = angular.module("sampleApp", []);

app.component('sample', {
  controller: ctrlFunc,
  controllerAs: 'sample',
  template: '<input type="button" value="button" ng-click="sample.func();">'
});
</script>
<title>sample</title>
</head>
<body>
<sample></sample>
</body>
</html>
モジュールを使用する場合
追加モジュールを使用する場合、パッケージ管理にbowerというJavaScriptのパッケージ管理を使用すると便利である。
以下はhtmlをバインドする例。
<!doctype html>
<html ng-app="sampleApp">
<head>
<meta charset="UTF-8">
<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.min.js"></script>
<script>
ctrlFunc = function() {
  this.content = '<div>hogehoge</div>';
};

var app = angular.module("sampleApp", ['ngSanitize']);

app.component('sample', {
  controller: ctrlFunc,
  bindings: {},
  controllerAs: 'sample',
  template: '<div ng-bind-html="sample.content"></div>'
  });
</script>
<title>sample</title>
</head>
<body>
<sample></sample>
</body>
</html>
計算する場合
<!doctype html>
<html ng-app="sampleApp">
<head>
<meta charset="UTF-8">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
<script>
var app = angular.module("sampleApp", []);

app.component("sample", {
  template: '<div>{{1 + 3}}</div>',
  controller: function() {},
  bindings: {}
});
</script>
<title>sample</title>
</head>
<body>
<sample></sample>
</body>
</html>
コーディングレスのデータバインディング
<!doctype html>
<html ng-app="sampleApp">
<head>
<meta charset="UTF-8">
<script
src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
<script>
var app = angular.module("sampleApp", []);

app.component("sample", {
  template: '<input type="text" ng-model="sample.text"><div>text: {{sample.text}}</div>',
  controllerAs: "sample",
  controller: function() {
    this.text = "hoge";
  },
  bindings: {}
});
</script>
<title>sample</title>
</head>
<body>
<sample></sample>
</body>
</html>

Appendix

bower

bowerとはフロントエンド(クライアントサイドJavaScript)用パッケージマネージャである。

bowerは以下のようにしてインストールできる。

  1. npmをインストール。
    user% aptitude install npm
  2. bowerをインストール。
    user% npm install bower -g

npmとはNode Package Managerの略で、Node.js(サーバーサイドJavaScript)用のパッケージ管理ツールのこと。
理由は不明だが、bowerはnpm経由でインストールできる。

bowerの使用例を以下に簡単に記す。
user% bower search キーワード
検索
user% bower install ソース
user% bower install ソース#バージョン
インストール
トラブルシューティング

以下、正常にできなかった場合にありそうなミス。

  • AngularJSを取り込むscriptタグの閉じタグが抜けていた。
  • ;または)などの終端を表す記号が漏れていた。

Stream メモ

投稿日:
タグ:

JavaのStreamに関するメモ。

Stream API

概要

Java8から導入されたStream APIは、簡単に言ってデータ集合を扱う機能である。 Stream APIを使うとループ文やイテレータでは、冗長な記載しかできなかった文が簡潔に書けることが多い。

ただし、プロジェクト内でうまくルールを決めないと、同じような実装をやるために、さまざまな書き方で書かれることになる。

また、性能的な観点でいると、簡単なデータ集合を扱うだけならば、わざわざStream型を使用するよりも、基本(プリミティブ)型を使ったほうが速い。

ループ文
java Hoge 0.30s user 0.02s system 99% cpu 0.318 total
String[] ary = {"hoge", "fuga", "piyo"};
for (int i=0;i<3;i++){
   System.out.println(ary[i]);
}
Stream API
java Hoge 0.46s user 0.06s system 113% cpu 0.460 total
String[] ary = {"hoge", "fuga", "piyo"};
Stream.of(ary).forEach(System.out::println);

基本

データ集合にはそれに対応するストリームの種類がある。

  • オブジェクト型
    • Stream
  • 基本(プリミティブ)型
    ストリーム対応するプリミティブ型
    IntStreamint
    LongStreamlong
    DoubleStreamdouble

ストリームは上記のStreamやIntStreamクラスなどのstaticメソッドや、配列やコレクション型などのデータ集合をストリームに型変換することで、記載する。

ストリームに0回以上の中間操作というメソッド呼び出しを行い、最後に終端操作と呼ばれるメソッドを呼ぶのがストリームの基本的な考え方である。

以下、0〜20の間の3の倍数の奇数を出力するサンプル。

IntStream.rangeClosed(0, 20)            // ストリームを取得: 0〜20
         .filter((v) -> v%3==0)         // 中間操作: 3の倍数
         .filter((v) -> v%2==1)         // 中間操作: 奇数
         .forEach(System.out::println); // 終端操作:各要素を引数にメソッド呼び出し
メソッドは以下のように記載する。
  • クラス::メソッド
  • オブジェクト::メソッド
当然this::メソッドもある。

レシピ集

要素に対する条件
allMatch(条件)
全て一致
int [] ary = {1, 2, 3, 4, 5};
if (IntStream.of(ary).allMatch((i) -> i > 0)){
  System.out.println("OK");
}
noneMatchというものもある
noneMatch(条件)
int[] v = {0, 0, 0, 0};
if (IntStream.of(v).noneMatch((x) -> x > 0)){
  System.out.println("OK");
}
anyMatch(条件)
いずれかが一致
int [] ary = {1, 2, 3, 4, 5};
if (IntStream.of(ary).anyMatch((i) -> i > 3)){
  System.out.println("OK");
}
多次元配列から1次元配列へ
flatMap(expression)のexpressionにはStreamを返すメソッドを指定する(flatMapToInt()の場合、IntStream)。
オブジェクト型
String[][] ary = {{"a", "b"}, {"c", "d", "e"}};
String[] s = Stream.of(ary).flatMap(Stream::of)
                           .toArray(String[]::new);
String[][][] ary = {{{"a", "b"}, {"c", "d", "e"}},
                    {{"f", "g"},{"h", "i"}}};
String[] s = Stream.of(ary).flatMap(Stream::of)
                           .flatMap(Stream::of)
                           .toArray(String[]::new);
プリミティブ型
int[][] ary = {{123, 456}, {789, 101}};
int[] iary = Stream.of(ary).flatMapToInt(IntStream::of)
	                   .toArray();
int[][][][] ary = {{{{123, 456}, {789, 101}}, {{121, 131}, {415, 161}}}, {{{718, 192}, {212, 132}}}};
int[] iary = Stream.of(ary).flatMap(Stream::of)
	                   .flatMap(Stream::of)
	                   .flatMapToInt(IntStream::of)
	                   .toArray();
型変換
int[]からList<Integer>への型変換
int [] ary = {1, 2, 3, 4, 5};
List<Integer> list = IntStream.of(ary).mapToObj(Integer::new)
                                      .collect(Collectors.toList());
List<Integer>からint[]への型変換
int[] ary = list.stream().mapToInt((v) -> v).toArray();
String[][]からint[]への型変換
String[][] ary = {{"123", "456"}, {"789", "101"}};
int[] iary = Stream.of(ary)
	   .map((x) -> Stream.of(x).mapToInt((y) -> Integer.parseInt(y)).toArray())
	   .flatMapToInt((v) -> IntStream.of(v))
	   .toArray();
int[]からint
int[] ary = {1, 2, 3, 4};
IntStream.of(ary).reduce((b, i) -> b + i).ifPresent(System.out::println);
ListからMapへ
List.of("a", "b", "c").stream().collect(Function.identity(), e -> String.fomat("{%s}", e));
配列・コレクション型の初期化
List<Integer>の作成
List<Integer> ary = IntStream.rangeClosed(1, 5)
                             .mapToObj(Integer::new)
			     .collect(Collectors.toList());
連続する数字のストリーム。range()の範囲は「開始番号 ≦ n < 終了番号」 rangeClosed()の範囲は「開始番号 ≦ n≦ 終了番号
Set<Integer>
List<Integer> ary = IntStream.rangeClosed(1, 5)
                             .mapToObj(Integer::new)
			     .collect(Collectors.toSet());
ArrayList<String>[]
@SuppressWarnings("unchecked")
public static void main(String[] args){
  ArrayList<String>[] ary =
  Stream.generate(ArrayList::new).limit(5).toArray(ArrayList[]::new);
}
数列
乱数配列
double[] ary = DoubleStream.generate(() -> Math.random()).limit(3).toArray();
generate()は無限に続くストリーム。遅延評価されるため、実際に使用する際は基本的にlimit(n)のように使用する分だけを指定する。
  • 0.39132915962622816
  • 0.7227708950486765
  • 0.47467761699532696
配列に変換(1〜4)
int[] a = IntStream.range(1, 5).toArray()
終了値を含める場合(1〜5)
int[] a = IntStream.rangeClosed(1, 5).toArray()
1, 4, 9, 16, 25,..
IntStream.rangeClosed(1, 5).map((x) -> x * x);
2, 4, 6, 8, 10,..
Stream.iterate(2, x -> x + 2).limit(8)
iterate()はlimit()がないと無制限に続く
Stream.iterate(初期値, 増加式)
その他

文字列結合

Java8からString.join()やStringJoinerというクラスが導入された。 これらを使うと文字列結合が非常に便利になる。

また、Streamと組み合わせると更に記載がシンプルになる。

hoge,fuga,piyo,foo
String.join(",", ary);
レコード:{hoge,fuga,piyo,foo}
Stream.of(ary).collect(Collectors.joining(",", "レコード:{", "}"));

Appendix

ストリーム取得
  • Arrays.stream(配列)
  • Stream.of(配列)
  • Stream.of(要素, ...)
  • コレクション型.stream()
中間操作
Stream.filter(expression)
条件に合致するもののみを抽出する
Stream.distinct()
Streamから要素の重複を排除したものを取得する。
Stream.sorted()
Stream.sorted(expression)
ソートしたストリームを取得する
Stream.map(expression)
Stream.mapToInt(expression)
Stream.mapToLong(expression)
Stream.mapToDouble(expression)
IntStream.mapToObj(expression)
LongStream.mapToObj(expression)
DoubleStream.mapToObj(expression)
各要素を別の型に変換する。map()やmapToObj()はStream型を返し、それ以外は対応するストリームを返す(例:mapToInt()であればIntStream)。
Stream.flatMap(expression)
Stream.flatMapToInt(expression)
Stream.flatMapToLong(expression)
Stream.flatMapToDouble(expression)
各要素をストリームとして、それらを結合したものを取得する expressionには対応するストリームを返すメソッドを指定する。
Stream.parallel()
IntStream.parallel()
LongStream.parallel()
DoubleStream.parallel()
並列実行用のストリームを取得する
終端操作
forEachOrdered(expression)
forEach(expression)
メソッド呼び出し。返り値なし。
Stream.count()
Streamの要素数をカウント。返り値は要素数。
IntStream.sum()
LongStream.sum()
Streamの要素を合計する。返り値は和。

AngularJS1.5 メモ(2)

投稿日:
タグ:

AngularJS1.5のメモ。

サービス

$window
メソッド説明
$window.alert(メッセージ)警告
$window.confirm(メッセージ)確認
$window.prompt(メッセージ)テキスト入力

$http

HTTPクライアントやajax通信を行う方法の1つに$httpサービスを使用する方法がある。
以下、そのサンプルである。

<!doctype html>
<html ng-app="sampleApp">
<head>
<meta charset="UTF-8">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
<script>
var app = angular.module("sampleApp", []);

function ctrlFunc($http, $window) {

  this.func = function(){

    $http.post('http://localhost:8090',{data:{value1:'hoge', value2:2}})
    .success(function(data, status, headers, config){
      $window.confirm("success");
    })
    .error(function(data, status, headers, config){
      $window.alert("error");
    });
  };

}

app.component("sample", {
    template: '<input type="button" value="button" ng-click="sample.func();" >',
    controllerAs: "sample",
    controller: ctrlFunc,
    bindings: {}
  });
</script>
<title>sample</title>
</head>
<body>
<sample></sample>
</body>
</html>
$httpサービスのショートカットメソッド
HTTPメソッドメソッド備考
GET$http.get(url, config)
POST$http.post(url, config)
PUT$http.put(url, config)
PATCH$http.patch(url, config)
DELETE$http.delete(url, config)
HEAD$http.head(url, config)
$http.jsonp(url, config)JSONPでリクエスト
$http(config)のパラメータ
パラメータ名備考
methodメソッド
urlURL
paramsクエリ情報
dataリクエスト本体のデータ
headersリクエストヘッダ
timeoutタイムアウト時間
responseTypeレスポンスの型
cacheHTTP GETリクエストをキャッシュするか
xsrfHeaderNameCSRFトークンで利用するリクエストヘッダ名
xsrfCookieNameCSRFトークンを含んだクッキー名
paramSerializerクエリ情報のシリアライズ方法
transformRquestリクエスト変換関数
transformResponseレスポンス変換関数
withCredentialsクロスドメイン(オリジン)のアクセスを許可するか
$http(config)のコールバック設定
メソッド備考
success(成功時のメソッド)
error(失敗時のメソッド)
then(成功時のメソッド, 失敗時のメソッド)

$resource(URL, デフォルトパラメータ, 追加情報, 動作オプション) ※第1引数以外全て任意

クライアントがREST(というかCRUDな)設計の場合、$httpを使用せずに$resourceで簡単に記載することができることがある。
以下、サンプルである。

  • <!doctype html>
    <html ng-app="sampleApp">
    <head>
    <meta charset="UTF-8">
    <script src="bower_components/angular/angular.min.js"></script>
    <script src="bower_components/angular-resource/angular-resource.min.js"></script>
    <script>
    var app = angular.module("sampleApp", ['ngResource']);
    
    function ctrlFunc($resource, $window) {
      function success(){window.confirm("success");}
      function fail(){window.alert("fail");}
      this.func = function(){
        var hoge = $resource('http://localhost:8090/hoge/');
        var val = hoge.query({}, success, fail);
      };
    }
    
    app.component("sample", {
      template: '<input type="button" value="button" ng-click="sample.func();" >',
      controllerAs: 'sample',
      controller: ctrlFunc,
      bindings: {}
      });
    </script>
    <title>sample</title>
    </head>
    <body>
    <sample></sample>
    </body>
    </html>
  • 
    
func(パラメータ, 成功時のメソッド, 失敗時のメソッド) ※引数は全て任意
HTTPメソッドメソッド備考
GETquery{method:'GET', isArray:true}全取得
GETget{method:'GET'}単一取得
POSTsave{method: 'POST'}新規データ登録
DELETEremove{method: 'DELETE'}既存データ削除
delete
追加情報
メソッド説明

メタプログラミングRuby まとめ

投稿日:
タグ:

Ruby

本稿は『メタプログラミングRuby』を読んだ自分用のまとめ。

Rubyの基本メソッド
BasicObject#ancestors
継承関係
メソッドを呼び出す場合,ここで取得する配列の0から順番に検索され,使用される。 例えば,「"hoge".func」のようなメソッドを呼んだ場合,Stringから順に検索される。
[String, Comparable, Object, Kernel, BasicObject]
Kernel#p
オブジェクトの中身を形成(Object#inspectメソッドを呼び出す)して出力する
BasicObject#methods
クラスで定義されたメソッド一覧
public_methods
protected_methods
private_methods
singleton_methods
public・protected・private・特異メソッド一覧
BasicObject#instance_varitables
オブジェクトが持つインスタンス変数一覧
Object#class
クラスを取得
Class#superclass
スーパクラスを取得
オブジェクトモデル
オープンクラス

Rubyでは,定義されたクラスを開いて,内容を変更することができる。

p String.methods.include?(:to_num) # false

class String
  def to_num()
    self.to_i # ※ Rubyのメソッド定義ではreturnがない場合,最後に評価した値を返す
  end
end

a = "123"
p a.to_num # 123

ちなみに既に定義されたメソッドがある場合,上書きする(モンキーパッチ)。

class Hoge
  def func()
    puts "hello"
  end
end

a = Hoge.new

class Hoge
  def func()
    puts "bye"
  end
end

a.func() # byeを出力

Refinements(Ruby2.0以降)
usingを使用すると,引数で指定したモジュールで定義された拡張を現在のクラス、モジュールで有効にできる。
module RefStr
  refine String do
    def to_num()
      self.to_i
    end
  end
end


using RefStr

"123".to_num
メソッド
動的ディスパッチ
Object#send(name [, args, ...])
実行時に呼び出すメソッドを動的に変更する。
class Hoge
  def func1()
    puts "hello"
  end
  def func2(str)
    puts "#{str}"
  end
  def func3()
    puts "bye"
  end
end

a = Hoge.new
a.send(:func1)
a.send(:func2, "yeah!")  
a.send("func3".to_sym)   # 文字列 -> シンボル
動的メソッド
Module#define_method(methodname){|arg, ..| block }
メソッドを動的に定義する。
define_method(:func1){|x| puts x}

class Hoge
  define_method(:func2){|x| puts x}
end

func1("hello")
Hoge.new.func2("bye!")
Object#method_missing(methodname, [*args, &block])
メソッドが見つからない場合,method_missingが呼び出される。
class Hoge
  def method_missing(methodname)
    puts "No #{methodname}!"
  end
end

a = Hoge.new
a.func # No func!
ゴーストメソッド
def method_missing(methodname, *args, &blcok)
  if methodname==:func1 then
    puts "#{args}" 
  else
    super.method_missing(methodname, args, block)
  end
end

func1 1 # [1]
func2 2 # ↓
# NoMethodError: undefined method `func2' for main:Object
#       from (irb):8:in `method_missing'
#       from (irb):13
#       from /usr/bin/irb:12:in `<main>'
その他
可変長引数(*v)
呼び出される側
func(*v)
  p v
end
irb(main):002:0> func(1,2,3)
[1, 2, 3]
=> [1, 2, 3]
呼び出す側
def func(a,b)
   puts "a=#{a}, b=#{b}"
end
irb(main):015:0> args = [1,2].product(["a", "b"])
=> [[1, "a"], [1, "b"], [2, "a"], [2, "b"]]
irb(main):016:0> args.each{|a| func(*a)}
a=1, b=a
a=1, b=b
a=2, b=a
a=2, b=b
=> [[1, "a"], [1, "b"], [2, "a"], [2, "b"]]
irb(main):017:0>

Docker メモ(2)

投稿日:
タグ:

コンテナに固定のIPをふる方法(Docker Network)

コンテナに固定のIPをふるには以下のように実施する。

  • ネットワーク作成
    user% sudo docker network create --subnet=172.18.0.0/16 test_nw
  • コンテナ実行時にIPとネットワークを指定
    user% sudo docker run --name=testcontainer --ip=172.18.0.2 --net=test_nw -d mariadb:10.1.29
固定IPやネットワークを設定すればあとはルータ側で、静的ルーティングを追加すれば外部にサービスを提供することが可能。

その他

ホスト側とファイルの共有(-v)
コンテナ側にファイルを連携する方法の1つに-vオプションを使用する方法がある。
user% sudo docker run --name=test -v ホスト側のパス:コンテナ内のパス イメージ
user% sudo docker run --name=test -v $HOME/work/docker/web:/var/web httpd
今だと以下のように記載するのが一般的?
user% sudo docker container run --name=test -v $HOME/work/docker/web:/var/web httpd
containerは管理コマンドで、以前までのコンテナ関連のコマンドはcontainerの下のコマンドとして管理されている。
docker 管理コマンド コマンド オプション
尚、過去の実行方法と互換があるため、containerを省略してもできなくはないようだ。
ホスト側のポートとマッピング(-p)
コンテナ内のサービスを外部に提供する簡単な方法はホスト側のポートにマッピングする方法である。
user% sudo docker container run -p ホスト側のポート番号:コンテナ内のポート番号 -d mariadb:latest

一覧