2015年1月30日金曜日

Perl でキーボードからリアルタイムに文字入力

Perl で入力した文字をリアルタイムに読みたいことがあるが、普通に

$char = <STDIN>;

と書くと
  • ENTER キーを押すまでは入力されない
  • 入力した文字がそのまま画面に表示される(エコーバック)
という問題がある。

◇ (方法1) Term::ReadKey を使う (Windows, UNIX)


Windows で使える方法はないかと思って調べたら、実は PerlFAQ に書いてあった方法。知らなかったが通常はこちらで OK だろう。

use Term::ReadKey;
ReadMode('cbreak');
while(1) {
  if (defined ($char = ReadKey(-1)) ) {
    # 文字が入力された場合
      print STDERR $char, "\n";
  } else {
    # 文字が入力されていない場合
  }
}
# ReadMode('normal');


こう書けば押したキーの文字がリアルタイムで $char に代入される。
何も押さなかった場合は「# 文字が入力されていない場合」のほうが実行される。

"Term::ReadKey" モジュールのインストールが必要。
便利なことに Windows のプロンプトでも利用できる。ActivePerl5.20.1 ではTerm::ReadKey モジュールは最初からインストールされていた。

◇ (方法2) stty コマンドを使う (UNIX)

$|=0;

system("stty cbreak");
system("stty -echo");

while(1) {
  sysread(STDIN,$char,1);
  print STDERR $char, "\n"

}

UNIX 限定だが、system() 関数で stty コマンドを実行することでも似たことが実現できる。
"Term::ReadKey" モジュールがインストールできない場合はこちらで。 ただ、この書き方では何か文字が入力されるまで sysread(STDIN,$char,1); のところで動作が停止(ブロック) してしまうので、リアルタイムな処理はできない。リアルタイムに処理するためには工夫が必要になる。

2014年6月22日日曜日

AVR-GCC でタイマ割り込みに間に合わない場合(部分的に最適化レベルを変える)

タイマ割り込み中に行う処理は、当然だが割り込み間隔以下の時間で実行が終わるステップ数で書かないといけない。

AVR-GCC でコンパイルするときには、できるだけコードサイズを節約するために、最適化オプションをコードサイズ優先 (-Os) にすることが多い。しかし、タイマ割り込み中に実行する命令が多すぎて、割り込み間隔に間に合わないときには、割り込みハンドラの部分だけを速度優先 (-O3) で最適化してほしい。

これは下記のように 割り込みハンドラの前に #pragma を置いて最適化レベルを速度優先 (-O3) と指定すれば実現できる。割り込みハンドラの後ろにも #pragma を置いて、最適化レベルをコードサイズ優先 (-Os)に戻す

#pragma GCC optimize ("O3")
ISR(SIG_OUTPUT_COMPARE0A) {
  タイマ割り込み中に行う処理
} 
#pragma GCC optimize ("Os")

コンパイルするときに下記のように -Os オプションを付ければ、割り込みハンドラ以外はコードサイズ優先 (-Os) で、割り込みハンドラ部分だけ速度優先 (-O3) で最適化される。

% avr-gcc -g -Os -mmcu=atmega8 -o out.o main.c

2014年6月19日木曜日

AVR-GCC 用内蔵 EEPROM 簡易ファイルシステム

ダウンロード

MEGA8 と TINY85 で動作確認したが、おそらく他の AVR でも使える。avr-gcc, avr-libc でプログラミングする人向け。

解説

多くの AVR マイコンには EEPROM (不揮発性メモリ) が内蔵されている。容量は少ない機種で 64Byte、多くても数キロバイト程度だが、電源を切っても消えないので、設定やデータをちょっと保存するのに便利だ。

AVR 用のライブラリ avr-libc には EEPROM を読み書きする関数があるので、バイト単位やワード単位でアドレスを指定して読み書きできる。しかし、EEPROM に複数の可変長のデータを保存したい時など、もう少しだけ扱いやすい読み書きの手段が欲しい。自分は赤外線リモコンを作った時に学習データ(可変長。複数。学習のたびに書き換えが必要)を保存するために必要だった。

そこで、AVR 内蔵 EEPROM 用に簡易ファイルシステムを作ってみた。C 言語でファイルアクセスする時の open(), close() といったスタイルをまねてはいるが、必要最小限の機能のみに絞った。原理はおそらく誰もが思いつく一番単純な方式。

コードサイズは1kB弱。使わない関数をコメントアウトすればもう少しだけ小さくできる。

使用例


例として、MEGA8 を PC のシリアルポートに接続して、MEGA8 から出力した文字列を PC に表示させる回路を作った。



MEGA8 に書き込んだデモのコードは次の通り。このプログラムは、文字列を直接シリアルポートに出力するのではなく、一旦ファイルに保存して、ファイルから読みだした文字列をシリアルポートに出力するというもの。

// EEPROM 用簡易ファイルシステムのデモ (ATMEGA8)
#include <avr/io.h>
#include <avr/eeprom.h>
#include "fs.h"

// シリアルポートの初期化(1200bps, MCU クロック=1MHz)
void uart_init(void) {
  UCSRB  = _BV(TXEN);
  UBRRH=0;  UBRRL= 51;
}

//
// シリアルポートに1文字出力
//
void  uart_putchar(char c) {
  loop_until_bit_is_set(UCSRA, UDRE);
  UDR = c;
  return;
}

// メイン
//
int main(void) {

  uint8_t fileName;
  char buf[100];
  char *pt;

  uart_init();
  fs_init();

  fileName = 0;
  if (fs_ropen(fileName) == FS_OK) {
    // ファイル0 がオープンできたら、ファイルからシリアルポートに出力
    // 1行目表示
    while (!fs_EOF()) uart_putchar(fs_read_b());
    // 2行目表示
    fileName = 1;
    fs_str_r(buf, fileName, 100);
    pt=buf;
    while (*pt) { uart_putchar(*pt); pt++; }
  } else {
    // ファイル0 がオープンできなかったら、文章をファイルに書き込む
    fileName = 0;
    if (fs_wopen(fileName) == FS_OK) {
      char* pt = "恥の多い生涯を送って来ました。"
        "自分には、人間の生活というものが、見当つかないのです。\r\n";
      while (*pt!='\0') { fs_write_b(*pt); pt++; }
      fs_wclose();
      fileName = 1;
      fs_str_w("自分は東北の田舎に生れましたので、汽車をはじめて見たのは、"
               "よほど大きくなってからでした。\r\n", fileName);
    }
  }
  while (1); // どちらかの処理をした後は無限ループ
  return 0;
}


このソースファイル(main.c) と同じ場所に "fs.c" と "fs.h" を置き、コンパイルするときに fs.c にある関数がリンクされるようにする。コンパイルの手順はこんな感じ。

# コンパイルとリンク,Intel HEX 形式の出力
% avr-gcc -g -Os -mmcu=atmega8 -o out.o main.c fs.c
% avr-objcopy -j .text -j .data -O ihex out.o out.hex

ヒューズは MEGA8 のデフォルト(hfuse=0xd9, lfuse=0xe1)にする。

生成した out.hex を MEGA8 に書きこんだら、上記の回路図に従って MEGA8  を PC のシリアルポートに接続して、PC 上で TeraTerm を起動する。TeraTerm でシリアルポートを 1200bps に設定する。

1回目の電源ONではファイルが存在しないため、fs_ropen(fileName) はエラーを返すので、ファイルに文字列が書き込まれるだけで、シリアルポートには何も出力されない。2回目以降の電源 ON ではファイルが存在するので fs_ropen(fileName) に成功して分岐し、ファイルから文字列が読み出されシリアルポートに出力される。Teraterm で見ると、2回目以降の実行ではちゃんと文字列が出力されている。



ファイルシステム fs.c の使い方

 使い方は次の通り。ファイル名(というかファイル番号) は、0~255 の値1バイトのみ。

ファイルから読み込む場合は、まず fs_ropen(fileName) でファイルを読み込みモードでオープンする。続けて fs_read_b() (8bitの場合)または fs_read_w() (16bitの場合) を繰り返して読み込む。読むと自動でポインタが進む。最後まで読むと fs_EOF() が真になる。読み込みモードでオープンした場合はクローズはしなくてもよい。

ファイルに書き込む場合は、まず fs_wopen(fileName) でファイルを書き込みモードでオープンする。fs_write_b(8bit値) または fs_write_w(16bit値) を繰り返して書き込む。書き込むと自動でポインタが進む。書き終わったら fs_wclose() でクローズする必要がある。

オープンできるファイルは、読み込みと書き込み合わせて1つだけ。同時オープンはできない(EEPROM の書きこみ/読み込み位置を保持している変数が1つしかないため)。

オープン、クローズの手続きが不要で、文字列を一気に読み込む fs_str_r(buf, fileName, maxlen) という関数と、 同じく文字列を一気に書き込む fs_str_w(buf、fileName) という関数もある。文字列を扱うならそちらの方が便利。

構造

全てのファイルの先頭に3バイトのヘッダを付けて EEPROM に格納する。ヘッダの1バイト目はファイル名、2~3バイト目はファイルサイズ。ファイルは詰めて記録し、最後のファイルの後に終了マークとして 0xFF を3バイト書く。構造はこれだけ。

ファイルが何もない状態から、ファイル名=2 のファイル(サイズ=10バイト)を書き込み、次にファイル名=0のファイル(サイズ=15バイト)を書き込むと、EEPROM は下の図のような状態になる。

もし下記のような状態でファイル2が削除されたときは、その下にあるファイル0を上方向(低アドレス側)に詰める。つまりファイル0のヘッダとファイル0の実体、終了マーク全体を13バイト上にずらす EEPROM のコピーの動作が自動的に行われる。非効率的で遅いが、EEPROM のサイズは小さいので、このような力技でもなんとかなる。もっとも頻繁にファイルの削除や更新が発生する用途やタイムクリティカルな用途には向かない。

fs.h で指定された EEPROM のアドレス範囲外には書き込みが行われないようにアドレス範囲をチェックしていて、もしアドレス範囲をオーバーしている時は実際の書き込みは行わず、戻り値でエラー(FS_DISKFULL) を返す。ファイルの書き込み後にはファイルクローズが必要だが、クローズ時にアドレス範囲のオーバーが発生していないことを再度確認している。なので、書き込み後にはファイルをクローズして fs_wclose() の戻り値が FS_OK になっていることを確認する必要がある。

EEPROM のデータ構造 (ファイル2とファイル0の2個のファイルが保存されている状態)


定義

注: ソースファイルの方の変数の型の書き方を変更しました (例: unsigned char → uint8_t など)。プログラムの動作は変わりませんが、下記の説明の型とは食い違っています。

定数
#define EEPROM_FS ((uint8_t *)0)
ファイルシステムで使用したい EEPROM の先頭アドレスを fs.h 内のこの #define 文で指定すること。普通はこの例のようにアドレスに 0 を指定すればよい。 EEPROM の先頭アドレスから使用される。


定数
#define FS_SIZE (E2END-(unsigned int)EEPROM_FS+1-4)
ファイルシステムで使用したい EEPROM のサイズを fs.h 内のこの #define 文で指定すること。E2END が EEPROM の最終アドレス(avr-libc で定義済み)。EEPROM の全領域を使用するなら E2END-EEPROM_FS+1 を指定する。ただし EEPROM の末尾 4バイトはフラッシュメモリの書き込み回数の記録に使われることが多いので、普通はこの例のように -4 して末尾 4 バイトを空けたほうが良い。

マクロ
char fs_EOF()
fs_ropen() による読み込みオープン中のみ有効。オープン中のファイルに、まだ読めるデータが残っていれば偽、すでに最後まで読み終わっていれば真になる。実体はマクロ。

引数
  なし
戻り値
  ファイルを最後まで読み終わっていれば真

マクロ
void fs_format()
ファイルをすべて削除する。ファイルシステムに割り当てられた EEPROM の全領域に 0xff を書き込む(実際は先頭3バイトと末尾3バイトのみに 0xff を書きこめば事足りるが、コードの簡略化のため全領域に書いている)。出荷時の AVR の EEPROM は全領域が 0xff なので、フォーマット済みとして扱える。実体はマクロ。

引数
  なし
戻り値
  なし

関数
void fs_init(void);
ファイルシステム関係の変数を初期化する。コードの先頭で1回実行する。

引数
  なし
戻り値
  なし

関数
unsigned char fs_delete(unsigned char fn); 
指定したファイルを削除する。

引数
  fn: ファイル名
戻り値
  FS_OK: 削除に成功した
  FS_NOTFOUND: 指定されたファイルは存在しない

関数
unsigned char fs_ropen(unsigned char fn);
引数で指定したファイルを読み込みモードでオープンする。読み込みでオープンした場合はクローズはしなくてもよい。ファイルがオープンされている間は次の変数が使える(下記の変数は読み込みのみ。変更してはいけない)。

