fast file mapping for loop
Subsystems (10)
RFC PATCH 1/1 fast file mapping for loop
Hi,
loop.c currently uses the page cache interface to do IO to file backed devices. This works reasonably well for simple things, like mapping an iso9660 file for direct mount and other read-only workloads. Writing is somewhat problematic, as anyone who has really used this feature can attest to - it tends to confuse the vm (hello kswapd) since it break dirty accounting and behaves very erratically on writeout. Did I mention that it's pretty slow as well, for both reads and writes?
It also behaves differently than a real drive. For writes, completions are done once they hit page cache. Since loop queues bio's async and hands them off to a thread, you can have a huge backlog of stuff to do. It's hard to attempt to guarentee data safety for file systems on top of loop without making it even slower than it currently is.
Back when loop was only used for iso9660 mounting and other simple things, this didn't matter. Now it's often used in xen (and others) setups where we do care about performance AND writing. So the below is a attempt at speeding up loop and making it behave like a real device. It's a somewhat quick hack and is still missing one piece to be complete, but I'll throw it out there for people to play with and comment on.
So how does it work? Instead of punting IO to a thread and passing it through the page cache, we instead attempt to send the IO directly to the filesystem block that it maps to. loop maintains a prio tree of known extents in the file (populated lazily on demand, as needed). Advantages of this approach:
- It's fast, loop will basically work at device speed.
- It's fast, loop it doesn't put a huge amount of system load on the system when busy. When I did comparison tests on my notebook with an external drive, running a simple tiobench on the current in-kernel loop with a sparse file backing rendered the notebook basically unusable while the test was ongoing. The remapper version had no more impact than it did when used directly on the external drive.
- It behaves like a real block device.
- It's easy to support IO barriers, which is needed to ensure safety especially in virtualized setups.
Disadvantages:
- The file block mappings must not change while loop is using the file. This means that we have to ensure exclusive access to the file and this is the bit that is currently missing in the implementation. It would be nice if we could just do this via open(), ideas welcome...
- It'll tie down a bit of memory for the prio tree. This is GREATLY offset by the reduced page cache foot print though.
- It cannot be used with the loop encryption stuff. dm-crypt should be used instead, on top of loop (which, I think, is even the recommended way to do this today, so not a big deal).
This patch will automatically enable the new operation mode (called fastfs). I added an ioctl (LOOP_SET_FASTFS) that should be implemented in losetup, then we can remove this hunk in the code:
+ /*
+ * This needs to be done after setup with another ioctl, + * not automatically like this. + */
+ loop_init_fastfs(lo);
+
from loop_set_fd().
Patch is against 2.6.23-rc7 ('ish, as of this morning), will probably apply easily to 2.6.22 as well.
drivers/block/loop.c +667 -6
@@ -481,16 +481,51 @@ static int do_bio_filebacked(struct loop_device *lo, struct bio *bio)return ret; } +#define __lo_throttle (wq, lock, condition) \ +do { \ + DEFINE_WAIT(__wait); \ + for (;;) { \ + prepare_to_wait((wq), &__wait, TASK_UNINTERRUPTIBLE); \ + if (condition) \ + break; \ + spin_unlock_irq((lock)); \ + io_schedule(); \ + spin_lock_irq((lock)); \ + } \ + finish_wait((wq), &__wait); \ +} while (0) \ + +#define lo_act_bio (bio) ((bio)->bi_bdev) +#define LO_BIO_THROTTLE 128 +/* -* Add bio to back of pending list +* A normal block device will throttle on request allocation. Do the same +* for loop to prevent millions of bio's queued internally. +*/ +static void loop_bio_throttle (struct loop_device *lo,struct bio *bio) +{ +if (lo_act_bio (bio)) +__lo_throttle (&lo->lo_bio_wait, &lo->lo_lock, + lo->lo_bio_cnt <LO_BIO_THROTTLE ); +} + +/* +* Add bio to back of pending list and wakeup thread */ static void loop_add_bio (struct loop_device *lo,struct bio *bio) { +loop_bio_throttle (lo, bio); +if (lo->lo_biotail) { lo->lo_biotail->bi_next = bio; lo->lo_biotail = bio; }else lo->lo_bio = lo->lo_biotail = bio; + +if (lo_act_bio (bio)) + lo->lo_bio_cnt++; + +wake_up (&lo->lo_event); }/*
@@ -505,11 +540,230 @@ static struct bio *loop_get_bio(struct loop_device *lo) lo->lo_biotail =NULL ; lo->lo_bio = bio->bi_next; bio->bi_next =NULL ; + +if (lo_act_bio (bio)) + lo->lo_bio_cnt--; +if (lo->lo_bio_cnt <LO_BIO_THROTTLE || !lo->lo_bio) +wake_up (&lo->lo_bio_wait); }return bio; } +struct loop_file_extent { +struct prio_tree_node prio_node; +sector_t disk_block; +sector_t file_block; +unsigned int nr_blocks; +}; + +#define node_to_lfe (n) prio_tree_entry((n), struct loop_file_extent, prio_node) + +static void loop_prio_remove_node (struct loop_device *lo, +struct loop_file_extent *lfe) +{ +if (lo->last_lookup == &lfe->prio_node) + lo->last_lookup =NULL ; +if (lo->last_insert == &lfe->prio_node) + lo->last_insert =NULL ; + +prio_tree_remove (&lo->prio_root, &lfe->prio_node); +} + +/* +* Drop and free all stored extents +*/ +static void loop_drop_extents (struct loop_device *lo) +{ +struct prio_tree_node *node; +struct prio_tree_iter iter; +unsigned int nr_extents =0 ; + +/* +* We may need a few iterations to prune all entries, since we are +* removing nodes while iterating the tree. +*/ +do { +struct loop_file_extent *lfe; + +prio_tree_iter_init (&iter, &lo->prio_root,0 , (pgoff_t) -1 ); + +while ((node =prio_tree_next (&iter)) !=NULL ) { + lfe =node_to_lfe (node); +loop_prio_remove_node (lo, lfe); +kfree (lfe); + nr_extents++; + } + }while (!prio_tree_empty (&lo->prio_root)); + +printk (KERN_INFO "loop%d: dropped %u extents\n" , lo->lo_number, nr_extents); +} + +/* +* Most lookups are for the same extent a number of times, before switching +* to a new one. This happens for bio page adds, for instance. So cache +* the last lookup to prevent doing a full prio_tree lookup if we are within +* the range of the current 'last_lookup' +*/ +static inline int loop_check_last_node (struct loop_device *lo,sector_t block) +{ +struct prio_tree_node *n = lo->last_lookup; + +if (n) +return (block >= n->start) && (block <= n->last); + +return 0 ; +} + +/* +* Find extent mapping this lo device block to the file block on the real +* device +*/ +static struct loop_file_extent *loop_lookup_extent (struct loop_device *lo, +sector_t block) +{ +if (!loop_check_last_node (lo, block)) { +struct prio_tree_iter iter; + +prio_tree_iter_init (&iter, &lo->prio_root, block, block); + lo->last_lookup =prio_tree_next (&iter); + } + +if (lo->last_lookup) +return node_to_lfe (lo->last_lookup); + +return NULL ; +} + +static void loop_handle_extent_hole (struct loop_device *lo,struct bio *bio) +{ +/* +* for a read, just zero the data and end the io +*/ +if (bio_data_dir (bio) ==READ ) { +struct bio_vec *bvec; +unsigned long flags; +int i; + +bio_for_each_segment (bvec, bio, i) { +char *dst =bvec_kmap_irq (bvec, &flags); + +memset (dst,0 , bvec->bv_len); +bvec_kunmap_irq (dst, &flags); + } +bio_endio (bio,0 ); + }else { +/* +* let the page cache handling path do this bio, and then +* lookup the mapped blocks after the io has been issued to +* instantiate extents. +*/ +loop_add_bio (lo, bio); + } +} + +/* +* Alloc a hint bio to tell the loop thread to read file blocks for a given +* range +*/ +#define LOOP_EXTENT_RW_MAGIC 0x19283746 +static void loop_schedule_extent_mapping (struct loop_device *lo, +sector_t disk_block, +unsigned int nr_blocks,int wait) +{ +struct bio *bio, stackbio; + +might_sleep_if (wait); + +/* +* it's ok if we occasionally fail. if called with blocking set, +* then use an on-stack bio since that must not fail. +*/ +if (wait) { + bio = &stackbio; +bio_init (bio); + }else + bio =bio_alloc (GFP_ATOMIC ,0 ); + +if (bio) { +DECLARE_COMPLETION_ONSTACK (comp); + + bio->bi_rw =LOOP_EXTENT_RW_MAGIC ; + bio->bi_sector = disk_block; + bio->bi_size = nr_blocks; + +loop_add_bio (lo, bio); + +if (wait) { +/* +* ok to set here, loop_add_bio() doesn't drop lock +* for this bio (!lo_act_bio(bio)) +*/ + bio->bi_private = ∁ + +/* +* never called with wait != 0 where it's not +* allowed to use spin_unlock_irq() which +* unconditionally enables interrupts. +*/ +spin_unlock_irq (&lo->lo_lock); +wait_for_completion (&comp); +spin_lock_irq (&lo->lo_lock); + } + } +} + +/* +* Change mapping of the bio, so that it points to the real bdev and offset +*/ +static int loop_redirect_bio (struct loop_device *lo,struct bio *bio) +{ +struct loop_file_extent *lfe; +sector_t disk_block; +sector_t extent_off; + + disk_block = bio->bi_sector >> (lo->blkbits -9 ); + +/* +* if no extent exists yet, map this extent first and populate a few +* ahead +*/ + lfe =loop_lookup_extent (lo, disk_block); +if (!lfe) { +unsigned int nr_blocks = bio->bi_size >> (lo->blkbits -9 ); + +loop_schedule_extent_mapping (lo, disk_block, nr_blocks,1 ); + + lfe =loop_lookup_extent (lo, disk_block); +BUG_ON (!lfe); + } + +/* +* handle sparse io +*/ +if (!lfe->file_block) { +loop_handle_extent_hole (lo, bio); +return 0 ; + } + +/* +* not a hole, redirect +*/ + extent_off = bio->bi_sector - (lfe->disk_block << (lo->blkbits -9 )); + bio->bi_bdev = lo->fs_bdev; + bio->bi_sector = lfe->file_block + extent_off; +return 1 ; +} + +/* +* Wait on bio's on our list to complete before sending a barrier bio +* to the below device. Called with lo_lock held. +*/ +static void loop_wait_on_bios (struct loop_device *lo) +{ +__lo_throttle (&lo->lo_bio_wait, &lo->lo_lock, !lo->lo_bio); +} +static int loop_make_request (struct request_queue *q,struct bio *old_bio) {struct loop_device *lo = q->queuedata;
@@ -525,15 +779,33 @@ static int loop_make_request(struct request_queue *q, struct bio *old_bio) goto out;if (unlikely (rw ==WRITE && (lo->lo_flags &LO_FLAGS_READ_ONLY ))) goto out; +if (lo->lo_flags &LO_FLAGS_FASTFS ) { +/* +* If we get a barrier bio, then we just need to wait for +* existing bio's to be complete. This can only happen +* on the 'new' extent mapped loop, since that is the only +* one that supports barriers. +*/ +if (bio_barrier (old_bio)) +loop_wait_on_bios (lo); + +if (loop_redirect_bio (lo, old_bio)) + goto out_redir; + + goto out_end; + }loop_add_bio (lo, old_bio); -wake_up (&lo->lo_event);spin_unlock_irq (&lo->lo_lock);return 0 ; out: -spin_unlock_irq (&lo->lo_lock);bio_io_error (old_bio); +out_end: +spin_unlock_irq (&lo->lo_lock);return 0 ; +out_redir: +spin_unlock_irq (&lo->lo_lock); +return 1 ; }/*
@@ -547,20 +819,62 @@ static void loop_unplug(struct request_queue *q)blk_run_address_space (lo->lo_backing_file->f_mapping); } +static void loop_unplug_fastfs (struct request_queue *q) +{ +struct loop_device *lo = q->queuedata; +struct request_queue *rq =bdev_get_queue (lo->fs_bdev); + +clear_bit (QUEUE_FLAG_PLUGGED , &q->queue_flags); + +if (rq->unplug_fn) + rq->unplug_fn (rq); +} +struct switch_request {struct file *file;struct completion wait; };static void do_loop_switch (struct loop_device *,struct switch_request *); +static void loop_hole_filled (struct loop_device *,struct bio *); +static int loop_init_fastfs (struct loop_device *); +static int loop_read_bmap (struct loop_device *,struct inode *,sector_t , +sector_t ,unsigned int );static inline void loop_handle_bio (struct loop_device *lo,struct bio *bio) { -if (unlikely (!bio->bi_bdev)) { +if (!bio->bi_bdev && bio->bi_rw ==LOOP_EXTENT_RW_MAGIC ) { +struct inode *inode = lo->lo_backing_file->f_mapping->host; + +loop_read_bmap (lo, inode, bio->bi_sector, (sector_t) -1 ,8 ); + +/* +* completion bio is stack allocated (since it's sync) +*/ +if (bio->bi_private) +complete (bio->bi_private); +else +bio_put (bio); + }else if (!bio->bi_bdev) {do_loop_switch (lo, bio->bi_private);bio_put (bio); }else { -int ret =do_bio_filebacked (lo, bio); +int ret; + + ret =do_bio_filebacked (lo, bio); + +/* +* must be filling a hole. kick off writeback of the pages +* so that we know they have a disk mapping. lookup the new +* disk blocks and update our prio tree, splitting the extent +* covering the old hole and adding new extents for the new +* blocks. +*/ +if (lo->lo_flags &LO_FLAGS_FASTFS ) { +filemap_fdatawrite (lo->lo_backing_file->f_mapping); +loop_hole_filled (lo, bio); + } +bio_endio (bio, ret); } }
@@ -610,7 +924,7 @@ static int loop_thread(void *data)static int loop_switch (struct loop_device *lo,struct file *file) {struct switch_request w; -struct bio *bio =bio_alloc (GFP_KERNEL ,1 ); +struct bio *bio =bio_alloc (GFP_KERNEL ,0 );if (!bio)return -ENOMEM ;init_completion (&w.wait);
@@ -637,6 +951,21 @@ static void do_loop_switch(struct loop_device *lo, struct switch_request *p) mapping->host->i_bdev->bd_block_size : PAGE_SIZE; lo->old_gfp_mask =mapping_gfp_mask (mapping);mapping_set_gfp_mask (mapping, lo->old_gfp_mask & ~(__GFP_IO|__GFP_FS)); + +if (lo->lo_flags &LO_FLAGS_FASTFS ) { +unsigned long flags; + +/* +* teardown existing extent mappings and init for a new file +*/ + lo->lo_flags &= ~LO_FLAGS_FASTFS ; + +spin_lock_irqsave (&lo->lo_lock, flags); +loop_drop_extents (lo); +loop_init_fastfs (lo); +spin_unlock_irqrestore (&lo->lo_lock, flags); + } +complete (&p->wait); }
@@ -700,6 +1029,321 @@ static int loop_change_fd(struct loop_device *lo, struct file *lo_file,return error; } +static struct loop_file_extent * +loop_merge_last_node (struct loop_device *lo,sector_t disk_block, +sector_t file_block,unsigned int nr_blocks) +{ +unsigned int file_blocks, lfe_file_blocks; +struct loop_file_extent *lfe; +struct prio_tree_node *node; + + node = lo->last_insert; +if (unlikely (!node)) +return NULL ; + + lfe =node_to_lfe (node); + file_blocks = nr_blocks << (lo->blkbits -9 ); + lfe_file_blocks = lfe->nr_blocks << (lo->blkbits -9 ); + +/* +* See if we can add to front or back of this node +*/ +if ((lfe->disk_block + lfe->nr_blocks == disk_block) && + (lfe->file_block + lfe_file_blocks == file_block)) { +/* +* back merge +*/ +return lfe; + }else if ((disk_block + nr_blocks == lfe->disk_block) && + (file_block + file_blocks == lfe->file_block)) { +/* +* front merge +*/ + lfe->disk_block = disk_block; +return lfe; + } + +return NULL ; +} + +static int __loop_add_extent (struct loop_device *lo, +struct loop_file_extent *lfe) +{ +struct prio_tree_node *ret; + +INIT_PRIO_TREE_NODE (&lfe->prio_node); + lfe->prio_node.start = lfe->disk_block; + lfe->prio_node.last = lfe->disk_block + lfe->nr_blocks -1 ; + + ret =prio_tree_insert (&lo->prio_root, &lfe->prio_node); +if (ret == &lfe->prio_node) { +if (lfe->file_block) + lo->last_insert = &lfe->prio_node; +return 0 ; + } + +return -EEXIST ; +} + +/* +* Add an extent starting at 'disk_block' loop block and 'file_block' +* fs block, spanning 'nr_blocks'. file_block may be 0, in which case +* this extent describes a hole in the file. +*/ +static int loop_add_extent (struct loop_device *lo,sector_t disk_block, +sector_t file_block,unsigned int nr_blocks) +{ +struct loop_file_extent *lfe; +int ret; + + file_block = file_block << (lo->blkbits -9 ); +spin_lock_irq (&lo->lo_lock); + +/* +* See if we can merge with the last inserted node +*/ + lfe =loop_merge_last_node (lo, disk_block, file_block, nr_blocks); +if (lfe) { + lfe->nr_blocks += nr_blocks; +loop_prio_remove_node (lo, lfe); + }else { +/* +* no merge, allocate and add a new node +*/ + lfe =kzalloc (sizeof (*lfe),GFP_ATOMIC ); +if (!lfe) { + ret = -ENOMEM ; + goto out; + } + lfe->disk_block = disk_block; + lfe->file_block = file_block; + lfe->nr_blocks = nr_blocks; + } + + ret =__loop_add_extent (lo, lfe); +if (ret) { +/* +* Most likely we raced with someone else in setting up this +* extent, so just drop this one +*/ +kfree (lfe); + } + +out: +spin_unlock_irq (&lo->lo_lock); +return ret; +} + +/* +* See if adding this bvec would cause us to spill into a new extent. If so, +* disallow the add to start a new bio. This ensures that the bio we receive +* in loop_make_request() never spans two extents or more. +*/ +static int loop_merge_bvec (struct request_queue *q,struct bio *bio, +struct bio_vec *bvec) +{ +struct loop_device *lo = q->queuedata; +struct loop_file_extent *lfe; +unsigned int nr_blocks, ret; +unsigned long flags; +sector_t disk_block; + + disk_block = bio->bi_sector >> (lo->blkbits -9 ); + nr_blocks = (bio->bi_size + bvec->bv_len) >> lo->blkbits; + ret = bvec->bv_len; + +spin_lock_irqsave (&lo->lo_lock, flags); + lfe =loop_lookup_extent (lo, disk_block); +if (lfe) { +/* +* have extent, disallow if outside that extent +*/ +if (disk_block + nr_blocks > lfe->disk_block + lfe->nr_blocks) + ret =0 ; + }else { +/* +* no extent yet, disallow if we already added to the bio and +* schedule read of blocks for this extent +*/ +loop_schedule_extent_mapping (lo, disk_block, nr_blocks,0 ); +if (bio->bi_size) + ret =0 ; + } + +spin_unlock_irqrestore (&lo->lo_lock, flags); +return ret; +} + +/* +* Read and populate the prio tree starting from 'block' disk block +* and to 'end', unless we hit 'max_ext' first. +*/ +static int loop_read_bmap (struct loop_device *lo,struct inode *inode, +sector_t block,sector_t end,unsigned int max_ext) +{ +sector_t expected_block, disk_start, file_start; +unsigned int extent_blocks, nr_extents; + +if (end == (sector_t) -1 ) { +struct inode *inode = lo->lo_backing_file->f_mapping->host; + + end =i_size_read (inode) >> inode->i_blkbits; + } + + nr_extents =0 ; + expected_block = block +1 ; + file_start = disk_start =-1 ; + extent_blocks =0 ; + +while (block <= end && nr_extents < max_ext) { +sector_t file_block =bmap (inode, block); + +if (disk_start ==-1 ) { +start_extent: + disk_start = block; + file_start = file_block; + extent_blocks =1 ; + }else if (expected_block == file_block) + extent_blocks++; +else { +if (loop_add_extent (lo, disk_start, file_start, extent_blocks)) { + disk_start =-1 ; +break ; + } + nr_extents++; + goto start_extent; + } + +if (file_block) + expected_block = file_block +1 ; +else + expected_block =0 ; + + block++; + } + +if (disk_start !=-1 ) { +if (!loop_add_extent (lo, disk_start, file_start, extent_blocks)) + nr_extents++; + } + +return nr_extents; +} + +/* +* Initialize the members pertaining to extent mapping. We will populate +* the tree lazily on demand, as a full scan of a big file can take some +* time. +*/ +static int loop_init_fastfs (struct loop_device *lo) +{ +struct file *file = lo->lo_backing_file; +struct inode *inode = file->f_mapping->host; +struct request_queue *fs_q; + +if (!S_ISREG (inode->i_mode)) +return -EINVAL ; + +/* +* Need a working bmap. TODO: use the same optimization that +* direct-io.c does for get_block() mapping more than one block +* at the time. +*/ +if (inode->i_mapping->a_ops->bmap ==NULL ) +return -EINVAL ; + +INIT_PRIO_TREE_ROOT (&lo->prio_root); + lo->blkbits = inode->i_blkbits; + lo->fs_bdev = file->f_mapping->host->i_sb->s_bdev; + lo->lo_flags |=LO_FLAGS_FASTFS ; + lo->lo_queue->unplug_fn = loop_unplug_fastfs; + +blk_queue_merge_bvec (lo->lo_queue, loop_merge_bvec); +blk_queue_ordered (lo->lo_queue,QUEUE_ORDERED_DRAIN ,NULL ); + + fs_q =bdev_get_queue (lo->fs_bdev); +blk_queue_stack_limits (lo->lo_queue, fs_q); + +printk (KERN_INFO "loop%d: fast redirect\n" , lo->lo_number); +return 0 ; +} + +/* +* We filled the hole at the location specified by bio. Lookup the extent +* covering this hole, and either split it into two or shorten it depending +* on what part the bio covers. After that we need to lookup the new blocks +* and add extents for those. +*/ +static void loop_hole_filled (struct loop_device *lo,struct bio *bio) +{ +struct inode *inode = lo->lo_backing_file->f_mapping->host; +struct loop_file_extent *lfe, *lfe_e; +unsigned int nr_blocks; +sector_t disk_block; + + disk_block = bio->bi_sector >> (lo->blkbits -9 ); + lfe_e =NULL ; + +spin_lock_irq (&lo->lo_lock); + lfe =loop_lookup_extent (lo, disk_block); +BUG_ON (!lfe); + +/* +* Remove extent from tree and trim or split it +*/ +loop_prio_remove_node (lo, lfe); +spin_unlock_irq (&lo->lo_lock); + + nr_blocks = bio->bi_size >> lo->blkbits; + +/* +* Either we need to trim at the front, at the end, or split +* it into two if the write is in the middle +*/ +if (disk_block == lfe->disk_block) { +/* +* Trim front +*/ + lfe->disk_block += nr_blocks; + lfe->nr_blocks -= nr_blocks; + }else if (disk_block + nr_blocks == lfe->disk_block + lfe->nr_blocks) { +/* +* Trim end +*/ + lfe->nr_blocks -= nr_blocks; + }else { +unsigned int total_blocks = lfe->nr_blocks; + +/* +* Split extent in two +*/ + lfe->nr_blocks = disk_block - lfe->disk_block; + + lfe_e =kzalloc (sizeof (*lfe_e),GFP_NOIO | __GFP_NOFAIL); + lfe_e->disk_block = disk_block + nr_blocks; + lfe_e->nr_blocks = total_blocks - nr_blocks - lfe->nr_blocks; + } + +if (!lfe->nr_blocks) { +kfree (lfe); + lfe =NULL ; + } + +spin_lock_irq (&lo->lo_lock); + +/* +* Re-add hole extent(s) +*/ +if (lfe) +__loop_add_extent (lo, lfe); +if (lfe_e) +__loop_add_extent (lo, lfe_e); + +spin_unlock_irq (&lo->lo_lock); + +loop_read_bmap (lo, inode, disk_block, disk_block + nr_blocks -1 ,-1 ); +} +static inline int is_loop_device (struct file *file) {struct inode *i = file->f_mapping->host;
@@ -748,6 +1392,7 @@ static int loop_set_fd(struct loop_device *lo, struct file *lo_file, mapping = file->f_mapping; inode = mapping->host; + lo->lo_flags =0 ;if (!(file->f_mode &FMODE_WRITE )) lo_flags |=LO_FLAGS_READ_ONLY ;
@@ -811,6 +1456,12 @@ static int loop_set_fd(struct loop_device *lo, struct file *lo_file,set_blocksize (bdev, lo_blocksize); +/* +* This needs to be done after setup with another ioctl, +* not automatically like this. +*/ +loop_init_fastfs (lo); + lo->lo_thread =kthread_create (loop_thread, lo,"loop%d" , lo->lo_number);if (IS_ERR (lo->lo_thread)) {
@@ -896,6 +1547,9 @@ static int loop_clr_fd(struct loop_device *lo, struct block_device *bdev)kthread_stop (lo->lo_thread); +if (lo->lo_flags &LO_FLAGS_FASTFS ) +loop_drop_extents (lo); + lo->lo_backing_file =NULL ;loop_release_xfer (lo);
@@ -943,6 +1597,9 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info)if (info->lo_encrypt_type) {unsigned int type = info->lo_encrypt_type; +if (lo->lo_flags &LO_FLAGS_FASTFS ) +return -EINVAL ; +if (type >=MAX_LO_CRYPT )return -EINVAL ; xfer = xfer_funcs[type];
@@ -1153,6 +1810,9 @@ static int lo_ioctl(struct inode * inode, struct file * file,case LOOP_GET_STATUS64 : err =loop_get_status64 (lo, (struct loop_info64 __user *) arg);break ; +case LOOP_SET_FASTFS : + err =loop_init_fastfs (lo); +break ;default : err = lo->ioctl ? lo->ioctl (lo, cmd, arg) : -EINVAL ; }
@@ -1412,6 +2072,7 @@ static struct loop_device *loop_alloc(int i) lo->lo_number = i; lo->lo_thread =NULL ;init_waitqueue_head (&lo->lo_event); +init_waitqueue_head (&lo->lo_bio_wait);spin_lock_init (&lo->lo_lock); disk->major =LOOP_MAJOR ; disk->first_minor = i;
include/linux/loop.h +11 -0
@@ -18,6 +18,7 @@#include <linux/blkdev.h> #include <linux/spinlock.h> #include <linux/mutex.h> +#include <linux/prio_tree.h> /* Possible states of device */ enum {
@@ -50,6 +51,7 @@ struct loop_device {struct file * lo_backing_file;struct block_device *lo_device; +struct block_device *fs_bdev;unsigned lo_blocksize;void *key_data;
@@ -58,14 +60,21 @@ struct loop_device {spinlock_t lo_lock;struct bio *lo_bio;struct bio *lo_biotail; +unsigned int lo_bio_cnt;int lo_state;struct mutex lo_ctl_mutex;struct task_struct *lo_thread;wait_queue_head_t lo_event; +wait_queue_head_t lo_bio_wait;struct request_queue *lo_queue;struct gendisk *lo_disk;struct list_head lo_list; + +struct prio_tree_root prio_root; +struct prio_tree_node *last_insert; +struct prio_tree_node *last_lookup; + unsigned int blkbits; };#endif /* __KERNEL__ */
@@ -76,6 +85,7 @@ struct loop_device {enum {LO_FLAGS_READ_ONLY =1 ,LO_FLAGS_USE_AOPS =2 , +LO_FLAGS_FASTFS =4 , };#include <asm/posix_types.h> /* for __kernel_old_dev_t */
@@ -159,5 +169,6 @@ int loop_unregister_transfer(int number);#define LOOP_SET_STATUS64 0x4C04#define LOOP_GET_STATUS64 0x4C05#define LOOP_CHANGE_FD 0x4C06 +#define LOOP_SET_FASTFS 0x4C07#endif
lib/prio_tree.c +4 -0
@@ -14,6 +14,7 @@#include <linux/init.h> #include <linux/mm.h> #include <linux/prio_tree.h> +#include <linux/module.h> /* * A clever mix of heap and radix trees forms a radix priority search tree (PST)
@@ -255,6 +256,7 @@ struct prio_tree_node *prio_tree_insert(struct prio_tree_root *root,BUG ();return NULL ; } +EXPORT_SYMBOL (prio_tree_insert);/* * Remove a prio_tree_node @node from a radix priority search tree @root. The
@@ -304,6 +306,7 @@ void prio_tree_remove(struct prio_tree_root *root, struct prio_tree_node *node)while (cur != node) cur =prio_tree_replace (root, cur->parent, cur); } +EXPORT_SYMBOL (prio_tree_remove);/* * Following functions help to enumerate all prio_tree_nodes in the tree that
@@ -482,3 +485,4 @@ repeat: goto repeat; } +EXPORT_SYMBOL (prio_tree_next);
Signature · 1 line
Jens Axboe
Quoted 4 lines · Jens Axboe
- The file block mappings must not change while loop is using the file. This means that we have to ensure exclusive access to the file and this is the bit that is currently missing in the implementation. It would be nice if we could just do this via
open(), ideas welcome...
And the way this is done is simply broken. It means you have to get rid of things like delayed or unwritten hands beforehand, it'll be a complete pain for COW or non-block backed filesystems.
The right way to do this is to allow direct I/O from kernel sources where the filesystem is in-charge of submitting the actual I/O after the pages are handed to it. I think Peter Zijlstra has been looking into something like that for swap over nfs.
Quoted 9 lines · Christoph Hellwig
Quoted 4 lines · Jens Axboe
- The file block mappings must not change while loop is using the file. This means that we have to ensure exclusive access to the file and this is the bit that is currently missing in the implementation. It would be nice if we could just do this via
open(), ideas welcome...And the way this is done is simply broken. It means you have to get rid of things like delayed or unwritten hands beforehand, it'll be a complete pain for COW or non-block backed filesystems.
COW is not that hard to handle, you just need to be notified of moving blocks. If you view the patch as just a tighter integration between loop and fs, I don't think it's necessarily that broken.
I did consider these cases, and it can be done with the existing approach.
Quoted 4 lines
The right way to do this is to allow direct I/O from kernel sources where the filesystem is in-charge of submitting the actual I/O after the pages are handed to it. I think Peter Zijlstra has been looking into something like that for swap over nfs.
That does sound like a nice approach, but a lot more work. It'll behave differently too, the advantage of what I proposed is that it behaves like a real device.
I'm not asking you to love it (in fact I knew some people would complain about this approach and I understand why), just tossing it out there to get things rolling. If we end up doing it differently I don't really care, I'm not married to any solution but merely wish to solve a problem. If that ends up being solved differently, the outcome is the same to me.
Signature · 1 line
Jens Axboe
Quoted 8 lines · On Wed, 9 Jan 2008 10:43:21 +0100 Jens Axboe <[email protected]> wrote:
Quoted 7 lines · Christoph Hellwig
Quoted 5 lines · Jens Axboe
- The file block mappings must not change while loop is using the file. This means that we have to ensure exclusive access to the file and this is the bit that is currently missing in the implementation. It would be nice if we could just do this via
open(), ideas welcome...
Quoted 8 lines
Quoted 3 lines
And the way this is done is simply broken. It means you have to get rid of things like delayed or unwritten hands beforehand, it'll be a complete pain for COW or non-block backed filesystems.
COW is not that hard to handle, you just need to be notified of moving blocks. If you view the patch as just a tighter integration between loop and fs, I don't think it's necessarily that broken.
Filling holes (delayed allocation) and COW are definitely a problem. But at least for the loop use case, most non-cow filesystems will want to preallocate the space for loop file and be done with it. Sparse loop definitely has uses, but generally those users are willing to pay a little performance.
Jens' patch falls back to buffered writes for the hole case and pretends cow doesn't exist. It's a good starting point that I hope to extend with something like the extent_map apis.
Quoted 11 lines
I did consider these cases, and it can be done with the existing approach.
Quoted 4 lines
The right way to do this is to allow direct I/O from kernel sources where the filesystem is in-charge of submitting the actual I/O after the pages are handed to it. I think Peter Zijlstra has been looking into something like that for swap over nfs.
That does sound like a nice approach, but a lot more work. It'll behave differently too, the advantage of what I proposed is that it behaves like a real device.
The problem with O_DIRECT (or even O_SYNC) loop is that every write into loop becomes synchronous, and it really changes the performance of things like filemap_fdatawrite.
If we just hand ownership of the file over to loop entirely and prevent other openers (perhaps even forcing backups through the loop device), we get fewer corner cases and much better performance.
-chris
Quoted 3 lines · Jens Axboe
So how does it work? Instead of punting IO to a thread and passing it through the page cache, we instead attempt to send the IO directly to the
Great -- something like this was needed for a long time.
Quoted 4 lines
- The file block mappings must not change while loop is using the file. This means that we have to ensure exclusive access to the file and this is the bit that is currently missing in the implementation. It would be nice if we could just do this via
open(), ideas welcome...
get_write_access()/put_write_access() will block other writers.
But as pointed out by others that is not enough for this.
I suppose you could use a white list like a special flag for file systems (like ext2/ext3) that do not reallocate blocks.
-Andi
Alasdair G Kergon
maintainerHere's the latest version of dm-loop, for comparison.
To try it out,
ln -s dmsetup dmlosetup
and supply similar basic parameters to losetup.
(using dmsetup version 1.02.11 or higher)
Alasdair
From: Bryn Reeves <[email protected]>
This implements a loopback target for device mapper allowing a regular file to be treated as a block device.
--- drivers/md/Kconfig | 9 drivers/md/Makefile | 1 drivers/md/dm-loop.c | 1036 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1046 insertions(+)
drivers/md/Kconfig +9 -0
@@ -229,6 +229,15 @@ config DM_CRYPT If unsure, say N. +config DM_LOOP + tristate "Loop target (EXPERIMENTAL)" + depends on BLK_DEV_DM && EXPERIMENTAL + ---help--- + This device-mapper target allows you to treat a regular file as + a block device. + + If unsure, say N. + config DM_SNAPSHOT tristate "Snapshot target (EXPERIMENTAL)" depends on BLK_DEV_DM && EXPERIMENTAL
drivers/md/Makefile +1 -0
@@ -34,6 +34,7 @@ obj-$(CONFIG_BLK_DEV_MD) += md-mod.o obj-$(CONFIG_BLK_DEV_DM) += dm-mod.o obj-$(CONFIG_DM_CRYPT) += dm-crypt.o obj-$(CONFIG_DM_DELAY) += dm-delay.o +obj-$(CONFIG_DM_LOOP) += dm-loop.o obj-$(CONFIG_DM_MULTIPATH) += dm-multipath.o dm-round-robin.o obj-$(CONFIG_DM_MULTIPATH_EMC) += dm-emc.o obj-$(CONFIG_DM_MULTIPATH_HP) += dm-hp-sw.o
drivers/md/dm-loop.c new file +1036 -0
1,036 lines not on this page · raw message
Quoted 3 lines · Jens Axboe
So how does it work? Instead of punting IO to a thread and passing it through the page cache, we instead attempt to send the IO directly to the filesystem block that it maps to.
You told Christoph that just using direct-IO from kernel still doesn't give you the required behaviour... What about queueing the IO directly *and* using direct-IO? I guess it still has to go through the underlying filesystem, but that's probably a good thing.
Quoted 2 lines
loop maintains a prio tree of known
extents in the file (populated lazily on demand, as needed).
Just a quick question (I haven't looked closely at the code): how come you are using a prio tree for extents? I don't think they could be overlapping?
Quoted 6 lines · Alasdair G Kergon
Here's the latest version of dm-loop, for comparison.
To try it out,
ln -s dmsetup dmlosetup
and supply similar basic parameters to losetup.
(using dmsetup version 1.02.11 or higher)
Why oh why does dm always insist to reinvent everything? That's bad enough in itself, but on top of that most of the extra stuff ends up being essentially unmaintained.
If we instead improve loop, everyone wins.
Sorry to sound a bit harsh, but sometimes it doesn't hurt to think a bit outside your own sandbox.
Signature · 1 line
Jens Axboe
Quoted 10 lines · Nick Piggin
Quoted 3 lines · Jens Axboe
So how does it work? Instead of punting IO to a thread and passing it through the page cache, we instead attempt to send the IO directly to the filesystem block that it maps to.
You told Christoph that just using direct-IO from kernel still doesn't give you the required behaviour... What about queueing the IO directly *and* using direct-IO? I guess it still has to go through the underlying filesystem, but that's probably a good thing.
If it was O_DIRECT and aio, then we would be close.
Quoted 6 lines
Quoted 2 lines
loop maintains a prio tree of known
extents in the file (populated lazily on demand, as needed).Just a quick question (I haven't looked closely at the code): how come you are using a prio tree for extents? I don't think they could be overlapping?
Because I'm really lazy - the core of this was basically first written as a quick hack and then I always go shopping for reusable data structures. prio trees fit the bill nicely, they described extents and allowed loopup with a key anywhere in that extent. You are right in that I don't need the overlap handling at all, and Chris already tried to talk me into reusing his btrfs extent code :-)
So I may just do the latter, turning it into a lib/extent-map.c in the longer run. My first priority was just having something that worked so I could test it. At the end of the day, not a single soul would ever notice if the prio tree ended up being slightly slower than a custom solution.
Signature · 1 line
Jens Axboe
Quoted 8 lines · Nick Piggin
Quoted 3 lines
So how does it work? Instead of punting IO to a thread and passing it through the page cache, we instead attempt to send the IO directly to the filesystem block that it maps to.
You told Christoph that just using direct-IO from kernel still doesn't give you the required behaviour... What about queueing the IO directly *and* using direct-IO? I guess it still has to go through the underlying filesystem, but that's probably a good thing.
We defintively need to go through the filesystem for I/O submission, and also for I/O completion. Thinking of the async submission might be what Peter actually implemented for his network swapping patches as you really wouldn't want to write it out synchronously.
Peter, any chance you could chime in here?
Quoted 7 lines
Quoted 3 lines
loop maintains a prio tree of known
extents in the file (populated lazily on demand, as needed).Just a quick question (I haven't looked closely at the code): how come you are using a prio tree for extents? I don't think they could be overlapping?
IMHO this shouldn't be done in the loop driver anyway. Filesystems have their own effricient extent lookup trees (well, at least xfs and btrfs do), and we should leverage that instead of reinventing it.
Quoted 8 lines · Jens Axboe
Quoted 6 lines · Alasdair G Kergon
Here's the latest version of dm-loop, for comparison.
To try it out,
ln -s dmsetup dmlosetup
and supply similar basic parameters to losetup.
(using dmsetup version 1.02.11 or higher)
Quoted 8 lines
Why oh why does dm always insist to reinvent everything? That's bad enough in itself, but on top of that most of the extra stuff ends up being essentially unmaintained.
If we instead improve loop, everyone wins.
Sorry to sound a bit harsh, but sometimes it doesn't hurt to think a bit outside your own sandbox.
So I looked at the code - it seems you build a full extent of the blocks in the file, filling holes as you go along. I initally did that as well, but that is to slow to be usable in real life.
You also don't support sparse files, falling back to normal fs read/write paths. Supporting sparse files properly is a must, people generally don't want to prealloc a huge disk backing.
Signature · 1 line
Jens Axboe
Quoted 7 lines · Andi Kleen
Quoted 3 lines · Jens Axboe
So how does it work? Instead of punting IO to a thread and passing it through the page cache, we instead attempt to send the IO directly to the
Great -- something like this was needed for a long time.
Quoted 8 lines
Quoted 4 lines
- The file block mappings must not change while loop is using the file. This means that we have to ensure exclusive access to the file and this is the bit that is currently missing in the implementation. It would be nice if we could just do this via
open(), ideas welcome...get_write_access()/put_write_access() will block other writers.
But as pointed out by others that is not enough for this.
Yeah, basically allowing O_RDONLY | O_DIRECT opens should be ok, but we can't allow writes and we can't allow page cache to exist for this file outside of loop.
Quoted 2 lines
I suppose you could use a white list like a special flag for file systems (like ext2/ext3) that do not reallocate blocks.
Irk, but yeah we probably need something like that for now until Chris proposes his API addition.
Signature · 1 line
Jens Axboe
Quoted 10 lines · Christoph Hellwig
Quoted 6 lines
Quoted 2 lines
loop maintains a prio tree of known
extents in the file (populated lazily on demand, as needed).Just a quick question (I haven't looked closely at the code): how come you are using a prio tree for extents? I don't think they could be overlapping?
IMHO this shouldn't be done in the loop driver anyway. Filesystems have their own effricient extent lookup trees (well, at least xfs and btrfs do), and we should leverage that instead of reinventing it.
Completely agree, it's just needed right now for this solution since all we have is a crappy bmap() interface to get at those mappings.
Signature · 1 line
Jens Axboe
Quoted 6 lines · Jens Axboe
Quoted 3 lines
IMHO this shouldn't be done in the loop driver anyway. Filesystems have their own effricient extent lookup trees (well, at least xfs and btrfs do), and we should leverage that instead of reinventing it.
Completely agree, it's just needed right now for this solution since all we have is a crappy
bmap()interface to get at those mappings.
So let's fix the interface instead of piling crap ontop of it. As I said I think Peter has something to start with so let's beat on it
until we have something suitable. If we aren't done by end of Feb
I'm happy to host a hackfest to get it sorted around the fs/storage summit..
Quoted 11 lines · Christoph Hellwig
Quoted 6 lines · Jens Axboe
Quoted 3 lines
IMHO this shouldn't be done in the loop driver anyway. Filesystems have their own effricient extent lookup trees (well, at least xfs and btrfs do), and we should leverage that instead of reinventing it.
Completely agree, it's just needed right now for this solution since all we have is a crappy
bmap()interface to get at those mappings.So let's fix the interface instead of piling crap ontop of it. As I said I think Peter has something to start with so let's beat on it until we have something suitable.
Sure, I'm all for doing it the Right Way. I wasn't aware of anything Peter was doing in this area, so lets please see it.
It's not like opportunities to improve this haven't been around. My plan was/is to convert to using the get_block() tricks of O_DIRECT, one could easily argue that the work should have been done then. And perhaps direct-io.c wouldn't be such a steaming pile of crap if it had been done, and loop would already be fine since we could have tapped into that.
Quoted 2 lines
If we aren't done by end of Feb I'm happy to host a hackfest to get it sorted around the fs/storage summit..
Count me in :)
Signature · 1 line
Jens Axboe
Quoted 1 line · Christoph Hellwig
Peter, any chance you could chime in here?
I have this patch to add swap_out/_in methods. I expect we can loosen the requirement for swapcache pages and change the name a little.
previously posted here:
http://lkml.org/lkml/2007/5/4/143
---
Subject: mm: add support for non block device backed swap files
New addres_space_operations methods are added:
int swapfile(struct address_space *, int);
int swap_out(struct file *, struct page *, struct writeback_control *); int swap_in(struct file *, struct page *);
When during sys_swapon() the swapfile() method is found and returns no error the swapper_space.a_ops will proxy to sis->swap_file->f_mapping->a_ops, and make use of swap_{out,in}() to write/read swapcache pages.
The swapfile method will be used to communicate to the address_space that the VM relies on it, and the address_space should take adequate measures (like reserving memory for mempools or the like).
This new interface can be used to obviate the need for ->bmap in the swapfile code. A filesystem would need to load (and maybe even allocate) the full block map for a file into memory and pin it there on ->swapfile(,1) so that ->swap_{out,in}() have instant access to it. It can be released on ->swapfile(,0).
The reason to provide ->swap_{out,in}() over using {write,read}page() is to
1) make a distinction between swapcache and pagecache pages, and
2) to provide a struct file * for credential context (normally not needed in the context of writepage, as the page content is normally dirtied using either of the following interfaces:
write_{begin,end}()
{prepare,commit}_write()
page_mkwrite()
which do have the file context.
--- Documentation/filesystems/Locking | 19 ++++++++++++ Documentation/filesystems/vfs.txt | 17 +++++++++++ include/linux/buffer_head.h | 2 - include/linux/fs.h | 8 +++++ include/linux/swap.h | 3 + mm/Kconfig | 3 + mm/page_io.c | 58 ++++++++++++++++++++++++++++++++++++++ mm/swap_state.c | 5 +++ mm/swapfile.c | 22 +++++++++++++- 9 files changed, 135 insertions(+), 2 deletions(-)
include/linux/swap.h +3 -0
@@ -164,6 +164,7 @@ enum {SWP_USED = (1 <<0 ),/* is slot in swap_info[] used? */ SWP_WRITEOK = (1 <<1 ),/* ok to write to this swap? */ SWP_ACTIVE = (SWP_USED |SWP_WRITEOK ), +SWP_FILE = (1 <<2 ),/* file swap area */ /* add others here before... */ SWP_SCANNING = (1 << 8), /* refcount in scan_swap_map */ };
@@ -261,6 +262,8 @@ extern void swap_unplug_io_fn(struct bac/* linux/mm/page_io.c */ extern int swap_readpage (struct file *,struct page *);extern int swap_writepage (struct page *page,struct writeback_control *wbc); +extern void swap_sync_page (struct page *page); +extern int swap_set_page_dirty (struct page *page);extern void end_swap_bio_read (struct bio *bio,int err);/* linux/mm/swap_state.c */
mm/page_io.c +58 -0
82 lines not on this page · raw message
mm/swap_state.c +5 -0
@@ -27,8 +27,13 @@*/ static const struct address_space_operations swap_aops = { .writepage = swap_writepage, +#ifdef CONFIG_SWAP_FILE + .sync_page = swap_sync_page, + .set_page_dirty = swap_set_page_dirty, +#else .sync_page = block_sync_page, .set_page_dirty = __set_page_dirty_nobuffers, +#endif .migratepage = migrate_page, };
mm/swapfile.c +21 -1
@@ -1015,6 +1015,13 @@ static void destroy_swap_extents(structlist_del (&se->list);kfree (se); } +#ifdef CONFIG_SWAP_FILE +if (sis->flags &SWP_FILE ) { + sis->flags &= ~SWP_FILE ; + sis->swap_file->f_mapping->a_ops-> +swapfile (sis->swap_file->f_mapping,0 ); + } +#endif }/*
@@ -1107,6 +1114,19 @@ static int setup_swap_extents(struct swa goto done; } +#ifdef CONFIG_SWAP_FILE +if (sis->swap_file->f_mapping->a_ops->swapfile) { + ret = sis->swap_file->f_mapping->a_ops-> +swapfile (sis->swap_file->f_mapping,1 ); +if (!ret) { + sis->flags |=SWP_FILE ; + ret =add_swap_extent (sis,0 , sis->max,0 ); + *span = sis->pages; + } + goto done; + } +#endif + blkbits = inode->i_blkbits; blocks_per_page =PAGE_SIZE >> blkbits;
@@ -1671,7 +1691,7 @@ asmlinkage long sys_swapon(const char __mutex_lock (&swapon_mutex);spin_lock (&swap_lock); - p->flags =SWP_ACTIVE ; + p->flags |=SWP_WRITEOK ; nr_swap_pages += nr_good_pages; total_swap_pages += nr_good_pages;
include/linux/fs.h +8 -0
@@ -479,6 +479,14 @@ struct address_space_operations {int (*migratepage) (struct address_space *,struct page *,struct page *);int (*launder_page) (struct page *); + +/* +* swapfile support +*/ +int (*swapfile)(struct address_space *,int ); +int (*swap_out)(struct file *file,struct page *page, +struct writeback_control *wbc); +int (*swap_in)(struct file *file,struct page *page); };/*
Documentation/filesystems/Locking +19 -0
@@ -171,6 +171,9 @@ prototypes: int (*direct_IO)(int, struct kiocb *, const struct iovec *iov, loff_t offset, unsigned long nr_segs); int (*launder_page) (struct page *); + int (*swapfile) (struct address_space *, int); + int (*swap_out) (struct file *, struct page *, struct writeback_control *); + int (*swap_in) (struct file *, struct page *); locking rules: All except set_page_dirty may block
@@ -192,6 +195,9 @@ invalidatepage: no yes releasepage: no yes direct_IO: no launder_page: no yes +swapfile no +swap_out no yes, unlocks +swap_in no yes, unlocks ->prepare_write(), ->commit_write(), ->sync_page() and ->readpage() may be called from the request handler (/dev/loop).
@@ -291,6 +297,19 @@ cleaned, or an error value if not. Note getting mapped back in and redirtied, it needs to be kept locked across the entire operation. + ->swapfile() will be called with a non zero argument on address spaces +backing non block device backed swapfiles. A return value of zero indicates +success. In which case this address space can be used for backing swapspace. +The swapspace operations will be proxied to the address space operations. +Swapoff will call this method with a zero argument to release the address +space. + + ->swap_out() when swapfile() returned success, this method is used to +write the swap page. + + ->swap_in() when swapfile() returned success, this method is used to +read the swap page. + Note: currently almost all instances of address_space methods are using BKL for internal serialization and that's one of the worst sources of contention. Normally they are calling library functions (in fs/buffer.c)
mm/Kconfig +3 -0
@@ -185,6 +185,9 @@ config BOUNCE def_bool y depends on BLOCK && MMU && (ZONE_DMA || HIGHMEM) +config SWAP_FILE + def_bool n + config NR_QUICK int depends on QUICKLIST
include/linux/buffer_head.h +1 -1
@@ -335,7 +335,7 @@ static inline void invalidate_inode_buffstatic inline int remove_inode_buffers (struct inode *inode) {return 1 ; }static inline int sync_mapping_buffers (struct address_space *mapping) {return 0 ; }static inline void invalidate_bdev (struct block_device *bdev) {} - +static inline void block_sync_page (struct page *) { }#endif /* CONFIG_BLOCK */ #endif /* _LINUX_BUFFER_HEAD_H */
Documentation/filesystems/vfs.txt +17 -0
@@ -542,6 +542,10 @@ struct address_space_operations { /* migrate the contents of a page to the specified target */ int (*migratepage) (struct page *, struct page *); int (*launder_page) (struct page *); + int (*swapfile)(struct address_space *, int); + int (*swap_out)(struct file *file, struct page *page, + struct writeback_control *wbc); + int (*swap_in)(struct file *file, struct page *page); }; writepage: called by the VM to write a dirty page to backing store.
@@ -727,6 +731,19 @@ struct address_space_operations { prevent redirtying the page, it is kept locked during the whole operation. + swapfile: Called with a non-zero argument when swapon is used on a file. A + return value of zero indicates success. In which case this + address_space can be used to back swapspace. The swapspace operations + will be proxied to this address space's ->swap_{out,in} methods. + Swapoff will call this method with a zero argument to release the + address space. + + swap_out: Called to write a swapcache page to a backing store, similar to + writepage. + + swap_in: Called to read a swapcache page from a backing store, similar to + readpage. + The File Object ===============
Quoted 27 lines · Peter Zijlstra
Quoted 1 line · Christoph Hellwig
Peter, any chance you could chime in here?
I have this patch to add swap_out/_in methods. I expect we can loosen the requirement for swapcache pages and change the name a little.
previously posted here:
http://lkml.org/lkml/2007/5/4/143---
Subject: mm: add support for non block device backed swap filesNew
addres_space_operationsmethods are added:
intswapfile(struct address_space *, int);
intswap_out(struct file *, struct page *, struct writeback_control *); intswap_in(struct file *, struct page *);When during
sys_swapon()theswapfile()method is found and returns no error the swapper_space.a_ops will proxy tosis->swap_file->f_mapping->a_ops, and make use of swap_{out,in}() to write/read swapcache pages.The swapfile method will be used to communicate to the
address_spacethat the VM relies on it, and theaddress_spaceshould take adequate measures (like reserving memory for mempools or the like).
Quoted 5 lines
This new interface can be used to obviate the need for ->bmap in the swapfile code. A filesystem would need to load (and maybe even allocate) the full block map for a file into memory and pin it there on ->swapfile(,1) so that ->swap_{out,in}() have instant access to it. It can be released on ->swapfile(,0).
So this is where I don't think that's good enough, you cannot require a full block/extent mapping of a file on setup. It can take quite some time, a little testing I did here easily took 5 seconds for only a couple of gigabytes. And that wasn't even worst case for that size. It also wastes memory by populating extents that we may never read or write.
If you look at the loop addition I did, it populates lazily as needed with some very simple logic to populate-ahead. In practice that performs as well as a pre-populated map, the first IO to a given range will just be a little slower since we have to bmap() it.
Do you have plans to improve this area?
Signature · 1 line
Jens Axboe
Quoted 41 lines · Jens Axboe
Quoted 27 lines · Peter Zijlstra
Quoted 1 line · Christoph Hellwig
Peter, any chance you could chime in here?
I have this patch to add swap_out/_in methods. I expect we can loosen the requirement for swapcache pages and change the name a little.
previously posted here:
http://lkml.org/lkml/2007/5/4/143---
Subject: mm: add support for non block device backed swap filesNew
addres_space_operationsmethods are added:
intswapfile(struct address_space *, int);
intswap_out(struct file *, struct page *, struct writeback_control *); intswap_in(struct file *, struct page *);When during
sys_swapon()theswapfile()method is found and returns no error the swapper_space.a_ops will proxy tosis->swap_file->f_mapping->a_ops, and make use of swap_{out,in}() to write/read swapcache pages.The swapfile method will be used to communicate to the
address_spacethat the VM relies on it, and theaddress_spaceshould take adequate measures (like reserving memory for mempools or the like).Quoted 5 lines
This new interface can be used to obviate the need for ->bmap in the swapfile code. A filesystem would need to load (and maybe even allocate) the full block map for a file into memory and pin it there on ->swapfile(,1) so that ->swap_{out,in}() have instant access to it. It can be released on ->swapfile(,0).
So this is where I don't think that's good enough, you cannot require a full block/extent mapping of a file on setup. It can take quite some time, a little testing I did here easily took 5 seconds for only a couple of gigabytes. And that wasn't even worst case for that size. It also wastes memory by populating extents that we may never read or write.
Quoted 6 lines
If you look at the loop addition I did, it populates lazily as needed with some very simple logic to populate-ahead. In practice that performs as well as a pre-populated map, the first IO to a given range will just be a little slower since we have to
bmap()it.Do you have plans to improve this area?
Nope, for swap it _must_ be there, there is just no way we can do block allocation on swapout.
That said, the swapfile() interface can be used to pre-populate the extend/block mapping, and when using swap_(in/out) without it, it can be done lazily.
Quoted 48 lines · Peter Zijlstra
Quoted 46 lines · Jens Axboe
Quoted 27 lines · Peter Zijlstra
Quoted 1 line · Christoph Hellwig
Peter, any chance you could chime in here?
I have this patch to add swap_out/_in methods. I expect we can loosen the requirement for swapcache pages and change the name a little.
previously posted here:
http://lkml.org/lkml/2007/5/4/143---
Subject: mm: add support for non block device backed swap filesNew
addres_space_operationsmethods are added:
intswapfile(struct address_space *, int);
intswap_out(struct file *, struct page *, struct writeback_control *); intswap_in(struct file *, struct page *);When during
sys_swapon()theswapfile()method is found and returns no error the swapper_space.a_ops will proxy tosis->swap_file->f_mapping->a_ops, and make use of swap_{out,in}() to write/read swapcache pages.The swapfile method will be used to communicate to the
address_spacethat the VM relies on it, and theaddress_spaceshould take adequate measures (like reserving memory for mempools or the like).Quoted 5 lines
This new interface can be used to obviate the need for ->bmap in the swapfile code. A filesystem would need to load (and maybe even allocate) the full block map for a file into memory and pin it there on ->swapfile(,1) so that ->swap_{out,in}() have instant access to it. It can be released on ->swapfile(,0).
So this is where I don't think that's good enough, you cannot require a full block/extent mapping of a file on setup. It can take quite some time, a little testing I did here easily took 5 seconds for only a couple of gigabytes. And that wasn't even worst case for that size. It also wastes memory by populating extents that we may never read or write.
If you look at the loop addition I did, it populates lazily as needed with some very simple logic to populate-ahead. In practice that performs as well as a pre-populated map, the first IO to a given range will just be a little slower since we have to
bmap()it.
Quoted 4 lines
Quoted 1 line
Do you have plans to improve this area?
Nope, for swap it _must_ be there, there is just no way we can do block allocation on swapout.
I appreciate that fact, just saying that we could have more flexibility for other uses.
Quoted 3 lines
That said, the
swapfile()interface can be used to pre-populate the extend/block mapping, and when usingswap_(in/out)without it, it can be done lazily.
I think the interface is too simple, I would much rather have a way to dig into the file mappings and be allowed to request population and so on. Without that, it can't be used for eg loop.
Signature · 1 line
Jens Axboe
Quoted 57 lines · Jens Axboe
Quoted 48 lines · Peter Zijlstra
Quoted 46 lines · Jens Axboe
Quoted 27 lines · Peter Zijlstra
Quoted 1 line · Christoph Hellwig
Peter, any chance you could chime in here?
I have this patch to add swap_out/_in methods. I expect we can loosen the requirement for swapcache pages and change the name a little.
previously posted here:
http://lkml.org/lkml/2007/5/4/143---
Subject: mm: add support for non block device backed swap filesNew
addres_space_operationsmethods are added:
intswapfile(struct address_space *, int);
intswap_out(struct file *, struct page *, struct writeback_control *); intswap_in(struct file *, struct page *);When during
sys_swapon()theswapfile()method is found and returns no error the swapper_space.a_ops will proxy tosis->swap_file->f_mapping->a_ops, and make use of swap_{out,in}() to write/read swapcache pages.The swapfile method will be used to communicate to the
address_spacethat the VM relies on it, and theaddress_spaceshould take adequate measures (like reserving memory for mempools or the like).Quoted 5 lines
This new interface can be used to obviate the need for ->bmap in the swapfile code. A filesystem would need to load (and maybe even allocate) the full block map for a file into memory and pin it there on ->swapfile(,1) so that ->swap_{out,in}() have instant access to it. It can be released on ->swapfile(,0).
So this is where I don't think that's good enough, you cannot require a full block/extent mapping of a file on setup. It can take quite some time, a little testing I did here easily took 5 seconds for only a couple of gigabytes. And that wasn't even worst case for that size. It also wastes memory by populating extents that we may never read or write.
If you look at the loop addition I did, it populates lazily as needed with some very simple logic to populate-ahead. In practice that performs as well as a pre-populated map, the first IO to a given range will just be a little slower since we have to
bmap()it.Quoted 4 lines
Quoted 1 line
Do you have plans to improve this area?
Nope, for swap it _must_ be there, there is just no way we can do block allocation on swapout.
I appreciate that fact, just saying that we could have more flexibility for other uses.
Quoted 7 lines
Quoted 3 lines
That said, the
swapfile()interface can be used to pre-populate the extend/block mapping, and when usingswap_(in/out)without it, it can be done lazily.I think the interface is too simple, I would much rather have a way to dig into the file mappings and be allowed to request population and so on. Without that, it can't be used for eg loop.
I'm open to suggestions :-), if you can propose an interface we both can use I'm all ears.
Although poking at the extends sounds like bmap and we were just trying to get rid of that.
Perhaps something like badvise(), where you can suggest loading/dropping extend information on a voluntary basis.
Quoted 11 lines · On Thu, 10 Jan 2008 09:31:31 +0100 Jens Axboe <[email protected]> wrote:
Quoted 6 lines · Alasdair G Kergon
Here's the latest version of dm-loop, for comparison.
To try it out,
ln -s dmsetup dmlosetup
and supply similar basic parameters to losetup.
(using dmsetup version 1.02.11 or higher)Why oh why does dm always insist to reinvent everything? That's bad enough in itself, but on top of that most of the extra stuff ends up being essentially unmaintained.
I don't quite get how the dm version is reinventing things. They use the dmsetup command that they use for everything else and provide a small and fairly clean module for bio specific loop instead of piling it onto loop.c....
Their code doesn't have the fancy hole handling that yours does, but neither did yours 4 days ago ;)
Quoted 6 lines
If we instead improve loop, everyone wins.
Sorry to sound a bit harsh, but sometimes it doesn't hurt to think a bit outside your own sandbox.
It is a natural fit in either place, as both loop and dm have a good infrastructure for it. I'm not picky about where it ends up, but dm wouldn't be a bad place.
-chris
Quoted 10 lines · On Thu, 10 Jan 2008 08:54:59 +0000 Christoph Hellwig <[email protected]> wrote:
Quoted 8 lines · Jens Axboe
Quoted 4 lines
IMHO this shouldn't be done in the loop driver anyway. Filesystems have their own effricient extent lookup trees (well, at least xfs and btrfs do), and we should leverage that instead of reinventing it.
Completely agree, it's just needed right now for this solution since all we have is a crappy
bmap()interface to get at those mappings.
Quoted 6 lines
So let's fix the interface instead of piling crap ontop of it. As I said I think Peter has something to start with so let's beat on it
until we have something suitable. If we aren't done by end of Feb
I'm happy to host a hackfest to get it sorted around the fs/storage summit..
Ok, I've been meaning to break my extent_map code up, and this is a very good reason. I'll work up a sample today based on Jens' code.
The basic goals:
* Loop (swap) calls into the FS for each mapping. Any caching happens on the FS side.
* The FS returns an extent, filling any holes
Swap would need to use an extra call early on for preallocation.
Step two is having a call back into the FS allow the FS to delay the bios until commit completion so that COW and delalloc blocks can be fully on disk when the bios are reported as done. Jens, can you add some way to queue the bio completions up?
-chris
Quoted 11 lines · Chris Mason
Quoted 8 lines · On Thu, 10 Jan 2008 09:31:31 +0100 Jens Axboe <[email protected]> wrote:
Quoted 6 lines · Alasdair G Kergon
Here's the latest version of dm-loop, for comparison.
To try it out,
ln -s dmsetup dmlosetup
and supply similar basic parameters to losetup.
(using dmsetup version 1.02.11 or higher)
Quoted 5 lines
Quoted 3 lines
Why oh why does dm always insist to reinvent everything? That's bad enough in itself, but on top of that most of the extra stuff ends up being essentially unmaintained.
I don't quite get how the dm version is reinventing things. They use
Things like raid, now file mapping functionality. I'm sure there are more examples, it's how dm was always developed probably originating back to when they developed mostly out of tree. And I think it's a bad idea, we don't want duplicate functionality. If something is wrong with loop, fix it, don't write dm-loop.
Quoted 3 lines
the dmsetup command that they use for everything else and provide a small and fairly clean module for bio specific loop instead of piling it onto
loop.c....
If loop.c is a problem, I'd rather see a newloop.c (with a better name, of course) that we can transition to.
Quoted 2 lines
Their code doesn't have the fancy hole handling that yours does, but neither did yours 4 days ago ;)
Well mine didn't exist 4 days ago, I was just listing missing functionality.
Quoted 11 lines
Quoted 7 lines
If we instead improve loop, everyone wins.
Sorry to sound a bit harsh, but sometimes it doesn't hurt to think a bit outside your own sandbox.
It is a natural fit in either place, as both loop and dm have a good infrastructure for it. I'm not picky about where it ends up, but dm wouldn't be a bad place.
I know that's your opinion, I reserve the right to have my own on where this functionality belongs :)
Signature · 1 line
Jens Axboe
Quoted 20 lines · Chris Mason
Quoted 10 lines · On Thu, 10 Jan 2008 08:54:59 +0000 Christoph Hellwig <[email protected]> wrote:
Quoted 8 lines · Jens Axboe
Quoted 4 lines
IMHO this shouldn't be done in the loop driver anyway. Filesystems have their own effricient extent lookup trees (well, at least xfs and btrfs do), and we should leverage that instead of reinventing it.
Completely agree, it's just needed right now for this solution since all we have is a crappy
bmap()interface to get at those mappings.Quoted 6 lines
So let's fix the interface instead of piling crap ontop of it. As I said I think Peter has something to start with so let's beat on it
until we have something suitable. If we aren't done by end of Feb
I'm happy to host a hackfest to get it sorted around the fs/storage summit..
Quoted 2 lines
Ok, I've been meaning to break my
extent_mapcode up, and this is a very good reason. I'll work up a sample today based on Jens' code.
Great!
Quoted 5 lines
The basic goals:
* Loop (swap) calls into the FS for each mapping. Any caching happens on the FS side.
* The FS returns an extent, filling any holes
We don't want to fill holes for a read, but I guess that's a given?
Quoted 6 lines
Swap would need to use an extra call early on for preallocation.
Step two is having a call back into the FS allow the FS to delay the bios until commit completion so that COW and delalloc blocks can be fully on disk when the bios are reported as done. Jens, can you add some way to queue the bio completions up?
Sure, a function to save a completed bio and a function to execute completions on those already stored?
Signature · 1 line
Jens Axboe
Quoted 21 lines · On Thu, 10 Jan 2008 14:03:24 +0100 Jens Axboe <[email protected]> wrote:
Quoted 20 lines · Chris Mason
Quoted 16 lines · On Thu, 10 Jan 2008 08:54:59 +0000 Christoph Hellwig <[email protected]> wrote:
Quoted 8 lines · Jens Axboe
Quoted 4 lines
IMHO this shouldn't be done in the loop driver anyway. Filesystems have their own effricient extent lookup trees (well, at least xfs and btrfs do), and we should leverage that instead of reinventing it.
Completely agree, it's just needed right now for this solution since all we have is a crappy
bmap()interface to get at those mappings.So let's fix the interface instead of piling crap ontop of it. As I said I think Peter has something to start with so let's beat
on it until we have something suitable. If we aren't done by
end of Feb I'm happy to host a hackfest to get it sorted around the fs/storage summit..
Quoted 4 lines
Quoted 2 lines
Ok, I've been meaning to break my
extent_mapcode up, and this is a very good reason. I'll work up a sample today based on Jens' code.Great!
Grin, we'll see how the sample looks.
Quoted 8 lines
Quoted 6 lines
The basic goals:
* Loop (swap) calls into the FS for each mapping. Any caching happens on the FS side.
* The FS returns an extent, filling any holesWe don't want to fill holes for a read, but I guess that's a given?
Right.
Quoted 11 lines
Quoted 7 lines
Swap would need to use an extra call early on for preallocation.
Step two is having a call back into the FS allow the FS to delay the bios until commit completion so that COW and delalloc blocks can be fully on disk when the bios are reported as done. Jens, can you add some way to queue the bio completions up?
Sure, a function to save a completed bio and a function to execute completions on those already stored?
Sounds right, I'm mostly looking for a way to aggregate a few writes to make the commits a little larger.
-chris
Quoted 16 lines · On Thursday January 10, [email protected] wrote:
Quoted 15 lines · Chris Mason
Quoted 11 lines · On Thu, 10 Jan 2008 09:31:31 +0100 Jens Axboe <[email protected]> wrote:
Quoted 6 lines · Alasdair G Kergon
Here's the latest version of dm-loop, for comparison.
To try it out,
ln -s dmsetup dmlosetup
and supply similar basic parameters to losetup.
(using dmsetup version 1.02.11 or higher)Why oh why does dm always insist to reinvent everything? That's bad enough in itself, but on top of that most of the extra stuff ends up being essentially unmaintained.
Quoted 7 lines
Quoted 1 line
I don't quite get how the dm version is reinventing things. They use
Things like raid, now file mapping functionality. I'm sure there are more examples, it's how dm was always developed probably originating back to when they developed mostly out of tree. And I think it's a bad idea, we don't want duplicate functionality. If something is wrong with loop, fix it, don't write dm-loop.
I'm with Jens here.
We currently have two interfaces that interesting block devices can be written for: 'dm' and 'block'.
We really should aim to have just one. I would call it 'block' and move anything really useful from dm into block.
As far as I can tell, the important things that 'dm' has that 'block' doesn't have are:
- a standard ioctl interface for assembling and creating interesting devices.
For 'block', everybody just rolls there own. e.g. md, loop, and nbd all use totally different approaches for setup and tear down etc.
- suspend/reconfigure/resume.
This is something that I would really like to see in 'block'. If I had a filesystem mounted on /dev/sda1 and I wanted to make it a raid1, it would be cool if I could
suspend /dev/sda1
build a raid1 from sda1 and something else
plug tha raid1 in as 'sda1'.
resume sda1
- Integrated 'linear' mapping.
This is the bit of 'dm' that I think of as yucky. If I read the code correctly, every dm device is a linear array of a bunch of targets. Each target can be a stripe-set(raid0) or a multipath or a raid1 or a plain block device or whatever.
Having 'linear' at a different level to everything else seems a bit ugly, but it isn't really a big deal.
I would really like to see every 'dm' target being just a regular 'block' device. Then a 'linear' block device could be used to assemble dm targets into a dm device. Or the targets could be used directly if the 'linear' function wasn't needed.
Each target/device could respond to both dm ioctls and 'adhoc' ioctls. That is a bit ugly, but backwards compatibility always is, but it isn't a big cost.
I think the way forward here is to put the important suspend/reconfig/resume functionality into the block layer, then work on making code work with multiple ioctl interfaces.
I *don't* think the way forward is to duplicate current block devices as dm targets. This is duplication of effort (which I admit isn't always a bad thing) and a maintenance headache (which is).
"Help, I'm having a problem with the loop driver"
"Which one, dm or regular, because if it is the 'dm' one you have to
ask over there ..."
It has already happened occasionally with raid1 and multipath - both of which have pointless multiple implementations.
Quoted 4 lines
Quoted 4 lines
the dmsetup command that they use for everything else and provide a small and fairly clean module for bio specific loop instead of piling it onto
loop.c....
I'm missing something here... "fairly clean module for bio specific loop". Isn't that what 'loop.c' is meant to be?
NeilBrown
Quoted 10 lines · Jens Axboe
Hi,
loop.ccurrently uses the page cache interface to do IO to file backed devices. This works reasonably well for simple things, like mapping an iso9660 file for direct mount and other read-only workloads. Writing is somewhat problematic, as anyone who has really used this feature can attest to - it tends to confuse the vm (hello kswapd) since it break dirty accounting and behaves very erratically on writeout. Did I mention that it's pretty slow as well, for both reads and writes?
Since you are looking for comments, I'll mention a loop-related behavior I've been seeing and see if it gets comments or is useful, since it can be used to tickle bad behavior on demand.
I have an 6GB sparse file, which I mount with cryptoloop and populate as an ext3 filesystem (more later on why). I then copy ~5.8GB of data to the filesystem, which is unmounted to be burnt to a DVD. Before it's burned the "dvdisaster" application is used to add some ECC information to the end, and make an image which fits on a DVD-DL. Media will be burned and distributed to multiple locations.
The problem:
When copying with rsync, the copy runs at ~25MB/s for a while, then falls into a pattern of bursts of 25MB/s followed by 10-15 sec of iowait with no disk activity. So I tried doing the copy by cpio
find . -depth | cpio -pdm /mnt/loop
which shows exactly the same behavior. Then, for no good reason I tried
find . -depth | cpio -pBdm /mnt/loop
and the copy ran at 25MB/s for the whole data set.
I was able to see similar results with a pure loop mount, I only mention the crypto for accuracy. Because many of these have been shipped over the last two years and new loop code would only be useful in this case if it were compatible so old data sets could be read.
Quoted 43 lines
It also behaves differently than a real drive. For writes, completions are done once they hit page cache. Since loop queues bio's async and hands them off to a thread, you can have a huge backlog of stuff to do. It's hard to attempt to guarentee data safety for file systems on top of loop without making it even slower than it currently is.
Back when loop was only used for iso9660 mounting and other simple things, this didn't matter. Now it's often used in xen (and others) setups where we do care about performance AND writing. So the below is a attempt at speeding up loop and making it behave like a real device. It's a somewhat quick hack and is still missing one piece to be complete, but I'll throw it out there for people to play with and comment on.
So how does it work? Instead of punting IO to a thread and passing it through the page cache, we instead attempt to send the IO directly to the filesystem block that it maps to. loop maintains a prio tree of known extents in the file (populated lazily on demand, as needed). Advantages of this approach:
- It's fast, loop will basically work at device speed.
- It's fast, loop it doesn't put a huge amount of system load on the system when busy. When I did comparison tests on my notebook with an external drive, running a simple tiobench on the current in-kernel loop with a sparse file backing rendered the notebook basically unusable while the test was ongoing. The remapper version had no more impact than it did when used directly on the external drive.
- It behaves like a real block device.
- It's easy to support IO barriers, which is needed to ensure safety especially in virtualized setups.Disadvantages:
- The file block mappings must not change while loop is using the file. This means that we have to ensure exclusive access to the file and this is the bit that is currently missing in the implementation. It would be nice if we could just do this via
open(), ideas welcome...
- It'll tie down a bit of memory for the prio tree. This is GREATLY offset by the reduced page cache foot print though.
- It cannot be used with the loop encryption stuff. dm-crypt should be used instead, on top of loop (which, I think, is even the recommended way to do this today, so not a big deal).
Signature · 3 lines
Bill Davidsen <[email protected]> "We have more to fear from the bungling of the incompetent than from the machinations of the wicked." - from Slashdot
Quoted 16 lines · Mikulas Patocka
Quoted 7 lines
So I looked at the code - it seems you build a full extent of the blocks in the file, filling holes as you go along. I initally did that as well, but that is to slow to be usable in real life.
You also don't support sparse files, falling back to normal fs read/write paths. Supporting sparse files properly is a must, people generally don't want to prealloc a huge disk backing.
How would you do sparse file support with passthrough loopback that doesn't use pagecache?
Holes are allocated at
get_blockfunction provided by each filesystem and the function gets a buffer that is supposed to be in the pagecache. Now if you want to allocate holes without pagecache, there's a problem --- new interface to all filesystems is needed.
Quoted 6 lines
It could be possible to use pagecache interface for filling holes and passthrough interface for other requests --- but
get_blockis allowed to move other blocks on the filesystem (and on UFS it really does), so callingget_blockto fill a hole could move other unrelated blocks which would result in desychronized block map and corruption of both filesystems.
Please read the posted patch and the posts from Chris as well, it basically covers everything in your email.
The patch, as posted, doesn't work if the fs moves blocks around.
Signature · 1 line
Jens Axboe
Quoted 7 lines
So I looked at the code - it seems you build a full extent of the blocks in the file, filling holes as you go along. I initally did that as well, but that is to slow to be usable in real life.
You also don't support sparse files, falling back to normal fs read/write paths. Supporting sparse files properly is a must, people generally don't want to prealloc a huge disk backing.
How would you do sparse file support with passthrough loopback that doesn't use pagecache?
Holes are allocated at get_block function provided by each filesystem and the function gets a buffer that is supposed to be in the pagecache. Now if you want to allocate holes without pagecache, there's a problem --- new interface to all filesystems is needed.
It could be possible to use pagecache interface for filling holes and passthrough interface for other requests --- but get_block is allowed to move other blocks on the filesystem (and on UFS it really does), so calling get_block to fill a hole could move other unrelated blocks which would result in desychronized block map and corruption of both filesystems.
Mikulas
Quoted 51 lines · On Fri, 11 Jan 2008 10:01:18 +1100 Neil Brown <[email protected]> wrote:
Quoted 16 lines · On Thursday January 10, [email protected] wrote:
Quoted 15 lines · Chris Mason
Quoted 11 lines · On Thu, 10 Jan 2008 09:31:31 +0100 Jens Axboe <[email protected]> wrote:
Quoted 6 lines · Alasdair G Kergon
Here's the latest version of dm-loop, for comparison.
To try it out,
ln -s dmsetup dmlosetup
and supply similar basic parameters to losetup.
(using dmsetup version 1.02.11 or higher)Why oh why does dm always insist to reinvent everything? That's bad enough in itself, but on top of that most of the extra stuff ends up being essentially unmaintained.
Quoted 8 lines
Quoted 2 lines
I don't quite get how the dm version is reinventing things. They use
Things like raid, now file mapping functionality. I'm sure there are more examples, it's how dm was always developed probably originating back to when they developed mostly out of tree. And I think it's a bad idea, we don't want duplicate functionality. If something is wrong with loop, fix it, don't write dm-loop.
I'm with Jens here.
We currently have two interfaces that interesting block devices can be written for: 'dm' and 'block'.
We really should aim to have just one. I would call it 'block' and move anything really useful from dm into block.As far as I can tell, the important things that 'dm' has that 'block' doesn't have are:
- a standard ioctl interface for assembling and creating interesting devices.
For 'block', everybody just rolls there own. e.g. md, loop, and nbd all use totally different approaches for setup and tear down etc.- suspend/reconfigure/resume.
This is something that I would really like to see in 'block'. If I had a filesystem mounted on/dev/sda1and I wanted to make it a raid1, it would be cool if I could
suspend/dev/sda1
build a raid1 from sda1 and something else
plug tha raid1 in as 'sda1'.
resume sda1
Quoted 8 lines
- Integrated 'linear' mapping.
This is the bit of 'dm' that I think of as yucky. If I read the code correctly, every dm device is a linear array of a bunch of targets. Each target can be a stripe-set(raid0) or a multipath or a raid1 or a plain block device or whatever.
Having 'linear' at a different level to everything else seems a bit ugly, but it isn't really a big deal.
DM is also a framework where you can introduce completely new types of block devices without having to go through the associated pain of finding major numbers. In terms of developing new things with greater flexibility, I think it is easier.
Quoted 9 lines
I would really like to see every 'dm' target being just a regular 'block' device. Then a 'linear' block device could be used to assemble dm targets into a dm device. Or the targets could be used directly if the 'linear' function wasn't needed.
Each target/device could respond to both dm ioctls and 'adhoc' ioctls. That is a bit ugly, but backwards compatibility always is, but it isn't a big cost.
Quoted 8 lines
I think the way forward here is to put the important suspend/reconfig/resume functionality into the block layer, then work on making code work with multiple ioctl interfaces.
I *don't* think the way forward is to duplicate current block devices as dm targets. This is duplication of effort (which I admit isn't always a bad thing) and a maintenance headache (which is).
raid in dm aside (that's an entirely different debate ;), loop is a pile of things which dm can nicely layer out into pieces (dm-crypt vs loopback crypt). Also, dm doesn't have to jump through hoops to get a variable number of minors.
Yes, the loop side was recently improved for # of minors, and it does have enough in there for userland to do variable number of minors, but this is one specific case where dm is just easier.
At any rate, I'm all for ideas that make dm less of the evil stepchild of the block layer ;) I'm not saying everything should be dm, but I did want to point out that dm-loop isn't entirely silly.
I have a version of Jens' patch in testing here that makes a new API with the FS for mapping extents and hope to post it later today.
-chris
Hi Jens,
This looks really useful.
Quoted 7 lines · Jens Axboe
Disadvantages:
- The file block mappings must not change while loop is using the file. This means that we have to ensure exclusive access to the file and this is the bit that is currently missing in the implementation. It would be nice if we could just do this via
open(), ideas welcome...
Get_block methods are pretty fast and you have caching in the level above you, so you might be able to get away with no cache of physical addresses at all, in which case you just need i_mutex and i_alloc_sem at get_block time. This would save a pile of code and still have the main benefit of avoiding double caching.
If you use ->get_block instead of bmap, it will fill in file holes for you, but of course get_block is not exposed, and Al is likely to bark at anyone who exposes it.
Instead of exposing get_block you could expose an aops method like ->bio_transfer that would hide the use of *_get_block in a library routine, just as __blockdev_direct_IO does. Chances are, there are other users besides loop that would be interested in a generic way of performing bio transfers to files.
I presume you would fall back to the existing approach for any filesystem without get_block. You could handle this transparently with a default library method that does read/write.
Regards,
Daniel
Quoted 13 lines · Daniel Phillips
Hi Jens,
This looks really useful.
Quoted 7 lines · Jens Axboe
Disadvantages:
- The file block mappings must not change while loop is using the file. This means that we have to ensure exclusive access to the file and this is the bit that is currently missing in the implementation. It would be nice if we could just do this via
open(), ideas welcome...
Quoted 5 lines
Get_blockmethods are pretty fast and you have caching in the level above you, so you might be able to get away with no cache of physical addresses at all, in which case you just needi_mutexandi_alloc_sematget_blocktime. This would save a pile of code and still have the main benefit of avoiding double caching.
I'm not too fond of the tree either, but it serves an important purpose as well - we need to be careful in calling bmap() as not to deadlock the fs under vm pressure. So the current code punts to a thread for bmap() on extents we don't already have stored in loop. And that slows things down of course, we would have to still punt every IO to loopd instead of just doing a quick remap. But...
Quoted 10 lines
If you use ->get_block instead of bmap, it will fill in file holes for you, but of course
get_blockis not exposed, and Al is likely to bark at anyone who exposes it.Instead of exposing
get_blockyou could expose an aops method like ->bio_transfer that would hide the use of*_get_blockin a library routine, just as__blockdev_direct_IOdoes. Chances are, there are other users besides loop that would be interested in a generic way of performing bio transfers to files.
Quoted 3 lines
I presume you would fall back to the existing approach for any filesystem without
get_block. You could handle this transparently with a default library method that does read/write.
... things are already moving forward, Chris has a new interface for this and tied it in with the loop fastfs mode. I think he'll post it later today.
Signature · 1 line
Jens Axboe
Hello everyone,
Here is a modified version of Jens' patch. The basic idea is to push the mapping maintenance out of loop and down into the filesystem (ext2 in this case).
Two new address_space operations are added, one to map extents and the other to provide call backs into the FS as io is completed.
Still TODO for this patch:
* Add exclusion between filling holes and readers. This is partly implemented, when a hole is filled by the FS, the extent is flagged as having a hole. The idea is to check this flag before trying to read the blocks and just send back all zeros.
The flag would be cleared when the blocks filling the hole have been written.
* Exclude page cache readers and writers
* Add a way for the FS to request a commit before telling the higher layers the IO is complete. This way we can make sure metadata related to holes is on disk before claiming the IO is really done. COW based filesystems will also needed it.
* Change loop to use fast mapping only when the new address_space op is provided (whoops, forgot about this until just now)
* A few other bits for COW, not really relevant until there is...something COW using it.
-chris
drivers/block/loop.c +464 -8
@@ -76,6 +76,7 @@#include <linux/gfp.h> #include <linux/kthread.h> #include <linux/splice.h> +#include <linux/extent_map.h> #include <asm/uaccess.h>
@@ -481,16 +482,51 @@ static int do_bio_filebacked(struct loopreturn ret; } +#define __lo_throttle (wq, lock, condition) \ +do { \ + DEFINE_WAIT(__wait); \ + for (;;) { \ + prepare_to_wait((wq), &__wait, TASK_UNINTERRUPTIBLE); \ + if (condition) \ + break; \ + spin_unlock_irq((lock)); \ + io_schedule(); \ + spin_lock_irq((lock)); \ + } \ + finish_wait((wq), &__wait); \ +} while (0) \ + +#define lo_act_bio (bio) ((bio)->bi_bdev) +#define LO_BIO_THROTTLE 128 +/* -* Add bio to back of pending list +* A normal block device will throttle on request allocation. Do the same +* for loop to prevent millions of bio's queued internally. +*/ +static void loop_bio_throttle (struct loop_device *lo,struct bio *bio) +{ +if (lo_act_bio (bio)) +__lo_throttle (&lo->lo_bio_wait, &lo->lo_lock, + lo->lo_bio_cnt <LO_BIO_THROTTLE ); +} + +/* +* Add bio to back of pending list and wakeup thread */ static void loop_add_bio (struct loop_device *lo,struct bio *bio) { +loop_bio_throttle (lo, bio); +if (lo->lo_biotail) { lo->lo_biotail->bi_next = bio; lo->lo_biotail = bio; }else lo->lo_bio = lo->lo_biotail = bio; + +if (lo_act_bio (bio)) + lo->lo_bio_cnt++; + +wake_up (&lo->lo_event); }/*
@@ -510,6 +546,178 @@ static struct bio *loop_get_bio(struct lreturn bio; } +static void loop_exit_fastfs (struct loop_device *lo) +{ +/* +* drop what page cache we instantiated filling holes +*/ +invalidate_inode_pages2 (lo->lo_backing_file->f_mapping); + +blk_queue_ordered (lo->lo_queue,QUEUE_ORDERED_NONE ,NULL ); +} + +static inline u64 lo_bio_offset (struct loop_device *lo,struct bio *bio) +{ +return (u64 )lo->lo_offset + ((u64 )bio->bi_sector <<9 ); +} + +/* +* Find extent mapping this lo device block to the file block on the real +* device +*/ +static struct extent_map *loop_lookup_extent (struct loop_device *lo, +u64 offset,gfp_t gfp_mask) +{ +struct address_space *mapping; +struct extent_map *em; +u64 len =1 << lo->blkbits; + + mapping = lo->lo_backing_file->f_mapping; + em = mapping->a_ops->map_extent (mapping,NULL ,0 , + offset, len,0 , gfp_mask); +return em; +} + +/* +* Alloc a hint bio to tell the loop thread to read file blocks for a given +* range +*/ +static void loop_schedule_extent_mapping (struct loop_device *lo, +sector_t sector, +unsigned long len,int wait) +{ +struct bio *bio, stackbio; + +/* +* it's ok if we occasionally fail. if called with blocking set, +* then use an on-stack bio since that must not fail. +*/ +if (wait) { + bio = &stackbio; +bio_init (bio); + }else + bio =bio_alloc (GFP_ATOMIC ,0 ); + +if (bio) { +DECLARE_COMPLETION_ONSTACK (comp); + + bio->bi_rw =LOOP_EXTENT_RW_MAGIC ; + bio->bi_sector = sector; + bio->bi_size = len; + +loop_add_bio (lo, bio); + +if (wait) { +/* +* ok to set here, loop_add_bio() doesn't drop lock +* for this bio (!lo_act_bio(bio)) +*/ + bio->bi_private = ∁ + +/* +* never called with wait != 0 where it's not +* allowed to use spin_unlock_irq() which +* unconditionally enables interrupts. +*/ +spin_unlock_irq (&lo->lo_lock); +wait_for_completion (&comp); +spin_lock_irq (&lo->lo_lock); + } + } +} + +static void loop_handle_extent_hole (struct loop_device *lo,struct bio *bio) +{ +/* +* for a read, just zero the data and end the io +*/ +if (bio_data_dir (bio) ==READ ) { +struct bio_vec *bvec; +unsigned long flags; +int i; + +bio_for_each_segment (bvec, bio, i) { +char *dst =bvec_kmap_irq (bvec, &flags); + +memset (dst,0 , bvec->bv_len); +bvec_kunmap_irq (dst, &flags); + } +bio_endio (bio,0 ); + }else { +/* +* let the page cache handling path do this bio, and then +* lookup the mapped blocks after the io has been issued to +* instantiate extents. +*/ +loop_add_bio (lo, bio); + } +} + +static inline int lo_is_switch_bio (struct bio *bio) +{ +return !bio->bi_bdev && bio->bi_rw ==LOOP_SWITCH_RW_MAGIC ; +} + +static inline int lo_is_map_bio (struct bio *bio) +{ +return !bio->bi_bdev && bio->bi_rw ==LOOP_EXTENT_RW_MAGIC ; +} + +/* +* Change mapping of the bio, so that it points to the real bdev and offset +*/ +static int loop_redirect_bio (struct loop_device *lo,struct bio *bio) +{ +struct extent_map *lfe; +u64 extent_off; +u64 disk_block; +u64 start =lo_bio_offset (lo, bio); + + lfe =loop_lookup_extent (lo, start,GFP_ATOMIC ); +if (IS_ERR (lfe)) +return -EIO ; + +while (!lfe) { +loop_schedule_extent_mapping (lo, bio->bi_sector, + bio->bi_size,1 ); + lfe =loop_lookup_extent (lo, start,GFP_ATOMIC ); +if (IS_ERR (lfe)) +return -EIO ; + } +/* +* handle sparse io +*/ +if (lfe->block_start ==EXTENT_MAP_HOLE ) { +loop_handle_extent_hole (lo, bio); +free_extent_map (lfe); +return 0 ; + } + +/* +* not a hole, redirect +*/ + disk_block = lfe->block_start; + extent_off = start - lfe->start; + bio->bi_bdev = lfe->bdev; + bio->bi_sector = (disk_block + extent_off) >>9 ; +free_extent_map (lfe); +return 1 ; +} + +/* +* Wait on bio's on our list to complete before sending a barrier bio +* to the below device. Called with lo_lock held. +*/ +static void loop_wait_on_bios (struct loop_device *lo) +{ +__lo_throttle (&lo->lo_bio_wait, &lo->lo_lock, !lo->lo_bio); +} + +static void loop_wait_on_switch (struct loop_device *lo) +{ +__lo_throttle (&lo->lo_bio_wait, &lo->lo_lock, !lo->lo_switch); +} +static int loop_make_request (struct request_queue *q,struct bio *old_bio) {struct loop_device *lo = q->queuedata;
@@ -525,15 +733,39 @@ static int loop_make_request(struct requ goto out;if (unlikely (rw ==WRITE && (lo->lo_flags &LO_FLAGS_READ_ONLY ))) goto out; +if (lo->lo_flags &LO_FLAGS_FASTFS ) { +/* +* If we get a barrier bio, then we just need to wait for +* existing bio's to be complete. This can only happen +* on the 'new' extent mapped loop, since that is the only +* one that supports barriers. +*/ +if (bio_barrier (old_bio)) +loop_wait_on_bios (lo); + +/* +* if file switch is in progress, wait for it to complete +*/ +if (!lo_is_switch_bio (old_bio) && lo->lo_switch) +loop_wait_on_switch (lo); + +if (loop_redirect_bio (lo, old_bio)) + goto out_redir; + goto out_end; + }loop_add_bio (lo, old_bio); -wake_up (&lo->lo_event);spin_unlock_irq (&lo->lo_lock);return 0 ; out: +bio_io_error (old_bio); +out_end:spin_unlock_irq (&lo->lo_lock); -bio_io_error (old_bio);return 0 ; + +out_redir: +spin_unlock_irq (&lo->lo_lock); +return 1 ; }/*
@@ -547,21 +779,117 @@ static void loop_unplug(struct request_qblk_run_address_space (lo->lo_backing_file->f_mapping); } +static void loop_unplug_fastfs (struct request_queue *q) +{ +struct loop_device *lo = q->queuedata; +struct request_queue *rq =bdev_get_queue (lo->fs_bdev); + +clear_bit (QUEUE_FLAG_PLUGGED , &q->queue_flags); + +if (rq->unplug_fn) + rq->unplug_fn (rq); +} +struct switch_request {struct file *file;struct completion wait; };static void do_loop_switch (struct loop_device *,struct switch_request *); +static int loop_init_fastfs (struct loop_device *); + +static void end_bio_hole_filling (struct bio *bio,int err) +{ +struct bio *orig_bio = bio->bi_private; +struct inode *inode = bio->bi_bdev->bd_inode; +u64 start = orig_bio->bi_sector <<9 ; +u64 len = bio->bi_size; + +if (inode->i_mapping->a_ops->extent_io_complete) { + inode->i_mapping->a_ops->extent_io_complete (inode->i_mapping, + start, len); + } +bio_put (bio); +bio_endio (orig_bio, err); +} + +static int fill_extent_hole (struct loop_device *lo,struct bio *bio) +{ +struct address_space *mapping = lo->lo_backing_file->f_mapping; +struct bio *new_bio; +struct extent_map *em; +u64 len = bio->bi_size; +u64 start =lo_bio_offset (lo, bio); +u64 disk_block; +u64 extent_off; +int err; + + new_bio =bio_clone (bio,GFP_NOIO ); +if (!new_bio) { + err = -ENOMEM ; + goto out; + } +/* +* change the sector so we can find the correct file offset in our +* endio +*/ + bio->bi_sector =lo_bio_offset (lo, bio) >>9 ; + +mutex_lock (&mapping->host->i_mutex); + + em = mapping->a_ops->map_extent (mapping,NULL ,0 , + start, len,1 ,GFP_KERNEL ); +mark_inode_dirty (mapping->host); +mutex_unlock (&mapping->host->i_mutex); + +if (!em ||IS_ERR (em)) { + err = -EIO ; + goto out;; + } + + + disk_block = em->block_start; + extent_off = start - em->start; + new_bio->bi_sector = (disk_block + extent_off) >>9 ; + new_bio->bi_bdev = em->bdev; + new_bio->bi_private = bio; + new_bio->bi_size = bio->bi_size; + new_bio->bi_end_io = end_bio_hole_filling; +free_extent_map (em); + +generic_make_request (new_bio); +return 0 ; + +out: +bio_endio (bio, err); +return 0 ; +}static inline void loop_handle_bio (struct loop_device *lo,struct bio *bio) { -if (unlikely (!bio->bi_bdev)) { +struct extent_map *lfe; + +if (lo_is_map_bio (bio)) { + lfe =loop_lookup_extent (lo,lo_bio_offset (lo, bio), +GFP_KERNEL ); +free_extent_map (lfe); +if (bio->bi_private) +complete (bio->bi_private); +else +bio_put (bio); + }else if (lo_is_switch_bio (bio)) {do_loop_switch (lo, bio->bi_private);bio_put (bio); }else { -int ret =do_bio_filebacked (lo, bio); -bio_endio (bio, ret); +int ret; + +if (lo->lo_flags &LO_FLAGS_FASTFS ) { +/* we only get here when filling holes */ + ret =fill_extent_hole (lo, bio); + }else { + ret =do_bio_filebacked (lo, bio); +bio_endio (bio, ret); + } } }
@@ -581,6 +909,7 @@ static int loop_thread(void *data) {struct loop_device *lo = data;struct bio *bio; +int bio_act;set_user_nice (current,-20 );
@@ -588,7 +917,6 @@ static int loop_thread(void *data)wait_event_interruptible (lo->lo_event, lo->lo_bio ||kthread_should_stop ()); -if (!lo->lo_bio)continue ;spin_lock_irq (&lo->lo_lock);
@@ -596,7 +924,19 @@ static int loop_thread(void *data)spin_unlock_irq (&lo->lo_lock);BUG_ON (!bio); +if (lo_act_bio (bio)) + bio_act =1 ; +else + bio_act =0 ; +loop_handle_bio (lo, bio); + +spin_lock_irq (&lo->lo_lock); +if (bio_act) + lo->lo_bio_cnt--; +if (lo->lo_bio_cnt <LO_BIO_THROTTLE || !lo->lo_bio) +wake_up (&lo->lo_bio_wait); +spin_unlock_irq (&lo->lo_lock); }return 0 ;
@@ -610,13 +950,15 @@ static int loop_switch(struct loop_devicstatic int loop_switch (struct loop_device *lo,struct file *file) {struct switch_request w; -struct bio *bio =bio_alloc (GFP_KERNEL ,1 ); +struct bio *bio =bio_alloc (GFP_KERNEL ,0 );if (!bio)return -ENOMEM ;init_completion (&w.wait); w.file = file; bio->bi_private = &w; bio->bi_bdev =NULL ; + bio->bi_rw =LOOP_SWITCH_RW_MAGIC ; + lo->lo_switch =1 ;loop_make_request (lo->lo_queue, bio);wait_for_completion (&w.wait);return 0 ;
@@ -630,6 +972,10 @@ static void do_loop_switch(struct loop_dstruct file *file = p->file;struct file *old_file = lo->lo_backing_file;struct address_space *mapping = file->f_mapping; +const int fastfs = lo->lo_flags &LO_FLAGS_FASTFS ; + +if (fastfs) +loop_exit_fastfs (lo);mapping_set_gfp_mask (old_file->f_mapping, lo->old_gfp_mask); lo->lo_backing_file = file;
@@ -637,6 +983,13 @@ static void do_loop_switch(struct loop_d mapping->host->i_bdev->bd_block_size : PAGE_SIZE; lo->old_gfp_mask =mapping_gfp_mask (mapping);mapping_set_gfp_mask (mapping, lo->old_gfp_mask & ~(__GFP_IO|__GFP_FS)); + +if (fastfs) +loop_init_fastfs (lo); + + lo->lo_switch =0 ; +wake_up (&lo->lo_bio_wait); +complete (&p->wait); }
@@ -700,6 +1053,85 @@ static int loop_change_fd(struct loop_dereturn error; } +/* +* See if adding this bvec would cause us to spill into a new extent. If so, +* disallow the add to start a new bio. This ensures that the bio we receive +* in loop_make_request() never spans two extents or more. +*/ +static int loop_merge_bvec (struct request_queue *q,struct bio *bio, +struct bio_vec *bvec) +{ +struct loop_device *lo = q->queuedata; +struct extent_map *lfe; +unsigned int ret; +u64 start; +u64 len; + + start =lo_bio_offset (lo, bio); + len = bio->bi_size + bvec->bv_len; + ret = bvec->bv_len; + + lfe =loop_lookup_extent (lo, start,GFP_ATOMIC ); +if (lfe && !IS_ERR (lfe)) { +/* +* have extent, disallow if outside that extent +*/ +if (start + len > lfe->start + lfe->len) + ret =0 ; + +free_extent_map (lfe); + }else { +if (bio->bi_size) + ret =0 ; + } +return ret; +} + +/* +* Initialize the members pertaining to extent mapping. We will populate +* the tree lazily on demand, as a full scan of a big file can take some +* time. +*/ +static int loop_init_fastfs (struct loop_device *lo) +{ +struct file *file = lo->lo_backing_file; +struct inode *inode = file->f_mapping->host; +struct request_queue *fs_q; +int ret; + +if (!S_ISREG (inode->i_mode)) +return -EINVAL ; + +/* +* Need a working bmap. TODO: use the same optimization that +* direct-io.c does for get_block() mapping more than one block +* at the time. +*/ +if (inode->i_mapping->a_ops->bmap ==NULL ) +return -EINVAL ; +/* +* invalidate all page cache belonging to this file, it could become +* stale when we directly overwrite blocks. +*/ + ret =invalidate_inode_pages2 (file->f_mapping); +if (unlikely (ret)) +return ret; + + lo->blkbits = inode->i_blkbits; + lo->fs_bdev = file->f_mapping->host->i_sb->s_bdev; + lo->lo_flags |=LO_FLAGS_FASTFS ; + lo->lo_queue->unplug_fn = loop_unplug_fastfs; + +blk_queue_merge_bvec (lo->lo_queue, loop_merge_bvec); +blk_queue_ordered (lo->lo_queue,QUEUE_ORDERED_DRAIN ,NULL ); + + fs_q =bdev_get_queue (lo->fs_bdev); +blk_queue_stack_limits (lo->lo_queue, fs_q); + +printk (KERN_INFO "loop%d: fast redirect\n" , lo->lo_number); +return 0 ; +} +static inline int is_loop_device (struct file *file) {struct inode *i = file->f_mapping->host;
@@ -748,6 +1180,7 @@ static int loop_set_fd(struct loop_devic mapping = file->f_mapping; inode = mapping->host; + lo->lo_flags =0 ;if (!(file->f_mode &FMODE_WRITE )) lo_flags |=LO_FLAGS_READ_ONLY ;
@@ -810,6 +1243,12 @@ static int loop_set_fd(struct loop_devicbd_set_size (bdev, size <<9 );set_blocksize (bdev, lo_blocksize); + +/* +* This needs to be done after setup with another ioctl, +* not automatically like this. +*/ +loop_init_fastfs (lo); lo->lo_thread =kthread_create (loop_thread, lo,"loop%d" , lo->lo_number);
@@ -896,6 +1335,9 @@ static int loop_clr_fd(struct loop_devickthread_stop (lo->lo_thread); +if (lo->lo_flags &LO_FLAGS_FASTFS ) +loop_exit_fastfs (lo); + lo->lo_backing_file =NULL ;loop_release_xfer (lo);
@@ -943,6 +1385,9 @@ loop_set_status(struct loop_device *lo,if (info->lo_encrypt_type) {unsigned int type = info->lo_encrypt_type; +if (lo->lo_flags &LO_FLAGS_FASTFS ) +return -EINVAL ; +if (type >=MAX_LO_CRYPT )return -EINVAL ; xfer = xfer_funcs[type];
@@ -950,6 +1395,13 @@ loop_set_status(struct loop_device *lo,return -EINVAL ; }else xfer =NULL ; + +/* +* for remaps, offset must be a multiple of full blocks +*/ +if ((lo->lo_flags &LO_FLAGS_FASTFS ) && + (((1 << lo->blkbits) -1 ) & info->lo_offset)) +return -EINVAL ; err =loop_init_xfer (lo, xfer, info);if (err)
@@ -1152,6 +1604,9 @@ static int lo_ioctl(struct inode * inodebreak ;case LOOP_GET_STATUS64 : err =loop_get_status64 (lo, (struct loop_info64 __user *) arg); +break ; +case LOOP_SET_FASTFS : + err =loop_init_fastfs (lo);break ;default : err = lo->ioctl ? lo->ioctl (lo, cmd, arg) : -EINVAL ;
@@ -1412,6 +1867,7 @@ static struct loop_device *loop_alloc(in lo->lo_number = i; lo->lo_thread =NULL ;init_waitqueue_head (&lo->lo_event); +init_waitqueue_head (&lo->lo_bio_wait);spin_lock_init (&lo->lo_lock); disk->major =LOOP_MAJOR ; disk->first_minor = i;
fs/Makefile +1 -1
@@ -11,7 +11,7 @@ obj-y := open.o read_write.o file_table. attr.o bad_inode.o file.o filesystems.o namespace.o aio.o \ seq_file.o xattr.o libfs.o fs-writeback.o \ pnode.o drop_caches.o splice.o sync.o utimes.o \ - stack.o + stack.o extent_map.o ifeq ($(CONFIG_BLOCK),y) obj-y += buffer.o bio.o block_dev.o direct-io.o mpage.o ioprio.o
fs/dcache.c +2 -0
@@ -31,6 +31,7 @@#include <linux/seqlock.h> #include <linux/swap.h> #include <linux/bootmem.h> +#include <linux/extent_map.h> #include "internal.h"
@@ -2170,6 +2171,7 @@ void __init vfs_caches_init(unsigned londcache_init ();inode_init (); +extent_map_init ();files_init (mempages);mnt_init ();bdev_cache_init ();
fs/ext2/ext2.h +3 -0
@@ -1,5 +1,6 @@#include <linux/fs.h> #include <linux/ext2_fs.h> +#include <linux/extent_map.h> /* * ext2 mount options
@@ -62,6 +63,8 @@ struct ext2_inode_info {struct mutex truncate_mutex;struct inode vfs_inode;struct list_head i_orphan;/* unlinked but open inodes */ + +struct extent_map_tree extent_tree; };/*
fs/ext2/ialloc.c +1 -0
@@ -584,6 +584,7 @@ got: ei->i_block_alloc_info =NULL ; ei->i_block_group = group; ei->i_dir_start_lookup =0 ; +extent_map_tree_init (&ei->extent_tree); ei->i_state =EXT2_STATE_NEW ;ext2_set_inode_flags (inode);spin_lock (&sbi->s_next_gen_lock);
fs/ext2/inode.c +15 -0
@@ -31,6 +31,7 @@#include <linux/writeback.h> #include <linux/buffer_head.h> #include <linux/mpage.h> +#include <linux/extent_map.h> #include "ext2.h" #include "acl.h" #include "xip.h"
@@ -70,9 +71,11 @@ void ext2_delete_inode (struct inode * iif (inode->i_blocks)ext2_truncate (inode);ext2_free_inode (inode); +remove_extent_mappings (&EXT2_I (inode)->extent_tree,0 , (u64)-1 );return ; no_delete: +remove_extent_mappings (&EXT2_I (inode)->extent_tree,0 , (u64)-1 );clear_inode (inode);/* We must guarantee clearing of inode... */ }
@@ -709,6 +712,16 @@ int ext2_get_block(struct inode *inode, } +static struct extent_map *ext2_map_extent (struct address_space *mapping, +struct page *page, +size_t page_offset,u64 start, +u64 len,int create,gfp_t gfp_mask) +{ +return map_extent_get_block (&EXT2_I (mapping->host)->extent_tree, + mapping, start, len, create, gfp_mask, + ext2_get_block); +} +static int ext2_writepage (struct page *page,struct writeback_control *wbc) {return block_write_full_page (page, ext2_get_block, wbc);
@@ -796,6 +809,7 @@ const struct address_space_operations ex .direct_IO = ext2_direct_IO, .writepages = ext2_writepages, .migratepage = buffer_migrate_page, + .map_extent = ext2_map_extent, };const struct address_space_operations ext2_aops_xip = {
@@ -1242,6 +1256,7 @@ void ext2_read_inode (struct inode * ino ei->i_state =0 ; ei->i_block_group = (ino -1 ) /EXT2_INODES_PER_GROUP (inode->i_sb); ei->i_dir_start_lookup =0 ; +extent_map_tree_init (&ei->extent_tree);/* * NOTE! The in-memory inode i_data array is in little-endian order
fs/ext2/super.c +2 -0
@@ -156,6 +156,7 @@ static struct inode *ext2_alloc_inode(ststatic void ext2_destroy_inode (struct inode *inode) { +remove_extent_mappings (&EXT2_I (inode)->extent_tree,0 , (u64)-1 );kmem_cache_free (ext2_inode_cachep,EXT2_I (inode)); }
@@ -168,6 +169,7 @@ static void init_once(struct kmem_cacheinit_rwsem (&ei->xattr_sem);#endif mutex_init (&ei->truncate_mutex); +extent_map_tree_init (&ei->extent_tree);inode_init_once (&ei->vfs_inode); }
fs/extent_map.c new file +402 -0
402 lines not on this page · raw message
include/linux/extent_map.h new file +58 -0
58 lines not on this page · raw message
include/linux/fs.h +10 -0
@@ -439,6 +439,7 @@ static inline size_t iov_iter_count(stru } +struct extent_map ;struct address_space_operations {int (*writepage)(struct page *page,struct writeback_control *wbc);int (*readpage)(struct file *,struct page *);
@@ -479,6 +480,15 @@ struct address_space_operations {int (*migratepage) (struct address_space *,struct page *,struct page *);int (*launder_page) (struct page *); + +/* raw extent mapping to the disk */ +struct extent_map *(*map_extent)(struct address_space *mapping, +struct page *page, +size_t page_offset, +u64 start,u64 len,int create, +gfp_t gfp_mask); +int (*extent_io_complete)(struct address_space *mapping, +u64 start,u64 len); };/*
include/linux/loop.h +17 -0
@@ -18,6 +18,7 @@#include <linux/blkdev.h> #include <linux/spinlock.h> #include <linux/mutex.h> +#include <linux/rbtree.h> /* Possible states of device */ enum {
@@ -50,22 +51,31 @@ struct loop_device {struct file * lo_backing_file;struct block_device *lo_device; +struct block_device *fs_bdev;unsigned lo_blocksize;void *key_data; +unsigned int lo_switch;gfp_t old_gfp_mask;spinlock_t lo_lock;struct bio *lo_bio;struct bio *lo_biotail; +unsigned int lo_bio_cnt;int lo_state;struct mutex lo_ctl_mutex;struct task_struct *lo_thread;wait_queue_head_t lo_event; +wait_queue_head_t lo_bio_wait;struct request_queue *lo_queue;struct gendisk *lo_disk;struct list_head lo_list; + +struct prio_tree_root prio_root; +struct prio_tree_node *last_insert; +struct prio_tree_node *last_lookup; + unsigned int blkbits; };#endif /* __KERNEL__ */
@@ -76,6 +86,7 @@ enum {enum {LO_FLAGS_READ_ONLY =1 ,LO_FLAGS_USE_AOPS =2 , +LO_FLAGS_FASTFS =4 , };#include <asm/posix_types.h> /* for __kernel_old_dev_t */
@@ -159,5 +170,11 @@ int loop_unregister_transfer(int number)#define LOOP_SET_STATUS64 0x4C04#define LOOP_GET_STATUS64 0x4C05#define LOOP_CHANGE_FD 0x4C06 +#define LOOP_SET_FASTFS 0x4C07 + +enum { +LOOP_EXTENT_RW_MAGIC =0x19283746 , +LOOP_SWITCH_RW_MAGIC =0xfeedbeef , +};#endif
Quoted 27 lines · Chris Mason
Hello everyone,
Here is a modified version of Jens' patch. The basic idea is to push the mapping maintenance out of loop and down into the filesystem (ext2 in this case).
Two new
address_spaceoperations are added, one to map extents and the other to provide call backs into the FS as io is completed.Still TODO for this patch:
* Add exclusion between filling holes and readers. This is partly implemented, when a hole is filled by the FS, the extent is flagged as having a hole. The idea is to check this flag before trying to read the blocks and just send back all zeros.
The flag would be cleared when the blocks filling the hole have been written.
* Exclude page cache readers and writers
* Add a way for the FS to request a commit before telling the higher layers the IO is complete. This way we can make sure metadata related to holes is on disk before claiming the IO is really done. COW based filesystems will also needed it.
Quoted 5 lines
* Change loop to use fast mapping only when the new
address_spaceop is provided (whoops, forgot about this until just now)* A few other bits for COW, not really relevant until there is...something COW using it.
Looks pretty good. Essentially the loop side is 100% the same, it just offloads the extent ownership to the fs (where it belongs). So I like it. Attaching a small cleanup/fixup patch for loop, don't think it needs further explanations.
One suggestion - free_extent_map(), I would call that put_extent_map() instead.
drivers/block/loop.c +10 -14
@@ -677,13 +677,14 @@if (IS_ERR (lfe))return -EIO ; -while (!lfe) { +while (!lfe) {loop_schedule_extent_mapping (lo, bio->bi_sector, bio->bi_size,1 ); lfe =loop_lookup_extent (lo, start,GFP_ATOMIC );if (IS_ERR (lfe))return -EIO ; } +/* * handle sparse io */
@@ -802,13 +803,13 @@ {struct bio *orig_bio = bio->bi_private;struct inode *inode = bio->bi_bdev->bd_inode; +struct address_space *mapping = inode->i_mapping;u64 start = orig_bio->bi_sector <<9 ;u64 len = bio->bi_size; -if (inode->i_mapping->a_ops->extent_io_complete) { - inode->i_mapping->a_ops->extent_io_complete (inode->i_mapping, - start, len); - } +if (mapping->a_ops->extent_io_complete) + mapping->a_ops->extent_io_complete (mapping, start, len); +bio_put (bio);bio_endio (orig_bio, err); }
@@ -829,6 +830,7 @@ err = -ENOMEM ; goto out; } +/* * change the sector so we can find the correct file offset in our * endio
@@ -847,7 +849,6 @@ goto out;; } - disk_block = em->block_start; extent_off = start - em->start; new_bio->bi_sector = (disk_block + extent_off) >>9 ;
@@ -924,11 +925,8 @@spin_unlock_irq (&lo->lo_lock);BUG_ON (!bio); -if (lo_act_bio (bio)) - bio_act =1 ; -else - bio_act =0 ; + bio_act =lo_act_bio (bio);loop_handle_bio (lo, bio);spin_lock_irq (&lo->lo_lock);
@@ -1103,11 +1101,9 @@return -EINVAL ;/* -* Need a working bmap. TODO: use the same optimization that -* direct-io.c does for get_block() mapping more than one block -* at the time. +* Need a working extent_map */ -if (inode->i_mapping->a_ops->bmap ==NULL ) +if (inode->i_mapping->a_ops->map_extent ==NULL )return -EINVAL ;/* * invalidate all page cache belonging to this file, it could become
include/linux/loop.h +0 -4
@@ -18,7 +18,6 @@#include <linux/blkdev.h> #include <linux/spinlock.h> #include <linux/mutex.h> -#include <linux/rbtree.h> /* Possible states of device */ enum {
@@ -72,9 +71,6 @@struct gendisk *lo_disk;struct list_head lo_list; -struct prio_tree_root prio_root; -struct prio_tree_node *last_insert; -struct prio_tree_node *last_lookup;unsigned int blkbits; };
Signature · 1 line
Jens Axboe
Quoted 34 lines · Jens Axboe
Quoted 27 lines · Chris Mason
Hello everyone,
Here is a modified version of Jens' patch. The basic idea is to push the mapping maintenance out of loop and down into the filesystem (ext2 in this case).
Two new
address_spaceoperations are added, one to map extents and the other to provide call backs into the FS as io is completed.Still TODO for this patch:
* Add exclusion between filling holes and readers. This is partly implemented, when a hole is filled by the FS, the extent is flagged as having a hole. The idea is to check this flag before trying to read the blocks and just send back all zeros.
The flag would be cleared when the blocks filling the hole have been written.
* Exclude page cache readers and writers
* Add a way for the FS to request a commit before telling the higher layers the IO is complete. This way we can make sure metadata related to holes is on disk before claiming the IO is really done. COW based filesystems will also needed it.
Quoted 5 lines
* Change loop to use fast mapping only when the new
address_spaceop is provided (whoops, forgot about this until just now)* A few other bits for COW, not really relevant until there is...something COW using it.
Quoted 7 lines
Looks pretty good. Essentially the loop side is 100% the same, it just offloads the extent ownership to the fs (where it belongs). So I like it. Attaching a small cleanup/fixup patch for loop, don't think it needs further explanations.
One suggestion -
free_extent_map(), I would call thatput_extent_map()instead.
I split and merged the patch into five bits (added ext3 support), so perhaps that would be easier for people to read/review. Attached and also exist in the loop-extent_map branch here:
http://git.kernel.dk/?p=linux-2.6-block.git;a=shortlog;h=loop-extent_map
Signature · 1 line
Jens Axboe
Quoted 1 line
From
b179b114b7ef6f4df1520e0013b8efe631ff577dMon Sep 17 00:00:00 2001
From: Chris Mason <[email protected]>
Date: Tue, 15 Jan 2008 10:04:43 +0100
Subject: [PATCH] fs: add extent_map library
--- fs/Makefile | 2 +- fs/dcache.c | 2 + fs/extent_map.c | 402 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/extent_map.h | 58 +++++++ 4 files changed, 463 insertions(+), 1 deletions(-) create mode 100644 fs/extent_map.c create mode 100644 include/linux/extent_map.h
fs/Makefile +1 -1
@@ -11,7 +11,7 @@ obj-y := open.o read_write.o file_table.o super.o \ attr.o bad_inode.o file.o filesystems.o namespace.o aio.o \ seq_file.o xattr.o libfs.o fs-writeback.o \ pnode.o drop_caches.o splice.o sync.o utimes.o \ - stack.o + stack.o extent_map.o ifeq ($(CONFIG_BLOCK),y) obj-y += buffer.o bio.o block_dev.o direct-io.o mpage.o ioprio.o
fs/dcache.c +2 -0
@@ -31,6 +31,7 @@#include <linux/seqlock.h> #include <linux/swap.h> #include <linux/bootmem.h> +#include <linux/extent_map.h> #include "internal.h"
@@ -2170,6 +2171,7 @@ void __init vfs_caches_init(unsigned long mempages)dcache_init ();inode_init (); +extent_map_init ();files_init (mempages);mnt_init ();bdev_cache_init ();
fs/extent_map.c new file +402 -0
402 lines not on this page · raw message
include/linux/extent_map.h new file +58 -0
58 lines not on this page · raw message
Signature · 1 line
1.5.4.rc2.84.gf85fd
Quoted 1 line
From
10a93b6e5a2c7209c34937f946bf4a1e4b36e46dMon Sep 17 00:00:00 2001
From: Chris Mason <[email protected]>
Date: Tue, 15 Jan 2008 10:05:20 +0100
Subject: [PATCH] fs: add extent_map hooks to the address space operations
--- include/linux/fs.h | 10 ++++++++++ 1 files changed, 10 insertions(+), 0 deletions(-)
include/linux/fs.h +10 -0
@@ -439,6 +439,7 @@ static inline size_t iov_iter_count(struct iov_iter *i) } +struct extent_map ;struct address_space_operations {int (*writepage)(struct page *page,struct writeback_control *wbc);int (*readpage)(struct file *,struct page *);
@@ -479,6 +480,15 @@ struct address_space_operations {int (*migratepage) (struct address_space *,struct page *,struct page *);int (*launder_page) (struct page *); + +/* raw extent mapping to the disk */ +struct extent_map *(*map_extent)(struct address_space *mapping, +struct page *page, +size_t page_offset, +u64 start,u64 len,int create, +gfp_t gfp_mask); +int (*extent_io_complete)(struct address_space *mapping, +u64 start,u64 len); };/*
Signature · 1 line
1.5.4.rc2.84.gf85fd
Quoted 1 line
From
bf6e48c71803f2c7dfab10196ef43ca1d8f2146fMon Sep 17 00:00:00 2001
From: Chris Mason <[email protected]>
Date: Tue, 15 Jan 2008 10:05:46 +0100
Subject: [PATCH] ext2: add support for extent_map API
--- fs/ext2/ext2.h | 3 +++ fs/ext2/ialloc.c | 1 + fs/ext2/inode.c | 15 +++++++++++++++ fs/ext2/super.c | 2 ++ 4 files changed, 21 insertions(+), 0 deletions(-)
fs/ext2/ext2.h +3 -0
@@ -1,5 +1,6 @@#include <linux/fs.h> #include <linux/ext2_fs.h> +#include <linux/extent_map.h> /* * ext2 mount options
@@ -62,6 +63,8 @@ struct ext2_inode_info {struct mutex truncate_mutex;struct inode vfs_inode;struct list_head i_orphan;/* unlinked but open inodes */ + +struct extent_map_tree extent_tree; };/*
fs/ext2/ialloc.c +1 -0
@@ -584,6 +584,7 @@ got: ei->i_block_alloc_info =NULL ; ei->i_block_group = group; ei->i_dir_start_lookup =0 ; +extent_map_tree_init (&ei->extent_tree); ei->i_state =EXT2_STATE_NEW ;ext2_set_inode_flags (inode);spin_lock (&sbi->s_next_gen_lock);
fs/ext2/inode.c +15 -0
@@ -31,6 +31,7 @@#include <linux/writeback.h> #include <linux/buffer_head.h> #include <linux/mpage.h> +#include <linux/extent_map.h> #include "ext2.h" #include "acl.h" #include "xip.h"
@@ -70,9 +71,11 @@ void ext2_delete_inode (struct inode * inode)if (inode->i_blocks)ext2_truncate (inode);ext2_free_inode (inode); +remove_extent_mappings (&EXT2_I (inode)->extent_tree,0 , (u64)-1 );return ; no_delete: +remove_extent_mappings (&EXT2_I (inode)->extent_tree,0 , (u64)-1 );clear_inode (inode);/* We must guarantee clearing of inode... */ }
@@ -709,6 +712,16 @@ int ext2_get_block(struct inode *inode, sector_t iblock, struct buffer_head *bh_ } +static struct extent_map *ext2_map_extent (struct address_space *mapping, +struct page *page, +size_t page_offset,u64 start, +u64 len,int create,gfp_t gfp_mask) +{ +return map_extent_get_block (&EXT2_I (mapping->host)->extent_tree, + mapping, start, len, create, gfp_mask, + ext2_get_block); +} +static int ext2_writepage (struct page *page,struct writeback_control *wbc) {return block_write_full_page (page, ext2_get_block, wbc);
@@ -796,6 +809,7 @@ const struct address_space_operations ext2_aops = { .direct_IO = ext2_direct_IO, .writepages = ext2_writepages, .migratepage = buffer_migrate_page, + .map_extent = ext2_map_extent, };const struct address_space_operations ext2_aops_xip = {
@@ -1242,6 +1256,7 @@ void ext2_read_inode (struct inode * inode) ei->i_state =0 ; ei->i_block_group = (ino -1 ) /EXT2_INODES_PER_GROUP (inode->i_sb); ei->i_dir_start_lookup =0 ; +extent_map_tree_init (&ei->extent_tree);/* * NOTE! The in-memory inode i_data array is in little-endian order
fs/ext2/super.c +2 -0
@@ -156,6 +156,7 @@ static struct inode *ext2_alloc_inode(struct super_block *sb)static void ext2_destroy_inode (struct inode *inode) { +remove_extent_mappings (&EXT2_I (inode)->extent_tree,0 , (u64)-1 );kmem_cache_free (ext2_inode_cachep,EXT2_I (inode)); }
@@ -168,6 +169,7 @@ static void init_once(struct kmem_cache * cachep, void *foo)init_rwsem (&ei->xattr_sem);#endif mutex_init (&ei->truncate_mutex); +extent_map_tree_init (&ei->extent_tree);inode_init_once (&ei->vfs_inode); }
Signature · 1 line
1.5.4.rc2.84.gf85fd
Quoted 1 line
From
0bed11ab335981f8ef9a0fe98785c0d2f0200c52Mon Sep 17 00:00:00 2001
From: Jens Axboe <[email protected]>
Date: Tue, 15 Jan 2008 10:06:08 +0100
Subject: [PATCH] ext3: add support for extent_map API
Just a port of Chris' ext2 version.
--- fs/ext3/ialloc.c | 2 ++ fs/ext3/inode.c | 18 ++++++++++++++++++ fs/ext3/super.c | 2 ++ include/linux/ext3_fs_i.h | 3 +++ 4 files changed, 25 insertions(+), 0 deletions(-)
fs/ext3/ialloc.c +2 -0
@@ -23,6 +23,7 @@#include <linux/buffer_head.h> #include <linux/random.h> #include <linux/bitops.h> +#include <linux/extent_map.h> #include <asm/byteorder.h>
@@ -579,6 +580,7 @@ got: ei->i_dtime =0 ; ei->i_block_alloc_info =NULL ; ei->i_block_group = group; +extent_map_tree_init (&ei->extent_tree);ext3_set_inode_flags (inode);if (IS_DIRSYNC (inode))
fs/ext3/inode.c +18 -0
@@ -36,6 +36,7 @@#include <linux/mpage.h> #include <linux/uio.h> #include <linux/bio.h> +#include <linux/extent_map.h> #include "xattr.h" #include "acl.h"
@@ -228,8 +229,10 @@ void ext3_delete_inode (struct inode * inode)else ext3_free_inode (handle, inode);ext3_journal_stop (handle); +remove_extent_mappings (&EXT3_I (inode)->extent_tree,0 , (u64) -1 );return ; no_delete: +remove_extent_mappings (&EXT3_I (inode)->extent_tree,0 , (u64) -1 );clear_inode (inode);/* We must guarantee clearing of inode... */ }
@@ -993,6 +996,17 @@ get_block:return ret; } +static struct extent_map *ext3_map_extent (struct address_space *mapping, +struct page *page,size_t page_offset, +u64 start,u64 len,int create, +gfp_t gfp_mask) +{ +struct extent_map_tree *tree = &EXT3_I (mapping->host)->extent_tree; + +return map_extent_get_block (tree, mapping, start, len, create, gfp_mask, + ext3_get_block); +} +/* * `handle' can be NULL if create is zero */
@@ -1780,6 +1794,7 @@ static const struct address_space_operations ext3_ordered_aops = { .releasepage = ext3_releasepage, .direct_IO = ext3_direct_IO, .migratepage = buffer_migrate_page, + .map_extent = ext3_map_extent, };static const struct address_space_operations ext3_writeback_aops = {
@@ -1794,6 +1809,7 @@ static const struct address_space_operations ext3_writeback_aops = { .releasepage = ext3_releasepage, .direct_IO = ext3_direct_IO, .migratepage = buffer_migrate_page, + .map_extent = ext3_map_extent, };static const struct address_space_operations ext3_journalled_aops = {
@@ -1807,6 +1823,7 @@ static const struct address_space_operations ext3_journalled_aops = { .bmap = ext3_bmap, .invalidatepage = ext3_invalidatepage, .releasepage = ext3_releasepage, + .map_extent = ext3_map_extent, };void ext3_set_aops (struct inode *inode)
@@ -2785,6 +2802,7 @@ void ext3_read_inode(struct inode * inode)init_special_inode (inode, inode->i_mode,new_decode_dev (le32_to_cpu (raw_inode->i_block[1 ]))); } +extent_map_tree_init (&ei->extent_tree);brelse (iloc.bh);ext3_set_inode_flags (inode);return ;
fs/ext3/super.c +2 -0
@@ -469,6 +469,7 @@ static void ext3_destroy_inode(struct inode *inode) false);dump_stack (); } +remove_extent_mappings (&EXT3_I (inode)->extent_tree,0 , (u64) -1 );kmem_cache_free (ext3_inode_cachep,EXT3_I (inode)); }
@@ -481,6 +482,7 @@ static void init_once(struct kmem_cache * cachep, void *foo)init_rwsem (&ei->xattr_sem);#endif mutex_init (&ei->truncate_mutex); +extent_map_tree_init (&ei->extent_tree);inode_init_once (&ei->vfs_inode); }
include/linux/ext3_fs_i.h +3 -0
@@ -20,6 +20,7 @@#include <linux/rbtree.h> #include <linux/seqlock.h> #include <linux/mutex.h> +#include <linux/extent_map.h> /* data type for block offset of block group */ typedef int ext3_grpblk_t ;
@@ -142,6 +143,8 @@ struct ext3_inode_info {*/ struct mutex truncate_mutex;struct inode vfs_inode; + + struct extent_map_tree extent_tree; };#endif /* _LINUX_EXT3_FS_I */
Signature · 1 line
1.5.4.rc2.84.gf85fd
Quoted 1 line
From
1f8a7cd25cee70cfa2c022993b371b8d1b8d1abfMon Sep 17 00:00:00 2001
From: Jens Axboe <[email protected]>
Date: Tue, 15 Jan 2008 10:06:34 +0100
Subject: [PATCH] loop: fastfs support
Add code to support redirecting IO directly to the filesystem blocks instead of going through the page cache.
--- drivers/block/loop.c | 466 +++++++++++++++++++++++++++++++++++++++++++++++++- include/linux/loop.h | 13 ++ 2 files changed, 472 insertions(+), 7 deletions(-)
drivers/block/loop.c +459 -7
609 lines not on this page · raw message
include/linux/loop.h +13 -0
@@ -50,22 +50,28 @@ struct loop_device {struct file * lo_backing_file;struct block_device *lo_device; +struct block_device *fs_bdev;unsigned lo_blocksize;void *key_data; +unsigned int lo_switch;gfp_t old_gfp_mask;spinlock_t lo_lock;struct bio *lo_bio;struct bio *lo_biotail; +unsigned int lo_bio_cnt;int lo_state;struct mutex lo_ctl_mutex;struct task_struct *lo_thread;wait_queue_head_t lo_event; +wait_queue_head_t lo_bio_wait;struct request_queue *lo_queue;struct gendisk *lo_disk;struct list_head lo_list; + + unsigned int blkbits; };#endif /* __KERNEL__ */
@@ -76,6 +82,7 @@ struct loop_device {enum {LO_FLAGS_READ_ONLY =1 ,LO_FLAGS_USE_AOPS =2 , +LO_FLAGS_FASTFS =4 , };#include <asm/posix_types.h> /* for __kernel_old_dev_t */
@@ -159,5 +166,11 @@ int loop_unregister_transfer(int number);#define LOOP_SET_STATUS64 0x4C04#define LOOP_GET_STATUS64 0x4C05#define LOOP_CHANGE_FD 0x4C06 +#define LOOP_SET_FASTFS 0x4C07 + +enum { +LOOP_EXTENT_RW_MAGIC =0x19283746 , +LOOP_SWITCH_RW_MAGIC =0xfeedbeef , +};#endif
Signature · 1 line
1.5.4.rc2.84.gf85fd
Quoted 40 lines · Jens Axboe
Quoted 39 lines · Jens Axboe
Quoted 27 lines · Chris Mason
Hello everyone,
Here is a modified version of Jens' patch. The basic idea is to push the mapping maintenance out of loop and down into the filesystem (ext2 in this case).
Two new
address_spaceoperations are added, one to map extents and the other to provide call backs into the FS as io is completed.Still TODO for this patch:
* Add exclusion between filling holes and readers. This is partly implemented, when a hole is filled by the FS, the extent is flagged as having a hole. The idea is to check this flag before trying to read the blocks and just send back all zeros.
The flag would be cleared when the blocks filling the hole have been written.
* Exclude page cache readers and writers
* Add a way for the FS to request a commit before telling the higher layers the IO is complete. This way we can make sure metadata related to holes is on disk before claiming the IO is really done. COW based filesystems will also needed it.
Quoted 5 lines
* Change loop to use fast mapping only when the new
address_spaceop is provided (whoops, forgot about this until just now)* A few other bits for COW, not really relevant until there is...something COW using it.
Looks pretty good. Essentially the loop side is 100% the same, it just offloads the extent ownership to the fs (where it belongs). So I like it. Attaching a small cleanup/fixup patch for loop, don't think it needs further explanations.
Quoted 8 lines
Quoted 2 lines
One suggestion -
free_extent_map(), I would call thatput_extent_map()instead.I split and merged the patch into five bits (added ext3 support), so perhaps that would be easier for people to read/review. Attached and also exist in the loop-extent_map branch here:
http://git.kernel.dk/?p=linux-2.6-block.git;a=shortlog;h=loop-extent_map
Seems my ext3 version doesn't work, it craps out in ext3_get_blocks_handle() triggering this bug:
J_ASSERT(handle != NULL || create == 0);
I'll see if I can fix that, being fairly fs ignorant...
Signature · 1 line
Jens Axboe
Quoted 48 lines · Jens Axboe
Quoted 47 lines · Jens Axboe
Quoted 34 lines · Jens Axboe
Quoted 27 lines · Chris Mason
Hello everyone,
Here is a modified version of Jens' patch. The basic idea is to push the mapping maintenance out of loop and down into the filesystem (ext2 in this case).
Two new
address_spaceoperations are added, one to map extents and the other to provide call backs into the FS as io is completed.Still TODO for this patch:
* Add exclusion between filling holes and readers. This is partly implemented, when a hole is filled by the FS, the extent is flagged as having a hole. The idea is to check this flag before trying to read the blocks and just send back all zeros.
The flag would be cleared when the blocks filling the hole have been written.
* Exclude page cache readers and writers
* Add a way for the FS to request a commit before telling the higher layers the IO is complete. This way we can make sure metadata related to holes is on disk before claiming the IO is really done. COW based filesystems will also needed it.
Quoted 5 lines
* Change loop to use fast mapping only when the new
address_spaceop is provided (whoops, forgot about this until just now)* A few other bits for COW, not really relevant until there is...something COW using it.
Quoted 7 lines
Looks pretty good. Essentially the loop side is 100% the same, it just offloads the extent ownership to the fs (where it belongs). So I like it. Attaching a small cleanup/fixup patch for loop, don't think it needs further explanations.
One suggestion -
free_extent_map(), I would call thatput_extent_map()instead.I split and merged the patch into five bits (added ext3 support), so perhaps that would be easier for people to read/review. Attached and also exist in the loop-extent_map branch here:
Quoted 8 lines
Seems my ext3 version doesn't work, it craps out in
ext3_get_blocks_handle()triggering this bug:
J_ASSERT(handle != NULL || create == 0);I'll see if I can fix that, being fairly fs ignorant...
This works, but probably pretty suboptimal (should end the new journal in map_io_complete()?). And yes I know the >> 9 isn't correct, since the fs block size is larger. Just making sure that we always have enough blocks.
Punting to Chris!
fs/ext3/inode.c +15 -1
@@ -1002,11 +1002,25 @@ static struct extent_map *ext3_map_extent(struct address_space *mapping, gfp_t gfp_mask) {struct extent_map_tree *tree = &EXT3_I (mapping->host)->extent_tree; +handle_t *handle =NULL ; +struct extent_map *ret; -return map_extent_get_block (tree, mapping, start, len, create, gfp_mask, +if (create) { + handle =ext3_journal_start (mapping->host, len >>9 ); +if (IS_ERR (handle)) +return (struct extent_map *) handle; + } + + ret =map_extent_get_block (tree, mapping, start, len, create, gfp_mask, ext3_get_block); +if (handle) +ext3_journal_stop (handle); + +return ret; } + +/* * `handle' can be NULL if create is zero */
Signature · 1 line
Jens Axboe
Quoted 3 lines · On Tue, 15 Jan 2008 11:07:40 +0100 Jens Axboe <[email protected]> wrote:
Quoted 3 lines
Quoted 3 lines
I split and merged the patch into five bits (added ext3 support), so perhaps that would be easier for people to read/review. Attached and also exist in the loop-extent_map branch here:
Thanks!
Quoted 6 lines
Quoted 6 lines
Seems my ext3 version doesn't work, it craps out in
ext3_get_blocks_handle()triggering this bug:
Quoted 8 lines
Quoted 3 lines
J_ASSERT(handle != NULL || create == 0);I'll see if I can fix that, being fairly fs ignorant...
This works, but probably pretty suboptimal (should end the new journal in
map_io_complete()?). And yes I know the >> 9 isn't correct, since the fs block size is larger. Just making sure that we always have enough blocks.
You can use DIO_CREDITS instead of len >> 9, just like the ext3 O_DIRECT code does. Your current patch is fine, except it breaks data=ordered rules. My plan to work within data=ordered:
1) Inside ext3_map_extent (while the transaction was running), increment a counter in the ext3 journal for number of pending IOs. Then end the transaction handle.
2) Drop this counter inside the IO completion call
3) Change the ext3 commit code to wait for the IO count to be zero.
I'll give it a shot later this week, until then your current patch is just data=writeback, which is good enough for testing.
-chris