]> git.hungrycats.org Git - linux/commitdiff
[PATCH] Add helper routines for fixing up page alignment on xdr_buf
authorTrond Myklebust <trond.myklebust@fys.uio.no>
Sun, 15 Dec 2002 08:11:24 +0000 (00:11 -0800)
committerLinus Torvalds <torvalds@home.transmeta.com>
Sun, 15 Dec 2002 08:11:24 +0000 (00:11 -0800)
In order to speed up NFS reads, we attempt to copy directly from
skbuffs into the pagecache pages. As we cannot do XDR decoding in the
soft interrupts, we attempt to estimate the size of the RPC header (+
attributes,...) that will precede the actual data that goes in the
pagecache. If we get the estimate wrong, the XDR decode routines
perform a realignment of the data into the pagecache.

In the existing code, we do a multi-page kmap() from the xdr_buf into
an iovec array, in order to do the shift.

The following patch adds tools for doing the realigment without going
through the iovec array (and without having to do the deadlock-prone
multi-page kmap()).

It also adds the 2 helper routines xdr_read_pages()/xdr_write_pages()
which will be needed for NFSv4 reads/writes in order to add pre/post
operation GETATTR calls.

include/linux/sunrpc/xdr.h
net/sunrpc/sunrpc_syms.c
net/sunrpc/xdr.c

index 74c5260b23432fec705dc0b6cd4f1c1ec918e91d..fd871e87f7f4d8c7589ab9e64ab3e8c72fa1ffbf 100644 (file)
@@ -211,6 +211,10 @@ xdr_reserve_space(struct xdr_stream *xdr, size_t nbytes)
        return p;
 }
 
+extern void xdr_write_pages(struct xdr_stream *xdr, struct page **pages,
+               unsigned int base, unsigned int len);
+extern void xdr_read_pages(struct xdr_stream *xdr, unsigned int len);
+
 /*
  * Initialize an xdr_stream for decoding data.
  */
index f3c5407546cd5da96a3603d95f4ed42b27b1dd0d..2c06ada571bec24d48913df8e6999353fa937e7a 100644 (file)
@@ -120,6 +120,8 @@ EXPORT_SYMBOL(xdr_encode_netobj);
 EXPORT_SYMBOL(xdr_encode_pages);
 EXPORT_SYMBOL(xdr_inline_pages);
 EXPORT_SYMBOL(xdr_shift_buf);
+EXPORT_SYMBOL(xdr_write_pages);
+EXPORT_SYMBOL(xdr_read_pages);
 
 /* Debugging symbols */
 #ifdef RPC_DEBUG
index e5f05adbb7c918444a6da73787a405410a4fda32..b7ba4f10b96c6296ea43bb5acfb5357f7a7439ae 100644 (file)
@@ -141,7 +141,6 @@ xdr_inline_pages(struct xdr_buf *xdr, unsigned int offset,
        xdr->len += len;
 }
 
