FreeBSD 9.0-RELEASEのインストール(zfs環境構築)

目標よりも遅れてしまいましたが,FreeBSD 9.0-Rへの移行を開始しました.

移行するにあたって,次の2つを目的としました.

bootをZFSにするかどうかは迷いましたが,すべてZFSで構築することにしています.

先達の報告を読むかぎり問題はなさそうです.

謝辞

分かりやすい手順を書いてくれていた,kinusatiさんとgkontosさんに感謝します.

このポストの後半はお2人の書かれた内容を併せた形になっています.

次の「参考資料」のリンク先も参照してください.

続きを読む

FreeBSDで学ぶインラインアセンブラの読み方

この記事は,http://atnd.org/events/21910のために書かれました.
文章が全然まとまっていない..

目的

FreeBSDのcpufunc.hからインラインアセンブラの読み方を学ぶ

環境

FreeBSD 8.2 on i386

準備

○cpufunc.h
FreeBSDi386な人は,
/usr/include/machine/cpufunc.h
を探してみる.


FreeBSDi386ではない人は,
/usr/src/sys/i386/include/cpufunc.h
を探してみる.


FreeBSDでない人は,
http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/i386/include/cpufunc.h
から探してみる.


○参考資料
IA-32 インテル アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル
英語大丈夫な人は,下のURLからファイルをダウンロード.
http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
必要なのは,Volume:1,2,3の各ファイル.
全部入りなら下のファイルを選択.
・IntelR 64 and IA-32 Architectures Software Developerfs Manual Combined Volumes:1, 2A, 2B, 2C, 3A, 3B, and 3C


日本語大丈夫な人は,下のURLからファイルをダウンロード.
http://www.intel.com/jp/download/index.htm
必要なのは下の4つ.
IA-32 インテル® アーキテクチャー・ソフトウェア・デベロッパーズ・マニュアル、上巻: 基本アーキテクチャ
IA-32 インテル® アーキテクチャー・ソフトウェア・デベロッパーズ・マニュアル、中巻 A: 命令セット・リファレンス A-M
IA-32 インテル® アーキテクチャー・ソフトウェア・デベロッパーズ・マニュアル、中巻 B: 命令セット・リファレンス N-Z
IA-32 インテル® アーキテクチャー・ソフトウェア・デベロッパーズ・マニュアル、下巻: システム・プログラミング・ガイド


インラインアセンブラ
『6.4 Extensions to the C Language Family』
http://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions
特に,『6.41 Assembler Instructions with C Expression Operands』や『6.42 Constraints for asm Operands』
GCCインラインアセンブリを使用する方法と留意点等 for x86
http://sci10.org/on_gcc_asm.html
GCCインラインアセンブラの書き方 for x86
http://d.hatena.ne.jp/wocota/20090628/1246188338
Linuxにおけるx86インライン・アセンブラー』
http://www.ibm.com/developerworks/jp/linux/library/l-ia/
『Using Inline Assembly With gcc
http://www.cs.virginia.edu/~clc5q/gcc-inline-asm.pdf
GCC-Inline-Assembly-HOWTO』
http://ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html

読んでみる

・breakpoint関数

static __inline void
breakpoint(void)
{
	__asm __volatile("int $3");
}

__inlineがつくことで関数がインライン化される.
関数呼び出しなしで関数内が実行される.
ただ,そのままでは名前空間の関係で他のソースコードから呼ばれる可能性があるため,関数がコード上に出力されてしまう.
static __inlineとすることで関数がファイルローカルになり,関数が完全に出力されなくなる.


__inlineなし,__inlineあり,static __inlineありの3パターンのコードを用意して,gcc -O2 → objdump -d などして確かめてみると違いが分かる.


インライン化は関数呼び出しのオーバヘッドを無くすために利用している.
特にここでは,アセンブラを発行するだけの関数なので,アセンブラを発行する度に関数呼び出しの処理が入るのは無駄.
オーバヘッドの問題だけでなく,プログラマが望んだコードとならないことも問題.


関数内を読んでみる.__asm は インラインアセンブラを示すキーワード(環境や定義によって__asm__ だったり asm だったり).
__asm ("hoge"); と書くと hoge というアセンブラをコードに出力する.