unsigned int fs_size: オープン中のファイルのサイズ
uint8_t *fs_pt: 現在の読み込み位置(EEPROM のアドレス)

引数
  fn: ファイル名
戻り値
  FS_OK: オープンに成功した
  FS_NOTFOUND: 指定されたファイルは存在しない

関数
unsigned char fs_read_b(void);
読み込みモードでオープン中のファイルから 8bit 値を読み込む。

引数
  なし
戻り値
  8bit値

関数
unsigned int fs_read_w(void);
読み込みモードでオープン中のファイルから 16bit 値を読み込む。EEPROM での 16bit 値のバイトオーダーは下位-上位の順(リトルエンディアン)。

引数
  なし
戻り値
  16bit値

関数
unsigned char fs_wopen(unsigned char fn);
指定したファイルを書き込みモードでオープンする。引数で指定したファイルがすでに存在していた場合は、書き込みモードでオープンした時点で削除される。ファイルがオープンされている間は次の変数が使える(下記の変数は読み込みのみ。変更してはいけない)。

unsigned int fs_size: 書き込んだサイズ
uint8_t *fs_pt: 現在の書き込み位置(EEPROM のアドレス)

引数
  fn: ファイル名
戻り値
  FS_OK: オープンに成功した

関数 
unsigned char fs_write_b(unsigned char val);
書き込みモードでオープン中のファイルに 8bit 値を書き込む。

引数
  val: 8bit値
戻り値
  FS_OK: 書き込みに成功した
  FS_NOTFOUND: 書き込みモードでオープンされていない
  FS_DISKFULL: 容量の限界のため書き込めなかった

関数
unsigned char fs_write_w(unsigned int val);
書き込みモードでオープン中のファイルに 16bit 値を書き込む。EEPROM での 16bit 値のバイトオーダーは下位-上位の順(リトルエンディアン)。

引数
  val: 16bit値
戻り値
  FS_OK: 書き込みに成功した
  FS_NOTFOUND: 書き込みモードでオープンされていない
  FS_DISKFULL: 容量の限界のため書き込めなかった

関数
unsigned char fs_wclose(void);
書き込みモードでオープン中のファイルをクローズする。この関数が FS_OK を返さなかった場合はファイルは書き込まれていない。書き込み後にこの関数を呼ばなかった場合も、ファイルには書きこまれない。

引数
  なし
戻り値
  FS_OK: 書き込みに成功した
  FS_NOTFOUND: 書き込みモードでオープンされていない
  FS_DISKFULL: 容量の限界のため書き込めなかった

関数
void fs_str_w(char* str, unsigned int fn);
fn で指定したファイルに、文字列 str を書き込む。 str は '\0' で終端されている必要がある。処理はこの関数だけで完結するので、別途ファイルをオープン、クローズする必要はない。

引数
  str: 書き込む文字列へのポインタ
  fn: ファイル名
戻り値
  なし

関数
char* fs_str_r(char* str, unsigned char fn, unsigned int maxlen); 
fn で指定したファイルから、文字列 str に読み込む。maxlen には str に読み込める最大の長さ(終端のための '\0' を含む)を指定する。読み終わった場合も、maxlen の制限で読み込みが中断した場合も str は '\0' で終端される。処理はこの関数だけで完結するので、別途ファイルをオープン、クローズする必要はない。

引数
  str: 書き込む文字列へのポインタ
  fn: ファイル名
  maxlen: str に読み込める最大の長さ(終端のための '\0' を含む)
戻り値
  str: str のポインタを返す

関数
unsigned int fs_free(void);
空き容量を返す。

引数
  なし
戻り値
  空き容量を返す
 

公開される変数
extern unsigned char fs_filename;
  オープン中のファイル名

公開される変数
extern unsigned int fs_size;
オープン中のファイルのサイズ。特定のファイルのファイルサイズを知りたいときは、試しにfs_ropen(unsigned char fn) してから fs_size の値を読む。


公開される変数
extern uint8_t *fs_pt;
  現在読み書き中のポインタ(EEPROM のアドレス)


2014年6月3日火曜日

AVR がウォッチドッグタイマ発動後に再起動を繰り返す問題

AVR マイコンにはウォッチドッグタイマが搭載されている。 ウォッチドッグタイマとは、メインプログラムの実行とは独立に勝手にカウントアップされていくカウンタで、そのカウンタがオーバーフローすると強制的にマイコンにリセットがかかるというものである。よってウォッチドッグタイマを有効にしたときには、メインプログラム側で、定期的にウォッチドッグタイマの値を0にクリアする命令を実行しないといけない。

これが何の役に立つかというと、例えば異常が発生してメインプログラムが無限ループにはまった時には、ウォッチドッグタイマがオーバーフローして自動的にリセットしてくれる。よって、完全に固まるという最悪の状況だけは避けることができる。

※ もっとも、運悪く無限ループの中にウォッチドッグタイマの値を0にクリアする命令が含まれていた場合は、ウォッチドッグタイマを有効にしていたとしても固まってしまうが。

他の使い方もある。例えば AVR にはリセット命令がないので、ソフトウェアからリセットするには、ウォッチドッグタイマを有効にした上で無限ループを実行するのが正規のやり方だそうである。

今回、赤外線リモコンの制作で、AVR が暴走した時のために、ATTiny85 のウォッチドッグタイマを 有効にした。AVR-libc のマクロ (avr/wdt.h) を使えば簡単に設定できる。実際に無限ループに陥った時には、ウォッチドッグタイマはきちんと動作して、リセットがかかってくれた。ところがその後がよくない。リセットがかかった直後にまたウォッチドッグタイマによるリセットが再発生し、再起動を繰り返してしまう。

これは一部の新しい AVR のみに発生する現象のようで、AVR-libc のマニュアルに回避策が書いてあった。ただ意味がちょっとわかりにくかったので、その部分をチョー訳してみた。

ソースファイルのどこかに下記の緑色の部分のコードを書いておけば、ウォッチドッグタイマによるリセットの後に、自動的にウォッチドッグタイマが無効になる。main() の途中で改めて wdt_enable() を使ってウォッチドッグタイマを有効にすればよい。

AVR-libc のドキュメントWatchdog timer (日本語)より


(原文)
Detailed Description
#include <avr/wdt.h>
This header file declares the interface to some inline macros handling the watchdog timer present in many AVR devices. In order to prevent the watchdog timer configuration from being accidentally altered by a crashing application, a special timed sequence is required in order to change it. The macros within this header file handle the required sequence automatically before changing any value. Interrupts will be disabled during the manipulation.

Note:
Depending on the fuse configuration of the particular device, further restrictions might apply, in particular it might be disallowed to turn off the watchdog timer.

Note that for newer devices (ATmega88 and newer, effectively any AVR that has the option to also generate interrupts), the watchdog timer remains active even after a system reset (except a power-on condition), using the fastest prescaler value (approximately 15 ms). It is therefore required to turn off the watchdog early during program startup, the datasheet recommends a sequence like the following:

    
#include <stdint.h>
#include <avr/wdt.h>
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) __attribute__((naked)) __attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}

(日本語訳)
詳細説明
#include <avr/wdt.h>
このヘッダファイルには、多くのAVRデバイスでサポートされているウォッチドッグタイマを扱うためのインラインマクロへのインタフェースが書かれている。アプリケーションがクラッシュして誤った警報を出さないように、ウォッチドッグタイマの設定を変更する時は特殊なタイミングの手続きが必要である。このヘッダファイルにあるマクロを使って変更すれば、変更前にその手続きが自動的に行われる。 変更中は割り込みは禁止される。

注意:
デバイスのヒューズの設定によっては、とくにウォッチドッグタイマの無効化が禁止されている場合には、さらなる制約がかかることがある。

新しいデバイス(ATmega88 以降。割り込みを生成するオプションがあるデバイス)では、(電源 ON の直後を除く)システムリセットの後でもなお、ウォッチドッグタイマが有効のままになり、最速のプリスケーラ値(約15ms)が設定されることに注意が必要である。そのため、データシートでは、プログラムの起動時の最初の方で、次のようなシーケンスでウォッチドッグを無効にするように要求している。
    
#include <stdint.h>
#include <avr/wdt.h>
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) __attribute__((naked)) __attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}
 



さて、この問題が起こる「新しいデバイス」とはどのデバイスのことなのか。マニュアルの記載からはわかりにくいが、ウォッチドッグタイマのオーバーフロー割り込みを持つ AVR マイコンという事だと思う。

同じく AVR-libc のマニュアルの「interrupt」の所に、各割り込みを持つデバイスはどれかの表がある。表によれば、下記の (1)~(3) に名前があるデバイスには上記の対応が必要のようだ。

(1) WATCHDOG_vect (SIG_WATCHDOG_TIMEOUT)
ATtiny24, ATtiny44, ATtiny84

(2) WDT_OVERFLOW_vect (SIG_WATCHDOG_TIMEOUT, SIG_WDT_OVERFLOW)
ATtiny2313

(3) WDT_vect (SIG_WDT, SIG_WATCHDOG_TIMEOUT)
AT90PWM3, AT90PWM2, AT90PWM1, ATmega1284P, ATmega168P, ATmega328P, ATmega32HVB, ATmega406, ATmega48P, ATmega88P, ATmega168, ATmega48, ATmega88, ATmega640, ATmega1280, ATmega1281, ATmega2560, ATmega2561, ATmega324P, ATmega164P, ATmega644P, ATmega644, ATmega16HVA, ATtiny13, ATtiny43U, ATtiny48, ATtiny45, ATtiny25, ATtiny85, ATtiny261, ATtiny461, ATtiny861, AT90USB162, AT90USB82, AT90USB1287, AT90USB1286, AT90USB647, AT90USB646

たとえば、自分の使っているデバイスでは、ATmega8, ATmega64 はこのリストにないが、ATmega88, ATtiny85 は含まれている。

2014年6月2日月曜日

どんな信号でも学習できる赤外線リモコンを作りたい

(2015/2/28 補足) 本文中に部品選択に関する記述がありますが、いろいろな製品で試した結果、現在推奨している赤外線受信ユニットは GP1UXC41QS、赤外線 LED は L-53F3BT です。

(2016/7/1 補足) Digispark 赤外線受信シールドに使われている赤外線受信ユニット TSOP38238 も適していますが、国内での入手が難しいかもしれません。

 まえがき

AVR マイコンATTiny85 で学習型万能赤外線リモコンを作った。

※ エアコンのリモコンも学習できるようになった
※ 実際に作ってみたい方は Wiki (←リンク) を参照 。
※ この技術を応用して USB から制御できる赤外線リモコン(リンク)を作成した。

プリント基板版

ブレッドボード版


実際に作ってみるまで分からなかったことが結構あって、今後赤外線リモコンを一から自作する人の参考になるかもしれないので、こちらのブログに、感想とか工夫した点とかノウハウっぽいことを書いておく。

赤外線リモコンの制作は電子工作ワールドでは人気テーマで、検索すると制作例が山のように出てくるので、新しく開発するからには何か特徴がないとおもしろくない。そういうわけで、今回の設計では「リモコンの種類(通信フォーマット)によらずなんでも学習できる」赤外線リモコンを作ることを目標にした。

また、ピン数の足りない安い8pinのマイコンで作れるように、学習と送信とその他の操作を全て1ボタンで操作できるようにした。

「なんでも学習できる」リモコンをわずかなメモリしかないマイコンで作るのは大変だ。受信した赤外線データを生のまま不揮発メモリ(内蔵EEPROM)に記憶して、それを送信する仕組みにすれば「なんでも学習できる」は実現できるが、そのためには生データを記憶しておくための不揮発メモリが大量に必要になるためだ。

これを解決するにはデータを圧縮するしかない。しかし、どんなデータでもきっちり圧縮できる (ZIPみたいな) 本物の圧縮アルゴリズムを作るのは難しいし、特にコード用のメモリも RAM も少ないマイコンで実装するのは簡単ではなさそうだ。そこで「赤外線リモコンに特化した、簡単で万能で効率の良い圧縮アルゴリズムを考える」という作戦にした。

工夫の結果、自分の身の回りのリモコンについてはほとんど学習して不揮発メモリに保存できるようになった。ただ圧縮がリアルタイムではできない(赤外線信号をサンプリングする速度に追いつかない)ため、圧縮前の生データを一時的にマイコンのメイン RAM に記録する必要があり、そこがネックになっている。メイン RAM のサイズが 512バイトのマイコンでは、1つの信号が RAM サイズを超えるために学習できなかったエアコンのリモコンがある。可能ならいずれ改良したい。 → この問題は、赤外線の受信中にリアルタイムにランレングス圧縮を行う機能を実装して解決した。

赤外線リモコンのフォーマット


