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

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

unix.superglobalmegacorp.com

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