PPS使用ntpdのjitterを改善する

GPSのPPSを用いた、Stratum 1のNTPサーバ。
CPUがだいぶ古かったので、新しくしたら、何故かjitterが(当然offsetも)大幅に悪化。
こんなはずでは…。

# 環境
- OS
    - Arch Linux
    - kernel 4.8〜4.9 (調査中にupdateしちゃった)
- GPS/PPS/ntpd周辺 (旧環境・新環境共通)
    - GPSは、Garmin GPS 18x LVC [Firmware version 4.00](https://www8.garmin.com/support/download_details.jsp?id=4055)
    - PPSは、RS-232CのDCDに接続
    - RS-232Cは、USB-シリアル変換経由ではなく、マザーボード上のもの (Novoton?)
    - ntpdは、[timepps.h](http://www.linuxpps.org/gitweb/?p=pps-tools;a=blob;f=timepps.h;h=d2628d2d061ea2a3623e57990d9ada62623773cf;hb=HEAD)を/usr/includeにコピーしてリビルドし、PPS対応に
    - `/etc/ntp.conf`での時刻源は、[Generic NMEA](https://www.eecis.udel.edu/~mills/ntp/html/drivers/driver20.html)
    - PPSは、`# ldattach PPS /dev/ttyS0`で、`/dev/pps1`を作成 (`/dev/pps0`はNICがPTP向けに使用)
- 旧環境
    - Pentium Dual-Core E2200
    - Intel DG965SS
    - jitterは平均1usec未満
    - offsetは数usec (環境温度に影響される)
- 新環境
    - Core i7-3770
    - ASUS B75M-PLUS
    - jitterが数百usecというところからスタート

# 自分の環境で効いた、改善ネタ
## ntpdのniceオプション
ntpdの起動オプションに`-N`を追加する。[ntpdのソース](https://github.com/ntp-project/ntp/blob/stable/ntpd/ntpd.c)を見ると、`SCHED_FIFO`が設定される模様。設定したら、`top`でも`PR`が`rt`と表示された。
これは効果があった(記憶では)。ざっくり、数百usec->数usec(記憶では)。
OSのアップデート等と混ぜてしまったり、旧環境との比較の記録等がなく、正確な比較はできていないが…。
ntpdが優先されて困りそうなものもないし、設定してしまう。

### `-N`オプションの追加方法
Arch Linuxのsystemdの場合。
1. `/usr/lib/systemd/system/ntpd.service`の`ExecStart`に、`-N`を追加
2. `# systemctl daemon-reload`
3. `# systemctl restart ntpd.service`

## C state
これに気づくのが大変だった。
ArchWikiの[周波数スケーリング](https://wiki.archlinuxjp.org/index.php/CPU_%E5%91%A8%E6%B3%A2%E6%95%B0%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0)の記事を見ながら、`cpupower`に次いで、ふと`i7z`を実行したところ、99%、C6 stateに居る。
C6からの割り込み応答時間は、ぱっと探したところではわからなかったが、C7からで110usecという[記事](http://www.4gamer.net/games/132/G013298/20130601003/)を発見。C6からでも10数usecはかかりそうな予感。古いCPUには無いstateと考えると、辻褄も合う。
完全省電力Offはいまいちなので、復帰が一瞬と思われる、C1までを使うように設定する。
なお、BIOS設定は無視されるとの[情報](http://www.torutk.com/projects/swe/wiki/LinuxKernel)もある。
Raspberry Piなら、CPUが違うので、関係ないね。

### GRUBでの設定方法
Arch Linuxで実験。ArchWikiの[カーネルパラメータ](https://wiki.archlinuxjp.org/index.php/%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF)参照。
1. `/etc/default/grub`の`GRUB_CMDLINE_LINUX_DEFAULT`に、`intel_idle.max_cstate=1`を追記
2. `# grub-mkconfig -o /boot/grub/grub.cfg`
3. 再起動

# `intel_idle.max_cstate=1`の効果を、図で
![ntp_offset-day.png](https://qiita-image-store.s3.amazonaws.com/0/104225/6f6aca85-258d-72d1-faaa-df2ba516ae61.png)

ちなみに、UPSのloadに変化は見られなかった。

# その他、ググった改善ネタ

## ASYNC_LOW_LATENCY
シリアルポートには、low latencyに設定するフラグがある。ただ、カーネルのソースを`ASYNC_LOW_LATENCY`で検索すると、USB-シリアル変換などでは効果がありそうに見えたが、16550Aドライバでの扱いは、よくわからなかった。
おまじない程度に、やっておくかな。

- `# setserial /dev/ttyS0 low_latency`

[linuxppsの例](http://linuxpps.org/wiki/index.php/LinuxPPS_NTPD_support)では、udevのrulesの中で、`setserial`している。

## PPS Kernel Discipline
PPSを、Kernel Disciplineで使う。詳細は[こちら](http://www.ntp.org/ntpfaq/NTP-s-algo-kernel.htm)。
Raspberry Piで成功してる方もいるようだが、やってみたところ、msオーダー(?)に悪化。
`dmesg`で`hardpps`と表示はされたものの、自分のやり方が根本的に間違っている気がする。
`ppsctl`のオプションを変えたりしたが、結果はあまり変わらなかった…。

### 試した内容 (間違っている気がする)
1. kernelのconfigで、`CONFIG_NO_HZ=y`をやめ、`CONFIG_HZ_PERIODIC=y`を選択。
2. [kernelを再コンパイル](https://wiki.archlinuxjp.org/index.php/%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB/Arch_Build_System)後、再起動。
3. [こちら](https://github.com/ago/pps-tools)からpps-toolsの`ppsctl`を用意
4. `# ppsctl -b -f -a /dev/pps1`
5. `/etc/ntp.conf`の`fudge 127.127.20.0`の`flag3`を`1`に
6. `# systemctl restart ntpd.service`

## CPU governor
CPUクロックを高クロックで固定する。
`# cpupower frequency-set -g performance`で設定できそうだが、ぶんまわしたくないので、未確認。

# 宿題
- 一瞬、J3455でもやってみたが、`-N`だけでも、offsetは10usec前後、jitterは1usecくらいだったんだよなぁ(記憶では)。C stateはどこに居たんだろ。Atom系のC sataeは[議論](https://bugzilla.kernel.org/show_bug.cgi?id=109051)があったようだが、詳細未確認。
- Kernel Disciplineは、理解未達。
- Raspberry Piでも試したいが、まずPCでやっているのは、PTP対応NICが使えるから。

# 一言
[ArchWiki](https://wiki.archlinuxjp.org/index.php/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8)が発想の源となった。感謝。

# 参考文献
[GPSD Time Service HOWTO](http://www.catb.org/gpsd/gpsd-time-service-howto.html)