CPUを自作したりコンピューターアーキテクチャを理解するためにおすすめの本の一覧

hikaliumの独断と偏見で、積読は除いている。最近も結構新しい本が色々出ているので、それもいいかもしれないが、ある程度評価の定まった本を探したい場合に参考になれば。

ちなみに、hikaliumがセキュキャンでCPU自作を教えていたときのコードはここにある。参考にならないかもしれないが、おまけにどうぞ。

github.com

ディジタル回路設計とコンピュータアーキテクチャ

ハードウエア記述言語で実際にCPUをつくりながら、各アーキテクチャについても学べる良書。

MIPS版が広く知られているが、ARM版、RISC-V版も登場している。無印版はよくある技術書サイズだが、ARMとRISC-V版は大型本なので、そこらへんの好みとかも勘案するとよいかもしれない。

CPUの創り方

表紙がメイドさんだが、侮ることなかれ。(と私は中学生の時にクラスの人々の前で言いました。)

電子工作初心者の人々にもわかりやすく、回路素子の基本的な扱い方や、データシートの読み方も教えてくれる、実装寄りの良書。

74HCシリーズのICが手に入りにくいという現代的な事情はあるが、等価な回路を上の本などを参考にしつつVerilog-HDLで記述したりすれば、FPGAボードひとつでも等価なものを十分につくることができる。

RISC-V原典 オープンアーキテクチャのススメ

きれいなアーキテクチャで知られるRISC-Vの設計思想の背景や、どうしてその設計が選択されたのか、という思考の背後を知ることができる本。もう少し高いレベルからCPUのことを知りたかったり、RISC-Vについて知りたい場合には役立つ。

コンピュータアーキテクチャ 定量的アプローチ

通称ヘネパタ。大学でこの本を参考にキャッシュの特性を調べるグラフを描画する課題が出て、面白いなあと思った記憶がある。

めちゃくちゃ教科書的な内容なので、読むのは大変だし最初から最後まで読むタイプの本ではないが、持っておいて損はない。ペラペラめくったり目次をみて、興味のあるところや、参考になりそうなところを読む、辞書的な使い方をするのがよい。

作りながら学ぶコンピュータアーキテクチャ

上の方の本の翻訳にも携わっておられる、天野先生の著書。他の本では忘れられがちな、割り込み処理の実装方法が記述されているとネットで聞いて自分も購入した。

絶版本なので手に入れるのは少し大変。図書館などにあるかどうか、確認してみるのも手かもしれない。

上の分厚い辞書みたいな本たちにくらべたら、薄くて読みやすそうな見た目をしているので、一見の価値はある。

FPGAの原理と構成

いま気づいたがこれも天野先生の著書じゃん。おそらくCPU自作をはじめる最も手軽な方法はFPGAを用いることだが、じゃあFPGAって一体どうやって動いているの?というところの疑問を解決してくれる本。これも小さめ技術書によくあるサイズ感なので、電車の中でも読みやすいのでおすすめ。

Chromebookの開発者モードへの遷移をちょっと追う

某ブログに触発されて、公開情報からChromiumOSの気になるところを追う軽い記事でも書こうかなという気持ちに急になったので書いていく。正確さも何も保証しないし、公開情報しか載っていないのであしからず。公開情報であることを示すために出典へのリンクをいっぱい張ります。

Chromebookには開発者モードというunsecureなモードがあって、これを使うと自作OSをブートさせたり色々できて楽しい。それに入るためには、リカバリモードに入ってCtrl-Dを押してEnterを押すという作業が通常必要なわけだが、これで何が起きるのかちょっと追いかけてみよう。

まず、ChromebookBIOSでもUEFIでもないファームウエアを採用している。(一部例外はある。)これはverified bootとかvbootとか呼ばれていて、このあたりにいい感じの図がある。情報が最新の状態に更新されているかは謎だが…(斜め上に視線を動かす)

あと、ここのページにファームウエアまわりの情報が載っていて、こんなかっこいい一文が書いてある。

The Chrome OS firmware is always verified as signed by Google

いいはなし。Root of Trustってやつだね。

さて、このページの下の方に、H2C (UEFI-based), U-boot, Coreboot, とChromebookで使われていた歴代ファームウエアの名前が並び、それに続いてdepthchargeってやつが新し目のChromebookだと使われているぞ!と書いてある。へえ〜!

そこからこんなスライドへもリンクが飛んでいるが、なんと2014年である。まあつまるところ、多分近頃のChromebookはみんなdepthchargeを使ってるんじゃないかな?(適当)

さて、そんなわけで、depthchargeのコードはここにあるわけだが、まずREADMEがない。なるほど…?

ちなみに"depthcharge chromiumos"でググる無機質なGitilesというシステムのページに飛ばされるが、ここは検索機能とかがないので、便利なChromium Code Searchを使おう!超便利だよ!(いつもお世話になっているので頭が上がらない)

さて本題に戻って、とりあえずdepthcharge配下で"recovery"とかの単語で調べると、src/platform/depthcharge/src/vboot/ui.hというファイルがヒットする。

/* Screens. */
enum ui_screen {
    /* Wait screen for EC sync and AUXFW sync */
    UI_SCREEN_FIRMWARE_SYNC             = 0x100,
    /* Broken screen */
    UI_SCREEN_RECOVERY_BROKEN           = 0x110,
    /* Advanced options */
    UI_SCREEN_ADVANCED_OPTIONS          = 0x120,
    /* Language selection screen */
    UI_SCREEN_LANGUAGE_SELECT           = 0x130,
    /* Debug info */
    UI_SCREEN_DEBUG_INFO                = 0x140,
    /* Firmware log */
    UI_SCREEN_FIRMWARE_LOG              = 0x150,
    /* First recovery screen to select recovering from disk or phone */
    UI_SCREEN_RECOVERY_SELECT           = 0x200,
    /* Invalid recovery media inserted */
    UI_SCREEN_RECOVERY_INVALID          = 0x201,
    /* Confirm transition to developer mode */
    UI_SCREEN_RECOVERY_TO_DEV           = 0x202,
    /* Recovery using phone */
    UI_SCREEN_RECOVERY_PHONE_STEP1          = 0x210,
    UI_SCREEN_RECOVERY_PHONE_STEP2          = 0x211,
    /* Recovery using disk */
    UI_SCREEN_RECOVERY_DISK_STEP1           = 0x220,
    UI_SCREEN_RECOVERY_DISK_STEP2           = 0x221,
    UI_SCREEN_RECOVERY_DISK_STEP3           = 0x222,
    /* Developer mode screen */
    UI_SCREEN_DEVELOPER_MODE            = 0x300,

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/depthcharge/src/vboot/ui.h;l=198-226;drc=3eda3d7bd3195f6d8ff59954e6b2967e42201bcb

UI_SCREEN_RECOVERY_TO_DEVとか、いかにもそれっぽい名前なので、今度はそれで検索すると、src/platform/depthcharge/src/vboot/ui.cというファイルが見つかる。まさに特定のキーを押したときに画面遷移するコードが書いてある!