この hoge が,本当にその位置で実行されるかは分からない.
その原因の1つが,コンパイラによる最適化.
コンパイラの判断によって,hoge が消されたり位置が変えられたりすることがある.


コンパイラの最適化を防ぐのが,__volatile(だったり,__volatile__ だったり volatile だったり.).
__asm __volatile ("hoge"); と書くとアセンブラコンパイラの最適化を防いで,hoge をそこで実行することを意味する.


さて,int $3命令の意味を調べてみたい.
説明は,"IntelR 64 and IA-32 Architectures Software Developerfs Manual"のVolume 2で探す.
すると,int $3 は,INT n/INTO/INT 3?Call to Interrupt Procedureの仲間でオペコードがccということが分かる.


Descriptionの3段落目には,debug exception handlerを呼ぶ命令ともある.
debug exceptionについては,Volume 3 の Chapter 6 の 6.15 EXCEPTION AND INTERRUPT REFERENCEや,Chapter 17 DEBUGGING, BRANCH PROFILING, AND TIME-STAMP COUNTER に詳しい.


デバッガでブレークポイントを設定するときにこの命令が埋めこまれると考えれば良い.
話は戻るけれど,ブレークポイントを設定するのでint $3は指定した位置で実行してもらわないと困る.
それで__inlineでコンパイラの最適化を防いでいる.


・bsfl

static __inline u_int
bsfl(u_int mask)
{
	u_int	result;

	__asm("bsfl %1,%0" : "=r" (result) : "rm" (mask) : "cc");
	return (result);
}

u_int は unsigned intのこと.


マニュアルからbsfl命令を探しても見付からない.
GNUアセンブラでは複数のデータ型を扱う命令の最後に付けた文字で,その命令が扱うデータ型を表現している.
bsflの l は,扱うデータ型がlong(32bit)であることを表現している.
それで,マニュアルからは,bsf命令を探そう.


bsflの後ろに%1と%0がある.
そして,"bsfl %1,%0"の後には,さっきは無かったコロンで区切られた指示が続いている.
これらはワンセットで見ないといけない.


ここで,__asm()を入力と出力を持ったモジュールと考えてみよう.
__asm()を実行する際は,メモリやレジスタを入力として渡し,実行後はメモリやレジスタに出力が返る.
__asm()は入力を元に何らかの処理を行なうので,場合によってはレジスタを書き換えることもある.
上で出てきたコロンの指示はこれを表わしている.


この形式は拡張アセンブリと呼ばれていて,次の構文になっている.
__asm(アセンブリコード : 出力オペランド : 入力オペランド : 上書きされるレジスタ);
より正確には,次のような構文だ.
__asm("アセンブリコード" : "制約"(出力オペランド) : "制約"(入力オペランド) : "上書きされるレジスタ");


まず,アセンブリコードを見てみよう.
マニュアルでは,
BSF r32, r/m32
となっている.
このとき,Intelの書式では,1番目のr32がデスティネーション,2番目のr/m32がソースになっている.
ところが,GNUが採用しているAT&Tの書式では,1番目がソース,2番目がデスティネーションになる.


したがって,

	__asm("bsfl %1,%0" : "=r" (result) : "rm" (mask) : "cc")

は,%1の最下位側に立っているビット位置を%0に格納することになる.
それでは,%0や%1は何だろうか.


ここで,次にアセンブリコードの次の出力オペランドと入力オペランドを見てみよう.
出力オペランドの制約は"=r",オペランドは(result),入力オペランドの制約は"rm",オペランドは(mask)だ.


%0や%1は,この出力オペランドと入力オペランドに対応している.
左から順に,%0,%1,%2に対応する.


したがって,"=r"(result)が%0,"rm"(mask)が%1に対応していて,ようするに変数maskの最下位側に立っているビットの位置を変数resultに格納することを示している.
変数maskは,bsfl関数の引数,変数resultは,bsfl関数のはじめで定義されていて戻り値に利用されている.


では,出力オペランドの制約はどういう意味だろう.
制約は"=r"となっている.
1文字目の"="は,出力オペランドには付けるきまりになっている.
2文字目の"r"は,オペランド(ここでは,変数result)を汎用レジスタに対応付けることを指示している.


