On the client, a write request consists of (from nfs4_xdr_enc_write): 1. the rpc header, compound header, a putfh, and a getattr; 2. a whole bunch of write data; and, finally 3. maybe a little padding and a final getattr Obviously, 1 and 3 are short, 2 is long. It's more efficient to pass around references to the data in the page cache than to copy around the actual data. Read responses are similar: a little bit at the beginning, then a bunch of page data, then a bit at the end. So the xdr_buf captures this common case with: head: an iovec with the initial stuff pages: an array of pages with data tail: another iovec with whatever's left at the end. In more detail: head[0].iov_len: the length of the head tail[0].iov_len: the length of the tail pages: an array of pointers to struct page page_base: the offset, in bytes, counting from the beginning of the first page in pages, to the start of the actual data. Note that page_base > PAGE_CACHE_SIZE is actually possible, in which case some initial pages in the array may be completely unused. page_len: the length of the page data, in bytes. So the page data ends at page_base + page_len bytes from the start of the first page. len: the *total* length of the xdr'd data; so len = head[0].iov_len + tail[0].iov_len + page_len However, I think I've noticed on both the client and server that len is not always kept up to date, so I'd be careful about depending on that last equation actually being correct at any point in the processing. When setting up reads on the client, it's OK to overestimate the amount of space needed in the head iovec; it's *not* OK to underestimate it. To put it another way, there exists a facility to shift the page data forward, taking some out of the head, if necessary, but there's no way to shift the page data back. xdr_read_pages does the following: calculate "shift", the number of bytes left in the head iovec after the read pointer p. If shift > 0, call xdr_shrink_bufhead to shift all the page data forward. (note if shift < 0, we're in deep doo-doo; but p should never have been allowed to fall off the end of the head iovec anyway) at this point we can assume that p is at the end of the head iovec. round len up to the nearest multiple of 4. If there's more than len bytes of page data, then shift the remaining data into the tail iovec using xdr_shrink_pagelen.