 /* Manual recovery keyboard shortcuts */
    if (ui->key == UI_KEY_REC_TO_DEV ||
        (CONFIG(DETACHABLE) &&
         ui->key == UI_BUTTON_VOL_UP_DOWN_COMBO_PRESS)) {
        return ui_screen_change(ui, UI_SCREEN_RECOVERY_TO_DEV);
    }

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/depthcharge/src/vboot/ui.c;l=39-44;drc=fcfa6efb8f221877e771a7bc382267a3ebcdee48

ちなみにここに書いてあるとおり、キーボードが外れるタイプのデバイス(DETACHABLE)だと、音量キーの同時押しでdevmodeに入れることがわかる。(もちろんドキュメントに書いてあるよ!)

さて、じゃあこの画面の遷移した先を見に行こう。

src/platform/depthcharge/src/vboot/ui/screens.c

static vb2_error_t recovery_to_dev_init(struct ui_context *ui)
{
    if (ui->ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) {
        /* Notify the user that they are already in dev mode */
        UI_WARN("Already in dev mode\n");
        return set_ui_error_and_go_back(
            ui, UI_ERROR_DEV_MODE_ALREADY_ENABLED);
    }

    if (!CONFIG(PHYSICAL_PRESENCE_KEYBOARD) &&
        ui_is_physical_presence_pressed()) {
        UI_INFO("Physical presence button stuck?\n");
        return ui_screen_back(ui);
    }


    if (CONFIG(PHYSICAL_PRESENCE_KEYBOARD)) {
        ui->state->selected_item = RECOVERY_TO_DEV_ITEM_CONFIRM;
    } else {
        /*
         * Disable "Confirm" button for other physical presence types.
         */
        UI_SET_BIT(ui->state->hidden_item_mask,
               RECOVERY_TO_DEV_ITEM_CONFIRM);
        ui->state->selected_item = RECOVERY_TO_DEV_ITEM_CANCEL;
    }

    ui->physical_presence_button_pressed = 0;

    return VB2_SUCCESS;
}

static vb2_error_t recovery_to_dev_finalize(struct ui_context *ui)
{
    UI_INFO("Physical presence confirmed!\n");

    /* Validity check, should never happen. */
    if (ui->state->screen->id != UI_SCREEN_RECOVERY_TO_DEV ||
        (ui->ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) ||
        ui->ctx->boot_mode != VB2_BOOT_MODE_MANUAL_RECOVERY) {
        UI_ERROR("ERROR: Dev transition validity check failed\n");
        return VB2_SUCCESS;
    }

    UI_INFO("Enabling dev mode and rebooting...\n");

    if (vb2api_enable_developer_mode(ui->ctx) != VB2_SUCCESS) {
        UI_WARN("Failed to enable developer mode\n");
        return VB2_SUCCESS;
    }

    return VB2_REQUEST_REBOOT_EC_TO_RO;
}

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/depthcharge/src/vboot/ui/screens.c;l=799-851;drc=85f76f13f623d3e4e3a01224a381abf755f1c1bb

vb2api_enable_developer_mode()ってやつがあやしいね!そして、それを呼んだあとは再起動してるみたい。depthchargeの中にはtest用のmockしかなかったので、ひとつ上のsrc/platformで検索をかけると、vboot_referenceで実装が見つかった。

vb2_error_t vb2api_enable_developer_mode(struct vb2_context *ctx)
{
    if (ctx->boot_mode != VB2_BOOT_MODE_MANUAL_RECOVERY) {
        VB2_DEBUG("ERROR: Can only enable developer mode from manual "
              "recovery mode\n");
        return VB2_ERROR_API_ENABLE_DEV_NOT_ALLOWED;
    }

    uint32_t flags;

    VB2_DEBUG("Enabling developer mode...\n");

    flags = vb2_secdata_firmware_get(ctx, VB2_SECDATA_FIRMWARE_FLAGS);
    flags |= VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE;
    vb2_secdata_firmware_set(ctx, VB2_SECDATA_FIRMWARE_FLAGS, flags);

    VB2_DEBUG("Mode change will take effect on next reboot\n");

    return VB2_SUCCESS;
}

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/vboot_reference/firmware/2lib/2misc.c;l=433-452;drc=def2f5af7ac03eac3a4a79795275f1cf6cd7f827

VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE ってフラグがセットされるんだね〜

ということでこいつを調べると、フラグの定義も見つかった。

/* Flags for firmware space */
enum vb2_secdata_firmware_flags {
    /*
     * Last boot was developer mode.  TPM ownership is cleared when
     * transitioning to/from developer mode.  Set/cleared by
     * vb2_check_dev_switch().
     */
    VB2_SECDATA_FIRMWARE_FLAG_LAST_BOOT_DEVELOPER = (1 << 0),

