Converts page_map for bdev usage
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 28 Sep 2010 22:33:09 +0000 (15:33 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:54 +0000 (17:35 -0700)
Both inodes and blockdevs now have page mappings.  The block_readpage is
untested (and unused).

kern/include/blockdev.h
kern/include/pagemap.h [new file with mode: 0644]
kern/include/vfs.h
kern/src/Makefrag
kern/src/blockdev.c
kern/src/kfs.c
kern/src/pagemap.c [new file with mode: 0644]
kern/src/vfs.c

index 1742026..c18cd99 100644 (file)
@@ -10,6 +10,7 @@
 #include <ros/common.h>
 #include <kref.h>
 #include <slab.h>
+#include <pagemap.h>
 
 /* All block IO is done assuming a certain size sector, which is the smallest
  * possible unit of transfer between the kernel and the block layer.  This can
@@ -27,6 +28,7 @@ struct block_device {
        unsigned int                            b_sector_size;          /* HW sector size */
        unsigned long                           b_num_sectors;          /* Total sectors on dev */
        struct kref                                     b_kref;
+       struct page_map                         b_pm;
        void                                            *b_data;                        /* dev-specific use */
        char                                            b_name[BDEV_INLINE_NAME];
        // TODO: list or something of buffer heads (?)
diff --git a/kern/include/pagemap.h b/kern/include/pagemap.h
new file mode 100644 (file)
index 0000000..56dd9c2
--- /dev/null
@@ -0,0 +1,62 @@
+/* Copyright (c) 2010 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Page mapping: maps an object (inode or block dev) in page size chunks.
+ * Analagous to Linux's "struct address space".  While this is closely coupled
+ * with the VFS, block devices also use it (hence the separate header and c
+ * file). */
+
+#ifndef ROS_KERN_PAGEMAP_H
+#define ROS_KERN_PAGEMAP_H
+
+#include <radix.h>
+#include <atomic.h>
+
+/* Need to be careful, due to some ghetto circular references */
+struct page;
+struct inode;
+struct block_device;
+struct page_map_operations;
+
+/* Every object that has pages, like an inode or the swap (or even direct block
+ * devices) has a page_map, tracking which of its pages are currently in memory.
+ * It is a map, per object, from index to physical page frame. */
+struct page_map {
+       union {
+               struct inode                            *pm_host;       /* inode of the owner, if any */
+               struct block_device                     *pm_bdev;       /* bdev of the owner, if any */
+       };
+       struct radix_tree                       pm_tree;                /* tracks present pages */
+       spinlock_t                                      pm_tree_lock;   /* spinlock => we can't block */
+       unsigned long                           pm_num_pages;   /* how many pages are present */
+       struct page_map_operations      *pm_op;
+       unsigned int                            pm_flags;
+       /*... and private lists, backing block dev info, other mappings, etc. */
+};
+
+/* Operations performed on a page_map.  These are usually FS specific, which
+ * get assigned when the inode is created.
+ * Will fill these in as they are created/needed/used. */
+struct page_map_operations {
+       int (*readpage) (struct page_map *, struct page *);
+/*     readpages: read a list of pages
+       writepage: write from a page to its backing store
+       writepages: write a list of pages
+       sync_page: start the IO of already scheduled ops
+       set_page_dirty: mark the given page dirty
+       prepare_write: prepare to write (disk backed pages)
+       commit_write: complete a write (disk backed pages)
+       bmap: get a logical block number from a file block index
+       invalidate page: invalidate, part of truncating
+       release page: prepare to release 
+       direct_io: bypass the page cache */
+};
+
+/* Page cache functions */
+void pm_init(struct page_map *pm, struct page_map_operations *op, void *host);
+struct page *pm_find_page(struct page_map *pm, unsigned long index);
+int pm_insert_page(struct page_map *pm, unsigned long index, struct page *page);
+int pm_remove_page(struct page_map *pm, struct page *page);
+
+#endif /* ROS_KERN_PAGEMAP_H */
index fd138fe..f2b33ec 100644 (file)
@@ -20,6 +20,7 @@
 #include <timing.h>
 #include <radix.h>
 #include <hashtable.h>
+#include <pagemap.h>
 #include <blockdev.h>
 
 /* ghetto preprocessor hacks (since proc includes vfs) */
@@ -38,8 +39,6 @@ struct poll_table_struct {int x;};
 
 #include <ros/fs.h>
 
-struct page_map;       /* analagous to linux's address_space object */
-struct page_map_operations;
 struct super_block;
 struct super_operations;
 struct dentry;
@@ -108,37 +107,6 @@ struct nameidata {
 #define LOOKUP_CREATE          0x11    /* create a file if it doesn't exist */
 #define LOOKUP_ACCESS          0x12    /* access / check user permissions */
 
-/* Every object that has pages, like an inode or the swap (or even direct block
- * devices) has a page_map, tracking which of its pages are currently in memory.
- * It is a map, per object, from index to physical page frame. */
-struct page_map {
-       struct inode                            *pm_host;               /* inode of the owner, if any */
-       struct radix_tree                       pm_tree;                /* tracks present pages */
-       spinlock_t                                      pm_tree_lock;   /* spinlock => we can't block */
-       unsigned long                           pm_num_pages;   /* how many pages are present */
-       struct page_map_operations      *pm_op;
-       unsigned int                            pm_flags;
-       /*... and private lists, backing block dev info, other mappings, etc. */
-};
-
-/* Operations performed on a page_map.  These are usually FS specific, which
- * get assigned when the inode is created.
- * Will fill these in as they are created/needed/used. */
-struct page_map_operations {
-       int (*readpage) (struct page_map *, struct page *);
-/*     readpages: read a list of pages
-       writepage: write from a page to its backing store
-       writepages: write a list of pages
-       sync_page: start the IO of already scheduled ops
-       set_page_dirty: mark the given page dirty
-       prepare_write: prepare to write (disk backed pages)
-       commit_write: complete a write (disk backed pages)
-       bmap: get a logical block number from a file block index
-       invalidate page: invalidate, part of truncating
-       release page: prepare to release 
-       direct_io: bypass the page cache */
-};
-
 /* Superblock: Specific instance of a mounted filesystem.  All synchronization
  * is done with the one spinlock. */
 
@@ -217,7 +185,7 @@ struct inode {
        struct inode_operations         *i_op;
        struct file_operations          *i_fop;
        struct super_block                      *i_sb;
-       struct page_map                         *i_mapping;             /* usually points to i_data */
+       struct page_map                         *i_mapping;             /* usually points to i_pm */
        struct page_map                         i_pm;                   /* this inode's page cache */
        union {
                struct pipe_inode_info          *i_pipe;
@@ -481,11 +449,6 @@ int do_mkdir(char *path, int mode);
 int do_rmdir(char *path);
 struct file *dentry_open(struct dentry *dentry, int flags);
 void file_release(struct kref *kref);
-
-/* Page cache functions */
-struct page *pm_find_page(struct page_map *pm, unsigned long index);
-int pm_insert_page(struct page_map *pm, unsigned long index, struct page *page);
-int pm_remove_page(struct page_map *pm, struct page *page);
 int file_load_page(struct file *file, unsigned long index, struct page **pp);
 
 /* Process-related File management functions */
index 1fce1f6..2041660 100644 (file)
@@ -47,6 +47,7 @@ KERN_SRCFILES := $(KERN_ARCH_SRCFILES) \
                  $(KERN_SRC_DIR)/blockdev.c \
                  $(KERN_SRC_DIR)/ext2fs.c \
                  $(KERN_SRC_DIR)/testing.c \
+                 $(KERN_SRC_DIR)/pagemap.c \
                  $(KERN_SRC_DIR)/arsc.c
 
 # Only build files if they exist.
index d98cd02..be12fe1 100644 (file)
@@ -9,8 +9,10 @@
 #include <kmalloc.h>
 #include <slab.h>
 #include <page_alloc.h>
+#include <pmap.h>
 
 struct file_operations block_f_op;
+struct page_map_operations block_pm_op;
 struct kmem_cache *breq_kcache;
 
 void block_init(void)
@@ -31,12 +33,15 @@ void block_init(void)
        ram_bd->b_sector_size = 512;
        ram_bd->b_num_sectors = (unsigned int)_binary_mnt_ext2fs_img_size / 512;
        kref_init(&ram_bd->b_kref, fake_release, 1);
+       pm_init(&ram_bd->b_pm, &block_pm_op, ram_bd);
        ram_bd->b_data = _binary_mnt_ext2fs_img_start;
        strncpy(ram_bd->b_name, "RAMDISK", BDEV_INLINE_NAME);
        ram_bd->b_name[BDEV_INLINE_NAME - 1] = '\0';
        /* Connect it to the file system */
        struct file *ram_bf = make_device("/dev/ramdisk", S_IRUSR | S_IWUSR,
                                          __S_IFBLK, &block_f_op);
+       /* make sure the inode tracks the right pm (not it's internal one) */
+       ram_bf->f_dentry->d_inode->i_mapping = &ram_bd->b_pm;
        ram_bf->f_dentry->d_inode->i_bdev = ram_bd;     /* this holds the bd kref */
        kref_put(&ram_bf->f_kref);
        #endif /* __CONFIG_EXT2FS__ */
@@ -93,6 +98,54 @@ int make_request(struct block_device *bdev, struct block_request *req)
        return 0;
 }
 
+/* Fills page with the appropriate data from the block device.  There is only
+ * one BH, since bdev pages must be made of contiguous blocks.  Ideally, this
+ * will work for a bdev file that reads the pm via generic_file_read(), though
+ * that is a ways away still. */
+int block_readpage(struct page_map *pm, struct page *page)
+{
+       int retval;
+       unsigned long first_byte = page->pg_index * PGSIZE;
+       struct block_device *bdev = pm->pm_bdev;
+       struct block_request *breq = kmem_cache_alloc(breq_kcache, 0);
+       struct buffer_head *bh = kmem_cache_alloc(bh_kcache, 0);
+
+       assert(breq && bh);             /* should unlock_page and return -1 */
+       assert(pm == page->pg_mapping);
+
+       /* TODO: move this BH stuff to a helper.  Check for existing BH.  Check for
+        * exceeding the size, sort out block size */
+
+       /* Set up the BH.  bdevs do a 1:1 BH to page mapping */
+       bh->bh_page = page;                                                             /* weak ref */
+       bh->bh_buffer = page2kva(page);
+       bh->bh_flags = 0;                                                               /* whatever... */
+       bh->bh_next = 0;                                                                /* only one BH needed */
+       bh->bh_bdev = bdev;                                                             /* uncounted ref */
+       bh->bh_blocknum = page->pg_index * PGSIZE / bdev->b_sector_size;
+       bh->bh_numblock = PGSIZE / bdev->b_sector_size;
+       page->pg_private = bh;
+
+       /* Build the request.  TODO: think about the overlap btw this and the BH */
+       breq->flags = BREQ_READ;
+       breq->buffer = page2kva(page);
+       breq->first_sector = first_byte >> SECTOR_SZ_LOG;
+       breq->amount = PGSIZE >> SECTOR_SZ_LOG; // TODO: change amount to num_sect
+       retval = make_request(bdev, breq);                               /* This blocks */
+       assert(!retval);
+
+       /* after the data is read, we mark it up to date and unlock the page: */
+       page->pg_flags |= PG_UPTODATE;
+       unlock_page(page);
+       kmem_cache_free(breq_kcache, breq);
+       return 0;
+}
+
+/* Block device page map ops: */
+struct page_map_operations block_pm_op = {
+       block_readpage,
+};
+
 /* Block device file ops: for now, we don't let you do much of anything */
 struct file_operations block_f_op = {
        dev_c_llseek,
index 3709246..14fea7b 100644 (file)
@@ -148,7 +148,7 @@ int kfs_readpage(struct page_map *pm, struct page *page)
        bh->bh_next = 0;                                                                /* only one BH needed */
        bh->bh_bdev = pm->pm_host->i_sb->s_bdev;                /* uncounted */
        bh->bh_blocknum = page->pg_index;
-       bh->bh_numblock = 1;
+       bh->bh_numblock = 1;                                                    /* sector size = PGSIZE */
        page->pg_private = bh;
        /* This is supposed to be done in the IO system when the operation is
         * complete.  Since we aren't doing a real IO request, and it is already
diff --git a/kern/src/pagemap.c b/kern/src/pagemap.c
new file mode 100644 (file)
index 0000000..d980f91
--- /dev/null
@@ -0,0 +1,76 @@
+/* Copyright (c) 2010 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Page mapping: maps an object (inode or block dev) in page size chunks.
+ * Analagous to Linux's "struct address space" */
+
+#include <pmap.h>
+#include <atomic.h>
+#include <radix.h>
+#include <kref.h>
+#include <assert.h>
+#include <stdio.h>
+
+/* Initializes a PM.  Host should be an *inode or a *bdev (doesn't matter).  The
+ * reference this stores is uncounted. */
+void pm_init(struct page_map *pm, struct page_map_operations *op, void *host)
+{
+       pm->pm_bdev = host;                                             /* note the uncounted ref */
+       radix_tree_init(&pm->pm_tree);
+       spinlock_init(&pm->pm_tree_lock);
+       pm->pm_num_pages = 0;                                   /* no pages in a new pm */
+       pm->pm_op = op;
+       pm->pm_flags = 0;
+}
+
+/* Looks up the index'th page in the page map, returning an incref'd reference,
+ * or 0 if it was not in the map. */
+struct page *pm_find_page(struct page_map *pm, unsigned long index)
+{
+       spin_lock(&pm->pm_tree_lock);
+       struct page *page = (struct page*)radix_lookup(&pm->pm_tree, index);
+       if (page)
+               page_incref(page);
+       spin_unlock(&pm->pm_tree_lock);
+       return page;
+}
+
+/* Attempts to insert the page into the page_map, returns 0 for success, or an
+ * error code if there was one already (EEXIST) or we ran out of memory
+ * (ENOMEM).  On success, this will preemptively lock the page, and will also
+ * store a reference to the page in the pm. */
+int pm_insert_page(struct page_map *pm, unsigned long index, struct page *page)
+{
+       int error = 0;
+       spin_lock(&pm->pm_tree_lock);
+       error = radix_insert(&pm->pm_tree, index, page);
+       if (!error) {
+               page_incref(page);
+               page->pg_flags |= PG_LOCKED | PG_BUFFER;
+               page->pg_mapping = pm;
+               page->pg_index = index;
+               pm->pm_num_pages++;
+       }
+       spin_unlock(&pm->pm_tree_lock);
+       return error;
+}
+
+/* Removes the page, including its reference.  Not sure yet what interface we
+ * want to this (pm and index or page), and this has never been used.  There are
+ * also issues with when you want to call this, since a page in the cache may be
+ * mmap'd by someone else. */
+int pm_remove_page(struct page_map *pm, struct page *page)
+{
+       void *retval;
+       warn("pm_remove_page() hasn't been thought through or tested.");
+       /* TODO: check for dirty pages, don't let them be removed right away.  Need
+        * to schedule them for writeback, and then remove them later (callback). */
+       spin_lock(&pm->pm_tree_lock);
+       retval = radix_delete(&pm->pm_tree, page->pg_index);
+       spin_unlock(&pm->pm_tree_lock);
+       assert(retval == (void*)page);
+       page_decref(page);
+       pm->pm_num_pages--;
+       return 0;
+}
index 7fc7493..9e0f87a 100644 (file)
@@ -902,13 +902,9 @@ struct inode *get_inode(struct dentry *dentry)
        atomic_set(&inode->i_writecount, 0);
        /* Set up the page_map structures.  Default is to use the embedded one.
         * Might push some of this back into specific FSs.  For now, the FS tells us
-        * what pm_op they want via i_pm.pm_op, which we use when we point i_mapping
-        * to i_pm. */
+        * what pm_op they want via i_pm.pm_op, which we set again in pm_init() */
        inode->i_mapping = &inode->i_pm;
-       inode->i_mapping->pm_host = inode;
-       radix_tree_init(&inode->i_mapping->pm_tree);
-       spinlock_init(&inode->i_mapping->pm_tree_lock);
-       inode->i_mapping->pm_flags = 0;
+       pm_init(inode->i_mapping, inode->i_pm.pm_op, inode);
        return inode;
 }
 
@@ -1705,59 +1701,6 @@ void file_release(struct kref *kref)
        kmem_cache_free(file_kcache, file);
 }
 
-/* Page cache functions */
-
-/* Looks up the index'th page in the page map, returning an incref'd reference,
- * or 0 if it was not in the map. */
-struct page *pm_find_page(struct page_map *pm, unsigned long index)
-{
-       spin_lock(&pm->pm_tree_lock);
-       struct page *page = (struct page*)radix_lookup(&pm->pm_tree, index);
-       if (page)
-               page_incref(page);
-       spin_unlock(&pm->pm_tree_lock);
-       return page;
-}
-
-/* Attempts to insert the page into the page_map, returns 0 for success, or an
- * error code if there was one already (EEXIST) or we ran out of memory
- * (ENOMEM).  On success, this will preemptively lock the page, and will also
- * store a reference to the page in the pm. */
-int pm_insert_page(struct page_map *pm, unsigned long index, struct page *page)
-{
-       int error = 0;
-       spin_lock(&pm->pm_tree_lock);
-       error = radix_insert(&pm->pm_tree, index, page);
-       if (!error) {
-               page_incref(page);
-               page->pg_flags |= PG_LOCKED | PG_BUFFER;
-               page->pg_mapping = pm;
-               page->pg_index = index;
-               pm->pm_num_pages++;
-       }
-       spin_unlock(&pm->pm_tree_lock);
-       return error;
-}
-
-/* Removes the page, including its reference.  Not sure yet what interface we
- * want to this (pm and index or page), and this has never been used.  There are
- * also issues with when you want to call this, since a page in the cache may be
- * mmap'd by someone else. */
-int pm_remove_page(struct page_map *pm, struct page *page)
-{
-       void *retval;
-       warn("pm_remove_page() hasn't been thought through or tested.");
-       /* TODO: check for dirty pages, don't let them be removed right away.  Need
-        * to schedule them for writeback, and then remove them later (callback). */
-       spin_lock(&pm->pm_tree_lock);
-       retval = radix_delete(&pm->pm_tree, page->pg_index);
-       spin_unlock(&pm->pm_tree_lock);
-       assert(retval == (void*)page);
-       page_decref(page);
-       pm->pm_num_pages--;
-       return 0;
-}
-
 /* Makes sure the index'th page from file is loaded in the page cache and
  * returns its location via **pp.  Note this will give you a refcnt'd reference.
  * This may block! TODO: (BLK) */