入力オペランドの制約も同様に見てみよう.
制約は"rm"となっている.
1文字目の"r"は,先程と同様に汎用レジスタへの対応付けを,2文字目の"m"はメモリへの対応付けを指示している.
maskは,汎用レジスタあるいはメモリに対応付けられる.


最後に残るのが,上書きされるレジスタの項目.
ここでレジスタを指定すると,コンパイラが必要に応じて命令実行前の退避や実行後の復帰をしてくれる.
今回使われている"cc"は何かというと,Condition Code registerのこと.
詳細はこのページを見てほしい.http://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Extended-Asm


condition code registerは別名flag registerとも呼ばれていて,こっちの方がなじみがある呼び方かもしれない.
結果が負数になったsign flag立って,,とかのあれ.
x86ではEFLAGSがcondition code registerの役割を担当している.


つまり,"cc"が付いているということは,EFLAGAレジスタの結果が上書きされるということを意味している.


マニュアルのVolume1,Appendix A EFLAGS Cross-Referenceを見てみると,各命令がEFLAGSのどのフラグに影響を与えるか書かれている.
フラグをresetのみする命令やsetのみする命令もあるが,どうやら,結果に応じてsetとresetのどちらかを行なう命令に"cc"を付ける必要があるようだ.
nBSF命令はZFがそうなっている.
(ソースのビットが立っていないときは,デスティネーションに0が入ってZFが立ち,ソースのビットが立っていればデスティネーションに1が入ってZFが落ちる.)


・cflush
bsrl関数は,bsfl関数と同じなので飛ばして次へ.

static __inline void
clflush(u_long addr)
{

	__asm __volatile("clflush %0" : : "m" (*(char *)addr));
}

コロンが2つしか無い.
そう,この構文では,入力オペランドだけが書かれている.
出力オペランドと上書きされるレジスタは書かれていない.
上書きされるレジスタがない場合は,3つめのコロンは省略できる.


clflushは,キャッシュラインを無効化するための命令.
リニアアドレスをソースで指定すると,そのリニアアドレスを含むキャッシュラインを無効化する.


・disable_intr

static __inline void
disable_intr(void)
{
#ifdef XEN
	xen_cli();
#else	
	__asm __volatile("cli" : : : "memory");
#endif
}

XENFreeBSDXENのゲストで利用する場合に有効にする.
ここでは,XENが定義されていないときの方を見てみよう.


cli命令は,割り込みフラグを落として,割り込みを無効化する命令.


上書きされるレジスタの箇所に"memory"がある.
"memory"は予期しないメモリ破壊があるときに書いておく,と説明されている.
どういうことかというと,cli命令の実行前後でメモリの値をレジスタにキャッシュしたままにしないことをコンパイラに要求している.
メモリの値をレジスタに入れていたら,cli命令を実行する前にレジスタからメモリに書き戻してくださいね,と.


"memory"指定は,インラインアセンブラを読む上では,あまり気にしなくて良いかもしれない.
書いてる本人も詳しくない.


・do_cpuid

static __inline void
do_cpuid(u_int ax, u_int *p)
{
	__asm __volatile("cpuid"
			 : "=a" (p[0]), "=b" (p[1]), "=c" (p[2]), "=d" (p[3])
			 :  "0" (ax));
}

cpuid命令は,eaxレジスタで指定した種類のCPU情報(CPUの種類や利用可能な機能など)を汎用レジスタeax,ebx,ecx,edxに返す命令.
ところで,cpuid命令の説明の See also: の直前には,serialize instruction execution と書かれている.
cpuid命令が入ると命令の実行がシリアル化されるので,out-of-orderな最近のプロセッサで命令の追い越しがおきなくなる効果あり.(Pentium以降)
リング0からリング3のどの特権でも使える命令の中で一番使いやすいこともあって,シリアル化命令の中ではよく利用されていると思う.
rdtsc命令と一緒に使うのがよくある使い方だろうか.
シリアル化命令の詳細は,マニュアルの 8.3 SERIALIZING INSTRUCTIONS に詳しい.