    /*
     * Virtual developer mode switch is on.  Set/cleared by the
     * keyboard-controlled dev screens in recovery mode.  Cleared by
     * vb2_check_dev_switch().
     */
    VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE = (1 << 1),
};

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/vboot_reference/firmware/2lib/include/2secdata.h;l=25-40;drc=6a33a0fca3e4f5cd9c3b3fd4ac0b5b8c7ffc018e

コメントいわく、最後の起動時の状態も記録しておいて、もしnormalからdevに遷移していたら、TPMのownershipをクリアするって書いてあるね!まあ要するに、normalのときに保存されたユーザーの秘密情報がもれないように、なんでもありのdevmodeに遷移するときはデータを消し飛ばすってことなんでしょう。(実際devmodeに移行するとなんかの消去が走っている画面になる。)

次回はその消去の画面のところを見に行きましょう!(オープンソース部分であることを祈りましょう…)

おやすみなさい!

VRM as a Code - またはコードによりあなたのアバターを定義する方法について

下書きのまましばらく放置されたのでインターネットの海に放流

目標

hikaliumのアバターをコードで定義して、GUIを用いることなく服を着せ替えたりパラメーターを操作するなどできるようにしたい

先行研究

背景情報

VRM形式

人型アバターで最近主流になりつつあるファイルフォーマット。glTF形式をベースにしている。

glTF形式

調査

VRM/glTFを理解する

bin領域のデータのエンディアン

Little Endianにしなければならない(MUST)

All buffer data defined in this specification (i.e., geometry attributes, geometry indices, sparse accessor data, animation inputs and outputs, inverse bind matrices) MUST use little endian byte order.

UV map

https://docs.blender.org/manual/ja/dev/addons/import_export/scene_gltf2.html#uv-mapping

KHR_texture_transform

Animation

three-vrm example の humanoidAnimation のデモがデバッグに使えそう

実装

github.com

とりあえずVRMをglTFとして読み込んだら、テクスチャ付きでprimitive単位に分割して再出力することはできた。

また気力が湧いたら続きをやろう。

2022年の振り返り

例年通り、今年あったことの概要をまとめておく。

1月

3Dプリンターをお買い上げしてた(今年だったんだ…)

ちなみにブログ記事も書いた

hikalium.hatenablog.jp

2月

3Dプリンタメンガーのスポンジを印刷してた(筆記用具は商品には含まれません)

そしてhikaliumとして初の社のイベント登壇をした

そしてもう一台3Dプリンタが増えた(錯乱)

戦争とかが始まったのもこのあたりらしい(世も末ですな)

あまりにも眠れなさすぎる現状を歌にするなどした(かっとしてつい…聴かなくていいですよ!!!)

3月

情報処理学会のOS研究会で社の仮想ブースを回すなどしていた

新しいお友達もお迎え(コアスイッチとして現在も頑張ってくれています)

おうちNOC業の様子

車輪を再発明した(車輪ではなく車(輪がついた物体の進入を防ぐ)輪)

技術的な方法でTwitterの閲覧時間を削減する試みを開始(まあまあ役に立っており、今もつかっている)

4月

社の公式ブログでお仕事に関する記事を書いた

Redox を読む会を始めた(次回をスケジュールするのを忘れていました…来年やります!)

セキュキャンのCコンパイラゼミが、元受講生の講師を迎えて再始動することが決定(本当にめでたかった!)

ZOZOMATを試すなど(足、印刷しなきゃ…)

ちなみにイエベ春らしいです(ZOZOGLASS曰く)

社のイベントで人々に自作OSはいいぞ!をするなどした

スライドはここ

そして Asahi Lina さんとコラボした!(Linaさん、つよつよなので尊敬している)

Linaさんのチャンネルにお邪魔して、私が自作OS語りをしてくる回もありました!

5月

kurenaif魔女のチャンネルにKOBA789氏と一緒にお邪魔して、DirtyPipe ( CVE-2022-0847 )を完全理解した

そして弊チャンネル初のSuperThanks受領(ありがとうございます!)

思い立ったのでosdev-jpのcode of conductを制定するなどした

2月に公開した歌の解説記事を書いた(解説の必要な歌って、なに?)

3Dプリンタがあるなら、ということで3Dスキャナに入門した(むずかしい)

USB Type-C の電気的な側面の理解を試みるなどした(少し理解した)

そして!はじめての!オフィスでの!夕食!(入社2年半以来初の快挙)

分解衝動の発散

6月

計算資源が追加された

ふるさと納税メロンを摂取(ひとりで2個まるごと食べるのはなかなかの難易度だった…超美味しかったけど…)

ついにToDoリストアプリをつくるというToDoに着手(現在メンテ中で落ちてる)

7月

Rust for Linuxでお遊びする配信をするなどした

[大人の]科学を大人買いするなど

そしてRP2040を飛ばす

そして破壊衝動の発散(ではなく異音の原因を除去した)

ちゃんと原因特定している図

他の物体も分解(分解ばっかりしてるな)

Optaneはもうおしまいです、わたしは悲しいよ…(終わらせないぞ!!!)

活版印刷入門

8月

夏はやっぱりセキュキャン!自作OS!だよね!!

あと電源は大事です。電源は大事なので。

インターンの人々の優しい言葉に救われました。本当にありがとうございます!!!

そして深淵を覗く

9月

はじめての入院入門!

ハードウェアアップデートのためしばらくオフラインに

生還したので辞世の句にはならなかった歌ってみた動画

2週間の入院後なんとか帰宅したら家は無事ではなかった話(またエアコンから水かよ(自力でなんとかしました))

リコリコに影響されて「力」を手に入れるなど(もちろん合法な力です)

ikaliumです(入院中に発売されていたスプラ3に入門)

10月

低レイヤーガール、チャンネル登録者数4000人突破!!(ありがとうございます!)

二次元セルオートマトンを一発実装する THE FIRST CODE をやってみたら、しょうもないコンパイルエラーで終了した件(ちゃんと後でデバッグしました!)

そして新しい物理アバターのmetrics exporter(丸くてかわいいのでお気に入りです)

久々に物理世界のイベントに参加して自己肯定感が向上した図(hikaliumは優しい皆様に救われています)

11月

情報公開(伏線回収)

やさしい世界

そしてDNSの浸透

3年間お預けだった、リアルワールド開催のSTEP Class 8に参加してきた!(mallocチャレンジの参加者の何名かに賞を差し上げました!)

テプラSR5900Pのプロトコルを解析して、CLIから印刷できるツールを実装した

物質、いいよね(用法用量を守って正しく使いましょう)

12月

ポケモンSV、はじめました!

Open Source Summit 聴講(社の人間としては初の物理カンファレンス参加)

WebUSBに入門した(自作OSでドライバ実装したときの大変さに比べたらマジで拍子抜けするほど簡単でびっくりした、みんなもっとこれで遊ぼう!)

/sys/hikalium/tracing を生やした(デバッグ用なのでノイズが多いですが興味あればどうぞ)

hikalium.hatenadiary.jp

胸に刻んでおきたい言葉(手前味噌ですが)

hikaliumのロゴの背後の深層心理はこれでは?という発見

no (クリスマス) spanning-tree

大掃除

著者近影

総括

振り返ってみれば、想像以上にたくさんのことをやっていたことに気づいた。もっと自信を持って欲しい > 自分

ついに情報公開も果たしたが、誰もが何事もなく受け止めてくれて、人々の優しさを再確認した。これからは、以前にも増して、自分の「好き」をもっと大切にしてゆきたいと思う。(みなさんもぜひ、そうしてくださいね!)

おしごとに関しては、外から見える範囲のCommunity contributionをたくさんできたのは素晴らしかったし、これからも続けてゆきたいが、もっと強くなって技術的な成果のほうもさらに挙げてゆきたい。問題は、時間が足りないというところだが…(生き残りたい!!!)

あと、もう少し精神を安定させてゆきたいですね。もっと、ゆっくり、やっていこう。活動を抑え気味にしているつもりでも、これだけのことをできているのだから。

今年お世話になった皆様におかれましては、本当にありがとうございました。

来年以降も引き続き精進してまいりますので、hikaliumをどうかよろしくお願いいたします!!!

Rustで自作OSをしているときのデバッグ例 - syscall 命令と仲良くなりたい!後編

この記事は自作OS Advent Calendar 2022の18日目の記事です。前回のあらすじはこちら:

hikalium.hatenablog.jp

例外を吐いてるのはどこのどいつだぁ?

さて、前回の状況をおさらいすると、

