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

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

unix.superglobalmegacorp.com

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