-
 /*
  * Realign the iovec if the server missed out some reply elements
  * (such as post-op attributes,...)
@@ -318,13 +317,308 @@ copy_tail:
                copy_actor(desc, (char *)xdr->tail[0].iov_base + base, len);
 }
 
+
+/*
+ * Helper routines for doing 'memmove' like operations on a struct xdr_buf
+ *
+ * _shift_data_right_pages
+ * @pages: vector of pages containing both the source and dest memory area.
+ * @pgto_base: page vector address of destination
+ * @pgfrom_base: page vector address of source
+ * @len: number of bytes to copy
+ *
+ * Note: the addresses pgto_base and pgfrom_base are both calculated in
+ *       the same way:
+ *            if a memory area starts at byte 'base' in page 'pages[i]',
+ *            then its address is given as (i << PAGE_CACHE_SHIFT) + base
+ * Also note: pgfrom_base must be < pgto_base, but the memory areas
+ *     they point to may overlap.
+ */
+static void
+_shift_data_right_pages(struct page **pages, size_t pgto_base,
+               size_t pgfrom_base, size_t len)
+{
+       struct page **pgfrom, **pgto;
+       char *vfrom, *vto;
+       size_t copy;
+
+       BUG_ON(pgto_base <= pgfrom_base);
+
+       pgto_base += len;
+       pgfrom_base += len;
+
+       pgto = pages + (pgto_base >> PAGE_CACHE_SHIFT);
+       pgfrom = pages + (pgfrom_base >> PAGE_CACHE_SHIFT);
+
+       pgto_base &= ~PAGE_CACHE_MASK;
+       pgfrom_base &= ~PAGE_CACHE_MASK;
+
+       do {
+               /* Are any pointers crossing a page boundary? */
+               if (pgto_base == 0) {
+                       pgto_base = PAGE_CACHE_SIZE;
+                       pgto--;
+               }
+               if (pgfrom_base == 0) {
+                       pgfrom_base = PAGE_CACHE_SIZE;
+                       pgfrom--;
+               }
+
+               copy = len;
+               if (copy > pgto_base)
+                       copy = pgto_base;
+               if (copy > pgfrom_base)
+                       copy = pgfrom_base;
+               pgto_base -= copy;
+               pgfrom_base -= copy;
+
+               vto = kmap_atomic(*pgto, KM_USER0);
+               vfrom = kmap_atomic(*pgfrom, KM_USER1);
+               memmove(vto + pgto_base, vfrom + pgfrom_base, copy);
+               kunmap_atomic(vfrom, KM_USER1);
+               kunmap_atomic(vto, KM_USER0);
+
+       } while ((len -= copy) != 0);
+}
+
+/*
+ * _copy_to_pages
+ * @pages: array of pages
+ * @pgbase: page vector address of destination
+ * @p: pointer to source data
+ * @len: length
+ *
+ * Copies data from an arbitrary memory location into an array of pages
+ * The copy is assumed to be non-overlapping.
+ */
+static void
+_copy_to_pages(struct page **pages, size_t pgbase, const char *p, size_t len)
+{
+       struct page **pgto;
+       char *vto;
+       size_t copy;
+
+       pgto = pages + (pgbase >> PAGE_CACHE_SHIFT);
+       pgbase &= ~PAGE_CACHE_MASK;
+
+       do {
+               copy = PAGE_CACHE_SIZE - pgbase;
+               if (copy > len)
+                       copy = len;
+
+               vto = kmap_atomic(*pgto, KM_USER0);
+               memcpy(vto + pgbase, p, copy);
+               kunmap_atomic(vto, KM_USER0);
+
+               pgbase += copy;
+               if (pgbase == PAGE_CACHE_SIZE) {
+                       pgbase = 0;
+                       pgto++;
+               }
+               p += copy;
+
+       } while ((len -= copy) != 0);
+}
+
+/*
+ * _copy_from_pages
+ * @p: pointer to destination
+ * @pages: array of pages
+ * @pgbase: offset of source data
+ * @len: length
+ *
+ * Copies data into an arbitrary memory location from an array of pages
+ * The copy is assumed to be non-overlapping.
+ */
+static void
+_copy_from_pages(char *p, struct page **pages, size_t pgbase, size_t len)
+{
+       struct page **pgfrom;
+       char *vfrom;
+       size_t copy;
+
+       pgfrom = pages + (pgbase >> PAGE_CACHE_SHIFT);
+       pgbase &= ~PAGE_CACHE_MASK;
+
+       do {
+               copy = PAGE_CACHE_SIZE - pgbase;
+               if (copy > len)
+                       copy = len;
+
+               vfrom = kmap_atomic(*pgfrom, KM_USER0);
+               memcpy(p, vfrom + pgbase, copy);
+               kunmap_atomic(vfrom, KM_USER0);
+
+               pgbase += copy;
+               if (pgbase == PAGE_CACHE_SIZE) {
+                       pgbase = 0;
+                       pgfrom++;
+               }
+               p += copy;
+
+       } while ((len -= copy) != 0);
+}
+
+/*
+ * xdr_shrink_bufhead
+ * @buf: xdr_buf
+ * @len: bytes to remove from buf->head[0]
+ *
+ * Shrinks XDR buffer's header iovec buf->head[0] by 
+ * 'len' bytes. The extra data is not lost, but is instead
+ * moved into the inlined pages and/or the tail.
+ */
 void
