|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2021 nono project
4: // Licensed under nono-license.txt
5: //
6:
7: //
8: // スレッドを持つデバイス
9: //
10:
11: #include "thread.h"
1.1.1.3 root 12: #include "mainapp.h"
1.1 root 13: #include "mythread.h"
1.1.1.6 ! root 14: #include <cpuid.h>
! 15: #include <time.h>
! 16: #include <algorithm>
! 17:
! 18: static void PrintSuggestion(int) __unused;
1.1 root 19:
20: // コンストラクタ
1.1.1.3 root 21: ThreadDevice::ThreadDevice(uint objid_)
1.1.1.2 root 22: : inherited(objid_)
1.1 root 23: {
1.1.1.5 root 24: // SetName() はこのクラスでオーバーライドしているので
25: // ここで(改めて)実行する必要がある。
26: SetName(GetName());
1.1 root 27: }
28:
29: // デストラクタ
30: ThreadDevice::~ThreadDevice()
31: {
32: // ここで virtual の Terminate() は呼べないので、
33: // 継承側のデストラクタで TerminateThread() をそれぞれ呼ぶこと。
34: }
35:
1.1.1.5 root 36: // オブジェクト名を設定(更新)するとともにスレッド名も更新する。
37: // Object::SetName() のオーバーロード。
1.1 root 38: void
1.1.1.5 root 39: ThreadDevice::SetName(const std::string& newname)
40: {
41: inherited::SetName(newname);
42: SetThreadName(newname);
43: }
44:
45: // スレッドにつけるスレッド名を覚えておく
46: // (このスレッドにスレッド名を設定する、ではない)。
47: void
48: ThreadDevice::SetThreadName(const std::string& threadname_)
1.1 root 49: {
50: threadname = threadname_;
51: }
52:
53: // スレッド開始
54: bool
55: ThreadDevice::StartThread()
56: {
1.1.1.5 root 57: // ここまでに名前はセットしておくこと。
58: assert(threadname.empty() == false);
59:
1.1.1.6 ! root 60: // スレッド起動。
1.1 root 61: std::lock_guard<std::mutex> lock(thread_starter);
1.1.1.4 root 62: try {
1.1.1.6 ! root 63: thread.reset(new std::thread(&ThreadDevice::OnStart, this));
1.1.1.4 root 64: } catch (...) { }
1.1 root 65: if ((bool)thread == false) {
1.1.1.5 root 66: warnx("Failed to initialize thread(%s) at %s",
67: threadname.c_str(), __method__);
1.1 root 68: return false;
69: }
70: return true;
71: }
72:
1.1.1.6 ! root 73: // 開始されたスレッドでのエントリポイント。
! 74: void
! 75: ThreadDevice::OnStart()
! 76: {
! 77: // このスレッドにスレッド名を設定。
! 78: PTHREAD_SETNAME(threadname.c_str());
! 79:
! 80: // パフォーマンス計測のためのリストに追加。
! 81: // デバッガスレッドはなくてもいいか。
! 82: if (GetId() != OBJ_DEBUGGER && GetId() != OBJ_HOSTCOM_DBG) {
! 83: auto thman = gMainApp.GetThreadManager();
! 84: thman->RegistThread(this, threadname);
! 85: }
! 86:
! 87: // 実行開始。
! 88: std::lock_guard<std::mutex> lock_sub(this->thread_starter);
! 89: ThreadRun();
! 90: }
! 91:
1.1 root 92: // スレッドに終了を要求し、その終了を待つ。
93: // スレッド外から呼ぶこと。
94: void
95: ThreadDevice::TerminateThread()
96: {
97: if ((bool)thread) {
1.1.1.6 ! root 98: auto thman = gMainApp.GetThreadManager();
! 99: thman->UnregistThread(this);
! 100:
1.1 root 101: Terminate();
102:
103: if (thread->joinable()) {
104: thread->join();
105: }
106:
107: thread.reset();
108: }
109: }
1.1.1.3 root 110:
1.1.1.6 ! root 111: // このスレッドのスレッドアフィニティを設定する。
1.1.1.5 root 112: /*static*/ void
113: ThreadBase::SetThreadAffinityHint(AffinityClass hint)
1.1.1.3 root 114: {
115: #if defined(HAVE_PTHREAD_SETAFFINITY_NP) && defined(HAVE_MICPUSET)
1.1.1.6 ! root 116: std::lock_guard<std::mutex> lock(exlock);
1.1.1.3 root 117:
1.1.1.6 ! root 118: // policy, hint から cpuset を作成する。
1.1.1.3 root 119: //
1.1.1.6 ! root 120: // policy hint 動作
! 121: // ------- ----- ----
! 122: // Neutral * nop
! 123: // Binding Light set Lo (if 高性能コア数 < 3)
! 124: // Binding Light nop (if 高性能コア数 >= 3)
! 125: // Binding Heavy set Hi
! 126:
! 127: if (gMainApp.affinity_policy == MainApp::AffinityPolicy::Neutral) {
1.1.1.3 root 128: return;
129: }
130:
1.1.1.6 ! root 131: const std::vector<bool>& fastcore = gMainApp.fastcore;
! 132:
! 133: // 高性能コアが少ない場合、Light スレッドに高性能コアを奪われたくないので
! 134: // Light スレッドを低性能コアに縛っておきたい。
! 135: // 高性能コアの数が多ければ、そこまでする必要もないか。
! 136: if (hint == AffinityClass::Light) {
! 137: uint nhigh = std::count(fastcore.begin(), fastcore.end(), true);
! 138: if (nhigh >= 3) {
! 139: return;
! 140: }
! 141: }
! 142:
! 143: // ここまで来ると、このスレッドが Heavy なら高性能コアに、
! 144: // Light なら低性能コアにバインドすることが確定。
1.1.1.3 root 145: MICPUSet cset;
1.1.1.6 ! root 146: bool target = (hint == AffinityClass::Heavy);
! 147: for (uint i = 0, end = fastcore.size(); i < end; i++) {
! 148: if (fastcore[i] == target) {
! 149: cset.Set(i);
1.1.1.3 root 150: }
151: }
152:
1.1.1.6 ! root 153: // 設定。
1.1.1.3 root 154: int r = PTHREAD_SETAFFINITY(pthread_self(), cset);
155:
156: if (gMainApp.hostcpu->loglevel >= 1) {
157: std::string csetstr;
1.1.1.6 ! root 158: for (uint i = 0, end = fastcore.size(); i < end; i++) {
! 159: // 読みやすさのための区切り。先頭の1つは表示時に捨てる。
! 160: uint n = i % 16;
! 161: if (n == 0) {
! 162: csetstr += '\'';
! 163: }
! 164: if (cset.Get(i)) {
! 165: csetstr += "0123456789ABCDEF"[n];
! 166: } else {
! 167: csetstr += '_';
! 168: }
1.1.1.3 root 169: }
170: gMainApp.hostcpu->putmsgn("SetAffinity:%-12s(%s) %s%s%s",
171: PTHREAD_GETNAME().c_str(),
172: (hint == AffinityClass::Light ? "Light" : "Heavy"),
1.1.1.6 ! root 173: csetstr.c_str() + 1,
1.1.1.3 root 174: (r == 0 ? "" : " = "),
175: (r == 0 ? "" : strerror(r)));
1.1.1.6 ! root 176:
! 177: // この後のエラーメッセージより先にログスレッドが動いたら嬉しい。
! 178: usleep(1);
1.1.1.3 root 179: }
180:
181: // エラーなら errno ではなく戻り値にエラー番号が返ってくる。
1.1.1.6 ! root 182: // NetBSD では sysctl で許可しないと一般ユーザはこの機能を使えない。
! 183: if (r != 0) {
! 184: errno = r;
! 185: warn("PTHREAD_SETAFFINITY");
! 186: PrintSuggestion(r);
! 187: // 本当は終了したいが、この時点からではちょっと面倒なので放置。
! 188: // 以降は何もしない。
! 189: gMainApp.affinity_policy = MainApp::AffinityPolicy::Neutral;
1.1.1.3 root 190: }
191: #else
192: // なければ無視する。
193: #endif
194: }
1.1.1.6 ! root 195:
! 196: /*static*/ std::mutex ThreadBase::exlock;
! 197:
! 198:
! 199: //
! 200: // スレッドマネージャ
! 201: //
! 202:
! 203: // コンストラクタ
! 204: ThreadManager::ThreadManager()
! 205: : inherited(OBJ_NONE)
! 206: {
! 207: SetName("ThreadManager");
! 208: }
! 209:
! 210: // デストラクタ
! 211: ThreadManager::~ThreadManager()
! 212: {
! 213: }
! 214:
! 215: // 表示順のスレッド名
! 216: static const std::vector<const char *>
! 217: disp_order = {
! 218: "Main",
! 219: "Logger",
! 220: "SignalThread",
! 221: "VM",
! 222: "VirtIOBlock0",
! 223: "VirtIOBlock1",
! 224: "VirtIOBlock2",
! 225: "VirtIOBlock3",
! 226: "VirtIOBlock4",
! 227: "VirtIOBlock5",
! 228: "VirtIOBlock6",
! 229: "VirtIOBlock7",
! 230: "VirtIONet",
! 231: "VirtIOSCSI",
! 232: "VirtIOEntropy",
! 233: "VideoRenderer",
! 234: "SoundRenderer",
! 235: "HostNet0",
! 236: "HostNet1",
! 237: "HostCOM0",
! 238: "HostCOM1",
! 239: "HostCOM2",
! 240: "HostSound",
! 241: };
! 242:
! 243: // スレッドリストに登録する。
! 244: // 登録したいスレッド自身から呼ぶこと。
! 245: void
! 246: ThreadManager::RegistThread(Object *obj, const std::string& name)
! 247: {
! 248: // パフォーマンス計測のため自スレッドの clockid を取得。
! 249: clockid_t clockid;
! 250: pthread_getcpuclockid(pthread_self(), &clockid);
! 251:
! 252: std::lock_guard<std::mutex> lock(threads_mutex);
! 253: threads.emplace_back(obj, name, clockid);
! 254: // 都度表示順にソートする。
! 255: std::sort(threads.begin(), threads.end(),
! 256: [](const ThreadInfo& a, const ThreadInfo& b) {
! 257: auto ia = std::find(disp_order.begin(), disp_order.end(), a.name);
! 258: auto ib = std::find(disp_order.begin(), disp_order.end(), b.name);
! 259: return ia < ib;
! 260: }
! 261: );
! 262: }
! 263:
! 264: // スレッドリストから obj を削除する。
! 265: void
! 266: ThreadManager::UnregistThread(Object *obj)
! 267: {
! 268: std::lock_guard<std::mutex> lock(threads_mutex);
! 269:
! 270: for (auto it = threads.begin(); it != threads.end(); ++it) {
! 271: if ((*it).obj == obj) {
! 272: threads.erase(it);
! 273: break;
! 274: }
! 275: }
! 276: }
! 277:
! 278: // スレッドの CPU 利用率を記録する。
! 279: void
! 280: ThreadManager::CalcPerf(uint64 rtime)
! 281: {
! 282: std::lock_guard<std::mutex> lock(threads_mutex);
! 283:
! 284: for (auto& info : threads) {
! 285: struct timespec ts;
! 286: clock_gettime(info.clockid, &ts);
! 287: uint64 t = (uint64)ts.tv_sec * 1_sec + ts.tv_nsec * 1_nsec;
! 288: if (__predict_true(last_rtime != 0)) {
! 289: // 2回目以降
! 290: uint percent = (t - info.last_clock) * 100 / (rtime - last_rtime);
! 291: info.load->EnqueueForce(percent);
! 292: }
! 293: info.last_clock = t;
! 294: }
! 295: last_rtime = rtime;
! 296: }
! 297:
! 298: #define hostcpu_putmsg(lv, fmt...) do { \
! 299: if (hostcpu->loglevel >= (lv)) \
! 300: hostcpu->putmsgn(fmt); \
! 301: } while (0)
! 302:
! 303: #if defined(__x86_64__)
! 304: // x86_64 CPU の P コア、E コアを調べて fastcore 配列に返す。
! 305: // fastcore は事前にコア数分確保してあること。
! 306: // 検出できれば fastcore を更新して true を返す。
! 307: // 検出できなければ fastcore は不定で false を返す。
! 308: // 失敗ならエラーメッセージも表示する。
! 309: // MainApp から呼ばれるが、スレッド関連なのでこっちに置く。
! 310: /*static*/ bool
! 311: ThreadManager::DetectCPUAffinity_x86(std::vector<bool>& fastcore)
! 312: {
! 313: Object *hostcpu = gMainApp.hostcpu.get();
! 314:
! 315: // CPUID(0x1a, 0) は Intel Core 12世代以降でハイブリッドアーキテクチャ
! 316: // 情報を返す。eax の bit31-24 (最上位バイト) が 0x40 なら P コア、
! 317: // 0x20 なら E コア。0 ならたぶんハイブリッド構成でない CPU。
! 318: //
! 319: // まずどのコアでもいいので現在のコアで実行してみる。
! 320: // ここでハイブリッド構成だと分かれば、個別のコアについて調べるのだが、
! 321: // ただしそのためのスレッドアフィニティ指定には特権が必要な場合があるので、
! 322: // 特権を要求する作業が必要になるかどうかを先に調べておきたいということ。
! 323:
! 324: #if defined(HAVE_PTHREAD_SETAFFINITY_NP) && defined(HAVE_MICPUSET)
! 325: // まず現在のコアで。
! 326: uint32 eax, ebx, ecx, edx;
! 327: __cpuid_count(0x1a, 0, eax, ebx, ecx, edx);
! 328:
! 329: if ((eax >> 24) == 0) {
! 330: // CPU はハイブリッド構成ではない。
! 331: hostcpu_putmsg(1, "%s: Not a hybrid type core", __func__);
! 332: return false;
! 333: } else {
! 334: // CPU はハイブリッド構成なので、個別のコアについて調べる。
! 335: for (uint i = 0, end = fastcore.size(); i < end; i++) {
! 336: int r;
! 337: std::thread th([&] {
! 338: MICPUSet cset;
! 339: cset.Set(i);
! 340: r = PTHREAD_SETAFFINITY(pthread_self(), cset);
! 341: __cpuid_count(0x1a, 0, eax, ebx, ecx, edx);
! 342: if ((eax >> 24) == 0x40) {
! 343: fastcore[i] = true;
! 344: }
! 345: });
! 346: th.join();
! 347:
! 348: if (r != 0) {
! 349: errno = r;
! 350: warn("PTHREAD_SETAFFINITY");
! 351: PrintSuggestion(r);
! 352: return false;
! 353: }
! 354: hostcpu_putmsg(1, "%s: Core%u is %c",
! 355: __func__, i, (fastcore[i] ? 'P' : 'E'));
! 356: }
! 357: return true;
! 358: }
! 359: #else
! 360: hostcpu_putmsg(1, "%s: No PTHREAD_SETAFFINITY", __func__);
! 361: return false;
! 362: #endif
! 363: }
! 364: #endif // __x86_64__
! 365:
! 366: // PTHREAD_SETAFFINITY() が EPERM になった時に表示するお知らせ。
! 367: // 2箇所から呼ばれるので関数にしてある。
! 368: static void
! 369: PrintSuggestion(int errorcode)
! 370: {
! 371: #if defined(__NetBSD__)
! 372: if (errorcode == EPERM) {
! 373: fprintf(stderr,
! 374: " ** To allow non-privileged users to set thread affinity on NetBSD:\n"
! 375: " ** # sysctl -w security.models.extensions.user_set_cpu_affinity=1\n"
! 376: );
! 377: }
! 378: #endif
! 379: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.