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 かどうかとか乱暴すぎるなぁ。削除すべき場合というのは
- page table entry がページ・ディスクリプタを指してる時
ってことで良いのかどうか。でもこれってページディスクリプタの配列全部ナメなきゃ駄目じゃん。新規作成、な場合を切り分けてあげれば良いのでしょうけれど、どうしたものかな。
とりあえず 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 側でちゃんと仕事すれば良いのか