Hyper-V環境

Hyper-V環境

Windows8の環境で Hyper-V(仮想PC)を試してみました

検証環境
CPU Core i7-4770S 3.1GHz
(第4世代 Haswel)
マザーボート ASRock社製 H87 Pro4 (LGA1150)
メモリ 32GB
グラフィック PowerColor社製 AX7850 1GBD5-S3DH
(RADEON HD7850 GDDR5-1GB)
HDD Cドライブ 500GB + データ用 2TB
いずれも 5400rpm SATA
OS Windows8.1Pro 64bit

Hyper-Vは PCのエミュレータを実現できるソフトですが
正確には ハイパーバイザ型の仮想環境を構築するためのもので ホストOS上で 複数のゲストOSが動作します
Hypre-Vを動作させるには CPUが SLAT(Second Level Address Translation)に対応している必要があります
そのため CPUを最新の Coreiシリーズに買い換えました

Hyper-Vセットアップ

Hyper-Vのセットアップの紹介です
Windowsのコントロールパネルからインストール
Windowsのコントロールパネル→プログラムと機能→Windowsの機能の有効化または無効化
から Hyper-Vを選択します Windowsの再起動が求められます
スタートメニューに Hyper-Vマネージャ
スタートメニューに Hyper-Vマネージャが追加されているはず
Hyper-V管理マネージャ
Hyper-V管理コンソールです 複数の仮想マシンを設定し 起動・停止を制御できます
仮想マシンの作成
仮想マシンを1台作成しました
世代というのは Hyper-Vでの仮想マシンのバージョンのことで
第2世代は PXEブートなど最新の機能がサポートされているが IDEが使えないなど制約もあります
WindowsVista以降の OSを使うなら 第2世代ですかね
仮想マシン起動中
xp という名前をつけて起動してみました
システム上 裏では起動しています 「接続」を選択して動作画面にアクセスできます
動作画面へのアクセス
動作画面を見ると 確かにPCが起動しています
何もインストールしていないので OSのインストーラを求められます
仮想CD-ROMに WindowsXPの CDイメージを指定して仮想マシンをリセットします
WindowsXPインストール失敗
WindowsXP のCDイメージから インストールを進めてみた画面です
秋葉原で購入した怪しいCDのためか インストールに失敗しました
ただ 仮想マシンとしては あたかももう1台のPCが動いているかのように動作します

この後いろいろな OSを試してみました
Windows98, Windows2000, Linux だいたい動作しました
以前は PCエミュレータではオーバヘッドが大きくで重い感じでしたが
最近の PCはマルチコアでオーバヘッドも小さく仮想環境を快適に試すことができます

仮想環境 Virtual PC

Virtual PC

Virtual PCとは Windows上で動作する デスクトップ仮想化ツールです
もともと有償販売されていたものを Microsoftが買収して無償化しました

Windows 7 に含まれています
今や Windows XPからの移行を促すための 旧環境併用ツールとなってますが
ここでは Virtual PC 2007 の使い勝手を簡単に紹介します

Virtual PC 2007

Windows Vista上で動作確認しました

動作環境
CPU Core2Quad Q9550s (4コア 4スレッド 2.83GHz)
メモリ 16GB
グラフィックカード GIGABYTE GV-R485MC-1GH (RADEON HD4850 1G)
HDD 200GB (C:ドライブ), 500GB (E:ドライブ)

Virtual PC 2007 コンソールメニュー
Virtual PCは Microsoftのダウンロードページから取得できます
(2013年11月時点の確認) 今はもう Windows Virtual PC という名前になってますね
Windows Vistaで使えるか未確認です

ともかく Virtual PCを起動すると 図のような管理画面が出てきます
仮想マシンを複数登録したり 起動したりします
Virtual PC 2007 バーチャルマシンウィザード
仮想マシンを追加してみます
Virtual PC 2007 バーチャルマシン命名
古い Windows95のゲームができないか試したかったので
図のような名前になってます

結論からいえば Win9x系のDirectXに非対応でした
Windows Virtual PCでは少なくとも Windows 2000以降 DirectXが動作するようです
Virtual PC 2007 バーチャルマシンスペック設定1
ターゲットとなる OSを指定してメモリなどのリソース割り当てを決めます
Virtual PC 2007 バーチャルマシン設定変更
スペック設定は後からも変更可能です
Virtual PC 2007 Ubuntu-11.04を試してみた
Windows95以外の いろいろOSを試すことにしました
Linuxディストリビューションの Ubuntuです ISOイメージを使いましたが
物理CDROMを直接 仮想マシン上に認識させることもできます
Virtual PC 2007 Windows2000を試してみた
Windows2000も問題なく動きました
Virtual PC 2007 Windows2000起動直後
10年前だと少なくとも PC上で PCを利用するのは現実的に厳しいかったですが
マシンスペックが向上した今 ゲーム以外の用途なら十分使えます
企業によっては 社員にWindows仮想環境を提供する例もあります

ちなみに Windows Virtual PCでは
ゲストWindowsのウィンドウを ホストWindows上で表示するような便利機能あります
(Windows XPからの環境以降を円滑にするために提供された仕組みと思われます)

検証で使った Core2Quadは CPUが準仮想化に対応していないので
Hyper-Vといった より効率の良い仮想化方式が使えません

UML (User Mode Linux) 構築

仮想化 – UML (User Mode Linux)

過去の Software Design を読み返していたら UMLを紹介する記事がありました
最近の自己課題となっている仮想化環境実現の選択肢になるかと考え
最新の Linuxカーネルで試してみたらちゃんと動作したので その紹介をします

Software Design 2002-02
UMLとは User Mode Linuxの略でつまり
既に動いている Linuxシステム上で 別の Linuxカーネルを動かせます
いわゆる JAIL環境です ゲストとしての Linuxは割り当てられたリソース範囲内で動作します

ホストシステム構成K/caption>

CPU AMD PhenomII X4 905e (2.5GHz 4コア)
メモリ 16GB
OS Gentoo-1.12.14 Linux-3.7.8 x86_64 UTF-8
コンパイラ gcc-4.5.2
Cライブラリ glibc-2.13

ゲストシステムとしても 同じ最新のカーネル Linux-3.7.8 を使いました

構築 UML utilities

ゲスト用 Linuxカーネルと OSディスクイメージ を別途作成する必要ありますが
その前に ゲストLinuxを制御するツールをインストールします

まず制御ツールは UML Utilities というパッケージです
パッケージシステムを使っている Linuxならすぐインストールできると思います
ここでは 手動ビルドによるインストールを行います

UML Downloads Page
UMLのダウンロードページから ソースファイルを拾ってきて
以下のようにビルドします

$ tar -xjf uml_utilities_20070815.tar.bz2
$ cd tools-20070815
 

configureがないので Makefileを直接修正して必要な設定を行います
デフォルトでは 必要な tunctlツールがビルドされないのでこれを対象に加えます
Makefile の SUBDIR に tunctl エントリを追加します

TUNCTL = $(shell [ -e /usr/include/linux/if_tun.h ] && echo tunctl)

SUBDIRS = lib jail jailtest humfsify mconsole moo port-helper $(TUNCTL) \
        uml_net uml_switch watchdog umlfs tunctl
UMLVER = $(shell date +%Y%m%d)
TARBALL = uml_utilities_$(UMLVER).tar.bz2
BIN_DIR = /usr/bin

ifeq ($(shell uname -m),x86_64)
LIB_DIR = /usr/lib64/uml
else
LIB_DIR = /usr/lib/uml
endif

CFLAGS = -g -Wall
#CFLAGS = -g -O2 -Wall

export BIN_DIR LIB_DIR CFLAGS

 

なお インストール先のディレクトリを修正するなら
Makefile の BIN_DIR や LIB_DIR も修正してください
jail/Makefile の SBIN_DIR も修正が必要です

$ CC=gcc make
〜
make[1]: ディレクトリ `/home/admin/tools-20070815/mconsole' に入ります
gcc -g -Wall   -c -o uml_mconsole.o uml_mconsole.c
gcc -g -Wall -o uml_mconsole uml_mconsole.o ../lib/libuml.a -lreadline -lncurses
/usr/binutils/2.21/x86_64/bin/x86_64-gnu-linux-ld: cannot find -lncurses
collect2: ld はステータス 1 で終了しました
make[1]: *** [uml_mconsole] エラー 1
make[1]: ディレクトリ `/home/admin/tools-20070815/mconsole' から出ます
make: *** [all] エラー 2
 

ホスト環境では libncurses がないためエラーとなっていますが
libncursestw が代わりにあるためそれをリンクするように指示します
mconsole/Makefile の LIBS を修正します

BIN = uml_mconsole
OBJS = $(BIN).o
CFLAGS ?= -g -Wall
LIBS = ../lib/libuml.a -lreadline -lncursestw

BIN_DIR ?= /usr/bin

all : $(BIN)

$(BIN) : $(OBJS)
        $(CC) $(CFLAGS) -o $(BIN) $(OBJS) $(LIBS)

clean : 
        rm -f $(BIN) $(OBJS) *~

