keventd is inappropriate for running block request queues because keventd
itself can get blocked on disk I/O.  Via call_usermodehelper()'s vfork and,
presumably, GFP_KERNEL allocations.

So create a new gang of kernel threads whose mandate is for running low-level
disk operations.  It must ever block on disk IO, so any memory allocations
should be GFP_NOIO.



 drivers/block/Makefile    |    5 +++++
 drivers/block/ll_rw_blk.c |   21 ++++++++++++++++++++-
 include/linux/blkdev.h    |    4 ++++
 3 files changed, 29 insertions(+), 1 deletion(-)

diff -puN drivers/block/Makefile~kblockd drivers/block/Makefile
--- 25/drivers/block/Makefile~kblockd	2003-03-23 00:23:59.000000000 -0800
+++ 25-akpm/drivers/block/Makefile	2003-03-23 00:23:59.000000000 -0800
@@ -8,6 +8,11 @@
 # In the future, some of these should be built conditionally.
 #
 
+#
+# NOTE that ll_rw_blk.c must come early in linkage order - it starts the
+# kblockd threads
+#
+
 obj-y	:= elevator.o ll_rw_blk.o ioctl.o genhd.o scsi_ioctl.o deadline-iosched.o
 
 obj-$(CONFIG_MAC_FLOPPY)	+= swim3.o
diff -puN drivers/block/ll_rw_blk.c~kblockd drivers/block/ll_rw_blk.c
--- 25/drivers/block/ll_rw_blk.c~kblockd	2003-03-23 00:23:59.000000000 -0800
+++ 25-akpm/drivers/block/ll_rw_blk.c	2003-03-23 00:23:59.000000000 -0800
@@ -56,6 +56,11 @@ static int batch_requests;
 unsigned long blk_max_low_pfn, blk_max_pfn;
 int blk_nohighio = 0;
 
+/*
+ * Controlling structure to kblockd
+ */
+static struct workqueue_struct *kblockd_workqueue;
+
 static wait_queue_head_t congestion_wqh[2];
 
 /*
@@ -2179,11 +2184,25 @@ void end_that_request_last(struct reques
 	__blk_put_request(req->q, req);
 }
 
+int kblockd_schedule_work(struct work_struct *work)
+{
+	return queue_work(kblockd_workqueue, work);
+}
+
+void kblockd_flush(void)
+{
+	flush_workqueue(kblockd_workqueue);
+}
+
 int __init blk_dev_init(void)
 {
 	int total_ram = nr_free_pages() << (PAGE_SHIFT - 10);
 	int i;
 
+	kblockd_workqueue = create_workqueue("kblockd");
+	if (!kblockd_workqueue)
+		panic("Failed to create kblockd\n");
+
 	request_cachep = kmem_cache_create("blkdev_requests",
 			sizeof(struct request), 0, 0, NULL, NULL);
 	if (!request_cachep)
@@ -2216,7 +2235,7 @@ int __init blk_dev_init(void)
 	for (i = 0; i < ARRAY_SIZE(congestion_wqh); i++)
 		init_waitqueue_head(&congestion_wqh[i]);
 	return 0;
-};
+}
 
 EXPORT_SYMBOL(end_that_request_first);
 EXPORT_SYMBOL(end_that_request_chunk);
diff -puN include/linux/blkdev.h~kblockd include/linux/blkdev.h
--- 25/include/linux/blkdev.h~kblockd	2003-03-23 00:23:59.000000000 -0800
+++ 25-akpm/include/linux/blkdev.h	2003-03-23 00:23:59.000000000 -0800
@@ -460,6 +460,10 @@ static inline void put_dev_sector(Sector
 	page_cache_release(p.v);
 }
 
+struct work_struct;
+int kblockd_schedule_work(struct work_struct *work);
+void kblockd_flush(void);
+
 #ifdef CONFIG_LBD
 # include <asm/div64.h>
 # define sector_div(a, b) do_div(a, b)

_