summaryrefslogtreecommitdiff
path: root/ext2fs/pager.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext2fs/pager.c')
-rw-r--r--ext2fs/pager.c409
1 files changed, 270 insertions, 139 deletions
diff --git a/ext2fs/pager.c b/ext2fs/pager.c
index eac1e9f8..9e8b5bb3 100644
--- a/ext2fs/pager.c
+++ b/ext2fs/pager.c
@@ -29,127 +29,256 @@ spin_lock_t node_to_page_lock = SPIN_LOCK_INITIALIZER;
#else
#define MAY_CACHE 1
#endif
+
+/* ---------------------------------------------------------------- */
-/* Find the location on disk of page OFFSET in pager UPI. Return the
- disk address (in disk block) in *ADDR. If *NPLOCK is set on
- return, then release that mutex after I/O on the data has
- completed. Set DISKSIZE to be the amount of valid data on disk.
- (If this is an unallocated block, then set *ADDR to zero.) */
+/* Find the location on disk of page OFFSET in NODE. Return the disk block
+ in BLOCK (if unallocated, then return 0). If *NODE_LOCK is set on return,
+ then release that mutex after I/O on the data has completed. Set LENGTH
+ to be the amount of valid data on disk. */
static error_t
-find_address (struct user_pager_info *upi,
- vm_address_t offset,
- daddr_t *addr,
- int *length,
- struct rwlock **nplock)
+find_block (struct node *node, vm_offset_t offset,
+ daddr_t *block, int *length, int create,
+ struct rwlock **node_lock)
{
- assert (upi->type == DISK || upi->type == FILE_DATA);
+ error_t err;
+
+ rwlock_reader_lock (&node->dn->alloc_lock);
+ *node_lock = &node->dn->alloc_lock;
- if (upi->type == DISK)
+ if (offset >= node->allocsize)
{
- *length = vm_page_size;
- *addr = offset / DEV_BSIZE;
- *nplock = 0;
+ rwlock_reader_unlock (&node->dn->alloc_lock);
+ return EIO;
+ }
+
+ if (offset + vm_page_size > node->allocsize)
+ *length = node->allocsize - offset;
+ else
+ *length = vm_page_size;
+
+ err = ext2_getblk (node, offset >> log2_block_size, create, block);
+ if (err == EINVAL)
+ /* Don't barf yet if the node is unallocated. */
+ {
+ *block = 0;
+ err = 0;
+ }
+
+ return err;
+}
+
+/* ---------------------------------------------------------------- */
+
+static error_t
+disk_pager_read_page (vm_offset_t page, vm_address_t *buf, int *writelock)
+{
+ int length = vm_page_size;
+
+ if (page + vm_page_size > device_size)
+ length = device_size - offset;
+
+ err = dev_read_sync (block, (void *)buf, length);
+ if (!err && length != vm_page_size)
+ bzero ((void *)(*buf + length), vm_page_size - length);
+
+ *writelock = 0;
+
+ return err;
+}
+
+static error_t
+disk_pager_write_page (vm_offset_t page, vm_address_t buf)
+{
+ int length = vm_page_size;
+ if (page + vm_page_size > device_size)
+ length = device_size - offset;
+ return dev_write_sync (block, buf, length);
+}
+
+/* ---------------------------------------------------------------- */
+
+/* Read one page for the pager backing NODE at offset PAGE, into BUF. This
+ may need to read several filesystem blocks to satisfy one page, and tries
+ to consolidate the i/o if possible. */
+static error_t
+file_pager_read_page (struct node *node, vm_offset_t page,
+ vm_address_t *buf, int *writelock)
+{
+ int offs = 0;
+ struct rwlock *node_lock = NULL;
+ int left = vm_page_size;
+ daddr_t pending_blocks = 0;
+ int num_pending_blocks = 0;
+
+ /* Read the NUM_PENDING_BLOCKS blocks in PENDING_BLOCKS, into the buffer
+ pointed to by BUF (allocating it if necessary) at offset OFFS. OFFS in
+ adjusted by the amount read, and NUM_PENDING_BLOCKS is zeroed. Any read
+ error is returned. */
+ error_t do_pending_reads ()
+ {
+ daddr_t dev_block = pending_block >> log2_dev_blocks_per_fs_block;
+ int length = num_pending_blocks << log2_block_size;
+ vm_address_t new_buf;
+
+ err = dev_read_sync (dev_block, &new_buf, length);
+ if (err)
+ return err;
+
+ if (offs == 0)
+ /* First read, make the returned page be our buffer. */
+ *buf = new_buf;
+ else
+ /* We've already got some buffer, so copy into it. */
+ bcopy ((char *)*buf + offs, (char *)new_buf, length);
+
+ offs += length;
+ num_pending_blocks = 0;
+
return 0;
}
- else
+
+ while (left > 0)
{
- error_t err;
- struct node *np = upi->np;
-
- rwlock_reader_lock (&np->dn->alloc_lock);
- *nplock = &np->dn->alloc_lock;
+ u32 block, length;
- if (offset >= np->allocsize)
+ err = find_block (node, page, &block, &length, 0, &node_lock);
+ if (err)
+ break;
+
+ if (block != pending_blocks + num_pending_blocks);
{
- rwlock_reader_unlock (&np->dn->alloc_lock);
- return EIO;
+ err = do_dev_reads ();
+ if (err)
+ break;
+ pending_blocks = block;
}
-
- if (offset + vm_page_size > np->allocsize)
- *length = np->allocsize - offset;
- else
- *length = vm_page_size;
- err = ext2_getblk(np, offset / block_size, 0, addr);
- if (err == EINVAL)
+ if (block == 0)
+ /* Reading unallocate block, just make a zero-filled one. */
{
- *addr = 0;
- err = 0;
+ if (offs == 0)
+ /* No page allocated to read into yet. */
+ {
+ err = vm_allocate (mach_task_self (), &block, vm_page_size, 1);
+ if (err)
+ break;
+ *writelock = 1;
+ }
+ bzero ((char *)*buf + offs, block_size);
+ offs += block_size;
}
+ else
+ num_pending_blocks++;
- return err;
+ left -= length;
}
-}
+ if (!err && num_pending_blocks > 0)
+ do_pending_reads();
+
+ if (node_lock)
+ rwlock_reader_unlock (node_lock);
-/* Implement the pager_read_page callback from the pager library. See
- <hurd/pager.h> for the interface description. */
-error_t
-pager_read_page (struct user_pager_info *pager,
- vm_offset_t page,
- vm_address_t *buf,
- int *writelock)
+ return err;
+}
+
+/* ---------------------------------------------------------------- */
+
+/* Write one page for the pager backing NODE, at offset PAGE, into BUF. This
+ may need to write several filesystem blocks to satisfy one page, and tries
+ to consolidate the i/o if possible. */
+static error_t
+file_pager_write_page (struct node *node, vm_offset_t page, vm_address_t buf)
{
- error_t err;
- struct rwlock *nplock;
- daddr_t addr;
- int length;
-
- err = find_address (pager, page, &addr, &length, &nplock);
- if (err)
- return err;
-
- if (addr)
+ int offs = 0;
+ struct rwlock *node_lock = NULL;
+ int left = vm_page_size;
+ daddr_t pending_blocks = 0;
+ int num_pending_blocks = 0;
+
+ /* Write the NUM_PENDING_BLOCKS blocks in PENDING_BLOCKS, from BUF at
+ offset OFFS. OFFS in adjusted by the amount write, and
+ NUM_PENDING_BLOCKS is zeroed. */
+ error_t do_pending_writes ()
{
- err = dev_read_sync (addr, (void *)buf, length);
- if (!err && length != vm_page_size)
- bzero ((void *)(*buf + length), vm_page_size - length);
- *writelock = 0;
+ daddr_t dev_block = pending_block >> log2_dev_blocks_per_fs_block;
+ int length = num_pending_blocks << log2_block_size;
+
+ if (offs > 0)
+ /* Put what we're going to write into a page-aligned buffer. */
+ {
+ bcopy (buf + offs, page_buf, length);
+ err = dev_write_sync (dev_block, page_buf, length);
+ }
+ else
+ err = dev_write_sync (dev_block, buf, length);
+ if (err)
+ return err;
+
+ offs += length;
+ num_pending_blocks = 0;
+
+ return 0;
}
- else
+
+ while (left > 0)
{
- vm_allocate (mach_task_self (), buf, vm_page_size, 1);
- *writelock = 1;
+ u32 block, length;
+
+ err = find_block (node, page, &block, &length, 1, &node_lock);
+ if (err)
+ break;
+
+ if (block != pending_blocks + num_pending_blocks);
+ {
+ err = do_dev_writes ();
+ if (err)
+ break;
+ pending_blocks = block;
+ }
+
+ num_pending_blocks++;
+ left -= length;
}
+
+ if (!err && num_pending_blocks > 0)
+ do_pending_writes();
- if (nplock)
- rwlock_reader_unlock (nplock);
-
+ if (node_lock)
+ rwlock_reader_unlock (node_lock);
+
return err;
}
+
+/* ---------------------------------------------------------------- */
-/* Implement the pager_write_page callback from the pager library. See
- <hurd/pager.h> for the interface description. */
-error_t
-pager_write_page (struct user_pager_info *pager,
- vm_offset_t page,
+/* Satisfy a pager read request for either the disk pager or file pager
+ PAGER, to the page at offset PAGE into BUF. WRITELOCK should be set if
+ the pager should make the page writeable. */
+static error_t
+pager_read_page (struct user_pager_info *pager, vm_offset_t page,
+ vm_address_t *buf, int *writelock)
+{
+ if (pager->type == DISK)
+ return disk_pager_read_page (page, buf, writelock);
+ else
+ return file_pager_read_page (pager->node, page, buf, writelock);
+}
+
+/* Satisfy a pager write request for either the disk pager or file pager
+ PAGER, from the page at offset PAGE from BUF. */
+static error_t
+pager_write_page (struct user_pager_info *pager, vm_offset_t page,
vm_address_t buf)
{
- daddr_t addr;
- int length;
- struct rwlock *nplock;
- error_t err;
-
- err = find_address (pager, page, &addr, &length, &nplock);
- if (err)
- return err;
-
- if (addr)
- err = dev_write_sync (addr, buf, length);
+ if (pager->type == DISK)
+ return disk_pager_write_page (page, buf);
else
- {
- ext2_error("pager_write_page",
- "Attempt to write unallocated disk;"
- " object = %p; offset = 0x%x", pager, page);
- /* unallocated disk; error would be pointless */
- err = 0;
- }
-
- if (nplock)
- rwlock_reader_unlock (nplock);
-
- return err;
+ return file_pager_write_page (pager->node, page, buf);
}
+
+/* ---------------------------------------------------------------- */
/* Implement the pager_unlock_page callback from the pager library. See
<hurd/pager.h> for the interface description. */
@@ -163,14 +292,14 @@ pager_unlock_page (struct user_pager_info *pager,
{
error_t err;
char *buf;
- struct node *np = pager->np;
- struct disknode *dn = np->dn;
+ struct node *node = pager->node;
+ struct disknode *dn = node->dn;
rwlock_writer_lock (&dn->alloc_lock);
err = diskfs_catch_exception ();
if (!err)
- err = ext2_getblk(np, address / block_size, 1, &buf);
+ err = ext2_getblk(node, address >> log2_block_size, 1, &buf);
diskfs_end_catch_exception ();
rwlock_writer_unlock (&dn->alloc_lock);
@@ -178,6 +307,8 @@ pager_unlock_page (struct user_pager_info *pager,
return err;
}
}
+
+/* ---------------------------------------------------------------- */
/* Implement the pager_report_extent callback from the pager library. See
<hurd/pager.h> for the interface description. */
@@ -193,7 +324,7 @@ pager_report_extent (struct user_pager_info *pager,
if (pager->type == DISK)
*size = device_size;
else
- *size = pager->np->allocsize;
+ *size = pager->node->allocsize;
return 0;
}
@@ -205,16 +336,16 @@ pager_clear_user_data (struct user_pager_info *upi)
{
assert (upi->type == FILE_DATA);
spin_lock (&node_to_page_lock);
- upi->np->dn->fileinfo = 0;
+ upi->node->dn->fileinfo = 0;
spin_unlock (&node_to_page_lock);
- diskfs_nrele_light (upi->np);
+ diskfs_nrele_light (upi->node);
*upi->prevp = upi->next;
if (upi->next)
upi->next->prevp = upi->prevp;
free (upi);
}
-
+/* ---------------------------------------------------------------- */
/* Create a the DISK pager, initializing DISKPAGER, and DISKPAGERPORT */
void
@@ -222,58 +353,34 @@ create_disk_pager ()
{
disk_pager = malloc (sizeof (struct user_pager_info));
disk_pager->type = DISK;
- disk_pager->np = 0;
+ disk_pager->node = 0;
disk_pager->p = pager_create (disk_pager, MAY_CACHE, MEMORY_OBJECT_COPY_NONE);
disk_pager_port = pager_get_port (disk_pager->p);
mach_port_insert_right (mach_task_self (), disk_pager_port, disk_pager_port,
MACH_MSG_TYPE_MAKE_SEND);
}
-/* This syncs a single file (NP) to disk. Wait for all I/O to complete
- if WAIT is set. NP->lock must be held. */
-void
-diskfs_file_update (struct node *np, int wait)
-{
- struct user_pager_info *upi;
-
- spin_lock (&node_to_page_lock);
- upi = np->dn->fileinfo;
- if (upi)
- pager_reference (upi->p);
- spin_unlock (&node_to_page_lock);
-
- if (upi)
- {
- pager_sync (upi->p, wait);
- pager_unreference (upi->p);
- }
-
- pokel_sync (&np->dn->pokel, wait);
-
- diskfs_node_update (np, wait);
-}
-
/* Call this to create a FILE_DATA pager and return a send right.
- NP must be locked. */
+ NODE must be locked. */
mach_port_t
-diskfs_get_filemap (struct node *np)
+diskfs_get_filemap (struct node *node)
{
struct user_pager_info *upi;
mach_port_t right;
- assert (S_ISDIR (np->dn_stat.st_mode)
- || S_ISREG (np->dn_stat.st_mode)
- || (S_ISLNK (np->dn_stat.st_mode)));
+ assert (S_ISDIR (node->dn_stat.st_mode)
+ || S_ISREG (node->dn_stat.st_mode)
+ || (S_ISLNK (node->dn_stat.st_mode)));
spin_lock (&node_to_page_lock);
- if (!np->dn->fileinfo)
+ if (!node->dn->fileinfo)
{
upi = malloc (sizeof (struct user_pager_info));
upi->type = FILE_DATA;
- upi->np = np;
- diskfs_nref_light (np);
+ upi->node = node;
+ diskfs_nref_light (node);
upi->p = pager_create (upi, MAY_CACHE, MEMORY_OBJECT_COPY_DELAY);
- np->dn->fileinfo = upi;
+ node->dn->fileinfo = upi;
spin_lock (&pager_list_lock);
upi->next = file_pager_list;
@@ -283,7 +390,7 @@ diskfs_get_filemap (struct node *np)
file_pager_list = upi;
spin_unlock (&pager_list_lock);
}
- right = pager_get_port (np->dn->fileinfo->p);
+ right = pager_get_port (node->dn->fileinfo->p);
spin_unlock (&node_to_page_lock);
mach_port_insert_right (mach_task_self (), right, right,
@@ -292,15 +399,39 @@ diskfs_get_filemap (struct node *np)
return right;
}
+/* This syncs a single file (NODE) to disk. Wait for all I/O to complete
+ if WAIT is set. NODE->lock must be held. */
+void
+diskfs_file_update (struct node *node, int wait)
+{
+ struct user_pager_info *upi;
+
+ spin_lock (&node_to_page_lock);
+ upi = node->dn->fileinfo;
+ if (upi)
+ pager_reference (upi->p);
+ spin_unlock (&node_to_page_lock);
+
+ if (upi)
+ {
+ pager_sync (upi->p, wait);
+ pager_unreference (upi->p);
+ }
+
+ pokel_sync (&node->dn->pokel, wait);
+
+ diskfs_node_update (node, wait);
+}
+
/* Call this when we should turn off caching so that unused memory object
ports get freed. */
void
-drop_pager_softrefs (struct node *np)
+drop_pager_softrefs (struct node *node)
{
struct user_pager_info *upi;
spin_lock (&node_to_page_lock);
- upi = np->dn->fileinfo;
+ upi = node->dn->fileinfo;
if (upi)
pager_reference (upi->p);
spin_unlock (&node_to_page_lock);
@@ -314,12 +445,12 @@ drop_pager_softrefs (struct node *np)
/* Call this when we should turn on caching because it's no longer
important for unused memory object ports to get freed. */
void
-allow_pager_softrefs (struct node *np)
+allow_pager_softrefs (struct node *node)
{
struct user_pager_info *upi;
spin_lock (&node_to_page_lock);
- upi = np->dn->fileinfo;
+ upi = node->dn->fileinfo;
if (upi)
pager_reference (upi->p);
spin_unlock (&node_to_page_lock);
@@ -333,13 +464,13 @@ allow_pager_softrefs (struct node *np)
/* Call this to find out the struct pager * corresponding to the
FILE_DATA pager of inode IP. This should be used *only* as a subsequent
argument to register_memory_fault_area, and will be deleted when
- the kernel interface is fixed. NP must be locked. */
+ the kernel interface is fixed. NODE must be locked. */
struct pager *
-diskfs_get_filemap_pager_struct (struct node *np)
+diskfs_get_filemap_pager_struct (struct node *node)
{
/* This is safe because fileinfo can't be cleared; there must be
an active mapping for this to be called. */
- return np->dn->fileinfo->p;
+ return node->dn->fileinfo->p;
}
/* Call function FUNC (which takes one argument, a pager) on each pager, with