install : $(BIN)
        install -d $(DESTDIR)$(BIN_DIR)
        install -s $(BIN) $(DESTDIR)$(BIN_DIR)
 
$ CC=gcc make
set -e ; for dir in lib jail jailtest humfsify mconsole moo port-helper  uml_net uml_switch watchdog umlfs; do make -C $dir all; done
make[1]: ディレクトリ `/home/admin/tools-20070815/lib' に入ります

〜

gcc -g -Wall -D_FILE_OFFSET_BITS=64 -I/usr/local/include -o uml_mount uml_mount.o -lfuse
make[1]: ディレクトリ `/home/admin/tools-20070815/umlfs' から出ます
make[1]: ディレクトリ `/home/admin/tools-20070815/tunctl' に入ります
gcc -g -Wall   -c -o tunctl.o tunctl.c
gcc -g -Wall -o tunctl tunctl.o
make[1]: ディレクトリ `/home/admin/tools-20070815/tunctl' から出ます
$
 

なお ビルドにあたって fuse が必要になるので
fuse関連のエラーが出た場合はそこを疑ってください

次にインストールですが インストール先を変更するには
先に Makefile の BINDIR や jail/Makefile の SBINDIR を手修正します
デフォルト /usr/bin /usr/sbin /usr/libか/usr/lib64 のままでほぼ問題ないです

$ su
# make install
set -e ; for dir in lib jail jailtest humfsify mconsole moo port-helper  uml_net uml_switch watchdog umlfs; do make -C $dir install; done
make[1]: ディレクトリ `/home/admin/tools-20070815/lib' に入ります
make[1]: `install' に対して行うべき事はありません.

〜

