仮想環境のゲストOS間でシリアル通信はできるのか実験 (VirtualBox, Parallels Desktop for Mac)

仮想環境のゲストOS間でシリアル通信はできるのか実験 (VirtualBox, Parallels Desktop for Mac)
Page content

シリアル通信どこでも開発

データ送受信をシリアルポートで行うLinux上のプログラムの開発が、カフェでもどこでも行えるように、仮想環境のゲストOS間でシリアル通信はできるのかを実験しました。結論としては通信可能です。

今回は「Ubuntu (x86_64) 上のVirtualBox」と「macOS (M1) 上のParallels Desktop for Mac」による2つの仮想環境を用意し、同じ仮想ホスト上の仮想マシン2台の間でシリアル通信を行っています。仮想マシンは1台にして自身のCOM1とCOM2を通信させるという構成も、試してはいませんが同様に可能だろうと思います。

Case-1: VirtualBox

ホスト環境がx86_64 (amd64アーキテクチャ) であれば、仮想環境の構築に私はVirtualBoxをよく用います。シリアルポートに関する公式ドキュメントは Oracle® VM VirtualBox User Manual for Release 6.0 > 3.10. Serial Ports です。

以下のように設定すると、VirtualBoxホスト上の通信ポート (例えば1234/tcp) を介して2台の仮想マシンのCOM1同士が接続されてシリアル通信できます。シリアルポート1はゲストOS (Debian GNU/Linux) から/dev/ttyS0として見えます。

  1. 仮想マシンAとBの [設定] > [シリアルポート] > [ポート1] を下記画像の内容にそれぞれ設定する
  2. 先に仮想マシンAを起動して、次に仮想マシンBを起動する
  3. ページ下部の「【共通手順】ゲストOS間のシリアル通信の確認方法」を実施する

仮想マシンA > シリアルポート > ポート1

項目内容
シリアルポートを有効化ON (チェックする)
ポートモードTCP
存在するパイプ/ソケットに接続OFF (チェックしない)
パス/アドレス例えば1234

仮想マシンB > シリアルポート > ポート1

項目内容
シリアルポートを有効化ON (チェックする)
ポートモードTCP
存在するパイプ/ソケットに接続ON (チェックする)
パス/アドレスlocalhost:1234

Case-2: macOS (M1) x Parallels Desktop for Mac

ホスト環境がmacOSでCPUがM1系のarm64アーキテクチャ (aarch64) の場合は、馴染みのVirtualBoxが利用できません。そこでM1 Macに対応したParallels Desktop for Macを用意します。シリアルポートに関する公式ドキュメントは Parallels Desktop Help > シリアルポート設定 です。

パラレルス Parallels Desktop 17 Retail Box JP(通常版)

パラレルス Parallels Desktop 17 Retail Box JP(通常版)

コーレル

以下のように設定すると、Parallels Desktopホストのソケット (UNIXドメインソケット) を介して2台の仮想マシンがシリアル通信できます。シリアルポート2はゲストOS (Debian GNU/Linux) から/dev/ttyAMA1として見えます。なお、UNIXドメインソケットとしての特殊なファイルは、macOS環境の一時ファイル$TMPDIR/ソケット名として生成されるようです。

  1. 仮想マシンAとBの [ハードウェア] でシリアルポートを下記画像の内容にそれぞれ設定する
  2. 先に仮想マシンAを起動して、次に仮想マシンBを起動する
  3. ページ下部の「【共通手順】ゲストOS間のシリアル通信の確認方法」を実施する

仮想マシンA

シリアルポート1

シリアルポート1を「+」を押して追加し、設定します。

項目内容
ソース切断済み
モード(何でも良い)
  • 追加はするが「切断済み」とする理由: Parallels DesktopゲストOSのシリアルポート1は、Debian環境においてはログインプロンプトが表示されるシリアルコンソールにデフォルトで割り当てられ、その状態だと任意のシリアル通信には使用できません。そこで、シリアルポート1をDebian環境のシリアルコンソールになってもらうだけの捨て駒として追加します。
シリアルポート2

続いてシリアルポート2を「+」を押して追加し、設定します。

項目内容
ソース新規作成するソケット (例: socket_serial2)
モードサーバ

仮想マシンB

シリアルポート1

シリアルポート1を「+」を押して追加し、設定します。

項目内容
ソース切断済み
モード(何でも良い)
シリアルポート2

続いてシリアルポート2を「+」を押して追加し、設定します。

項目内容
ソース仮想マシンA > シリアルポート2 と同じソースを選択
モードクライアント

【共通手順】ゲストOS間のシリアル通信の確認方法

仮想マシン2台のOSとして「Debian GNU/Linux 11 (bullseye)」を想定します。

$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
  1. 仮想マシンA,B) 最低限のパッケージ導入と初期ユーザに関するグループ設定を行う