さて,本題.
出力オペランドがずらっと並んでいる.
"a"はeax(あるいはax,al),"b"はebx(あるいはbx,bl),"c"はecx(あるいはcx,cl),"d"はedx(あるいはdx,dl)


入力オペランドは1つだけ.
"0"は,アセンブリで指定する %0 と似た意味で,出力・入力オペランドで登場した順番でレジスタやメモリを指している.
"0"なので,"=a" (p[0]) を指している.


上書きされるレジスタは指定なし.


・cpuid_count関数

static __inline void
cpuid_count(u_int ax, u_int cx, u_int *p)
{
	__asm __volatile("cpuid"
			 : "=a" (p[0]), "=b" (p[1]), "=c" (p[2]), "=d" (p[3])
			 :  "0" (ax), "c" (cx));
}

一部,ecxレジスタの値も利用するものがあり,そのために入力オペランドに "c" (cx) が追加している.


・enable_intr関数

static __inline void
enable_intr(void)
{
#ifdef XEN
	xen_sti();
#else
	__asm __volatile("sti");
#endif
}

stiは,cliの逆で,EFLAGSのIFを立てる命令.
割り込みが有効になる.


・mfence関数

static __inline void
mfence(void)
{

	__asm __volatile("mfence" : : : "memory");
}

mfence命令は,store系の命令(レジスタ→メモリ)とload系の命令(メモリ→レジスタ)をシリアル化する命令だ.
mfence命令より前のstore系の命令やload系の命令は,mfence命令の実行前に完了し,後のstore系・load系の命令は,mfence命令の実行後に実行されるように,CPUに指示をしている.
メモリバリアとかメモリフェンスとか呼ばれる.

マニュアルの 8.2.5 Strengthening or Weakening the Memory-Ordering Model も詳しい.

まとめ

cpufunc.h を全部読むまでには至らなかった.
ただ,今回読んだ範囲の知識を活用すれば,残りのインラインアセンブラも読めると思う.
インラインアセンブラを書けるようになることをめざそう.

Unix/Linuxプログラミング 理論と実践 第2章(プログラミング課題) つづき2

備忘録です.
環境:FreeBSD 8.2-STABLE
書籍:Unix/Linuxプログラミング理論と実践

2.15の話.ソースコードFreeBSD版から引用.

続きを読む

Unix/Linuxプログラミング理論と実践 第2章(プログラミング課題) つづき

備忘録です.
環境:FreeBSD 8.2-STABLE
http://www.amazon.co.jp/gp/product/4048700219

2.11の話.FreeBSD版のソースコードから引用.

manを読んでみる

コピー元とコピー先の両方が同一ファイルだとコピーに失敗する.

$ man cp
...
If cp detects an attempt to copy a file to itself, the copy will fail.

cpを実行してみる

同一ファイルに対して実行すると下記のとおり,警告が出力される.

$ cp file1 file1
cp: file1 and file1 are identical (not copied).

ソースコード調査