-xdr_shift_buf(struct xdr_buf *xdr, size_t len)
+xdr_shrink_bufhead(struct xdr_buf *buf, size_t len)
 {
-       struct iovec iov[MAX_IOVEC];
-       unsigned int nr;
+       struct iovec *head, *tail;
+       size_t copy, offs;
+       unsigned int pglen = buf->page_len;
+
+       tail = buf->tail;
+       head = buf->head;
+       BUG_ON (len > head->iov_len);
+
+       /* Shift the tail first */
+       if (tail->iov_len != 0) {
+               if (tail->iov_len > len) {
+                       copy = tail->iov_len - len;
+                       memmove((char *)tail->iov_base + len,
+                                       tail->iov_base, copy);
+               }
+               /* Copy from the inlined pages into the tail */
+               copy = len;
+               if (copy > pglen)
+                       copy = pglen;
+               offs = len - copy;
+               if (offs >= tail->iov_len)
+                       copy = 0;
+               else if (copy > tail->iov_len - offs)
+                       copy = tail->iov_len - offs;
+               if (copy != 0)
+                       _copy_from_pages((char *)tail->iov_base + offs,
+                                       buf->pages,
+                                       buf->page_base + pglen + offs - len,
+                                       copy);
+               /* Do we also need to copy data from the head into the tail ? */
+               if (len > pglen) {
+                       offs = copy = len - pglen;
+                       if (copy > tail->iov_len)
+                               copy = tail->iov_len;
+                       memcpy(tail->iov_base,
+                                       (char *)head->iov_base +
+                                       head->iov_len - offs,
+                                       copy);
+               }
+       }
+       /* Now handle pages */
+       if (pglen != 0) {
+               if (pglen > len)
+                       _shift_data_right_pages(buf->pages,
+                                       buf->page_base + len,
+                                       buf->page_base,
+                                       pglen - len);
+               copy = len;
+               if (len > pglen)
+                       copy = pglen;
+               _copy_to_pages(buf->pages, buf->page_base,
+                               (char *)head->iov_base + head->iov_len - len,
+                               copy);
+       }
+       head->iov_len -= len;
+       buf->len -= len;
+}
+
+/*
+ * xdr_shrink_pagelen
+ * @buf: xdr_buf
+ * @len: bytes to remove from buf->pages
+ *
+ * Shrinks XDR buffer's page array buf->pages by 
+ * 'len' bytes. The extra data is not lost, but is instead
+ * moved into the tail.
+ */
+void
+xdr_shrink_pagelen(struct xdr_buf *buf, size_t len)
+{
+       struct iovec *tail;
+       size_t copy;
+       char *p;
+       unsigned int pglen = buf->page_len;
+
+       tail = buf->tail;
+       BUG_ON (len > pglen);
+
+       /* Shift the tail first */
+       if (tail->iov_len != 0) {
+               p = (char *)tail->iov_base + len;
+               if (tail->iov_len > len) {
+                       copy = tail->iov_len - len;
+                       memmove(p, tail->iov_base, copy);
+               } else
+                       buf->len -= len;
+               /* Copy from the inlined pages into the tail */
+               copy = len;
+               if (copy > tail->iov_len)
+                       copy = tail->iov_len;
+               _copy_from_pages((char *)tail->iov_base,
+                               buf->pages, buf->page_base + pglen - len,
+                               copy);
+       }
+       buf->page_len -= len;
+       buf->len -= len;
+}
 
-       nr = xdr_kmap(iov, xdr, 0);
-       xdr_shift_iovec(iov, nr, len);
-       xdr_kunmap(xdr, 0);
+void
+xdr_shift_buf(struct xdr_buf *buf, size_t len)
+{
+       xdr_shrink_bufhead(buf, len);
+}
+
+void
+xdr_write_pages(struct xdr_stream *xdr, struct page **pages, unsigned int base,
+                unsigned int len)
+{
+       struct xdr_buf *buf = xdr->buf;
+       struct iovec *iov = buf->tail;
+       buf->pages = pages;
+       buf->page_base = base;
+       buf->page_len = len;
+
+       iov->iov_base = (char *)xdr->p;
+       iov->iov_len  = 0;
+       xdr->iov = iov;
+
+       if (len & 3) {
+               unsigned int pad = 4 - (len & 3);
+
+               BUG_ON(xdr->p >= xdr->end);
+               iov->iov_base = (char *)xdr->p + (len & 3);
+               iov->iov_len  += pad;
+               len += pad;
+               *xdr->p++ = 0;
+       }
+       buf->len += len;
+}
+
+void
+xdr_read_pages(struct xdr_stream *xdr, unsigned int len)
+{
+       struct xdr_buf *buf = xdr->buf;
+       struct iovec *iov;
+       ssize_t shift;
+
+       /* Realign pages to current pointer position */
+       iov  = buf->head;
+       shift = iov->iov_len + (char *)iov->iov_base - (char *)xdr->p;
+       if (shift > 0)
+               xdr_shrink_bufhead(buf, shift);
+
+       /* Truncate page data and move it into the tail */
+       len = XDR_QUADLEN(len) << 2;
+       if (buf->page_len > len)
+               xdr_shrink_pagelen(buf, buf->page_len - len);
+       xdr->iov = iov = buf->tail;
+       xdr->p = (uint32_t *)iov->iov_base;
+       xdr->end = (uint32_t *)((char *)iov->iov_base + iov->iov_len);
 }