赤外線リモコンのフォーマットについては、ELM さんの「赤外線リモコンの通信フォーマット」がとても詳しい。これによると、日本では主に NECフォーマット、家製協(AEHA)フォーマット、SONYフォーマットが使われているので、この三種類の規格に対応できればよさそうだ。

と思っていたがこれはだいぶ甘くて、実際に身の回りのリモコンの信号をサンプリングして見てみると、微妙に規格に沿っていないものがかなり多かった。

まずは手持ちのリモコンの信号を片っ端からサンプリングして見てみることにする。AVR マイコンでサンプリングした信号をシリアルで出力して、可視化するプログラムを VisualBasic 2008 で作ってみた。やはり可視化は重要で、生の数字を見るよりはるかに分かりやすい(赤い線と緑の線があるが、圧縮する際の参考用に色を変えているだけなのでここでは無視してよい)。

サンプリングした信号を可視化するプログラム(Visual Basic 2008)
Hが点灯、Lが消灯状態。赤外線受信モジュールの出力は負論理でこの図とは逆なので注意。
搬送波は赤外線受信モジュールで除去済み。

テレビのリモコンのフォーマットはばらばら

ビデオプレイヤーなどの AV 機器のリモコンにはたいてい他社のテレビを操作する機能がある。試しに Panasonic のブルーレイレコーダ DMR-BR670V のリモコンで、各社のテレビのリモコンの「電源」ボタンの信号をサンプリングしてみた。メーカー名の後のカッコの数字はこのブルーレイレコーダ独自のメーカーコード。同じメーカーに複数のメーカーコードが割り当てられているのは、同じメーカーでもテレビの機種によって送信するデータが異なる場合があるということ。


上の図を元の大きさで表示する
 ※ 図の1ドット=50[μs]。Hが点灯、Lが消灯状態。赤外線受信モジュールの出力は負論理でこの図とは逆なので注意。
各社のテレビのリモコンの赤外線フォーマット(各社対応リモコンからサンプリング)
サンプリング結果を観察してまとめてみたのが上の表。フォーマットはかなりばらばらだ。特に特殊なのはシャープ、ビクター、三菱のテレビだが、それ以外にも独自フォーマットの機種がある(上の図と対応させるために、全く同じデータの場合も表には重複して載せている)。

独自フォーマットのシャープ(02)とシャープ(11)にはリーダーがない。 また 30~50[ms]の間隔をあけて2つの異なるパターンが連続で送られてくるので、両方記録しないといけない(※図にはないが、シャープのVHSビデオデッキ VC-VS1 もこれと同じフォーマットだった)。シャープ(21)は素直な家製協フォーマット。シャープは昔は独自フォーマットだったが、最近の製品は家製協フォーマットに準拠したということだろうか。

ビクター(14)はリーダに続いてまず16bitのデータを送信する。その後同じデータが反復されて送られてくるが、反復の方にはリーダがなく、いきなり16bitデータから始まっている。手元にあったビクター製VHS/DVDデッキ用リモコン(RM-SHR013J)も同じフォーマットなので、これがビクター独自のフォーマットなのだろう。

三菱(08)と三菱(12)はリーダはなく、データも6bitのみ。必要最小限といった感じ。

受信側の機器の挙動にも注意が必要。Panasonic のアナログテレビ TH-15FA5 は、電源を OFF にするときは、素直な家製協フォーマットの信号に反応する。その直後は同じ信号で電源 ON にできる。しかし、電源を OFF にしたあと数分たつと、テレビが内部的にパワーセーブモード(?)になるらしい。パワーセーブモードからは、電源 ON のパターン1つを受信しただけでは ON にならず、最低1回は同じデータを反復受信しないと電源が入らない。さらにその場合は、最初のデータと反復データの間が40ms以上開いていないと電源が入らなかった。誤動作しないように、電源 ON のボタンだけは厳密に判定しているようだ(※シャープの AQUOS テレビ LC-20DZ3(家製協フォーマット) も同じような挙動)。

テレビ以外のリモコンのフォーマット

テレビ以外の機器のリモコンもサンプリングしてみた。

上の図を元の大きさで表示する
※ 図の1ドット=50[μs]。Hが点灯、Lが消灯状態。赤外線受信モジュールの出力は負論理でこの図とは逆なので注意。
テレビ以外のリモコンの赤外線フォーマット


三洋のプロジェクタ LP-XP100L は天吊りの大型のもの。ビクターの DVD プレイヤー RM-SHR013Jはビクターの独自フォーマットだった。シャープのエアコン(AY-Z22VX)は家製協フォーマットのようだがやたらに長い。蛍光灯のリモコンの信号もやたら長い。

TV会議システム VSX7000 は、米国 POLYCOM 社の製品。
これはちょっとクセのある挙動をする。1つのボタンに2つのパターンが割り当てられていることがあって(数字ボタンなど)、そのボタンを押すたびに2つのパターンが交互に出力される。また、リモコン本体を床から持ち上げるとスイッチが入って、自動でいくつかの信号を送信する。

WOLFVISION の卓上カメラはこういう製品。リモコンのフォーマットはソニーフォーマットに似ていた。(後に自作リモコンが完成した後に試したところ、電源 OFF は確実にできたが、電源 ON に失敗することが多い。原因は不明) → (2015/2/28 補足: 若干長めに学習してしまうバグがあったため。修正したら ON/OFF ともできるようになった。ただし赤外線の到達距離が短く、近距離からのみ)。





赤外線信号の保存形式(EEPROM 保存時の圧縮)


以上の観察結果をふまえて、赤外線信号の保存形式を設計することにする。

万能のリモコンにするためには、どんなフォーマットだったとしても正しく保存・再生ができる必要がある。また、容量の節約のためにできる限り短いデータ形式で保存したい。そこで、受信した赤外線信号を次の形式で EEPROM に保存する。

まず、基本方針として、一連の赤外線パルスの発光時間を、そのまま生で保存することにする。ただし、信号の途中にビット列とみなして短縮(圧縮)形式で保存できる部分が見つかった場合は、その部分は可能な限り短縮形式で保存する。

まずは圧縮せずに、発光時間を生のまま保存する方法について。信号がうまく圧縮できない場合はこの方式で記録することになる (ただし後述の方法で、部分的にでも圧縮できるところは圧縮する)。

どんな赤外線信号でも、最初は赤外線 ON から始まって一定時間継続し、その後にOFF が一定時間継続する。これの繰り返しである。そこで、ONの継続時間と OFF の継続時間を測定して、15bit の値としてそのまま保存する(時間は2バイト値で保存するが、後述する短縮形式との区別のため、最上位ビット(MSB)は常に0にしておく。そのため実際のデータが保存できるのは 15bit)。この形式では必ず ON と OFF のペアで記録することにし、一つの ON+OFF のペアで 4バイトのメモリを消費する。時間の計測は 72kHz の割り込みで行っているので、1[秒]÷72[kHz] = 13[μs] が1単位になる。15bit 値では 32767 まで表現できるので、ON も OFF も最大 32767 * 13[μs] = 425971[μs] = 425.971[ms] までの時間が記録できる。



赤外線信号の記録形式 (EEPROM上のフォーマット)

次に圧縮の方法を考える。圧縮のための短縮形式で記録する方法について。

赤外線信号にはさまざまなフォーマットがあるが、大抵どれも下の図に示した 「短いON」「長いOFF」「長いON」「短いOFF」 の4種類の時間の組み合わせで表現できる(リーダーや区切りなど特殊な部分以外は)。そこで、取り込んだ信号全体を一度プレスキャンして、4種類の時間がそれぞれ何μs になっているかを、パルス長の平均をとるなどしてがんばって求める。そしてこの4種類の長さを EEPROM の別の領域に保存しておく。
赤外線信号を構成する4種類の長さ

実はここに工夫できる余地がある。一種類のリモコンには、普通はこの4種類の時間のうち3種類しか存在しない。なぜかというと、一般的なフォーマット(NEC、家製協、ソニー、ビクターなど) はどれも、ON か OFF かどちらかの長さは固定で、もう片方の長さで1か0かを表現しているから。そのため、普通は、プレスキャンした結果を見ると「(A) 短いONと長いONは同じ長さだった」 または「(B) 長いOFFと短いOFFは同じ長さだった」のどちらかの結果になる。つまり4つの値のうち、(A)または(B)は同じ値になる。

同じ値があっても気にせずに、取り込んだ一連の信号と上の4つの値を順次比較し、取り込んだ信号の中に「長いON+長いOFF」 が見つかったら 1 「短いON+短いOFF」が見つかったら 0、としてエンコードする。この2種類以外のパターンは考慮しなくてよい(比較する時には測定誤差を考慮して若干のマージンを持たせる)。

以上の方法で判定すると、結局、下の表の上の段の4つのパターンは1として、下の段の2つのパターンが0として判定される。 これだけで、一般的なフォーマット(NEC、家製協、ソニー、ビクターなど) なら、うまい具合に 0 と 1 のビット列に変換でき、後から同じ波形を再生できる。再生する時は、同じ4つの値を使って、ビットが1なら「長いON+長いOFF」 を、0なら「短いON+短いOFF」を出力すればよい。

※ 分かりにくいので補足: 要するに長いONまたは長いOFFのどちらかを含むパターンは1、どちらも含まないパターンは0とエンコードするということ。一般的なフォーマット(NEC、家製協、ソニー、ビクターなど)ならこの方法で再現できる。


※ ただし1と0のパルス長が同じ長さのフォーマット(変調方式が PPM でない)もあり、その場合はこの方式でビット列化できない 。YUASA の扇風機のリモコンはそうだった。その場合は圧縮できないので生で記録するしかない。

 0 と 1 の列に変換したら、ヘッダを付けて 0/1 のビット列を記録する。ヘッダは目印として MSB(最上位ビット) を 1 にする。ヘッダの MSB 以外の 7bit には、その後に続くデータのビット数を格納する。続くデータの長さが中途半端な場合は 8bit 単位でパディングする。

もしこの方法でエンコードしている最中に、0とも1とも判定されない長さのパルスが出てきたら(信号の途中にリーダーや区切りが入っているとそうなる)、一旦パディングしてビット列の短縮記録を止め、その特殊な長さのパルスだけは生のまま4バイトを使って記録す る。その後にまたヘッダを付けて、ビット列の記録を再開する。

受信した信号を 0/1 の列に変換する (EEPROM上のフォーマット)

一旦ビット列に直せれば、その後はいろいろな圧縮アルゴリズムが適用できるが、今回はこれ以上圧縮せずにそのまま記録している。

最悪のケースでは、全くビットに直せない信号の場合には全て生のまま記録することになるが、ともかくこの方法なら、どんな信号がやってきても記録することができる。

追加: リアルタイムランレングス圧縮 (エアコンのリモコン対策)


上に 「赤外線信号の保存形式(EEPROM 保存時の圧縮)」 として、EEPROM に保存する時に容量を節約する方法について書いた。これによって、保存先の容量は少なくてもよくなった。大抵のリモコンの信号が96バイト以内に収まるようになるので EEPROM が512バイトのマイコンでもある程度の信号が保存可能である。

保存するときはそれでよいが、問題はリアルタイムに学習している最中のメモリ不足。学習中は受信した赤外線を一旦 RAM に記録し、これをあとで上記の方法で圧縮することになる。ところが TINY84 は EEPROM だけでなく、RAM も 512 バイトしかない。しかも、信号の保存専用に使える EEPROM と違って、RAM は変数やスタック (割り込みを使うと、割り込みの前後で多数のレジスタをスタックに PUSH/POP しないといけないため、結構なスタックが必要になる) のために消費されるので、赤外線信号の記録に使える RAM は、実質的には 330バイトくらいしかない。TV などの通常のリモコンなら収まるが、エアコンなどの長い信号は 330 バイトに収まりきらなくなる。そうなると学習できないという事になる。

そこで、学習時には RAM に記録する前にリアルタイムにランレングス圧縮を行い、RAM から読みだす時はリアルタイムに展開するようにした。この方法で、従来の方法では RAM 不足で学習できなかったダイキン、日立製エアコンの学習が可能になった

今回とった方法は以下の通り。まず RAM 上で数値を記録する時の表記方法の規定。下記の3種類のいずれかの形式で表現することにする。
RAM 上のフォーマット
0~63 までの数値は形式1、64~16383 までの数値は形式2で保存する (16384 以上の数値は表現できない)。形式2では、上位-下位の順に記録する。形式3はランレングス圧縮された値を示し、保存してある「直前の値」 を 「繰り返し長」の回数繰り返す。

「直前の値」について、今回の工夫として、RAM の奇数バイト目と偶数バイト目を分けて扱うことで圧縮効率を上げている。RAM に記録すべき生のリモコンの信号は 、ON の時間長と OFF の時間長を交互に並べたデータである。よって、常に偶数バイト目は ON の時間長、奇数バイト目は OFF の時間長を表している。ON 同士、OFF 同士は同じ値になることが多い。そこで、今回は「直前の値」として、着目しているのが奇数バイト目なら直前の奇数バイト目の値、偶数バイト目なら直前の偶数バイト目の値を使うことにした。

