Linux-5.18: replace readpages with readahead 53/14953/13
authorCheyenne Wills <cwills@sinenomine.net>
Mon, 27 Jun 2022 14:38:25 +0000 (08:38 -0600)
committerBenjamin Kaduk <kaduk@mit.edu>
Fri, 1 Jul 2022 02:30:43 +0000 (22:30 -0400)
The linux 5.18 commit 'fs: Remove ->readpages address space
operation' (704528d8) removes the address_space_operations operation
"readpages", which is replaced with the "readahead" operation
that was introduced with the 5.8 commit 'mm: add readahead address
space operation' (8151b4c8).

When readahead is called, the pages in 'rac' have already been added to
the lru caches and are locked. For each page that we get from the 'rac'
(i.e. from 'readahead_page(rac)'), we must unlock and put_page the page;
if we successfully populated the page with data, we also set
PageUpToDate.  If we don't process all the pages in 'rac', the caller
will handle cleaning up any remaining pages; we don't need to unlock/put
them or touch them at all.
  (See Linux Documentation/filesystems/vfs.rst)

Add an autoconf test to detect the presence of 'readahead' in the
address_space_operations structure.

For the implementation of readahead (which is contained in Linux's
osi_vnodeops.c):

Add new functions 'afs_linux_bypass_readahead' and 'afs_linux_readahead'
as replacements for 'afs_bypass_readpages' and 'afs_linux_readpages'
when the linux kernel supports the readahead operation.

Notes:
  In afs_linux_bypass_readahead, the pages are already locked and are
  already in the page cache, we just need to place the page into the
  iovecp.  The page's refcount will be decremented and will be unlocked
  when processing the read request.

  In afs_linux_readahead, the lrupages is needed in case a page is added
  to the cachefp's mapping in afs_linux_read_cache (which also handles
  unlocking the page).

  In afs_linux_readahead, if there is no tdc, we must still unlock the
  page otherwise the read process will wait on that page.

Change-Id: I6960a2fc14df85869c373f3e3afbf3ee5eb7228f
Reviewed-on: https://gerrit.openafs.org/14953
Tested-by: BuildBot <buildbot@rampaginggeek.com>
Reviewed-by: Andrew Deason <adeason@sinenomine.net>
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>

src/afs/LINUX/osi_vnodeops.c
src/cf/linux-kernel-struct.m4

index 6bf0019..5838205 100644 (file)
@@ -2532,6 +2532,93 @@ afs_linux_prefetch(struct file *fp, struct page *pp)
 
 }
 
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD)
+/*
+ * Bypass the cache while performing a readahead.
+ * See the comments for afs_linux_readahead for the semantics
+ * for 'rac'.
+ */
+static void
+afs_linux_bypass_readahead(struct readahead_control *rac)
+{
+    struct file *fp = rac->file;
+    unsigned num_pages = readahead_count(rac);
+    afs_int32 page_ix;
+    afs_offs_t offset;
+    struct iovec* iovecp;
+    struct nocache_read_request *ancr;
+    struct page *pp;
+    afs_int32 code = 0;
+
+    cred_t *credp;
+    struct inode *ip = FILE_INODE(fp);
+    struct vcache *avc = VTOAFS(ip);
+    afs_int32 base_index = 0;
+    afs_int32 page_count = 0;
+    afs_int32 isize;
+
+    ancr = afs_alloc_ncr(num_pages);
+    if (ancr == NULL)
+       goto done;
+
+    iovecp = ancr->auio->uio_iov;
+
+    for (page_ix = 0; page_ix < num_pages; ++page_ix) {
+       pp = readahead_page(rac);
+       if (pp == NULL)
+           break;
+
+       isize = (i_size_read(fp->f_mapping->host) - 1) >> PAGE_SHIFT;
+       if (pp->index > isize) {
+           if (PageLocked(pp))
+               unlock_page(pp);
+           put_page(pp);
+           continue;
+       }
+
+       if (page_ix == 0) {
+           offset = page_offset(pp);
+           ancr->offset = ancr->auio->uio_offset = offset;
+           base_index = pp->index;
+       }
+       iovecp[page_ix].iov_len = PAGE_SIZE;
+       if (base_index != pp->index) {
+           if (PageLocked(pp))
+                unlock_page(pp);
+           put_page(pp);
+           iovecp[page_ix].iov_base = NULL;
+           base_index++;
+           ancr->length -= PAGE_SIZE;
+           continue;
+       }
+       base_index++;
+       page_count++;
+       /* save the page for background map */
+       iovecp[page_ix].iov_base = pp;
+    }
+
+    /* If there were useful pages in the page list, schedule
+     * the read */
+    if (page_count > 0) {
+       credp = crref();
+       /* The background thread frees the ancr */
+       code = afs_ReadNoCache(avc, ancr, credp);
+       crfree(credp);
+    } else {
+       /* If there is nothing for the background thread to handle,
+        * it won't be freeing the things that we never gave it */
+       afs_free_ncr(&ancr);
+    }
+    /* we do not flush, release, or unmap pages--that will be
+     * done for us by the background thread as each page comes in
+     * from the fileserver */
+
+ done:
+    /* The vfs layer will unlock/put any of the pages in the rac that were not
+     * processed */
+    return;
+}
+#else /* STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD */
 static int
 afs_linux_bypass_readpages(struct file *fp, struct address_space *mapping,
                           struct list_head *page_list, unsigned num_pages)
