Reads and writes are pretty similar in some ways probably actually: there's a proc_read_setup(struct nfs_read_data *). When the operation completes, an nfs{?|3|4}_read_done is called, which calls.... nfs_readpage_result(struct rpc_task *): loops over singly-linked list of nfs_pages data->pages, does some magic to tell the mm what we've found out about these pages (memclear_highpage_flush, SetPageUptodate, unlock_page, etc.), does magic to release this nfs_page. Now on 2.6.7-CITI_NFS4_ALL-8, tracing starting from nfs_readpages. nfs_readpages first gets an nfs_open_context, which is where we get the rpc credential and nfs4 state from. Then it does read_cache_pages(): Add to radix tree of pages associated with the vma. call the passed-in callback (readpage_async_filler in this case) adds the page to the "inactive list" (what's that?) (subtlety: actually it batches the addition of pages to the list using __pagevec_lru_add()) In case of an error return from the callback, just releases the rest of the pages? readpage_async_filler(): nfs_wb_page(): calls nfs_sync_inode, which in our case just does nfs_flush_inode() and nfs_commit_inode() until the latter returns 0. nfs_flush_inode(): nfs_scan_dirty():