上記の表の表現形式でランレングス圧縮を行えば、データは短くなることはあっても長くなることはない。(奇偶ごとに1個おきに)同じ値が頻出する赤外線信号では、これだけで必要な RAM の量はかなり減る。

問題はマイコンでのランレングス圧縮の処理時間だった。赤外線信号の送受信はタイミングが重要なので、72kHz のタイマー割り込みのイベントハンドラ内で行っている。そのため、期間内(13.9μs)に送受信に関する全ての処理を終わらせないといけない。クロック数で言うと 222 クロックしかないわけで、今回そこにランレングス圧縮の処理が加わることになる。これは難しいかと思ったが、AVR-GCC の最適化オプションを速度優先 (-O3) にすることで何とか間に合った。

学習結果の EEPROM への保存

学習結果は AVR 内蔵の EEPROM (容量 512バイト) に保存する。その際、独自に開発した AVR-GCC 用内蔵 EEPROM 簡易ファイルシステム を使っている。ファイルシステムについては別途記事を書いたので、そちら (←リンク)を参照。



部品の選択

重要な部品は AVR マイコン、赤外線LED、赤外線受光ユニット、ピエゾブザーである。


AVR マイコン

ATTINY85-20PU-ND

回路の心臓部のマイコンには ATTiny85 を採用した。DIP 8pin(表面実装でないタイプ)の製品。価格は Digikeyで1個買うと199円。25個まとめ買いすると1個当たり161.16円。Aliexpress では 20個まとめ買いで1個当たり US$1.25 くらいの店があるようだ(Aliexpress は中国の通販サイト。送料無料の店もあるが、その場合注文してから届くまで遅いときには1か月くらいかかる)。マルツだと215円、秋月では扱っていない(2014年6月)。Futurlec は $5 の送料がかかるが(またこちらも届くまでちょっと時間がかかるが) 1個 $1.15 と安い。たくさん買うなら Futurlecで。

スペックは、フラッシュメモリ8KB, RAM512B, EEPROM512B。電源電圧は4.5~5.5V。以前 LAN から操作できる赤外線リモコンをATMega64(フラッシュメモリ64kB, RAM 4kB, EEPROM 2kB) で作成したことがあるが、今回の ATTiny85はそれと比べるとかなり非力。

ATTiny85 は CPU クロックに、内蔵発振回路(外付け部品なし)の 16MHzが使える(High Frequency PLL Clock)。これはとても重要。クロックを遅くすれば消費電力が減るので省電力的には有利なのだが、今回は RAM の少なさをカバーするため、学習中にランレングス圧縮を行うなど結構なことをしていて、72kHz の割り込み時間内にそれらの処理を完了させる必要がある。そのためクロックが 16MHz でないと間に合わないところがあった。

ATTiny85 にはシリアルインタフェースがない。学習したデータを出力して PCで確認する必要があったので、そのために後日ソフトウェア UART (送信部分のみ)を自作した。(追記: 後日受信部分も作成して、PC から本機をコントロールできるようにした)。

ケースの都合上、回路をボタン電池 LR44*3個の 4.5V で駆動する必要があり、 省電力が重要になった。一定時間操作がないときは AVR をパワーダウン状態にしている。パワーダウン中の消費電流は 2μA 以下。消費電力を削減するために、ADC やコンパレータをソフトウェア的に OFF にしている。また、内蔵プルアップ抵抗を使うとパワーダウン中の消費電力が激増するので、内蔵プルアップは使わず、外付けのプルアップ抵抗(10kΩ)にした。

問題はパワーダウン状態にするときに BOD(Brown-out Detector) を無効にしないと、パワーダウン中の消費電流が 20μA 以上に跳ね上がること。ATTiny85 のデータシートには「ソフトウェアで BOD  を無効にするためにはリビジョンが C 以降(ATtiny85, revision C, and newer) でないといけない」と書いてあるが、リビジョンの調べ方がわからない。リビジョンについて、まず米国 Digikey に問い合わせてみたが、明確な回答は得られず。次にアトメルジャパンに問い合わせたところ、「リビジョン C 以降にあたる製品は車載用の型番のみで、通常品では BOD をソフトウェアで ON/OFF することはできない」とのこと。やむを得ず、ヒューズビットの設定で BOD を常時無効にした。BOD が常時無効だと、ショックで EEPROM が消えることがあるがしかたがない。

ソフトウェアの開発には GCC (avr-gcc-4.5.1_1) を使った。 開発は FreeBSD9.2 で行った。avr-libc-1.8.0,1 を利用し、ソフトウェアは本当に一から全部作った。AVR への書き込みには FreeBSD でavrdude-5.11 を使い、FreeBSD マシンのパラレルポートと ATTiny85 を直結して書き込んだ。これらの必要なソフト (avr-gcc-4.5.1_1 avr-libc-1.8.0,1 avr-binutils-2.20.1_1 avrdude-5.11) は FreeBSD 9.2 の ports に全部あった。

#追記(2014/6/8)
マイコンに ATTiny85 を選択したのはなかなか絶妙だったかも。DIP 品があり、外付け部品なしでクロック16MHz が実現できる AVR 製品がほかにない。ATTiny85の難点は RAM が 512Byte な点。本当は RAM が 1kByte の品(ATMega8など)を使いたいのだが、ATMega8 を 16MHz で動かすには外付け部品が必要。ATMega8U2 なら、外付け部品なしでも 16MHz で動かせそうだが、DIP 品がない(おまけに高い)。

赤外線LED


OSIR5113A
(現在は L-53F3BT を推奨します)

ボタン電池 LR44*3個の 4.5V 電源で数mの距離から使えるリモコンを作るには、実質この赤外線 LED を選ぶしかなかった(2014/6/25: 電源をリチウムコイン電池 2個直列の 6V にする場合は、東芝の赤外線 LED,  TLN105B の方が照射角度が広くてよい。到達距離は結構短くなるが)。

2014/8/20 追記: TLN105B は入手しにくい(ディスコン?)。広角に照射したい場合は共立エレショップで取り扱いのある L-53F3BT を使う。

赤外線 LEDの使い方は通常の LED と同じだが、光っても肉眼で見えないので選ぶのが難しい。最終的には実際にリモコンとして使ってみて動作を見るしかなかったりする。主なパラメータは順方向電圧(VF)、最大電流、放射強度、半値全角など。もちろん小さい電流で放射強度が大きく、半値全角が広い方がよいが、トレードオフになっていて、どれかを犠牲にして選ばなくてはいけない。 TLN105B, TLN103, OSIR5113A などを試して、最終的にOSIR5113A を使うことにした(高輝度版のOSI5FU5113Aというのもあったが試していない→2014/6/21 高輝度版のOSI5FU5113A で試してみたが、大きな違いは見られなかった)。 OSIR5113A は VF=1.25V, 最大電流が 100mA, 半値全角が15度。指向性が強いので受信部を狙わないといけない。秋月で100個まとめ買いすると1個7円。安い。

電源がひ弱なアルカリボタン電池(4.5V)なので、赤外線 LED に直列に入れる電流制限抵抗の選び方は難しい。抵抗値が大きいと赤外線 LED の発光が弱すぎて本当に近くからしか使えなくなる。かといって抵抗値が小さいとボタン電池が電源の場合は、発光時に電源電圧が瞬間的に下がって誤動作するし、スイッチング電源などを電源にすると AVR に過電流が流れてリセットがかかる。ぎりぎりの選択で 51Ωにした。

2014/8/20 追記: 電源が 6V の場合は 100Ω にする。

赤外線受信モジュール


RPM7138-R
(現在は GP1UXC41QS を推奨します)

電源、GND、信号出力(VOUT)の三本足。電源を入れると VOUT が H になり、赤外線を受信している間だけ L になる。これもいろいろな製品が出ている。これまではずっと秋月で 1個50円くらいの製品(OSRB38C9AA等) を買って使っていたが、動作がデリケートで、電気的ノイズ、周囲の明るさや蛍光灯の影響で、赤外線信号が入っていないのに誤検出することがよくあって困っていた。

一度近所のマルツ仙台店で RPM7138-R を買ってみたら、使い勝手がすこぶるよかったので、以後少々高いけれどもずっとそれを使っている。まずこの製品は電源電圧がかなり下がってしまってもなんとか動作してくれる。また、オシロスコープを使って確認してみると、赤外線信号をかなり忠実にサンプリングできている。日本のロームという会社の製品で、ロームの赤外線モジュールのカタログを読んでみると、正確に受信するためにかなりいろいろな工夫をしていることがわかった。プラスチックパッケージで、電源電圧は 4.5-5.5V。消費電流は 1.5mA。マルツだと1個158円。Digikeyだと1個200円。10個まとめ買いすると1個当たり157.7円。

消費電流の 1.5mA は結構大きいので、赤外線受信モジュールの電源 を AVR の出力ピンからとって、不要なときは電源を切るようにしている。このような使い方をする時の注意点として、赤外線受信モジュールの電源を ON にした直後にすぐに受信を開始してはいけない。誤動作する。動作が安定するまで 100ms くらい待ってから受信をスタートするようにした。

低電圧(2.7V)で動作する版(RPM7238-R)も存在し、本当はそちらを使いたいのだがまだ手に入らない。低電圧版が手に入れば、システム全体を 3V のリチウムコイン電池で駆動できそうなのだが。

2014/7/7 追記:  低電圧で動作するロームの赤外線受信モジュール RPM7238-H8R(電源電圧: 推奨2.7~3.6V, 最大6.3V) を入手して試したところ、システム全体を 3V のリチウムコイン電池で駆動できた。赤外線信号の学習も特に問題なくできた。ただし低電圧版モジュールにはプラスチックパッケージ版がなく、全て金属シールド付きなので、回路はそのままでよいが、部品の配置を変更する必要がある。

2014/7/7 追記: RPM7238-H8R の金属シールドを外せば形状は 5V品の RPM7138-R と同じ。シールドを外してみても一応動作はしたが、ノイズ耐性などは不明。

2014/8/20 追記: 秋月の GP1UXC41QS が 2.7V~5.5V で動作し、ノイズにも強くてよいことが分かった。金属シールドはなく、外装はプラスチック。ピン配列はロームの赤外線モジュールと同じ。


ピエゾブザー


PKM13EPYH4000-A0

学習開始やエラーなどをビープ音で知らせるために、ムラタのピエゾブザー PKM13EPYH4000-A0 を使っている。秋月で1個30円。Digikey で1個68円。なぜかこの製品だけ安い。サイズが小さくてよいが音が少し小さい(今回は操作確認音に使うので音は小さくてもよしとする)。ブザー単品(円盤状の金属)だけでも売られていることがあるが、その状態だと本当にかすかな蚊の鳴くような音しか出ない。黒い樹脂ケース部分の設計が重要のようだ。ピエゾブザーは電圧をかけただけでは鳴らず、AVR で鳴らしたい音程のパルスを生成して送る必要がある。今回はタイマ割り込みで 440Hz と 880Hz の矩形波を作って鳴らしている。






2014年2月14日金曜日

バックグラウンドで ssh ポートフォワードを実行

screen + ssh ポートフォワード

ログアウト後も ssh で TCP のポートフォワードを持続させるために、screen コマンドで開いたウインドウで ssh による接続を行う。

% screen -S 文字列 -d -m ssh -g -R転送元ポート番号:転送先アドレス:転送先ポート番号 接続先アドレス

  • 転送元のマシンが、他のホストからの接続を受け付けて転送する (転送元のマシンが listen する) ためには、上記のように -g オプションを付けた上で、転送元マシンの sshd_config に "GatewayPorts yes" が必要
  • screen に "-S 文字列" オプションを付けておくと、"screen -ls" を実行した時に文字列が表示される。複数のポートフォワードを実行する時に便利。


Linux を CUIで快適に使いたい

 FreeBSD と Linux


普段は FreeBSD の tcsh を使っている。たまに Linux の bash を使うと勝手が違うことがあるので、なるべく違和感をなくすための工夫をする。
  • less の終了後に画面を消さないようにする
less -X
  • ちなみに less の検索で大文字小文字を区別しないようにするため、次の設定も行う。
less -i
  • "emacs -nw " の終了後に画面を消さないようにする
    • これは難しい。"setenv TERM vt100" としておけば画面は消えなくなるが、端末エミュレータ内でのカラー文字表示などの機能が使えなくなってしまう。いい方法はないものか。
  •  bash で CTRL-p, CTRL-n (ヒストリの検索) を有効にする
~/.inputrc に下記の2行を追加する
"\en":history-search-forward
"\ep":history-search-backward

    • ~/.inputrc  の記述は bash だけでなく、readline ライブラリを使っているほかのソフトウェアにも有効。 上記の作業で mysql コマンドでも CTRL-p, CTRL-n が効くようになった。

  • ls を実行すると文字がカラー表示になって見にくい
