ゼロからのOS自作入門 読みメモ 1章

PCの仕組みとハローワールド

$ sum BOOTX64.EFI   
12430 2 BOOTX64.EFI

本の値と一致した!

  • BOOTX64.EFI を含むディスクイメージを作成する
$ qemu-img create -f raw disk.img 200M
  • -fオプションでディスクイメージの形式を指定
$ mkfs.fat -n 'HOGE OS' -s 2 -f 2 -R 32 -F 32 disk.img
  • mkfs.fatはフォーマットをおこなうコマンド
  • -n でボリューム名をつける
  • -s ではクラスタごとのセクタの数をきめる
  • -f FATの数を決める デフォルトは2(FATの数ってなんだろう)
  • -R reserved sectors の数を決める
  • -F FATのタイプ指定(12, 16, 32ビットから指定する)
  • 見た Design of the FAT file system - Wikipedia
  • Mac だと newfs_msdos で使えるっぽい?
newfs_msdos -v 'HOGE OS' -c 2 -n 2 -r 32 -F 32 disk.img
  • オプションは多分これ(-c == -s, -n == -f, -r == -R )
  • newfs_msdosの前にMacだとディスクイメージにattachしないといけなさそう?
$ hdiutil attach -nomount ./disk.img
> dev/disk2 # <=これを指定する
$ newfs_msdos -v 'HOGE OS' -c 2 -n 2 -r 32 -F 32 disk2
$ hdiutil mountvol /dev/disk2 # これでマウントされる
> /dev/disk2                                             /Volumes/HOGE OS
$ sudo mkdir /Volumes/HOGE\ OS/EFI/BOOT
$ sudo cp BOOTX64.EFI /Volumes/HOGE\ OS/EFI/BOOT/BOOTX64.EFI
$ hdiutil detach disk2
$ cp $HOME/edk2/Build/OvmfX64/DEBUG_XCODE5/FV/OVMF.fd .
$ qemu-system-x86_64 -bios OVMF.fd -hda disk.img

f:id:dvi2911:20210403193133p:plain

動いた!

C言語Hello Worldは昔やったので次は2章読もう

ゼロからのOS自作入門 読みメモ 0章

  • 30日OS面白かったので買いました
  • UEFIブート, 64ビット, USBドライバ周りなどが気になる

OSって何

  • アプリがOSの機能、周辺機器を使う際のインターフェース
  • コンピュータの計算能力、メモリ、ストレージの読み書きなどのリソースをアプリへ配分する
  • 人間がコンピュータを扱うためのインターフェース

システムコール楽しみ

メタプログラミングRuby読みメモ 6

9章 Active Record の設計

  • Active Record は、Ruby のオブジェクトをデータベース のテーブルにマッピングするもの

ActiveRecord::Base

  • Active Record は、Active Support と Active Modelの2つのライブラリに大きく依存している
  • ActiveSupport::Autoloadモジュールのautoloadを用いて多くのモジュールのincludeを実現している
  • autoload https://docs.ruby-lang.org/ja/latest/method/Module/i/autoload.html
  • モジュール名を最初に参照したときに自動的にソースを探してrequireする

  • ActiveRecors::Base はモジュールの外側にある機能をまとめている

  • オートローディングのおかげでソースコードを先にrequireしなくてもincludeできる
  • ActiveRecordはデータベースの操作とオブジェクトモデルの操作の二つに分割された
  • データベースの操作はActiveRecordに残り、オブジェクトモデルの操作はActiveModelに移動した

10章 Active Support の Concern モジュール

  • Rails のモジュールは、モジュールをインクルードするとインスタンスメソッドとクラスメソッドの両方が手に入る

    Concern 以前の Rails

  • ActiveRecord::Validation で定義されていた
  • ActiveRecord::Base が Validations をインクルードすると
    • Validations のインスタンスメソッドが Base クラスのインスタンスメソッドになる
    • ValidationsのincludedというフックメソッドをActiveRecord::Baseを引数にして呼び出す
    • フックメソッドではActiveRecord::BaseクラスをActiveRecord::Validations::ClassMethodモジュールでextendしている。
      • これはクラス拡張なので、ClassMethods のメソッドは Base のクラスメソッドになる
    • その結果、Base はインスタンスメソッドとクラスメソッドの両方を手に入れることができた