install -s uml_mount /usr/bin
make[1]: ディレクトリ `/home/admin/tools-20070815/umlfs' から出ます
make[1]: ディレクトリ `/home/admin/tools-20070815/tunctl' に入ります
install -d /usr/bin
install -s tunctl /usr/bin
make[1]: ディレクトリ `/home/admin/tools-20070815/tunctl' から出ます
#
 

bin/ 以下 sbin/ 以下には下記プログラムがインストールされます

bin/:
humfsify  tunctl        uml_mkcow  uml_mount  uml_switch
jailtest  uml_mconsole  uml_moo    uml_net    uml_watchdog

sbin/:
jail_uml
 

構築 ディスクイメージ

ゲストLinuxの動作用として Gentoo stage3 ベースのシステムを準備します
Gentoo amd64 Downloads Page
UMLはエミュレータではありませんので
ホストシステムと同じアーキテクチャの システムイメージが必要です
ここでは 検証環境と同じ amd64 の tarball をダウンロードしました

下記のように 1GB の EXT3ディスクイメージを作って tarballを展開します

$ dd if=/dev/zero of=uml_gentoo_st3_20120605 bs=1048576 count=1024
1024+0 レコード入力
1024+0 レコード出力
1073741824 バイト (1.1 GB) コピーされました、 1.25505 秒、 856 MB/秒
$ mke2fs -j uml_gentoo_st3_20120605
mke2fs 1.41.12 (17-May-2010)
uml_gentoo_st3_20120605 is not a block special device.
Proceed anyway? (y,n) y
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
65536 inodes, 262144 blocks
13107 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=268435456
8 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks: 
        32768, 98304, 163840, 229376

Writing inode tables: done                            
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 22 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.
$ su
Password:
# mount -t ext3 -o loop uml_gentoo_st3_20120605 /mnt/target
# cd /mnt/target
# tar -xjpf ~admin/stage3-amd64-20130130.tar.bz2
# ls
bin   dev  home  lib32  lost+found  mnt  proc  run   sys  usr
boot  etc  lib   lib64  media       opt  root  sbin  tmp  var
#
 

ファイルシステムへの書き込みのために root権限を使いましたが
Gentooのシステムは書き込めました

ゲストファイルシステムの設定を続けます etc/fstab をカスタマイズします

#/dev/BOOT              /boot           ext2    noauto,noatime          1 2
#/dev/ROOT              /               ext3    noatime                 0 1
/dev/ubda               /               ext3    noatime,defaults        0 1
proc                    /proc           proc    defaults                0 1
#/dev/SWAP              none            swap    sw                      0 0
#/dev/cdrom             /mnt/cdrom      auto    noauto,ro               0 0
#/dev/fd0               /mnt/floppy     auto    noauto                  0 0
 

SWAPも無効化してます ルートディレクトリの /dev/ubda は仮想ファイルシステムで
UBD(UML Block Device)です ホスト側のシステムイメージを UMLで扱えます
/dev/udba を作成しておく必要があります 下記の通りです

# mknod /mnt/target/dev/udba c 98 0
#
 

UBDは メジャー番号 98 です
複数のシステムイメージを扱うなら MAKEDEVスクリプトでまとめて作る方法もあります

次は ネットワークを設定です SSHでログインできるようにします
まず ネットワークの設定です ゲスト側 etc/conf.d/net に設定するだけです

config_lo0="127.0.0.1 netmask 255.0.0.0 brd 127.255.255.255"
config_eth0="192.168.255.254 netmask 255.255.255.0"
routes_eth0="default via 192.168.255.1"
 

今回の UML構築にあたっては TUN/TAP仮想ネットワークを利用した
下記のネットワークで構築します
UML TUN/TAPネットワーク構成
ゲスト側の eth0インターフェース と ホスト側の tap0インターフェース が通信します
その通信ネットワークとして動作するのが TUN仮想ネットワークです

ホストLinux側で IPv4のルーティングを有効化すれば
家庭内LANとの通信も可能になるので UMLサーバを外部と通信させることも可能です
(ホスト側で echo 1 > /proc/sys/net/ipv4/ip_forward  を実行する)

TUN/TAPを使わない別の方法としては uml_switch を使った仮想ネットワークが考えられますが
家庭内LANとの通信はできない 閉じたネットワークとなります

また start/stopスクリプトの net.eth0 がゲスト起動時にサービス開始され
eth0 を初期化するようにします

# ln -s /etc/init.d/net.lo /mnt/target/etc/init.d/net.eth0
#
 

続いて SSHログインできるようにするための設定ですが
まず ログイン用アカウントを作成するところから必要です
chrootを使って ゲスト側のイメージにルートディレクトリを変更して作業します

# chroot /mnt/target /bin/bash
# useradd admin -m -g users -G wheel -s /bin/bash
# passwd admin
新しいパスワード:
よくないパスワード: 短かすぎます
よくないパスワード: 簡単すぎます
新しいパスワードを再入力してください:
passwd: パスワードは正しく更新されました
# passwd root
新しいパスワード:
よくないパスワード: 短かすぎます
よくないパスワード: 簡単すぎます
新しいパスワードを再入力してください:
passwd: パスワードは正しく更新されました
# id admin
uid=1000(admin) gid=100(users) groups=100(users),10(wheel)
#
 

ゲスト側のイメージに adminアカウントが作成されました
admin権限にスイッチして SSHの設定作業します

# su admin
$ mkdir ~/.ssh
$ cd !!:1
$ ssh-keygen -t rsa -f uml_admin
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in uml_admin.
Your public key has been saved in uml_admin.pub.
The key fingerprint is:
eb:57:3a:**:**:**:**:**:**:**:**:**:e2:ff:fb:67 admin@phenom
The key's randomart image is:
+--[ RSA 2048]----+
|                 |
|  o.*o+.o + E    |
| . .=Xo.+=.+     |
+-----------------+
$ ls
uml_admin  uml_admin.pub
$ cat uml_admin.pub > authorized_keys
$ chmod 600 authorized_keys
$
 

SSHの RSAログイン用のキーを作って設定しました
最後に ゲストシステムの起動時に sshdが起動する設定をいれます

$ exit
# ln -s /etc/init.d/sshd /etc/runlevels/default/sshd
#
 

ゲストファイルシステムのマウントまで解除して完成です
なお SSHのプライベートキー uml_admin ファイルは SSHログインに必要なので
ホストシステム側に退避しておきます

# exit
# cp /mnt/target/home/admin/.ssh/uml_admin ~admin/
# umount -d /mnt/target
#
 

これで ゲストシステムが起動したとき SSHログインできるはずです

構築 Linuxカーネル

Linuxのカーネルは ゲストLinux用 に構築する必要があります
構築についても単純で makeオプションに ARCH=um を指定するだけです

ただし ビルド用のディレクトリは ホスト側カーネル構築用ディレクトリと
別に作る必要はあります

早速 実施してみます Linuxカーネルのソースを展開したディレクトリで

$ make CC=gcc NM=nm AR=ar LD=ld OBJCOPY=objcopy ARCH=um mrproper
$ make CC=gcc NM=nm AR=ar LD=ld OBJCOPY=objcopy ARCH=um menuconfig
 

UML専用のメニュー項目がいくつか見えます
UML Linux Kernel Config 1
ほとんどの選択肢は 初期状態のままで大丈夫です
ポイントとなる箇所を 下記画面キャプチャで紹介します
UML Linux Kernel Config 2
UML-specific options の Host filesystem はここでは無効化しています
Host filesystemとは ゲストシステム側からホスト側のディレクトリをマウントする便利機能です

ゲストシステムを他人に渡して使ってもらうような サーバレンタル用途を考えているなら
ホストシステムファイルの参照権限を渡さないほうがよいでしょう
UML Linux Kernel Config 3
UML Charcter Devices で ゲスト側の起動コンソールを設定できます

ゲスト側ブートコンソールとなるのが Default main console channel initialization の部分で
fd:0,fd:1 という指定は 標準出力と標準入力を使ってください という意味です

その下の xterm という指定は コンソールログイン端末の指定です
ブート完了時に xtermが開いてログインプロンプトが表示されます
ただし検証環境は Xサーバを動かしてないモニタレスサーバなので xtermは立ち上がりません
UML Linux Kernel Config 4
Block devices では Virtual block device というのが重要です
これにより 先ほどのOSイメージを扱う ubd仮想デバイスが使えるようになります
UML Linux Kernel Config 5
UML Network Devices でネットワークデバイス関連の設定が可能です
TUN/TAP経由でホスト側と仮想ネットワーク接続するので TUN/TAPの設定が必要です
また複数のゲストLinuxを仮想ネットワーク接続する場合 Daemon transportが必要です
UML Linux Kernel Config 6
最後 File systemsですが
デフォルトでは ext2 と ext3 が無効化されていたので 有効化しました
OSイメージのファイルシステムや 必要性に応じて判断してください

以上でカーネルのコンフィグを終了して ビルド開始します

scripts/kconfig/mconf arch/x86/um/Kconfig

#
# configuration written to .config
#


*** End of the configuration.
*** Execute 'make' to start the build or try 'make help'.

$ make CC=gcc NM=nm AR=ar LD=ld OBJCOPY=objcopy ARCH=um
  HOSTLD  scripts/kconfig/conf
scripts/kconfig/conf --silentoldconfig arch/x86/um/Kconfig

〜

  LD [M]  drivers/net/slip/slhc.ko
  CC      drivers/net/slip/slip.mod.o
  LD [M]  drivers/net/slip/slip.ko
  CC      drivers/net/tun.mod.o
  LD [M]  drivers/net/tun.ko
  CC      fs/autofs4/autofs4.mod.o
  LD [M]  fs/autofs4/autofs4.ko
  CC      fs/binfmt_misc.mod.o
  LD [M]  fs/binfmt_misc.ko
  CC      fs/isofs/isofs.mod.o
  LD [M]  fs/isofs/isofs.ko
  CC      sound/soundcore.mod.o
  LD [M]  sound/soundcore.ko
$ ls -l linux
-rwxr-xr-x 2 deer wheel 43645642 Mar  5 23:08 linux
$
 

linux という実行形式ファイルができています
これが UMLで動作する Linuxカーネル本体です

この linuxコマンド自体は ホストシステム側から起動しますが
それ以外 System.map カーネルモジュール はゲストシステムへ転送する必要があります

$ su
Password:
# mount -t ext3 ~admin/uml_gentoo_st3_20120605 /mnt/target
# cp System.map /mnt/target/boot/
# make CC=gcc NM=nm AR=ar LD=ld OBJCOPY=objcopy ARCH=um INSTALL_MOD_PATH=/mnt/target modules_install
  INSTALL arch/um/drivers/hostaudio.ko
  INSTALL block/cfq-iosched.ko
  INSTALL crypto/ansi_cprng.ko
  INSTALL crypto/krng.ko
  INSTALL crypto/rng.ko
  INSTALL drivers/block/loop.ko
  INSTALL drivers/block/nbd.ko
  INSTALL drivers/net/dummy.ko
  INSTALL drivers/net/ppp/ppp_generic.ko
  INSTALL drivers/net/slip/slhc.ko
  INSTALL drivers/net/slip/slip.ko
  INSTALL drivers/net/tun.ko
  INSTALL fs/autofs4/autofs4.ko
  INSTALL fs/binfmt_misc.ko
  INSTALL fs/isofs/isofs.ko
  INSTALL sound/soundcore.ko
  DEPMOD  3.7.8
# umount -d /mnt/target
# exit
$
 

これで INSTALL_MOD_PATH配下の lib/modules/ にカーネルモジュールが入りました
ちなみにゲストファイルシステム側に linux カーネル本体を入れる必要はありません

ゲストLinux起動

ゲストLinux 起動の前に ホスト側のネットワークインターフェース作成します

$ su
Password:
# tunctl -t tap0 -u 500
Set 'tap0' persistent and owned by uid 500
# ifconfig tap0 192.168.255.1 netmask 255.255.255.0 up
# ifconfig tap0
tap0      Link encap:Ethernet  HWaddr 36:d1:e7:3d:75:44  
          inet addr:192.168.255.1  Bcast:192.168.255.255  Mask:255.255.255.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:500 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

#
 

tunctl時に -u で ゲストシステムを起動するユーザの UIDを指定する必要がありました
(そうしないと ゲストLinuxの起動時に eth0 が up状態にできませんでした)
tap0 は使い終わったら tunctl -d tap0 で削除できます


ともかくホスト側に 192.168.255.0/24 のネットワークが構築されました
ホスト側の準備は以上です

ゲストLinuxを起動してみます

$ ~/linux-3.7.8/linux ubd0=~/uml_gentoo_st3_20120605 eth0=tuntap,tap0 mem=96M
Core dump limits :
        soft - 0
        hard - NONE
Checking that ptrace can change system call numbers...OK
Checking syscall emulation patch for ptrace...OK
Checking advanced syscall emulation patch for ptrace...OK
Checking for tmpfs mount on /dev/shm...OK
Checking PROT_EXEC mmap in /dev/shm/...OK
Checking for the skas3 patch in the host:

〜

 * Initializing random number generator ...
 [ ok ]
INIT: Entering runlevel: 3
 * Bringing up interface eth0
 *   192.168.255.254 ...
 [ ok ]
 *   Adding routes
 *     default via 192.168.255.1 ...
 [ ok ]
 * Mounting network filesystems ...
 [ ok ]
 * Generating dsa host key ...
Generating public/private dsa key pair.
Your identification has been saved in /etc/ssh/ssh_host_dsa_key.
Your public key has been saved in /etc/ssh/ssh_host_dsa_key.pub.
The key fingerprint is:
e2:da:53:06:1e:d5:97:b9:6e:e1:3a:94:4c:4c:9a:48 root@localhost
The key's randomart image is:
+--[ DSA 1024]----+
|         .   o   |
|      E . o +    |
|     . o = . .   |
|      + o o o    |
|     ..oSo + .   |
|     ...o + +    |
|      .o . o     |
|     o.   o      |
|    . ..   .     |
+-----------------+
 [ ok ]
 * Generating rsa host key ...
Generating public/private rsa key pair.
Your identification has been saved in /etc/ssh/ssh_host_rsa_key.
Your public key has been saved in /etc/ssh/ssh_host_rsa_key.pub.
The key fingerprint is:
6e:f1:ea:e7:bc:23:f1:d5:78:f7:4d:88:a5:a9:b3:a1 root@localhost
The key's randomart image is:
+--[ RSA 2048]----+
|                 |
|                 |
|                 |
|              .  |
|        S    B . |
|       ..o  * + o|
|        oooo . oo|
|       ..+*.    o|
|       .E+==     |
+-----------------+
 [ ok ]
 * Starting sshd ...
 [ ok ]
 * Starting local
bash: no job control in this shell
 

初回起動のため SSHのサンプル鍵ペアが自動作成されてますが 使うことはありません
既に SSHの鍵ペアは作成済のためです

また ゲストLinux側のコンソールは起動プロセスが完了した後 プロンプトがなくなりました
Xが動くホストシステムなら xtermが何枚か開いて ログインプロンプトが出るはずです

ホストLinuxから TUN/TAP経由で SSHログインしてみましょう

$ ssh -i uml_admin admin@192.168.255.254
The authenticity of host '192.168.255.254 (192.168.255.254)' can't be established.
RSA key fingerprint is 6e:f1:ea:**:**:**:**:**:**:**:**:**:**:a9:b3:a1.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.255.254' (RSA) to the list of known hosts.
Password:
admin@localhost ~ $
 

ゲストシステムに SSHログインすることができました
ここまでできれば 残りのシステム構築は SSH経由で可能です

admin@localhost ~ $ uname -a
Linux localhost 3.7.8 #1 Tue Mar 5 23:08:40 JST 2013 x86_64 UML User Mode Linux GNU/Linux
admin@localhost ~ $ ifconfig -a
eth0: flags=4163  mtu 1500
        inet 192.168.255.254  netmask 255.255.255.0  broadcast 192.168.255.255
        ether 0a:ea:b7:27:57:6f  txqueuelen 1000  (Ethernet)
        RX packets 75  bytes 7145 (6.9 KiB)
        RX errors 0  dropped 6  overruns 0  frame 0
        TX packets 44  bytes 6456 (6.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 5  

lo: flags=73  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 0  (Local Loopback)
        RX packets 4  bytes 344 (344.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4  bytes 344 (344.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

admin@localhost ~ $ cat /proc/cpuinfo
processor       : 0
vendor_id       : User Mode Linux
model name      : UML
mode            : skas
host            : Linux phenom 3.7.8 #9 SMP Tue Feb 26 19:37:43 JST 2013 x86_64
bogomips        : 2505.11

admin@localhost ~ $  cat /proc/meminfo
MemTotal:          90912 kB
MemFree:           68276 kB
Buffers:            1248 kB
Cached:            11476 kB
SwapCached:            0 kB
Active:             9484 kB
Inactive:           7116 kB
Active(anon):       3908 kB
Inactive(anon):       20 kB
Active(file):       5576 kB
Inactive(file):     7096 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:          3892 kB
Mapped:             3820 kB
Shmem:                52 kB
Slab:               4412 kB
SReclaimable:       2448 kB
SUnreclaim:         1964 kB
KernelStack:         240 kB
PageTables:          884 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:       45456 kB
Committed_AS:      10880 kB
VmallocTotal:   534134768 kB
VmallocUsed:           0 kB
VmallocChunk:   534134768 kB
admin@localhost ~ $
 

ゲストシステムには 1CPUしか割り当てられないので
シングルCPUマシンのように表示されます

メモリは ゲスト起動時に mem=96M と割り当てられた通りです

次に ネットワークの接続性です

admin@localhost ~ $ ping 192.168.0.254
PING 192.168.0.254 (192.168.0.254) 56(84) bytes of data.
64 bytes from 192.168.0.254: icmp_seq=1 ttl=64 time=0.042 ms
64 bytes from 192.168.0.254: icmp_seq=2 ttl=64 time=0.041 ms
^C
--- 192.168.0.254 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1007ms
rtt min/avg/max/mdev = 0.041/0.041/0.042/0.006 ms
admin@localhost ~ $ ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
^C
--- 192.168.0.1 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1007ms
admin@localhost ~ $
 

ホストLinuxとは疎通しますが ホームゲートウェイとはつながりません
なぜでしょうか?
ホームゲートウェイに 192.168.255.0/24 へのルーティング設定が必要です
ゲストファイルシステムの設定のところで説明した ネットワーク図を見ればわかりますが

インターネットと接続するためには 上記ルーティングの追加と
ゲストシステムへの DNS設定が必要となります
また レンタルサーバとして他人に提供する場合にセキュリティ考慮が必要です
家庭内LAN 192.168.0.0/24 が覗けないように DMZ化する検討が必要です
ホストシステム側 tap0 に対するファイアウォール設定で実現できます

最後に ゲストシステムのシャットダウンですが
ゲストシステムがフリーズした場合など ホストシステムから制御する方法があります
uml_mconsole コマンドが使えます ホストシステム側から下記のように操作します

$ cd ~/.uml
$ ls
yJ4qJG
$ uml_mconsole yJ4qJG
(yJ4qJG) int
(yJ4qJG) quit
$
 

yJ4qJG と見えているのは UNIXドメインソケットです
このソケットを通じてゲストシステムを制御できます
int と入力するとゲストシステムが終了するのを確認できます

最後に

UMLを使うことで 仮想マシンに近い環境を構築できることを確認しました

ただし User Mode と冠がついているのとは実際は異なり
(カーネルは 一般ユーザ権限でビルドできますが)
ファイルシステムの作成 や TUN/TAPインターフェースの生成に root権限が必要です
(ファイルシステムについては UML用のイメージを探すなどの代替策は考えられます)

MegaDrive Mega-CD エミュレータ Gens

だめもとで試してみた Gens Linux port が動作した

というわけで ビルドしてインストールするまでの様子をレポートします
以下の評価環境で実験しました NASMアセンブラがビルドに必要のようです
評価環境

OS Linux kernel-2.6.24
CPU Turion64 MT-37 (2GHz)
グラフィック GeForce6600GT NVIDIA-Linux-x86-96.43.05
OpenGL Mesa-6.5.2
サウンド ALSA SoundBlaster Live Gamer
ゲームパッド USB ELECOM JC-PS201U (プレステコントローラアダプタ)
Gens Gens/GS Milestone 6 (Source Code)
コンパイラ gcc バージョン 4.1.2
アセンブラ NASM version 0.98.39

Gensのソースは SONIC RETRO GENS/GS から取得します
以下のように ソースファイルを一時ディレクトリに展開してビルドを開始します
$ tar -xzf Gens-2.15.5-gs-m6.tar.gz
$ cd gens-2.15.5-gs-m6
$ ./configure --prefix=/usr/local
checking build system type... i686-pc-linux-gnu
checking host system type... i686-pc-linux-gnu
checking target system type... i686-pc-linux-gnu
checking for a BSD-compatible install... /bin/install -c
〜
config.status: creating src/libpng/Makefile
config.status: creating src/mp3_dec/Makefile
config.status: creating src/starscream/Makefile
config.status: creating src/starscream/main68k/Makefile
config.status: creating src/starscream/sub68k/Makefile
config.status: creating config.h
config.status: executing depfiles commands
$ make
検証の環境においては 実際は以降に述べるようなコンパイルエラーが出ました
〜
if g++ -DHAVE_CONFIG_H -I. -I. -I../.. -I. -I./../ -I./ui/   -I./ui/gtk/    -DGENS_DATADIR=\"/home/admin/share/gens\" -I/usr/local/include/SDL -D_GNU_SOURCE=1 -D_REENTRANT -I/usr/local/include/gtk-2.0 -I/usr/local/lib/gtk-2.0/include -I/usr/local/include/atk-1.0 -I/usr/local/include/cairo -I/usr/local/include/pango-1.0 -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -I/usr/local/include -I/usr/local/include/freetype2 -I/usr/local/include/libpng12 -I/usr/X11R7/include   -DGTK_DISABLE_DEPRECATED -O2 -Wall -Wextra -g  -g -O2 -O2 -Wall -Wextra -g  -g -O2 -MT ui/gtk/gens/gens-gens_window_callbacks.o -MD -MP -MF "ui/gtk/gens/.deps/gens-gens_window_callbacks.Tpo" -c -o ui/gtk/gens/gens-gens_window_callbacks.o `test -f 'ui/gtk/gens/gens_window_callbacks.cpp' || echo './'`ui/gtk/gens/gens_window_callbacks.cpp; \
then mv -f "ui/gtk/gens/.deps/gens-gens_window_callbacks.Tpo" "ui/gtk/gens/.deps/gens-gens_window_callbacks.Po"; else rm -f "ui/gtk/gens/.deps/gens-gens_window_callbacks.Tpo"; exit 1; fi
ui/gtk/gens/gens_window_callbacks.cpp: In function 'void gens_window_drag_data_received(GtkWidget*, GdkDragContext*, gint, gint, GtkSelectionData*, guint, guint, void*)':
ui/gtk/gens/gens_window_callbacks.cpp:127: error: 'g_uri_unescape_string' was not declared in this scope
ui/gtk/gens/gens_window_callbacks.cpp: At global scope:
ui/gtk/gens/gens_window_callbacks.cpp:93: 警告: unused parameter 'widget'
ui/gtk/gens/gens_window_callbacks.cpp:93: 警告: unused parameter 'x'
ui/gtk/gens/gens_window_callbacks.cpp:93: 警告: unused parameter 'y'
ui/gtk/gens/gens_window_callbacks.cpp:93: 警告: unused parameter 'target_type'
ui/gtk/gens/gens_window_callbacks.cpp:93: 警告: unused parameter 'data'
ui/gtk/gens/gens_window_callbacks.cpp:149: 警告: unused parameter 'x'
ui/gtk/gens/gens_window_callbacks.cpp:149: 警告: unused parameter 'y'
ui/gtk/gens/gens_window_callbacks.cpp:149: 警告: unused parameter 'user_data'
ui/gtk/gens/gens_window_callbacks.cpp:171: 警告: unused parameter 'widget'
ui/gtk/gens/gens_window_callbacks.cpp:171: 警告: unused parameter 'event'
ui/gtk/gens/gens_window_callbacks.cpp:171: 警告: unused parameter 'user_data'
ui/gtk/gens/gens_window_callbacks.cpp:185: 警告: unused parameter 'widget'
ui/gtk/gens/gens_window_callbacks.cpp:185: 警告: unused parameter 'event'
ui/gtk/gens/gens_window_callbacks.cpp:185: 警告: unused parameter 'user_data'
make[3]: *** [ui/gtk/gens/gens-gens_window_callbacks.o] エラー 1
make[3]: Leaving directory `/home/admin/gens-2.15.5-gs-m6/src/gens'
make[2]: *** [all-recursive] エラー 1
make[2]: Leaving directory `/home/admin/gens-2.15.5-gs-m6/src'
make[1]: *** [all-recursive] エラー 1
make[1]: Leaving directory `/home/admin/gens-2.15.5-gs-m6'
make: *** [all] エラー 2
$
‘g_uri_unescape_string’ was not declared in this scope と怒られました
g_uri_unescape_string() 関数とは Glib で提供されていた関数で
文字列中のエスケープ文字を処理する機能を提供するようです
最近の Glib のバージョンのためか 現在はこの機能が削除されたのでしょう

