6.828: Operating System Engineering (50)

ええと、page_free_list が空ではない、ということは page_remove で戻されたソレが page_alloc で使われてない、というか page_alloc が呼ばれていない、という観点で確認してみると、pgdir_walk 手続きの以下なあたりが問題の箇所なのかどうか。

    struct Page *page;
    pte_t *pte, ret;
    pde_t *pde = &pgdir[PDX(va)];

    pte = (pte_t *)PTE_ADDR(*pde);

    if (pte == NULL) {
        if (create == 0) {
            return NULL;
        }
        
        if (page_alloc(&page) == -E_NO_MEM) {

pte をそのまんま解放して良いのかなぁ。あ、違うや。

  • page_insert において page_remove で実際に va なページ・ディスクリプタが解放された場合、再度プールから取得してあげる必要がある
  • 削除対象のページ・ディスクリプタを 0 で初期化する必要がある

ということなのかな。とりあえず page_insert を以下にして

int
page_insert(pde_t *pgdir, struct Page *pp, void *va, int perm) 
{
    // Fill this function in
    pte_t *tmp = pgdir_walk(pgdir, va, 0);

    if (tmp != NULL && PTE_ADDR(*tmp) == page2pa(pp)) {
        page_remove(pgdir, va);
        if (LIST_FIRST(&page_free_list) == pp)
            page_alloc(&pp);
    }

    pte_t *pte = pgdir_walk(pgdir, va, ~0);
    if (pte == NULL)
        return -E_NO_MEM;

    pp->pp_ref++;
    *pte = page2pa(pp);
    *pte = *pte | perm | PTE_P;

    return 0;
}

page_remove は以下になってるカンジ。

void
page_remove(pde_t *pgdir, void *va)
{
    // Fill this function in
    pte_t *pte;

    struct Page *page = page_lookup(pgdir, va, &pte);

    if (page == NULL)
        return;

    page_decref(page);
    if (LIST_FIRST(&page_free_list) == page)
        *pte = 0;

    tlb_invalidate(pgdir, va);
}

あ、page_lookup は以下です。

struct Page *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
    // Fill this function in
    pte_t *pte = pgdir_walk(pgdir, va, 0);
    if (pte_store) {
        *pte_store = pte;
    }
    
    return pte == NULL ? NULL : pa2page((physaddr_t)*pte);
}

なんかヤッツケ感満点ですね。ちなみにまだ読めてなくってパスしてる試験が以下な模様。

    // check that pgdir_walk returns a pointer to the pte
    ptep = KADDR(PTE_ADDR(boot_pgdir[PDX(PGSIZE)]));
    assert(pgdir_walk(boot_pgdir, (void*)PGSIZE, 0) == ptep+PTX(PGSIZE));

    // should be able to change permissions too.
    assert(page_insert(boot_pgdir, pp2, (void*) PGSIZE, PTE_U) == 0);
    assert(check_va2pa(boot_pgdir, PGSIZE) == page2pa(pp2));
    assert(pp2->pp_ref == 1);
    assert(*pgdir_walk(boot_pgdir, (void*) PGSIZE, 0) & PTE_U);
    assert(boot_pgdir[0] & PTE_U);

現状は、いっちゃん下にある assert で panic してます。

読んでみる

ちょっと時間が空いたので確認続行。
最後のソレ以外はイメージ通り。いっちゃん下は page directory の該当要素もフラグが立ってないとダメ、って意味なのかなぁ。
# かなぁ、というかそのまんま
でもとりあえず、インテルのマニュアルにはディレクトリがスーパバイザでテーブルがユーザだったら権限はスーパバイザになる的な記述があるので、テーブルが PTE_U な権限設定だとディレクトリも、ということで良さげな気もしてます。
と、いうことで page_insert 手続きの末端が以下なカンジになって

    pte_t *pte = pgdir_walk(pgdir, va, ~0);
    if (pte == NULL)
        return -E_NO_MEM;

    pp->pp_ref++;
    *pte = page2pa(pp);
    *pte = *pte | perm | PTE_P;

    if (perm & PTE_U)
        pgdir[PDX(va)] |= PTE_U;

    return 0;
}

