Hi,
Here is a patch for 2.5.3pre6 to make RAW and O_DIRECT use "bio"
more efficiently. Idea is to simply eliminate intermediate "kiobuf".
I used some of the infrastructure from Ben's AIO patch (2.4.18).
Since Ben's patch is for 2.4, I had to rewrite some of it to suit
2.5 BIO design.
Description:
============
Added kvec (memory container), kvec_cb (control block for kvec IO).
Added support routines for kvec: map_user_kvec(), unmap_kvec(),
alloc_kvec(), free_kvec() etc..
Use veclets from kvec directly as bio_vecs for "bio" to do the IO.
Issues:
=======
generic_direct_IO() uses stack to build a extent list <block#, size>
of discontiguous disk blocks and passes it to brw_kvec(). Need to
find a better way to do it.
I am restructuring the code so that, I can push down one extent at a time
and wait for completion at higher level. One problem with this approach
is kvec_cb (control block) need to have info on the last veclet where
we did IO.
Any ideas ?
Things to follow:
=================
1) generic_direct_IO() cleanup.
2) I have not removed f_iobuf from filp. Need to do some kiobuf cleanup.
3) Add support for readv/writev for RAW.
Any comments on approach/code are welcome. I can make 2.5.4-preX patch
if the approach is acceptable.
Thanks,
Badari
diff -Naur -X dontdiff linux-2.5.3pre6.org/drivers/char/raw.c linux-2.5.3pre6/drivers/char/raw.c
--- linux-2.5.3pre6.org/drivers/char/raw.c Tue Jan 1 11:40:34 2002
+++ linux-2.5.3pre6/drivers/char/raw.c Mon Feb 4 20:36:39 2002
@@ -85,12 +85,6 @@
return 0;
}
- if (!filp->f_iobuf) {
- err = alloc_kiovec(1, &filp->f_iobuf);
- if (err)
- return err;
- }
-
down(&raw_devices[minor].mutex);
/*
* No, it is a normal raw device. All we need to do on open is
@@ -255,8 +249,7 @@
ssize_t rw_raw_dev(int rw, struct file *filp, char *buf,
size_t size, loff_t *offp)
{
- struct kiobuf * iobuf;
- int new_iobuf;
+ struct kvec *vec;
int err = 0;
unsigned long blocks;
size_t transferred;
@@ -273,17 +266,11 @@
minor = minor(filp->f_dentry->d_inode->i_rdev);
- new_iobuf = 0;
- iobuf = filp->f_iobuf;
- if (test_and_set_bit(0, &filp->f_iobuf_lock)) {
- /*
- * A parallel read/write is using the preallocated iobuf
- * so just run slow and allocate a new one.
- */
- err = alloc_kiovec(1, &iobuf);
- if (err)
- goto out;
- new_iobuf = 1;
+
+ vec = alloc_kvec((size/PAGE_SIZE) + 1);
+ if (!vec) {
+ err = -ENOMEM;
+ goto out;
}
dev = to_kdev_t(raw_devices[minor].binding->bd_dev);
@@ -318,14 +305,14 @@
iosize = blocks << sector_bits;
- err = map_user_kiobuf(rw, iobuf, (unsigned long) buf, iosize);
+ err = map_user_kvec(rw, vec, (unsigned long) buf, iosize);
if (err)
break;
- err = brw_kiovec(rw, 1, &iobuf, dev, &blocknr, sector_size);
+ err = brw_kvec(rw, 1, vec, dev, &blocknr, &iosize, sector_size);
if (rw == READ && err > 0)
- mark_dirty_kiobuf(iobuf, err);
+ mark_dirty_kvec(vec, err);
if (err >= 0) {
transferred += err;
@@ -335,7 +322,7 @@
blocknr += blocks;
- unmap_kiobuf(iobuf);
+ unmap_kvec(vec);
if (err != iosize)
break;
@@ -346,11 +333,8 @@
err = transferred;
}
- out_free:
- if (!new_iobuf)
- clear_bit(0, &filp->f_iobuf_lock);
- else
- free_kiovec(1, &iobuf);
- out:
+out_free:
+ free_kvec(vec);
+out:
return err;
}
diff -Naur -X dontdiff linux-2.5.3pre6.org/fs/bio.c linux-2.5.3pre6/fs/bio.c
--- linux-2.5.3pre6.org/fs/bio.c Thu Dec 27 08:15:15 2001
+++ linux-2.5.3pre6/fs/bio.c Wed Feb 6 21:17:14 2002
@@ -495,6 +495,98 @@
return 0;
}
+static int bio_end_io_kvec(struct bio *bio, int nr_sectors)
+{
+ struct kvec_cb *kvec_cb = (struct kvec_cb *) bio->bi_private;
+
+ end_kvec_request(kvec_cb, test_bit(BIO_UPTODATE, &bio->bi_flags));
+ bio_put(bio);
+ return 0;
+}
+
+int ll_rw_kvec(int rw, struct kvec_cb *vec_cb, kdev_t dev, int nr, sector_t b[], int len[])
+{
+ int i, err, tlen, blen, total;
+ struct kvec *vec = vec_cb->vec;
+ struct bio *bio;
+ kveclet_t *veclet;
+ int sector;
+
+ err = 0;
+ total = 0;
+
+ if ((rw & WRITE) && is_read_only(dev)) {
+ printk("ll_rw_kvec: WRITE to ro device %s\n", kdevname(dev));
+ err = -EPERM;
+ goto out;
+ }
+
+ if (!vec->nr) {
+ err = -EINVAL;
+ goto out;
+ }
+
+
+ atomic_set(&vec_cb->io_count, 1);
+ veclet = vec->veclet;
+
+ for (i = 0; i < nr; i++) {
+ tlen = len[i];
+ sector = b[i];
+
+ while (tlen) {
+
+ blen = 0;
+
+ /*
+ * allocate bio and do initial setup
+ */
+ if ((bio = bio_alloc(GFP_NOIO, 0)) == NULL) {
+ err = -ENOMEM;
+ goto out;
+ }
+ atomic_inc(&vec_cb->io_count);
+
+ bio->bi_sector = sector;
+ bio->bi_dev = dev;
+ bio->bi_idx = 0;
+ bio->bi_end_io = bio_end_io_kvec;
+ bio->bi_private = vec_cb;
+ bio->bi_io_vec = veclet;
+
+ while (blen < tlen) {
+ if ((blen + veclet->bv_len) > BIO_MAX_SIZE)
+ break;
+
+ blen += veclet->bv_len;
+ bio->bi_vcnt++;
+ veclet++;
+ }
+
+ bio->bi_size = blen;
+ sector += (blen >> 9);
+ tlen -= blen;
+
+ submit_bio(rw, bio);
+ total += blen;
+ }
+ }
+
+out:
+ if (err)
+ vec_cb->errno = err;
+
+ /*
+ * final atomic_dec of io_count to match our initial setting of 1.
+ * I/O may or may not have completed at this point, final completion
+ * handler is only run on last decrement.
+ */
+ end_kvec_request(vec_cb, !err);
+ return total;
+}
+
module_init(init_bio);
EXPORT_SYMBOL(bio_alloc);
diff -Naur -X dontdiff linux-2.5.3pre6.org/fs/block_dev.c linux-2.5.3pre6/fs/block_dev.c
--- linux-2.5.3pre6.org/fs/block_dev.c Mon Feb 4 21:20:28 2002
+++ linux-2.5.3pre6/fs/block_dev.c Mon Feb 4 21:06:44 2002
@@ -134,9 +134,9 @@
return 0;
}
-static int blkdev_direct_IO(int rw, struct inode * inode, struct kiobuf * iobuf, unsigned long blocknr, int blocksize)
+static int blkdev_direct_IO(int rw, struct inode * inode, struct kvec * iobuf, unsigned long blocknr, int iosize, int blocksize)
{
- return generic_direct_IO(rw, inode, iobuf, blocknr, blocksize, blkdev_get_block);
+ return generic_direct_IO(rw, inode, iobuf, blocknr, blocksize, iosize, blkdev_get_block);
}
static int blkdev_writepage(struct page * page)
diff -Naur -X dontdiff linux-2.5.3pre6.org/fs/buffer.c linux-2.5.3pre6/fs/buffer.c
--- linux-2.5.3pre6.org/fs/buffer.c Mon Feb 4 21:20:28 2002
+++ linux-2.5.3pre6/fs/buffer.c Wed Feb 6 22:41:59 2002
@@ -1919,14 +1919,20 @@
return tmp.b_blocknr;
}
-int generic_direct_IO(int rw, struct inode * inode, struct kiobuf * iobuf, unsigned long blocknr, int blocksize, get_block_t * get_block)
+int generic_direct_IO(int rw, struct inode * inode, struct kvec * vec, unsigned long blocknr, int blocksize, int iosize, get_block_t * get_block)
{
int i, nr_blocks, retval;
- sector_t *blocks = iobuf->blocks;
+ int blkno, len;
+ sector_t blocks[256];
+ int blen[256];
+
+ nr_blocks = iosize/blocksize;
+ i = 0;
+ len = 0;
+ blkno = -2;
- nr_blocks = iobuf->length / blocksize;
/* build the blocklist */
- for (i = 0; i < nr_blocks; i++, blocknr++) {
+ while (nr_blocks) {
struct buffer_head bh;
bh.b_state = 0;
@@ -1937,12 +1943,17 @@
if (retval)
goto out;
+ blocknr++;
+ nr_blocks--;
+
if (rw == READ) {
if (buffer_new(&bh))
BUG();
if (!buffer_mapped(&bh)) {
/* there was an hole in the filesystem */
blocks[i] = -1UL;
+ blen[i] = blocksize;
+ i++;
continue;
}
} else {
@@ -1951,11 +1962,28 @@
if (!buffer_mapped(&bh))
BUG();
}
- blocks[i] = bh.b_blocknr;
+ if (bh.b_blocknr == blkno + 1) {
+ len += blocksize;
+ } else {
+ if (len != 0) {
+ blocks[i] = blkno * (blocksize >> 9);
+ blen[i] = len;
+ i++;
+ }
+ blkno = bh.b_blocknr;
+ len = blocksize;
+ }
+
+ }
+
+ if (blkno != -2) {
+ blocks[i] = blkno * (blocksize >> 9);
+ blen[i] = len;
+ i++;
}
/* This does not understand multi-device filesystems currently */
- retval = brw_kiovec(rw, 1, &iobuf, inode->i_dev, blocks, blocksize);
+ retval = brw_kvec(rw, i, vec, inode->i_dev, blocks, blen, blocksize);
out:
return retval;
@@ -2017,6 +2045,40 @@
if (!err)
transferred += iobuf->length;
}
+
+ return err ? err : transferred;
+}
+
+int brw_kvec(int rw, int nr, struct kvec *vec, kdev_t dev, sector_t b[],
+ int len[], int size)
+{
+ int transferred;
+ int i, err;
+ struct kvec_cb vec_cb;
+ kveclet_t *vecl;
+
+ if (!nr)
+ return 0;
+
+ if (!vec->nr)
+ panic("brw_kvec: kvec not initialised");
+
+ vecl = vec->veclet;
+ for (i = 0; i < vec->nr; i++, vecl++) {
+ if ((vecl->bv_offset & (size-1)) || (vecl->bv_len & (size-1)))
+ return -EINVAL;
+ }
+
+ vec_cb.errno = 0;
+ init_waitqueue_head(&vec_cb.wait_queue);
+ atomic_set(&vec_cb.io_count, 0);
+ vec_cb.vec = vec;
+
+ transferred = ll_rw_kvec(rw, &vec_cb, dev, nr, b, len);
+
+ kvec_wait_for_io(&vec_cb);
+
+ err = vec_cb.errno;
return err ? err : transferred;
}
diff -Naur -X dontdiff linux-2.5.3pre6.org/fs/ext2/inode.c linux-2.5.3pre6/fs/ext2/inode.c
--- linux-2.5.3pre6.org/fs/ext2/inode.c Mon Feb 4 21:20:28 2002
+++ linux-2.5.3pre6/fs/ext2/inode.c Mon Feb 4 21:13:06 2002
@@ -592,9 +592,9 @@
{
return generic_block_bmap(mapping,block,ext2_get_block);
}
-static int ext2_direct_IO(int rw, struct inode * inode, struct kiobuf * iobuf, unsigned long blocknr, int blocksize)
+static int ext2_direct_IO(int rw, struct inode * inode, struct kvec * iobuf, unsigned long blocknr, int blocksize, int iosize)
{
- return generic_direct_IO(rw, inode, iobuf, blocknr, blocksize, ext2_get_block);
+ return generic_direct_IO(rw, inode, iobuf, blocknr, blocksize, iosize, ext2_get_block);
}
struct address_space_operations ext2_aops = {
readpage: ext2_readpage,
diff -Naur -X dontdiff linux-2.5.3pre6.org/fs/iobuf.c linux-2.5.3pre6/fs/iobuf.c
--- linux-2.5.3pre6.org/fs/iobuf.c Tue Nov 27 09:23:27 2001
+++ linux-2.5.3pre6/fs/iobuf.c Tue Feb 5 16:38:09 2002
@@ -120,3 +120,56 @@
+struct kvec *alloc_kvec(int nr)
+{
+ struct kvec *vec;
+
+ vec = kmalloc(sizeof(struct kvec) + nr * sizeof(kveclet_t), GFP_KERNEL);
+ if (!vec) {
+ return 0;
+ }
+ vec->nr = 0;
+ vec->max_nr = nr;
+ return vec;
+}
+
+void free_kvec(struct kvec *vec)
+{
+ kfree(vec);
+}
+
+int end_kvec_request(struct kvec_cb *vec_cb, int uptodate)
+{
+ int ret = 1;
+
+ if ((!uptodate) && !vec_cb->errno)
+ vec_cb->errno = -EIO;
+
+ if (atomic_dec_and_test(&vec_cb->io_count)) {
+ ret = 0;
+ wake_up(&vec_cb->wait_queue);
+ }
+
+ return ret;
+}
+
+void kvec_wait_for_io(struct kvec_cb *vec_cb)
+{
+ struct task_struct *tsk = current;
+ DECLARE_WAITQUEUE(wait, tsk);
+
+ if (atomic_read(&vec_cb->io_count) == 0)
+ return;
+
+ add_wait_queue(&vec_cb->wait_queue, &wait);
+repeat:
+ set_task_state(tsk, TASK_UNINTERRUPTIBLE);
+ if (atomic_read(&vec_cb->io_count) != 0) {
+ run_task_queue(&tq_disk);
+ schedule();
+ if (atomic_read(&vec_cb->io_count) != 0)
+ goto repeat;
+ }
+ tsk->state = TASK_RUNNING;
+ remove_wait_queue(&vec_cb->wait_queue, &wait);
+}
diff -Naur -X dontdiff linux-2.5.3pre6.org/include/linux/fs.h linux-2.5.3pre6/include/linux/fs.h
--- linux-2.5.3pre6.org/include/linux/fs.h Mon Feb 4 21:20:28 2002
+++ linux-2.5.3pre6/include/linux/fs.h Mon Feb 4 21:13:39 2002
@@ -350,6 +350,7 @@
struct page;
struct address_space;
struct kiobuf;
+struct kvec;
struct address_space_operations {
int (*writepage)(struct page *);
@@ -366,7 +367,7 @@
int (*flushpage) (struct page *, unsigned long);
int (*releasepage) (struct page *, int);
#define KERNEL_HAS_O_DIRECT /* this is for modules out of the kernel */
- int (*direct_IO)(int, struct inode *, struct kiobuf *, unsigned long, int);
+ int (*direct_IO)(int, struct inode *, struct kvec *, unsigned long, int, int);
};
struct address_space {
@@ -1434,7 +1435,7 @@
sector_t generic_block_bmap(struct address_space *, sector_t, get_block_t *);
int generic_commit_write(struct file *, struct page *, unsigned, unsigned);
int block_truncate_page(struct address_space *, loff_t, get_block_t *);
-extern int generic_direct_IO(int, struct inode *, struct kiobuf *, unsigned long, int, get_block_t *);
+extern int generic_direct_IO(int, struct inode *, struct kvec *, unsigned long, int, int, get_block_t *);
extern int waitfor_one_page(struct page*);
extern int generic_file_mmap(struct file *, struct vm_area_struct *);
diff -Naur -X dontdiff linux-2.5.3pre6.org/include/linux/iobuf.h linux-2.5.3pre6/include/linux/iobuf.h
--- linux-2.5.3pre6.org/include/linux/iobuf.h Mon Jan 14 13:27:14 2002
+++ linux-2.5.3pre6/include/linux/iobuf.h Tue Feb 5 16:28:54 2002
@@ -12,6 +12,7 @@
#include <linux/init.h>
#include <linux/wait.h>
#include <asm/atomic.h>
+#include <linux/bio.h>
/*
* The kiobuf structure describes a physical set of pages reserved
@@ -84,5 +85,42 @@
/* fs/bio.c */
void ll_rw_kio(int rw, struct kiobuf *kio, kdev_t dev, sector_t block);
+
+
+typedef struct bio_vec kveclet_t;
+
+struct kvec {
+ unsigned max_nr;
+ unsigned nr;
+ kveclet_t veclet[0];
+};
+
+struct kvec_cb {
+ struct kvec *vec;
+ atomic_t io_count;
+ int errno;
+ wait_queue_head_t wait_queue;
+};
+
+/* mm/memory.c */
+
+int map_user_kvec(int rw, struct kvec *vec, unsigned long va, size_t len);
+void unmap_kvec(struct kvec *vec);
+void mark_dirty_kvec(struct kvec *vec, int bytes);
+
+/* fs/iobuf.c */
+
+int end_kvec_request(struct kvec_cb *, int);
+struct kvec *alloc_kvec(int nr);
+void free_kvec(struct kvec *);
+void kvec_wait_for_io(struct kvec_cb *);
+
+/* fs/buffer.c */
+int brw_kvec(int rw, int nr, struct kvec *vec,
+ kdev_t dev, sector_t [], int len[], int size);
+
+/* fs/bio.c */
+int ll_rw_kvec(int rw, struct kvec_cb *vec_cb, kdev_t dev, int nr, sector_t b[], int len[]);
+
#endif /* __LINUX_IOBUF_H */
diff -Naur -X dontdiff linux-2.5.3pre6.org/mm/filemap.c linux-2.5.3pre6/mm/filemap.c
--- linux-2.5.3pre6.org/mm/filemap.c Mon Feb 4 21:20:28 2002
+++ linux-2.5.3pre6/mm/filemap.c Mon Feb 4 21:08:09 2002
@@ -1483,22 +1483,14 @@
static ssize_t generic_file_direct_IO(int rw, struct file * filp, char * buf, size_t count, loff_t offset)
{
ssize_t retval;
- int new_iobuf, chunk_size, blocksize_mask, blocksize, blocksize_bits, iosize, progress;
- struct kiobuf * iobuf;
+ int chunk_size, blocksize_mask, blocksize, blocksize_bits, iosize, progress;
+ struct kvec *vec;
struct address_space * mapping = filp->f_dentry->d_inode->i_mapping;
struct inode * inode = mapping->host;
- new_iobuf = 0;
- iobuf = filp->f_iobuf;
- if (test_and_set_bit(0, &filp->f_iobuf_lock)) {
- /*
- * A parallel read/write is using the preallocated iobuf
- * so just run slow and allocate a new one.
- */
- retval = alloc_kiovec(1, &iobuf);
- if (retval)
- goto out;
- new_iobuf = 1;
+ vec = alloc_kvec((count/PAGE_SIZE) + 1);
+ if (!vec) {
+ return -ENOMEM;
}
blocksize = 1 << inode->i_blkbits;
@@ -1528,14 +1520,14 @@
if (iosize > chunk_size)
iosize = chunk_size;
- retval = map_user_kiobuf(rw, iobuf, (unsigned long) buf, iosize);
+ retval = map_user_kvec(rw, vec, (unsigned long) buf, iosize);
if (retval)
break;
- retval = mapping->a_ops->direct_IO(rw, inode, iobuf, (offset+progress) >> blocksize_bits, blocksize);
+ retval = mapping->a_ops->direct_IO(rw, inode, vec, (offset+progress) >> blocksize_bits, blocksize, iosize);
if (rw == READ && retval > 0)
- mark_dirty_kiobuf(iobuf, retval);
+ mark_dirty_kvec(vec, retval);
if (retval >= 0) {
count -= retval;
@@ -1543,7 +1535,7 @@
progress += retval;
}
- unmap_kiobuf(iobuf);
+ unmap_kvec(vec);
if (retval != iosize)
break;
@@ -1553,10 +1545,7 @@
retval = progress;
out_free:
- if (!new_iobuf)
- clear_bit(0, &filp->f_iobuf_lock);
- else
- free_kiovec(1, &iobuf);
+ free_kvec(vec);
out:
return retval;
}
diff -Naur -X dontdiff linux-2.5.3pre6.org/mm/memory.c linux-2.5.3pre6/mm/memory.c
--- linux-2.5.3pre6.org/mm/memory.c Mon Feb 4 21:20:28 2002
+++ linux-2.5.3pre6/mm/memory.c Wed Feb 6 17:13:41 2002
@@ -1443,3 +1443,138 @@
len, write, 0, NULL, NULL);
return ret == len ? 0 : -1;
}
+
+int get_user_pages_veclet(struct task_struct *tsk, struct mm_struct *mm,
+ unsigned long start, int len, int write, int force, kveclet_t *vecl)
+{
+ int i = 0;
+ unsigned long offset;
+
+ offset = start & ~PAGE_MASK;
+
+ do {
+ struct vm_area_struct * vma;
+
+ vma = find_extend_vma(mm, start);
+
+ if ( !vma ||
+ (!force &&
+ ((write && (!(vma->vm_flags & VM_WRITE))) ||
+ (!write && (!(vma->vm_flags & VM_READ))) ) )) {
+ if (i) return i;
+ return -EFAULT;
+ }
+
+ spin_lock(&mm->page_table_lock);
+ do {
+ struct page *map;
+ while (!(map = follow_page(mm, start, write))) {
+ spin_unlock(&mm->page_table_lock);
+ switch (handle_mm_fault(mm, vma, start, write)) {
+ case 1:
+ tsk->min_flt++;
+ break;
+ case 2:
+ tsk->maj_flt++;
+ break;
+ case 0:
+ if (i) return i;
+ return -EFAULT;
+ default:
+ if (i) return i;
+ return -ENOMEM;
+ }
+ spin_lock(&mm->page_table_lock);
+ }
+ map = get_page_map(map);
+ if (map) {
+ vecl->bv_page = map;
+ page_cache_get(map);
+ flush_dcache_page(map);
+ } else
+ printk (KERN_INFO "Mapped page missing\n");
+
+ vecl->bv_offset = offset;
+ vecl->bv_len = PAGE_SIZE - offset;
+ if (len < vecl->bv_len)
+ vecl->bv_len = len;
+ start += PAGE_SIZE;
+ offset = 0;
+ len -= vecl->bv_len;
+ vecl++;
+ i++;
+ } while(len && start < vma->vm_end);
+ spin_unlock(&mm->page_table_lock);
+ } while(len);
+ return i;
+}
+
+int map_user_kvec(int rw, struct kvec *vec, unsigned long va, size_t len)
+{
+ int pgcount, err;
+ struct mm_struct *mm;
+
+ mm = current->mm;
+
+ pgcount = (va + len + PAGE_SIZE - 1)/PAGE_SIZE - va/PAGE_SIZE;
+
+ if (!pgcount) BUG();
+
+ if (vec->nr + pgcount > vec->max_nr)
+ return -ENOMEM;
+
+ /* Try to fault in all of the necessary pages */
+ down_read(&mm->mmap_sem);
+
+ /* rw==READ means read from disk, write into memory area */
+ err = get_user_pages_veclet(current, mm, va, len,
+ (rw==READ), 0, &vec->veclet[vec->nr]);
+
+ up_read(&mm->mmap_sem);
+
+ if (err < 0) {
+ unmap_kvec(vec);
+ return err;
+ }
+ vec->nr += err;
+
+ return 0;
+}
+
+void unmap_kvec (struct kvec *vec)
+{
+ int i;
+ struct page *map;
+ kveclet_t *vecl;
+
+ vecl = vec->veclet;
+
+ for (i = 0; i < vec->nr; i++, vecl++) {
+ map = vecl->bv_page;
+ if (map) {
+ page_cache_release(map);
+ }
+ }
+
+ vec->nr = 0;
+}
+
+void mark_dirty_kvec(struct kvec *vec, int bytes)
+{
+ int index;
+ struct page *page;
+ kveclet_t *vecl;
+
+ index = 0;
+ vecl = vec->veclet;
+ while (bytes > 0 && index < vec->max_nr) {
+ page = vecl->bv_page;
+
+ if (!PageReserved(page))
+ SetPageDirty(page);
+
+ bytes -= vecl->bv_len;
+ index++;
+ vecl++;
+ }
+}
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
This archive was generated by hypermail 2b29 : Thu Feb 07 2002 - 21:00:56 EST