src/gens/ui/gtk/gens/gens_window_callbacks.cpp で発生したエラーなので
このソースコードの 空いている箇所に 以下のコードを追加します
 static int
 unescape_character (const char *scanner)
 {
   int first_digit;
   int second_digit;

   first_digit = g_ascii_xdigit_value (*scanner++);
   if (first_digit < 0)
     return -1;

   second_digit = g_ascii_xdigit_value (*scanner++);
   if (second_digit < 0)
     return -1;

   return (first_digit << 4) | second_digit;
 }

 static char *
 g_uri_unescape_segment (const char *escaped_string,
                         const char *escaped_string_end,
                         const char *illegal_characters)
 {
   const char *in;
   char *out, *result;
   gint character;

   if (escaped_string == NULL)
     return NULL;

   if (escaped_string_end == NULL)
     escaped_string_end = escaped_string + strlen (escaped_string);

   result = (char*) g_malloc (escaped_string_end - escaped_string + 1);

   out = result;
   for (in = escaped_string; in < escaped_string_end; in++)
     {
       character = *in;

       if (*in == '%')
         {
           in++;

           if (escaped_string_end - in < 2)
             {
               /* Invalid escaped char (to short) */
               g_free (result);
               return NULL;
             }

           character = unescape_character (in);

           /* Check for an illegal character. We consider '\0' illegal here. */
           if (character <= 0 ||
               (illegal_characters != NULL &&
                strchr (illegal_characters, (char)character) != NULL))
             {
               g_free (result);
               return NULL;
             }

           in++; /* The other char will be eaten in the loop header */
         }
       *out++ = (char)character;
     }
 
   *out = '\0';

   return result;
 }

 char *
 g_uri_unescape_string (const char *escaped_string,
                        const char *illegal_characters)
 {
   return g_uri_unescape_segment (escaped_string, NULL, illegal_characters);
 } 