あらかじめ設定ファイルを作っておいて、 ログイン時に毎回 eval コマンドで有効にする。
http://www.atmarkit.co.jp/flinux/rensai/linuxtips/358dircolors.html
を参考に設定。

2014年1月6日月曜日

DELL Latitude X1 の電源修理

愛用のノート PC Latitude X1 の電源が入らなくなってしまったのでダメ元で分解したら、電源ジャックのはんだ付けが1か所取れていただけで無事直った。

Latitude X1 システムボードの電源部分(赤い囲みがハンダが外れていた箇所)

故障部分の拡大図 (修理後)

症状は、本体に電源ピンを挿してもLEDが点灯せず、電源が入らないというもの。ただし別の PC で充電したバッテリーを装着してみると、バッテリーでは動作する。本体の電源コネクタの部分が壊れている感じ。そういえばこの Latitude X1 は、完全に故障する前から、電源ピンの差込部分を動かすと電源が入ったり切れたりして不安定だった。

プロな方々がノート PC の修理をしている記事を見て、どうせ使えないならと分解修理に挑戦してみた。


DELL のサイトからはサービスマニュアル(英文)がダウンロードできて、完全に分解する方法が書いてある。こういうところが日本のメーカーと違うところ。素晴らしい。

今回はサービスマニュアルの "System Board" のところを見て分解。1-7の手順でシステムボードが露出する。組み立てはこの逆。

1. 作業の準備
2. キーボードを外す
3. 液晶を外す
4. メモリを外す
5. HDD を外す
6. 放熱板を外す
7. パームレストを外す

ノートの分解は初めて。特に液晶部分を外すのなどはいかにも難しそうに見えたが、丁寧にやれば簡単に分解できる。作業時間は30分から1時間くらい。DELL 製品は特に作業しやすいのかもしれない。7 のパームレストを外すところで力を入れすぎてツメを一か所折ってしまったが。ネジは何種類かあるので、組み立てる時にわからなくならないよう注意する必要がある。

上の写真の赤で囲んだ部分のハンダがとれていたので、ここをハンダ付けしただけで直った。ここまでバラバラにした状態でも、液晶モニタのコネクタだけをつないで電源を入れるとちゃんと起動画面が出るので、動作確認ができる。

しかし上の記事にあるようなチップコンデンサの交換などは素人にはとても無理だと思った。部品が小さすぎて手作業できる気がしないし、そもそもコンデンサが壊れているかどうか見てもわからなさそう。できることは今回行ったハンダ付けや、せいぜい表面実装のヒューズ(X150F 24V と書いてある)の交換程度だと思う。

2013年10月5日土曜日

ケーブルテレビ JCOM 仙台

実家で初めてケーブルテレビに加入した。会社はJCOM 仙台キャベツ。ケーブルテレビの仕組みがわかっていなかった上に説明が分かりにくくて、何かと困った。とにかく制約やできないことが多すぎて不便。ネット上で評判が悪く、さんざんに言われているのも納得だった。これから契約する方は事前に調査して慎重に。

ケーブルテレビの仕組み

 そもそもの仕組みとして、ケーブルテレビの番組はすべて同軸ケーブルを通して、C-CASカードが差し込まれたセットトップボックス(STB)でデコードしないと視聴できない形で送られてくる。STB はデコードした画像を後述の形式で映像出力する、チューナのような挙動をする。

つまりケーブルテレビの番組は、STBの映像出力(HDMI,D4, S, コンポジット)以外の方法では全く視聴できず、録画もできない(例外は地デジと、デジアナ変換のアナログ放送。これらだけは生のまま送られてくるので、市販のテレビやチューナーに同軸ケーブルを接続すればC-CASカードなしでもデコードできる)。複数の部屋で視聴したい場合は、STBとC-CASカードが複数必要(複数の契約をしないといけない)。

ハイビジョン画質での録画はできない(重要)

今回 JCOM 仙台から貸し出された STB の機種はパイオニア製の BD-V300。STB の機種は工事当日まで分からなかったし、事後に交渉しても交換してはもらえなかった。BD-V300 には HDMI,D4, S, コンポジット出力がある。i-Link 端子はない。よって録画するためには S か コンポジット端子から外部のレコーダにアナログで接続し、低画質(SD)で録画するしかない。ハイビジョン画質で録画するためには、JCOM に別料金を払ってブルーレイ内蔵型の STB をレンタルする契約に変更するしかない。

低画質で録画してもコピー制限がかかる

ちなみに Sやコンポジット端子から外部のレコーダにアナログで接続して低画質で録画する場合にも、CGMS-A信号が入って、録画した番組には動画はコピー制限がかかる。録画先の画像を再度ダビングすることはできない(これはケーブルテレビに流れているデジアナ変換のアナログ放送をレコーダーで録画した場合も同様で、録画した番組を再度ダビングすることはできない)。

J:COM TV My style NEXTでは追加契約できるチャンネルに制約がある

J:COM TV My style NEXT というコースを契約。JCOMが指定した5チャンネル(A,B,Cの3種類がある)のみを見られる契約。2年縛りで、中途解約すると解約料がかかる。5チャンネル以外には「オプションチャンネル」としてJCOMが指定したチャンネルを有料で追加できるが、オプションチャンネル」に記載がないチャンネルは追加契約することはできない

リモコンの選局ボタンで契約していないチャンネルを飛ばすことができない

J:COM TV My style NEXT では 5チャンネルしか見られないので、リモコンのチャンネルの上下ボタンを押すと、ほとんどのチャンネルで「ご加入のケーブルテレビ局に連絡してください」という表示になる。これはきわめてわずらわしいので契約中のチャンネル以外は飛ばしたいが設定方法が分からない。サポートに電話したが、その機能はないと言われた。代わりに「お好み」ボタンをあらかじめ設定して使うようにとのこと(このお好みボタンがまた動作が遅くて使いにくい)。現在自分が契約中のチャンネル一覧がどれか、というのも画面上からはわからない。

特に赤字で書いたような情報は事前に知りたかった。初めての人にもわかるように JCOM の WEB ページ上に書いておいてほしい。もっともこの状況を事前に知っていたら、契約しないように家族を止めただろう。ただ JCOM 仙台の社員さんの対応は非常に良く、また電話で話した JCOM のサポートセンターの方も親切だった。ケーブルテレビがひどく不便な理由は、おそらくこの会社の対応が悪いというよりは、日本の放送ビジネス自体が、ユーザの利便性を軽視しているからだろうと思う。

2013年8月29日木曜日

FreeBSD 9.1 で PCIe のパラレルインタフェースボードを認識させる

状況

FreeBSD 9.1 が PCI Express 用のパラレルインタフェースボード 、玄人志向の 1P-LPPCIE2 (使用チップ: MOSCHIP MCS9901) を認識してくれなくて困ったのでメモ。

 
玄人志向 1P-LPPCIE2


1P-LPPCIE2 が FreeBSD から認識されていない状態で dmesg コマンドで見ると
pci1: <simple comms, parallel port> at device 0.2 (no driver attached)
と表示される。海外の掲示板には同じチップの MCS9901 を使ったボードが使えたという記事もあったので、OS は対応はしている模様。そこで、カーネルのソースコードを書き換えてカーネルの再構築を行い、認識させる。

pciconf -lv コマンドを実行すると chip=0x99009710 と表示される(赤字部分)。

# /usr/sbin/pciconf -lv
none1@pci0:1:0:0:       class=0x070002 card=0x1000a000 chip=0x99059710 rev=0x00 hdr=0x00
  vendor     = 'NetMos Technology'
  class      = simple comms
  subclass   = UART
ppc1@pci0:1:0:2:        class=0x070103 card=0x2000a000 chip=0x99009710 rev=0x00 hdr=0x00
  vendor     = 'NetMos Technology'
  class      = simple comms
  subclass   = parallel port
※ "NetMos Technology"はMOSCHIP社の旧名。

 対策

/usr/src/sys/dev/ppc/ppc_pci.c の static struct pci_id pci_ids[] の定義に次の1行を追加して

{ 0x99009710, "MosChip MCS9901 PCIe Printer port", 0x10 }
カーネルの再構築と再起動を行ったところ、無事使えるようになった。
再構築前と再構築後で dmseg コマンドの表示は次のように変わる。

再構築前

# dmesg
pci1: <simple comms, parallel port> at device 0.2 (no driver attached)

再構築後
# dmesg
ppc1: <MosChip MCS9901 PCIe Printer port> port 0xee00-0xee07,0xed00-0xed07 mem 0xfddfd000-0xfddfdfff,0xfddfc000-0xfddfcfff irq 18 at device 0.2 on pci1
ppc1: Generic chipset (EPP/NIBBLE) in COMPATIBLE mode
ppbus0: <Parallel port bus> on ppc1
plip0: <PLIP network interface> on ppbus0
lpt0: <Printer> on ppbus0
lpt0: Interrupt-driven port
ppi0: <Parallel I/O> on ppbus0




2013年6月10日月曜日

VB.NET でデフォルト値を指定できる Dictionary クラスを作る

VB.NET のサンプル。VB2008 で確認。VB のバージョンが古いと動作しないかもしれない。
Dictionary クラスを継承して、デフォルトのプロパティーを上書きして実現。

※ VB.NET ではデフォルトのプロパティーを使うと、オブジェクト名(引数) のように、オブジェクト名にドットを付けず、直接カッコと引数を付けた時に呼ばれるサブルーチンを作成できる。

.NET の Dictionary コンテナは、Hashtable と異なり、値を代入していないキーを参照すると、例外KeyNotFoundException を投げる。

下記の Dic2 クラスは、コンストラクタでデフォルト値を指定(または DefaultValue メンバにデフォルト値を代入)できる。 使うときは、ソースの末尾などに下の基本版または完全版のコードを追加してから
Dim d As New Dic2(of integer,string)("デフォルト値")
などとして d の宣言と初期化を行う。d は Dictionary 型のオブジェクトとして使用でき、値を代入していないキーを参照すると、例外KeyNotFoundException を投げずにデフォルト値を返す。

基本版


' ---------------- このクラスをコードの末尾に挿入して定義してください
' Dic2: デフォルト値を持つ Dictionary コンテナ
' USAGE: dim h as new Dic2(of キーの型, 値の型)(デフォルト値)
' 
' 使用例:
' Dim h As New Dic2(Of Integer, String)("デフォルト")
' h(0) = "文字列"
Public Class Dic2(Of TK1, TV)
  Inherits Dictionary(Of TK1, TV)
  Public DefaultValue As TV = Nothing
  ' --- 基底クラスのコンストラクタ
  Public Sub New(ByRef a1 As Dictionary(Of TK1, TV))
    MyBase.New(a1)
  End Sub
  ' --- 追加のコンストラクタ
  Public Sub New()
  End Sub
  Public Sub New(ByVal def As TV)
    DefaultValue = def
  End Sub
  ' --- 引数1個の Item() メソッド
  Default Shadows Property item(ByVal a1 As TK1) As TV
    Get
      If (Not MyBase.ContainsKey(a1) And DefaultValue IsNot Nothing) Then Return DefaultValue
      Return MyBase.Item(a1)
    End Get
    Set(ByVal value As TV)
      MyBase.Item(a1) = value
    End Set
  End Property
End Class

コンストラクタを追加した完全版

' ---------------- このクラスをコードの末尾に挿入して定義してください
' Dic2: デフォルト値を持つ Dictionary コンテナ
' USAGE: dim h as new Dic2(of キーの型, 値の型)(デフォルト値)
' 
' 使用例:
' Dim h As New Dic2(Of Integer, String)("デフォルト")
' h(0) = "文字列"
<Serializable()> Public Class Dic2(Of TK1, TV)
    Inherits Dictionary(Of TK1, TV)
    Public DefaultValue As TV = Nothing
    ' --- 基底クラスのコンストラクタ
    Public Sub New(ByRef a1 As Dictionary(Of TK1, TV))
        MyBase.New(a1)
    End Sub
    Public Sub New(ByRef a1 As IEqualityComparer(Of TK1))
        MyBase.New(a1)
    End Sub
    'Public Sub New(ByRef a1 As Int32)
    '   MyBase.New(a1)
    'End Sub
    Public Sub New(ByRef a1 As IDictionary(Of TK1, TV), ByRef a2 As IEqualityComparer(Of TK1))
        MyBase.New(a1, a2)
    End Sub
    Public Sub New(ByRef a1 As Int32, ByRef a2 As IEqualityComparer(Of TK1))
        MyBase.New(a1, a2)
    End Sub
    '---- シリアライズ
    Private Sub New(ByVal info As System.Runtime.Serialization.SerializationInfo, _
                    ByVal context As System.Runtime.Serialization.StreamingContext)
        MyBase.New(info, context)
        DefaultValue = info.GetValue("DEFAULT", GetType(TV)) ' 追加したメンバ
    End Sub
    Public Overrides Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, _
                             ByVal context As System.Runtime.Serialization.StreamingContext)
        MyBase.GetObjectData(info, context)
        info.AddValue("DEFAULT", DefaultValue) ' 追加したメンバ
    End Sub
    ' --- 追加のコンストラクタ
    Public Sub New()
    End Sub
    Public Sub New(ByVal def As TV)
        DefaultValue = def
    End Sub
    ' --- 引数1個の Item() メソッド
    Default Shadows Property item(ByVal a1 As TK1) As TV
        Get
            If (Not MyBase.ContainsKey(a1) And DefaultValue IsNot Nothing) Then Return DefaultValue
            Return MyBase.Item(a1)
        End Get
        Set(ByVal value As TV)
            MyBase.Item(a1) = value
        End Set
    End Property
