|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2022 nono project
4: // Licensed under nono-license.txt
5: //
6:
7: //
8: // 実時間同期
9: //
10:
11: #include "sync.h"
12: #include "bus.h"
13: #include "mainapp.h"
14: #include "ram.h"
15: #include "scheduler.h"
16:
17: //#define DEBUG
18:
19: #if defined(DEBUG)
20: #define DPRINTF(fmt...) printf(fmt);
21: #else
22: #define DPRINTF(fmt...) /**/
23: #endif
24:
25: // グローバル参照用
26: Sync *gSync;
27:
28: // パフォーマンス測定を行う実時間間隔
29: static constexpr uint64 PERF_INTERVAL = 1000_msec;
30:
31: // コンストラクタ
32: Sync::Sync()
33: : inherited("Sync")
34: {
35: for (int i = 0; i < perfq.Capacity(); i++) {
36: perfq.Enqueue(1000);
37: }
38:
39: sync_event.func = ToEventCallback(&Sync::SyncCallback);
40: sync_event.time = 50_msec;
41: sync_event.Regist("Sync");
42:
43: gScheduler->ConnectMessage(MessageID::SCHEDULER_SYNCMODE, this,
44: ToMessageCallback(&Sync::ChangeModeCallback));
45: }
46:
47: // デストラクタ
48: Sync::~Sync()
49: {
50: gSync = NULL;
51: }
52:
53: // 初期化
54: bool
55: Sync::Init()
56: {
57: mode = SCHED_POWEROFF;
58:
59: overslept = 0;
60:
61: // 起動時引数指定の高速モード
62: RequestFullSpeed(gMainApp.fast_mode);
63:
64: return true;
65: }
66:
67: void
68: Sync::StartRealTime()
69: {
70: realtime.Start();
71: }
72:
73: void
74: Sync::StopRealTime()
75: {
76: realtime.Stop();
77: }
78:
79: // 時間の始まり
80: void
81: Sync::StartTime()
82: {
83: gScheduler->StartEvent(sync_event);
84:
85: // 諸々の初期化が終わった頃にタイマーを開始。
86: // Stopwatch.Restart() は .Net 由来なので
87: // Restart() は経過時間をリセットしてからタイマーを開始の意。
88: realtime.Restart();
89:
90: rtime = realtime.Elapsed();
91: rtime_epoch = rtime;
92: vtime_epoch = gScheduler->GetVirtTime();
93:
94: // パフォーマンス表示用。
95: // 最初の集計が行われる起動後1秒までは、この値が表示されるので
96: // インチキして 100% にしておく。
97: perf_counter = 100;
98: last_perf_rtime = rtime_epoch;
99: last_perf_vtime = vtime_epoch;
100: next_perf_rtime = PERF_INTERVAL;
101: }
102:
103: // CPU 状態変更(通常/STOP/HALT)を指示する。
104: // CPU が呼ぶはずなので自スレッドからのみ呼ばれるはず。
105: void
106: Sync::RequestCPUMode(uint32 cpumode)
107: {
108: uint32 newmode;
109:
110: newmode = (mode & ~SCHED_CPU_MASK) | cpumode;
111:
112: ChangeMode(newmode);
113: }
114:
115: // 高速モード/同期モードの変更を指示する。
116: // true なら高速モード、false なら同期モード。
117: // 他スレッドから呼んでもよい。
118: void
119: Sync::RequestFullSpeed(bool enable)
120: {
121: gScheduler->SendMessage(MessageID::SCHEDULER_SYNCMODE, enable);
122: }
123:
124: // ブートページモードの変更を指示する。
125: // (VM::SwitchBootPage から呼ぶので個別には呼ばないこと)
126: void
127: Sync::RequestBootPageMode(bool isrom)
128: {
129: uint32 newmode;
130:
131: if (isrom) {
132: newmode = mode | SCHED_BOOT;
133: } else {
134: newmode = mode & ~SCHED_BOOT;
135: }
136:
137: ChangeMode(newmode);
138: }
139:
140: // キー入力状態の変更を指示する。
141: void
142: Sync::RequestKeyPressed(bool pressed)
143: {
144: uint32 newmode;
145:
146: if (pressed) {
147: newmode = mode | SCHED_KEY;
148: } else {
149: newmode = mode & ~SCHED_KEY;
150: }
151:
152: ChangeMode(newmode);
153: }
154:
155: // 電源状態の変更を指示する。
156: void
157: Sync::RequestPowerOffMode(bool poweroff)
158: {
159: uint32 newmode;
160:
161: if (poweroff) {
162: newmode = mode | SCHED_POWEROFF;
163: } else {
164: newmode = mode & ~SCHED_POWEROFF;
165: }
166:
167: ChangeMode(newmode);
168: }
169:
170: // 動作状態変更のメッセージコールバック
171: void
172: Sync::ChangeModeCallback(MessageID msgid, uint32 arg)
173: {
174: uint32 newmode;
175:
176: if (msgid == MessageID::SCHEDULER_SYNCMODE) {
177: if (arg) {
178: newmode = mode & ~SCHED_SYNC;
179: } else {
180: newmode = mode | SCHED_SYNC;
181: }
182:
183: ChangeMode(newmode);
184: }
185: }
186:
187: // メンバ変数 mode_request に従ってスケジューラの動作状態を変更。
188: // 自スレッドから呼ぶこと。
189: void
190: Sync::ChangeMode(uint32 newmode)
191: {
192: // いずれかでも変化していれば..
193: if (mode != newmode) {
194: // 動作モード変更要因を全部調べた結果、変更が必要か。
195: // mode、newmode どちらもゼロ(高速モード)か非ゼロ(通常モード)か
196: // なので、等値比較ではなく二値にしてから比較。
197: if ((bool)mode != (bool)newmode) {
198: // モードが変わるのでここで一旦同期をとる
199: DoSync();
200:
201: if (newmode == 0) {
202: // 高速モードに
203: putlog(2, "高速モード");
204:
205: // 高速モードでは RAM は直接アクセス
206: ::direct_ram_size = gRAM->GetSize();
207:
208: } else {
209: // 同期モードに
210: putlog(2, "同期モード");
211:
212: // 同期モードでは RAM アクセスはデバイス経由
213: ::direct_ram_size = 0;
214:
215: // 同期用の基準点を設置。
216: // ゲストが STOP 命令による定常状態にいる時は
217: // 割り込みなど短期間だけ高速モードになるのを繰り返す。
218: // スリープ粒度の粗い NetBSD ホストでは同期モードに
219: // 切り替えるたびに基準点を移動していると、スリープ
220: // しすぎてしまうので、実時間と仮想時間のズレが小さい場合は
221: // 基準点を移動せず無視する。
222: uint64 vtime = gScheduler->GetVirtTime();
223: uint64 rspan = rtime - rtime_epoch;
224: uint64 vspan = vtime - vtime_epoch;
225: if (vspan > rspan + overslept) {
226: rtime_epoch = rtime;
227: vtime_epoch = vtime;
228: }
229: }
230: }
231:
232: // ブール比較の後で、全ビットを維持したまま代入。
233: mode = newmode;
234: }
235: DPRINTF("ChangeMode end mode=$%x\n", mode);
236: }
237:
238: // 同期イベント
239: void
240: Sync::SyncCallback(Event& ev)
241: {
242: DPRINTF("SyncCallback\n");
243: DoSync();
244:
245: // 次回のイベントを再登録
246: gScheduler->StartEvent(ev);
247: }
248:
249: // 同期処理
250: // ここで仮想時間を実時間と同期させる。
251: // スリープしすぎた実時間を返す。
252: void
253: Sync::DoSync()
254: {
255: // 実時間をここで更新
256: rtime = realtime.Elapsed();
257: DPRINTF("Sync begin rtime=%s\n", SecToStr(rtime).c_str());
258:
259: // 統計情報
260: sync_count++;
261: // XXX どこでやる?
262: if (__predict_false(rtime >= next_perf_rtime)) {
263: CalcPerf();
264: }
265:
266: // XXX 次回 Start する時のイベント間隔をここ(DoSync())で事前に決めている
267: sync_event.time = 50_msec;
268:
269: // 同期モードなら..
270: if (mode != 0) {
271: uint64 vtime = gScheduler->GetVirtTime();
272: uint64 rspan = rtime - rtime_epoch;
273: uint64 vspan = vtime - vtime_epoch;
274: if (vspan > rspan) {
275: // 仮想時間のほうが進んでいれば、スリープして待つ
276: uint64 diff = vspan - rspan;
277: uint64 prev = rtime;
278:
279: DPRINTF("Sync wait_for=%s\n", SecToStr(diff).c_str());
280: gScheduler->Sleep(diff);
281:
282: // スリープしすぎた時間を計測。
283: // この時間にはスリープ時間を計算するための時間も含む。
284: rtime = realtime.Elapsed();
285: DPRINTF("Sync wakeup rtime=%s\n", SecToStr(rtime).c_str());
286: uint64 realslept = rtime - prev;
287: if (realslept > diff) {
288: uint64 over = realslept - diff;
289: if (over > overslept) {
290: overslept = over;
291: }
292: } else if (realslept < diff) {
293: uint64 under = diff - realslept;
294: if (under > overslept) {
295: sync_event.time = 0;
296: }
297: }
298: } else {
299: // 実時間のほうが進んでいれば、間に合ってない
300:
301: // XXX ここで間引き運転とか
302: }
303: }
304: }
305:
306: void
307: Sync::CalcPerf()
308: {
309: uint64 vtime = gScheduler->GetVirtTime();
310: uint64 rperf = rtime - last_perf_rtime;
311: uint64 vperf = vtime - last_perf_vtime;
312:
313: // 百分率で小数以下1桁分までとなるよう整数で計算する
314: perfq.EnqueueForce((uint32)(vperf * 1000 / rperf));
315: // 移動平均を求める (perfq は常に要素数分埋めてある)
316: int ma = 0;
317: int qlen = perfq.Length();
318: __assume(qlen > 0);
319: for (int i = 0; i < qlen; i++) {
320: auto v = perfq.Peek(i);
321: ma += v;
322: }
323: ma /= qlen;
324: // 最下位を四捨五入して捨てる (整数%になる)
325: ma = (ma + 5) / 10;
326: perf_counter = ma;
327:
328: // 直近1秒の Sync 回数
329: last_sync_count = sync_count;
330: sync_count = 0;
331:
332: last_perf_rtime = rtime;
333: last_perf_vtime = vtime;
334: next_perf_rtime = rtime + PERF_INTERVAL;
335: }
336:
337: // パフォーマンス測定用に実時間をログ出力
338: void
339: Sync::PutlogRealtime()
340: {
341: uint64 vtime = gScheduler->GetVirtTime();
342:
343: // 電源オン時からここまでの時間を表示するだけでリセット等はしない。
344: putlog(0, "電源オンから %d.%03d 秒経過 %6.2f%%",
345: (int)(rtime / (1000 * 1000 * 1000)),
346: (int)((rtime / (1000 * 1000)) % 1000),
347: (double)vtime / rtime * 100);
348:
349: #if 0
350: // イベントキューの性能評価用
351: uint64 sum = 0;
352: for (const auto e : events) {
353: sum += e->count;
354: }
355: putlog(0, "%lu events done, %6.3fM/sec", sum, (double)sum / rtime * 1000);
356: #endif
357: }
358:
359: // モニタ更新の下請け (Scheduler から呼ばれる)
360: int
361: Sync::MonitorUpdateSub(TextScreen& screen, uint64 vtime)
362: {
363: int y = 0;
364:
365: screen.Print(0, y, "Mode: 0x%04x (", mode);
366: switch (mode & SCHED_CPU_MASK) {
367: case SCHED_CPU_NORMAL:
368: screen.Puts(14, y, TA::Off, "Run");
369: break;
370: case SCHED_CPU_STOP:
371: screen.Puts(14, y, TA::On, "STOP");
372: break;
373: case SCHED_CPU_HALT:
374: screen.Puts(14, y, TA::On, "HALT");
375: break;
376: }
377: if ((mode & SCHED_SYNC)) {
378: screen.Puts(19, y, TA::On, "Sync");
379: } else {
380: screen.Puts(19, y, TA::Off, "Full");
381: }
382: screen.Puts(24, y, TA::OnOff((mode & SCHED_BOOT)), "Boot");
383: screen.Puts(29, y, TA::OnOff((mode & SCHED_KEY)), "Key");
384: screen.Puts(34, y, TA::OnOff((mode & SCHED_POWEROFF)), "POff");
385: screen.Puts(38, y, ")");
386: y++;
387: uint64 rt = rtime;
388: uint64 vt = vtime;
389:
390: auto s_rt = TimeToStr(rt);
391: auto s_vt = TimeToStr(vt);
392: std::string s_sync_rt {};
393: std::string s_sync_vt {};
394: if (mode != 0) {
395: uint64 sync_rt = rtime - rtime_epoch;
396: uint64 sync_vt = vtime - vtime_epoch;
397: s_sync_rt = TimeToStr(sync_rt);
398: s_sync_vt = TimeToStr(sync_vt);
399: }
400: // 日まで表示しようとするときは桁位置を動かす
401: int width = std::max(rt, vt) < 86400_sec ? 20 : 25;
402:
403: screen.Print(0, y++, "Total Real Time: %*s", width, s_rt.c_str());
404: screen.Print(4, y++, "Virtual Time: %*s", width, s_vt.c_str());
405: screen.Print(0, y++, "Sync Real Time: %*s", width, s_sync_rt.c_str());
406: screen.Print(4, y++, "Virtual Time: %*s", width, s_sync_vt.c_str());
407:
408: screen.Print(0, y++, "Performance : %4d%%", perf_counter);
409:
410: int x = 52;
411: int y1 = 0;
412: screen.Print(x, y1++, "Max OverSlept: %13s", SecToStr(overslept).c_str());
413: screen.Print(x, y1++, "Sync per sec : %4u", last_sync_count);
414:
415: return y;
416: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.