これら 3つの関数を直接記述することで GLibの g_uri_unescape_string() を代用します

ふたたび make したところ 今度は
mkdir -p gens_core/gfx/ gens_core/io/ gens_core/mem/ gens_core/misc/ ;
mkdir -p gens_core/sound/ gens_core/cpu/sh2/ gens_core/vdp/ gens_core/cpu/z80/ ;
/usr/local/bin/nasm -O3 -f elf -D __GCC2 -D __OBJ_ELF -g -F dwarf -w-orphan-labels -I./gens_core/ -I./gens_core/io/ -I./gens_core/misc/ -I./gens_core/vdp/ -I./gens_core/sound/ -I./gens_core/gfx/ -I./gens_core/cpu/sh2/ -I./gens_core/cpu/z80/ -I./gens_core/mem/ gens_core/gfx/fastblur_16_x86.asm -o gens_core/gfx/fastblur_16_x86.o
nasm: fatal: unrecognized debug format `dwarf' for output format `elf'
type `nasm -h' for help
make[3]: *** [gens_core/gfx/fastblur_16_x86.o] エラー 1
make[3]: Leaving directory `/home/admin/gens-2.15.5-gs-m6/src/gens'
make[2]: *** [all-recursive] エラー 1
make[2]: Leaving directory `/home/admin/gens-2.15.5-gs-m6/src'
make[1]: *** [all-recursive] エラー 1
make[1]: Leaving directory `/home/admin/gens-2.15.5-gs-m6'
make: *** [all] エラー 2
elfフォーマットでは dwarfデバッグフォーマットは使えない と言われました
確かに ELFは Linuxなどでよく使われる実行ファイルのフォーマットで
DWARFはデバッグ情報のフォーマットです

仕方がないので DWARF機能を無効化してみます
うまくビルドできたとしても デバッガ上での動作で不具合を起こす可能性が高くなります

src/gens/Makefile 中に 以下のように NASMのオプションんを設定している箇所があるので
 NASM = /usr/local/bin/nasm -O3 -f elf -D __GCC2 -D __OBJ_ELF -g -F dwarf -w-orphan-labels $(NASM_INCLUDES)
次のように書き換えます
 NASM = /usr/local/bin/nasm -O3 -f elf -D __GCC2 -D __OBJ_ELF -g -w-orphan-labels $(NASM_INCLUDES)
-F dwarf のオプションを削除して 回避します
で make コマンドでビルド続行

またまたエラーが出ました
mkdir -p gens_core/gfx/ gens_core/io/ gens_core/mem/ gens_core/misc/ ;
mkdir -p gens_core/sound/ gens_core/cpu/sh2/ gens_core/vdp/ gens_core/cpu/z80/ ;
/usr/local/bin/nasm -O3 -f elf -D __GCC2 -D __OBJ_ELF -g -w-orphan-labels -I./gens_core/ -I./gens_core/io/ -I./gens_core/misc/ -I./gens_core/vdp/ -I./gens_core/sound/ -I./gens_core/gfx/ -I./gens_core/cpu/sh2/ -I./gens_core/cpu/z80/ -I./gens_core/mem/ plugins/render/hq2x/mdp_render_hq2x_16_x86.asm -o plugins/render/hq2x/mdp_render_hq2x_16_x86.o
plugins/render/hq2x/mdp_render_hq2x_16_x86.asm:2066: error: phase error detected at end of assembly.
make[3]: *** [plugins/render/hq2x/mdp_render_hq2x_16_x86.o] エラー 1
make[3]: Leaving directory `/home/admin/gens-2.15.5-gs-m6/src/gens'
make[2]: *** [all-recursive] エラー 1
make[2]: Leaving directory `/home/admin/gens-2.15.5-gs-m6/src'
make[1]: *** [all-recursive] エラー 1
make[1]: Leaving directory `/home/admin/gens-2.15.5-gs-m6'
make: *** [all] エラー 2
「phase error」!… あまり見かけない類のエラーです… しかも NASMでのエラー
インターネットで検索すると エラーの理由と対処方法が書かれてました

「NASMのバグにより -O2 -O3 などのオプティマイズに失敗し jmp系のジャンプ命令の距離計算を間違える」
-O1 や -O999 にすると改善される

-O999 というのも意味不明なので -O1 に書き換えてみます
src/gens/Makefile のやはり先程と同じ NASMの定義行を 今度は次のように書き換えます
  NASM = /usr/local/bin/nasm -O1 -f elf -D __GCC2 -D __OBJ_ELF -g -w-orphan-labels $(NASM_INCLUDES)
-O3 だったところを -O1 に書き換えて再度 make します
〜
Making all in pixmaps
make[2]: Entering directory `/home/admin/gens-2.15.5-gs-m6/pixmaps'
make[2]: `all' に対して行うべき事はありません。
make[2]: Leaving directory `/home/admin/gens-2.15.5-gs-m6/pixmaps'
Making all in xdg
make[2]: Entering directory `/home/admin/gens-2.15.5-gs-m6/xdg'
make[2]: `all' に対して行うべき事はありません。
make[2]: Leaving directory `/home/admin/gens-2.15.5-gs-m6/xdg'
make[2]: Entering directory `/home/admin/gens-2.15.5-gs-m6'
make[2]: Leaving directory `/home/admin/gens-2.15.5-gs-m6'
make[1]: Leaving directory `/home/admin/gens-2.15.5-gs-m6'
$ su
# make install
/bin/sh ./git_version.sh -k -s . -o git_version.h
git_version.sh: Not a git repo, keeping existing git_version.h
make  install-recursive
make[1]: Entering directory `/home/gens-2.15.5-gs-m6'
Making install in src
〜
make[2]: Entering directory `/home/admin/gens-2.15.5-gs-m6'
make[3]: Entering directory `/home/admin/gens-2.15.5-gs-m6'
make[3]: `install-exec-am' に対して行うべき事はありません。
make[3]: `install-data-am' に対して行うべき事はありません。
make[3]: Leaving directory `/home/admin/gens-2.15.5-gs-m6'
make[2]: Leaving directory `/home/admin/gens-2.15.5-gs-m6'
make[1]: Leaving directory `/home/admin/gens-2.15.5-gs-m6'
うまくコンパイルが通りました
インストールされるファイルは /usr/local/bin/gens 本体と
/usr/local/share/applications/gens.desktop
/usr/local/share/gens/ 配下にアイコンファイルがいくつかです

Gens を使ってみる

使用感をレポートします

Gens画面 ROM

GUIはこんな感じです Windows版に準拠しています
画面は ハードドライビン です
メガドライブ版はポリゴン黎明期ゲームに過ぎませんが
アーケード版は 4段変速ギア クラッチ エンジンキー まで搭載した筐体で
まさしく ドライブシミュレータのようなリアルさが心を打ちます

Gens画面 ファイルメニュー

BootCD の項目があるとおり MEGA-CDが使えるようです
またステートセーブもできそうです
画面は カダッシュです アーケード版ではパスワードシステムが斬新でした