@@ -2628,7 +2715,7 @@ afs_linux_bypass_readpages(struct file *fp, struct address_space *mapping,
      * from the fileserver */
     return afs_convert_code(code);
 }
-
+#endif /* STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD */
 
 static int
 afs_linux_bypass_readpage(struct file *fp, struct page *pp)
@@ -2809,11 +2896,104 @@ get_dcache_readahead(struct dcache **adc, struct file **acacheFp,
     return code;
 }
 
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD)
+/*
+ * Readahead reads a number of pages for a particular file. We use
+ * this to optimise the reading, by limiting the number of times upon which
+ * we have to lookup, lock and open vcaches and dcaches.
+ *
+ * Upon return, the vfs layer handles unlocking and putting any pages in the
+ * rac that we did not process here.
+ *
+ * Note: any errors detected during readahead are ignored at this stage by the
+ * vfs. We just need to unlock/put the page and return.  Errors will be detected
+ * later in the vfs processing.
+ */
+static void
+afs_linux_readahead(struct readahead_control *rac)
+{
+    struct page *page;
+    struct address_space *mapping = rac->mapping;
+    struct inode *inode = mapping->host;
+    struct vcache *avc = VTOAFS(inode);
+    struct dcache *tdc;
+    struct file *cacheFp = NULL;
+    int code;
+    loff_t offset;
+    struct afs_lru_pages lrupages;
+    struct afs_pagecopy_task *task;
+
+    if (afs_linux_bypass_check(inode)) {
+       afs_linux_bypass_readahead(rac);
+       return;
+    }
+    if (cacheDiskType == AFS_FCACHE_TYPE_MEM)
+       return;
+
+    /* No readpage (ex: tmpfs) , skip */
+    if (cachefs_noreadpage)
+       return;
+
+    AFS_GLOCK();
+    code = afs_linux_VerifyVCache(avc, NULL);
+    if (code != 0) {
+       AFS_GUNLOCK();
+       return;
+    }
+
+    ObtainWriteLock(&avc->lock, 912);
+    AFS_GUNLOCK();
+
+    task = afs_pagecopy_init_task();
+
+    tdc = NULL;
+
+    afs_lru_cache_init(&lrupages);
+
+    while ((page = readahead_page(rac)) != NULL) {
+       offset = page_offset(page);
+
+       code = get_dcache_readahead(&tdc, &cacheFp, avc, offset);
+       if (code != 0) {
+           if (PageLocked(page)) {
+               unlock_page(page);
+           }
+           put_page(page);
+           goto done;
+       }
+
+       if (tdc != NULL) {
+           /* afs_linux_read_cache will unlock the page */
+           afs_linux_read_cache(cacheFp, page, tdc->f.chunk, &lrupages, task);
+       } else if (PageLocked(page)) {
+           unlock_page(page);
+       }
+       put_page(page);
+    }
+
+ done:
+    afs_lru_cache_finalize(&lrupages);
+
+    if (cacheFp != NULL)
+       filp_close(cacheFp, NULL);
+
+    afs_pagecopy_put_task(task);
+
+    AFS_GLOCK();
+    if (tdc != NULL) {
+       ReleaseReadLock(&tdc->lock);
+       afs_PutDCache(tdc);
+    }
+
+    ReleaseWriteLock(&avc->lock);
+    AFS_GUNLOCK();
+    return;
+}
+#else /* STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD */
 /* Readpages reads a number of pages for a particular file. We use
  * this to optimise the reading, by limiting the number of times upon which
  * we have to lookup, lock and open vcaches and dcaches
  */
-
 static int
 afs_linux_readpages(struct file *fp, struct address_space *mapping,
                    struct list_head *page_list, unsigned int num_pages)
@@ -2890,6 +3070,7 @@ out:
     AFS_GUNLOCK();
     return 0;
 }
+#endif /* STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD */
 
 /* Prepare an AFS vcache for writeback. Should be called with the vcache
  * locked */
@@ -3328,7 +3509,11 @@ static struct inode_operations afs_file_iops = {
 
 static struct address_space_operations afs_file_aops = {
   .readpage =          afs_linux_readpage,
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD)
+  .readahead =         afs_linux_readahead,
+#else
   .readpages =                 afs_linux_readpages,
+#endif
   .writepage =         afs_linux_writepage,
 #if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_DIRTY_FOLIO)
   .dirty_folio =       block_dirty_folio,
index 2d8cee6..597289b 100644 (file)
@@ -5,6 +5,8 @@ AC_CHECK_LINUX_STRUCT([address_space_operations],
                       [write_begin], [fs.h])
 dnl linux 5.18 replaced set_page_dirty with dirty_folio
 AC_CHECK_LINUX_STRUCT([address_space_operations], [dirty_folio], [fs.h])
+dnl linux 5.18 replaced readpages with readahead (introduced in 5.8)
+AC_CHECK_LINUX_STRUCT([address_space_operations], [readahead], [fs.h])
 AC_CHECK_LINUX_STRUCT([backing_dev_info], [name],
                       [backing-dev.h])
 AC_CHECK_LINUX_STRUCT([cred], [session_keyring], [cred.h])