"are identical (not copied)."をコード中で探すと,cp.cのl.321以降で登場する.

			if (to_stat.st_dev == curr->fts_statp->st_dev &&
			    to_stat.st_ino == curr->fts_statp->st_ino) {
				warnx("%s and %s are identical (not copied).",
				    to.p_path, curr->fts_path);

to_statとcurr->fts_statpを見つけて,そのメンバを見れば何をやっているのかが分かりそう.順に探す.

to_statとは?

to_statは以下のとおり定義され,データを格納されている.
stat()は,第1引数のパスが指すファイルの情報を第2引数に格納する.詳細は,$ man 2 stat で.
struct statを読めば上で何を比較しているのかすぐに分かるけれど,ここはあえて置いておく.

	struct stat to_stat;

		if (stat(to.p_path, &to_stat) == -1)
			dne = 1;

stat()の第1引数で渡しているtoの定義はつぎのとおり.

static char emptystring[] = "";

PATH_T to = { to.p_path, emptystring, "" };

ここで出てくるPATH_Tは,cp.cと同じディレクトリのextern.hに定義あり.

typedef struct {
	char	*p_end;			/* pointer to NULL at end of path */
	char	*target_end;		/* pointer to end of target base */
	char	p_path[PATH_MAX];	/* pointer to the start of a path */
} PATH_T;

それで,実際にto.pathに何が格納されているのかというと,コピー先のパスで初期化されている.

	target = argv[--argc];
	if (strlcpy(to.p_path, target, sizeof(to.p_path)) >= sizeof(to.p_path))
		errx(1, "%s: name too long", target);

ということで,to_statにはコピー先ファイルの情報が格納されていることが分かった.

curr->fts_statpとは?

currは,FTSENT型へのポインタで,fts_read()の結果が格納されている.

	FTSENT *curr;

	for (badcp = rval = 0; (curr = fts_read(ftsp)) != NULL; badcp = 0) {

ftspは,FTS型へのポインタで,fts_opne()の結果が格納されている.

	FTS *ftsp;

	if ((ftsp = fts_open(argv, fts_options, mastercmp)) == NULL)

ここで出てきたFTSとFTSENTは,FreeBSDが提供するftsライブラリ(UNIXのファイル階層を再帰的に処理するときに役立つ)の構造体.
FTS構造体は,ファイル階層を表現して,FTSENT構造体はファイル階層のファイルを表現するとのこと.
詳細は,$ man fts 参照.ftsは,よく分かっていない.


fts_open()が実行される段階で,argvを渡している.
このargvは何を指しているのか.


argcとargvに格納された値は,プログラム中で変更されている.

  • オプションのチェックが終わった段階で,argvは{"cp", "file1, "file2"}の"file1"を指し,argcには2が格納されている状態.

その後,下の処理が行なわれるため,fts_opne()実行時に,argvは{"cp", "file1", NULL}の"file1"を指している.

	/* Set end of argument list for fts(3). */
	argv[argc] = NULL;

argvは"cp"の次の"file1"を指しており,コピー先ファイルを示す最後のエントリは消されている(NULL).
それで,fts_open()にはコピー元ファイル名が渡される.
fts_open()で,渡したファイル群に対するFTS構造体を受け取り,fts_read()で順にファイルのFTSENT構造体を受け取る.
したがって,currにはコピー元ファイルの情報が入ることになる.


結果,コピー元ファイルとコピー先ファイルが同一であると判断する基準は,以下の2つを満足するときであると分かる.

  • コピー元ファイルとコピー先ファイルのあるデバイスIDが等しい
  • コピー元ファイルとコピー先ファイルのinodeが等しい

どう実装するのか

2章で作ったcpコマンドは,コピー元が1つしか無い前提.
そのため,コピー元もコピー先と同じように,stat()を利用してstat構造体を取得するのが分かりやすそう.

Unix/Linuxプログラミング理論と実践 第2章(プログラミング課題)

備忘録です.解答にはなっていないかったりします.
環境:FreeBSD 8.2-STABLE
http://www.amazon.co.jp/gp/product/4048700219

2.10 アイデンティティの危機

who am i

$ who am i

$ who -m

と同義.

manには「-m 標準入力に接続された端末情報のみを表示します。」と説明ある.
上記コマンドを実行すると,コマンドを実行した端末の情報のみを表示する.


who2.cを書き換える際の参考情報.
システムのstruct utmpの中から,標準入力に接続された端末の情報のみを表示する.


まずは,端末名を取得する.
ttyname()を使い,ファイルディスクリプを渡すと,ttyの名前を/dev/ttyxxの形式で返してくる.
なお,標準入力のファイルディスクリプタはSTDIN_FILENO.

#include <unistd.h>
char* ttyname(int fd)

struct utmpのut_lineに端末名があるが,これはttyxxの形式で入っている.
それで,まずは/dev/ttyxxの形式からttyxxだけを抜き出す.
抜き出すために,次のライブラリ関数を利用できる.
strrchr()は,char型のポインタsが指す文字列中で,文字cが最後に出た場所のポインタを返す.

#include <string.h>
char* strrchr(const char *s, int c);
whoami
$ whoami

は,

$ id -un

と同じ.
機能は,実効UID(Effective UID),つまりプログラム実行時の権限判断に利用するUIDの名前を表示することです.
$ who am i で表示されるのは,ユーザのログイン名.


whoamiコマンドはidコマンドと同じプログラムから生成(実行時にコマンド名で処理切り替え)するので,idコマンドのプログラムをチェックする.
id.cのl.174からが該当する処理です.

	if (uflag) {
		id = pw ? pw->pw_uid : rflag ? getuid() : geteuid();
		if (nflag && (pw = getpwuid(id)))
			(void)printf("%s\n", pw->pw_name);
		else
			(void)printf("%u\n", id);
		exit(0);
	}

まずは次の行.

		id = pw ? pw->pw_uid : rflag ? getuid() : geteuid();

変数pwはl.79で定義されている.

	struct passwd *pw;

passwd構造体は/usr/include/pwd.hのl.116から定義されている.
この構造体を,FreeBSDでユーザIDやパスワードの管理に利用している.$ man 5 passwd が参考になる.

struct passwd {
	char	*pw_name;		/* user name */
	char	*pw_passwd;		/* encrypted password */
	uid_t	pw_uid;			/* user uid */
	gid_t	pw_gid;			/* user gid */
	time_t	pw_change;		/* password change time */
	char	*pw_class;		/* user access class */
	char	*pw_gecos;		/* Honeywell login info */
	char	*pw_dir;		/* home directory */
	char	*pw_shell;		/* default shell */
	time_t	pw_expire;		/* account expiration */
	int	pw_fields;		/* internal: fields filled in */
};

変数pwは,id.cのl.153で初期化されている.

	pw = *argv ? who(*argv) : NULL;

このときargvは,コマンド実行時のオプションの後ろを指している.
オプションの後ろには,ユーザ名 or ユーザIDを指定できることになっている.
つまり,ユーザ名orユーザIDを指定していればwho()が呼ばれ,何も指定していなければNULLがpwには入る.今はNULL.
戻って,次の行.pw==NULL,rflag==0なので,idにはgeteuid()の結果が返る.
geteuid()は呼び出しプロセスの実効ユーザIDを返す.

		id = pw ? pw->pw_uid : rflag ? getuid() : geteuid();

次の行.nflag==1.getpwuid()はパスワードデータベースを検索して渡したidに該当する最初のエントリを返す.
struct passwdへのポインタがpwに返り,printf()でstruct passwdのpw_nameを表示する.
したがって,whoamiコマンドは,呼び出しプロセスの実効ユーザ名を表示する.

		if (nflag && (pw = getpwuid(id)))
			(void)printf("%s\n", pw->pw_name);

Unix/Linuxプログラミング理論と実践 第2章(研究課題)

備忘録です.解答にはなっていないかったりします.
環境:FreeBSD 8.2-STABLE
http://www.amazon.co.jp/gp/product/4048700219

2.1 wコマンド

whoとwを実行して結果を比較してみると,wではidle時間が表示されていることが分かる.
wコマンドのソースコードを見てみると,"現在時間-最後にttyにアクセスした時間"がidle時間とされている.

                if ((ep->idle = now - touched) < 0)
                        ep->idle = 0;

wコマンドは,下の構造体を定義して使っている.
dev_t tdev(デバイス番号),struct kinfo_proc *kp(トップレベルのプロセス?),char *args(引数のリスト)なども利用している様子.

/*
 * One of these per active utmp entry.
 */
struct  entry {
        struct  entry *next;
        struct  utmp utmp;
        dev_t   tdev;                   /* dev_t of terminal */
        time_t  idle;                   /* idle time of terminal in seconds */
        struct  kinfo_proc *kp;         /* `most interesting' proc */
        char    *args;                  /* arg list of interesting process */
        struct  kinfo_proc *dkp;        /* debug option proc list */
} *ep, *ehead = NULL, **nextp = &ehead;

2.4

FILE構造体は,/usr/include/stdio.hで定義されている.

2.5

syncシステムコールとsyncユーティリティ

2.9

$ man lseek に書いてあるとおり,lseek()でEOF以降に位置を設定して書き込むことが可能.
その場合,元のEOFから書き込み位置までの間を読み出すと 0 が返ってくる.
lseek()で設定しただけでは,ファイルサイズは変わらない.