Annotation of nono/vm/syncer.cpp, revision 1.1.1.4

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.