End Class

2013年6月9日日曜日

VB.NET で2個のキーを持つDictionaryクラスを作る

VB.NET で複数のキーを持つ Dictionary が使いたかったので、新しく Dic3 クラスを作成した。動作確認は VB.NET 2008 で行った。VB のバージョンが古いと動作しないかもしれない。

使うときは、ソースの末尾などに下の基本版または完全版のコードを追加してから
Dim d As New Dic3(Of Integer, Integer, String)("デフォルト値")

として d の宣言と初期化を行う。 こう定義しておくと、コード中で
d(1, 2) ="文字列"

 と書いて、二次元配列のように使える。あらかじめ添え字(キー)の最大値を指定する必要がない。つまり、一度 Dic3 クラスのオブジェクトを作成すれば、あとは何も気にせずに可変長の二次元配列として使えるのがポイント。

【注意】 d(1)(2) ではなく d(1,2) と書くこと

添え字(キー)と値の型は Integer, String 以外も指定できる。

解説

使うときは、下記の(1)か(2)いずれかの形式でオブジェクト d の宣言と初期化を行う。初期化時の "Of Integer, Integer, String" の部分で「最初のキー」「2番目のキー」「値」の型をそれぞれ指定している。

(1) デフォルト値を指定する場合

Dim d As New Dic3(Of Integer, Integer, String)("デフォルト値")
のようにコンストラクタでデフォルト値を指定しておくと、存在しないキーを参照したときに、 例外 KeyNotFoundException を投げるかわりにデフォルト値を返す。

(2) デフォルト値を指定しない場合

Dim d As New Dic3(Of Integer, Integer, String)()
のように初期値を指定しなかった場合は、存在しないキーを参照したときに、通常通り例外 KeyNotFoundException を投げる。

こうして作成したオブジェクト dd(1, 2) = "ABC" のように、二つの添え字を持つ二次元配列のように使える。引数と値の型は自由に指定できて、例えば上記の宣言時に "
Dim d As New Dic3(Of String, String, String)" 
と指定すると d("X","Y")="ABC" のように引数も文字列型にできる。

 

補足

今回作成した Dic3 クラスは 「Dictionary の Dictionary」を継承している。またVB.NET の機能「デフォルトのプロパティ」を使って(item メソッドを書き換えて)二次元配列のように、カッコ内に2個の引数をとって使えるようにしている。

通常、「Dictionary の Dictionary」 では、代入する時に 2番目の Dictionary オブジェクトの初期化が必要になるが、今回は新しいキーが現れると、2番目の Dictionary のメンバが New されるようにしてあるので、 2番目の Dictionary を明示的に初期化しなくてもよい。

普通は基本版を使えばよい。完全版は基本版にコンストラクタなどを追加したもの。完全版の方はオブジェクトのバイナリでのシリアライズができる。

基本版

' ---------------- このクラスをコードの末尾に挿入して定義してください
' Dic3: 2個のキーを持つ Dictionary コンテナ
' USAGE: dim d as new Dic3(of キー1の型, キー2の型, 値の型)(デフォルト値)
' 
' 使用例:
' Dim d As New Dic3(Of Integer, Integer, String)("デフォルト")
' d(0,0) = "文字列" ' 初期化せずいきなり代入可能
Public Class Dic3(Of TK1, TK2, TV)
  Inherits Dictionary(Of TK1, Dictionary(Of TK2, TV))
  Public DefaultValue As TV = Nothing
  ' --- 追加のコンストラクタ
  Public Sub New()
  End Sub
  Public Sub New(ByVal def As TV)
    DefaultValue = def
  End Sub
  ' --- 引数1個の Item() メソッド
  Default Shadows Property item(ByVal a1 As TK1) As Dictionary(Of TK2, TV)
    Get
      If (Not Me.ContainsKey(a1)) Then MyBase.Item(a1) = New Dictionary(Of TK2, TV)
      Return MyBase.Item(a1)
    End Get
    Set(ByVal value As Dictionary(Of TK2, TV))
      MyBase.Item(a1) = value
    End Set
  End Property
  ' --- 引数2個の Item() メソッド
  Default Shadows Property item(ByVal a1 As TK1, ByVal a2 As TK2) As TV
    <DebuggerHidden()> Get
      If (Not Me.ContainsKey(a1) And DefaultValue IsNot Nothing) Then
        MyBase.Item(a1) = New Dictionary(Of TK2, TV)
        Return DefaultValue
      End If
      If (Not MyBase.Item(a1).ContainsKey(a2) And DefaultValue IsNot Nothing) Then Return DefaultValue
      Return MyBase.Item(a1)(a2)
    End Get
    Set(ByVal Value As TV)
      If (Not Me.ContainsKey(a1)) Then MyBase.Item(a1) = New Dictionary(Of TK2, TV)
      MyBase.Item(a1)(a2) = Value
    End Set
  End Property
End Class

コンストラクタを追加した完全版

'---------------- このクラスをコードの末尾に挿入して定義してください
' Dic3: 2個のキーを持つ Dictionary コンテナ
' USAGE: dim d as new Dic3(of キー1の型, キー2の型, 値の型)(デフォルト値)
' 
' 使用例:
' Dim d As New Dic3(Of Integer, Integer, String)("デフォルト")
' d(0,0) = "文字列" ' 初期化せずいきなり代入可能

 <Serializable()> Public Class Dic3(Of TK1, TK2, TV)
    Inherits Dictionary(Of TK1, Dictionary(Of TK2, TV))
    Public DefaultValue As TV = Nothing
    ' --- 基底クラスのコンストラクタ
    Public Sub New(ByRef a1 As Dictionary(Of TK1, Dictionary(Of TK2, TV)))
        MyBase.New(a1)
    End Sub
    Public Sub New(ByRef a1 As IEqualityComparer(Of TK1))
        MyBase.New(a1)
    End Sub
    'Public Sub New(ByRef a1 As Int32)
    '   MyBase.New(a1)
    'End Sub
    Public Sub New(ByRef a1 As IDictionary(Of TK1, TV), ByRef a2 As IEqualityComparer(Of TK1))
        MyBase.New(a1, a2)
    End Sub
    Public Sub New(ByRef a1 As Int32, ByRef a2 As IEqualityComparer(Of TK1))
        MyBase.New(a1, a2)
    End Sub
    '---- シリアライズ
    Private Sub New(ByVal info As System.Runtime.Serialization.SerializationInfo, _
                    ByVal context As System.Runtime.Serialization.StreamingContext)
        MyBase.New(info, context)
        DefaultValue = info.GetValue("DEFAULT", GetType(TV)) ' 追加したメンバ
    End Sub
    Public Overrides Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, _
                             ByVal context As System.Runtime.Serialization.StreamingContext)
        MyBase.GetObjectData(info, context)
        info.AddValue("DEFAULT", DefaultValue) ' 追加したメンバ
    End Sub
    ' --- 追加のコンストラクタ
    Public Sub New()
    End Sub
    Public Sub New(ByVal def As TV)
        DefaultValue = def
    End Sub
    ' --- 引数1個の Item() メソッド
    Default Shadows Property item(ByVal a1 As TK1) As Dictionary(Of TK2, TV)
        Get
            If (Not Me.ContainsKey(a1)) Then MyBase.Item(a1) = New Dictionary(Of TK2, TV)
            Return MyBase.Item(a1)
        End Get
        Set(ByVal value As Dictionary(Of TK2, TV))
            MyBase.Item(a1) = value
        End Set
    End Property
    ' --- 引数2個の Item() メソッド
    Default Shadows Property item(ByVal a1 As TK1, ByVal a2 As TK2) As TV
        <DebuggerHidden()> Get
            If (Not Me.ContainsKey(a1) And DefaultValue IsNot Nothing) Then
                MyBase.Item(a1) = New Dictionary(Of TK2, TV)
                Return DefaultValue
            End If
            If (Not MyBase.Item(a1).ContainsKey(a2) And DefaultValue IsNot Nothing) Then Return DefaultValue
            Return MyBase.Item(a1)(a2)
        End Get
        Set(ByVal Value As TV)
            If (Not Me.ContainsKey(a1)) Then MyBase.Item(a1) = New Dictionary(Of TK2, TV)
            MyBase.Item(a1)(a2) = Value
        End Set
    End Property
End Class

2013年2月26日火曜日

Samba で「特定のグループやユーザからだけ見える共有を作成」したときのアクセス権の問題

長年の問題が解決したので一言。

職場の Linux で Samba2.2.8 を使っていた時に、Linux のグループが root になっているユーザにだけ、特定の Samba の共有が見えるようにするため、特定のグループやユーザからだけ見える共有を作成する を見て設定した。

Samba3.0.33 ではこの設定のままでも使えていたが、Samba3.5.2 では、この共有に Windows からアクセスしようとすると「[共有名] に対するアクセス許可がありません」 と表示されるという現象になり困っていた。

この現象は、Linux のディレクトリのパーミションが drwxrwx--- だと発生して drwxrwxrwx だと発生しない。でも後者にすると Linux 上で root グループ以外のユーザにも読み書きされてしまうので意味がない、という状態ではまっていた(また Windows7 では発生するが WindowsXP では発生しなかった)。

が、今日たまたま Samba [実践] 入門という本を眺めていたら、偶然開いたページに原因が書いてあった。

原因は smb.conf の [global] セクションに "profile acls = Yes" と書いてあったこと。これは Samba サーバを WindowsXP SP1  以降の ドメインコントローラにして、移動プロファイルを使うために必要な設定だが、[global] ではなく 、移動プロファイルが保存される共有に書くべきものだったようだ。この1行を [homes] セクションに移したら現象は解消した。
「Samba [実践] 入門 (P213)」:  「profile acls = Yes」を有効に設定したファイル共有では、Windows から ACL を参照した際に、この ACL チェックで問題が発生しないような形にアクセス許可が強制的に変更されて返却されます。このため、このファイル共有は移動ユーザープ口ファイルの格納専用として用いることを強くお勧めします。
とのこと。

この本、他にも知らないことがいろいろ書いてあるっぽい。ちゃんと読もう。

2012年8月12日日曜日

Shuttle XS35 V2 に FreeBSD を入れる

結論

Shuttle XS35 V2 に FreeBSD9.0 をインストールした場合、有線 LAN で省電力対応のギガビットスイッチングハブに接続すると、デフォルトではリンクアップしない。いまどきのギガビットスイッチはほとんど省電力対応なので困る。


ファンレスサーバが作りたい