11章 alias_method_chain の盛衰

  • alias_method_chain ターゲットのメソッドの名前, 追加機能の名前とすると
    • メソッドwith追加機能
    • メソッドwithout追加機能
    • という二つのメソッドを作り出す
  • これは元のメソッドを新しい追加機能で包んだメソッドを作り出してくれる
    • 元のメソッドはメソッドwithout追加機能としてエイリアスされ、メソッドwith追加機能を元のメソッド名としてエイリアスする
  • 古いバージョンのActiveRecord::Validationsはsaveとsave!をハックしてvalidationを追加している
    • validationしたくないsaveをするのであればsave_without_validationを使えば良い

      alias_method_chainの衰退

  • alias_method_chainはRailsの中でメソッドのリネームやシャッフルを繰り返した結果、どのバージョンを使っているのかわからなくなってしまう問題があった
  • オブジェクト指向には別のモジュールにメソッドを定義し、元のモジュールをインクルードするという使い方ができる
  • alias_method_chain を使わずともsuperを用いて周囲に機能を追加することができる

    Module#prepend の誕生

  • 継承チェーンの高い位置にあるメソッドをオーバーライドするモジュールを使いたい場合superで呼び出すことができない
  • この時中間モジュールを作り継承チェーンに差し込むことで実現できるが、Railsのライブラリなど、直接コードを触りたくない場合もある
  • Module#prepend を用いることで継承チェーンの下に目的のモジュールを差し込むことで解決している

12章 アトリビュートメソッドの進化

  • ActiveRecordはカラムをみてカラム=, カラム?などのアクセサメソッドを自動的に生成する
  • はじめはmethod_missingを使ったゴーストメソッドだったが、存在しないメソッドを用いると継承チェーンを1周して探してしまう
  • 動的メソッドを用いて問い合わせ用のアクセサを全て作成するという方法もあったが、Railsはそれらを両方取り入れた
  • はじめにmethod_missingを呼び、その後にdefine_attribute_methodを呼び出す
    • 2回目以降はゴーストメソッドではなく、実体を呼び出せるようになる
  • その後のRailsではアトリビュートメソッドはさらに改善されている
  • アトリビュートアクセサを定義する時にUnboundMethodに変更してからキャッシュに保存
    • 別のクラスが同じアトリビュート名で同じアクセサを必要とした場合これを使い回している!

13章 最後の教訓

メタプログラミングRuby読みメモ 5

6章 コードを記述するコード

Kernel#eval

  • Kernel#evalは、ブロックではなくRubyのコードの文字列を受け取る
  • これをコード文字列と呼ぶ
  • eval は渡されたコード文字列を実行する
  • instance_evalとclass_evalもブロックだけでなく、コード文字列も受け取れる

Bindingオブジェクト

  • Binding はスコープをまとめたもの
  • Binding を使ってローカルスコープを取得すればそのスコープを持ち回すことができる
  • Kernel#bindingメソッドで生成できる
  • スコープを持っているため、evalでBindingオブジェクトに渡すことでそのスコープでコードを評価できる
    • すごい!こんな仕組みだったんだ
  • pryというgemにはオブジェクトのスコープでインタラクティブセッションを開くObject#pryメソッドが定義されている
  • これを用いてbindingのpryを呼び出すことでデバッガのように使うことができる

irb

  • irbは標準入力やファイルをパースして、それぞれの行をevalに渡すプログラム
  • こうしたプログラムはコードプロセッサと呼ばれる

eval の問題点

  • コード文字列はシンタックスハイライトが効かない
  • 外部からのコード文字列を受け取るとコードインジェクションの危険がある

