KVMで`virt-install`する時に`extra-args`に指定する`console=tty0 console=ttyS0,115200n8r`とは一体何者なのか

# KVMで`virt-install`する時に`extra-args`に指定する`console=tty0 console=ttyS0,115200n8r`とは一体何者なのか

## 検証環境

```console
# cat /etc/centos-release
CentOS Linux release 7.3.1611 (Core)

# uname -r
3.10.0-514.el7.x86_64
```

## 疑問
KVMの手順で、ほとんどの場合特に何も説明せず書かれている「–extra-args="console=tty0 console=ttyS0,115200n8r"」とは一体何なのか。

## 結論
Linuxカーネルのシリアル接続用のパラメータ
https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/serial-console.rst


## そもそもttyとは
TeleTYpewriteのことで、標準入出力デバイスの事を指しています。
普段意識して使うことはあまりありませんが、機材にモニタを接続してのログイン、そして今回利用するシリアル接続でのログインの際に作成される、仮想端末の入出力に利用されています。

仮想端末は、伝統的に横80文字、縦25列のコンソール画面です。

### 取りうる名前の一式
仮想端末では以下の名称を利用します。(Xは数値)

・ttyX
・ttySX
・lpX
・ttyUSBX

tty0は今プロセスの実行ユーザーが接続している仮想端末に対するエイリアスです（つまり、利用している仮想端末が変わる可能性があります）。
ttyXは一般的なLinuxでは1~6の値が用意されており、コンソール画面で Alt+F1～F6 のボタンを押すことで利用することができます。
ttyの数は`/etc/systemd/logind.conf`の`NAutoVTs`で制御可能です。
OSによってはグラフィカルログイン画面まで起動した状態からでも、Ctrl+Alt+F1～F6 のボタンを押すことで利用することができます。
ttyS(数値)はシリアル接続用の仮想端末です。

また、lp0というパラレルポート用の仮想端末、ttyUSB0というUSB接続のシリアルデバイス用の仮想端末も用意されています。

つまり、疑問の項のtty0 ttyS0は、仮想端末、シリアルデバイス用仮想端末に対して接続するという意味です。0はプロセスが利用している仮想端末ですが、この時点では定義されていないため、適当な空いているものを利用するという意味として扱われます。

### SSH接続時のtty
SSH等の物理的な機材が接続されないリモート接続の場合、 pseudo-terminal(pts) という仮想端末が利用されます。
実際にSSH接続を行い、ttyコマンドを実行すると以下のように表示されました。
ttyコマンドは、現在利用しているコンソールに接続されている仮想端末名を出力するコマンドです。

```console
# tty
/dev/pts/0
```

## CentOSでのターミナルサービス
CentOSではTTYの機能を以下のサービスが処理して受け持っています。

* /usr/lib/systemd/system/getty@.service
* /usr/lib/systemd/system/serial-getty@.service

上記のサービスはOSの起動の際に呼び出され、以下のようにCPUやメモリ等の実機材とOSの橋渡しを担当することで、我々はPCの操作を行うことができています。

* 物理的なキーボード等
    * [実機材] - ttyX - [getty] - [入出力機材]
* SSH, telnet等
    * [実機材] - pts* - [sshd, telnetd] - [入出力機材]

### gettyの初期パラメータ
今回の環境では、以下のようにパラメータが指定されています。

#### /usr/lib/systemd/system/serial-getty@.service

```ini
[Unit]
Description=Serial Getty on %I
Documentation=man:agetty(8) man:systemd-getty-generator(8)
Documentation=http://0pointer.de/blog/projects/serial-console.html
BindsTo=dev-%i.device
After=dev-%i.device systemd-user-sessions.service plymouth-quit-wait.service
After=rc-local.service

Before=getty.target
IgnoreOnIsolate=yes

[Service]
ExecStart=-/sbin/agetty --keep-baud 115200,38400,9600 %I $TERM
Type=idle
Restart=always
UtmpIdentifier=%I
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
KillMode=process
IgnoreSIGPIPE=no
SendSIGHUP=yes

[Install]
WantedBy=getty.target
```

ExecStart行では、以下のとおり設定が行われております。これは、対応する後述のボーレートを指定しています。

