17e810229c
This patch adds POSIX_FADV_NOREUSE to vma_has_recency() so that the LRU algorithm can ignore access to mapped files marked by this flag. The advantages of POSIX_FADV_NOREUSE are: 1. Unlike MADV_SEQUENTIAL and MADV_RANDOM, it does not alter the default readahead behavior. 2. Unlike MADV_SEQUENTIAL and MADV_RANDOM, it does not split VMAs and therefore does not take mmap_lock. 3. Unlike MADV_COLD, setting it has a negligible cost, regardless of how many pages it affects. Its limitations are: 1. Like POSIX_FADV_RANDOM and POSIX_FADV_SEQUENTIAL, it currently does not support range. IOW, its scope is the entire file. 2. It currently does not ignore access through file descriptors. Specifically, for the active/inactive LRU, given a file page shared by two users and one of them having set POSIX_FADV_NOREUSE on the file, this page will be activated upon the second user accessing it. This corner case can be covered by checking POSIX_FADV_NOREUSE before calling folio_mark_accessed() on the read path. But it is considered not worth the effort. There have been a few attempts to support POSIX_FADV_NOREUSE, e.g., [1]. This time the goal is to fill a niche: a few desktop applications, e.g., large file transferring and video encoding/decoding, want fast file streaming with mmap() rather than direct IO. Among those applications, an SVT-AV1 regression was reported when running with MGLRU [2]. The following test can reproduce that regression. kb=$(awk '/MemTotal/ { print $2 }' /proc/meminfo) kb=$((kb - 8*1024*1024)) modprobe brd rd_nr=1 rd_size=$kb dd if=/dev/zero of=/dev/ram0 bs=1M mkfs.ext4 /dev/ram0 mount /dev/ram0 /mnt/ swapoff -a fallocate -l 8G /mnt/swapfile mkswap /mnt/swapfile swapon /mnt/swapfile wget http://ultravideo.cs.tut.fi/video/Bosphorus_3840x2160_120fps_420_8bit_YUV_Y4M.7z 7z e -o/mnt/ Bosphorus_3840x2160_120fps_420_8bit_YUV_Y4M.7z SvtAv1EncApp --preset 12 -w 3840 -h 2160 \ -i /mnt/Bosphorus_3840x2160.y4m For MGLRU, the following change showed a [9-11]% increase in FPS, which makes it on par with the active/inactive LRU. patch Source/App/EncApp/EbAppMain.c <<EOF 31a32 > #include <fcntl.h> 35d35 < #include <fcntl.h> /* _O_BINARY */ 117a118 > posix_fadvise(config->mmap.fd, 0, 0, POSIX_FADV_NOREUSE); EOF [1] https://lore.kernel.org/r/1308923350-7932-1-git-send-email-andrea@betterlinux.com/ [2] https://openbenchmarking.org/result/2209259-PTS-MGLRU8GB57 Link: https://lkml.kernel.org/r/20221230215252.2628425-2-yuzhao@google.com Signed-off-by: Yu Zhao <yuzhao@google.com> Cc: Alexander Viro <viro@zeniv.linux.org.uk> Cc: Andrea Righi <andrea.righi@canonical.com> Cc: Johannes Weiner <hannes@cmpxchg.org> Cc: Michael Larabel <Michael@MichaelLarabel.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
233 lines
5.6 KiB
C
233 lines
5.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* mm/fadvise.c
|
|
*
|
|
* Copyright (C) 2002, Linus Torvalds
|
|
*
|
|
* 11Jan2003 Andrew Morton
|
|
* Initial version.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/file.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/backing-dev.h>
|
|
#include <linux/pagevec.h>
|
|
#include <linux/fadvise.h>
|
|
#include <linux/writeback.h>
|
|
#include <linux/syscalls.h>
|
|
#include <linux/swap.h>
|
|
|
|
#include <asm/unistd.h>
|
|
|
|
#include "internal.h"
|
|
|
|
/*
|
|
* POSIX_FADV_WILLNEED could set PG_Referenced, and POSIX_FADV_NOREUSE could
|
|
* deactivate the pages and clear PG_Referenced.
|
|
*/
|
|
|
|
int generic_fadvise(struct file *file, loff_t offset, loff_t len, int advice)
|
|
{
|
|
struct inode *inode;
|
|
struct address_space *mapping;
|
|
struct backing_dev_info *bdi;
|
|
loff_t endbyte; /* inclusive */
|
|
pgoff_t start_index;
|
|
pgoff_t end_index;
|
|
unsigned long nrpages;
|
|
|
|
inode = file_inode(file);
|
|
if (S_ISFIFO(inode->i_mode))
|
|
return -ESPIPE;
|
|
|
|
mapping = file->f_mapping;
|
|
if (!mapping || len < 0)
|
|
return -EINVAL;
|
|
|
|
bdi = inode_to_bdi(mapping->host);
|
|
|
|
if (IS_DAX(inode) || (bdi == &noop_backing_dev_info)) {
|
|
switch (advice) {
|
|
case POSIX_FADV_NORMAL:
|
|
case POSIX_FADV_RANDOM:
|
|
case POSIX_FADV_SEQUENTIAL:
|
|
case POSIX_FADV_WILLNEED:
|
|
case POSIX_FADV_NOREUSE:
|
|
case POSIX_FADV_DONTNEED:
|
|
/* no bad return value, but ignore advice */
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Careful about overflows. Len == 0 means "as much as possible". Use
|
|
* unsigned math because signed overflows are undefined and UBSan
|
|
* complains.
|
|
*/
|
|
endbyte = (u64)offset + (u64)len;
|
|
if (!len || endbyte < len)
|
|
endbyte = LLONG_MAX;
|
|
else
|
|
endbyte--; /* inclusive */
|
|
|
|
switch (advice) {
|
|
case POSIX_FADV_NORMAL:
|
|
file->f_ra.ra_pages = bdi->ra_pages;
|
|
spin_lock(&file->f_lock);
|
|
file->f_mode &= ~(FMODE_RANDOM | FMODE_NOREUSE);
|
|
spin_unlock(&file->f_lock);
|
|
break;
|
|
case POSIX_FADV_RANDOM:
|
|
spin_lock(&file->f_lock);
|
|
file->f_mode |= FMODE_RANDOM;
|
|
spin_unlock(&file->f_lock);
|
|
break;
|
|
case POSIX_FADV_SEQUENTIAL:
|
|
file->f_ra.ra_pages = bdi->ra_pages * 2;
|
|
spin_lock(&file->f_lock);
|
|
file->f_mode &= ~FMODE_RANDOM;
|
|
spin_unlock(&file->f_lock);
|
|
break;
|
|
case POSIX_FADV_WILLNEED:
|
|
/* First and last PARTIAL page! */
|
|
start_index = offset >> PAGE_SHIFT;
|
|
end_index = endbyte >> PAGE_SHIFT;
|
|
|
|
/* Careful about overflow on the "+1" */
|
|
nrpages = end_index - start_index + 1;
|
|
if (!nrpages)
|
|
nrpages = ~0UL;
|
|
|
|
force_page_cache_readahead(mapping, file, start_index, nrpages);
|
|
break;
|
|
case POSIX_FADV_NOREUSE:
|
|
spin_lock(&file->f_lock);
|
|
file->f_mode |= FMODE_NOREUSE;
|
|
spin_unlock(&file->f_lock);
|
|
break;
|
|
case POSIX_FADV_DONTNEED:
|
|
__filemap_fdatawrite_range(mapping, offset, endbyte,
|
|
WB_SYNC_NONE);
|
|
|
|
/*
|
|
* First and last FULL page! Partial pages are deliberately
|
|
* preserved on the expectation that it is better to preserve
|
|
* needed memory than to discard unneeded memory.
|
|
*/
|
|
start_index = (offset+(PAGE_SIZE-1)) >> PAGE_SHIFT;
|
|
end_index = (endbyte >> PAGE_SHIFT);
|
|
/*
|
|
* The page at end_index will be inclusively discarded according
|
|
* by invalidate_mapping_pages(), so subtracting 1 from
|
|
* end_index means we will skip the last page. But if endbyte
|
|
* is page aligned or is at the end of file, we should not skip
|
|
* that page - discarding the last page is safe enough.
|
|
*/
|
|
if ((endbyte & ~PAGE_MASK) != ~PAGE_MASK &&
|
|
endbyte != inode->i_size - 1) {
|
|
/* First page is tricky as 0 - 1 = -1, but pgoff_t
|
|
* is unsigned, so the end_index >= start_index
|
|
* check below would be true and we'll discard the whole
|
|
* file cache which is not what was asked.
|
|
*/
|
|
if (end_index == 0)
|
|
break;
|
|
|
|
end_index--;
|
|
}
|
|
|
|
if (end_index >= start_index) {
|
|
unsigned long nr_pagevec = 0;
|
|
|
|
/*
|
|
* It's common to FADV_DONTNEED right after
|
|
* the read or write that instantiates the
|
|
* pages, in which case there will be some
|
|
* sitting on the local LRU cache. Try to
|
|
* avoid the expensive remote drain and the
|
|
* second cache tree walk below by flushing
|
|
* them out right away.
|
|
*/
|
|
lru_add_drain();
|
|
|
|
invalidate_mapping_pagevec(mapping,
|
|
start_index, end_index,
|
|
&nr_pagevec);
|
|
|
|
/*
|
|
* If fewer pages were invalidated than expected then
|
|
* it is possible that some of the pages were on
|
|
* a per-cpu pagevec for a remote CPU. Drain all
|
|
* pagevecs and try again.
|
|
*/
|
|
if (nr_pagevec) {
|
|
lru_add_drain_all();
|
|
invalidate_mapping_pages(mapping, start_index,
|
|
end_index);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(generic_fadvise);
|
|
|
|
int vfs_fadvise(struct file *file, loff_t offset, loff_t len, int advice)
|
|
{
|
|
if (file->f_op->fadvise)
|
|
return file->f_op->fadvise(file, offset, len, advice);
|
|
|
|
return generic_fadvise(file, offset, len, advice);
|
|
}
|
|
EXPORT_SYMBOL(vfs_fadvise);
|
|
|
|
#ifdef CONFIG_ADVISE_SYSCALLS
|
|
|
|
int ksys_fadvise64_64(int fd, loff_t offset, loff_t len, int advice)
|
|
{
|
|
struct fd f = fdget(fd);
|
|
int ret;
|
|
|
|
if (!f.file)
|
|
return -EBADF;
|
|
|
|
ret = vfs_fadvise(f.file, offset, len, advice);
|
|
|
|
fdput(f);
|
|
return ret;
|
|
}
|
|
|
|
SYSCALL_DEFINE4(fadvise64_64, int, fd, loff_t, offset, loff_t, len, int, advice)
|
|
{
|
|
return ksys_fadvise64_64(fd, offset, len, advice);
|
|
}
|
|
|
|
#ifdef __ARCH_WANT_SYS_FADVISE64
|
|
|
|
SYSCALL_DEFINE4(fadvise64, int, fd, loff_t, offset, size_t, len, int, advice)
|
|
{
|
|
return ksys_fadvise64_64(fd, offset, len, advice);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(CONFIG_COMPAT) && defined(__ARCH_WANT_COMPAT_FADVISE64_64)
|
|
|
|
COMPAT_SYSCALL_DEFINE6(fadvise64_64, int, fd, compat_arg_u64_dual(offset),
|
|
compat_arg_u64_dual(len), int, advice)
|
|
{
|
|
return ksys_fadvise64_64(fd, compat_arg_u64_glue(offset),
|
|
compat_arg_u64_glue(len), advice);
|
|
}
|
|
|
|
#endif
|
|
#endif
|