オブジェクトの汚染とセーフレベル

  • 潜在的に安全ではないオブジェクトに自動的に汚染の印がつく
    • 汚染オブジェクトには、ウェブフォーム、ファイル、コマンドライン、システム変数、プログラムが読み込んだ文字列などが含まれる
  • 汚染された文字列を操作して新しい文字列を作ると、その新しい文字列も汚染される
  • taind?メソッドで確認できる
  • Ruby はセーフレベルを設定できる
  • セーフレベルが0より大きいと汚染した文字列を評価できない

フックメソッド

  • イベントをキャッチできるメソッド
  • Class#inheritedは継承されたときに実行してくれるメソッド
    • デフォルトでは何もしないのでオーバーライドして使う
  • Module#includedModule#prependedでモジュールのライフサイクルにプラグインできる
  • Module#extendedでモジュールを拡張したときに入り込める
  • 特異メソッドをキャッチするときにはKernel#singleton_method_addedなどを使う

メタプログラミングRuby読みメモ 4

5章 クラス定義

  • Ruby の class キーワードはオブジェクトの動作を規定しているだけでなく、実際にコードを実行している
  • クラス(やモジュール)定義の中ではクラスがカレントオブジェクトselfになる

カレントクラス

  • Ruby は常にカレントオブジェクトselfを持っている
  • 同様にカレントクラス(およびモジュール)も持っている
  • メソッドを定義すると、それはカレントクラスのインスタンスメソッドになる
  • class キーワードでクラスをオープンするとカレントクラスになる
  • メソッドの中ではカレントオブジェクトのクラスがカレントクラスになる

class_eval

  • Module#class_eval はそこにあるクラスのコンテキストでブロック評価する
  • class_eval は self とカレントクラスを変更する
  • class キーワードは定数を必要とするが、class_evalはクラスを参照する変数ならなんでも使える

クラスインスタンス変数

  • インスタンス変数はカレントオブジェクトselfに属しているもの
  • クラス定義の中ではselfはクラス自身となる
  • 従ってクラスのインスタンス変数とクラスのオブジェクトのインスタンス変数は別物となる

  • クラス変数はクラスインスタンス変数と異なり、クラスのインスタンスやサブクラスからもアクセスできる

    • main で定義したクラス変数はObjectで定義されるのでObjectを継承した他のクラスで同名のクラス変数を用いると上書きされてしまう罠があったりする

特異メソッド

  • 単一のオブジェクトのみもつメソッド
  • 追加しても他の同じクラスのオブジェクトに影響はない
  • クラスメソッドはクラスの特異メソッド

クラスマクロ

  • Ruby にはアトリビュートがないが、Module#attr_*を使うと読み書きのメソッドを生成できる
  • この attracts_accessor などのようなメソッドをクラスマクロと呼ぶ
  • キーワードのように見えるがクラスメソッドである

特異クラス

  • オブジェクトはクラスではないのでメソッドを持つことはできない
  • オブジェクトは自身のクラスとは別に特別なクラスを持っており、特異クラスと呼ばれる
  • 特異メソッドはここに存在する
  • クラスメソッドはクラスの特異クラスが持つメソッドである
  • 特異クラスのスーパークラススーパークラスの特異クラスの関係
  • 特異クラスはクラスであるため特異クラスも特異クラスを持つ

Rubyのオブジェクトの7つのルール

  • オブジェクトは1種類しかない
    • 通常のオブジェクトかモジュール
  • モジュールは1種類しかない
    • 通常のモジュール、クラス、特異クラス
  • メソッドは1種類しかない。メソッドはモジュール(大半はクラス)が持つ
  • 全てのオブジェクトは本物のクラスを持つ
    • 通常のクラスか特異クラス
  • 全てのクラスは一つの祖先を持つ(BasicObject除く)
  • オブジェクトの特異クラスのスーパークラスはオブジェクトのクラス
  • メソッドを呼び出すときは本物のクラスを探し、そこから継承チェーンを登っていく