  • 0x200fd0の命令がアドレス0x2000000000にアクセスしようとしてページフォルト(0xe)を発生させた。
  • ページフォルトを処理している最中に一般保護例外(0xd)が発生し、一般保護例外を処理中にもさらに一般保護例外が発生することで、順次スタックが食い潰されていった。
  • 例外ハンドラのスタックが枯渇し、0x3de4bed8にアクセスしようとした段階でページフォルトが発生した。これにより、ダブルフォルトが発生した。
  • ダブルフォルトハンドラ内で再度ページフォルトが発生した結果、トリプルフォルトとなってリセットがかかった。

というわけで、まずは最初のきっかけである 0x200fd0とかにある命令がどこのどいつかを特定することにしましょう。

まず、アプリのロード状況を確認します。OSのログを見ると、

Executable found: Elf { name: hello, data: @0x000000003e2d9000 } 
This ELF seems to be executable!
phdr_start: 64, phdr_entry_size: 56, num_of_phdr_entry: 5
type  : (rwx) = (101)
offset: 0x0000000000000000
vaddr : 0x0000000000200000
fsize : 0x0000000000001B64
vsize : 0x0000000000001B64
align : 0x0000000000001000

type  : (rwx) = (110)
offset: 0x0000000000001B68
vaddr : 0x0000000000202B68
fsize : 0x00000000000001C8
vsize : 0x00000000000001C8
align : 0x0000000000001000

load_region_start = 0x0000000000200000
load_region_end = 0x0000000000203000
load_region @ 0x000000003d233000
dst_range: 0x0000000000000000 - 0x0000000000001B64
dst_range: 0x0000000000002B68 - 0x0000000000002D30
run the code!
entry_point = 0x000000003d2332f0

というふうになっています。これはどういうことかというと…図示したほうがはやそうなので、お絵描きしました。

現在のアプリケーションのロードの様子

現在は手抜きをするために、アプリケーションとOSのメモリ空間を分けていないので、アプリの希望通りのメモリレイアウトではなく、相対位置を維持したまま、異なるアドレスにCodeとDataの両方をロードしているのでした。

さて、例外を発生させたメモリアドレス(0x200fd0)は、このロードされたコード(0x3d233000-0x3d234b64)の中…にありませんね…あれ?

…と思って状況をよくよく確認すると、どうもこのアドレスは、アプリがもともと予期していた、コード領域がロードされるべきアドレス範囲(0x200000-0x201b64)の中にあるようです…つまり、アプリケーションが正しく再配置可能ではなかった、もしくはローダーが仕事をサボっているせいで、正しく再配置できなかった、のいずれかの可能性であると考えられます。ちょっとアプリのバイナリのほうを確認してみましょう。

$ `brew --prefix binutils`/bin/objdump -d -C target/x86_64-unknown-elf/release/hello | grep -A 3 200fd0
0000000000200fd0 <<core::fmt::Arguments as core::fmt::Display>::fmt>:
  200fd0:       48 83 ec 38             sub    $0x38,%rsp
  200fd4:       48 8b 06                mov    (%rsi),%rax
  200fd7:       48 8b 76 08             mov    0x8(%rsi),%rsi
  200fdb:       48 8b 4f 28             mov    0x28(%rdi),%rcx

objdumpに-Cオプションをつけると、シンボルをデマングル(人間が読みやすい形式に変換)してくれるみたいで、Rustのシンボル名にも対応してるみたいです。ありがとうbinutils!

というわけでですね、この200fd0は、明らかにDisplay::fmtを呼び出しているコードで利用されていて、我々のprintln!のコードパスで通っていそうな雰囲気がひしひしと伝わってきます。

さて、これを呼び出している側のコードはどうなっているのでしょうか?rip相対のcallではこのようなことは起こり得ないので、レジスタ間接でのcallがDisplay::fmtへ制御を移そうとしているはずです。なので、レジスタ間接callを使っている人々をリストアップしましょう。

$ `brew --prefix binutils`/bin/objdump -d -C target/x86_64-unknown-elf/release/hello | grep -E -e 'call.*\*' -e ' <.*>:$' | grep 'call.*\*' -B 1
0000000000200860 <<core::panic::panic_info::PanicInfo as core::fmt::Display>::fmt>:
  200886:       41 ff 56 18             call   *0x18(%r14)
  2008bf:       ff 50 18                call   *0x18(%rax)
--
0000000000201030 <core::fmt::write>:
  2010b7:       ff 50 18                call   *0x18(%rax)
  201177:       ff 54 08 08             call   *0x8(%rax,%rcx,1)
  2011d8:       ff 50 18                call   *0x18(%rax)
  2011e9:       42 ff 54 35 08          call   *0x8(%rbp,%r14,1)
  20122f:       ff 51 18                call   *0x18(%rcx)
0000000000201250 <core::fmt::Formatter::pad_integral>:
  2013f1:       ff 55 20                call   *0x20(%rbp)
  20150a:       41 ff 55 20             call   *0x20(%r13)
  201557:       ff 55 18                call   *0x18(%rbp)
  201582:       41 ff 55 18             call   *0x18(%r13)
  2015aa:       41 ff 55 20             call   *0x20(%r13)
00000000002015d0 <core::fmt::Formatter::pad_integral::write_prefix>:
  2015f1:       41 ff 54 24 20          call   *0x20(%r12)
0000000000201630 <core::fmt::Formatter::pad>:
  2018ea:       41 ff 56 20             call   *0x20(%r14)
  20190d:       41 ff 56 18             call   *0x18(%r14)
  20193f:       41 ff 56 20             call   *0x20(%r14)

まあ…たぶんcore::fmt::writeですよね…きっとそうだ…

`brew --prefix binutils`/bin/objdump -d -C target/x86_64-unknown-elf/release/hello | grep '<core::fmt::write>:' -A 155 | grep -z -E -e 'call' --color='always'

qemu/target/i386/cpu.h

$ cat qemu_debug.log | grep hikalium | head -n 3
hikalium: raise_interrupt2: is_int == 0 intno = 14 eip=0x000000000000200FD0 error_code = 0x000000000000000000
hikalium: rsp: 00000000003E286BE0
hikalium: ret_to: 00000000003D2341EE
$ cat com2.log | grep -a -e entry_point
entry_point = 0x000000003d2332f0
$ make objdump_hello | grep '<entry>:'
00000000002002f0 <entry>:
$ ./scripts/debug/inspect_app_crash.sh 
0000000000201030 <core::fmt::write>:
  2011e9:       42 ff 54 35 08          call   *0x8(%rbp,%r14,1)
  2011ee:       84 c0                   test   %al,%al

.data.rel.roがあやしい!

https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/specialsections.html

Chromium Docs - Native Relocations

Relative relocations and RELR | MaskRay

$ `brew --prefix binutils`/bin/objcopy -O binary --only-section=.data.rel.ro generated/bin/hello data.bin
[00:34:16]
[local] hikalium@t05.z01.hikalium.com: ~/repo/wasabi  
$ hexdump -C data.bin 
00000000  58 01 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |X. .............|
00000010  58 01 20 00 00 00 00 00  01 00 00 00 00 00 00 00  |X. .............|
00000020  58 01 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |X. .............|
00000030  e0 04 20 00 00 00 00 00  08 00 00 00 00 00 00 00  |. .............|
00000040  08 00 00 00 00 00 00 00  20 06 20 00 00 00 00 00  |........ . .....|
00000050  f0 04 20 00 00 00 00 00  c0 05 20 00 00 00 00 00  |. ...... .....|
00000060  60 01 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |`. .............|
00000070  60 01 20 00 00 00 00 00  01 00 00 00 00 00 00 00  |`. .............|
00000080  61 01 20 00 00 00 00 00  08 00 00 00 00 00 00 00  |a. .............|
00000090  60 01 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |`. .............|
000000a0  e0 04 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |. .............|
000000b0  01 00 00 00 00 00 00 00  70 07 20 00 00 00 00 00  |........p. .....|
000000c0  b0 03 20 00 00 00 00 00  80 04 20 00 00 00 00 00  |. ....... .....|
000000d0  e0 04 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |. .............|
000000e0  01 00 00 00 00 00 00 00  70 19 20 00 00 00 00 00  |........p. .....|
000000f0  94 01 20 00 00 00 00 00  13 00 00 00 00 00 00 00  |.. .............|
00000100  37 00 00 00 05 00 00 00  94 01 20 00 00 00 00 00  |7......... .....|
00000110  13 00 00 00 00 00 00 00  53 00 00 00 05 00 00 00  |........S.......|
00000120  db 01 20 00 00 00 00 00  01 00 00 00 00 00 00 00  |. .............|
00000130  dc 01 20 00 00 00 00 00  03 00 00 00 00 00 00 00  |. .............|
00000140  a8 02 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |. .............|
00000150  a8 02 20 00 00 00 00 00  01 00 00 00 00 00 00 00  |. .............|
*
00000170  40 1a 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |@. .............|
00000180  01 00 00 00 00 00 00 00  50 08 20 00 00 00 00 00  |........P. .....|
00000190  b8 02 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |. .............|
000001a0  b8 02 20 00 00 00 00 00  02 00 00 00 00 00 00 00  |. .............|
000001b0

https://uclibc.org/docs/elf-64-gen.pdf

Hardening ELF binaries using Relocation Read-Only (RELRO)

https://maskray.me/blog/2021-08-29-all-about-global-offset-table

Airs – Ian Lance Taylor » Linker relro

cnlelema.github.io

時間切れ

というわけで追いかけていたら.data.rel.roの沼にはまったので今日はここまで。明日もやるかも。おやすみなさい…。

Rustで自作OSをしているときのデバッグ例 - syscall 命令と仲良くなりたい!前編

この記事は自作OS Advent Calendar 2022の17日目の記事です。他の記事も是非お楽しみください!(そして書ける方はぜひ参加してみてください!!)

前回(?)までのあらすじ

hikaliumは自作OS上で動くアプリからsyscall命令を使ってシステムコールを呼べるようにしようと頑張っていたが、なぜか発生するトリプルフォルトによりQEMUが再起動してしまい、3時間のデバッグの末力尽きてしまった。一体なぜ例外が発生するのか、その謎を解くため、我々は数日の休息をとったのち、バイナリの森へと旅立った…。

前回(という名の配信アーカイブ):

www.youtube.com

状況を整理しよう

バイナリの森は危険だ。無闇に動きまわっては、x86の沼に足をとられて命を落としかねない。まずは我々の向かっていた先と、これまでに得た情報をまとめることにしよう。

どこへ向かっていたのか

我々のひとまずの目標は、自作OS上で実行されるアプリケーションから、Rustのprintln!()マクロを介して文字列を出力することであった。Rustはとてもよくできていて、coreクレートの中に、C言語でいうsprintfに相当する、fmtモジュールが存在しており、既存のOSの支援が期待できないno_std環境でも変数の中身などを容易にかつ美しく表示する関数およびマクロを容易に実装することができる。そう、我々はもう闇のようなlibc++やnewlibに頼らなくてもよくなったのだ。10年前のように、自前でsnprintfもどきを実装するまでもない。世界は日増しによくなりつつある。

ではprintln!マクロを実装するにあたり、自作OSが提供しなければならない機能は一体何かというと、それは「文字列を表示する」という機能である。というのも、coreクレートにはOSに依存するような機能は含められないため、「文字を画面に出す」などといった、物理世界とのインタラクション、つまりはハードウエアの制御などのOSが支配している領域の操作はcoreクレートの外で自前で用意してやらなければならない。core::fmtは、フォーマット文字列と引数をもとに、メモリ上に「表示するべき文字列」を生成してはくれるが、それを表示するのは我々の責務なのだ。

というわけで、我々はprint_stringというシステムコールを用意した。これは、メモリ上に存在する文字列へのポインタと、文字列の長さを指定すると、OSがそれを画面上に表示してくれる、というものである。実は、このシステムコール自体はどうも動作しているようで、実際にアプリケーションからprint_stringシステムコールを介して固定の文字列を出力してみた場合は、想定通りに文字列が出力されていたことが確認できている。

それならば、あとはprint_stringに対して、core::fmtによって生成された文字列を渡してあげればよいのではないか、我々はそう踏んでいたのだ。

なにがあったのか

…しかし、その考えはあまりにも浅はかだった。実際に、以下のようにprint_stringを置き換えてみたところ、なんとOSが再起動してしまったのだ。

#![no_std]
#![no_main]

use noli::*;

fn main() -> i64 {
    //sys_print("hello!");
    println!("{}", 42);
    return -42;
}

entry_point!(main);

どうも、何らかの理由でCPUの例外が発生し、トリプルフォルトに至って再起動してしまう、というところまではわかったのだが、我々はそこで一旦力尽きてしまったのだった…。

まずは何が起きているのかを理解しよう

現状は先ほど伝えた通りだ。次は、我々がどちらに向かえばよいのか、それを考えることにしよう。そのためには、我々がいま直面している現象を正しく理解することが必要不可欠だ。まずは、具体的にどのような例外でトリプルフォルトに至っているのか、それを確認することにしよう。

QEMUには、例外が発生した際にログを出力する機能が存在している。これはデフォルトでオフになっているが、-d というフラグを介して制御することが可能だ。

$ qemu-system-x86_64 -d ?
Log items (comma separated):
out_asm         show generated host assembly code for each compiled TB
in_asm          show target assembly code for each compiled TB
op              show micro ops for each compiled TB
op_opt          show micro ops after optimization
op_ind          show micro ops before indirect lowering
int             show interrupts/exceptions in short format
exec            show trace before each executed TB (lots of logs)
cpu             show CPU registers before entering a TB (lots of logs)
fpu             include FPU registers in the 'cpu' logging
mmu             log MMU-related activities
pcall           x86 only: show protected mode far calls/returns/exceptions
cpu_reset       show CPU state before CPU resets
unimp           log unimplemented functionality
guest_errors    log when the guest OS does something invalid (eg accessing a
non-existent register)
page            dump pages at beginning of user mode emulation
nochain         do not chain compiled TBs so that "exec" and "cpu" show
complete traces
plugin          output from TCG plugins

strace          log every user-mode syscall, its input, and its result
tid             open a separate log file per thread; filename must contain '%d'
trace:PATTERN   enable trace events

Use "-d trace:help" to get a list of trace events.

おっと、言い忘れていたが、我々は以下のバージョンのQEMUを自前ビルドして利用している。一応伝えておこう。

$ qemu-system-x86_64 --version
QEMU emulator version 7.2.0 (v7.2.0-dirty)
Copyright (c) 2003-2022 Fabrice Bellard and the QEMU Project developers

さて、本筋に戻るとしようか。というわけで、以下のようなオプションをQEMUに追加すれば、例外に関するログが、カレントディレクトリの qemu_debug.log というファイルに出力されるはずである。(出力先のファイルは-Dオプションで指定している。) また、 --no-reboot をつけておくと、トリプルフォルトでCPUにリセットがかかった際に、再起動するのではなくQEMUを終了することができるようになる。再起動のループを楽しみたい気分であるなら話は別だが、これも設定しておくとデバッグが楽になるだろう。

-d int,cpu_reset -D qemu_debug.log --no-reboot

さて、これでもう一回自作OSを起動してみる。アプリは自動で起動するように設定してあるから、クラッシュしてすぐQEMUが落ちるはずだ。

…予想通り落ちた。ログを確認しよう。

$ cat qemu_debug.log | grep -A 1 -e check_exception -e Triple
check_exception old: 0xffffffff new 0xe
   159: v=0e e=0000 i=0 cpl=0 IP=0008:0000000000200fd0 pc=0000000000200fd0 SP=0010:000000003e286be0 CR2=0000002000000000
--
check_exception old: 0xffffffff new 0xd
   160: v=0d e=0000 i=0 cpl=0 IP=0008:000000003e290f91 pc=000000003e290f91 SP=0010:000000003e286928 env->regs[R_EAX]=0000002000000000
--
check_exception old: 0xffffffff new 0xd
   161: v=0d e=0000 i=0 cpl=0 IP=0008:000000003e290f91 pc=000000003e290f91 SP=0010:000000003e286668 env->regs[R_EAX]=0000002000000000
--
...
--
check_exception old: 0xffffffff new 0xd
  6458: v=0d e=0000 i=0 cpl=0 IP=0008:000000003e290f91 pc=000000003e290f91 SP=0010:000000003de4c1a8 env->regs[R_EAX]=0000002000000000
--
check_exception old: 0xffffffff new 0xd
  6459: v=0d e=0000 i=0 cpl=0 IP=0008:000000003e290f91 pc=000000003e290f91 SP=0010:000000003de4bee8 env->regs[R_EAX]=0000002000000000
--
check_exception old: 0xd new 0xe
  6460: v=0e e=0002 i=0 cpl=0 IP=0008:000000003e290f91 pc=000000003e290f91 SP=0010:000000003de4bee8 CR2=000000003de4bed8
--
check_exception old: 0xe new 0xe
  6461: v=08 e=0000 i=0 cpl=0 IP=0008:000000003e290f91 pc=000000003e290f91 SP=0010:000000003de4bee8 env->regs[R_EAX]=0000002000000000
--
check_exception old: 0x8 new 0xe
Triple fault

おお、結構な量のログが出ている。はて、一体何が起きているのだろうか…。

例外コードを順に追っていくと、以下のような流れで例外が起きていることがわかる。

  • ページフォルト(0xe)が0x200fd0の命令を実行中に発生した。ページフォルトの要因となったアドレスは CR2=0x2000000000 である。
  • 一般保護例外(0xd)が0x3e290f91の命令を実行中に発生した。
  • 同じ例外が次々と発生し、スタックポインタが0x3e286928から0x3de4bee8まで変化した。
  • 最終的に、スタックポインタと同じアドレスであるCR2=0x3de4bed8 にアクセスしようとして、再度ページフォルト(0xe)が発生した。
  • ここでさらにページフォルト(0xe)が発生し、これによってダブルフォルト(0x8)が発生した。
  • ダブルフォルト(0x8)の発生中にさらにページフォルト(0xe)が発生し、最終的にトリプルフォルトとなってリセットがかかった。

つまるところ、

  • 最初のきっかけは、0x200fd0の命令がアドレス0x2000000000にアクセスしようとしてページフォルト(0xe)を発生させたことにある。
  • そのページフォルトを処理している最中に一般保護例外(0xd)が発生し、一般保護例外を処理中にもさらに一般保護例外が発生することで、順次スタックが食い潰されていった。
  • 例外ハンドラのスタックが枯渇し、0x3de4bed8にアクセスしようとした段階でページフォルトが発生した。これにより、ダブルフォルトが発生した。
  • ダブルフォルトハンドラも同一のスタックを使用していたため、再度ページフォルトが発生し、トリプルフォルトとなってリセットがかかった。

ということのようだ。 …例外を正しく処理できないOSでごめんなさい…(仕方ないよ、完璧なOSなんて存在しないもの。直していきましょう!)

さて、ここらへんで日付変更線が近づいてきたようだ。続きはまた明日…(睡眠は大事ですよ!おやすみなさい!)

「すいみんpreparation」解説

注意: この文章は教科書ではありませんし正確性も保証されていません。より詳細で正確な情報を知りたい場合には、ここで登場したキーワードをもとに自分で調べてみてください。

はじめに

眠れない夜にOSのことを考えていたら、ふとメロディを思いついてしまったのでそれを打ち込んだのが2/2のことでした。

そこから勢いで曲を打ち込んで歌詞をつくり(この段階で歌詞は出来上がっていたので伏線)

歌って動画を作った結果がこちらです。

なお歌はあまり上手くないので、声なしのバージョンがいいよという方はSoundCloudにありますのでそちらもどうぞ!

soundcloud.com

さて、この曲に対する人々の反応ですが、

はい、極めて正常な反応だと思います。

実のところ、この歌詞にはOSネタを大量に盛り込んでいたので、おそらくそのあたりの知識がないと完全には面白さが伝わらなかった可能性があります。

そこで本記事では、「すいみんpreparation」の歌詞を追いつつ、どのようにOSネタとの関連があるのか、どのような意図をこめた歌詞になっているのかについて解説したいと思います。

全体のテーマ

動画の概要欄に「やりたいことで手一杯なOSのみなさんのために」と書かれている通り、OSの果たす役割のうちの一つである「資源の管理」という側面について、その難しさを日常生活になぞらえて描写することが今回のテーマでした。

特に、タスクのスケジューリング問題に関しては、かなり強い仮定をおいてもなおNP完全な問題であることが "NP-complete scheduling problems" (J.D. Ullman, 1975) で示されています。ちなみにUllman氏は、コンパイラを実装したことのある皆様にはおなじみ、通称ドラゴンブックの作者の一人です。

www.sciencedirect.com

OSのみなさんだけでなく、人間のみなさんにとっても、日々遂行しなければならないタスクが山積していて、その処理に追われているという状況はよくみられることでしょう。

そのような困難な状況下にあっても、最優先で処理すべきタスクのひとつ、それが睡眠である、というのが本作品の主要な論点になります。

歌詞とその背景

では、最初から順番に歌詞を追ってゆきましょう。

いつも気づいたら 時計の針はとびまわり 
明日という日が 足音立てず向かってくるよ

作業に没頭していると、時間があっという間に過ぎてしまって、気づけば夜中になっていた!という経験はみなさんよくあるのではないでしょうか。

OSにおいては、並行処理における競合状態を回避するために「割り込み」を禁止することがあります。(このような競合状態の発生する可能性のある処理の範囲のことを、「クリティカルセクション」と呼びます。)

「割り込み」は、外部デバイスからの信号などを契機として、現在実行している処理に優先してなんらかの処理を行わせる仕組みのこと、またそれを引き起こす信号のことを指します。

割り込みを発生させる外部デバイスの状態変化の例としては、キーボードのキーを押下した時や、ネットワークを介してパケットが到着した時、外部記憶装置に対する書き込み要求が完了した時などが挙げられます。

他にも、重要かつ基本的な割り込みの一つとして、タイマー割り込みというものが存在します。これは、ある時間(たとえば10ms)が経過するごとに割り込みを発生させる、というものです。

OSは、このタイマー割り込みを活用することにより、時分割マルチタスクを実現しています。マルチタスクは、CPUの処理能力という限られた資源を有効活用するというOSの大事な役割のひとつです。 したがって、それを支えるタイマーや時刻の管理も、実はOSの重要な仕事のひとつなのです。(Linuxではtimekeepingサブシステムがこれを担当しています。)

さて、以上のことを念頭に一つ考えてみることにしましょう。もしも割り込みをずっと止めていたら、つまり割り込みを無視し続けたら、一体何が起こるでしょうか?

……そうです、時分割マルチタスクの仕組みが正しく動作せず、現在実行しているタスクが終わるまでずっとそのタスクを処理しつづけることになります。

これは、何かに没頭して時間の経過を忘れてしまうことと対応します。(人間においてこのような状況は「過集中」と呼ばれることもあります。)

その結果として、いつの間にか意図せず夜ふかしをしてしまうことを「明日という日が 足音立てず向かってくるよ」と表現しています。

また、タイマー割り込みによって定期的に行われていた時刻の管理も正しく行われなくなるため、時計が狂ってしまうこともあります。

それが「いつも気づいたら 時計の針はとびまわり」という部分の意味です。

今日も 下手したら 同じところを周ってばかりで
明日できるさ 帰納法によれば成り立たぬ

得てして長時間の割り込み禁止が発生してしまうのはバグの存在を意味します。というのも、先ほど説明した通り、長時間の割り込み禁止は様々な問題を引き起こす可能性があるため、そのような状況が発生しないように気をつけてコードを書く責任が開発者にはあるからです。

そもそも、アプリケーションレベルでは、割り込みを禁止することは一般に許可されていません。OSのみが割り込みに関連する制御を行うことができるよう、CPUの保護機構を活用して制限をかけている場合がほとんどです。(ユーザーモードカーネルモード・特権命令 等々で調べてみてください。)

したがって、この種のバグは、OS側で処理が無限ループするなどの要因で発生していることが考えられます。これを描いたのが「今日も 下手したら 同じところを周ってばかりで」という部分です。

これを日常生活における類推に落とし込むと「明日から本気出す」などの、問題を先送りにする場面が想像できます。(耳が痛いです…。)

残念ながら「明日になればできる」と考えているときは、永遠に来ない相対的な「明日」に対して問題の先送りを繰り返し、目標が達成されないか、達成されたとしても非常に長い時間がかかることが経験上知られています。

このように、いくつかの事実から一般的な規則を導く手法を枚挙的帰納法といいます。ただ、枚挙的帰納法数学的帰納法とは異なり、導かれた規則が正しいとは限りません。(ということで希望を失わずにがんばりましょう!)

やりたいことばかりで リソース不足まっしぐらなの
コンテキストスイッチだけで 手一杯だよ

これ…ほんとこれなんですよ。

時間は有限な資源です。それはOSにおいても同様です。 CPUのコア一つ一つは、単純な操作を繰り返しているに過ぎず、同時に複数の処理を行うことができません。 そのため、並行して処理を進めるためには、何らかの工夫をする必要があります。 複数のコアを利用して処理を進める並列計算はひとつの手段ですが、それでもコア数分しか並行に演算を行うことができません。 そこで、ほとんどのオペレーティングシステムでは、あるコアの上で実行されるアプリケーションを短い時間間隔(これをタイムスライスといいます)で切り替えることにより、大局的にみれば複数のアプリケーションが並行して動作しているようにみせる機能を有しています。これを、マルチタスキングといいます。

あるCPU上で実行されているアプリケーションを別のアプリケーションに切り替える際には、現在のCPUの内部状態の一部(CPUコンテキスト)を保存してあげる必要があります。この作業をコンテキストスイッチといいます。 より具体的には、CPUのレジスタに入っている値を、各プロセスごとに別々に用意されたメモリ領域にコピーして退避するなどの処理を行なっています。

コンテキストスイッチもCPU上で実行されるプログラムにすぎませんから、その処理には一定の時間がかかります。

ここまでくれば、もう察しの良い方は気づいているかもしれません…そうです、もしも非常に多くのアプリケーションを単一のマシン上で実行しようとすると、コンテキストスイッチにかかる時間が非常に大きくなり、さらには各アプリケーションが実行される頻度もどんどん下がってゆくことになります。つまり、アプリケーションの処理がほとんど進まなくなってしまう可能性があるわけです。これを飢餓状態(resource starvation)といいます。

…ですから、やりたいこと(アプリケーション)の数が多すぎるとリソース(資源、ここではCPU時間)不足に陥り、コンテキストスイッチだけでCPU時間を食い尽くす飢餓状態になる、ということです。

みなさんもやりたいことスタックの大きさには気をつけておきましょう。(耳が痛いです…。)

ぐーるぐるぐる回る run queue
このスケジューラーは壊れてる
panic 寸前のシステムを
救えるのは君だけさ ほらね

さて、前項で説明したコンテキストスイッチを司るのは、OSの中でもスケジューラーと呼ばれる部分になります。スケジューラーは、実行可能だが現在CPU上では実行されていないタスクの一覧をrun queueとよばれるデータ構造に保持しており、その中から次に実行すべきタスクを選択して、そのプロセスにコンテキストスイッチを行うことで、タスクを順々に実行していきます。(ちなみにここではアプリケーション・プロセス・・タスクという用語が入り乱れていますが、これらの厳密な定義はOSの実装に依存しますので、まあとにかく「処理の管理単位」なんだなあ、と思っていただければ助かります。)

実を言うと近年のほとんどのOSでは、タスクごとの優先度やリアルタイムタスクの実行などを考慮するために、単純なqueueを用いてスケジューラーが実装されているわけではないのですが、シンプルなモデルとしてここではqueueで考えることにします。

queueの中には実行を待っているタスクが入っており、OSはそれらを順番に取り出してCPUに割り当ててゆくことでマルチタスクを実現するので、まるでタスクがキューの中をぐるぐる回っているかのような感じになります。

もしもスケジューラーにバグがあったら、あるプログラムが永遠に実行されなかったり、色々困ったことが起きるかもしれません。下手をすると、システムの動作に必要なプロセスまでもがいつまでも実行されず、OSが固まってしまう、ということもあるかもしれません。そういった、OSにとって重大な問題が発生して、回復できる見込みがない場合は、OSはエラーメッセージを出力して再起動します。これをpanicと呼ぶことがあります。(Windowsでいえばブルースクリーンのようなものです。)

panicが発生するようなギリギリの状態にOSがある際は、十中八九割り込みが禁止されています。このような状況下では、キーボード割り込みなどの通常の割り込みは受け付けられません。

でも、こんなときでもなんとかなる割り込みが実はあるのです。

すいみん睡眠preparation 最高優先度でよろしくね
すいみん睡眠preparation 問答無用で割り込むよ
すいみん睡眠preparation これは無視不可能なシグナル
すいみん睡眠preparation いますぐおふとんへIRET

x86には、NMIという割り込みがあります。これは、Non-Maskable Interruptの略で、CPUでたとえ割り込みを禁止していたとしても、この割り込みに関しては即座に処理が行われる、という特別な割り込みです。(実はAMDのハイパーバイザまわりの文脈ではNMIをマスクできるとかいうNMIのアイデンディティ崩壊級の面白い話があったりするのですがそこらへんは一旦忘れましょう。)当然、この割り込みは、あらゆる割り込みに優先して(他の割り込み処理を実行中でも関係なく)処理が行われます。つまり、無視できないシグナルなわけです。

みなさんが何かの作業に熱中し過ぎている時には、きっと割り込みは無効化されているのでしょう。たとえば、宅配便が届いてインターホンが鳴っても気づかなかったり、呼びかけられても気づけなかったりするのがその一例です。

しかし、時には何よりも優先して実行すべきことがあるケースがあります。それが睡眠です(要出典)。というかそうしたいですね。難しいですが。

言い換えれば、我々の生活の健全性を担保するためには「マスク不可能な、おふとんへ今すぐ行け割り込み」が必要なわけです。

で、IRETというのは、RETurn from Interrtuptという命令で、割り込み処理を終了し、割り込みが起こった際に実行されていたコードの実行を再開するという命令です。 この命令はコンテキストスイッチでも利用されているので、まあ「マスク不可能な、おふとんへ今すぐ行け割り込み」が発生したら、熱中していたタスクから一旦コンテキストスイッチして、睡眠プロセスへ切り替えるというのが重要ですよ、という話をここでしています。

たまにどうしても 無理なときは
仕方ないから 全部吐き出して
今度直せばいいのさ 
次はもっとうまくやれるはず

まあ、無事におふとんへ辿り着けたらいいわけですが、ときには睡眠失敗するケースもあります。そういうときはまあpanicするしかないわけですが、panicする際は、後でなぜpanicしたのかデバッグできるように、メモリの状態をダンプすることがあります。これをコアダンプと言います。また、コアダンプを「吐く」とか「吐かせる」という慣用表現があり(dumpなので)、つまるところどうしても眠れないなら、現在のメモリの状態(頭の中のもやもや)を全部紙かなんかに書き出して、次の修正の参考にするといいよ、ってことを言ってます。

プログラムに完璧はありません。常にバグがあります。なので、コアダンプは大事です。失敗しないことよりも、次の失敗を防ぐ方法をつくること、そしてそのデバッグを容易にする仕組みを用意することが重要です。

だから
すいみん睡眠preparation 入出力は一時停止で
すいみん睡眠preparation 机の上を片付けようね
すいみん睡眠preparation 頭の中身書き残したら
すいみん睡眠preparation おやすみ世界

こっちは正常系の話で、suspend to diskの処理を表現しています。 よく、コンピューターにおけるメモリは、作業をするための机として例えられます。机が広ければよりたくさんのことを同時に効率よくできる、というアナロジーから来ているのでしょう。 さて、コンピューターがsuspendする際には、電力消費を抑えるために、メインメモリの内容をディスクなどの不揮発性の記憶装置に書き出します。(メモリが不揮発性とは限らないよ!NVDIMMがあるよ!と叫んでいる私のことは無視してください。)というのも、通常のコンピューターのメインメモリとして利用されているDRAMは、電荷コンデンサにためるような感じでデータを保持しており、その電荷は時間経過で抜けてしまうため、定期的にデータを読み出して再度書き込むという操作(リフレッシュ)を行う必要があり、継続的に電力を消費するため、ここにデータを置いたままにしておくのは不都合だからです。

とはいえ、OSのほとんどの状態はメモリ上にありますから、OSが必要な処理、たとえば入出力装置とのやりとりなどがsuspend処理中に起こるとよろしくありません。というわけで、一般的にコンピューターをsuspendする場合には、アプリケーションの動作を停止し、さらに入出力装置を一旦止め、そしてメモリの内容をディスクに書き出し、電源を落とす、という処理になります。

みなさんが睡眠の準備をする際も、うっかりSNSなどを見て割り込み処理が発生してしまうと、睡眠失敗panic路線まっしぐらになる可能性が高いです。というわけで、この順序を守って、安全にシステムをsuspendするようにしましょう。

ということで、おやすみなさい!よい夢を!