Gens画面 グラフィック

Linux版ということで OpenGLの項目があります
検証環境は十分高速な環境なので OpenGLを使う体感速度の差異は感じられません (むしろ遅くなった?)
画面は アドバンスド大戦略です 名作です
昔プレイしたときは シナリオが進むにつれ資金難となっていき ついには頓挫しました コツがあるのかな

Gens画面 CPU

CPU選択も Windows版に準拠しています
メニュー下にある SegaCD Perfect Sync をチェックしないと MEGA-CDがうまく動きませんでした
画面は スーパーハイドライドです
パソコンのハイドライド3をベースにして グラフィック や サウンド をパワーアップしています
アイテムに重さの概念があるため 敵を倒して小金を稼いでいるとそのうち動きが鈍くなります

Gens画面 サウンド

個別のチャンネル毎に ON/OFF できるようです
検証環境では MEGA-CDの CD演奏が鳴りませんでした
画面は クラック版?ソニック ! 誰かが作ったのでしょうか よくできてます

Gens画面 ジョイスティック

オプション項目です ジョイスティックも使えました キー配置も定義できます
画面は ダイナマイトヘッディー メガドライブ後期のアクションゲームです 名作です
伸びる首を使って 敵を倒したり 移動したりします

Gens画面 ディレクトリ

オプション項目です スクリーンショットや セーブデータの保存先を指定できます
デフォルトは ホームディレクトリにできる .gens ディレクトリが指定されます
画面は ワイアラエの奇蹟 当時 T&Eがリアルな 3Dゴルフシミュレーションを製作していました
ワイアラエの奇蹟 は 遥かなるオーガスタ ぺブルビーチの波涛 に続く 3作目です

Gens画面 BIOS

オプション項目です Windows版もそうですが MEGA-CD と 32X をプレイするには
BIOSのファイルが別途必要です
画面は 32X版 メタルヘッド 32Xらしくポリゴンが強化された 3Dロボットシューティングです
検証環境では 32Xのソフトは速度の関係からか サウンドがプツプツ切れます

Gens画面 MEGA-CD

MEGA-CDの動作検証もしました
前述したとおり SegaCD Perfect Sync を指定しないと ゲームが起動しませんでした
また CD音源が音が聞こえませんでした
画面は ソル・フィース 当時の グラディウスIII を対抗して作ったらしいシューティングゲームです
プログラム技術の高さと 自機の上下に付く可変アームが特徴的です

CDemu で CD-ROMイメージをマウントする

CDemuとは

Windowsでは daemontoolsが有名ですが
それと同様の機能を提供します
  • 仮想ドライブ /dev/sr〜 の提供
  • b6t,c2d,ccd,cdi,cif,cue,daa,iso,mds,nrg,toc 形式のサポート
  • CDオーディオのサポート

パッケージ構成

パッケージ構成の説明
cdemu-daemon CDemuのサーバ クライアントからの制御を受け付ける
cdemu-client コマンドラインのフロントエンド 仮想イメージの追加/削除をサーバに要求する
VHBA カーネルモジュール /dev/sr〜 の処理を行う /dev/vhba_ctl をサポート
libdaemon 常駐機能を実装するライブラリ
libmirage CD/DVDのイメージファイルを処理するライブラリ

このブログでは CDemuのソースコードからのインストールの注意点と 利用方法の概要説明 CDemu動作の問題点とその解決方法について記載しています

インストール

検証時の構成
CPU AMD Turion64 MT-37 (2GHz)
OS Linux-2.6.30.5
DVDドライブ /DC-DW1670
ATAPI内蔵
DVD-RW x6, DVD+RW x8, DVD-R x16, DVD+R x16
DVD-RAM x5, CD-R x48, CD-RW x32
コンパイル環境 GCC-4.1.2(i686), GLIBC-2.5
D-Bus dbus-1.0.2
VHBA vhba-module-1.2.1
libmirage libmirage-1.2.0
cdemu-daemon cdemu-daemon-1.2.0
cdemu-client cdemu-client-1.2.0
最終検証日 2010-5-23
ここでは cdemuのインストールについて 注意点等を記載します

libdaemon
libmirage については
ソースコードを展開して ./configure → make → make install (root権限で)
でインストールできました

vhba-module については
ソースコードを展開後 以下の手順でインストールします

$ make modules
$ su
# make module_install
 

ただし (Linuxの古いバージョン等) 以下のエラーが発生する場合

CC [M]  〜/vhba-module-1.0.0/vhba.o
〜/vhba-module-1.0.0/vhba.c:793:51: error: マクロ "INIT_WORK" は引数を 3 要求しますが、2 個しか与えられていません
 

INIT_WORKマクロの仕様が linuxカーネル側で修正になっているので vhba.c 793行目 を修正します

INIT_WORK(&vhost->scan_devices, vhba_scan_devices);
↓
INIT_WORK(&vhost->scan_devices, vhba_scan_devices, &vhost->scan_devices);

また 少なくとも kernel-2.6.24 以降では CFLAGSの設定でエラーが出るようです

make -C /lib/modules/`uname -r`/build M=/home/admin/vhba-module-1.0.0 modules
make[1]: Entering directory `/usr/src/linux-2.6.24'
scripts/Makefile.build:46: *** CFLAGS was changed in "/home/admin/vhba-module-1.0.0/Makefile". Fix it to use EXTRA_CFLAGS。中止。
make[1]: *** [_module_/home/admin/vhba-module-1.0.0] エラー 2
make[1]: Leaving directory `/usr/src/linux-2.6.24'
make: *** [modules] エラー 2
 

CFLAGS を EXTRA_CFLAGS に変えてくださいとのエラーのように見えます Makefile を以下のように修正します

2 PACKAGE = vhba-module-$(VHBA_VERSION)
3 
4 CFLAGS += -DVHBA_VERSION=\"$(VHBA_VERSION)\"
5 
6 obj-m += vhba.o
↓
2 PACKAGE = vhba-module-$(VHBA_VERSION)
3 
4 EXTRA_CFLAGS += -DVHBA_VERSION=\"$(VHBA_VERSION)\"
5 
6 obj-m += vhba.o
 

さらに以下のようなエラーが出る場合

make -C /lib/modules/`uname -r`/build M=/home/admin/vhba-module-1.0.0 modules
make[1]: Entering directory `/usr/src/linux-2.6.24'
  CC [M]  /home/admin/vhba-module-1.0.0/vhba.o
/home/admin/vhba-module-1.0.0/vhba.c: In function 'do_response':
/home/admin/vhba-module-1.0.0/vhba.c:523: error: 'struct scatterlist' has no member named 'page'
make[2]: *** [/home/admin/vhba-module-1.0.0/vhba.o] エラー 1
make[1]: *** [_module_/home/admin/vhba-module-1.0.0] エラー 2
make[1]: Leaving directory `/usr/src/linux-2.6.24'
make: *** [modules] エラー 2
 

少なくともカーネル 2.4.24以降の struct scatter_list のメンバ要素が仕様修正されてるようで vhba.c に以下の修正を加えました

522 
523             kaddr = kmap_atomic(sg[i].page, KM_USER0);
524             memcpy(kaddr + sg[i].offset, kbuf, len);
525             kunmap_atomic(kaddr, KM_USER0);
526 
↓
522 
523             kaddr = kmap_atomic(sg[i].page_link, KM_USER0);
524             memcpy(kaddr + sg[i].offset, kbuf, len);
525             kunmap_atomic(kaddr, KM_USER0);
526 
 

また udevのルール設定に追加が必要です 次のような 1行を追加しておきます

KERNEL=="vhba_ctl", NAME="%k", MODE="0660", OWNER="root", GROUP="users"
 

インストールがうまくいったら モジュールがロードできるようになります

# insmod /lib/modules/〜/extra/vhba.ko
# lsmod
Module                  Size  Used by
vhba                    7168  0 
nvidia               6816276  22 
 

vhba.ko は Linuxのシステム起動時に自動的に読み込まれるように設定しておくと便利です

次に cdemu-daemon 本体をインストールします ソースコードを展開後

$ ./configure --with-dbus-system-dir=/usr/local/etc/dbus-1/system.d
〜
 cdemu-daemon 1.2.0 configure summary:
  Prefix:                    /usr/local
  dbus-1 system.d dir:       /usr/local/etc/dbus-1/system.d
  C Compiler:                gcc -std=gnu99
  CFLAGS:                    -g -O2
$ make
$ su
# make install
 

–with-dbus-system-dir の設定は環境によっては /etc/dbus-1/system.d かもしれません
インストールが完了すれば cdemud のコマンドが使えるようになっています

最後に cdemu-client をインストールします ソースコードを展開後

$ make
$ su
# make install
 

インストール後は cdemu コマンドが使えるようになります

利用方法

まず 前提が 2つあり
1つ目は VHBAのカーネルモジュールがロードされている必要があるということです
(lsmodで確認 なければ modprobe vhba コマンドでロード完了です)
2つ目は D-Busも正しく起動して常駐されている必要があるということです
(ps -axで確認 なければ root権限にて dbus-daemon –system で起動されるか試してください)

