ページングでメモリを割り当てる処理の動きを確認できるものを作ってみた

Linuxのしくみの5章でページングの説明がありますね。なんとなく、この辺の挙動をエミュレートする感じのものでも作ってみようかなと思ったりしたので。

リポジトリはこちらです。

github.com

ページングのコードを実際に書くなら自作OSを書くのが良いと思いますが、ページの割り当てくらいならユーザーランドのプログラムでもそれっぽい感じにはなるかなということで^^;

動かし方はこんな感じで、4MiBのメモリがあって、4プロセスが5回ずつページの割り当てを行った感じが下のコマンドです。

masami@saga:~/codes/how_paging_works (master=)$ ./pg -p 4 -l 5 -m 4 | jq .

出力はjson形式になるようにしたので、pipeでjqコマンドに渡せば綺麗に見れます。

{
  "physical memory": {
    "size": "4MiB",
    "range": {
      "start": "0x7fd86ae2e000",
      "end": "0x7fd86b22e000"
    },
    "page flames": 1024
  },
  "page data": [
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae2e000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae2f000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae30000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae31000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae32000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae33000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae34000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae35000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae36000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae37000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae38000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae39000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae3a000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae3b000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae3c000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae3d000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae3e000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae3f000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae40000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae41000"
    }
  ]
}

プロセスのアドレス空間(仮想アドレス)は0x100000000000からスタートしてます。物理アドレスの方はposix_memalign(3)で取得したアドレスで、ASLRが有効な環境というのもあってどんな値かは基本的に予測できません。その代わりページサイズでアライメントを調整してあります。

このプログラムがやってるのは、こんな感じです。

  1. 最初に指定されたサイズのメモリを確保
  2. ページフレームを管理する構造体の初期化
  3. -pオプションで指定された数のスレッドを準備(プロセスの代わりにスレッドを使ってます)
  4. スレッドは-lオプションで指定された回数のループでページの割り当てを行う
  5. 全部のスレッドが終了したらjson形式のデータを出力

ページグローバルディレクトリのpgdはスレッドの開始時にそのスレッド用に作ってます。pmd、pud、pteはdo_page_fault()の中で作ってます。なんでdo_page_fault()って名前かって言うと、ページフォルトが起きた時に呼ばれる関数がこれなので(ここから更に関数の呼び出しはありますが)、それっぽい名前にしました。 他にもpgdやpte用のメモリ確保はpgd_alloc()や、pte_alloc()でLinuxを同じような名前にしてます。

これ自体は単にメモリを確保していって最後に表示するだけですが、適当に弄るともうちょっと遊べるんじゃないかと思いますm( )m

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識