メタプログラミングRuby読みメモ 3

4章 ブロック

  • ブロックはスコープを制御するのに強力なツール
  • ブロックは「呼び出し可能オブジェクト」
    • Proc や lambda などが含まれる

ブロックの基本

  • ブロックは、波括弧またはdo...endキーワードで定義できる
    • 1行のブロックには波括弧、複数行のブロックにはdo...endを使うという習慣がある
  • ブロックを定義できるのはメソッドを呼び出すときだけ
    • ブロックはメソッドに渡され、メソッドはyieldキーワードを使ってブロックをコールバックする
  • ブロックは引数を受け取ることもできる
    • ブロックをコールバックするときに、メソッドを呼び出すときと同じように引数を渡せる
  • メソッドの内部でKernel#block_given?メソッドでブロックの有無を確認できる

ブロックはクロージャ

  • ブロックのコードは単体では実行できない
    • ローカル変数、インスタンス変数、selfといった環境が必要
    • これらはオブジェクトに紐づけられた名前のことで、束縛とも呼ばれる
  • ブロックにはコードと束縛の両方が含まれる
  • ブロックを定義すると、その時点その場所にある束縛を取得する
  • ブロックをメソッドに渡したときにその束縛も一緒に連れて行く
  • ブロックの中で新しい束縛を定義することもできるが、ブロックが終了した時点で消える
  • このような特性からブロックはクロージャとも呼ばれる

スコープ

  • Ruby のスコープはきちんと区別されている
  • 新しいスコープに入ると以前のスコープは見えなくなる
  • メソッドから同じオブジェクトにある他のメソッドを呼び出すと、インスタンス変数は変わらない
  • グローバル変数はどのスコープからでもアクセスできる
  • トップレベルのインスタンス変数は、他のオブジェクトがselfになるまで呼び出せる

スコープゲート

  • プログラムがスコープを切り替えて新しいスコープをオープンする場所は3つある
    • クラス定義
    • モジュール定義
    • メソッド
  • これらの境界線はclass, module, def といったキーワードで印つけられている
  • この3つのキーワードは、スコープゲートとして振る舞う

  • class の代わりに Class.new で定義するとスコープを切り替えずに定義できる

  • def の代わりに define_method を使うとスコープを切り替えずにメソッドを定義できる

instance_eval

  • BasicObject#instance_evalに渡したブロックはレシーバをselfにしてから評価される
  • instance_evalに渡したブロックのことをコンテキスト探査機と呼ぶ
  • これを用いて、irbからオブジェクトの中身を見たいときに使うことができる

クリーンルーム

  • ブロックを評価するためだけにオブジェクトを生成することもある
  • このようなオブジェクトをクリーンルームと呼ぶ
  • 衝突を避けるためメソッドやインスタンス変数は増やさない方がいい

呼び出し可能オブジェクト

Proc オブジェクト

  • Ruby ではほぼ全てがオブジェクトだがブロックはオブジェクトではない
  • ブロックを保管しておいて後で実行したいときにオブジェクトが必要
  • Proc はブロックをオブジェクトにしたもの
  • オブジェクトになったブロックを評価するにはProc#callを呼び出す
  • Proc は遅延評価である
  • ブロックをProcに変換するカーネルメソッドはlambdaとprocと2つ用意されている

&修飾

  • ブロックはメソッドに渡す無名引数のようなもの
  • 通常はyieldを使って実行するが、yieldでは足りないケースが2つある
    • 他のメソッドにブロックを渡したいとき
    • ブロックをProcに変換したいとき
  • メソッドの引数列の最後に&修飾した名前を置くとブロックを束縛できる
  • これはProcとして変換されている

lambda

  • lambda で作られた Proc は他の Proc とは違う
  • Proc#lambda?で確認できる
  • lambda の return は lambda から戻るだけだが、Proc での return は Proc が定義されたスコープから戻る
  • lambda の方が Proc よりも引数の値に厳しい
    • Proc は引数の数が多いと切り落とし、少ないとnilを入れてくれる
    • lambda では ArgumentError となる