いよいよ本番ですが cdemud を常駐させます rootのコンソールを 1枚立ち上げて

# cdemud -a alsa
 - num devices: 1
 - ctl device: /dev/vhba_ctl
 - audio driver: alsa
 - bus type: system
 

とりあえず フロントエンドで常駐した状態になります

  • num_devices は cdemud に -n オプションを付けることで指定できる
  • バックエンドで常駐するには -d をつければよいはずですが 手元の環境では確認できませんでした
  • audio_backend は -a オプションで指定します libaoが適切に導入されている必要があります

cdemudが起動することで /dev/sr0 が 仮想CDデバイスとして見えるようになるはずです

以降は主に cdemu コマンドで CDイメージを操作することになります
まずは イメージのロードから

# cdemu load 0 ~/test.cue
#
 

上記コマンドでは test.cue のキューシートを指定して読み込む指定です
何事もエラーがでなければ OKです /dev/sr0 に CDイメージがセットされているはずです

仮想CDデバイスの利用状況は以下のように確認できます

# cdemu status
Devices status:
DEV   LOADED     TYPE       FILENAME
0     1          PARSER-CUE /root/test.cue
 

では 実際にロードしたイメージが 通常のCDデバイスと同じく扱えるか確認してみます

# mount -t iso9660 -o ro /dev/sr0 /mnt/cdrom
# ls /mnt/cdrom
AUTORUN.INF              Setup.exe     congrats.avi  layout.bin
Autorun.exe              _INST32I.EX_  credits.avi   lemmings.box
DATA.TAG                 _ISDel.exe    data1.cab     os.dat
DirectX7.0a              _Setup.dll    data1.hdr     setup.bmp
Intel Indeo Readme.txt   _sys1.cab     delreg.exe    setup.ins
Lemmings Revolution.exe  _sys1.hdr     failed.avi    setup.lid
Patch.box                _user1.cab    frontend.avi  splash256.bmp
SETUP.INI                _user1.hdr    gameover.avi
SavedGame.dat            complete.avi  lang.dat
 

Windows用のゲームの CD-ROMをマウントした結果 正しくファイルシステムが見えていることが分かります

イメージを利用し終えたら /dev/sr0 での利用を次のように解除します

# umount /mnt/cdrom
# cdemu unload 0
 

以上が CDemuの基本的な流れとなります
cdemu load と cdemu unload コマンドで LinuxにおいてもCDイメージが扱え非常に便利です

注意点としては cdemu load する時のイメージパスに日本語を含む場合は EUC-JP でなく UTF-8 で指定しないと受け付けてもらえず EUC-JPの環境では苦労することです

CDemuの問題について

検証環境にて CDemuを利用して遭遇した問題点について 解決できたのがあるので紹介します
問題点については以下のとおりです
  • CD-DAオーディオの問題 cdda-playerによる演奏がうまく鳴らない現象
  • データトラックへアクセスできない不具合 PC-EngineのCD-ROMなど特殊な構成で発生
まず CD-DAオーディオの問題ですが
下記のように cdda-player コマンドによる CD-DA演奏でどのトラックも音が鳴らず一瞬でトラック終了に達してしまいます

  $ cdda-player /dev/sr0
 

CDemu + cdda-player 失敗画面
実際は 一瞬音が鳴るのですが すぐに停止状態になってしまいます

ちなみに cdda-playerは libcdioパッケージの付属ツールです コマンドラインからCD演奏できます
cdemu-daemonでは 実際の CDデバイスに対する ioctl()処理をエミュレートする機能がありますが
その動作を解析した結果 問題箇所と回避策を見つけました

cdda-playerでは 演奏開始直後に CDデバイスに対して MODE SENSEコマンドを発行していたのです
MODE SENSEとは CDドライブの能力情報をドライブから取得するコマンドです
cdemuにおいては 下記のソースコード(cdemu-device.c)で

2770     } packet_commands[] = {
2771         { PC_GET_EVENT_STATUS_NOTIFICATION,
2772           "GET EVENT/STATUS NOTIFICATION",
2773           __cdemud_device_pc_get_event_status_notification,
2774           FALSE },
2775         { PC_GET_CONFIGURATION,
2776           "GET CONFIGURATION",
2777           __cdemud_device_pc_get_configuration,
2778           TRUE, },
2779         { PC_INQUIRY,
2780           "INQUIRY",
2781           __cdemud_device_pc_inquiry,
2782           FALSE },
2783         { PC_MODE_SELECT_6,
2784           "MODE SELECT (6)",
2785           __cdemud_device_pc_mode_select,
2786           TRUE },
2787         { PC_MODE_SELECT_10,
2788           "MODE SELECT (10)",
2789           __cdemud_device_pc_mode_select,
2790           TRUE },
2791         { PC_MODE_SENSE_6,
2792           "MODE SENSE (6)",
2793           __cdemud_device_pc_mode_sense,
2794           TRUE },
2795         { PC_MODE_SENSE_10,
2796           "MODE SENSE (10)",
2797           __cdemud_device_pc_mode_sense,
2798           TRUE },
2799         { PC_PAUSE_RESUME,
2800           "PAUSE/RESUME",
2801           __cdemud_device_pc_pause_resume,
2802           FALSE /* Well, it does... but in it's own, unique way :P */ },
 

各コマンドの 処理方法を定義していますが
それぞれ TRUE FALSE とある部分が コマンドを処理するときに CD-DAを停止するかどうかのフラグで
MODE_SENSE においては TRUE つまり 停止となっています

2792           "MODE SENSE (6)",
2793           __cdemud_device_pc_mode_sense,
2794           FALSE },
2795         { PC_MODE_SENSE_10,
2796           "MODE SENSE (10)",
2797           __cdemud_device_pc_mode_sense,
2798           FALSE },
 

上記のように FALSEに設定することで cdda-playerが正常に演奏されるようになりました

(実際のデバイスの振舞いと一致しているかどうかは未確認です
また cdda-player以外の再生環境では問題はでないかもしれません)

ちなみに CDemuでは 音声ファイルを libsndfileに任せているので
sndfile-play コマンドで演奏できるフォーマットは CUEシートに入力できます

FILE "test1.ogg" WAVE
 TRACK 1 AUDIO
 INDEX 1 00:00:00
FILE "test2.ogg" WAVE
 TRACK 2 AUDIO
 INDEX 1 00:00:00
 

上記のようなCUEシートで Oggのような圧縮音声形式もCDイメージ化でき大変コンパクトに収まります
(残念ながら mp3 は sndfileで扱ってなかったので 検証環境でCDイメージ化は不可でした)

次に データトラックへのアクセスの問題です
現象としては Mednafen(マルチゲームエミュレータ) + 「アルナムの牙」(PC-Engine CD-ROM2ゲーム)の CUEシートで全く動作しないという問題です
CUEシートは 以下のとおりです

FILE "track_01.wav" WAVE
 TRACK 1 AUDIO
 INDEX 1 00:00:00
FILE "track_02.bin" BINARY
 TRACK 2 MODE1/2352
 INDEX 1 00:00:00
FILE "track_03.wav" WAVE
 TRACK 3 AUDIO
 INDEX 1 00:00:00
FILE "track_04.wav" WAVE
 TRACK 4 AUDIO
 INDEX 1 00:00:00
FILE "track_05.wav" WAVE
 TRACK 5 AUDIO
 INDEX 1 00:00:00
FILE "track_06.wav" WAVE
 TRACK 6 AUDIO
 INDEX 1 00:00:00
FILE "track_07.wav" WAVE
 TRACK 7 AUDIO
 INDEX 1 00:00:00
FILE "track_08.wav" WAVE
 TRACK 8 AUDIO
 INDEX 1 00:00:00
FILE "track_09.wav" WAVE
 TRACK 9 AUDIO
 INDEX 1 00:00:00
FILE "track_10.wav" WAVE
 TRACK 10 AUDIO
 INDEX 1 00:00:00
FILE "track_11.wav" WAVE
 TRACK 11 AUDIO
 INDEX 1 00:00:00
FILE "track_12.wav" WAVE
 TRACK 12 AUDIO
 INDEX 1 00:00:00
FILE "track_13.wav" WAVE
 TRACK 13 AUDIO
 INDEX 1 00:00:00
FILE "track_14.wav" WAVE
 TRACK 14 AUDIO
 INDEX 1 00:00:00
FILE "track_15.wav" WAVE
 TRACK 15 AUDIO
 INDEX 1 00:00:00
FILE "track_16.wav" WAVE
 TRACK 16 AUDIO
 INDEX 1 00:00:00
FILE "track_17.wav" WAVE
 TRACK 17 AUDIO
 INDEX 1 00:00:00
FILE "track_18.wav" WAVE
 TRACK 18 AUDIO
 INDEX 1 00:00:00
FILE "track_19.wav" WAVE
 TRACK 19 AUDIO
 INDEX 1 00:00:00