su -
apt install sudo setserial minicom
adduser ユーザ名 sudo
adduser ユーザ名 dialout
exit
exit # 仮想マシンから完全にログアウトして、
ssh -l ユーザ名 仮想マシン # もう一度ログインする
  1. 仮想マシンA,B) /dev/ttyS*または/dev/ttyAMA*の存在を確認する
# VirtualBoxゲストの場合
$ ls -al /dev/ttyS*
crw-rw---- 1 root dialout 4, 64 Mar 19 20:36 /dev/ttyS0
crw-rw---- 1 root dialout 4, 65 Mar 19 20:36 /dev/ttyS1
crw-rw---- 1 root dialout 4, 66 Mar 19 20:36 /dev/ttyS2
crw-rw---- 1 root dialout 4, 67 Mar 19 20:36 /dev/ttyS3
$ setserial -g /dev/ttyS[0123]
/dev/ttyS0, UART: 16550A, Port: 0x03f8, IRQ: 4 # 認識されてそう
/dev/ttyS1, UART: unknown, Port: 0x02f8, IRQ: 3
/dev/ttyS2, UART: unknown, Port: 0x03e8, IRQ: 4
/dev/ttyS3, UART: unknown, Port: 0x02e8, IRQ: 3

# Parallels Desktopゲストの場合
$ ls -al /dev/ttyAMA*
crw--w---- 1 root tty     204, 64 Mar 20 21:12 /dev/ttyAMA0
crw-rw---- 1 root dialout 204, 65 Mar 20 21:12 /dev/ttyAMA1
$ setserial -g /dev/ttyAMA[0123]
/dev/ttyAMA0: Permission denied
/dev/ttyAMA1, UART: undefined, Port: 0x0000, IRQ: 14 # 認識されてそう
  1. 仮想マシンA,B) 両方でターミナルエミュレータminicomを次のように起動する (引数に-Hを付けると出力表示を16進表記にできる)
# VirtualBoxゲストの場合
minicom -b 9600 -D /dev/ttyS0

# Parallels Desktopゲストの場合
minicom -b 9600 -D /dev/ttyAMA1
  1. minicom内で設定変更するキー操作の例:
    • CTRL-A -> Z -> E: Local echo (echo back) のトグル
    • CTRL-A -> Z -> A: Add linefeed のトグル
  2. シリアル通信の動作確認として、仮想マシンAのminicom画面に文字を入力すると仮想マシンBのminicom画面にも文字が表示されること、かつその逆も行えることを確認する
  3. 仮想マシンA,B) minicom画面でCTRL-A -> X -> Enterを入力して終了する

ゲストOS間でシリアル通信してみた様子 (動画)

感想

VirtualBoxのゲストOS間でシリアル通信させる方法はすぐにわかったものの、Parallels Desktop for Macのほうが難産でした。公式情報の通りに仮想デバイスの設定は行っているはずなのに、ゲストOSでデバイスを認識できているのかどうかがわからず、悩むこと一日以上。まさかシリアルポートが今回のゲストOS (Debian GNU/Linux) では/dev/ttyS*ではなく/dev/ttyAMA*で見えているとは、気付きませんでした。

今回の試みは、実機やRS-232Cケーブルなどを傍らに置かずにシリアル通信のプログラムを書くのは無謀なことかも知れないですが、「ある程度は仮想環境でいけるんじゃね?」という思いつきの概念実証です。仮想環境としてはVirtualBoxとParallels Desktop for Macの他に、UTM (QEMUベース) も調査してみたものの、これはシリアルポートの設定方法がまだいまいち分かっていません。

パラレルス Parallels Desktop 17 Retail Box JP(通常版)

パラレルス Parallels Desktop 17 Retail Box JP(通常版)

コーレル

追記 [2022-03-23]

socat - Multipurpose relay (SOcket CAT)というコマンドの存在を初めて知りました。manでは次のように、双方向のバイトストリームを繋いでデータ転送を実現するユーティリティだと説明されています。様々な入出力に繋ぐことで便利に使えそうです。

Socat is a command line based utility that establishes two bidirectional byte streams and transfers data between them. Because the streams can be constructed from a large set of different types of data sinks and sources (see address types), and because lots of address options may be applied to the streams, socat can be used for many different purposes.

実際に試してみたところ、例えば本記事のCase-1: VirtualBoxで用意した仮想マシンAに関して仮想ホスト上て次のsocatコマンドを実行すると、仮想ホストの標準入力と、(VirtualBoxによって仮想ホストの1234/tcpに接続されている) 仮想マシンAの/dev/ttyS0とが繋がって通信できます。

$ socat -v -d -d - tcp:127.0.0.1:1234

ちなみに、socatを知った今根本的に考え直してみますと、本記事Case-1で扱ったシリアルポートのTCPへの変換と、本記事Case-2で扱ったシリアルポートのソケットへの変換は、そもそもVirtualBoxやParallels Desktop for Macの機能に頼らず、socatだけで実現できるものと思われます。

参考リンク