Method オブジェクト

  • Object#methodでメソッドそのものをオブジェクトとして取得できる
  • to_proc で Proc に変換できる
  • lambda は定義されたスコープで評価されるが、Methodは所属するオブジェクトのスコープで評価される

UnboundMethod

  • 元のクラスやモジュールから引き離されたメソッド
  • Method#unbindでMethodから変換できる
  • instance_methodを呼び出せばUnboundMethodを直接取得できる
  • UnboundMethodを呼び出すことはできないが、ここから通常のメソッドを生成することができる
    • UnboundMethod#bindで束縛することができる
    • ただし、元のクラスと同じクラス、もしくはサブクラスにしか束縛できない

メタプログラミングRuby読みメモ 2

3章 メソッド

重複問題

  • コードの重複は間違いを犯すかのせいをはらむ
  • Ruby はいくつか解決方法を持つ

動的メソッド

  • メソッドを動的に呼び出す
    • メソッドを呼び出すには通常はドット記法を使う
    • Object#sendを使って呼び出す方法もある
    • send の第一引数はオブジェクトに送信するメッセージ、つまりメソッド名である
      • 送るメソッド名にはシンボルまたは文字列が使える
    • send を用いることで、呼び出したいメソッド名が通常の引数となり、実行時に呼び出すメソッドを指定できる
    • これを動的ディスパッチと呼ぶ
    • send メソッドは privateのメソッドすら呼び出すことができる
  • メソッドを動的に定義する
    • Module#define_method を使うと、メソッドを動的に定義することもできる
      • メソッド名とブロックを渡し、ブロックがメソッドの本体となる
      • define_methodが実行されたクラスのインスタンスメソッドとして定義される
    • これを動的メソッドと呼ぶ

method_missing

  • Ruby にはメソッド呼び出しを制限するコンパイラは存在しないため、存在しないメソッドも呼び出すことができる
  • メソッドが見つからなかった時、RubyBasicObject#method_missingを呼び出す
  • method_missing は private だが、send を使えば呼び出すこともできる
  • method_missing は NoMethodError を出す

  • NoMethodErrorってこういう仕組みだったんだ...すごい

  • method_missing をオーバーライドすれば受け取ったメッセージを捕まえることができる

    • ブロックが渡されていればそれも捕まえることができる
  • method_missing をオーバーライドすることで、実際には存在しないメソッドを呼び出せる
  • method_missing でのバグは潰しにくい
    • method_missing をオーバーライドしているため NoMethodError を出せずに無限ループしてしまうこともある

ゴーストメソッド

  • 同じようなメソッドをたくさん定義しなければいけないときには、いちいち定義せずにmethod_missingの呼び出しに反応すればいいこともある
  • ここで処理されたメッセージは呼び出し側からは通常のメソッドのように見えるが、レシーバー側には定義されていない
  • これをゴーストメソッドと呼ぶ
  • ゴーストメソッドを補足して、他のオブジェクトに転送するオブジェクトを動的プロキシと呼ぶ
  • respond_to_missing?をオーバーライドすればrespond_toにも反応できるようになる
  • 常に super を呼び出す、respond_to_missing?を再定義することを基本に実装する必要がある
  • methods?の結果一覧に登場しないなどの通常のメソッドとの振る舞いの違いがある
  • 「可能であれば動的メソッドを使い、仕方がなければゴーストメソッドを使う」

ブランクスレート

  • ゴーストメソッドと継承した本物のメソッドの名前が衝突すると、後者が呼び出される
  • 継承したメソッドが必要ない場合は削除すれば衝突を解消できる
  • 名前の衝突を防ぎたいのであれば、不要メソッドは削除するべき
    • undef_method で削除できる
  • 最小限のメソッドしかない状態のクラスをブランクスレートと呼ぶ
  • Rubyにはこのブランクスレートが用意されている
    • それがBasicObjectである