タイマー
@uchan_nos
第14回 自作OSもくもく会
2019/01/20
時間計測の必要性
• 時計表示
• タスク切り替え
• sleep関数の実装
• 時間計測はOSを構成する基礎技術の1つ
パソコン用タイマーの種類
• PIT
• RTC
• HPET
• ACPI PM Timer
• Local APIC Timer
• TSC
PIT
• PIT: Programmable Interval Timer
• Programmable = 周期などを設定できる
• Interval = 間隔
• 「30日でできる!OS自作入門」でおなじみ
• 一定周期ごとに割り込みを発生させる
• ビープ音を鳴らす
• レガシーデバイス
RTC
• RTC: Real Time Clock
• 日時(年月日,時分秒)を扱う時計
• バッテリバックアップされている唯一のタイマー
• レガシーデバイス
HPET
• HPET: High Precision Event Timer
• High Precision = 高精度
• Event = ?(特に意味はないと思う)
• 分解能が100nsより優れていることが保証されている
• = 周波数が10MHz以上
• 割り込みを発生させられる
• レガシーデバイス(後述)
ACPI PM Timer
• PM: Power Management
• Power Management = 電源管理
• ACPI規格で定められているタイマー
• もともとはスリープ状態の時間を測るため,らしい
• でも,汎用的に使える
Local APIC Timer
• Local APIC規格で定められたタイマー
• Local = CPUコア内
• APIC = Advanced Programmable Interrupt Controller
• 各CPUコア内に存在する
→ 使用時オーバーヘッドが小さい
• 割り込みを発生させられる
TSC
• TSC: Time Stamp Counter
• 各CPUコア内に存在する
• CPUの動作クロックを数えるカウンタ
• CPUが省電力状態になっても周波数が変わらないもの
→ Invariant TSC
タイマーまとめ
名前 周波数 割り込み レガシー
PIT 1.193182MHz 可 レガシー
RTC 32.768KHz 可 レガシー
HPET 機種依存,レジスタから取得可 可 レガシー
ACPI PM Timer 3.579545MHz 不可
Local APIC Timer 機種依存,取得不可 可
TSC 機種依存,取得不可 不可
Invariant TSC 機種依存,レジスタから取得可 不可
レガシーデバイス
• Legacy = 遺産
• 現代におけるレガシーデバイスの定義(筆者独自の定義)
• 存在をシステマチックに確かめる術がないもの
• レジスタにアクセスしてみないとデバイスの有無が分からない
• 電源管理できないもの
• 省電力モードでも常に動き続ける
• この定義で行けばHPETはレガシーではないが…
HPETはレガシーデバイスか
• “N-series Intel Pentium Processors and Intel Celeron
Processors Datasheet - Volume 1 of 3”, Feb. 2016
• によれば,NシリーズのPentiumおよびCeleronでは
HPETはPCU-iLB内に存在
• PCU = Platform Controller Unit
• iLB = The Intel Legacy Block
• iLBは8259 PIC, I/O-APIC, 8254 PIT, HPET, RTCを含む
• → HPETはレガシーデバイスの仲間!
XSDTを確認してみる
• XSDT = ACPIのルートテーブル
• NシリーズCerelon搭載パソコン
• CPU: Celeron N3050
• PC名: Shuttle XS36V5
• 確かにHPETがXSDTに無い!
• ちなみにQEMUでは
FACP, APIC, HPET, BGRT が見える
筆者おすすめの組み合わせ
• 初期計時:ACPI PM Timer
• 普段使い:Local APIC Timer
• ACPI PM Timerは規格ではオプショナルだが事実上標準ぽい
• https://japan.zdnet.com/article/20365868/
• 「ACPI対応のマザーボードであれば、このタイマーが提供される。」
• Local APIC TimerはIntel SDMに載っている標準デバイス
初期計時のためのタイマー選定 1/2
• 最初から動作周波数が分かっているタイマーを使って
Local APIC Timerの周波数を測りたい
• 動作周波数が既知のタイマー
• PIT
• RTC
• HPET
• ACPI PM Timer
• Invariant TSC
• なるべく分解能が優れ,レガシーでないタイマーを選びたい
初期計時のためのタイマー選定 2/2
• レガシーでない,周波数が既知のタイマー
• ACPI PM Timer
• Invariant TSC
• Invariant TSCはレジスタから周波数を取得できるが,
BIOSの設定によってはズレることがある
• Bus Clockを変更するとズレる
• CPUによってはTSCがInvariantではない
• ということで,ACPI PM Timerを選ぶのをおすすめする
普段使いのためのタイマー選定
• 割り込みに対応したタイマーの中で,
動作オーバーヘッドが小さいものを使いたい
• 割り込みに対応したタイマー
• PIT
• RTC
• HPET
• Local APIC Timer
• この中でレガシーでないのはLocal APIC Timerのみ
• 動作オーバーヘッドが最も小さいのもLocal APIC Timer
タイマーの接続図
CPUコア
Local APIC Timer
Platform Controller Hub
ACPI PM Timer
バス
• Local APIC Timer, TSC
• CPUコア内に実装されている
• PIT, RTC, HPET, ACPI PM Timer
• CPU外に実装されている
• アーキテクチャ依存の場所
• Intel 5シリーズ以降はPCH内
TSC
RTCPIT HPET
ACPI PM Timerの使い方
UEFIで起動してからタイマーで時間を測るまでの道のり
大まかな流れ
• ACPIのXSDTを得る
• XSDTからFADTを探す
• FADTのPM_TMR_BLKレジスタを読む
• TMR_VALレジスタを読む
XSDTを得る
• ACPIのXSDTを取得する
• UEFI環境での方法は大神さんの同人誌が参考になる
• 『フルスクラッチで作る!x86_64自作OS パート2』
http://yuma.ohgami.jp/x86_64-Jisaku-OS-2/01_acpi.html
• ざっくり言うと
• EFI_SYSTEM_TABLE
• → EFI_CONFIGURATION_TABLE
• → RSDP
• → XSDT
XSDTからFADTを探す
• FADTのシグネチャは”FACP”
• テーブル名とシグネチャが異なるので注意!
• XSDTは64ビットアドレスの配列
• 順にアドレスが指す先を読んでFADTを探す
ACPI Specification, Version 6.2 Errata Aより
Sig = “FACP”である
ディスクリプタを探す
FADTのPM_TMR_BLKレジスタを読む
• FADTの位置が分かればPM_TMR_BLKレジスタを読める
• PM_TMR_BLKはFADT先頭から76バイト目
ACPI Specification, Version 6.2 Errata Aより
TMR_VALレジスタを読む
• PM_TMR_BLKはACPI PM Timer関連の
レジスタブロックを指すアドレス
• PM_TMR_BLKの説明に“System port address”とある
• メモリアドレスではなくI/Oポートのアドレスである
• movではなく,in命令で読む
• uint32_t pm_tmr_blk = *(uint32_t*)(fadt_addr + 76);
• uint32_t tmr_val = io_in32(pm_tmr_blk);
TMR_VALレジスタ
• 3.579545MHzでカウントアップし続ける
• 24ビットで1周 = 約4.69秒で1周
• 時間を測るにはこんな風にする(1秒測る例)
• uint32_t initial_val = io_in32(pm_tmr_blk);
• uint32_t target_val = initial_val + 3579545;
• While (io_in32(pm_tmr_blk) < target_val);
target_valが
24ビット以上に
ならないように
注意
Local APIC Timerの使い方
LVT Timerレジスタを設定
• LVT Timerレジスタ = FEE0 0320H
• ビット18:17でモードを設定
• 00b: One-shotモード
• 01b: Periodicモード
• 10b: TSC-deadlineモード
• 他のビットは割り込み関係の設定.
• ビット16を1にしておくと割り込みが発生しない.
Initial Countレジスタを設定
• Initial Countレジスタ = FEE0 0380H
• 0より大きな値を書くとタイマーの動作開始
• One-shotモード
• 値がCurrent Countレジスタにコピーされ,
Current Countレジスタがカウントダウンされる
• Periodicモード
• Current Countレジスタが0になると割り込み発生後,
Initial Countレジスタから値が再設定される
• 詳しくはIntel SDM Vol.3, “10.5.4 APIC Timer”
Periodicモード
Initial Countレジスタ
に値Nを書き込む
Current Countレジスタの値
0
N
時刻
割り込み 割り込み
補足: レジスタアドレスの謎
• Local APIC Timer関連のレジスタはアドレス固定
• Initial Countレジスタ = FEE0 0380H
• LVT Timerレジスタ = FEE0 0320H
• しかし,Local APIC Timerは各コア毎に存在するはず
• 同じアドレスなのに,なぜ区別できるのだろうか
CPUコア1
Initial Count
メモリバス
FEE0 0320
DDR SDRAM
EAX
mov [eax], ebx
LVT Timer
アドレスデコーダ
0003 0000EBX
FEE0 0000H
- FEE0 0400H
CPUコアN
Local APICレジスタへのアクセスは
CPUコア内部で完結する
→同じアドレスでも競合しない

Timers