FILE "track_20.wav" WAVE
 TRACK 20 AUDIO
 INDEX 1 00:00:00
FILE "track_21.wav" WAVE
 TRACK 21 AUDIO
 INDEX 1 00:00:00
FILE "track_22.wav" WAVE
 TRACK 22 AUDIO
 INDEX 1 00:00:00
FILE "track_23.wav" WAVE
 TRACK 23 AUDIO
 INDEX 1 00:00:00
FILE "track_24.wav" WAVE
 TRACK 24 AUDIO
 INDEX 1 00:00:00
FILE "track_25.wav" WAVE
 TRACK 25 AUDIO
 INDEX 1 00:00:00
FILE "track_26.wav" WAVE
 TRACK 26 AUDIO
 INDEX 1 00:00:00
FILE "track_27.wav" WAVE
 TRACK 27 AUDIO
 INDEX 1 00:00:00
FILE "track_28.wav" WAVE
 TRACK 28 AUDIO
 INDEX 1 00:00:00
FILE "track_29.wav" WAVE
 TRACK 29 AUDIO
 INDEX 1 00:00:00
FILE "track_30.wav" WAVE
 TRACK 30 AUDIO
 INDEX 1 00:00:00
FILE "track_31.wav" WAVE
 TRACK 31 AUDIO
 INDEX 1 00:00:00
FILE "track_32.wav" WAVE
 TRACK 32 AUDIO
 INDEX 1 00:00:00
FILE "track_33.wav" WAVE
 TRACK 33 AUDIO
 INDEX 1 00:00:00
FILE "track_34.wav" WAVE
 TRACK 34 AUDIO
 INDEX 1 00:00:00
FILE "track_35.wav" WAVE
 TRACK 35 AUDIO
 INDEX 1 00:00:00
FILE "track_36.wav" WAVE
 TRACK 36 AUDIO
 INDEX 1 00:00:00
FILE "track_37.wav" WAVE
 TRACK 37 AUDIO
 INDEX 1 00:00:00
FILE "track_38.wav" WAVE
 TRACK 38 AUDIO
 INDEX 1 00:00:00
FILE "track_39.wav" WAVE
 TRACK 39 AUDIO
 INDEX 1 00:00:00
FILE "track_40.wav" WAVE
 TRACK 40 AUDIO
 INDEX 1 00:00:00
FILE "track_41.wav" WAVE
 TRACK 41 AUDIO
 INDEX 1 00:00:00
FILE "track_42.wav" WAVE
 TRACK 42 AUDIO
 INDEX 1 00:00:00
 

PC-Engineの CDトラック構成は特殊で
1トラック目が音声(警告用) 2トラック目がデータ(プログラム) それ以降は 音声(CD-DA)
となっています ゲームによっては データトラックが最後に入ってくることもあります

Mednafenの起動時に固まってしまう問題で 問題のでないゲームもあり調査に時間がかかりました
結果 上記特殊トラック構成を扱う上での Linux(カーネル?)との相性問題にまで及んでいました

CUEシートのトラック構成は libmirageが扱っていますが 各トラックの開始セクタは次のようになっています

track01   start=-150   length=3743(sectors)    AUDIO  (2352bytes/sector)
track02   start=3593   length=45336(sectors)   BINARY (2352bytes/sector)
track03   start=48929  length=2925(sectors)    AUDIO  (2352bytes/sector)
〜
 

track01 の 開始セクタが -150 と負の値になっているのは プリギャップを加味してるためでここは問題ありません

問題となっているのは track02 です
現行の CDemudでは 2048bytes/sector しかデータトラックの読み出しをサポートしていません
ソースコードでは cdemud-device.c にあたります

1462         /* READ 10/12 should support only sectors with 2048-byte user data      */
1463         gint tmp_len = 0;
1464         guint8 *tmp_buf = NULL;
1465         guint8 *cache_ptr = _priv->buffer+_priv->buffer_size;
1466 
1467         mirage_sector_get_data(MIRAGE_SECTOR(cur_sector), &tmp_buf, &tmp_le     n, NULL);
1468         if (tmp_len != 2048) {
1469             CDEMUD_DEBUG(self, DAEMON_DEBUG_MMC, "%s: sector 0x%X does not      have 2048-byte user data (%i)\n", __debug__, sector, tmp_len);
1470             g_object_unref(cur_sector);
1471             __cdemud_device_write_sense_full(self, SK_ILLEGAL_REQUEST, ILLE     GAL_MODE_FOR_THIS_TRACK, 1, sector);
1472             return FALSE;
1473         }
1474
 

mirage_sector_get_data() でデータを読みだした結果 2048バイトでないとエラーとなります
ここを 以下のように修正しました

1462         /* READ 10/12 should support only sectors with 2048-byte user data */
1463         gint tmp_len = 0;
1464         guint8 *tmp_buf = NULL;
1465         guint8 *cache_ptr = _priv->buffer+_priv->buffer_size;
1466         gint tmp_type = 0;
1467         
1468         mirage_sector_get_data(MIRAGE_SECTOR(cur_sector), &tmp_buf, &tmp_len, NULL)     ;
1469 /*
1470  Add support some 2352bytes/sector format.
1471 */
1472         switch (tmp_len) {
1473             case 2048:  /* MODE1 */
1474                 memcpy(cache_ptr, tmp_buf, 2048);
1475                 _priv->buffer_size += 2048;
1476                 break;
1477             case 2352:  /* MODE2 FORM1 etc.*/
1478                 mirage_sector_get_sector_type(MIRAGE_SECTOR(cur_sector), &tmp_type, NULL);         
1479                 if ((tmp_type == MIRAGE_MODE_MODE1) || (tmp_type==MIRAGE_MODE_MODE2_FORM1)) {      
1480                     memcpy(cache_ptr, tmp_buf + 16, 2048);
1481                     _priv->buffer_size += 2048; 
1482                     break;
1483                 }   
1484                 /* Otherwise, I dont know where 2048-byte user data is in. */
1485             default:
1486                 CDEMUD_DEBUG(self, DAEMON_DEBUG_MMC, "%s: sector 0x%X does not have 2048-byte user data (%i)\n", __debug__, sector, tmp_len);
1487                 g_object_unref(cur_sector);
1488                 __cdemud_device_write_sense_full(self, SK_ILLEGAL_REQUEST, ILLEGAL_MODE_FOR_THIS_TRACK, 1, sector);
1489                 return FALSE;
1490         }
1491         
 

CD-ROMのデータトラックは 通常 1セクタあたり 2048バイトのデータ構成となっていますが
実際は ヘッダ情報や CRCなどの付加情報が前後に含まれています

上記コードは 2352バイトのフォーマットにおいても データ部分 2048バイトを抜き出す対応を追加したものです
ただし MODE1 など 一部のフォーマットのみの対応なので完全ではありません
そもそも CD-ROMからイメージを抜き出す際に MODE1(2048bytes/sector)を指定すれば ここの問題は発生しません

で 問題はさらに続きます
Mednafenで PC-Engineの処理を解析した結果 次の不具合が確認されました
「Mednafenでは データトラックの読み込み開始で 開始セクタ=3593 を指定しているのに
CDemuにおいては 開始セクタ=3592 のセクタリードが要求されている」

Mednafenとしてのデバイスアクセス ioctl() 開始セクタ=3593 で要求

Linuxカーネルのデバイスドライバ処理

VHBA (仮想ドライバ)

CDemu 開始セクタ=3592 の要求をイベントとして受ける

Linuxカーネルを経由することで セクタ番号がずれているように思われます
Linuxカーネルの内部動作まで確認しきれませんので いろいろ検証した結果以下の対応としました

「(Linuxでは?)CDトラックの開始セクタが 偶数セクタ番号となっているため
libmirageに調整処理を追加して トラックの開始セクタ番号が必ず偶数となるように合わせる」

これが一番 手短な改修でした libmirage のソースコード mirage-session.c に追加しました

  91         cur_track_address += track_length;
  92 
  93 /* track start address may be even-number.
  94    I found a troube at following environment.
  95     (MIXED-MODE)
  96      track01  start=-150  length=3743(sectors)   AUDIO  (2352bytes/sector)
  97      track02  start=3593  length=45336(sectors)  BINARY (2048bytes/sector)
  98      track03  start=48929 length=2925(sectors)   AUDIO  (2352bytes/sector)
  99      ...
 100    track02(DATA) start sector is just 3593, but Linux? set ioctl() start a     ddress 3592 (-1).
 101    So, CDemu is going to access to "track01" and fails.
 102    (Because CDemu fails to access to 2352bytes/sector at current version.)
 103 */
 104         if ( cur_track_address % 2 )  cur_track_address++;
 105     }
 106 
 107     return TRUE;
 

CUEシートからトラック情報を読み取る処理 に追加し 開始セクタ位置を調整しました

以上の作業で ようやく 「アルナムの牙」が動作しました
Mednafen + CDemu(.CUE) アルナムの牙
パソコンで PC-Engineのゲームができるようになるとは いい時代です
残りの CD-ROMゲームも吸い出してしまおう