以下の状況では、ページ エラー例外 (ページ欠落割り込みとも呼ばれます) が発生します:
1. 対応するページ ディレクトリ エントリまたはページ テーブル エントリが空、つまり、リニア アドレスと物理アドレス間のマッピング関係アドレスがまだ確立されていないか、取り消されていません。
2. 対応する物理ページがメモリ内にありません。この記事ではこの状況について説明します。
3. 命令で指定されたアクセス方法が、「読み取り専用」ページを書き込もうとしているなど、ページの権限と一致しません。
マッピングが確立されているが、ページ テーブル エントリの最後のビット P が 0 で、ページがメモリ内にないことを示し、ページ テーブル エントリ全体が以下に示すとおりであると仮定します。ディスク デバイス内のページの場所、つまりディスク デバイス。論理ページ番号とタイプは、ページがどのディスク デバイスにあるかを示します。页 1
図 1 ページ切り替え項目
ここでは、CPU の動作がページ異常サービスプログラム本体である DO_PAGE_FAULT() の入り口まで到達したものとします。
コードは次のとおりです: Arch/i386/mm/fault.c
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code){ struct task_struct *tsk; struct mm_struct *mm; struct vm_area_struct * vma; unsigned long address; unsigned long page; unsigned long fixup; int write; siginfo_t info; /* get the address */ __asm__("movl %%cr2,%0":"=r" (address));//把映射的失败的地址保存在address中 tsk = current;//task_struct /* * We fault-in kernel-space virtual memory on-demand. The * 'reference' page table is init_mm.pgd. * * NOTE! We MUST NOT take any locks for this case. We may * be in an interrupt or a critical region, and should * only copy the information from the master page table, * nothing more. */ if (address >= TASK_SIZE) goto vmalloc_fault; mm = tsk->mm;//mm_struct info.si_code = SEGV_MAPERR; /* * If we're in an interrupt or have no user * context, we must not take the fault.. */ if (in_interrupt() || !mm) goto no_context; down(&mm->mmap_sem); vma = find_vma(mm, address);//找出结束地址大于给定地址的第一个区间。 if (!vma)//没有找到,说明没有一个区间的结束地址高于给定的地址,参考上图,说明这个地址是在堆栈之下,也就是3G字节以上了。 goto bad_area; if (vma->vm_start <= address)//起始地址不高于address,说明映射已经建立,转到good_area去进一步检查失败原因。 goto good_area; if (!(vma->vm_flags & VM_GROWSDOWN)) goto bad_area; ..../* * Ok, we have a good vm_area for this memory access, so * we can handle it.. */good_area: info.si_code = SEGV_ACCERR; write = 0; switch (error_code & 3) {// 110 & 011 = 2 default: /* 3: write, present */#ifdef TEST_VERIFY_AREA if (regs->cs == KERNEL_CS) printk("WP fault at %08lx\n", regs->eip);#endif /* fall through */ case 2: /* write, not present */ if (!(vma->vm_flags & VM_WRITE)) goto bad_area; write++;//执行到这里 break; case 1: /* read, present */ goto bad_area; case 0: /* read, not present */ if (!(vma->vm_flags & (VM_READ | VM_EXEC))) goto bad_area; } /* * If for any reason at all we couldn't handle the fault, * make sure we exit gracefully rather than endlessly redo * the fault. */ switch (handle_mm_fault(mm, vma, address, write)) { case 1: tsk->min_flt++; break; case 2: tsk->maj_flt++; break; case 0: goto do_sigbus; default: goto out_of_memory; } /* * Did it hit the DOS screen memory VA from vm86 mode? */ if (regs->eflags & VM_MASK) { unsigned long bit = (address - 0xA0000) >> PAGE_SHIFT; if (bit < 32) tsk->thread.screen_bitmap |= 1 << bit; } up(&mm->mmap_sem); return; .......}
カーネルの割り込み/例外応答メカニズムも 2 つのパラメーターを渡します。 1 つは pt_regs 構造体ポインタ regs で、例外が発生する前の CPU 内の各レジスタの内容のコピーを指します。 error_code はさらに、マッピング失敗の具体的な理由を示します。
Error_code:
bit 0 == 0 はページが見つからないことを意味し、1 は保護違反を意味します
bit 1 == 0 は読み取りを意味し、1 は書き込みを意味します
bit 2 == 0 はカーネルを意味し、1 はユーザーモードを意味します時刻、error_code は 110、ユーザー モード、ページがメモリにありません、書き込みます。
int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct * vma, unsigned long address, int write_access){ int ret = -1; pgd_t *pgd; pmd_t *pmd; pgd = pgd_offset(mm, address);//返回页面表项指针 pmd = pmd_alloc(pgd, address);//中转了一下,还是页目录表项指针 if (pmd) { pte_t * pte = pte_alloc(pmd, address);//返回指向页表项的指针 if (pte) ret = handle_pte_fault(mm, vma, address, write_access, pte); } return ret;}
handle_pte_fault 関数、次のとおりです:
static inline int handle_pte_fault(struct mm_struct *mm, struct vm_area_struct * vma, unsigned long address, int write_access, pte_t * pte){ pte_t entry; /* * We need the page table lock to synchronize with kswapd * and the SMP-safe atomic PTE updates. */ spin_lock(&mm->page_table_lock); entry = *pte;//页表项中内容 if (!pte_present(entry)) {//页面不在内存中 /* * If it truly wasn't present, we know that kswapd * and the PTE updates will not touch it later. So * drop the lock. */ spin_unlock(&mm->page_table_lock); if (pte_none(entry))//页表项不为空 return do_no_page(mm, vma, address, write_access, pte); return do_swap_page(mm, vma, address, pte, pte_to_swp_entry(entry), write_access);//执行到这里 } if (write_access) { if (!pte_write(entry)) return do_wp_page(mm, vma, address, pte, entry); entry = pte_mkdirty(entry); } entry = pte_mkyoung(entry); establish_pte(vma, address, pte, entry); spin_unlock(&mm->page_table_lock); return 1;}
ap_page 関数は次のとおりです:
static int do_swap_page(struct mm_struct * mm, struct vm_area_struct * vma, unsigned long address, pte_t * page_table, swp_entry_t entry, int write_access){ struct page *page = lookup_swap_cache(entry);//从hash表中寻找 pte_t pte; if (!page) { lock_kernel(); swapin_readahead(entry);//预读页面 page = read_swap_cache(entry);//真正得到一个页面,这个页面可能从hash表中寻找到,因为上面预读了。或者自己申请页面,并且从盘上将其内容读进来。 unlock_kernel(); if (!page) return -1; flush_page_to_ram(page); flush_icache_page(vma, page); } mm->rss++; pte = mk_pte(page, vma->vm_page_prot);//形成页表项 /* * Freeze the "shared"ness of the page, ie page_count + swap_count. * Must lock page before transferring our swap count to already * obtained page count. */ lock_page(page); swap_free(entry); if (write_access && !is_page_shared(page)) pte = pte_mkwrite(pte_mkdirty(pte));//页表项赋予已写过对应的物理页,可进行读、写或者执行 UnlockPage(page); set_pte(page_table, pte);//页表项(属性刚才已经设置了)指向对应的页面 /* No need to invalidate - it was non-present before */ update_mmu_cache(vma, address, pte); return 1; /* Minor fault */}
1. 以下にそれぞれの機能を説明します。まず swapin_readahead 関数について説明します。
void swapin_readahead(swp_entry_t entry){ int i, num; struct page *new_page; unsigned long offset; /* * Get the number of handles we should do readahead io to. Also, * grab temporary references on them, releasing them as io completes. */ num = valid_swaphandles(entry, &offset); for (i = 0; i < num; offset++, i++) { ...... new_page = read_swap_cache_async(SWP_ENTRY(SWP_TYPE(entry), offset), 0); if (new_page != NULL) page_cache_release(new_page);//page使用计数减1 swap_free(SWP_ENTRY(SWP_TYPE(entry), offset)); } return;}
次の説明によると、__get_free_page、ページ使用数は 1、add_to_swap_cache、この時点でのページ使用数プラス 1。 page_cache_release、ページ使用量 再びカウントが1になります。プロセスが要求するまで 2 になります。
read_swap_cache_async 関数は次のとおりです:
struct page * read_swap_cache_async(swp_entry_t entry, int wait){ struct page *found_page = 0, *new_page; unsigned long new_page_addr; /* * Make sure the swap entry is still in use. */ if (!swap_duplicate(entry)) /* Account for the swap cache */ goto out; /* * Look for the page in the swap cache. */ found_page = lookup_swap_cache(entry);//假设没有找到 if (found_page) goto out_free_swap; new_page_addr = __get_free_page(GFP_USER);//刚申请的page结构,使用计数为1 if (!new_page_addr) goto out_free_swap; /* Out of memory */ new_page = virt_to_page(new_page_addr);//转化成对应的page结构指针 /* * Check the swap cache again, in case we stalled above. */ found_page = lookup_swap_cache(entry);//假设没有找到 if (found_page) goto out_free_page; /* * Add it to the swap cache and read its contents. */ lock_page(new_page); add_to_swap_cache(new_page, entry);//加入到对应的链表上 rw_swap_page(READ, new_page, wait);//真正的把磁盘上的数据读到新申请的page上,等待块设备驱动一章再来看 return new_page;out_free_page: page_cache_release(new_page);out_free_swap: swap_free(entry);out: return found_page;}
void add_to_swap_cache(struct page *page, swp_entry_t entry){ unsigned long flags;#ifdef SWAP_CACHE_INFO swap_cache_add_total++;#endif if (!PageLocked(page)) BUG(); if (PageTestandSetSwapCache(page)) BUG(); if (page->mapping) BUG(); flags = page->flags & ~((1 << PG_error) | (1 << PG_arch_1)); page->flags = flags | (1 << PG_uptodate); add_to_page_cache_locked(page, &swapper_space, entry.val);}
add_ to_page_cache _locked 関数のコードは次のとおりです:
void add_to_page_cache_locked(struct page * page, struct address_space *mapping, unsigned long index){ if (!PageLocked(page)) BUG(); page_cache_get(page);//增加了使用计数,现在使用计数为2 spin_lock(&pagecache_lock); page->index = index;//index存着页面交换项 add_page_to_inode_queue(mapping, page);//page->list链入mapping->clean_pages add_page_to_hash_queue(page, page_hash(mapping, index));//page->next_hash和page->pprev_hash链入全局的Hash表 lru_cache_add(page);//page->lru链入了全局的active_list spin_unlock(&pagecache_lock);}
add_page_to_inode_queue 関数のコードは次のとおりです:
static inline void add_page_to_inode_queue(struct address_space *mapping, struct page * page){ struct list_head *head = &mapping->clean_pages; mapping->nrpages++; list_add(&page->list, head);//page->list链入mapping->clean_pages page->mapping = mapping;//mapping指向了swapper_space}
struct address_space swapper_space = { LIST_HEAD_INIT(swapper_space.clean_pages), LIST_HEAD_INIT(swapper_space.dirty_pages), LIST_HEAD_INIT(swapper_space.locked_pages), 0, /* nrpages */ &swap_aops,};
static void add_page_to_hash_queue(struct page * page, struct page **p){ struct page *next = *p;//page->next_hash和page->pprev_hash链入全局的Hash表 *p = page; page->next_hash = next; page->pprev_hash = p; if (next) next->pprev_hash = &page->next_hash; if (page->buffers) PAGE_BUG(page); atomic_inc(&page_cache_size);}
lru_cache_add 関数は次のとおりです:
void lru_cache_add(struct page * page){ spin_lock(&pagemap_lru_lock); if (!PageLocked(page)) BUG(); DEBUG_ADD_PAGE add_page_to_active_list(page); /* This should be relatively rare */ if (!page->age) deactivate_page_nolock(page); spin_unlock(&pagemap_lru_lock);}
add_page_to_active_list 関数は次のとおりです:
#define add_page_to_active_list(page) { \ DEBUG_ADD_PAGE \ ZERO_PAGE_BUG \ SetPageActive(page); \ list_add(&(page)->lru, &active_list); \ //page->lru链入了全局的active_list nr_active_pages++; \ //全局的nr_active_pages加1}
2. read_swap_cache 関数については以下で説明します。次のように:
#define read_swap_cache(entry) read_swap_cache_async(entry, 1);
struct page * read_swap_cache_async(swp_entry_t entry, int wait){ struct page *found_page = 0, *new_page; unsigned long new_page_addr; /* * Make sure the swap entry is still in use. */ if (!swap_duplicate(entry)) /* Account for the swap cache */ goto out; /* * Look for the page in the swap cache. */ found_page = lookup_swap_cache(entry);//假设在hash表中找到对应的page,有进程认领了,使用计数为2 if (found_page) goto out_free_swap; new_page_addr = __get_free_page(GFP_USER); if (!new_page_addr) goto out_free_swap; /* Out of memory */ new_page = virt_to_page(new_page_addr); /* * Check the swap cache again, in case we stalled above. */ found_page = lookup_swap_cache(entry);//有可能__get_free_page,没有足够的可分配的页面,切换到其他进程了,再切回来时,在Hash表中再寻找一遍 if (found_page) goto out_free_page; /* * Add it to the swap cache and read its contents. */ lock_page(new_page); add_to_swap_cache(new_page, entry); rw_swap_page(READ, new_page, wait); return new_page;out_free_page: page_cache_release(new_page);out_free_swap: swap_free(entry);out: return found_page;}
struct page * lookup_swap_cache(swp_entry_t entry){ struct page *found;#ifdef SWAP_CACHE_INFO swap_cache_find_total++;#endif while (1) { /* * Right now the pagecache is 32-bit only. But it's a 32 bit index. =) */repeat: found = find_lock_page(&swapper_space, entry.val);//entry.val为页面交换项 if (!found) return 0; /* * Though the "found" page was in the swap cache an instant * earlier, it might have been removed by refill_inactive etc. * Re search ... Since find_lock_page grabs a reference on * the page, it can not be reused for anything else, namely * it can not be associated with another swaphandle, so it * is enough to check whether the page is still in the scache. */ if (!PageSwapCache(found)) { UnlockPage(found); page_cache_release(found); goto repeat; } if (found->mapping != &swapper_space) goto out_bad;#ifdef SWAP_CACHE_INFO swap_cache_find_success++;#endif UnlockPage(found); return found;}
find_lock_page 関数、次のように:
#define find_lock_page(mapping, index) \ __find_lock_page(mapping, index, page_hash(mapping, index))
すごいです
__find_page_nolock 関数、次のように:
struct page * __find_lock_page (struct address_space *mapping, unsigned long offset, struct page **hash){ struct page *page; /* * We scan the hash list read-only. Addition to and removal from * the hash-list needs a held write-lock. */repeat: spin_lock(&pagecache_lock); page = __find_page_nolock(mapping, offset, *hash);//得到了hash表的其中一个链表的头 if (page) { page_cache_get(page);//增加使用计数 spin_unlock(&pagecache_lock); lock_page(page); /* Is the page still hashed? Ok, good.. */ if (page->mapping) return page; /* Nope: we raced. Release and try again.. */ UnlockPage(page); page_cache_release(page); goto repeat; } spin_unlock(&pagecache_lock); return NULL;}
ページ交換項目に基づいて、ハッシュテーブル内のページ構造を検索します。
static inline struct page * __find_page_nolock(struct address_space *mapping, unsigned long offset, struct page *page){ goto inside; for (;;) { page = page->next_hash;//从hash表中寻找inside: if (!page) goto not_found; if (page->mapping != mapping) continue; if (page->index == offset) break; } /* * Touching the page may move it to the active list. * If we end up with too few inactive pages, we wake * up kswapd. */ age_page_up(page); if (inactive_shortage() > inactive_target / 2 && free_shortage()) wakeup_kswapd(0);not_found: return page;}
Swapin_readahead は、プロセスによって要求されていない場合、多くのページを事前に読み取ります。使用カウントは 1 です。