```
115200,38400,9600
```

virsh-installを行う際は、CentOS7では115200,38400,9600である必要があります。
これ以外の値では、仮想シリアルポートは想定する値と違う通信速度となり、serial-gettyサービスに接続することができず、仮想端末は正常に動作しません。


## 実際の挙動の確認
今回、SSHで複数の接続を行います。それぞれのttyコマンドの実行結果は以下のとおりです。

* SSH接続1

```console
# tty
/dev/pts/0
```

* SSH接続2

```console
# tty
/dev/pts/1
```

この状態で、SSH接続1から以下のコマンドを実行します。

```console
# echo "ssh1 hello" > /dev/pts/1
```

すると、SSH接続2の標準入力に以下の内容が表示されます。

```console
ssh1 hello
```

一般的にはプロセスによる標準エラー出力の際に見ることが多いと思われますが、ttyに対する標準入出力が行われるため、標準エラー出力による出力と同様にコンソールに接続している人に対して文字が出力されることが確認できます。


## 結局後ろの数値は何なのか
シリアル接続の際は、実際の機材によって対応している接続方法が違います。
そのため、どういう実デバイスなのかを指定するための、調整用のパラメータも併せて定義します。

パラメータの書式は以下のとおりです。

```
<ボーレート><パリティ方式><データビット数><フロー制御タイプ>
```

つまり、冒頭の115200n8rというのは、以下の意味に分解することができます。

* ボーレート
    * 115200
* パリティ方式
    * n
* データビット数
    * 8
* フロー制御
    * r

### ボーレート
ボーレートは、1秒間に行える変復調数を表しており、物理的に送信可能な信号数の数です。
実機材としてはRS-232規格が想定されており、お互いの端末が1秒あたりの信号の密度を正しく認識できるよう利用可能なビットレートを定義します。

Linuxカーネルでは以下の速度に対応しています。

* 1200
* 2400
* 4800
* 9600
* 19200
* 38400
* 57600
* 115200


### パリティ方式
パリティ方式は以下の三パターンが用意されています。

* n 
    * パリティなし
* e
    * 1ビット偶数パリティ
* o
    * 1ビット奇数パリティ

偶数パリティでは1の個数が偶数になるようにパリティビットを、奇数パリティでは1の個数が奇数になるようパリティビットを付与します。パリティビットなので、誤り検知にのみ利用可能です。データ修復機能はありません。

### データビット数
7か8を指定できます。

7というのはASCII文字を表現可能な最小限のビット数です。実用的には8の1バイトが一般的です。

### フロー制御タイプ
フロー制御タイプは以下のニパターンが設定可能です。

* <指定なし>
    * フロー制御なし
* r
    * RTSフロー制御

RTS(Request To Send)フロー制御を有効にすることで、受け取り側の受信バッファに余裕があれば送信するようフロー制御を行えます。



## まとめ
実際の挙動として、`virt-install`の際と、インストール後コンソールを操作する際の`virsh console`はシリアル接続での接続をおこないます。
そのため、`virsh console`だけを使うという観点では、console=tty0はあまり意味のない値です。

virt-install時にextra-argsで指定することで、今回の場合だとGRUBに対して以下のように自動的にシリアル接続用のパラメータがセットされます。

#### /etc/default/grub

```
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb console=tty0 console=ttyS0,115200n8r"
GRUB_DISABLE_RECOVERY="true"
```

GRUB_CMDLINE_LINUXのパラメータを利用することで、GRUBにおいてもシリアルコンソールが有効化され、`virsh console`の仮想シリアルポートはqemu->grub->gettyという経路で接続されます。

結果として、`virsh console`の際の接続にはconsole=ttyS0,115200n8rが利用され、仮想マシンのシリアルポートに対して1秒間に115200ビット、パリティなし、データ長8bitでの接続を行い、RTS制御を行います。
115200なのは、取りうる最大の速度のためです。

GRUBの設定を行わず、`serial-getty@`サービスのみを起動した場合でもシリアルコンソールの操作は可能です。
ただし、GRUBのブートメッセージが確認できないため、電源オン～ログインプロンプトの表示までの間に何らかの問題が生じた場合、確認することができなくなります。