Commit 6a2ea338 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] avoid allocating pte_chains for unshared pages

Patch from David McCracken.  It is an optimisation to the rmap
pte_chains.

In the common case where a page is mapped by only a single pte, we
don't need to allocate a pte_chain structure.  Just make the page's
pte_chain pointer point straight at that pte and flag this with
PG_direct.
parent fa08cc83
...@@ -157,8 +157,11 @@ struct page { ...@@ -157,8 +157,11 @@ struct page {
updated asynchronously */ updated asynchronously */
struct list_head lru; /* Pageout list, eg. active_list; struct list_head lru; /* Pageout list, eg. active_list;
protected by pagemap_lru_lock !! */ protected by pagemap_lru_lock !! */
struct pte_chain * pte_chain; /* Reverse pte mapping pointer. union {
struct pte_chain * chain; /* Reverse pte mapping pointer.
* protected by PG_chainlock */ * protected by PG_chainlock */
pte_t * direct;
} pte;
unsigned long private; /* mapping-private opaque data */ unsigned long private; /* mapping-private opaque data */
/* /*
......
...@@ -64,8 +64,10 @@ ...@@ -64,8 +64,10 @@
#define PG_private 12 /* Has something at ->private */ #define PG_private 12 /* Has something at ->private */
#define PG_writeback 13 /* Page is under writeback */ #define PG_writeback 13 /* Page is under writeback */
#define PG_nosave 15 /* Used for system suspend/resume */ #define PG_nosave 14 /* Used for system suspend/resume */
#define PG_chainlock 16 /* lock bit for ->pte_chain */ #define PG_chainlock 15 /* lock bit for ->pte_chain */
#define PG_direct 16 /* ->pte_chain points directly at pte */
/* /*
* Global page accounting. One instance per CPU. * Global page accounting. One instance per CPU.
...@@ -217,6 +219,12 @@ extern void get_page_state(struct page_state *ret); ...@@ -217,6 +219,12 @@ extern void get_page_state(struct page_state *ret);
#define ClearPageNosave(page) clear_bit(PG_nosave, &(page)->flags) #define ClearPageNosave(page) clear_bit(PG_nosave, &(page)->flags)
#define TestClearPageNosave(page) test_and_clear_bit(PG_nosave, &(page)->flags) #define TestClearPageNosave(page) test_and_clear_bit(PG_nosave, &(page)->flags)
#define PageDirect(page) test_bit(PG_direct, &(page)->flags)
#define SetPageDirect(page) set_bit(PG_direct, &(page)->flags)
#define TestSetPageDirect(page) test_and_set_bit(PG_direct, &(page)->flags)
#define ClearPageDirect(page) clear_bit(PG_direct, &(page)->flags)
#define TestClearPageDirect(page) test_and_clear_bit(PG_direct, &(page)->flags)
/* /*
* inlines for acquisition and release of PG_chainlock * inlines for acquisition and release of PG_chainlock
*/ */
......
...@@ -92,7 +92,7 @@ static void __free_pages_ok (struct page *page, unsigned int order) ...@@ -92,7 +92,7 @@ static void __free_pages_ok (struct page *page, unsigned int order)
BUG_ON(PageLRU(page)); BUG_ON(PageLRU(page));
BUG_ON(PageActive(page)); BUG_ON(PageActive(page));
BUG_ON(PageWriteback(page)); BUG_ON(PageWriteback(page));
BUG_ON(page->pte_chain != NULL); BUG_ON(page->pte.chain != NULL);
if (PageDirty(page)) if (PageDirty(page))
ClearPageDirty(page); ClearPageDirty(page);
BUG_ON(page_count(page) != 0); BUG_ON(page_count(page) != 0);
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
/* /*
* Locking: * Locking:
* - the page->pte_chain is protected by the PG_chainlock bit, * - the page->pte.chain is protected by the PG_chainlock bit,
* which nests within the pagemap_lru_lock, then the * which nests within the pagemap_lru_lock, then the
* mm->page_table_lock, and then the page lock. * mm->page_table_lock, and then the page lock.
* - because swapout locking is opposite to the locking order * - because swapout locking is opposite to the locking order
...@@ -71,10 +71,15 @@ int page_referenced(struct page * page) ...@@ -71,10 +71,15 @@ int page_referenced(struct page * page)
if (TestClearPageReferenced(page)) if (TestClearPageReferenced(page))
referenced++; referenced++;
/* Check all the page tables mapping this page. */ if (PageDirect(page)) {
for (pc = page->pte_chain; pc; pc = pc->next) { if (ptep_test_and_clear_young(page->pte.direct))
if (ptep_test_and_clear_young(pc->ptep))
referenced++; referenced++;
} else {
/* Check all the page tables mapping this page. */
for (pc = page->pte.chain; pc; pc = pc->next) {
if (ptep_test_and_clear_young(pc->ptep))
referenced++;
}
} }
return referenced; return referenced;
} }
...@@ -108,22 +113,39 @@ void page_add_rmap(struct page * page, pte_t * ptep) ...@@ -108,22 +113,39 @@ void page_add_rmap(struct page * page, pte_t * ptep)
pte_chain_lock(page); pte_chain_lock(page);
{ {
struct pte_chain * pc; struct pte_chain * pc;
for (pc = page->pte_chain; pc; pc = pc->next) { if (PageDirect(page)) {
if (pc->ptep == ptep) if (page->pte.direct == ptep)
BUG(); BUG();
} else {
for (pc = page->pte.chain; pc; pc = pc->next) {
if (pc->ptep == ptep)
BUG();
}
} }
} }
pte_chain_unlock(page); pte_chain_unlock(page);
#endif #endif
pte_chain = pte_chain_alloc();
pte_chain_lock(page); pte_chain_lock(page);
/* Hook up the pte_chain to the page. */ if (PageDirect(page)) {
pte_chain->ptep = ptep; /* Convert a direct pointer into a pte_chain */
pte_chain->next = page->pte_chain; pte_chain = pte_chain_alloc();
page->pte_chain = pte_chain; pte_chain->ptep = page->pte.direct;
pte_chain->next = NULL;
page->pte.chain = pte_chain;
ClearPageDirect(page);
}
if (page->pte.chain) {
/* Hook up the pte_chain to the page. */
pte_chain = pte_chain_alloc();
pte_chain->ptep = ptep;
pte_chain->next = page->pte.chain;
page->pte.chain = pte_chain;
} else {
page->pte.direct = ptep;
SetPageDirect(page);
}
pte_chain_unlock(page); pte_chain_unlock(page);
} }
...@@ -149,18 +171,38 @@ void page_remove_rmap(struct page * page, pte_t * ptep) ...@@ -149,18 +171,38 @@ void page_remove_rmap(struct page * page, pte_t * ptep)
return; return;
pte_chain_lock(page); pte_chain_lock(page);
for (pc = page->pte_chain; pc; prev_pc = pc, pc = pc->next) {
if (pc->ptep == ptep) { if (PageDirect(page)) {
pte_chain_free(pc, prev_pc, page); if (page->pte.direct == ptep) {
page->pte.direct = NULL;
ClearPageDirect(page);
goto out; goto out;
} }
} else {
for (pc = page->pte.chain; pc; prev_pc = pc, pc = pc->next) {
if (pc->ptep == ptep) {
pte_chain_free(pc, prev_pc, page);
/* Check whether we can convert to direct */
pc = page->pte.chain;
if (!pc->next) {
page->pte.direct = pc->ptep;
SetPageDirect(page);
pte_chain_free(pc, NULL, NULL);
}
goto out;
}
}
} }
#ifdef DEBUG_RMAP #ifdef DEBUG_RMAP
/* Not found. This should NEVER happen! */ /* Not found. This should NEVER happen! */
printk(KERN_ERR "page_remove_rmap: pte_chain %p not present.\n", ptep); printk(KERN_ERR "page_remove_rmap: pte_chain %p not present.\n", ptep);
printk(KERN_ERR "page_remove_rmap: only found: "); printk(KERN_ERR "page_remove_rmap: only found: ");
for (pc = page->pte_chain; pc; pc = pc->next) if (PageDirect(page)) {
printk("%p ", pc->ptep); printk("%p ", page->pte.direct);
} else {
for (pc = page->pte.chain; pc; pc = pc->next)
printk("%p ", pc->ptep);
}
printk("\n"); printk("\n");
printk(KERN_ERR "page_remove_rmap: driver cleared PG_reserved ?\n"); printk(KERN_ERR "page_remove_rmap: driver cleared PG_reserved ?\n");
#endif #endif
...@@ -270,25 +312,41 @@ int try_to_unmap(struct page * page) ...@@ -270,25 +312,41 @@ int try_to_unmap(struct page * page)
if (!page->mapping) if (!page->mapping)
BUG(); BUG();
for (pc = page->pte_chain; pc; pc = next_pc) { if (PageDirect(page)) {
next_pc = pc->next; ret = try_to_unmap_one(page, page->pte.direct);
switch (try_to_unmap_one(page, pc->ptep)) { if (ret == SWAP_SUCCESS) {
case SWAP_SUCCESS: page->pte.direct = NULL;
/* Free the pte_chain struct. */ ClearPageDirect(page);
pte_chain_free(pc, prev_pc, page); }
break; } else {
case SWAP_AGAIN: for (pc = page->pte.chain; pc; pc = next_pc) {
/* Skip this pte, remembering status. */ next_pc = pc->next;
prev_pc = pc; switch (try_to_unmap_one(page, pc->ptep)) {
ret = SWAP_AGAIN; case SWAP_SUCCESS:
continue; /* Free the pte_chain struct. */
case SWAP_FAIL: pte_chain_free(pc, prev_pc, page);
return SWAP_FAIL; break;
case SWAP_ERROR: case SWAP_AGAIN:
return SWAP_ERROR; /* Skip this pte, remembering status. */
prev_pc = pc;
ret = SWAP_AGAIN;
continue;
case SWAP_FAIL:
ret = SWAP_FAIL;
break;
case SWAP_ERROR:
ret = SWAP_ERROR;
break;
}
}
/* Check whether we can convert to direct pte pointer */
pc = page->pte.chain;
if (pc && !pc->next) {
page->pte.direct = pc->ptep;
SetPageDirect(page);
pte_chain_free(pc, NULL, NULL);
} }
} }
return ret; return ret;
} }
...@@ -336,7 +394,7 @@ static inline void pte_chain_free(struct pte_chain * pte_chain, ...@@ -336,7 +394,7 @@ static inline void pte_chain_free(struct pte_chain * pte_chain,
if (prev_pte_chain) if (prev_pte_chain)
prev_pte_chain->next = pte_chain->next; prev_pte_chain->next = pte_chain->next;
else if (page) else if (page)
page->pte_chain = pte_chain->next; page->pte.chain = pte_chain->next;
spin_lock(&pte_chain_freelist_lock); spin_lock(&pte_chain_freelist_lock);
pte_chain_push(pte_chain); pte_chain_push(pte_chain);
......
...@@ -48,7 +48,7 @@ static inline int page_mapping_inuse(struct page * page) ...@@ -48,7 +48,7 @@ static inline int page_mapping_inuse(struct page * page)
struct address_space *mapping = page->mapping; struct address_space *mapping = page->mapping;
/* Page is in somebody's page tables. */ /* Page is in somebody's page tables. */
if (page->pte_chain) if (page->pte.chain)
return 1; return 1;
/* XXX: does this happen ? */ /* XXX: does this happen ? */
...@@ -151,7 +151,7 @@ shrink_cache(int nr_pages, zone_t *classzone, ...@@ -151,7 +151,7 @@ shrink_cache(int nr_pages, zone_t *classzone,
* *
* XXX: implement swap clustering ? * XXX: implement swap clustering ?
*/ */
if (page->pte_chain && !page->mapping && !PagePrivate(page)) { if (page->pte.chain && !page->mapping && !PagePrivate(page)) {
page_cache_get(page); page_cache_get(page);
pte_chain_unlock(page); pte_chain_unlock(page);
spin_unlock(&pagemap_lru_lock); spin_unlock(&pagemap_lru_lock);
...@@ -171,7 +171,7 @@ shrink_cache(int nr_pages, zone_t *classzone, ...@@ -171,7 +171,7 @@ shrink_cache(int nr_pages, zone_t *classzone,
* The page is mapped into the page tables of one or more * The page is mapped into the page tables of one or more
* processes. Try to unmap it here. * processes. Try to unmap it here.
*/ */
if (page->pte_chain) { if (page->pte.chain) {
switch (try_to_unmap(page)) { switch (try_to_unmap(page)) {
case SWAP_ERROR: case SWAP_ERROR:
case SWAP_FAIL: case SWAP_FAIL:
...@@ -340,7 +340,7 @@ static void refill_inactive(int nr_pages) ...@@ -340,7 +340,7 @@ static void refill_inactive(int nr_pages)
entry = entry->prev; entry = entry->prev;
pte_chain_lock(page); pte_chain_lock(page);
if (page->pte_chain && page_referenced(page)) { if (page->pte.chain && page_referenced(page)) {
list_del(&page->lru); list_del(&page->lru);
list_add(&page->lru, &active_list); list_add(&page->lru, &active_list);
pte_chain_unlock(page); pte_chain_unlock(page);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment