Skip to contentSkip to conversation

superbh, fractured blocks, and grouped io

PATCH In review Jens Axboe 2 participants 9 messages Oct 14, 2002 – Oct 17, 2002

Subsystems (3)
lore
Share & tools
lore

Jens Axboe

author
#0 lore raw

PATCH 1/1 superbh, fractured blocks, and grouped io

Hi,

This patch introduces a concept called 'super buffer_heads', or just superbh. I came up with this beast to solve one problem, but in the end it actually solves two. So it can't be all bad.

Problem #1. Consider an application that internally deals with data in blocks of a certain size. This application would like to, from time to time, write out some of these blocks, and is uses raw io or direct io to do so. Lets assume that the block size is 32kb, the application writes out a nice 32kb chunk. Linux then breaks this down into device block size chunks, this is anywhere from 512 bytes to 4096 bytes, and passes them to the io scheduler. If we are lucky, these buffer_heads that make up the application block size will end up in one request and there is a good chance that the write will be atomic (not really 100% atomic, but close enough). If we are unlucky, the 8-64 buffer_heads will be scattered over the request queue and not written in order. If the machine looses power, there's a good chance that some of the block belonging to the application block are written and some are not. We call this fractured blocks, and this is something we want to avoid.

So intead of breaking io down into teeny bits, we submit io in multiples for the application block size. In 2.5 this can be done with a bio, in 2.4 we have to mess around with smaller buffer_heads. So we add a submit_bh_linked() that acts like submit_bh(), but you can pass in a chain of buffer_heads instead of just one (via b_reqnext, like the block layer would do).

We need some way of passing in a bunch of buffer_heads to the io scheduler for insertion (and merge), for this we add a concept called a superbh. A superbh is a pseudo buffer_head, that doesn't contain any data itself. Instead it describes a bunch of buffer_heads. A superbh can be as big as the

	unsigned short b_size;

allows. So submit_bh_linked() sets up a superbh of the right size, and passes it to generic_make_request().

We differentiate between superbh handling and atomic io blocks. An atomic io block will be submitted as a superbh, but it is possible for a queue to say that it can accept a superbh but it cannot guarentee atomic operations on it. Note that this does not require changes to block drivers, if they don't define a private make_request_fn(). So it will not directly work on loop, md, and lvm before someone does the work to support it. Should work on basically everything else, though.

The queue has two new properties:

	/*
	 * max size of an atomic io block, 0 if not supported
	 */
	int	grouped_queue;

	/*
	 * max size of a superbh, 0 if not supported
	 */
	int	superbh_queue;

Should be self-explanatory. Normally, superbh_queue is just the max size a request can be for this device as it requires no driver or hardware support. grouped_queue can only safely be set if the hardware has battery backed write cache, or can guarentee atomic io in some other way.

So what was problem #2? Well, queueing an O_DIRECT or raw write/read of 128KiB in units of 512 bytes *sucks* from a system utilization perspective. Even if you disregard the atomic io bit of the patch (which is basically not done, only the infrastructure is there), the patch can heavily cut down on sys time when lots of data is moved.

Patch is against 2.4.20-pre10

# This is a BitKeeper generated patch for the following project:
# Project Name: Linux kernel tree
# This patch format is intended for GNU patch command version 2.5 or higher. # This patch includes the following deltas:
# ChangeSet 1.737 -> 1.738
# drivers/ide/ide-probe.c 1.12 -> 1.13
# drivers/block/ll_rw_blk.c 1.40 -> 1.41
# include/linux/blkdev.h 1.22 -> 1.23
# drivers/block/elevator.c 1.7 -> 1.8
# include/linux/fs.h 1.69 -> 1.70
# drivers/scsi/scsi_merge.c 1.9 -> 1.10
# drivers/scsi/scsi.c 1.17 -> 1.18
# fs/buffer.c 1.74 -> 1.75
#
# The following is the BitKeeper ChangeSet Log
# --------------------------------------------
# 02/10/14 [email protected] 1.738
# Introduce superbh and grouped IO concept
# --------------------------------------------
#

drivers/block/elevator.c +4 -0
diff -Nru a/drivers/block/elevator.c b/drivers/block/elevator.c
@@ -103,6 +103,8 @@
 			continue;
 		if (__rq->nr_sectors + count > max_sectors)
 			continue;
+		if (__rq->dont_merge)
+			continue;
 		if (__rq->sector + __rq->nr_sectors == bh->b_rsector) {
 			ret = ELEVATOR_BACK_MERGE;
 			*req = __rq;
@@ -163,6 +165,8 @@
 		if (__rq->nr_sectors + count > max_sectors)
 			continue;
 		if (__rq->waiting)
+			continue;
+		if (__rq->dont_merge)
 			continue;
 		if (__rq->sector + __rq->nr_sectors == bh->b_rsector) {
 			*req = __rq;
drivers/block/ll_rw_blk.c +283 -22

389 lines not on this page · raw message

drivers/ide/ide-probe.c +6 -0
diff -Nru a/drivers/ide/ide-probe.c b/drivers/ide/ide-probe.c
@@ -603,9 +603,15 @@
 static void ide_init_queue(ide_drive_t *drive)
 {
 	request_queue_t *q = &drive->queue;
+	int max_sectors = 128;
 
 	q->queuedata = HWGROUP(drive);
 	blk_init_queue(q, do_ide_request);
+
+	if (HWIF(drive)->chipset == ide_pdc4030)
+		max_sectors = 127;
+
+	blk_queue_superbh(q, max_sectors);
 
 	if (drive->media == ide_disk) {
 #ifdef CONFIG_BLK_DEV_ELEVATOR_NOOP
drivers/scsi/scsi.c +8 -0
diff -Nru a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
@@ -198,6 +198,14 @@
 	blk_init_queue(q, scsi_request_fn);
 	blk_queue_headactive(q, 0);
 	q->queuedata = (void *) SDpnt;
+
+	q->max_segments = SHpnt->sg_tablesize;
+	blk_queue_superbh(q, SHpnt->max_sectors);
+
+	/*
+	 * FIXME: just for testing, needs to be based on real evidence
+	 */
+	blk_queue_grouped(q, SHpnt->max_sectors);
 }
 
 #ifdef MODULE
drivers/scsi/scsi_merge.c +2 -4
diff -Nru a/drivers/scsi/scsi_merge.c b/drivers/scsi/scsi_merge.c
@@ -810,11 +810,9 @@
 	/*
 	 * First we need to know how many scatter gather segments are needed.
 	 */
-	if (!sg_count_valid) {
+	count = req->nr_segments;
+	if (!sg_count_valid || req->seg_invalid)
 		count = __count_segments(req, use_clustering, dma_host, NULL);
-	} else {
-		count = req->nr_segments;
-	}
 
 	/*
 	 * If the dma pool is nearly empty, then queue a minimal request
fs/buffer.c +29 -5
diff -Nru a/fs/buffer.c b/fs/buffer.c
@@ -2220,7 +2220,7 @@
 	unsigned long	blocknr;
 	struct kiobuf *	iobuf = NULL;
 	struct page *	map;
-	struct buffer_head *tmp, **bhs = NULL;
+	struct buffer_head *tmp, **bhs = NULL, *bh_first, *bh_prev;
 
 	if (!nr)
 		return 0;
@@ -2241,6 +2241,7 @@
 	 * OK to walk down the iovec doing page IO on each page we find. 
 	 */
 	bufind = bhind = transferred = err = 0;
+	bh_first = bh_prev = NULL;
 	for (i = 0; i < nr; i++) {
 		iobuf = iovec[i];
 		offset = iobuf->offset;
@@ -2255,7 +2256,7 @@
 				err = -EFAULT;
 				goto finished;
 			}
-			
+
 			while (length > 0) {
 				blocknr = b[bufind++];
 				if (blocknr == -1UL) {
@@ -2272,6 +2273,9 @@
 				}
 				tmp = bhs[bhind++];
 
+				if (!bh_first)
+					bh_first = tmp;
+
 				tmp->b_size = size;
 				set_bh_page(tmp, map, offset);
 				tmp->b_this_page = tmp;
@@ -2280,6 +2284,7 @@
 				tmp->b_dev = dev;
 				tmp->b_blocknr = blocknr;
 				tmp->b_state = (1 << BH_Mapped) | (1 << BH_Lock) | (1 << BH_Req);
+				tmp->b_reqnext = NULL;
 
 				if (rw == WRITE) {
 					set_bit(BH_Uptodate, &tmp->b_state);
@@ -2288,11 +2293,21 @@
 					set_bit(BH_Uptodate, &tmp->b_state);
 
 				atomic_inc(&iobuf->io_count);
-				submit_bh(rw, tmp);
+
+				if (bh_prev)
+					bh_prev->b_reqnext = tmp;
+
+				bh_prev = tmp;
+
 				/* 
 				 * Wait for IO if we have got too much 
 				 */
 				if (bhind >= KIO_MAX_SECTORS) {
+submit_io:
+					/*
+					 * FIXME: use _grouped for atomic IO
+					 */
+					submit_bh_linked(rw, bh_first);
 					kiobuf_wait_for_io(iobuf); /* wake-one */
 					err = wait_kio(rw, bhind, bhs, size);
 					if (err >= 0)
@@ -2300,12 +2315,19 @@
 					else
 						goto finished;
 					bhind = 0;
+					bh_prev = bh_first = NULL;
 				}
 
 			skip_block:
 				length -= size;
 				offset += size;
 
+				/*
+				 * see if we need to submit the previous bit
+				 */
+				if ((blocknr == -1) && bhind)
+					goto submit_io;
+
 				if (offset >= PAGE_SIZE) {
 					offset = 0;
 					break;
@@ -2316,12 +2338,14 @@
 
 	/* Is there any IO still left to submit? */
 	if (bhind) {
+		/*
+		 * FIXME: use _grouped for atomic IO
+		 */
+		submit_bh_linked(rw, bh_first);
 		kiobuf_wait_for_io(iobuf); /* wake-one */
 		err = wait_kio(rw, bhind, bhs, size);
 		if (err >= 0)
 			transferred += err;
-		else
-			goto finished;
 	}
 
  finished:
include/linux/blkdev.h +28 -2
diff -Nru a/include/linux/blkdev.h b/include/linux/blkdev.h
@@ -46,6 +46,9 @@
 	struct buffer_head * bh;
 	struct buffer_head * bhtail;
 	request_queue_t *q;
+
+	char dont_merge;
+	char seg_invalid;
 };
 
 #include <linux/elevator.h>
@@ -126,6 +129,22 @@
 	 */
 	char			head_active;
 
+	/*
+	 * max segments
+	 */
+	int			max_segments;
+
+	/*
+	 * The biggest "atomic" io chunk we can deal with, or 0 if not
+	 * supported.
+	 */
+	int			grouped_queue;
+
+	/*
+	 * The biggest superbh we can generate for this queue
+	 */
+	int			superbh_queue;
+
 	unsigned long		bounce_pfn;
 
 	/*
@@ -154,6 +173,12 @@
 {
 	struct page *page = bh->b_page;
 
+	/*
+	 * not a real bh
+	 */
+	if (test_bit(BH_Super, &bh->b_state))
+		return bh;
+
 #ifndef CONFIG_DISCONTIGMEM
 	if (page - mem_map <= q->bounce_pfn)
 #else
@@ -209,6 +234,8 @@
 extern void blk_init_queue(request_queue_t *, request_fn_proc *);
 extern void blk_cleanup_queue(request_queue_t *);
 extern void blk_queue_headactive(request_queue_t *, int);
+extern void blk_queue_grouped(request_queue_t *, int);
+extern void blk_queue_superbh(request_queue_t *, int);
 extern void blk_queue_make_request(request_queue_t *, make_request_fn *);
 extern void generic_unplug_device(void *);
 extern inline int blk_seg_merge_ok(struct buffer_head *, struct buffer_head *);
@@ -227,8 +254,7 @@
 
 #define MAX_SEGMENTS 128
 #define MAX_SECTORS 255
-
-#define PageAlignSize(size) (((size) + PAGE_SIZE -1) & PAGE_MASK)
+#define MAX_SUPERBH 65535	/* must fit info ->b_size right now */
 
 #define blkdev_entry_to_request(entry) list_entry((entry), struct request, queue)
 #define blkdev_entry_next_request(entry) blkdev_entry_to_request((entry)->next)
include/linux/fs.h +6 -1
diff -Nru a/include/linux/fs.h b/include/linux/fs.h
@@ -220,7 +220,8 @@
 	BH_Wait_IO,	/* 1 if we should write out this buffer */
 	BH_Launder,	/* 1 if we can throttle on this buffer */
 	BH_JBD,		/* 1 if it has an attached journal_head */
-
+	BH_Super,	/* 1 if this is a superbh */
+	BH_Grouped,	/* 1 if this is a grouped submit */
 	BH_PrivateStart,/* not a state bit, but the first bit available
 			 * for private allocation by other entities
 			 */
@@ -283,6 +284,8 @@
 #define buffer_new(bh)		__buffer_state(bh,New)
 #define buffer_async(bh)	__buffer_state(bh,Async)
 #define buffer_launder(bh)	__buffer_state(bh,Launder)
+#define buffer_superbh(bh)	__buffer_state(bh,Super)
+#define buffer_grouped(bh)	__buffer_state(bh,Grouped)
 
 #define bh_offset(bh)		((unsigned long)(bh)->b_data & ~PAGE_MASK)
 
@@ -1377,6 +1380,8 @@
 extern struct buffer_head * getblk(kdev_t, int, int);
 extern void ll_rw_block(int, int, struct buffer_head * bh[]);
 extern void submit_bh(int, struct buffer_head *);
+extern int submit_bh_linked(int, struct buffer_head *);
+extern int submit_bh_grouped(int, struct buffer_head *);
 extern int is_read_only(kdev_t);
 extern void __brelse(struct buffer_head *);
 static inline void brelse(struct buffer_head *buf)
Signature · 1 line
Jens Axboe
#1 lore raw

On Mon, Oct 14, 2002 at 03:51:00PM +0200, Jens Axboe wrote:

Just a couple niggles, really. Looking good.

drivers/block/ll_rw_blk.c line 1331

Quoted 5 lines
drivers/block/ll_rw_blk.c +1 -2
 	/*
-	 * First step, 'identity mapping' - RAID or LVM might
-	 * further remap this.
+	 * detach each bh and resubmit, or completely and if its a grouped bh
 	 */

The last line of the comment means "completely fail if its
grouped", right?

Quoted 1 line

+#define MAX_SUPERBH 65535 /* must fit info ->b_size right now */

Why not sizeof(b_size) in case we ever care?

include/linux/fs.h line 1384

Quoted 2 lines
include/linux/fs.h +2 -0
+extern int submit_bh_linked(int, struct buffer_head *);
+extern int submit_bh_grouped(int, struct buffer_head *);

Why aren't these EXPORT_SYMBOL(), given that a third party
driver may wish to use them (eg, a filesystem doing its own O_DIRECT work)?

Joel

Signature · 8 lines
"We'd better get back, `cause it'll be dark soon,
 and they mostly come at night.  Mostly."

Joel Becker
Senior Member of Technical Staff
Oracle Corporation
E-mail: [email protected]
Phone: (650) 506-8127
author
#2 lore raw
Quoted 4 lines · Joel Becker

On Mon, Oct 14, 2002 at 03:51:00PM +0200, Jens Axboe wrote:

Just a couple niggles, really. Looking good.

Quoted 8 lines

drivers/block/ll_rw_blk.c line 1331

Quoted 5 lines
drivers/block/ll_rw_blk.c +1 -2
 	/*
-	 * First step, 'identity mapping' - RAID or LVM might
-	 * further remap this.
+	 * detach each bh and resubmit, or completely and if its a grouped bh
 	 */

The last line of the comment means "completely fail if its
grouped", right?

Oops, the 'and' should be an 'end'. So your wording is correct.

Quoted 3 lines
Quoted 1 line

+#define MAX_SUPERBH 65535 /* must fit info ->b_size right now */

Why not sizeof(b_size) in case we ever care?

We don't want to make them too large anyway, and I think that 64k-1 is more than enough. Maybe it would even be better to simply use an even 32kb. Consider someone writing in chunks of 64kb. It would simply suck to get one 65024b request followed by a 512b one.

Quoted 6 lines

include/linux/fs.h line 1384

Quoted 2 lines
include/linux/fs.h +2 -0
+extern int submit_bh_linked(int, struct buffer_head *);
+extern int submit_bh_grouped(int, struct buffer_head *);

Why aren't these EXPORT_SYMBOL(), given that a third party
driver may wish to use them (eg, a filesystem doing its own O_DIRECT work)?

Sure yes they will be, posted code was a first draft :-)

Signature · 1 line
Jens Axboe
#3 lore raw
Quoted 4 lines · Jens Axboe

We don't want to make them too large anyway, and I think that 64k-1 is more than enough. Maybe it would even be better to simply use an even 32kb. Consider someone writing in chunks of 64kb. It would simply suck to get one 65024b request followed by a 512b one.

Well, 64K-1 is really 64K-hardsect_size. In practice, it will
probably be 32K, as folks like power-of-two buffers. Some people would claim that larger sizes (128K, 512K) can improve throughput.

Joel

Signature · 9 lines
Life's Little Instruction Book #356

	"Be there when people need you."

Joel Becker
Senior Member of Technical Staff
Oracle Corporation
E-mail: [email protected]
Phone: (650) 506-8127
#4 lore raw
In reply to Jens Axboe

drivers/block/ll_rw_blk.c line 1016

Quoted 3 lines · Jens Axboe
drivers/block/ll_rw_blk.c +0 -0
@@ -943,7 +1015,6 @@
 	 */
 	bh = blk_queue_bounce(q, rw, bh);

I don't know why this only slightly bothered me until I oopsed.
This only bounces the superbh and certainly doesn't bounce all the attendant bhs in the list.

Joel

Signature · 8 lines
"In the room the women come and go
 Talking of Michaelangelo."

Joel Becker
Senior Member of Technical Staff
Oracle Corporation
E-mail: [email protected]
Phone: (650) 506-8127
#5 lore raw
Quoted 4 lines · Joel Becker

drivers/block/ll_rw_blk.c line 1016

Quoted 3 lines · Jens Axboe
drivers/block/ll_rw_blk.c +0 -0
@@ -943,7 +1015,6 @@
 	 */
 	bh = blk_queue_bounce(q, rw, bh);

Thinking about this, I went to add it into submit_bh_list()
where we already iterate the bhs. However, this would require some reordering and would require teaching create_bounce() about linked I/Os. Any better ideas?

Joel

Signature · 9 lines
Life's Little Instruction Book #43

	"Never give up on somebody.  Miracles happen every day."

Joel Becker
Senior Member of Technical Staff
Oracle Corporation
E-mail: [email protected]
Phone: (650) 506-8127
author
#6 lore raw
Quoted 10 lines · Joel Becker
Quoted 4 lines · Joel Becker

drivers/block/ll_rw_blk.c line 1016

Quoted 3 lines · Jens Axboe
drivers/block/ll_rw_blk.c +0 -0
@@ -943,7 +1015,6 @@
 	 */
 	bh = blk_queue_bounce(q, rw, bh);

Thinking about this, I went to add it into submit_bh_list()
where we already iterate the bhs. However, this would require some reordering and would require teaching create_bounce() about linked I/Os. Any better ideas?

The worst problem is the deadlock issue with potentially having to allocate more than one bounce page.

Signature · 1 line
Jens Axboe
author
#7 lore raw
In reply to Joel Becker
Quoted 8 lines · Joel Becker

drivers/block/ll_rw_blk.c line 1016

Quoted 3 lines · Jens Axboe
drivers/block/ll_rw_blk.c +0 -0
@@ -943,7 +1015,6 @@
 	 */
 	bh = blk_queue_bounce(q, rw, bh);

I don't know why this only slightly bothered me until I oopsed.
This only bounces the superbh and certainly doesn't bounce all the attendant bhs in the list.

Eh no, it actually doesn't bounce anything! I added a test to bypass bouncing temporarily in blk_queue_bounce() (see the BH_Super test in there), but forgot to replace it with the real thing. The superbh contains no data, so is not a candidate for bouncing.

Signature · 1 line
Jens Axboe
#8 lore raw
Quoted 4 lines · Jens Axboe

Eh no, it actually doesn't bounce anything! I added a test to bypass bouncing temporarily in blk_queue_bounce() (see the BH_Super test in there), but forgot to replace it with the real thing. The superbh contains no data, so is not a candidate for bouncing.

I understand that the superbh isn't a candidate for bouncing.
The problem is that the rest of the bhs are, and they never get bounced. scsi_merge.c checks "if (PageHighMem(bh->b_page)) BUG();" in __init_io(), and that obviously crashes.

Joel

Signature · 10 lines
"Conservative, n.  A statesman who is enamoured of existing evils,
 as distinguished from the Liberal, who wishes to replace them
 with others."
	- Ambrose Bierce, The Devil's Dictionary

Joel Becker
Senior Member of Technical Staff
Oracle Corporation
E-mail: [email protected]
Phone: (650) 506-8127