NetBSD/sys/uvm/uvm_page.c

2263 lines
56 KiB
C
Raw Normal View History

/* $NetBSD: uvm_page.c,v 1.256 2024/03/05 14:33:50 thorpej Exp $ */
/*-
* Copyright (c) 2019, 2020 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Andrew Doran.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
2001-05-25 08:06:11 +04:00
/*
* Copyright (c) 1997 Charles D. Cranor and Washington University.
2001-05-25 08:06:11 +04:00
* Copyright (c) 1991, 1993, The Regents of the University of California.
*
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* The Mach Operating System project at Carnegie-Mellon University.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)vm_page.c 8.3 (Berkeley) 3/21/94
1998-02-07 14:07:38 +03:00
* from: Id: uvm_page.c,v 1.1.2.18 1998/02/06 05:24:42 chs Exp
*
*
* Copyright (c) 1987, 1990 Carnegie-Mellon University.
* All rights reserved.
2001-05-25 08:06:11 +04:00
*
* Permission to use, copy, modify and distribute this software and
* its documentation is hereby granted, provided that both the copyright
* notice and this permission notice appear in all copies of the
* software, derivative works or modified versions, and any portions
* thereof, and that both notices appear in supporting documentation.
2001-05-25 08:06:11 +04:00
*
* CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
* CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND
* FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
2001-05-25 08:06:11 +04:00
*
* Carnegie Mellon requests users of this software to return to
*
* Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
* School of Computer Science
* Carnegie Mellon University
* Pittsburgh PA 15213-3890
*
* any improvements or extensions that they make and grant Carnegie the
* rights to redistribute these changes.
*/
/*
* uvm_page.c: page ops.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: uvm_page.c,v 1.256 2024/03/05 14:33:50 thorpej Exp $");
#include "opt_ddb.h"
#include "opt_uvm.h"
#include "opt_uvmhist.h"
#include "opt_readahead.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sched.h>
#include <sys/kernel.h>
#include <sys/vnode.h>
#include <sys/proc.h>
#include <sys/radixtree.h>
#include <sys/atomic.h>
#include <sys/cpu.h>
#include <ddb/db_active.h>
#include <uvm/uvm.h>
#include <uvm/uvm_ddb.h>
#include <uvm/uvm_pdpolicy.h>
#include <uvm/uvm_pgflcache.h>
/*
* number of pages per-CPU to reserve for the kernel.
*/
#ifndef UVM_RESERVED_PAGES_PER_CPU
#define UVM_RESERVED_PAGES_PER_CPU 5
#endif
int vm_page_reserve_kernel = UVM_RESERVED_PAGES_PER_CPU;
/*
* physical memory size;
*/
psize_t physmem;
/*
* local variables
*/
/*
* these variables record the values returned by vm_page_bootstrap,
* for debugging purposes. The implementation of uvm_pageboot_alloc
* and pmap_startup here also uses them internally.
*/
static vaddr_t virtual_space_start;
static vaddr_t virtual_space_end;
/*
* we allocate an initial number of page colors in uvm_page_init(),
* and remember them. We may re-color pages as cache sizes are
* discovered during the autoconfiguration phase. But we can never
* free the initial set of buckets, since they are allocated using
* uvm_pageboot_alloc().
*/
static size_t recolored_pages_memsize /* = 0 */;
static char *recolored_pages_mem;
/*
* freelist locks - one per bucket.
*/
union uvm_freelist_lock uvm_freelist_locks[PGFL_MAX_BUCKETS]
__cacheline_aligned;
/*
* basic NUMA information.
*/
static struct uvm_page_numa_region {
struct uvm_page_numa_region *next;
paddr_t start;
paddr_t size;
u_int numa_id;
} *uvm_page_numa_region;
#ifdef DEBUG
kmutex_t uvm_zerochecklock __cacheline_aligned;
vaddr_t uvm_zerocheckkva;
#endif /* DEBUG */
/*
* These functions are reserved for uvm(9) internal use and are not
* exported in the header file uvm_physseg.h
*
* Thus they are redefined here.
*/
void uvm_physseg_init_seg(uvm_physseg_t, struct vm_page *);
void uvm_physseg_seg_chomp_slab(uvm_physseg_t, struct vm_page *, size_t);
/* returns a pgs array */
struct vm_page *uvm_physseg_seg_alloc_from_slab(uvm_physseg_t, size_t);
/*
* inline functions
*/
/*
* uvm_pageinsert: insert a page in the object.
*
* => caller must lock object
* => call should have already set pg's object and offset pointers
* and bumped the version counter
*/
static inline void
uvm_pageinsert_object(struct uvm_object *uobj, struct vm_page *pg)
{
KASSERT(uobj == pg->uobject);
KASSERT(rw_write_held(uobj->vmobjlock));
KASSERT((pg->flags & PG_TABLED) == 0);
if ((pg->flags & PG_STAT) != 0) {
/* Cannot use uvm_pagegetdirty(): not yet in radix tree. */
const unsigned int status = pg->flags & (PG_CLEAN | PG_DIRTY);
if ((pg->flags & PG_FILE) != 0) {
if (uobj->uo_npages == 0) {
struct vnode *vp = (struct vnode *)uobj;
mutex_enter(vp->v_interlock);
KASSERT((vp->v_iflag & VI_PAGES) == 0);
vp->v_iflag |= VI_PAGES;
vholdl(vp);
mutex_exit(vp->v_interlock);
}
if (UVM_OBJ_IS_VTEXT(uobj)) {
cpu_count(CPU_COUNT_EXECPAGES, 1);
}
cpu_count(CPU_COUNT_FILEUNKNOWN + status, 1);
} else {
cpu_count(CPU_COUNT_ANONUNKNOWN + status, 1);
}
}
1998-03-09 03:58:55 +03:00
pg->flags |= PG_TABLED;
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
uobj->uo_npages++;
}
static inline int
uvm_pageinsert_tree(struct uvm_object *uobj, struct vm_page *pg)
{
const uint64_t idx = pg->offset >> PAGE_SHIFT;
int error;
KASSERT(rw_write_held(uobj->vmobjlock));
error = radix_tree_insert_node(&uobj->uo_pages, idx, pg);
if (error != 0) {
return error;
}
if ((pg->flags & PG_CLEAN) == 0) {
uvm_obj_page_set_dirty(pg);
}
KASSERT(((pg->flags & PG_CLEAN) == 0) ==
uvm_obj_page_dirty_p(pg));
return 0;
}
/*
* uvm_page_remove: remove page from object.
*
* => caller must lock object
*/
static inline void
uvm_pageremove_object(struct uvm_object *uobj, struct vm_page *pg)
{
KASSERT(uobj == pg->uobject);
KASSERT(rw_write_held(uobj->vmobjlock));
KASSERT(pg->flags & PG_TABLED);
if ((pg->flags & PG_STAT) != 0) {
/* Cannot use uvm_pagegetdirty(): no longer in radix tree. */
const unsigned int status = pg->flags & (PG_CLEAN | PG_DIRTY);
if ((pg->flags & PG_FILE) != 0) {
if (uobj->uo_npages == 1) {
struct vnode *vp = (struct vnode *)uobj;
mutex_enter(vp->v_interlock);
KASSERT((vp->v_iflag & VI_PAGES) != 0);
vp->v_iflag &= ~VI_PAGES;
holdrelel(vp);
mutex_exit(vp->v_interlock);
}
if (UVM_OBJ_IS_VTEXT(uobj)) {
cpu_count(CPU_COUNT_EXECPAGES, -1);
}
cpu_count(CPU_COUNT_FILEUNKNOWN + status, -1);
} else {
cpu_count(CPU_COUNT_ANONUNKNOWN + status, -1);
}
}
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
uobj->uo_npages--;
1998-03-09 03:58:55 +03:00
pg->flags &= ~PG_TABLED;
pg->uobject = NULL;
}
static inline void
uvm_pageremove_tree(struct uvm_object *uobj, struct vm_page *pg)
{
struct vm_page *opg __unused;
KASSERT(rw_write_held(uobj->vmobjlock));
opg = radix_tree_remove_node(&uobj->uo_pages, pg->offset >> PAGE_SHIFT);
KASSERT(pg == opg);
}
static void
uvm_page_init_bucket(struct pgfreelist *pgfl, struct pgflbucket *pgb, int num)
{
int i;
pgb->pgb_nfree = 0;
for (i = 0; i < uvmexp.ncolors; i++) {
LIST_INIT(&pgb->pgb_colors[i]);
}
pgfl->pgfl_buckets[num] = pgb;
}
/*
* uvm_page_init: init the page system. called from uvm_init().
2001-05-25 08:06:11 +04:00
*
* => we return the range of kernel virtual memory in kvm_startp/kvm_endp
*/
1998-03-09 03:58:55 +03:00
void
2005-06-27 06:19:48 +04:00
uvm_page_init(vaddr_t *kvm_startp, vaddr_t *kvm_endp)
{
static struct uvm_cpu uvm_boot_cpu __cacheline_aligned;
psize_t freepages, pagecount, bucketsize, n;
struct pgflbucket *pgb;
2010-11-11 18:47:43 +03:00
struct vm_page *pagearray;
char *bucketarray;
uvm_physseg_t bank;
int fl, b;
1998-03-09 03:58:55 +03:00
KASSERT(ncpu <= 1);
1998-03-09 03:58:55 +03:00
/*
* init the page queues and free page queue locks, except the
* free list; we allocate that later (with the initial vm_page
* structures).
1998-03-09 03:58:55 +03:00
*/
curcpu()->ci_data.cpu_uvm = &uvm_boot_cpu;
uvmpdpol_init();
for (b = 0; b < __arraycount(uvm_freelist_locks); b++) {
mutex_init(&uvm_freelist_locks[b].lock, MUTEX_DEFAULT, IPL_VM);
}
1998-03-09 03:58:55 +03:00
2001-05-25 08:06:11 +04:00
/*
* allocate vm_page structures.
1998-03-09 03:58:55 +03:00
*/
/*
* sanity check:
* before calling this function the MD code is expected to register
* some free RAM with the uvm_page_physload() function. our job
* now is to allocate vm_page structures for this memory.
*/
if (uvm_physseg_get_last() == UVM_PHYSSEG_TYPE_INVALID)
panic("uvm_page_bootstrap: no memory pre-allocated");
2001-05-25 08:06:11 +04:00
1998-03-09 03:58:55 +03:00
/*
2001-05-25 08:06:11 +04:00
* first calculate the number of free pages...
1998-03-09 03:58:55 +03:00
*
* note that we use start/end rather than avail_start/avail_end.
* this allows us to allocate extra vm_page structures in case we
* want to return some memory to the pool after booting.
*/
2001-05-25 08:06:11 +04:00
1998-03-09 03:58:55 +03:00
freepages = 0;
for (bank = uvm_physseg_get_first();
uvm_physseg_valid_p(bank) ;
bank = uvm_physseg_get_next(bank)) {
freepages += (uvm_physseg_get_end(bank) - uvm_physseg_get_start(bank));
2010-11-11 17:50:54 +03:00
}
1998-03-09 03:58:55 +03:00
/*
* Let MD code initialize the number of colors, or default
* to 1 color if MD code doesn't care.
*/
if (uvmexp.ncolors == 0)
uvmexp.ncolors = 1;
uvmexp.colormask = uvmexp.ncolors - 1;
KASSERT((uvmexp.colormask & uvmexp.ncolors) == 0);
/* We always start with only 1 bucket. */
uvm.bucketcount = 1;
1998-03-09 03:58:55 +03:00
/*
* we now know we have (PAGE_SIZE * freepages) bytes of memory we can
* use. for each page of memory we use we need a vm_page structure.
* thus, the total number of pages we can use is the total size of
* the memory divided by the PAGE_SIZE plus the size of the vm_page
* structure. we add one to freepages as a fudge factor to avoid
* truncation errors (since we can only allocate in terms of whole
* pages).
*/
pagecount = ((freepages + 1) << PAGE_SHIFT) /
1998-03-09 03:58:55 +03:00
(PAGE_SIZE + sizeof(struct vm_page));
bucketsize = offsetof(struct pgflbucket, pgb_colors[uvmexp.ncolors]);
bucketsize = roundup2(bucketsize, coherency_unit);
bucketarray = (void *)uvm_pageboot_alloc(
bucketsize * VM_NFREELIST +
pagecount * sizeof(struct vm_page));
pagearray = (struct vm_page *)
(bucketarray + bucketsize * VM_NFREELIST);
for (fl = 0; fl < VM_NFREELIST; fl++) {
pgb = (struct pgflbucket *)(bucketarray + bucketsize * fl);
uvm_page_init_bucket(&uvm.page_free[fl], pgb, 0);
}
memset(pagearray, 0, pagecount * sizeof(struct vm_page));
2001-05-25 08:06:11 +04:00
/*
* init the freelist cache in the disabled state.
*/
uvm_pgflcache_init();
1998-03-09 03:58:55 +03:00
/*
* init the vm_page structures and put them in the correct place.
1998-03-09 03:58:55 +03:00
*/
/* First init the extent */
1998-03-09 03:58:55 +03:00
for (bank = uvm_physseg_get_first(),
uvm_physseg_seg_chomp_slab(bank, pagearray, pagecount);
uvm_physseg_valid_p(bank);
bank = uvm_physseg_get_next(bank)) {
n = uvm_physseg_get_end(bank) - uvm_physseg_get_start(bank);
uvm_physseg_seg_alloc_from_slab(bank, n);
uvm_physseg_init_seg(bank, pagearray);
1998-03-09 03:58:55 +03:00
/* set up page array pointers */
pagearray += n;
pagecount -= n;
}
/*
* pass up the values of virtual_space_start and
* virtual_space_end (obtained by uvm_pageboot_alloc) to the upper
* layers of the VM.
*/
*kvm_startp = round_page(virtual_space_start);
*kvm_endp = trunc_page(virtual_space_end);
1998-03-09 03:58:55 +03:00
/*
* init various thresholds.
1998-03-09 03:58:55 +03:00
*/
1998-03-09 03:58:55 +03:00
uvmexp.reserve_pagedaemon = 1;
uvmexp.reserve_kernel = vm_page_reserve_kernel;
1998-03-09 03:58:55 +03:00
/*
* done!
*/
2007-02-22 09:05:00 +03:00
uvm.page_init_done = true;
}
/*
* uvm_pgfl_lock: lock all freelist buckets
*/
void
uvm_pgfl_lock(void)
{
int i;
for (i = 0; i < __arraycount(uvm_freelist_locks); i++) {
mutex_spin_enter(&uvm_freelist_locks[i].lock);
}
}
/*
* uvm_pgfl_unlock: unlock all freelist buckets
*/
void
uvm_pgfl_unlock(void)
{
int i;
for (i = 0; i < __arraycount(uvm_freelist_locks); i++) {
mutex_spin_exit(&uvm_freelist_locks[i].lock);
}
}
/*
* uvm_setpagesize: set the page size
2001-05-25 08:06:11 +04:00
*
* => sets page_shift and page_mask from uvmexp.pagesize.
2001-05-25 08:06:11 +04:00
*/
1998-03-09 03:58:55 +03:00
void
2005-06-27 06:19:48 +04:00
uvm_setpagesize(void)
{
/*
* If uvmexp.pagesize is 0 at this point, we expect PAGE_SIZE
* to be a constant (indicated by being a non-zero value).
*/
if (uvmexp.pagesize == 0) {
if (PAGE_SIZE == 0)
panic("uvm_setpagesize: uvmexp.pagesize not set");
uvmexp.pagesize = PAGE_SIZE;
}
1998-03-09 03:58:55 +03:00
uvmexp.pagemask = uvmexp.pagesize - 1;
if ((uvmexp.pagemask & uvmexp.pagesize) != 0)
panic("uvm_setpagesize: page size %u (%#x) not a power of two",
uvmexp.pagesize, uvmexp.pagesize);
1998-03-09 03:58:55 +03:00
for (uvmexp.pageshift = 0; ; uvmexp.pageshift++)
if ((1 << uvmexp.pageshift) == uvmexp.pagesize)
break;
}
/*
* uvm_pageboot_alloc: steal memory from physmem for bootstrapping
*/
vaddr_t
2005-06-27 06:19:48 +04:00
uvm_pageboot_alloc(vsize_t size)
{
2007-02-22 09:05:00 +03:00
static bool initialized = false;
vaddr_t addr;
#if !defined(PMAP_STEAL_MEMORY)
vaddr_t vaddr;
paddr_t paddr;
#endif
1998-03-09 03:58:55 +03:00
/*
* on first call to this function, initialize ourselves.
1998-03-09 03:58:55 +03:00
*/
2007-02-22 09:05:00 +03:00
if (initialized == false) {
pmap_virtual_space(&virtual_space_start, &virtual_space_end);
1998-03-09 03:58:55 +03:00
/* round it the way we like it */
virtual_space_start = round_page(virtual_space_start);
virtual_space_end = trunc_page(virtual_space_end);
2007-02-22 09:05:00 +03:00
initialized = true;
1998-03-09 03:58:55 +03:00
}
/* round to page size */
size = round_page(size);
uvmexp.bootpages += atop(size);
#if defined(PMAP_STEAL_MEMORY)
2001-05-25 08:06:11 +04:00
/*
* defer bootstrap allocation to MD code (it may want to allocate
* from a direct-mapped segment). pmap_steal_memory should adjust
* virtual_space_start/virtual_space_end if necessary.
*/
addr = pmap_steal_memory(size, &virtual_space_start,
&virtual_space_end);
2020-12-20 14:11:34 +03:00
return addr;
#else /* !PMAP_STEAL_MEMORY */
1998-03-09 03:58:55 +03:00
/*
* allocate virtual memory for this request
*/
if (virtual_space_start == virtual_space_end ||
(virtual_space_end - virtual_space_start) < size)
panic("uvm_pageboot_alloc: out of virtual space");
addr = virtual_space_start;
#ifdef PMAP_GROWKERNEL
/*
* If the kernel pmap can't map the requested space,
* then allocate more resources for it.
*/
if (uvm_maxkaddr < (addr + size)) {
uvm_maxkaddr = pmap_growkernel(addr + size);
if (uvm_maxkaddr < (addr + size))
panic("uvm_pageboot_alloc: pmap_growkernel() failed");
}
#endif
virtual_space_start += size;
1998-04-16 07:54:35 +04:00
/*
1998-03-09 03:58:55 +03:00
* allocate and mapin physical pages to back new virtual pages
*/
1998-03-09 03:58:55 +03:00
for (vaddr = round_page(addr) ; vaddr < addr + size ;
vaddr += PAGE_SIZE) {
1998-03-09 03:58:55 +03:00
if (!uvm_page_physget(&paddr))
panic("uvm_pageboot_alloc: out of memory");
/*
* Note this memory is no longer managed, so using
* pmap_kenter is safe.
*/
pmap_kenter_pa(vaddr, paddr, VM_PROT_READ|VM_PROT_WRITE, 0);
1998-03-09 03:58:55 +03:00
}
pmap_update(pmap_kernel());
2020-12-20 14:11:34 +03:00
return addr;
#endif /* PMAP_STEAL_MEMORY */
}
#if !defined(PMAP_STEAL_MEMORY)
/*
* uvm_page_physget: "steal" one page from the vm_physmem structure.
*
* => attempt to allocate it off the end of a segment in which the "avail"
* values match the start/end values. if we can't do that, then we
* will advance both values (making them equal, and removing some
* vm_page structures from the non-avail area).
* => return false if out of memory.
*/
/* subroutine: try to allocate from memory chunks on the specified freelist */
static bool uvm_page_physget_freelist(paddr_t *, int);
static bool
2005-06-27 06:19:48 +04:00
uvm_page_physget_freelist(paddr_t *paddrp, int freelist)
{
uvm_physseg_t lcv;
1998-03-09 03:58:55 +03:00
/* pass 1: try allocating from a matching end */
#if (VM_PHYSSEG_STRAT == VM_PSTRAT_BIGFIRST)
for (lcv = uvm_physseg_get_last(); uvm_physseg_valid_p(lcv); lcv = uvm_physseg_get_prev(lcv))
#else
for (lcv = uvm_physseg_get_first(); uvm_physseg_valid_p(lcv); lcv = uvm_physseg_get_next(lcv))
#endif
1998-03-09 03:58:55 +03:00
{
2007-02-22 09:05:00 +03:00
if (uvm.page_init_done == true)
panic("uvm_page_physget: called _after_ bootstrap");
1998-03-09 03:58:55 +03:00
/* Try to match at front or back on unused segment */
if (uvm_page_physunload(lcv, freelist, paddrp))
return true;
}
1998-03-09 03:58:55 +03:00
/* pass2: forget about matching ends, just allocate something */
#if (VM_PHYSSEG_STRAT == VM_PSTRAT_BIGFIRST)
for (lcv = uvm_physseg_get_last(); uvm_physseg_valid_p(lcv); lcv = uvm_physseg_get_prev(lcv))
#else
for (lcv = uvm_physseg_get_first(); uvm_physseg_valid_p(lcv); lcv = uvm_physseg_get_next(lcv))
#endif
1998-03-09 03:58:55 +03:00
{
/* Try the front regardless. */
if (uvm_page_physunload_force(lcv, freelist, paddrp))
return true;
1998-03-09 03:58:55 +03:00
}
return false;
}
bool
2005-06-27 06:19:48 +04:00
uvm_page_physget(paddr_t *paddrp)
{
int i;
/* try in the order of freelist preference */
for (i = 0; i < VM_NFREELIST; i++)
2007-02-22 09:05:00 +03:00
if (uvm_page_physget_freelist(paddrp, i) == true)
return (true);
return (false);
}
#endif /* PMAP_STEAL_MEMORY */
paddr_t
uvm_vm_page_to_phys(const struct vm_page *pg)
{
return pg->phys_addr & ~(PAGE_SIZE - 1);
}
/*
* uvm_page_numa_load: load NUMA range description.
*/
void
uvm_page_numa_load(paddr_t start, paddr_t size, u_int numa_id)
{
struct uvm_page_numa_region *d;
KASSERT(numa_id < PGFL_MAX_BUCKETS);
d = kmem_alloc(sizeof(*d), KM_SLEEP);
d->start = start;
d->size = size;
d->numa_id = numa_id;
d->next = uvm_page_numa_region;
uvm_page_numa_region = d;
}
/*
* uvm_page_numa_lookup: lookup NUMA node for the given page.
*/
static u_int
uvm_page_numa_lookup(struct vm_page *pg)
{
struct uvm_page_numa_region *d;
static bool warned;
paddr_t pa;
KASSERT(uvm_page_numa_region != NULL);
pa = VM_PAGE_TO_PHYS(pg);
for (d = uvm_page_numa_region; d != NULL; d = d->next) {
if (pa >= d->start && pa < d->start + d->size) {
return d->numa_id;
}
}
if (!warned) {
printf("uvm_page_numa_lookup: failed, first pg=%p pa=%#"
PRIxPADDR "\n", pg, VM_PAGE_TO_PHYS(pg));
warned = true;
}
return 0;
}
/*
* uvm_page_redim: adjust freelist dimensions if they have changed.
*/
static void
uvm_page_redim(int newncolors, int newnbuckets)
{
struct pgfreelist npgfl;
struct pgflbucket *opgb, *npgb;
struct pgflist *ohead, *nhead;
2020-03-03 11:13:44 +03:00
struct vm_page *pg;
size_t bucketsize, bucketmemsize, oldbucketmemsize;
int fl, ob, oc, nb, nc, obuckets, ocolors;
char *bucketarray, *oldbucketmem, *bucketmem;
KASSERT(((newncolors - 1) & newncolors) == 0);
/* Anything to do? */
if (newncolors <= uvmexp.ncolors &&
newnbuckets == uvm.bucketcount) {
return;
}
2007-02-22 09:05:00 +03:00
if (uvm.page_init_done == false) {
uvmexp.ncolors = newncolors;
return;
}
bucketsize = offsetof(struct pgflbucket, pgb_colors[newncolors]);
bucketsize = roundup2(bucketsize, coherency_unit);
bucketmemsize = bucketsize * newnbuckets * VM_NFREELIST +
coherency_unit - 1;
bucketmem = kmem_zalloc(bucketmemsize, KM_SLEEP);
bucketarray = (char *)roundup2((uintptr_t)bucketmem, coherency_unit);
ocolors = uvmexp.ncolors;
obuckets = uvm.bucketcount;
2024-02-10 12:24:17 +03:00
/* Freelist cache mustn't be enabled. */
uvm_pgflcache_pause();
/* Make sure we should still do this. */
uvm_pgfl_lock();
if (newncolors <= uvmexp.ncolors &&
newnbuckets == uvm.bucketcount) {
uvm_pgfl_unlock();
uvm_pgflcache_resume();
kmem_free(bucketmem, bucketmemsize);
return;
}
uvmexp.ncolors = newncolors;
uvmexp.colormask = uvmexp.ncolors - 1;
uvm.bucketcount = newnbuckets;
for (fl = 0; fl < VM_NFREELIST; fl++) {
/* Init new buckets in new freelist. */
memset(&npgfl, 0, sizeof(npgfl));
for (nb = 0; nb < newnbuckets; nb++) {
npgb = (struct pgflbucket *)bucketarray;
uvm_page_init_bucket(&npgfl, npgb, nb);
bucketarray += bucketsize;
}
/* Now transfer pages from the old freelist. */
for (nb = ob = 0; ob < obuckets; ob++) {
opgb = uvm.page_free[fl].pgfl_buckets[ob];
for (oc = 0; oc < ocolors; oc++) {
ohead = &opgb->pgb_colors[oc];
while ((pg = LIST_FIRST(ohead)) != NULL) {
LIST_REMOVE(pg, pageq.list);
/*
* Here we decide on the NEW color &
* bucket for the page. For NUMA
* we'll use the info that the
* hardware gave us. For non-NUMA
* assign take physical page frame
* number and cache color into
* account. We do this to try and
* avoid defeating any memory
* interleaving in the hardware.
*/
KASSERT(
uvm_page_get_bucket(pg) == ob);
KASSERT(fl ==
uvm_page_get_freelist(pg));
if (uvm_page_numa_region != NULL) {
nb = uvm_page_numa_lookup(pg);
} else {
nb = atop(VM_PAGE_TO_PHYS(pg))
/ uvmexp.ncolors / 8
% newnbuckets;
}
uvm_page_set_bucket(pg, nb);
npgb = npgfl.pgfl_buckets[nb];
npgb->pgb_nfree++;
nc = VM_PGCOLOR(pg);
nhead = &npgb->pgb_colors[nc];
LIST_INSERT_HEAD(nhead, pg, pageq.list);
}
}
}
/* Install the new freelist. */
memcpy(&uvm.page_free[fl], &npgfl, sizeof(npgfl));
}
/* Unlock and free the old memory. */
oldbucketmemsize = recolored_pages_memsize;
oldbucketmem = recolored_pages_mem;
recolored_pages_memsize = bucketmemsize;
recolored_pages_mem = bucketmem;
uvm_pgfl_unlock();
uvm_pgflcache_resume();
if (oldbucketmemsize) {
kmem_free(oldbucketmem, oldbucketmemsize);
}
/*
* this calls uvm_km_alloc() which may want to hold
* uvm_freelist_lock.
*/
uvm_pager_realloc_emerg();
}
/*
* uvm_page_recolor: Recolor the pages if the new color count is
* larger than the old one.
*/
void
uvm_page_recolor(int newncolors)
{
uvm_page_redim(newncolors, uvm.bucketcount);
}
/*
* uvm_page_rebucket: Determine a bucket structure and redim the free
* lists to match.
*/
void
uvm_page_rebucket(void)
{
u_int min_numa, max_numa, npackage, shift;
struct cpu_info *ci, *ci2, *ci3;
CPU_INFO_ITERATOR cii;
/*
* If we have more than one NUMA node, and the maximum NUMA node ID
* is less than PGFL_MAX_BUCKETS, then we'll use NUMA distribution
* for free pages.
*/
min_numa = (u_int)-1;
max_numa = 0;
for (CPU_INFO_FOREACH(cii, ci)) {
if (ci->ci_numa_id < min_numa) {
min_numa = ci->ci_numa_id;
}
if (ci->ci_numa_id > max_numa) {
max_numa = ci->ci_numa_id;
}
}
if (min_numa != max_numa && max_numa < PGFL_MAX_BUCKETS) {
aprint_debug("UVM: using NUMA allocation scheme\n");
2020-03-03 11:13:44 +03:00
for (CPU_INFO_FOREACH(cii, ci)) {
ci->ci_data.cpu_uvm->pgflbucket = ci->ci_numa_id;
}
uvm_page_redim(uvmexp.ncolors, max_numa + 1);
return;
}
/*
* Otherwise we'll go with a scheme to maximise L2/L3 cache locality
* and minimise lock contention. Count the total number of CPU
* packages, and then try to distribute the buckets among CPU
* packages evenly.
*/
npackage = curcpu()->ci_nsibling[CPUREL_PACKAGE1ST];
2020-03-03 11:13:44 +03:00
/*
* Figure out how to arrange the packages & buckets, and the total
* number of buckets we need. XXX 2 may not be the best factor.
*/
for (shift = 0; npackage > PGFL_MAX_BUCKETS; shift++) {
npackage >>= 1;
}
uvm_page_redim(uvmexp.ncolors, npackage);
/*
* Now tell each CPU which bucket to use. In the outer loop, scroll
* through all CPU packages.
*/
npackage = 0;
ci = curcpu();
ci2 = ci->ci_sibling[CPUREL_PACKAGE1ST];
do {
/*
* In the inner loop, scroll through all CPUs in the package
* and assign the same bucket ID.
*/
ci3 = ci2;
do {
ci3->ci_data.cpu_uvm->pgflbucket = npackage >> shift;
ci3 = ci3->ci_sibling[CPUREL_PACKAGE];
} while (ci3 != ci2);
npackage++;
ci2 = ci2->ci_sibling[CPUREL_PACKAGE1ST];
} while (ci2 != ci->ci_sibling[CPUREL_PACKAGE1ST]);
aprint_debug("UVM: using package allocation scheme, "
"%d package(s) per bucket\n", 1 << shift);
}
/*
* uvm_cpu_attach: initialize per-CPU data structures.
*/
void
uvm_cpu_attach(struct cpu_info *ci)
{
struct uvm_cpu *ucpu;
/* Already done in uvm_page_init(). */
if (!CPU_IS_PRIMARY(ci)) {
/* Add more reserve pages for this CPU. */
uvmexp.reserve_kernel += vm_page_reserve_kernel;
/* Allocate per-CPU data structures. */
ucpu = kmem_zalloc(sizeof(struct uvm_cpu) + coherency_unit - 1,
KM_SLEEP);
ucpu = (struct uvm_cpu *)roundup2((uintptr_t)ucpu,
coherency_unit);
ci->ci_data.cpu_uvm = ucpu;
} else {
ucpu = ci->ci_data.cpu_uvm;
}
uvmpdpol_init_cpu(ucpu);
}
/*
2019-12-31 16:07:09 +03:00
* uvm_availmem: fetch the total amount of free memory in pages. this can
* have a detrimental effect on performance due to false sharing; don't call
* unless needed.
*
* some users can request the amount of free memory so often that it begins
* to impact upon performance. if calling frequently and an inexact value
* is okay, call with cached = true.
*/
int
uvm_availmem(bool cached)
{
int64_t fp;
cpu_count_sync(cached);
if ((fp = cpu_count_get(CPU_COUNT_FREEPAGES)) < 0) {
/*
* XXXAD could briefly go negative because it's impossible
* to get a clean snapshot. address this for other counters
* used as running totals before NetBSD 10 although less
* important for those.
*/
fp = 0;
}
return (int)fp;
}
/*
* uvm_pagealloc_pgb: helper routine that tries to allocate any color from a
* specific freelist and specific bucket only.
*
* => must be at IPL_VM or higher to protect per-CPU data structures.
*/
2006-09-27 21:18:50 +04:00
static struct vm_page *
uvm_pagealloc_pgb(struct uvm_cpu *ucpu, int f, int b, int *trycolorp, int flags)
{
int c, trycolor, colormask;
struct pgflbucket *pgb;
struct vm_page *pg;
kmutex_t *lock;
bool fill;
/*
* Skip the bucket if empty, no lock needed. There could be many
* empty freelists/buckets.
*/
pgb = uvm.page_free[f].pgfl_buckets[b];
if (pgb->pgb_nfree == 0) {
return NULL;
}
/* Skip bucket if low on memory. */
lock = &uvm_freelist_locks[b].lock;
mutex_spin_enter(lock);
if (__predict_false(pgb->pgb_nfree <= uvmexp.reserve_kernel)) {
if ((flags & UVM_PGA_USERESERVE) == 0 ||
(pgb->pgb_nfree <= uvmexp.reserve_pagedaemon &&
curlwp != uvm.pagedaemon_lwp)) {
mutex_spin_exit(lock);
return NULL;
}
fill = false;
} else {
fill = true;
}
/* Try all page colors as needed. */
c = trycolor = *trycolorp;
colormask = uvmexp.colormask;
do {
pg = LIST_FIRST(&pgb->pgb_colors[c]);
if (__predict_true(pg != NULL)) {
/*
* Got a free page! PG_FREE must be cleared under
* lock because of uvm_pglistalloc().
*/
LIST_REMOVE(pg, pageq.list);
KASSERT(pg->flags == PG_FREE);
pg->flags = PG_BUSY | PG_CLEAN | PG_FAKE;
pgb->pgb_nfree--;
CPU_COUNT(CPU_COUNT_FREEPAGES, -1);
2020-03-03 11:13:44 +03:00
/*
* While we have the bucket locked and our data
* structures fresh in L1 cache, we have an ideal
* opportunity to grab some pages for the freelist
* cache without causing extra contention. Only do
* so if we found pages in this CPU's preferred
* bucket.
*/
if (__predict_true(b == ucpu->pgflbucket && fill)) {
uvm_pgflcache_fill(ucpu, f, b, c);
}
mutex_spin_exit(lock);
KASSERT(uvm_page_get_bucket(pg) == b);
CPU_COUNT(c == trycolor ?
CPU_COUNT_COLORHIT : CPU_COUNT_COLORMISS, 1);
CPU_COUNT(CPU_COUNT_CPUMISS, 1);
*trycolorp = c;
return pg;
}
c = (c + 1) & colormask;
} while (c != trycolor);
mutex_spin_exit(lock);
return NULL;
}
/*
* uvm_pagealloc_pgfl: helper routine for uvm_pagealloc_strat that allocates
* any color from any bucket, in a specific freelist.
*
* => must be at IPL_VM or higher to protect per-CPU data structures.
*/
static struct vm_page *
uvm_pagealloc_pgfl(struct uvm_cpu *ucpu, int f, int *trycolorp, int flags)
{
int b, trybucket, bucketcount;
struct vm_page *pg;
/* Try for the exact thing in the per-CPU cache. */
if ((pg = uvm_pgflcache_alloc(ucpu, f, *trycolorp)) != NULL) {
CPU_COUNT(CPU_COUNT_CPUHIT, 1);
CPU_COUNT(CPU_COUNT_COLORHIT, 1);
return pg;
}
/* Walk through all buckets, trying our preferred bucket first. */
trybucket = ucpu->pgflbucket;
b = trybucket;
bucketcount = uvm.bucketcount;
do {
pg = uvm_pagealloc_pgb(ucpu, f, b, trycolorp, flags);
if (pg != NULL) {
return pg;
}
b = (b + 1 == bucketcount ? 0 : b + 1);
} while (b != trybucket);
return NULL;
}
/*
* uvm_pagealloc_strat: allocate vm_page from a particular free list.
*
* => return null if no pages free
* => wake up pagedaemon if number of free pages drops below low water mark
* => if obj != NULL, obj must be locked (to put in obj's tree)
* => if anon != NULL, anon must be locked (to put in anon)
* => only one of obj or anon can be non-null
* => caller must activate/deactivate page if it is not wired.
* => free_list is ignored if strat == UVM_PGA_STRAT_NORMAL.
* => policy decision: it is more important to pull a page off of the
* appropriate priority free list than it is to get a page from the
* correct bucket or color bin. This is because we live with the
* consequences of a bad free list decision for the entire
* lifetime of the page, e.g. if the page comes from memory that
* is slower to access.
*/
1998-03-09 03:58:55 +03:00
struct vm_page *
2005-06-27 06:19:48 +04:00
uvm_pagealloc_strat(struct uvm_object *obj, voff_t off, struct vm_anon *anon,
int flags, int strat, int free_list)
{
int color, lcv, error, s;
struct uvm_cpu *ucpu;
1998-03-09 03:58:55 +03:00
struct vm_page *pg;
lwp_t *l;
KASSERT(obj == NULL || anon == NULL);
KASSERT(anon == NULL || (flags & UVM_FLAG_COLORMATCH) || off == 0);
KASSERT(off == trunc_page(off));
KASSERT(obj == NULL || rw_write_held(obj->vmobjlock));
KASSERT(anon == NULL || anon->an_lock == NULL ||
rw_write_held(anon->an_lock));
/*
* This implements a global round-robin page coloring
* algorithm.
*/
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
s = splvm();
ucpu = curcpu()->ci_data.cpu_uvm;
if (flags & UVM_FLAG_COLORMATCH) {
color = atop(off) & uvmexp.colormask;
} else {
color = ucpu->pgflcolor;
}
1998-03-09 03:58:55 +03:00
/*
* fail if any of these conditions is true:
* [1] there really are no free pages, or
* [2] only kernel "reserved" pages remain and
* reserved pages have not been requested.
1998-03-09 03:58:55 +03:00
* [3] only pagedaemon "reserved" pages remain and
* the requestor isn't the pagedaemon.
* we make kernel reserve pages available if called by a
* kernel thread.
1998-03-09 03:58:55 +03:00
*/
l = curlwp;
if (__predict_true(l != NULL) && (l->l_flag & LW_SYSTEM) != 0) {
flags |= UVM_PGA_USERESERVE;
}
again:
switch (strat) {
case UVM_PGA_STRAT_NORMAL:
/* Check freelists: descending priority (ascending id) order. */
for (lcv = 0; lcv < VM_NFREELIST; lcv++) {
pg = uvm_pagealloc_pgfl(ucpu, lcv, &color, flags);
if (pg != NULL) {
goto gotit;
}
}
/* No pages free! Have pagedaemon free some memory. */
splx(s);
uvm_kick_pdaemon();
return NULL;
case UVM_PGA_STRAT_ONLY:
case UVM_PGA_STRAT_FALLBACK:
/* Attempt to allocate from the specified free list. */
KASSERT(free_list >= 0);
KASSERT(free_list < VM_NFREELIST);
pg = uvm_pagealloc_pgfl(ucpu, free_list, &color, flags);
if (pg != NULL) {
goto gotit;
}
/* Fall back, if possible. */
if (strat == UVM_PGA_STRAT_FALLBACK) {
strat = UVM_PGA_STRAT_NORMAL;
goto again;
}
/* No pages free! Have pagedaemon free some memory. */
splx(s);
uvm_kick_pdaemon();
return NULL;
case UVM_PGA_STRAT_NUMA:
/*
* NUMA strategy (experimental): allocating from the correct
* bucket is more important than observing freelist
* priority. Look only to the current NUMA node; if that
* fails, we need to look to other NUMA nodes, so retry with
* the normal strategy.
*/
for (lcv = 0; lcv < VM_NFREELIST; lcv++) {
pg = uvm_pgflcache_alloc(ucpu, lcv, color);
if (pg != NULL) {
CPU_COUNT(CPU_COUNT_CPUHIT, 1);
CPU_COUNT(CPU_COUNT_COLORHIT, 1);
goto gotit;
}
pg = uvm_pagealloc_pgb(ucpu, lcv,
ucpu->pgflbucket, &color, flags);
if (pg != NULL) {
goto gotit;
}
}
strat = UVM_PGA_STRAT_NORMAL;
goto again;
default:
panic("uvm_pagealloc_strat: bad strat %d", strat);
/* NOTREACHED */
1998-03-09 03:58:55 +03:00
}
gotit:
/*
* We now know which color we actually allocated from; set
* the next color accordingly.
*/
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
ucpu->pgflcolor = (color + 1) & uvmexp.colormask;
/*
* while still at IPL_VM, update allocation statistics.
*/
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
if (anon) {
CPU_COUNT(CPU_COUNT_ANONCLEAN, 1);
}
splx(s);
KASSERT(pg->flags == (PG_BUSY|PG_CLEAN|PG_FAKE));
1998-03-09 03:58:55 +03:00
/*
* assign the page to the object. as the page was free, we know
* that pg->uobject and pg->uanon are NULL. we only need to take
* the page's interlock if we are changing the values.
*/
if (anon != NULL || obj != NULL) {
mutex_enter(&pg->interlock);
}
1998-03-09 03:58:55 +03:00
pg->offset = off;
pg->uobject = obj;
pg->uanon = anon;
KASSERT(uvm_page_owner_locked_p(pg, true));
1998-03-09 03:58:55 +03:00
if (anon) {
anon->an_page = pg;
pg->flags |= PG_ANON;
mutex_exit(&pg->interlock);
} else if (obj) {
/*
* set PG_FILE|PG_AOBJ before the first uvm_pageinsert.
*/
if (UVM_OBJ_IS_VNODE(obj)) {
pg->flags |= PG_FILE;
} else if (UVM_OBJ_IS_AOBJ(obj)) {
pg->flags |= PG_AOBJ;
}
uvm_pageinsert_object(obj, pg);
mutex_exit(&pg->interlock);
error = uvm_pageinsert_tree(obj, pg);
if (error != 0) {
mutex_enter(&pg->interlock);
uvm_pageremove_object(obj, pg);
mutex_exit(&pg->interlock);
uvm_pagefree(pg);
return NULL;
}
1998-03-09 03:58:55 +03:00
}
#if defined(UVM_PAGE_TRKOWN)
1998-03-09 03:58:55 +03:00
pg->owner_tag = NULL;
#endif
1998-03-09 03:58:55 +03:00
UVM_PAGE_OWN(pg, "new alloc");
if (flags & UVM_PGA_ZERO) {
/* A zero'd page is not clean. */
if (obj != NULL || anon != NULL) {
uvm_pagemarkdirty(pg, UVM_PAGE_STATUS_DIRTY);
}
pmap_zero_page(VM_PAGE_TO_PHYS(pg));
}
1998-03-09 03:58:55 +03:00
return(pg);
}
/*
* uvm_pagereplace: replace a page with another
*
* => object must be locked
* => page interlocks must be held
*/
void
2005-06-27 06:19:48 +04:00
uvm_pagereplace(struct vm_page *oldpg, struct vm_page *newpg)
{
struct uvm_object *uobj = oldpg->uobject;
struct vm_page *pg __diagused;
uint64_t idx;
2004-03-24 10:50:48 +03:00
KASSERT((oldpg->flags & PG_TABLED) != 0);
KASSERT(uobj != NULL);
KASSERT((newpg->flags & PG_TABLED) == 0);
KASSERT(newpg->uobject == NULL);
KASSERT(rw_write_held(uobj->vmobjlock));
KASSERT(mutex_owned(&oldpg->interlock));
KASSERT(mutex_owned(&newpg->interlock));
newpg->uobject = uobj;
newpg->offset = oldpg->offset;
idx = newpg->offset >> PAGE_SHIFT;
pg = radix_tree_replace_node(&uobj->uo_pages, idx, newpg);
KASSERT(pg == oldpg);
if (((oldpg->flags ^ newpg->flags) & PG_CLEAN) != 0) {
if ((newpg->flags & PG_CLEAN) != 0) {
uvm_obj_page_clear_dirty(newpg);
} else {
uvm_obj_page_set_dirty(newpg);
}
}
/*
* oldpg's PG_STAT is stable. newpg is not reachable by others yet.
*/
newpg->flags |=
(newpg->flags & ~PG_STAT) | (oldpg->flags & PG_STAT);
uvm_pageinsert_object(uobj, newpg);
uvm_pageremove_object(uobj, oldpg);
}
/*
* uvm_pagerealloc: reallocate a page from one object to another
*
* => both objects must be locked
*/
int
2005-06-27 06:19:48 +04:00
uvm_pagerealloc(struct vm_page *pg, struct uvm_object *newobj, voff_t newoff)
{
int error = 0;
1998-03-09 03:58:55 +03:00
/*
* remove it from the old object
*/
if (pg->uobject) {
uvm_pageremove_tree(pg->uobject, pg);
uvm_pageremove_object(pg->uobject, pg);
1998-03-09 03:58:55 +03:00
}
/*
* put it in the new object
*/
if (newobj) {
mutex_enter(&pg->interlock);
pg->uobject = newobj;
pg->offset = newoff;
if (UVM_OBJ_IS_VNODE(newobj)) {
pg->flags |= PG_FILE;
} else if (UVM_OBJ_IS_AOBJ(newobj)) {
pg->flags |= PG_AOBJ;
}
uvm_pageinsert_object(newobj, pg);
mutex_exit(&pg->interlock);
error = uvm_pageinsert_tree(newobj, pg);
if (error != 0) {
mutex_enter(&pg->interlock);
uvm_pageremove_object(newobj, pg);
mutex_exit(&pg->interlock);
}
1998-03-09 03:58:55 +03:00
}
return error;
}
/*
* uvm_pagefree: free page
*
* => erase page's identity (i.e. remove from object)
* => put page on free list
* => caller must lock owning object (either anon or uvm_object)
* => assumes all valid mappings of pg are gone
*/
void
2005-06-27 06:19:48 +04:00
uvm_pagefree(struct vm_page *pg)
{
struct pgfreelist *pgfl;
struct pgflbucket *pgb;
struct uvm_cpu *ucpu;
kmutex_t *lock;
int bucket, s;
bool locked;
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
#ifdef DEBUG
if (pg->uobject == (void *)0xdeadbeef &&
pg->uanon == (void *)0xdeadbeef) {
panic("uvm_pagefree: freeing free page %p", pg);
}
#endif /* DEBUG */
KASSERT((pg->flags & PG_PAGEOUT) == 0);
KASSERT(!(pg->flags & PG_FREE));
KASSERT(pg->uobject == NULL || rw_write_held(pg->uobject->vmobjlock));
2008-01-02 14:48:20 +03:00
KASSERT(pg->uobject != NULL || pg->uanon == NULL ||
rw_write_held(pg->uanon->an_lock));
/*
2020-03-03 10:51:26 +03:00
* remove the page from the object's tree before acquiring any page
* interlocks: this can acquire locks to free radixtree nodes.
*/
if (pg->uobject != NULL) {
uvm_pageremove_tree(pg->uobject, pg);
}
1998-03-09 03:58:55 +03:00
/*
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
* if the page is loaned, resolve the loan instead of freeing.
1998-03-09 03:58:55 +03:00
*/
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
if (pg->loan_count) {
KASSERT(pg->wire_count == 0);
2001-05-25 08:06:11 +04:00
1998-03-09 03:58:55 +03:00
/*
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
* if the page is owned by an anon then we just want to
* drop anon ownership. the kernel will free the page when
* it is done with it. if the page is owned by an object,
* remove it from the object and mark it dirty for the benefit
* of possible anon owners.
*
* regardless of previous ownership, wakeup any waiters,
* unbusy the page, and we're done.
1998-03-09 03:58:55 +03:00
*/
uvm_pagelock(pg);
locked = true;
if (pg->uobject != NULL) {
uvm_pageremove_object(pg->uobject, pg);
pg->flags &= ~(PG_FILE|PG_AOBJ);
} else if (pg->uanon != NULL) {
if ((pg->flags & PG_ANON) == 0) {
pg->loan_count--;
} else {
const unsigned status = uvm_pagegetdirty(pg);
pg->flags &= ~PG_ANON;
cpu_count(CPU_COUNT_ANONUNKNOWN + status, -1);
}
pg->uanon->an_page = NULL;
pg->uanon = NULL;
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
}
if (pg->pqflags & PQ_WANTED) {
wakeup(pg);
}
pg->pqflags &= ~PQ_WANTED;
pg->flags &= ~(PG_BUSY|PG_RELEASED|PG_PAGER1);
#ifdef UVM_PAGE_TRKOWN
pg->owner_tag = NULL;
#endif
KASSERT((pg->flags & PG_STAT) == 0);
if (pg->loan_count) {
KASSERT(pg->uobject == NULL);
if (pg->uanon == NULL) {
uvm_pagedequeue(pg);
}
uvm_pageunlock(pg);
return;
}
} else if (pg->uobject != NULL || pg->uanon != NULL ||
pg->wire_count != 0) {
uvm_pagelock(pg);
locked = true;
} else {
locked = false;
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
}
1998-03-09 03:58:55 +03:00
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
/*
* remove page from its object or anon.
*/
if (pg->uobject != NULL) {
uvm_pageremove_object(pg->uobject, pg);
} else if (pg->uanon != NULL) {
const unsigned int status = uvm_pagegetdirty(pg);
pg->uanon->an_page = NULL;
pg->uanon = NULL;
cpu_count(CPU_COUNT_ANONUNKNOWN + status, -1);
1998-03-09 03:58:55 +03:00
}
/*
* if the page was wired, unwire it now.
*/
if (pg->wire_count) {
1998-03-09 03:58:55 +03:00
pg->wire_count = 0;
atomic_dec_uint(&uvmexp.wired);
}
if (locked) {
/*
* wake anyone waiting on the page.
*/
if ((pg->pqflags & PQ_WANTED) != 0) {
pg->pqflags &= ~PQ_WANTED;
wakeup(pg);
}
/*
* now remove the page from the queues.
*/
uvm_pagedequeue(pg);
uvm_pageunlock(pg);
} else {
KASSERT(!uvmpdpol_pageisqueued_p(pg));
1998-03-09 03:58:55 +03:00
}
/*
* and put on free queue
1998-03-09 03:58:55 +03:00
*/
#ifdef DEBUG
1998-03-09 03:58:55 +03:00
pg->uobject = (void *)0xdeadbeef;
pg->uanon = (void *)0xdeadbeef;
#endif /* DEBUG */
/* Try to send the page to the per-CPU cache. */
s = splvm();
ucpu = curcpu()->ci_data.cpu_uvm;
bucket = uvm_page_get_bucket(pg);
if (bucket == ucpu->pgflbucket && uvm_pgflcache_free(ucpu, pg)) {
splx(s);
return;
}
/* Didn't work. Never mind, send it to a global bucket. */
pgfl = &uvm.page_free[uvm_page_get_freelist(pg)];
pgb = pgfl->pgfl_buckets[bucket];
lock = &uvm_freelist_locks[bucket].lock;
mutex_spin_enter(lock);
/* PG_FREE must be set under lock because of uvm_pglistalloc(). */
pg->flags = PG_FREE;
LIST_INSERT_HEAD(&pgb->pgb_colors[VM_PGCOLOR(pg)], pg, pageq.list);
pgb->pgb_nfree++;
CPU_COUNT(CPU_COUNT_FREEPAGES, 1);
mutex_spin_exit(lock);
splx(s);
}
/*
* uvm_page_unbusy: unbusy an array of pages.
*
* => pages must either all belong to the same object, or all belong to anons.
* => if pages are object-owned, object must be locked.
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
* => if pages are anon-owned, anons must be locked.
* => caller must make sure that anon-owned pages are not PG_RELEASED.
*/
void
2005-06-27 06:19:48 +04:00
uvm_page_unbusy(struct vm_page **pgs, int npgs)
{
struct vm_page *pg;
int i, pageout_done;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(ubchist);
pageout_done = 0;
for (i = 0; i < npgs; i++) {
pg = pgs[i];
if (pg == NULL || pg == PGO_DONTCARE) {
continue;
}
KASSERT(uvm_page_owner_locked_p(pg, true));
KASSERT(pg->flags & PG_BUSY);
if (pg->flags & PG_PAGEOUT) {
pg->flags &= ~PG_PAGEOUT;
pg->flags |= PG_RELEASED;
pageout_done++;
atomic_inc_uint(&uvmexp.pdfreed);
}
if (pg->flags & PG_RELEASED) {
Update the kernhist(9) kernel history code to address issues identified in PR kern/52639, as well as some general cleaning-up... (As proposed on tech-kern@ with additional changes and enhancements.) Details of changes: * All history arguments are now stored as uintmax_t values[1], both in the kernel and in the structures used for exporting the history data to userland via sysctl(9). This avoids problems on some architectures where passing a 64-bit (or larger) value to printf(3) can cause it to process the value as multiple arguments. (This can be particularly problematic when printf()'s format string is not a literal, since in that case the compiler cannot know how large each argument should be.) * Update the data structures used for exporting kernel history data to include a version number as well as the length of history arguments. * All [2] existing users of kernhist(9) have had their format strings updated. Each format specifier now includes an explicit length modifier 'j' to refer to numeric values of the size of uintmax_t. * All [2] existing users of kernhist(9) have had their format strings updated to replace uses of "%p" with "%#jx", and the pointer arguments are now cast to (uintptr_t) before being subsequently cast to (uintmax_t). This is needed to avoid compiler warnings about casting "pointer to integer of a different size." * All [2] existing users of kernhist(9) have had instances of "%s" or "%c" format strings replaced with numeric formats; several instances of mis-match between format string and argument list have been fixed. * vmstat(1) has been modified to handle the new size of arguments in the history data as exported by sysctl(9). * vmstat(1) now provides a warning message if the history requested with the -u option does not exist (previously, this condition was silently ignored, with only a single blank line being printed). * vmstat(1) now checks the version and argument length included in the data exported via sysctl(9) and exits if they do not match the values with which vmstat was built. * The kernhist(9) man-page has been updated to note the additional requirements imposed on the format strings, along with several other minor changes and enhancements. [1] It would have been possible to use an explicit length (for example, uint64_t) for the history arguments. But that would require another "rototill" of all the users in the future when we add support for an architecture that supports a larger size. Also, the printf(3) format specifiers for explicitly-sized values, such as "%"PRIu64, are much more verbose (and less aesthetically appealing, IMHO) than simply using "%ju". [2] I've tried very hard to find "all [the] existing users of kernhist(9)" but it is possible that I've missed some of them. I would be glad to update any stragglers that anyone identifies.
2017-10-28 03:37:11 +03:00
UVMHIST_LOG(ubchist, "releasing pg %#jx",
(uintptr_t)pg, 0, 0, 0);
KASSERT(pg->uobject != NULL ||
(pg->uanon != NULL && pg->uanon->an_ref > 0));
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
pg->flags &= ~PG_RELEASED;
uvm_pagefree(pg);
} else {
UVMHIST_LOG(ubchist, "unbusying pg %#jx",
(uintptr_t)pg, 0, 0, 0);
2009-01-16 10:01:28 +03:00
KASSERT((pg->flags & PG_FAKE) == 0);
pg->flags &= ~PG_BUSY;
uvm_pagelock(pg);
uvm_pagewakeup(pg);
uvm_pageunlock(pg);
UVM_PAGE_OWN(pg, NULL);
}
}
if (pageout_done != 0) {
uvm_pageout_done(pageout_done);
}
}
/*
* uvm_pagewait: wait for a busy page
*
* => page must be known PG_BUSY
* => object must be read or write locked
* => object will be unlocked on return
*/
void
uvm_pagewait(struct vm_page *pg, krwlock_t *lock, const char *wmesg)
{
KASSERT(rw_lock_held(lock));
KASSERT((pg->flags & PG_BUSY) != 0);
KASSERT(uvm_page_owner_locked_p(pg, false));
mutex_enter(&pg->interlock);
pg->pqflags |= PQ_WANTED;
rw_exit(lock);
UVM_UNLOCK_AND_WAIT(pg, &pg->interlock, false, wmesg, 0);
}
/*
* uvm_pagewakeup: wake anyone waiting on a page
*
* => page interlock must be held
*/
void
uvm_pagewakeup(struct vm_page *pg)
{
UVMHIST_FUNC(__func__); UVMHIST_CALLED(ubchist);
KASSERT(mutex_owned(&pg->interlock));
UVMHIST_LOG(ubchist, "waking pg %#jx", (uintptr_t)pg, 0, 0, 0);
if ((pg->pqflags & PQ_WANTED) != 0) {
wakeup(pg);
pg->pqflags &= ~PQ_WANTED;
}
}
/*
* uvm_pagewanted_p: return true if someone is waiting on the page
*
* => object must be write locked (lock out all concurrent access)
*/
bool
uvm_pagewanted_p(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, true));
return (atomic_load_relaxed(&pg->pqflags) & PQ_WANTED) != 0;
}
#if defined(UVM_PAGE_TRKOWN)
/*
* uvm_page_own: set or release page ownership
*
* => this is a debugging function that keeps track of who sets PG_BUSY
* and where they do it. it can be used to track down problems
* such a process setting "PG_BUSY" and never releasing it.
* => page's object [if any] must be locked
* => if "tag" is NULL then we are releasing page ownership
*/
1998-03-09 03:58:55 +03:00
void
2005-06-27 06:19:48 +04:00
uvm_page_own(struct vm_page *pg, const char *tag)
{
2006-04-13 12:33:18 +04:00
a whole bunch of changes to improve performance and robustness under load: - remove special treatment of pager_map mappings in pmaps. this is required now, since I've removed the globals that expose the address range. pager_map now uses pmap_kenter_pa() instead of pmap_enter(), so there's no longer any need to special-case it. - eliminate struct uvm_vnode by moving its fields into struct vnode. - rewrite the pageout path. the pager is now responsible for handling the high-level requests instead of only getting control after a bunch of work has already been done on its behalf. this will allow us to UBCify LFS, which needs tighter control over its pages than other filesystems do. writing a page to disk no longer requires making it read-only, which allows us to write wired pages without causing all kinds of havoc. - use a new PG_PAGEOUT flag to indicate that a page should be freed on behalf of the pagedaemon when it's unlocked. this flag is very similar to PG_RELEASED, but unlike PG_RELEASED, PG_PAGEOUT can be cleared if the pageout fails due to eg. an indirect-block buffer being locked. this allows us to remove the "version" field from struct vm_page, and together with shrinking "loan_count" from 32 bits to 16, struct vm_page is now 4 bytes smaller. - no longer use PG_RELEASED for swap-backed pages. if the page is busy because it's being paged out, we can't release the swap slot to be reallocated until that write is complete, but unlike with vnodes we don't keep a count of in-progress writes so there's no good way to know when the write is done. instead, when we need to free a busy swap-backed page, just sleep until we can get it busy ourselves. - implement a fast-path for extending writes which allows us to avoid zeroing new pages. this substantially reduces cpu usage. - encapsulate the data used by the genfs code in a struct genfs_node, which must be the first element of the filesystem-specific vnode data for filesystems which use genfs_{get,put}pages(). - eliminate many of the UVM pagerops, since they aren't needed anymore now that the pager "put" operation is a higher-level operation. - enhance the genfs code to allow NFS to use the genfs_{get,put}pages instead of a modified copy. - clean up struct vnode by removing all the fields that used to be used by the vfs_cluster.c code (which we don't use anymore with UBC). - remove kmem_object and mb_object since they were useless. instead of allocating pages to these objects, we now just allocate pages with no object. such pages are mapped in the kernel until they are freed, so we can use the mapping to find the page to free it. this allows us to remove splvm() protection in several places. The sum of all these changes improves write throughput on my decstation 5000/200 to within 1% of the rate of NetBSD 1.5 and reduces the elapsed time for "make release" of a NetBSD 1.5 source tree on my 128MB pc to 10% less than a 1.5 kernel took.
2001-09-16 00:36:31 +04:00
KASSERT((pg->flags & (PG_PAGEOUT|PG_RELEASED)) == 0);
KASSERT(uvm_page_owner_locked_p(pg, true));
2006-04-13 12:33:18 +04:00
1998-03-09 03:58:55 +03:00
/* gain ownership? */
if (tag) {
2006-04-13 12:33:18 +04:00
KASSERT((pg->flags & PG_BUSY) != 0);
1998-03-09 03:58:55 +03:00
if (pg->owner_tag) {
printf("uvm_page_own: page %p already owned "
2020-05-19 23:46:39 +03:00
"by proc %d.%d [%s]\n", pg,
pg->owner, pg->lowner, pg->owner_tag);
1998-03-09 03:58:55 +03:00
panic("uvm_page_own");
}
pg->owner = curproc->p_pid;
pg->lowner = curlwp->l_lid;
1998-03-09 03:58:55 +03:00
pg->owner_tag = tag;
return;
}
/* drop ownership */
2006-04-13 12:33:18 +04:00
KASSERT((pg->flags & PG_BUSY) == 0);
1998-03-09 03:58:55 +03:00
if (pg->owner_tag == NULL) {
printf("uvm_page_own: dropping ownership of an non-owned "
"page (%p)\n", pg);
panic("uvm_page_own");
}
pg->owner_tag = NULL;
}
#endif
/*
* uvm_pagelookup: look up a page
*
* => caller should lock object to keep someone from pulling the page
* out from under it
*/
struct vm_page *
uvm_pagelookup(struct uvm_object *obj, voff_t off)
{
struct vm_page *pg;
KASSERT(db_active || rw_lock_held(obj->vmobjlock));
pg = radix_tree_lookup_node(&obj->uo_pages, off >> PAGE_SHIFT);
KASSERT(pg == NULL || obj->uo_npages != 0);
KASSERT(pg == NULL || (pg->flags & (PG_RELEASED|PG_PAGEOUT)) == 0 ||
(pg->flags & PG_BUSY) != 0);
return pg;
}
/*
* uvm_pagewire: wire the page, thus removing it from the daemon's grasp
*
* => caller must lock objects
* => caller must hold pg->interlock
*/
void
uvm_pagewire(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, true));
KASSERT(mutex_owned(&pg->interlock));
#if defined(READAHEAD_STATS)
if ((pg->flags & PG_READAHEAD) != 0) {
uvm_ra_hit.ev_count++;
pg->flags &= ~PG_READAHEAD;
}
#endif /* defined(READAHEAD_STATS) */
if (pg->wire_count == 0) {
uvm_pagedequeue(pg);
atomic_inc_uint(&uvmexp.wired);
}
pg->wire_count++;
KASSERT(pg->wire_count > 0); /* detect wraparound */
}
/*
* uvm_pageunwire: unwire the page.
*
* => activate if wire count goes to zero.
* => caller must lock objects
* => caller must hold pg->interlock
*/
void
uvm_pageunwire(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, true));
KASSERT(pg->wire_count != 0);
KASSERT(!uvmpdpol_pageisqueued_p(pg));
KASSERT(mutex_owned(&pg->interlock));
pg->wire_count--;
if (pg->wire_count == 0) {
uvm_pageactivate(pg);
KASSERT(uvmexp.wired != 0);
atomic_dec_uint(&uvmexp.wired);
}
}
/*
* uvm_pagedeactivate: deactivate page
*
* => caller must lock objects
* => caller must check to make sure page is not wired
* => object that page belongs to must be locked (so we can adjust pg->flags)
* => caller must clear the reference on the page before calling
* => caller must hold pg->interlock
*/
void
uvm_pagedeactivate(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, false));
KASSERT(mutex_owned(&pg->interlock));
if (pg->wire_count == 0) {
KASSERT(uvmpdpol_pageisqueued_p(pg));
uvmpdpol_pagedeactivate(pg);
}
}
/*
* uvm_pageactivate: activate page
*
* => caller must lock objects
* => caller must hold pg->interlock
*/
void
uvm_pageactivate(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, false));
KASSERT(mutex_owned(&pg->interlock));
#if defined(READAHEAD_STATS)
if ((pg->flags & PG_READAHEAD) != 0) {
uvm_ra_hit.ev_count++;
pg->flags &= ~PG_READAHEAD;
}
#endif /* defined(READAHEAD_STATS) */
if (pg->wire_count == 0) {
uvmpdpol_pageactivate(pg);
}
}
/*
* uvm_pagedequeue: remove a page from any paging queue
2020-03-03 11:13:44 +03:00
*
* => caller must lock objects
* => caller must hold pg->interlock
*/
void
uvm_pagedequeue(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, true));
KASSERT(mutex_owned(&pg->interlock));
if (uvmpdpol_pageisqueued_p(pg)) {
uvmpdpol_pagedequeue(pg);
}
}
/*
* uvm_pageenqueue: add a page to a paging queue without activating.
* used where a page is not really demanded (yet). eg. read-ahead
*
* => caller must lock objects
* => caller must hold pg->interlock
*/
void
uvm_pageenqueue(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, false));
KASSERT(mutex_owned(&pg->interlock));
if (pg->wire_count == 0 && !uvmpdpol_pageisqueued_p(pg)) {
uvmpdpol_pageenqueue(pg);
}
}
/*
* uvm_pagelock: acquire page interlock
*/
void
uvm_pagelock(struct vm_page *pg)
{
mutex_enter(&pg->interlock);
}
/*
* uvm_pagelock2: acquire two page interlocks
*/
void
uvm_pagelock2(struct vm_page *pg1, struct vm_page *pg2)
{
if (pg1 < pg2) {
mutex_enter(&pg1->interlock);
mutex_enter(&pg2->interlock);
} else {
mutex_enter(&pg2->interlock);
mutex_enter(&pg1->interlock);
}
}
/*
* uvm_pageunlock: release page interlock, and if a page replacement intent
* is set on the page, pass it to uvmpdpol to make real.
2020-03-03 11:13:44 +03:00
*
* => caller must hold pg->interlock
*/
void
uvm_pageunlock(struct vm_page *pg)
{
if ((pg->pqflags & PQ_INTENT_SET) == 0 ||
(pg->pqflags & PQ_INTENT_QUEUED) != 0) {
mutex_exit(&pg->interlock);
return;
}
pg->pqflags |= PQ_INTENT_QUEUED;
mutex_exit(&pg->interlock);
uvmpdpol_pagerealize(pg);
}
/*
* uvm_pageunlock2: release two page interlocks, and for both pages if a
* page replacement intent is set on the page, pass it to uvmpdpol to make
* real.
2020-03-03 11:13:44 +03:00
*
* => caller must hold pg->interlock
*/
void
uvm_pageunlock2(struct vm_page *pg1, struct vm_page *pg2)
{
if ((pg1->pqflags & PQ_INTENT_SET) == 0 ||
(pg1->pqflags & PQ_INTENT_QUEUED) != 0) {
mutex_exit(&pg1->interlock);
pg1 = NULL;
} else {
pg1->pqflags |= PQ_INTENT_QUEUED;
mutex_exit(&pg1->interlock);
}
if ((pg2->pqflags & PQ_INTENT_SET) == 0 ||
(pg2->pqflags & PQ_INTENT_QUEUED) != 0) {
mutex_exit(&pg2->interlock);
pg2 = NULL;
} else {
pg2->pqflags |= PQ_INTENT_QUEUED;
mutex_exit(&pg2->interlock);
}
if (pg1 != NULL) {
uvmpdpol_pagerealize(pg1);
}
if (pg2 != NULL) {
uvmpdpol_pagerealize(pg2);
}
}
/*
* uvm_pagezero: zero fill a page
*
* => if page is part of an object then the object should be locked
* to protect pg->flags.
*/
void
uvm_pagezero(struct vm_page *pg)
{
uvm_pagemarkdirty(pg, UVM_PAGE_STATUS_DIRTY);
pmap_zero_page(VM_PAGE_TO_PHYS(pg));
}
/*
* uvm_pagecopy: copy a page
*
* => if page is part of an object then the object should be locked
* to protect pg->flags.
*/
void
uvm_pagecopy(struct vm_page *src, struct vm_page *dst)
{
uvm_pagemarkdirty(dst, UVM_PAGE_STATUS_DIRTY);
pmap_copy_page(VM_PAGE_TO_PHYS(src), VM_PAGE_TO_PHYS(dst));
}
/*
* uvm_pageismanaged: test it see that a page (specified by PA) is managed.
*/
bool
uvm_pageismanaged(paddr_t pa)
{
return (uvm_physseg_find(atop(pa), NULL) != UVM_PHYSSEG_TYPE_INVALID);
}
/*
* uvm_page_lookup_freelist: look up the free list for the specified page
*/
int
uvm_page_lookup_freelist(struct vm_page *pg)
{
uvm_physseg_t upm;
upm = uvm_physseg_find(atop(VM_PAGE_TO_PHYS(pg)), NULL);
KASSERT(upm != UVM_PHYSSEG_TYPE_INVALID);
return uvm_physseg_get_free_list(upm);
}
/*
* uvm_page_owner_locked_p: return true if object associated with page is
* locked. this is a weak check for runtime assertions only.
*/
bool
uvm_page_owner_locked_p(struct vm_page *pg, bool exclusive)
{
if (pg->uobject != NULL) {
return exclusive
? rw_write_held(pg->uobject->vmobjlock)
: rw_lock_held(pg->uobject->vmobjlock);
}
if (pg->uanon != NULL) {
return exclusive
? rw_write_held(pg->uanon->an_lock)
: rw_lock_held(pg->uanon->an_lock);
}
return true;
}
/*
* uvm_pagereadonly_p: return if the page should be mapped read-only
*/
bool
uvm_pagereadonly_p(struct vm_page *pg)
{
struct uvm_object * const uobj = pg->uobject;
KASSERT(uobj == NULL || rw_lock_held(uobj->vmobjlock));
KASSERT(uobj != NULL || rw_lock_held(pg->uanon->an_lock));
if ((pg->flags & PG_RDONLY) != 0) {
return true;
}
if (uvm_pagegetdirty(pg) == UVM_PAGE_STATUS_CLEAN) {
return true;
}
if (uobj == NULL) {
return false;
}
return UVM_OBJ_NEEDS_WRITEFAULT(uobj);
}
#ifdef PMAP_DIRECT
/*
* Call pmap to translate physical address into a virtual and to run a callback
* for it. Used to avoid actually mapping the pages, pmap most likely uses direct map
* or equivalent.
*/
int
uvm_direct_process(struct vm_page **pgs, u_int npages, voff_t off, vsize_t len,
int (*process)(void *, size_t, void *), void *arg)
{
int error = 0;
paddr_t pa;
size_t todo;
voff_t pgoff = (off & PAGE_MASK);
struct vm_page *pg;
KASSERT(npages > 0);
KASSERT(len > 0);
for (int i = 0; i < npages; i++) {
pg = pgs[i];
KASSERT(len > 0);
/*
* Caller is responsible for ensuring all the pages are
* available.
*/
KASSERT(pg != NULL);
KASSERT(pg != PGO_DONTCARE);
pa = VM_PAGE_TO_PHYS(pg);
todo = MIN(len, PAGE_SIZE - pgoff);
error = pmap_direct_process(pa, pgoff, todo, process, arg);
if (error)
break;
pgoff = 0;
len -= todo;
}
KASSERTMSG(error != 0 || len == 0, "len %lu != 0 for non-error", len);
return error;
}
#endif /* PMAP_DIRECT */
#if defined(DDB) || defined(DEBUGPRINT)
/*
* uvm_page_printit: actually print the page
*/
static const char page_flagbits[] = UVM_PGFLAGBITS;
static const char page_pqflagbits[] = UVM_PQFLAGBITS;
void
uvm_page_printit(struct vm_page *pg, bool full,
void (*pr)(const char *, ...))
{
struct vm_page *tpg;
struct uvm_object *uobj;
struct pgflbucket *pgb;
struct pgflist *pgl;
char pgbuf[128];
(*pr)("PAGE %p:\n", pg);
snprintb(pgbuf, sizeof(pgbuf), page_flagbits, pg->flags);
(*pr)(" flags=%s\n", pgbuf);
snprintb(pgbuf, sizeof(pgbuf), page_pqflagbits, pg->pqflags);
(*pr)(" pqflags=%s\n", pgbuf);
(*pr)(" uobject=%p, uanon=%p, offset=0x%llx\n",
pg->uobject, pg->uanon, (long long)pg->offset);
(*pr)(" loan_count=%d wire_count=%d bucket=%d freelist=%d\n",
pg->loan_count, pg->wire_count, uvm_page_get_bucket(pg),
uvm_page_get_freelist(pg));
(*pr)(" pa=0x%lx\n", (long)VM_PAGE_TO_PHYS(pg));
#if defined(UVM_PAGE_TRKOWN)
if (pg->flags & PG_BUSY)
2020-05-19 23:46:39 +03:00
(*pr)(" owning process = %d.%d, tag=%s\n",
pg->owner, pg->lowner, pg->owner_tag);
else
(*pr)(" page not busy, no owner\n");
#else
(*pr)(" [page ownership tracking disabled]\n");
#endif
if (!full)
return;
/* cross-verify object/anon */
if ((pg->flags & PG_FREE) == 0) {
if (pg->flags & PG_ANON) {
if (pg->uanon == NULL || pg->uanon->an_page != pg)
(*pr)(" >>> ANON DOES NOT POINT HERE <<< (%p)\n",
(pg->uanon) ? pg->uanon->an_page : NULL);
else
(*pr)(" anon backpointer is OK\n");
} else {
uobj = pg->uobject;
if (uobj) {
(*pr)(" checking object list\n");
tpg = uvm_pagelookup(uobj, pg->offset);
if (tpg)
(*pr)(" page found on object list\n");
else
(*pr)(" >>> PAGE NOT FOUND ON OBJECT LIST! <<<\n");
}
}
}
/* cross-verify page queue */
if (pg->flags & PG_FREE) {
int fl = uvm_page_get_freelist(pg);
int b = uvm_page_get_bucket(pg);
pgb = uvm.page_free[fl].pgfl_buckets[b];
pgl = &pgb->pgb_colors[VM_PGCOLOR(pg)];
(*pr)(" checking pageq list\n");
LIST_FOREACH(tpg, pgl, pageq.list) {
if (tpg == pg) {
break;
}
}
if (tpg)
(*pr)(" page found on pageq list\n");
else
(*pr)(" >>> PAGE NOT FOUND ON PAGEQ LIST! <<<\n");
}
}
/*
* uvm_page_printall - print a summary of all managed pages
*/
void
uvm_page_printall(void (*pr)(const char *, ...))
{
uvm_physseg_t i;
paddr_t pfn;
struct vm_page *pg;
(*pr)("%18s %4s %4s %18s %18s"
#ifdef UVM_PAGE_TRKOWN
" OWNER"
#endif
"\n", "PAGE", "FLAG", "PQ", "UOBJECT", "UANON");
for (i = uvm_physseg_get_first();
uvm_physseg_valid_p(i);
i = uvm_physseg_get_next(i)) {
for (pfn = uvm_physseg_get_start(i);
2017-02-05 10:25:49 +03:00
pfn < uvm_physseg_get_end(i);
pfn++) {
pg = PHYS_TO_VM_PAGE(ptoa(pfn));
(*pr)("%18p %04x %08x %18p %18p",
pg, pg->flags, pg->pqflags, pg->uobject,
pg->uanon);
#ifdef UVM_PAGE_TRKOWN
if (pg->flags & PG_BUSY)
(*pr)(" %d [%s]", pg->owner, pg->owner_tag);
#endif
(*pr)("\n");
}
}
}
/*
* uvm_page_print_freelists - print a summary freelists
*/
void
uvm_page_print_freelists(void (*pr)(const char *, ...))
{
struct pgfreelist *pgfl;
struct pgflbucket *pgb;
int fl, b, c;
(*pr)("There are %d freelists with %d buckets of %d colors.\n\n",
VM_NFREELIST, uvm.bucketcount, uvmexp.ncolors);
2020-03-03 11:13:44 +03:00
for (fl = 0; fl < VM_NFREELIST; fl++) {
pgfl = &uvm.page_free[fl];
(*pr)("freelist(%d) @ %p\n", fl, pgfl);
for (b = 0; b < uvm.bucketcount; b++) {
pgb = uvm.page_free[fl].pgfl_buckets[b];
(*pr)(" bucket(%d) @ %p, nfree = %d, lock @ %p:\n",
b, pgb, pgb->pgb_nfree,
&uvm_freelist_locks[b].lock);
for (c = 0; c < uvmexp.ncolors; c++) {
(*pr)(" color(%d) @ %p, ", c,
&pgb->pgb_colors[c]);
(*pr)("first page = %p\n",
LIST_FIRST(&pgb->pgb_colors[c]));
}
}
}
}
#endif /* DDB || DEBUGPRINT */