diff options
Diffstat (limited to 'ext2fs/pager.c')
-rw-r--r-- | ext2fs/pager.c | 409 |
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 |