Shuttle XS35 V2 に FreeBSD9.0 をインストールした。このマシンはファンレスなので、SSD を搭載すれば完全無音サーバが実現できる。今回は SSD ではなく、USB メモリに OS をインストールしてみた。
Shuttle Japan XS35 V2


    USB メモリからの起動

    使った USB メモリは Buffalo の RUF2-PS16GS (16GB)。製品の写真からわかるようにやたら小さい。あとからこの PC がかなり発熱することがわかったので、USB メモリは延長ケーブル経由で本体から少し離しておくことにした。
    Buffalo: RUF2-PS16GS

    FreeBSD のインストールと USB メモリからの起動は問題なく終わった(以前別の製品で、どうしても OS をインストールできなかった USB メモリがあった。経験上、起動メディアに使うなら国産の USB メモリにした方が良い気がする)。USB メモリは /dev/da0 として認識されている。
    % df -h
    Filesystem       Size    Used   Avail Capacity  Mounted on
    /dev/da0p2      13G    6.9G    5.8G    54%    /
    devfs               1.0k    1.0k      0B   100%    /dev
    tmpfs               953M    4.0k    953M     0%    /tmp
    OS の起動はさすがに HDD/SSD より遅いが、一度起動してしまえばさほどストレスなく利用できる。OS の起動時間を計ってみたら下記の通りだった。

    • 電源ONからブートメニュー画面が表示されるまで:72秒
    • 電源ONからログインプロンプトが出るまで: 101秒
    USBの読み書き速度は
    • 読み込み: 18.5 MByte/s
    • 書き込み: 3.4 MByte/s
    くらいだった。

    有線 LAN の問題

    Shuttle XS35 V2 の有線 LAN には JMicron JMC250 というチップが使われているらしい。FreeBSD9.0 にはドライバが用意されていて、何もしなくても jme0 として認識された。

    ところがここで問題が発生。この PC、特定のギガビットのスイッチングハブに接続した場合だけ通信できない。通信できない場合は、スイッチングハブの LINK ランプもつかない(リンクアップしない)。 

    JMicron JMC250 を省エネルギー対応のハブと接続するとリンクが確立しないというのは、実は既知の問題で、FreeBSD9.0 の jme のマニュアルページに明記してあった。

    % man jme
    CAVEATS
    (**snip**)
    There are two known 1000baseT link establishment issues with JMC25x.  If
    the full mask revision number of JMC25x controller is less than or equal
    to 4 and link partner enabled IEEE 802.3az Energy Efficient Ethernet fea-
    ture,  the controller would not be able to establish a 1000baseT link.
    Also if the length of cable is longer than 120 meters, controller can not
    establish a 1000baseT link.  The known workaround for the issue is to
    force manual link configuration with 100baseTX instead of relying on
    auto-negotiation.  The full mask revision number of controller could be
    checked with verbose kernel boot option.  Use lower nibble of chip revi-
    sion number to get full mask revision of the controller.
    訳してみると以下の通り(full mask revision numberについては後述)。
    %man jme
    警告
    (略)
    JMC25x には 1000baseT でのリンクに関して2つの問題がある。JMC25x の full mask revision number が 4以下で、接続先が IEEE 802.3az 省電力型イーサネットを有効にしている場合、このコントローラは 1000baseT でリンクを確立できない。また、ケーブル 長が120m以上の場合も1000baseT でリンクを確立できない。対策はオートネゴシエーションを使わず手動で100baseTX接続を設定することである。full mask revision number は詳細なカーネル起動オプションで確認できる。full mask revision number はチップのリビジョンの低ニブルから得られる。

    低速でも接続できればいいというなら
    # ifconfig jme0 media 100baseTX
    を実行すれば、省エネルギー対応のハブとも 100Mbps で接続できる。起動時に毎回設定するなら /etc/rc.conf に

    ifconfig_jme0="inet 192.168.100.100 media 100baseTX"
    などと書いておけばよい。ただせっかくのギガビット NIC なのに100Mbps接続になってしまう。

    省電力でないハブに変えて試してみるとギガビットで接続できた。でもいまどき省電力でないハブなんてなかなか手に入らない
     1Gbit接続可能だったハブ(省電力機能がない製品)
    • Buffalo LSW-GT-8NP
    • FUJITSU SH1508AT

    1Gbit接続不可だったハブ(製品の箱に「省電力」とは書いてあるが IEEE 802.3az とは書いていない)
    • Buffalo LSW3-GT-5NS
    • ELECOM LAN-GSW08/PHB 

    補足

    マニュアル中の full mask revision number とは「詳細なカーネル起動オプション」を指定して起動すると表示される チップのリビジョン」の下位4bit らしい。「詳細なカーネル起動オプション」を指定するために、OS のブート画面で [2] キーを押して止め "boot -v" と入力して起動してみた。/var/log/messages に残ったログは以下の通り。「チップのリビジョン」は 0x23 で「低ニブル(下位4bit)」は 3。4以下なので、確かにギガビット接続に問題のあるチップに該当していたようだ。

    Aug 13 11:33:50 lait kernel: jme0: <JMicron Inc, JMC25x Gigabit Ethernet> port 0xdc00-0xdc7f,0xd800-0xd8ff mem 0xfeaf4000-0xfeaf7fff irq 17 at device 0.5 on pci2
    Aug 13 11:33:50 lait kernel: jme0: MSIX count : 8
    Aug 13 11:33:50 lait kernel: jme0: MSI count : 8
    Aug 13 11:33:50 lait kernel: jme0: attempting to allocate 1 MSI-X vectors (8 supported)
    Aug 13 11:33:50 lait kernel: msi: routing MSI-X IRQ 257 to local APIC 0 vector 50
    Aug 13 11:33:50 lait kernel: jme0: using IRQ 257 for MSI-X
    Aug 13 11:33:50 lait kernel: jme0: Using 1 MSIX messages.
    Aug 13 11:33:50 lait kernel: jme0: PCI device revision : 0x0250
    Aug 13 11:33:50 lait kernel: jme0: Chip revision : 0x23
    Aug 13 11:33:50 lait kernel: jme0: ethernet hardware address not found in EEPROM.
    Aug 13 11:33:50 lait kernel: jme0: PHY is at address 1.
    Aug 13 11:33:50 lait kernel: jme0: Read request size : 512 bytes.
    Aug 13 11:33:50 lait kernel: jme0: TLP payload size : 128 bytes.
    Aug 13 11:33:50 lait kernel: miibus0: <MII bus> on jme0
    Aug 13 11:33:50 lait kernel: jmphy0: <JMP211 10/100/1000 media interface> PHY 1 on miibus0
    Aug 13 11:33:50 lait kernel: jmphy0: OUI 0x00d831, model 0x0021, rev. 1
    Aug 13 11:33:50 lait kernel: jmphy0:  none, 10baseT, 10baseT-FDX, 10baseT-FDX-flow, 100baseTX, 100baseTX-FDX, 100baseTX-FDX-flow, 1000baseT, 1000baseT-master, 1000baseT-FDX, 1000baseT-FDX-master, 1000baseT-FDX-flow, 1000baseT-FDX-flow-master, auto, auto-flow
    Aug 13 11:33:50 lait kernel: jme0: bpf attached

    Aug 13 11:33:50 lait kernel: jme0: Ethernet address: 80:ee:73:**:**:**
    【2013/2/9追記】
    このサーバが毎晩深夜 3:00 にkernel: panic する現象が発生。/var/log/messages には
      Jan 20 12:42:10 lait kernel: panic: ufs_dirbad:  /: bad dir ino 243130 at offset 76: mangled entry
    というのが残っていたが原因不明。検索しても同じ例はあまり見つからない。シングルユーザモードで ルートファイルシステムに fsck を実行してみても治らない。

    試行錯誤の結果、毎晩自動で実行されるセキュリティチェックの中の /etc/periodic/secirity/110.neggrpperm がkernel: panicの原因だったので、このファイルを削除したところ落ちなくなった。このスクリプトはファイルのパーミションを確認して、others に与えられているパーミションが group に与えられていない、というファイルを探すためのものらしい。


    自宅 PC 無音化計画

    概要

    実家とアジトの2拠点に、それぞれ Windows と FreeBSD があり、合計4台の PC が常時稼働中。すべての PC から可能な限り回転部分をなくしてみようという計画。

    実家

    PC1

    OSWindows Xp Professional SP3
    ハードウェアDELL Latitude X1
    スペックCPU=Pentium M 733(1.10GHz/超低電圧), RAM=768MB
    備考内蔵HDD をそのまま使用しているので回転部分あり

    PC2

    OSFreeBSD9.0
    ハードウェアDELL Inspiron Mini 9
    スペックCPU=Atom N270 (1.6GHz), RAM=2GB

    アジト

    PC3

    OSWindows Xp Professional SP3
    ハードウェアDELL Latitude X1
    スペックCPU=Pentium M 733(1.10GHz/超低電圧), RAM=768MB
    備考内蔵HDD を SSD(KSD-CF18.1-016MJ) に換装。 プチフリーズが激しい。

    PC4

    OSFreeBSD9.0
    ハードウェアShuttle XS35 V2
    スペックCPU=Atom D525(1.8GHz), RAM=2GB
    備考OS を USB メモリ(Buffalo RUF2-PS16GS, 16GB)にインストール

    FreeBSD で Kernel panic 時に再起動させる

    Kernel panic 後に再起動しない

    FreeBSD9.0 でディスクアクセスのエラーで Kernel Panic すると


    Automatic reboot in 15 seconds - press a key on the console to abort (15秒後に再起動します -  中断する場合はコンソールでキー入力してください)

    という表示が出る。しかしそのまま15秒待っていても自動で再起動しない問題が発生 (mdconfig コマンドで作成した RAMDISK に書き込んだ時にも Kernel Panic が発生して、同様の現象になったことがあった)。

    これでは、サーバが遠隔地に置いてあると、電源を入れなおしに行かなくてはいけない。

    対策

    とりあえずの解決策は以下の通り。ただ、対症療法なので、本当にこれでいいのかは不明だし Kernel  の再構築が必要で大変。もっと適切な方法を知っている方がいたら教えてください。

    Kernel panic 時に即座に再起動させるには(FreeBSD9.0)
    1. オプションに "options DDB"を付けて Kernel を再構築する
    2. /etc/rc.local を作成して /sbin/ddb script kdb.enter.panic=reset と書く
    3. # chmod a+x /etc/rc.local

    補足

    カーネルパニック時に再起動させる方法について検索したところ、Linux の情報は見つかったが、FreeBSD については見つけられなかったのでこの記事を書いた。掲示板には FreeBSD では
    # ddb script kdb.enter.panic=reset
    を実行しておけばよいという書き込みがあったが、これを GENERIC のカーネルで実行すると
    ddb: sysctl: debug.ddb.scripting.scripts: No such file or directory
    というエラーが出てうまくいかなかったので、オプションに "options DDB" を付けてカーネルの再構築をした次第。

    デバッガについては知識がなくて、どうしてこの方法でうまくいくのかは分からない。またそもそもなぜ最初の状態で、15秒後に自動で再起動できないのかも分からない。

    FreeBSD で Windows 用の NAS をマウントする (3)

    [シリーズの先頭から読む]
    (このシリーズはこれが最終です)


    ネットワークが切れたらどうなる?


    ネットワークのトラブルで 、急に FreeBSD から仮想 HDD への LAN 接続が切れたらどうなるのか。LAN が切れただけでファイルシステムが壊れて 1.4TBの HDD が簡単に全滅してしまったのでは困る。そこで、アクセス中(仮想 HDD 内で大きなファイルのコピー中)にLAN ケーブルを引っこ抜くという強引な方法で試してみた。すると

    • LAN が短時間(15~120秒?)のうちに回復すれば何事もなく復旧する
    • ある程度切れっぱなしだと Kernel panic で OS が落ちる

    という結果になった。少なくともファイルシステムが壊れて再起不能になるようなことはないようだ。Kernel panic に陥るまでの時間が一定しないのが困ったものだが。

    LAN が切断してしばらく回復しないと、コンソールに次のようなエラーが表示される。(仮想ファイルシステムを暗号化しているため?)

    messages:Aug 12 15:18:31 lait kernel: GEOM_ELI: g_eli_read_done() failed md1a.eli[READ(offset=1252104634368, length=131072)]
    messages:Aug 12 15:18:31 lait kernel: GEOM_ELI: Crypto WRITE request failed (error=9). md1a.eli[WRITE(offset=1107275448320, length=32768)]

    実行中のコマンドが
    Bad file descriptor

    というエラーで終了する場合もある。その後しばらくリトライしているようだが、いつまでも LAN が回復しないと、なんとKernel panic で OS が落ちる。まあ突然 HDD が消滅したようなものなのでしょうがないのか。

    再起動後は、仮想 HDD をマウントしなおして復旧できる。ただし記事の [その1] の「マウントできなくなった場合の対応」に書いたように、手動で fsck を実行する必要がある。

    Kernel Panic 時に再起動したい


    困ったのは、FreeBSD9 が Kernel Panic した後に
    Automatic reboot in 15 seconds - press a key on the console to abort
    という表示を出すにもかかわらず、そのまま15秒待っていても自動で再起動しない点。サーバが遠隔地に置いてあると、電源を入れなおしに行かなくてはいけない。

    一応なんとか解決できたが、カーネルの再構築が必要で結構おおごとだった。詳しくは別稿で。

    まだ試していないこと


    • 今回は UFS2 で仮想 HDD をフォーマットしたが、ZFSの方が良いかもしれない
    • 万一暗号化ファイルシステムが破損したらどうするのか。仮想ファイルシステムなら気軽に実験できそうなので、いずれ試してみたい


    2012年8月11日土曜日

    FreeBSD で Windows 用の NAS をマウントする (2)

    [シリーズの先頭から読む]


    アクセス速度の測定

    Windowsファイル共有(SMB) 用の NAS 上に作成した仮想ファイルシステム(仮想 FS) を /fs/img にマウントすることができた。仮想 FS の方は UFS2 のファイルシステムなので、普通にパーミションが付けられるし、シンボリックリンクも使える。試しに packages/All 以下の全ファイル(21479個)を /fs/img 以下にコピーして、ls -l を実行してみたところ、所要時間は 2.1秒。SMB 共有をそのまま使ったときは 10-30秒くらいかかっていたので、劇的に快適になった。ように見える。

    しかし、実際の転送速度はどうなっているのか。仮想FSのオーバーヘッドがあるはずだし、今回はファイルシステムの暗号化までしている。少なくとも元より早くなることはないはず。

    ちなみに、今回使った NAS(I/O-DATA HDL-GTR3.0) のパフォーマンス測定をきちんとやってくれている記事があったので読んでみると、ギガビット環境で接続すれば、かなりのパフォーマンスの出るNASらしい。

    元記事とは測定方法が違うが、FreeBSD から、SMB 共有に直接アクセスした場合と、今回作成した仮想FSにアクセスした場合の、読み書き速度を測定してみた。

    測定条件

    FreeBSD 上の RAMDISK(tmpfs) との間で、cp コマンドで下記のファイルの読み書きをして、速度を測定してみた。

    • 単一ファイル: ファイル数 = 1個, 632MB
    • 複数ファイル: ファイル数 = 289個, 合計 668MB

    スイッチングハブは Gigabit 対応。ケーブルは CAT6, NAS 側で Jumboframe を使用するようには設定していない(フレームサイズ = 1500byte)

    測定結果


    ファイルシステム 単一ファイル (読み込み) 複数ファイル ( 読み込み )  単一ファイル (書き込み)複数ファイル ( 書き込み )
    SMB 共有に直接アクセス(/mnt/smb)14.4 [MByte/s]11.7 [MByte/s]12.0 [MByte/s]10.6 [MByte/s]
    SMB 共有内の仮想FS(UFS2)にアクセス (/mnt/img)13.2 [MByte/s]10.0 [MByte/s]12.0 [MByte/s]9.4 [MByte/s]

    速度的には SMB 共有に直接アクセスした時と比べてほとんど遅くなっていない。体感的にも実用になるレベルだと思う。

    問題点

    単一の cp コマンドを実行して、コピー時間を測定した場合は上述のように特に遅くなることはなかったのだが、複数のプロセスが同時に仮想FSを読み書きすると、アクセス速度が極端に遅くなる場合があった。具体的には以下のような問題が発生した。

    1. 仮想 FS (/mnt/img) 以下にホームディレクトリを置く
    2. 仮想 FS に置いた巨大なファイル(15GByte)の MD5 値を算出(md5 /mnt/img/FILENAME)。このコマンドは終了するまでに時間がかかる
    3. md5 コマンドの実行が終わる前に新しくログインすると、異常に時間がかかる(1-3分)

    おそらくログイン時にホームディレクトリの .cshrc 等を読むのに時間がかかったのが原因だと思う。まだ詳しく調べていないが、複数ユーザが巨大ファイルをフルスピードでアクセスする用途には使えないのではないか。

    [続く]

    2012年8月10日金曜日

    FreeBSD で Windows 用の NAS をマウントする (1)

    まとめ

    1. NFS(Network File System) に非対応の Windows 用の NAS(LANハードディスク, SMB 用) を、なんとかして FreeBSD から普通に使いたい
    2. そのために Windows 用の NAS 内に巨大な1個のイメージファイル(fs.img)を作り、そのファイルを仮想ファイルシステム(仮想 HDD) として ufs2 でフォーマットして、FreeBSD9.0 からマウントする 
    3. geli コマンドで仮想ファイルシステム全体の暗号化もしてみた 
    4. このような使い方でも、ファイルへのアクセス速度は極端に遅くはならなかった(が同時アクセスには弱い。本文参照)
    5. この状態でネットワークに重大なエラーが発生すると FreeBSD9.0 が kernel panic で止まる。その場合でもマウント中の仮想 HDD が致命的に破損するケースには遭遇していない 



    動機


    FreeBSD サーバからネットワークストレージをマウントして使いたい。最近 NAS は簡単に買えるようになったが、なぜか NFS に対応した NAS がほとんど見当たらない(昔は普通に対応していた気がするのだが)。職場で使っているネットギア社の ReadyNAS シリーズでは、NFSに対応しているのは「ハイパフォーマンスモデル」だけ。「スタンダードモデル」だとWindowsファイル共有(SMB,CIFS) と Macファイル共有(AFP)のみに対応と書いてある。しかもかなり高価。

    FreeBSD も Windowsファイル共有(SMB)には対応しているので、Windows 用の NAS を一応直接マウントすることはできる。マウントするためのコマンドはこんな感じ(オプションの詳細は後述)。
    # mount_smbfs -E euc-jp:cp932 -I 192.168.100.200 //nas@LANDISK/share /mnt/smb

    試しにこれで FreeBSD から Windows ファイル共有を使ってみたが、以下の問題があってかなり使いにくかった。
    1. パーミションが自由に設定できない 
    2. シンボリックリンクが作れない 
    3. ディレクトリに大量のファイルがある時に、ファイルの一覧表示に異常に時間がかかる(ls -l packages/All/ を実行した時など。ファイル数=約20000 で約10-30秒。しかもキャッシュされていないようで毎回遅い) 

    そこで、一番上の図のように、まず FreeBSD サーバから Windows 用の NAS を SMB でマウントして、そこに巨大な(1.4TB)ファイルを作り、これを mdconfig コマンドで仮想ファイルシステムにする。その仮想ファイルシステムを ufs2 でフォーマットしてマウントしたところ(マウント先にあるファイルをさらにマウントするという、二重で妙な形にはなるが)、上記の問題 1,2,3 が解決して、NFS とほぼ同様に使うことができるようになった。

    こういう使い方をした場合、以下のような懸念があったので、本当に大丈夫なのかの実験もしてみた。
    1. ファイルへのアクセス速度は遅くならないか 
    2. ネットワークのエラーで仮想ファイルシステムが破損したりしないか 

    使った機器


    ◇ FreeBSD サーバ
    Shuttle Japan XS35 V2
    • OS: FreeBSD9.0-RELEASE, 起動 CD-ROM(FreeBSD-9.0-RELEASE-i386-bootonly.iso) から起動してネットワークインストール
    • ハードウェア: Shuttle Japan XS35 V2, Atom D525(1.8GHz), RAM 2GB, ファンレスマシン。ただし HDD/SSD は今回搭載せず、USB メモリ(Buffalo RUF2-PS16GS, 16GB)に OS をインストールしてあるちょっと変わったマシン
    • XS35 V2 の有線 LAN のチップは JMicron JMC250 (FreeBSD で使われるデバイスドライバは jme。省電力対応のギガビットハブと通信できない問題がある)

    ◇ Windows ファイル共有に対応した NAS

    I/O-DATA HDL-GTR3.0
    • I/O-DATA HDL-GTR3.0
    • RAID1+0 で使用、容量1.5TB
    • 他の機種では試していないが、巨大なファイルを作成する必要があるので,NAS 内蔵の HDD のフォーマット形式に注意する必要がありそう

    初期設定の手順

    1. NAS の設定

    NAS の Web インタフェースにログインして、ユーザ(今回は "nas" というユーザ名)を作成し、パスワードを付けておく。IP アドレス(192.168.100.200)、マシン名(LANDISK)を設定、共有(share)を作成しておく

    2. FreeBSD サーバから Windows 用の NAS をマウントする

    FreeBSD から以下のコマンドで NAS で作成した共有をマウントする。

    # mount_smbfs -E euc-jp:cp932 -f 600 -d 700 -I 192.168.1.200 //nas@LANDISK/share /mnt/smb

    このコマンドを実行すると NAS 側で設定したユーザ (ユーザ名=nas) のパスワードを聞かれるので入力する。実際にはここでマウントした先 (/mnt/smb) には巨大なイメージファイル fs.img を1個置くだけ。それ以外には直接は使用しない。

    mount_smbfs コマンドの引数の説明
    • -E: -E FreeBSD側の文字コード : NAS側の文字コード
    • -f: ファイルに対するパーミション
    • -d: ディレクトリに対するパーミッション
    • -I: NAS のIPアドレス
    • マウント元: NASのユーザ名=nas, NASのマシン名=LANDISK, 共有名=share (あらかじめ NAS の Web インタフェースで作成しておく)
    • マウント先: /mnt/smb
    引数ではパーミッションも指定している。/mnt/smb は root だけが読み書きできるだけでよい(それでも他のユーザは仮想ディスクに読み書きできる)し、誤って一般ユーザが仮想HDDのファイルを書き換えたり消したりすると、ディスクが全滅するので、パーミションの値は 600,700 として root のみに読み書きを許可した。

    3. マウントした Windows 共有上に空のイメージファイルを作成する

    # dd if=/dev/zero of=/mnt/smb/fs.img bs=1M seek=1400000 count=1
    このコマンドで約1.4TB(1468007448576 byte)の空のファイル /mnt/smb/fs.img が作成できた。巨大な空のファイルを作成するときには、 dd コマンドで seek オプションを使うのが便利。seek オプションを使わないと(大量の通信が発生して)おそらくものすごく時間がかかる。

    4. 作成したイメージファイルを仮想HDDにする

    # mdconfig -a -t vnode -f /mnt/smb/fs.img -u 1
    # bsdlabel -w md1 auto

    実行すると /mnt/smb/fs.img が /dev/md1 というデバイスファイルに割り当てられて、これ以降このデバイスファイルが仮想 HDD のように扱える。

    5. 仮想HDDを暗号化する

    # geli init -s 4096 /dev/md1a
    # geli attach /dev/md1a

    仮想 HDD の暗号化を行ってみた。上のコマンドを実行すると、/dev/md1a.eli というデバイスファイルができ、このデバイスファイルが暗号化された仮想HDDとして扱える。つまりイメージファイル fs.img への書き込みが自動的に暗号化されて、fs.img を生で読んでも解読できなくなる。"geli init" コマンドの実行時にパスフレーズを付けるように要求される。

    ※ 後日パスフレーズを変更する場合は
    # geli setkey /dev/md1a

    6. ファイルシステムの作成

    # newfs /dev/md1a.eli
    # tunefs -j enable -n enable /dev/md1a.eli
    実行すると、5で作った暗号化仮想 HDD /dev/md1a.eli が ufs2 でフォーマットされる。tunefs コマンドは FreeBSD9.0 で使えるようになったジャーナリング(Soft Update Journaling, SU+J) を有効にするため。有効にしておくとクラッシュ時の fsck が高速になるはず。

    7. マウント

    # mount /dev/md1a.eli /mnt/img
    実行すると、仮想 HDD が /mnt/img にマウントされて、読み書きできるようになる。実際の読み書きは(暗号化された上で) NAS 上のファイル fs.img に対して行われる。

    マウントする


    一度上記の手順で初期化すれば、二回目からは以下の手順でマウントできる。シェルスクリプトにでもしておこう。

    # mount_smbfs -E euc-jp:cp932 -f 600 -d 700 -I 192.168.100.200 /nas@LANDISK/share /mnt/smb
    # mdconfig -a -t vnode -f /mnt/smb/fs.img -u 1
    # geli attach /dev/md1a
    # mount /dev/md1a.eli /mnt/img
    mount_smbfs コマンドでは nas のパスワードを、 geli コマンドでは暗号化時に付けたパスフレーズを聞かれるので、それぞれ入力する。

    マウントしてから mount, df -H コマンドを実行すると、それぞれ次のように表示された。

    # mount
    /dev/da0p2 on / (ufs, NFS exported, local, noatime, journaled soft-updates)
    devfs on /dev (devfs, local, multilabel)
    tmpfs on /tmp (tmpfs, local)
    //NAS@LANDISK/SHARE on /mnt/smb (smbfs)
    /dev/md1a.eli on /mnt/img (ufs, local, journaled soft-updates)
    # df -H
    Filesystem       Size    Used   Avail Capacity  Mounted on
    /dev/da0p2      14G    7.4G    6.3G    54%    /
    devfs               1.0k    1.0k      0B   100%    /dev
    tmpfs               1.0G    4.1k      1G     0%    /tmp
    /dev/md1a.eli    1.4T    528G    801G    40%    /mnt/img

    マウントできなくなった場合の対応(重要)


    システムのクラッシュ後にマウントしようとすると、最後の
    # mount /dev/md1a.eli /mnt/img

    を実行したときに
    mount: /dev/md1a.eli : Operation not permitted

    というエラーが出てマウントできなくなる。その場合は以下のコマンドで仮想 HDD に手動で fsck_ufs を実行してからマウントすればよい。ジャーナリングのおかげで fsck はすぐに終わるはず。

    (上記の「マウントする」の "geli attach /dev/md1a" までを実行してから)
    # fsck_ufs /dev/md1a.eli
    # mount /dev/md1a.eli /mnt/img

    アンマウントする


    手動で強制的にアンマウントする手順

    # umount -f /mnt/img
    # geli detach md1a.eli
    # mdconfig -d -u 1
    # umount -f /mnt/smb


    [続く