以下なあたりの試験にもパス。

    assert(boot_pgdir[0] & PTE_U);
    
    // should not be able to map at PTSIZE because need free page for page table
    assert(page_insert(boot_pgdir, pp0, (void*) PTSIZE, 0) < 0);

    // insert pp1 at PGSIZE (replacing pp2)
    assert(page_insert(boot_pgdir, pp1, (void*) PGSIZE, 0) == 0);
    assert(!(*pgdir_walk(boot_pgdir, (void*) PGSIZE, 0) & PTE_U));

    // should have pp1 at both 0 and PGSIZE, pp2 nowhere, ...
    assert(check_va2pa(boot_pgdir, 0) == page2pa(pp1));
    assert(check_va2pa(boot_pgdir, PGSIZE) == page2pa(pp1));
    // ... and ref counts should reflect this
    assert(pp1->pp_ref == 2);
    assert(pp2->pp_ref == 0);

例によって、いっちゃん下の assert にパスしてません。
上記の試験ですが順に

  • page table に PTE_U な権限が付けられたら page directory にもその権限が付けられる
  • 新たに page table な領域を確保できないので -E_NO_MEM が戻る
  • PGSIZE 番地に pp1 を insert (するので pp2 は解放)
  • PGSIZE 番地を指す page table の権限に PTE_U は付いてない
  • 0 番地も PGSIEZE 番地も page table は pp1
  • pp1 の ref counts は 2
  • pp2 の ref counts は 0

ええと、PGSIZE 番地に pp2 の代わりに pp1 を割り当てる時点で pp2 の ref count は減らされなければならないのですが、どうなんですかね。page_insert 手続きなのか。

もう少し戻って流れを見てみる

ええと pp2 ですが順を追ってみますと

  • PGSIZE 番地に page_insert
  • 再度 PGSIZE 番地に page_insert
  • 権限を変えてもっかい PGSIZE 番地に page_insert
  • PGSIZE 番地に pp1 が page_insert されて pp2 は解放される

page_remove なブロックで printf な確認をした所、おそらく最後のソレで削除なブロックに入ってないですな。これって

    if (tmp != NULL && PTE_ADDR(*tmp) == page2pa(pp)) {

で纏めると微妙なのか。ええと、

  • tmp が NULL じゃない場合はとにかく page_remove すべき
  • 削除した後に PTE_ADDR(*tmp) が page2pa(pp) と同じ値で LIST_FIRST(&page_free_list) が pp と同値だった場合に page_alloc してあげれば良い

はず。あら? 違うソレでカブセる場合の記述は確かにありませんね。もっかい。

  • tmp が NULL じゃない場合は page_remove すべき
  • あら? tmp が NULL ではない場合は全て page_alloc すべき、なのかな

確認してみます。

NG

なんでかとゆーと、page talbe はあるけどその先が無い (新規作成) という場合があるのか。というか pgdir_walk(pgdir, va, 0) の戻りが NULL かどうかとか乱暴すぎるなぁ。削除すべき場合というのは

ってことで良いのかどうか。でもこれってページディスクリプタの配列全部ナメなきゃ駄目じゃん。新規作成、な場合を切り分けてあげれば良いのでしょうけれど、どうしたものかな。
とりあえず pgdir_walk 手続きの戻りが何なのか、がまだあまりはっきりイメージできとらん。頭を冷やして別途リトライの方向で。

再開

ちょいメモ。

  • pgdir_walk は va のための page table entry を戻す
    • page table entry ではなく、page table のアドレスだな
  • page table entry の中身は pa2page に渡すと Page * に変換できる
    • page_insert で Page * を page2pa で変換した値が格納されてる

うう

If there is already a page mapped at 'va', it should be page_remove()d.

をどう切り分ければ良いのかが分からんぞ。
もうちょいメモ。

  • page_lookup の引数 pte_store は pgdir_walk の戻りを設定してるけれど、page table entry のアドレスを設定すべきではないか
    • または、lookup 呼び出し側で正しく処理すべき
    • 今は page_remove で page_table の先頭アドレスに 0x0 が格納されててこれはマズい
  • check_va2pa 使ってもいいかなぁ
    • va が格納されてるページのアドレスが戻ってくるはず
    • もどりを pa として PPN(pa) が npages より小さければ valid なナニ
  • 後処理ちゃんとする必要あり
    • remove でも check_va2pa 使っていいですか先生
    • これは lookup 側でちゃんと仕事すれば良いのか