From: Linus Torvalds Date: Fri, 5 Aug 2022 23:32:45 +0000 (-0700) Subject: Merge tag 'mm-stable-2022-08-03' of git://git.kernel.org/pub/scm/linux/kernel/git... X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=6614a3c3164a5df2b54abb0b3559f51041cf705b;p=linux.git Merge tag 'mm-stable-2022-08-03' of git://git./linux/kernel/git/akpm/mm Pull MM updates from Andrew Morton: "Most of the MM queue. A few things are still pending. Liam's maple tree rework didn't make it. This has resulted in a few other minor patch series being held over for next time. Multi-gen LRU still isn't merged as we were waiting for mapletree to stabilize. The current plan is to merge MGLRU into -mm soon and to later reintroduce mapletree, with a view to hopefully getting both into 6.1-rc1. Summary: - The usual batches of cleanups from Baoquan He, Muchun Song, Miaohe Lin, Yang Shi, Anshuman Khandual and Mike Rapoport - Some kmemleak fixes from Patrick Wang and Waiman Long - DAMON updates from SeongJae Park - memcg debug/visibility work from Roman Gushchin - vmalloc speedup from Uladzislau Rezki - more folio conversion work from Matthew Wilcox - enhancements for coherent device memory mapping from Alex Sierra - addition of shared pages tracking and CoW support for fsdax, from Shiyang Ruan - hugetlb optimizations from Mike Kravetz - Mel Gorman has contributed some pagealloc changes to improve latency and realtime behaviour. - mprotect soft-dirty checking has been improved by Peter Xu - Many other singleton patches all over the place" [ XFS merge from hell as per Darrick Wong in https://lore.kernel.org/all/YshKnxb4VwXycPO8@magnolia/ ] * tag 'mm-stable-2022-08-03' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (282 commits) tools/testing/selftests/vm/hmm-tests.c: fix build mm: Kconfig: fix typo mm: memory-failure: convert to pr_fmt() mm: use is_zone_movable_page() helper hugetlbfs: fix inaccurate comment in hugetlbfs_statfs() hugetlbfs: cleanup some comments in inode.c hugetlbfs: remove unneeded header file hugetlbfs: remove unneeded hugetlbfs_ops forward declaration hugetlbfs: use helper macro SZ_1{K,M} mm: cleanup is_highmem() mm/hmm: add a test for cross device private faults selftests: add soft-dirty into run_vmtests.sh selftests: soft-dirty: add test for mprotect mm/mprotect: fix soft-dirty check in can_change_pte_writable() mm: memcontrol: fix potential oom_lock recursion deadlock mm/gup.c: fix formatting in check_and_migrate_movable_page() xfs: fail dax mount if reflink is enabled on a partition mm/memcontrol.c: remove the redundant updating of stats_flush_threshold userfaultfd: don't fail on unrecognized features hugetlb_cgroup: fix wrong hugetlb cgroup numa stat ... --- 6614a3c3164a5df2b54abb0b3559f51041cf705b diff --cc Documentation/mm/overcommit-accounting.rst index 0000000000000,1addb0c374a43..a4895d6fc1c23 mode 000000,100644..100644 --- a/Documentation/mm/overcommit-accounting.rst +++ b/Documentation/mm/overcommit-accounting.rst @@@ -1,0 -1,88 +1,86 @@@ -.. _overcommit_accounting: - + ===================== + Overcommit Accounting + ===================== + + The Linux kernel supports the following overcommit handling modes + + 0 + Heuristic overcommit handling. Obvious overcommits of address + space are refused. Used for a typical system. It ensures a + seriously wild allocation fails while allowing overcommit to + reduce swap usage. root is allowed to allocate slightly more + memory in this mode. This is the default. + + 1 + Always overcommit. Appropriate for some scientific + applications. Classic example is code using sparse arrays and + just relying on the virtual memory consisting almost entirely + of zero pages. + + 2 + Don't overcommit. The total address space commit for the + system is not permitted to exceed swap + a configurable amount + (default is 50%) of physical RAM. Depending on the amount you + use, in most situations this means a process will not be + killed while accessing pages but will receive errors on memory + allocation as appropriate. + + Useful for applications that want to guarantee their memory + allocations will be available in the future without having to + initialize every page. + + The overcommit policy is set via the sysctl ``vm.overcommit_memory``. + + The overcommit amount can be set via ``vm.overcommit_ratio`` (percentage) + or ``vm.overcommit_kbytes`` (absolute value). These only have an effect + when ``vm.overcommit_memory`` is set to 2. + + The current overcommit limit and amount committed are viewable in + ``/proc/meminfo`` as CommitLimit and Committed_AS respectively. + + Gotchas + ======= + + The C language stack growth does an implicit mremap. If you want absolute + guarantees and run close to the edge you MUST mmap your stack for the + largest size you think you will need. For typical stack usage this does + not matter much but it's a corner case if you really really care + + In mode 2 the MAP_NORESERVE flag is ignored. + + + How It Works + ============ + + The overcommit is based on the following rules + + For a file backed map + | SHARED or READ-only - 0 cost (the file is the map not swap) + | PRIVATE WRITABLE - size of mapping per instance + + For an anonymous or ``/dev/zero`` map + | SHARED - size of mapping + | PRIVATE READ-only - 0 cost (but of little use) + | PRIVATE WRITABLE - size of mapping per instance + + Additional accounting + | Pages made writable copies by mmap + | shmfs memory drawn from the same pool + + Status + ====== + + * We account mmap memory mappings + * We account mprotect changes in commit + * We account mremap changes in size + * We account brk + * We account munmap + * We report the commit status in /proc + * Account and check on fork + * Review stack handling/building on exec + * SHMfs accounting + * Implement actual limit enforcement + + To Do + ===== + * Account ptrace pages (this is hard) diff --cc Documentation/mm/page_migration.rst index 0000000000000,8c5cb8147e55e..11493bad71125 mode 000000,100644..100644 --- a/Documentation/mm/page_migration.rst +++ b/Documentation/mm/page_migration.rst @@@ -1,0 -1,288 +1,195 @@@ + .. _page_migration: + + ============== + Page migration + ============== + + Page migration allows moving the physical location of pages between + nodes in a NUMA system while the process is running. This means that the + virtual addresses that the process sees do not change. However, the + system rearranges the physical location of those pages. + + Also see :ref:`Heterogeneous Memory Management (HMM) ` + for migrating pages to or from device private memory. + + The main intent of page migration is to reduce the latency of memory accesses + by moving pages near to the processor where the process accessing that memory + is running. + + Page migration allows a process to manually relocate the node on which its + pages are located through the MF_MOVE and MF_MOVE_ALL options while setting + a new memory policy via mbind(). The pages of a process can also be relocated + from another process using the sys_migrate_pages() function call. The + migrate_pages() function call takes two sets of nodes and moves pages of a + process that are located on the from nodes to the destination nodes. + Page migration functions are provided by the numactl package by Andi Kleen + (a version later than 0.9.3 is required. Get it from + https://github.com/numactl/numactl.git). numactl provides libnuma + which provides an interface similar to other NUMA functionality for page + migration. cat ``/proc//numa_maps`` allows an easy review of where the + pages of a process are located. See also the numa_maps documentation in the + proc(5) man page. + + Manual migration is useful if for example the scheduler has relocated + a process to a processor on a distant node. A batch scheduler or an + administrator may detect the situation and move the pages of the process + nearer to the new processor. The kernel itself only provides + manual page migration support. Automatic page migration may be implemented + through user space processes that move pages. A special function call + "move_pages" allows the moving of individual pages within a process. + For example, A NUMA profiler may obtain a log showing frequent off-node + accesses and may use the result to move pages to more advantageous + locations. + + Larger installations usually partition the system using cpusets into + sections of nodes. Paul Jackson has equipped cpusets with the ability to + move pages when a task is moved to another cpuset (See + :ref:`CPUSETS `). + Cpusets allow the automation of process locality. If a task is moved to + a new cpuset then also all its pages are moved with it so that the + performance of the process does not sink dramatically. Also the pages + of processes in a cpuset are moved if the allowed memory nodes of a + cpuset are changed. + + Page migration allows the preservation of the relative location of pages + within a group of nodes for all migration techniques which will preserve a + particular memory allocation pattern generated even after migrating a + process. This is necessary in order to preserve the memory latencies. + Processes will run with similar performance after migration. + + Page migration occurs in several steps. First a high level + description for those trying to use migrate_pages() from the kernel + (for userspace usage see the Andi Kleen's numactl package mentioned above) + and then a low level description of how the low level details work. + + In kernel use of migrate_pages() + ================================ + + 1. Remove pages from the LRU. + + Lists of pages to be migrated are generated by scanning over + pages and moving them into lists. This is done by + calling isolate_lru_page(). + Calling isolate_lru_page() increases the references to the page + so that it cannot vanish while the page migration occurs. + It also prevents the swapper or other scans from encountering + the page. + + 2. We need to have a function of type new_page_t that can be + passed to migrate_pages(). This function should figure out + how to allocate the correct new page given the old page. + + 3. The migrate_pages() function is called which attempts + to do the migration. It will call the function to allocate + the new page for each page that is considered for + moving. + + How migrate_pages() works + ========================= + + migrate_pages() does several passes over its list of pages. A page is moved + if all references to a page are removable at the time. The page has + already been removed from the LRU via isolate_lru_page() and the refcount + is increased so that the page cannot be freed while page migration occurs. + + Steps: + + 1. Lock the page to be migrated. + + 2. Ensure that writeback is complete. + + 3. Lock the new page that we want to move to. It is locked so that accesses to + this (not yet up-to-date) page immediately block while the move is in progress. + + 4. All the page table references to the page are converted to migration + entries. This decreases the mapcount of a page. If the resulting + mapcount is not zero then we do not migrate the page. All user space + processes that attempt to access the page will now wait on the page lock + or wait for the migration page table entry to be removed. + + 5. The i_pages lock is taken. This will cause all processes trying + to access the page via the mapping to block on the spinlock. + + 6. The refcount of the page is examined and we back out if references remain. + Otherwise, we know that we are the only one referencing this page. + + 7. The radix tree is checked and if it does not contain the pointer to this + page then we back out because someone else modified the radix tree. + + 8. The new page is prepped with some settings from the old page so that + accesses to the new page will discover a page with the correct settings. + + 9. The radix tree is changed to point to the new page. + + 10. The reference count of the old page is dropped because the address space + reference is gone. A reference to the new page is established because + the new page is referenced by the address space. + + 11. The i_pages lock is dropped. With that lookups in the mapping + become possible again. Processes will move from spinning on the lock + to sleeping on the locked new page. + + 12. The page contents are copied to the new page. + + 13. The remaining page flags are copied to the new page. + + 14. The old page flags are cleared to indicate that the page does + not provide any information anymore. + + 15. Queued up writeback on the new page is triggered. + + 16. If migration entries were inserted into the page table, then replace them + with real ptes. Doing so will enable access for user space processes not + already waiting for the page lock. + + 17. The page locks are dropped from the old and new page. + Processes waiting on the page lock will redo their page faults + and will reach the new page. + + 18. The new page is moved to the LRU and can be scanned by the swapper, + etc. again. + + Non-LRU page migration + ====================== + -Although migration originally aimed for reducing the latency of memory accesses -for NUMA, compaction also uses migration to create high-order pages. ++Although migration originally aimed for reducing the latency of memory ++accesses for NUMA, compaction also uses migration to create high-order ++pages. For compaction purposes, it is also useful to be able to move ++non-LRU pages, such as zsmalloc and virtio-balloon pages. + -Current problem of the implementation is that it is designed to migrate only -*LRU* pages. However, there are potential non-LRU pages which can be migrated -in drivers, for example, zsmalloc, virtio-balloon pages. - -For virtio-balloon pages, some parts of migration code path have been hooked -up and added virtio-balloon specific functions to intercept migration logics. -It's too specific to a driver so other drivers who want to make their pages -movable would have to add their own specific hooks in the migration path. - -To overcome the problem, VM supports non-LRU page migration which provides -generic functions for non-LRU movable pages without driver specific hooks -in the migration path. - -If a driver wants to make its pages movable, it should define three functions -which are function pointers of struct address_space_operations. - -1. ``bool (*isolate_page) (struct page *page, isolate_mode_t mode);`` - - What VM expects from isolate_page() function of driver is to return *true* - if driver isolates the page successfully. On returning true, VM marks the page - as PG_isolated so concurrent isolation in several CPUs skip the page - for isolation. If a driver cannot isolate the page, it should return *false*. - - Once page is successfully isolated, VM uses page.lru fields so driver - shouldn't expect to preserve values in those fields. - -2. ``int (*migratepage) (struct address_space *mapping,`` -| ``struct page *newpage, struct page *oldpage, enum migrate_mode);`` - - After isolation, VM calls migratepage() of driver with the isolated page. - The function of migratepage() is to move the contents of the old page to the - new page - and set up fields of struct page newpage. Keep in mind that you should - indicate to the VM the oldpage is no longer movable via __ClearPageMovable() - under page_lock if you migrated the oldpage successfully and returned - MIGRATEPAGE_SUCCESS. If driver cannot migrate the page at the moment, driver - can return -EAGAIN. On -EAGAIN, VM will retry page migration in a short time - because VM interprets -EAGAIN as "temporary migration failure". On returning - any error except -EAGAIN, VM will give up the page migration without - retrying. - - Driver shouldn't touch the page.lru field while in the migratepage() function. - -3. ``void (*putback_page)(struct page *);`` - - If migration fails on the isolated page, VM should return the isolated page - to the driver so VM calls the driver's putback_page() with the isolated page. - In this function, the driver should put the isolated page back into its own data - structure. - -Non-LRU movable page flags - - There are two page flags for supporting non-LRU movable page. - - * PG_movable - - Driver should use the function below to make page movable under page_lock:: - - void __SetPageMovable(struct page *page, struct address_space *mapping) - - It needs argument of address_space for registering migration - family functions which will be called by VM. Exactly speaking, - PG_movable is not a real flag of struct page. Rather, VM - reuses the page->mapping's lower bits to represent it:: - - #define PAGE_MAPPING_MOVABLE 0x2 - page->mapping = page->mapping | PAGE_MAPPING_MOVABLE; - - so driver shouldn't access page->mapping directly. Instead, driver should - use page_mapping() which masks off the low two bits of page->mapping under - page lock so it can get the right struct address_space. - - For testing of non-LRU movable pages, VM supports __PageMovable() function. - However, it doesn't guarantee to identify non-LRU movable pages because - the page->mapping field is unified with other variables in struct page. - If the driver releases the page after isolation by VM, page->mapping - doesn't have a stable value although it has PAGE_MAPPING_MOVABLE set - (look at __ClearPageMovable). But __PageMovable() is cheap to call whether - page is LRU or non-LRU movable once the page has been isolated because LRU - pages can never have PAGE_MAPPING_MOVABLE set in page->mapping. It is also - good for just peeking to test non-LRU movable pages before more expensive - checking with lock_page() in pfn scanning to select a victim. - - For guaranteeing non-LRU movable page, VM provides PageMovable() function. - Unlike __PageMovable(), PageMovable() validates page->mapping and - mapping->a_ops->isolate_page under lock_page(). The lock_page() prevents - sudden destroying of page->mapping. - - Drivers using __SetPageMovable() should clear the flag via - __ClearMovablePage() under page_lock() before the releasing the page. - - * PG_isolated - - To prevent concurrent isolation among several CPUs, VM marks isolated page - as PG_isolated under lock_page(). So if a CPU encounters PG_isolated - non-LRU movable page, it can skip it. Driver doesn't need to manipulate the - flag because VM will set/clear it automatically. Keep in mind that if the - driver sees a PG_isolated page, it means the page has been isolated by the - VM so it shouldn't touch the page.lru field. - The PG_isolated flag is aliased with the PG_reclaim flag so drivers - shouldn't use PG_isolated for its own purposes. ++If a driver wants to make its pages movable, it should define a struct ++movable_operations. It then needs to call __SetPageMovable() on each ++page that it may be able to move. This uses the ``page->mapping`` field, ++so this field is not available for the driver to use for other purposes. + + Monitoring Migration + ===================== + + The following events (counters) can be used to monitor page migration. + + 1. PGMIGRATE_SUCCESS: Normal page migration success. Each count means that a + page was migrated. If the page was a non-THP and non-hugetlb page, then + this counter is increased by one. If the page was a THP or hugetlb, then + this counter is increased by the number of THP or hugetlb subpages. + For example, migration of a single 2MB THP that has 4KB-size base pages + (subpages) will cause this counter to increase by 512. + + 2. PGMIGRATE_FAIL: Normal page migration failure. Same counting rules as for + PGMIGRATE_SUCCESS, above: this will be increased by the number of subpages, + if it was a THP or hugetlb. + + 3. THP_MIGRATION_SUCCESS: A THP was migrated without being split. + + 4. THP_MIGRATION_FAIL: A THP could not be migrated nor it could be split. + + 5. THP_MIGRATION_SPLIT: A THP was migrated, but not as such: first, the THP had + to be split. After splitting, a migration retry was used for it's sub-pages. + + THP_MIGRATION_* events also update the appropriate PGMIGRATE_SUCCESS or + PGMIGRATE_FAIL events. For example, a THP migration failure will cause both + THP_MIGRATION_FAIL and PGMIGRATE_FAIL to increase. + + Christoph Lameter, May 8, 2006. + Minchan Kim, Mar 28, 2016. ++ ++.. kernel-doc:: include/linux/migrate.h diff --cc Documentation/translations/zh_CN/mm/free_page_reporting.rst index 0000000000000,83b14cce9adf9..5bfd58014c948 mode 000000,100644..100644 --- a/Documentation/translations/zh_CN/mm/free_page_reporting.rst +++ b/Documentation/translations/zh_CN/mm/free_page_reporting.rst @@@ -1,0 -1,38 +1,38 @@@ + .. include:: ../disclaimer-zh_CN.rst + -:Original: Documentation/mm/_free_page_reporting.rst ++:Original: Documentation/mm/free_page_reporting.rst + + :翻译: + + 司延腾 Yanteng Si + + :校译: + + ========== + 空闲页报告 + ========== + + 空闲页报告是一个API,设备可以通过它来注册接收系统当前未使用的页面列表。这在虚拟 + 化的情况下是很有用的,客户机能够使用这些数据来通知管理器它不再使用内存中的某些页 + 面。 + + 对于驱动,通常是气球驱动要使用这个功能,它将分配和初始化一个page_reporting_dev_info + 结构体。它要填充的结构体中的字段是用于处理散点列表的 "report" 函数指针。它还必 + 须保证每次调用该函数时能处理至少相当于PAGE_REPORTING_CAPACITY的散点列表条目。 + 假设没有其他页面报告设备已经注册, 对page_reporting_register的调用将向报告框 + 架注册页面报告接口。 + + 一旦注册,页面报告API将开始向驱动报告成批的页面。API将在接口被注册后2秒开始报告 + 页面,并在任何足够高的页面被释放之后2秒继续报告。 + + 报告的页面将被存储在传递给报告函数的散列表中,最后一个条目的结束位被设置在条目 + nent-1中。 当页面被报告函数处理时,分配器将无法访问它们。一旦报告函数完成,这些 + 页将被返回到它们所获得的自由区域。 + + 在移除使用空闲页报告的驱动之前,有必要调用page_reporting_unregister,以移除 + 目前被空闲页报告使用的page_reporting_dev_info结构体。这样做将阻止进一步的报 + 告通过该接口发出。如果另一个驱动或同一驱动被注册,它就有可能恢复前一个驱动在报告 + 空闲页方面的工作。 + + + Alexander Duyck, 2019å¹´12月04日 diff --cc Documentation/translations/zh_CN/mm/frontswap.rst index 0000000000000,5c18ea2be04ff..434975390b480 mode 000000,100644..100644 --- a/Documentation/translations/zh_CN/mm/frontswap.rst +++ b/Documentation/translations/zh_CN/mm/frontswap.rst @@@ -1,0 -1,196 +1,196 @@@ -:Original: Documentation/mm/_free_page_reporting.rst ++:Original: Documentation/mm/frontswap.rst + + :翻译: + + 司延腾 Yanteng Si + + :校译: + + ========= + Frontswap + ========= + + Frontswap为交换页提供了一个 “transcendent memory” 的接口。在一些环境中,由 + 于交换页被保存在RAM(或类似RAM的设备)中,而不是交换磁盘,因此可以获得巨大的性能 + 节省(提高)。 + + .. _Transcendent memory in a nutshell: https://lwn.net/Articles/454795/ + + Frontswap之所以这么命名,是因为它可以被认为是与swap设备的“back”存储相反。存 + 储器被认为是一个同步并发安全的面向页面的“伪RAM设备”,符合transcendent memory + (如Xen的“tmem”,或内核内压缩内存,又称“zcache”,或未来的类似RAM的设备)的要 + 求;这个伪RAM设备不能被内核直接访问或寻址,其大小未知且可能随时间变化。驱动程序通过 + 调用frontswap_register_ops将自己与frontswap链接起来,以适当地设置frontswap_ops + 的功能,它提供的功能必须符合某些策略,如下所示: + + 一个 “init” 将设备准备好接收与指定的交换设备编号(又称“类型”)相关的frontswap + 交换页。一个 “store” 将把该页复制到transcendent memory,并与该页的类型和偏移 + 量相关联。一个 “load” 将把该页,如果找到的话,从transcendent memory复制到内核 + 内存,但不会从transcendent memory中删除该页。一个 “invalidate_page” 将从 + transcendent memory中删除该页,一个 “invalidate_area” 将删除所有与交换类型 + 相关的页(例如,像swapoff)并通知 “device” 拒绝进一步存储该交换类型。 + + 一旦一个页面被成功存储,在该页面上的匹配加载通常会成功。因此,当内核发现自己处于需 + 要交换页面的情况时,它首先尝试使用frontswap。如果存储的结果是成功的,那么数据就已 + 经成功的保存到了transcendent memory中,并且避免了磁盘写入,如果后来再读回数据, + 也避免了磁盘读取。如果存储返回失败,transcendent memory已经拒绝了该数据,且该页 + 可以像往常一样被写入交换空间。 + + 请注意,如果一个页面被存储,而该页面已经存在于transcendent memory中(一个 “重复” + 的存储),要么存储成功,数据被覆盖,要么存储失败,该页面被废止。这确保了旧的数据永远 + 不会从frontswap中获得。 + + 如果配置正确,对frontswap的监控是通过 `/sys/kernel/debug/frontswap` 目录下的 + debugfs完成的。frontswap的有效性可以通过以下方式测量(在所有交换设备中): + + ``failed_stores`` + 有多少次存储的尝试是失败的 + + ``loads`` + 尝试了多少次加载(应该全部成功) + + ``succ_stores`` + 有多少次存储的尝试是成功的 + + ``invalidates`` + 尝试了多少次作废 + + 后台实现可以提供额外的指标。 + + 经常问到的问题 + ============== + + * 价值在哪里? + + 当一个工作负载开始交换时,性能就会下降。Frontswap通过提供一个干净的、动态的接口来 + 读取和写入交换页到 “transcendent memory”,从而大大增加了许多这样的工作负载的性 + 能,否则内核是无法直接寻址的。当数据被转换为不同的形式和大小(比如压缩)或者被秘密 + 移动(对于一些类似RAM的设备来说,这可能对写平衡很有用)时,这个接口是理想的。交换 + 页(和被驱逐的页面缓存页)是这种比RAM慢但比磁盘快得多的“伪RAM设备”的一大用途。 + + Frontswap对内核的影响相当小,为各种系统配置中更动态、更灵活的RAM利用提供了巨大的 + 灵活性: + + 在单一内核的情况下,又称“zcache”,页面被压缩并存储在本地内存中,从而增加了可以安 + 全保存在RAM中的匿名页面总数。Zcache本质上是用压缩/解压缩的CPU周期换取更好的内存利 + 用率。Benchmarks测试显示,当内存压力较低时,几乎没有影响,而在高内存压力下的一些 + 工作负载上,则有明显的性能改善(25%以上)。 + + “RAMster” 在zcache的基础上增加了对集群系统的 “peer-to-peer” transcendent memory + 的支持。Frontswap页面像zcache一样被本地压缩,但随后被“remotified” 到另一个系 + 统的RAM。这使得RAM可以根据需要动态地来回负载平衡,也就是说,当系统A超载时,它可以 + 交换到系统B,反之亦然。RAMster也可以被配置成一个内存服务器,因此集群中的许多服务器 + 可以根据需要动态地交换到配置有大量内存的单一服务器上......而不需要预先配置每个客户 + 有多少内存可用 + + 在虚拟情况下,虚拟化的全部意义在于统计地将物理资源在多个虚拟机的不同需求之间进行复 + 用。对于RAM来说,这真的很难做到,而且在不改变内核的情况下,要做好这一点的努力基本上 + 是失败的(除了一些广为人知的特殊情况下的工作负载)。具体来说,Xen Transcendent Memory + 后端允许管理器拥有的RAM “fallow”,不仅可以在多个虚拟机之间进行“time-shared”, + 而且页面可以被压缩和重复利用,以优化RAM的利用率。当客户操作系统被诱导交出未充分利用 + 的RAM时(如 “selfballooning”),突然出现的意外内存压力可能会导致交换;frontswap + 允许这些页面被交换到管理器RAM中或从管理器RAM中交换(如果整体主机系统内存条件允许), + 从而减轻计划外交换可能带来的可怕的性能影响。 + + 一个KVM的实现正在进行中,并且已经被RFC'ed到lkml。而且,利用frontswap,对NVM作为 + 内存扩展技术的调查也在进行中。 + + * 当然,在某些情况下可能有性能上的优势,但frontswap的空间/时间开销是多少? + + 如果 CONFIG_FRONTSWAP 被禁用,每个 frontswap 钩子都会编译成空,唯一的开销是每 + 个 swapon'ed swap 设备的几个额外字节。如果 CONFIG_FRONTSWAP 被启用,但没有 + frontswap的 “backend” 寄存器,每读或写一个交换页就会有一个额外的全局变量,而不 + 是零。如果 CONFIG_FRONTSWAP 被启用,并且有一个frontswap的backend寄存器,并且 + 后端每次 “store” 请求都失败(即尽管声称可能,但没有提供内存),CPU 的开销仍然可以 + 忽略不计 - 因为每次frontswap失败都是在交换页写到磁盘之前,系统很可能是 I/O 绑定 + 的,无论如何使用一小部分的 CPU 都是不相关的。 + + 至于空间,如果CONFIG_FRONTSWAP被启用,并且有一个frontswap的backend注册,那么 + 每个交换设备的每个交换页都会被分配一个比特。这是在内核已经为每个交换设备的每个交换 + 页分配的8位(在2.6.34之前是16位)上增加的。(Hugh Dickins观察到,frontswap可能 + 会偷取现有的8个比特,但是我们以后再来担心这个小的优化问题)。对于标准的4K页面大小的 + 非常大的交换盘(这很罕见),这是每32GB交换盘1MB开销。 + + 当交换页存储在transcendent memory中而不是写到磁盘上时,有一个副作用,即这可能会 + 产生更多的内存压力,有可能超过其他的优点。一个backend,比如zcache,必须实现策略 + 来仔细(但动态地)管理内存限制,以确保这种情况不会发生。 + + * 好吧,那就用内核骇客能理解的术语来快速概述一下这个frontswap补丁的作用如何? + + 我们假设在内核初始化过程中,一个frontswap 的 “backend” 已经注册了;这个注册表 + 明这个frontswap 的 “backend” 可以访问一些不被内核直接访问的“内存”。它到底提 + 供了多少内存是完全动态和随机的。 + + 每当一个交换设备被交换时,就会调用frontswap_init(),把交换设备的编号(又称“类 + 型”)作为一个参数传给它。这就通知了frontswap,以期待 “store” 与该号码相关的交 + 换页的尝试。 + + 每当交换子系统准备将一个页面写入交换设备时(参见swap_writepage()),就会调用 + frontswap_store。Frontswap与frontswap backend协商,如果backend说它没有空 + 间,frontswap_store返回-1,内核就会照常把页换到交换设备上。注意,来自frontswap + backend的响应对内核来说是不可预测的;它可能选择从不接受一个页面,可能接受每九个 + 页面,也可能接受每一个页面。但是如果backend确实接受了一个页面,那么这个页面的数 + 据已经被复制并与类型和偏移量相关联了,而且backend保证了数据的持久性。在这种情况 + 下,frontswap在交换设备的“frontswap_map” 中设置了一个位,对应于交换设备上的 + 页面偏移量,否则它就会将数据写入该设备。 + + 当交换子系统需要交换一个页面时(swap_readpage()),它首先调用frontswap_load(), + 检查frontswap_map,看这个页面是否早先被frontswap backend接受。如果是,该页 + 的数据就会从frontswap后端填充,换入就完成了。如果不是,正常的交换代码将被执行, + 以便从真正的交换设备上获得这一页的数据。 + + 所以每次frontswap backend接受一个页面时,交换设备的读取和(可能)交换设备的写 + 入都被 “frontswap backend store” 和(可能)“frontswap backend loads” + 所取代,这可能会快得多。 + + * frontswap不能被配置为一个 “特殊的” 交换设备,它的优先级要高于任何真正的交换 + 设备(例如像zswap,或者可能是swap-over-nbd/NFS)? + + 首先,现有的交换子系统不允许有任何种类的交换层次结构。也许它可以被重写以适应层次 + 结构,但这将需要相当大的改变。即使它被重写,现有的交换子系统也使用了块I/O层,它 + 假定交换设备是固定大小的,其中的任何页面都是可线性寻址的。Frontswap几乎没有触 + 及现有的交换子系统,而是围绕着块I/O子系统的限制,提供了大量的灵活性和动态性。 + + 例如,frontswap backend对任何交换页的接受是完全不可预测的。这对frontswap backend + 的定义至关重要,因为它赋予了backend完全动态的决定权。在zcache中,人们无法预 + 先知道一个页面的可压缩性如何。可压缩性 “差” 的页面会被拒绝,而 “差” 本身也可 + 以根据当前的内存限制动态地定义。 + + 此外,frontswap是完全同步的,而真正的交换设备,根据定义,是异步的,并且使用 + 块I/O。块I/O层不仅是不必要的,而且可能进行 “优化”,这对面向RAM的设备来说是 + 不合适的,包括将一些页面的写入延迟相当长的时间。同步是必须的,以确保后端的动 + 态性,并避免棘手的竞争条件,这将不必要地大大增加frontswap和/或块I/O子系统的 + 复杂性。也就是说,只有最初的 “store” 和 “load” 操作是需要同步的。一个独立 + 的异步线程可以自由地操作由frontswap存储的页面。例如,RAMster中的 “remotification” + 线程使用标准的异步内核套接字,将压缩的frontswap页面移动到远程机器。同样, + KVM的客户方实现可以进行客户内压缩,并使用 “batched” hypercalls。 + + 在虚拟化环境中,动态性允许管理程序(或主机操作系统)做“intelligent overcommit”。 + 例如,它可以选择只接受页面,直到主机交换可能即将发生,然后强迫客户机做他们 + 自己的交换。 + + transcendent memory规格的frontswap有一个坏处。因为任何 “store” 都可 + 能失败,所以必须在一个真正的交换设备上有一个真正的插槽来交换页面。因此, + frontswap必须作为每个交换设备的 “影子” 来实现,它有可能容纳交换设备可能 + 容纳的每一个页面,也有可能根本不容纳任何页面。这意味着frontswap不能包含比 + swap设备总数更多的页面。例如,如果在某些安装上没有配置交换设备,frontswap + 就没有用。无交换设备的便携式设备仍然可以使用frontswap,但是这种设备的 + backend必须配置某种 “ghost” 交换设备,并确保它永远不会被使用。 + + + * 为什么会有这种关于 “重复存储” 的奇怪定义?如果一个页面以前被成功地存储过, + 难道它不能总是被成功地覆盖吗? + + 几乎总是可以的,不,有时不能。考虑一个例子,数据被压缩了,原来的4K页面被压 + 缩到了1K。现在,有人试图用不可压缩的数据覆盖该页,因此会占用整个4K。但是 + backend没有更多的空间了。在这种情况下,这个存储必须被拒绝。每当frontswap + 拒绝一个会覆盖的存储时,它也必须使旧的数据作废,并确保它不再被访问。因为交 + 换子系统会把新的数据写到读交换设备上,这是确保一致性的正确做法。 + + * 为什么frontswap补丁会创建新的头文件swapfile.h? + + frontswap代码依赖于一些swap子系统内部的数据结构,这些数据结构多年来一直 + 在静态和全局之间来回移动。这似乎是一个合理的妥协:将它们定义为全局,但在一 + 个新的包含文件中声明它们,该文件不被包含swap.h的大量源文件所包含。 + + Dan Magenheimer,最后更新于2012å¹´4月9日 diff --cc Documentation/translations/zh_CN/mm/highmem.rst index 0000000000000,81202c65e0001..f74800a6d9a70 mode 000000,100644..100644 --- a/Documentation/translations/zh_CN/mm/highmem.rst +++ b/Documentation/translations/zh_CN/mm/highmem.rst @@@ -1,0 -1,128 +1,137 @@@ + .. include:: ../disclaimer-zh_CN.rst + + :Original: Documentation/mm/highmem.rst + + :翻译: + + 司延腾 Yanteng Si + + :校译: + + ========== + 高内存处理 + ========== + + 作者: Peter Zijlstra + + .. contents:: :local: + + 高内存是什么? + ============== + + 当物理内存的大小接近或超过虚拟内存的最大大小时,就会使用高内存(highmem)。在这一点上,内 + 核不可能在任何时候都保持所有可用的物理内存的映射。这意味着内核需要开始使用它想访问的物理内 + 存的临时映射。 + + 没有被永久映射覆盖的那部分(物理)内存就是我们所说的 "高内存"。对于这个边界的确切位置,有 + 各种架构上的限制。 + + 例如,在i386架构中,我们选择将内核映射到每个进程的虚拟空间,这样我们就不必为内核的进入/退 + 出付出全部的TLB作废代价。这意味着可用的虚拟内存空间(i386上为4GiB)必须在用户和内核空间之 + 间进行划分。 + + 使用这种方法的架构的传统分配方式是3:1,3GiB用于用户空间,顶部的1GiB用于内核空间。:: + + +--------+ 0xffffffff + | Kernel | + +--------+ 0xc0000000 + | | + | User | + | | + +--------+ 0x00000000 + + 这意味着内核在任何时候最多可以映射1GiB的物理内存,但是由于我们需要虚拟地址空间来做其他事 + 情--包括访问其余物理内存的临时映射--实际的直接映射通常会更少(通常在~896MiB左右)。 + + 其他有mm上下文标签的TLB的架构可以有独立的内核和用户映射。然而,一些硬件(如一些ARM)在使 + 用mm上下文标签时,其虚拟空间有限。 + + + 临时虚拟映射 + ============ + -内核包含几种创建临时映射的方法。: ++内核包含几种创建临时映射的方法。下面的列表按照使用的优先顺序显示了它们。 + -* vmap(). 这可以用来将多个物理页长期映射到一个连续的虚拟空间。它需要synchronization - 来解除映射。 ++* kmap_local_page()。这个函数是用来要求短期映射的。它可以从任何上下文(包括中断)中调用, ++ 但是映射只能在获取它们的上下文中使用。 + -* kmap(). 这允许对单个页面进行短期映射。它需要synchronization,但在一定程度上被摊销。 - 当以嵌套方式使用时,它也很容易出现死锁,因此不建议在新代码中使用它。 ++ 在可行的情况下,这个函数应该比其他所有的函数优先使用。 + -* kmap_atomic(). 这允许对单个页面进行非常短的时间映射。由于映射被限制在发布它的CPU上, - 它表现得很好,但发布任务因此被要求留在该CPU上直到它完成,以免其他任务取代它的映射。 - - kmap_atomic() 也可以由中断上下文使用,因为它不睡眠,而且调用者可能在调用kunmap_atomic() - 之后才睡眠。 - - 可以假设k[un]map_atomic()不会失败。 ++ 这些映射是线程本地和CPU本地的,这意味着映射只能从这个线程中访问,并且当映射处于活动状 ++ 态时,该线程与CPU绑定。即使线程被抢占了(因为抢占永远不会被函数禁用),CPU也不能通过 ++ CPU-hotplug从系统中拔出,直到映射被处理掉。 + ++ 在本地的kmap区域中采取pagefaults是有效的,除非获取本地映射的上下文由于其他原因不允许 ++ 这样做。 + -使用kmap_atomic -=============== ++ kmap_local_page()总是返回一个有效的虚拟地址,并且假定kunmap_local()不会失败。 + -何时何地使用 kmap_atomic() 是很直接的。当代码想要访问一个可能从高内存(见__GFP_HIGHMEM) -分配的页面的内容时,例如在页缓存中的页面,就会使用它。该API有两个函数,它们的使用方式与 -下面类似:: ++ 嵌套kmap_local_page()和kmap_atomic()映射在一定程度上是允许的(最多到KMAP_TYPE_NR), ++ 但是它们的调用必须严格排序,因为映射的实现是基于堆栈的。关于如何管理嵌套映射的细节, ++ 请参见kmap_local_page() kdocs(包含在 "函数 "部分)。 + - /* 找到感兴趣的页面。 */ - struct page *page = find_get_page(mapping, offset); - - /* 获得对该页内容的访问权。 */ - void *vaddr = kmap_atomic(page); ++* kmap_atomic(). 这允许对单个页面进行非常短的时间映射。由于映射被限制在发布它的CPU上, ++ 它表现得很好,但发布的任务因此被要求留在该CPU上直到它完成,以免其他任务取代它的映射。 + - /* 对该页的内容做一些处理。 */ - memset(vaddr, 0, PAGE_SIZE); ++ kmap_atomic()也可以被中断上下文使用,因为它不睡眠,调用者也可能在调用kunmap_atomic() ++ 后才睡眠。 + - /* 解除该页面的映射。 */ - kunmap_atomic(vaddr); ++ 内核中对kmap_atomic()的每次调用都会创建一个不可抢占的段,并禁用缺页异常。这可能是 ++ 未预期延迟的来源之一。因此用户应该选择kmap_local_page()而不是kmap_atomic()。 + -注意,kunmap_atomic()调用的是kmap_atomic()调用的结果而不是参数。 ++ 假设k[un]map_atomic()不会失败。 + -如果你需要映射两个页面,因为你想从一个页面复制到另一个页面,你需要保持kmap_atomic调用严 -格嵌套,如:: ++* kmap()。这应该被用来对单个页面进行短时间的映射,对抢占或迁移没有限制。它会带来开销, ++ 因为映射空间是受限制的,并且受到全局锁的保护,以实现同步。当不再需要映射时,必须用 ++ kunmap()释放该页被映射的地址。 + - vaddr1 = kmap_atomic(page1); - vaddr2 = kmap_atomic(page2); ++ 映射变化必须广播到所有CPU(核)上,kmap()还需要在kmap的池被回绕(TLB项用光了,需要从第 ++ 一项复用)时进行全局TLB无效化,当映射空间被完全利用时,它可能会阻塞,直到有一个可用的 ++ 槽出现。因此,kmap()只能从可抢占的上下文中调用。 + - memcpy(vaddr1, vaddr2, PAGE_SIZE); ++ 如果一个映射必须持续相对较长的时间,上述所有的工作都是必要的,但是内核中大部分的 ++ 高内存映射都是短暂的,而且只在一个地方使用。这意味着在这种情况下,kmap()的成本大 ++ 多被浪费了。kmap()并不是为长期映射而设计的,但是它已经朝着这个方向发展了,在较新 ++ 的代码中强烈不鼓励使用它,前面的函数集应该是首选。 + - kunmap_atomic(vaddr2); - kunmap_atomic(vaddr1); ++ 在64位系统中,调用kmap_local_page()、kmap_atomic()和kmap()没有实际作用,因为64位 ++ 地址空间足以永久映射所有物理内存页面。 + ++* vmap()。这可以用来将多个物理页长期映射到一个连续的虚拟空间。它需要全局同步来解除 ++ 映射。 + + 临时映射的成本 + ============== + + 创建临时映射的代价可能相当高。体系架构必须操作内核的页表、数据TLB和/或MMU的寄存器。 + + 如果CONFIG_HIGHMEM没有被设置,那么内核会尝试用一点计算来创建映射,将页面结构地址转换成 + 指向页面内容的指针,而不是去捣鼓映射。在这种情况下,解映射操作可能是一个空操作。 + + 如果CONFIG_MMU没有被设置,那么就不可能有临时映射和高内存。在这种情况下,也将使用计算方法。 + + + i386 PAE + ======== + + 在某些情况下,i386 架构将允许你在 32 位机器上安装多达 64GiB 的内存。但这有一些后果: + + * Linux需要为系统中的每个页面建立一个页帧结构,而且页帧需要驻在永久映射中,这意味着: + + * 你最多可以有896M/sizeof(struct page)页帧;由于页结构体是32字节的,所以最终会有 + 112G的页;然而,内核需要在内存中存储更多的页帧...... + + * PAE使你的页表变大--这使系统变慢,因为更多的数据需要在TLB填充等方面被访问。一个好处 + 是,PAE有更多的PTE位,可以提供像NX和PAT这样的高级功能。 + + 一般的建议是,你不要在32位机器上使用超过8GiB的空间--尽管更多的空间可能对你和你的工作 + 量有用,但你几乎是靠你自己--不要指望内核开发者真的会很关心事情的进展情况。 ++ ++函数 ++==== ++ ++该API在以下内核代码中: ++ ++include/linux/highmem.h ++ ++include/linux/highmem-internal.h diff --cc Documentation/translations/zh_CN/mm/index.rst index 0000000000000,4c8c6b7b72a35..2f53e37b80497 mode 000000,100644..100644 --- a/Documentation/translations/zh_CN/mm/index.rst +++ b/Documentation/translations/zh_CN/mm/index.rst @@@ -1,0 -1,54 +1,69 @@@ + .. include:: ../disclaimer-zh_CN.rst + + :Original: Documentation/mm/index.rst + + :翻译: + + 司延腾 Yanteng Si + + :校译: + + ================= + Linux内存管理文档 + ================= + -这是一个关于Linux内存管理(mm)子系统内部的文档集,其中有不同层次的细节,包括注释 -和邮件列表的回复,用于阐述数据结构和算法的基本情况。如果你正在寻找关于简单分配内存的建 -议,请参阅(Documentation/translations/zh_CN/core-api/memory-allocation.rst)。 -对于控制和调整指南,请参阅(Documentation/admin-guide/mm/index)。 -TODO:待引用文档集被翻译完毕后请及时修改此处) ++这是一份关于了解Linux的内存管理子系统的指南。如果你正在寻找关于简单分配内存的 ++建议,请参阅内存分配指南 ++(Documentation/translations/zh_CN/core-api/memory-allocation.rst)。 ++关于控制和调整的指南,请看管理指南 ++(Documentation/translations/zh_CN/admin-guide/mm/index.rst)。 ++ ++ ++.. toctree:: ++ :maxdepth: 1 ++ ++ highmem ++ ++该处剩余文档待原始文档有内容后翻译。 ++ ++ ++遗留文档 ++======== ++ ++这是一个关于Linux内存管理(MM)子系统内部的旧文档的集合,其中有不同层次的细节, ++包括注释和邮件列表的回复,用于阐述数据结构和算法的描述。它应该被很好地整合到上述 ++结构化的文档中,如果它已经完成了它的使命,可以删除。 + + .. toctree:: + :maxdepth: 1 + + active_mm + balance + damon/index + free_page_reporting - highmem + ksm + frontswap + hmm + hwpoison + hugetlbfs_reserv + memory-model + mmu_notifier + numa + overcommit-accounting + page_frags ++ page_migration + page_owner + page_table_check + remap_file_pages + split_page_table_lock ++ vmalloced-kernel-stacks + z3fold + zsmalloc + + TODOLIST: + * arch_pgtable_helpers + * free_page_reporting + * hugetlbfs_reserv -* page_migration + * slub + * transhuge + * unevictable-lru -* vmalloced-kernel-stacks diff --cc Documentation/translations/zh_CN/mm/page_frags.rst index 0000000000000,320952ca93af6..20bd3fafdc8c9 mode 000000,100644..100644 --- a/Documentation/translations/zh_CN/mm/page_frags.rst +++ b/Documentation/translations/zh_CN/mm/page_frags.rst @@@ -1,0 -1,38 +1,38 @@@ -:Original: Documentation/mm/page_frag.rst ++:Original: Documentation/mm/page_frags.rst + + :翻译: + + 司延腾 Yanteng Si + + :校译: + + + ======== + 页面片段 + ======== + + 一个页面片段是一个任意长度的任意偏移的内存区域,它位于一个0或更高阶的复合页面中。 + 该页中的多个碎片在该页的引用计数器中被单独计算。 + + page_frag函数,page_frag_alloc和page_frag_free,为页面片段提供了一个简单 + 的分配框架。这被网络堆栈和网络设备驱动使用,以提供一个内存的支持区域,作为 + sk_buff->head使用,或者用于skb_shared_info的 “frags” 部分。 + + 为了使用页面片段API,需要一个支持页面片段的缓冲区。这为碎片分配提供了一个中心点, + 并允许多个调用使用一个缓存的页面。这样做的好处是可以避免对get_page的多次调用, + 这在分配时开销可能会很大。然而,由于这种缓存的性质,要求任何对缓存的调用都要受到每 + 个CPU的限制,或者每个CPU的限制,并在执行碎片分配时强制禁止中断。 + + 网络堆栈在每个CPU使用两个独立的缓存来处理碎片分配。netdev_alloc_cache被使用 + netdev_alloc_frag和__netdev_alloc_skb调用的调用者使用。napi_alloc_cache + 被调用__napi_alloc_frag和__napi_alloc_skb的调用者使用。这两个调用的主要区别是 + 它们可能被调用的环境。“netdev” 前缀的函数可以在任何上下文中使用,因为这些函数 + 将禁用中断,而 ”napi“ 前缀的函数只可以在softirq上下文中使用。 + + 许多网络设备驱动程序使用类似的方法来分配页面片段,但页面片段是在环或描述符级别上 + 缓存的。为了实现这些情况,有必要提供一种拆解页面缓存的通用方法。出于这个原因, + __page_frag_cache_drain被实现了。它允许通过一次调用从一个页面释放多个引用。 + 这样做的好处是,它允许清理被添加到一个页面的多个引用,以避免每次分配都调用 + get_page。 + + Alexander Duyck,2016å¹´11月29日。 diff --cc Documentation/translations/zh_CN/mm/page_migration.rst index 0000000000000,0000000000000..076081dc16354 new file mode 100644 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/page_migration.rst @@@ -1,0 -1,0 +1,228 @@@ ++.. include:: ../disclaimer-zh_CN.rst ++ ++:Original: Documentation/mm/page_migration.rst ++ ++:翻译: ++ ++ 司延腾 Yanteng Si ++ ++:校译: ++ ++======== ++页面迁移 ++======== ++ ++页面迁移允许在进程运行时在NUMA系统的节点之间移动页面的物理位置。这意味着进程所看到的虚拟地 ++址并没有改变。然而,系统会重新安排这些页面的物理位置。 ++ ++也可以参见 :ref: `<异构内存管理 (HMM)>` 以了解将页面迁移到设备私有内存或从设备私有内存中迁移。 ++ ++页面迁移的主要目的是通过将页面移到访问该内存的进程所运行的处理器附近来减少内存访问的延迟。 ++ ++页面迁移允许进程通过MF_MOVE和MF_MOVE_ALL选项手动重新定位其页面所在的节点,同时通过 ++mbind()设置一个新的内存策略。一个进程的页面也可以通过sys_migrate_pages()函数调用从另 ++一个进程重新定位。migrate_pages()函数调用接收两组节点,并将一个进程位于旧节点上的页面移 ++动到目标节点上。页面迁移功能由Andi Kleen的numactl包提供(需要0.9.3以上的版本,其仓库 ++地址https://github.com/numactl/numactl.git)。numactl提供了libnuma,它为页面迁移 ++提供了与其他NUMA功能类似的接口。执行 cat ``/proc//numa_maps`` 允许轻松查看进 ++程的页面位置。参见proc(5)手册中的numa_maps文档。 ++ ++如果调度程序将一个进程重新安置到一个遥远的节点上的处理器,手动迁移是很有用的。批量调度程序 ++或管理员可以检测到这种情况,并将进程的页面移到新处理器附近。内核本身只提供手动的页迁移支持。 ++自动的页面迁移可以通过用户空间的进程移动页面来实现。一个特殊的函数调用 "move_pages" 允许 ++在一个进程中移动单个页面。例如,NUMA分析器可以获得一个显示频繁的节点外访问的日志,并可以使 ++用这个结果将页面移动到更有利的位置。 ++ ++较大型的设备通常使用cpusets将系统分割成若干个节点。Paul Jackson为cpusets配备了当任务被 ++转移到另一个cpuset时移动页面的能力(见:ref:`CPUSETS `)。Cpusets允许进程定 ++位的自动化。如果一个任务被移到一个新的cpuset上,那么它的所有页面也会随之移动,这样进程的 ++性能就不会急剧下降。如果cpuset允许的内存节点发生变化,cpuset中的进程页也会被移动。 ++ ++页面迁移允许为所有迁移技术保留一组节点中页面的相对位置,这将保留生成的特定内存分配模式即使 ++进程已被迁移。为了保留内存延迟,这一点是必要的。迁移后的进程将以类似的性能运行。 ++ ++页面迁移分几个步骤进行。首先为那些试图从内核中使用migrate_pages()的进程做一个高层次的 ++描述(对于用户空间的使用,可以参考上面提到的Andi Kleen的numactl包),然后对低水平的细 ++节工作做一个低水平描述。 ++ ++在内核中使用 migrate_pages() ++============================ ++ ++1. 从LRU中移除页面。 ++ ++ 要迁移的页面列表是通过扫描页面并把它们移到列表中来生成的。这是通过调用 isolate_lru_page() ++ 来完成的。调用isolate_lru_page()增加了对该页的引用,这样在页面迁移发生时它就不会 ++ 消失。它还可以防止交换器或其他扫描器遇到该页。 ++ ++ ++2. 我们需要有一个new_page_t类型的函数,可以传递给migrate_pages()。这个函数应该计算 ++ 出如何在给定的旧页面中分配正确的新页面。 ++ ++3. migrate_pages()函数被调用,它试图进行迁移。它将调用该函数为每个被考虑迁移的页面分 ++ 配新的页面。 ++ ++migrate_pages()如何工作 ++======================= ++ ++migrate_pages()对它的页面列表进行了多次处理。如果当时对一个页面的所有引用都可以被移除, ++那么这个页面就会被移动。该页已经通过isolate_lru_page()从LRU中移除,并且refcount被 ++增加,以便在页面迁移发生时不释放该页。 ++ ++步骤: ++ ++1. 锁定要迁移的页面。 ++ ++2. 确保回写已经完成。 ++ ++3. 锁定我们要迁移到的新页面。锁定它是为了在迁移过程中立即阻止对这个(尚未更新的)页面的 ++ 访问。 ++ ++4. 所有对该页的页表引用都被转换为迁移条目。这就减少了一个页面的mapcount。如果产生的 ++ mapcount不是零,那么我们就不迁移该页。所有试图访问该页的用户空间进程现在将等待页 ++ 面锁或者等待迁移页表项被移除。 ++ ++5. i_pages的锁被持有。这将导致所有试图通过映射访问该页的进程在自旋锁上阻塞。 ++ ++6. 检查该页的Refcount,如果还有引用,我们就退出。否则,我们知道我们是唯一引用这个页 ++ 面的人。 ++ ++7. 检查基数树,如果它不包含指向这个页面的指针,那么我们就退出,因为其他人修改了基数树。 ++ ++8. 新的页面要用旧的页面的一些设置进行预处理,这样访问新的页面就会发现一个具有正确设置 ++ 的页面。 ++ ++9. 基数树被改变以指向新的页面。 ++ ++10. 旧页的引用计数被删除,因为地址空间的引用已经消失。对新页的引用被建立,因为新页被 ++ 地址空间引用。 ++ ++11. i_pages锁被放弃。这样一来,在映射中的查找又变得可能了。进程将从在锁上自旋到在 ++ 被锁的新页上睡眠。 ++ ++12. 页面内容被复制到新的页面上。 ++ ++13. 剩余的页面标志被复制到新的页面上。 ++ ++14. 旧的页面标志被清除,以表明该页面不再提供任何信息。 ++ ++15. 新页面上的回写队列被触发了。 ++ ++16. 如果迁移条目被插入到页表中,那么就用真正的ptes替换它们。这样做将使那些尚未等待页 ++ 锁的用户空间进程能够访问。 ++ ++17. 页面锁从新旧页面上被撤销。等待页锁的进程将重做他们的缺页异常,并将到达新的页面。 ++ ++18. 新的页面被移到LRU中,可以被交换器等再次扫描。 ++ ++非LRU页面迁移 ++============= ++ ++尽管迁移最初的目的是为了减少NUMA的内存访问延迟,但压缩也使用迁移来创建高阶页面。 ++ ++目前实现的问题是,它被设计为只迁移*LRU*页。然而,有一些潜在的非LRU页面可以在驱动中 ++被迁移,例如,zsmalloc,virtio-balloon页面。 ++ ++对于virtio-balloon页面,迁移代码路径的某些部分已经被钩住,并添加了virtio-balloon ++的特定函数来拦截迁移逻辑。这对一个驱动来说太特殊了,所以其他想让自己的页面可移动的驱 ++动就必须在迁移路径中添加自己的特定钩子。 ++ ++为了克服这个问题,VM支持非LRU页面迁移,它为非LRU可移动页面提供了通用函数,而在迁移 ++路径中没有特定的驱动程序钩子。 ++ ++如果一个驱动程序想让它的页面可移动,它应该定义三个函数,这些函数是 ++struct address_space_operations的函数指针。 ++ ++1. ``bool (*isolate_page) (struct page *page, isolate_mode_t mode);`` ++ ++ VM对驱动的isolate_page()函数的期望是,如果驱动成功隔离了该页,则返回*true*。 ++ 返回true后,VM会将该页标记为PG_isolated,这样多个CPU的并发隔离就会跳过该 ++ 页进行隔离。如果驱动程序不能隔离该页,它应该返回*false*。 ++ ++ 一旦页面被成功隔离,VM就会使用page.lru字段,因此驱动程序不应期望保留这些字段的值。 ++ ++2. ``int (*migratepage) (struct address_space *mapping,`` ++| ``struct page *newpage, struct page *oldpage, enum migrate_mode);`` ++ ++ 隔离后,虚拟机用隔离的页面调用驱动的migratepage()。migratepage()的功能是将旧页 ++ 的内容移动到新页,并设置struct page newpage的字段。请记住,如果你成功迁移了旧页 ++ 并返回MIGRATEPAGE_SUCCESS,你应该通过page_lock下的__ClearPageMovable()向虚 ++ 拟机表明旧页不再可移动。如果驱动暂时不能迁移该页,驱动可以返回-EAGAIN。在-EAGAIN ++ 时,VM会在短时间内重试页面迁移,因为VM将-EAGAIN理解为 "临时迁移失败"。在返回除 ++ -EAGAIN以外的任何错误时,VM将放弃页面迁移而不重试。 ++ ++ 在migratepage()函数中,驱动程序不应该接触page.lru字段。 ++ ++3. ``void (*putback_page)(struct page *);`` ++ ++ 如果在隔离页上迁移失败,VM应该将隔离页返回给驱动,因此VM用隔离页调用驱动的 ++ putback_page()。在这个函数中,驱动应该把隔离页放回自己的数据结构中。 ++ ++非LRU可移动页标志 ++ ++ 有两个页面标志用于支持非LRU可移动页面。 ++ ++ * PG_movable ++ ++ 驱动应该使用下面的函数来使页面在page_lock下可移动。:: ++ ++ void __SetPageMovable(struct page *page, struct address_space *mapping) ++ ++ 它需要address_space的参数来注册将被VM调用的migration family函数。确切地说, ++ PG_movable不是struct page的一个真正的标志。相反,VM复用了page->mapping的低 ++ 位来表示它:: ++ ++ #define PAGE_MAPPING_MOVABLE 0x2 ++ page->mapping = page->mapping | PAGE_MAPPING_MOVABLE; ++ ++ 所以驱动不应该直接访问page->mapping。相反,驱动应该使用page_mapping(),它可 ++ 以在页面锁下屏蔽掉page->mapping的低2位,从而获得正确的struct address_space。 ++ ++ 对于非LRU可移动页面的测试,VM支持__PageMovable()函数。然而,它并不能保证识别 ++ 非LRU可移动页面,因为page->mapping字段与struct page中的其他变量是统一的。如 ++ 果驱动程序在被虚拟机隔离后释放了页面,尽管page->mapping设置了PAGE_MAPPING_MOVABLE, ++ 但它并没有一个稳定的值(看看__ClearPageMovable)。但是__PageMovable()在页 ++ 面被隔离后,无论页面是LRU还是非LRU可移动的,调用它开销都很低,因为LRU页面在 ++ page->mapping中不可能有PAGE_MAPPING_MOVABLE设置。在用pfn扫描中的lock_page() ++ 进行更大开销的检查来选择受害者之前,它也很适合只是瞥一眼来测试非LRU可移动的页面。 ++ ++ 为了保证非LRU的可移动页面,VM提供了PageMovable()函数。与__PageMovable()不 ++ 同,PageMovable()在lock_page()下验证page->mapping和 ++ mapping->a_ops->isolate_page。lock_page()可以防止突然破坏page->mapping。 ++ ++ 使用__SetPageMovable()的驱动应该在释放页面之前通过page_lock()下的 ++ __ClearMovablePage()清除该标志。 ++ ++ * PG_isolated ++ ++ 为了防止几个CPU同时进行隔离,VM在lock_page()下将隔离的页面标记为PG_isolated。 ++ 因此,如果一个CPU遇到PG_isolated非LRU可移动页面,它可以跳过它。驱动程序不需要 ++ 操作这个标志,因为VM会自动设置/清除它。请记住,如果驱动程序看到PG_isolated页, ++ 这意味着该页已经被VM隔离,所以它不应该碰page.lru字段。PG_isolated标志与 ++ PG_reclaim标志是同义的,所以驱动程序不应该为自己的目的使用PG_isolated。 ++ ++监测迁移 ++======== ++ ++以下事件(计数器)可用于监控页面迁移。 ++ ++1. PGMIGRATE_SUCCESS: 正常的页面迁移成功。每个计数器意味着一个页面被迁移了。如果该 ++ 页是一个非THP和非hugetlb页,那么这个计数器会增加1。如果该页面是一个THP或hugetlb ++ 页面,那么这个计数器会随着THP或hugetlb子页面的数量而增加。例如,迁移一个有4KB大小 ++ 的基础页(子页)的2MB THP,将导致这个计数器增加512。 ++ ++2. PGMIGRATE_FAIL: 正常的页面迁移失败。与上面PGMIGRATE_SUCCESS的计数规则相同:如 ++ 果是THP或hugetlb,这个计数将被子页的数量增加。 ++ ++3. THP_MIGRATION_SUCCESS: 一个THP被迁移而没有被分割。 ++ ++4. THP_MIGRATION_FAIL: 一个THP不能被迁移,也不能被分割。 ++ ++5. THP_MIGRATION_SPLIT: 一个THP被迁移了,但不是这样的:首先,这个THP必须被分割。 ++ 在拆分之后,对它的子页面进行了迁移重试。 ++ ++THP_MIGRATION_* 事件也会更新相应的PGMIGRATE_SUCCESS或PGMIGRATE_FAIL事件。 ++例如,一个THP迁移失败将导致THP_MIGRATION_FAIL和PGMIGRATE_FAIL增加。 ++ ++Christoph Lameter,2006å¹´5月8日。 ++ ++Minchan Kim,2016å¹´3月28日。 diff --cc Documentation/translations/zh_CN/mm/page_owner.rst index 0000000000000,03d9e613094a2..b7f81d7a6589c mode 000000,100644..100644 --- a/Documentation/translations/zh_CN/mm/page_owner.rst +++ b/Documentation/translations/zh_CN/mm/page_owner.rst @@@ -1,0 -1,116 +1,177 @@@ + :Original: Documentation/mm/page_owner.rst + + :翻译: + + 司延腾 Yanteng Si + + :校译: + + + ================================ + page owner: 跟踪谁分配的每个页面 + ================================ + + 概述 + ==== + + page owner是用来追踪谁分配的每一个页面。它可以用来调试内存泄漏或找到内存占用者。 + 当分配发生时,有关分配的信息,如调用堆栈和页面的顺序被存储到每个页面的特定存储中。 + 当我们需要了解所有页面的状态时,我们可以获得并分析这些信息。 + + 尽管我们已经有了追踪页面分配/释放的tracepoint,但用它来分析谁分配的每个页面是 + 相当复杂的。我们需要扩大跟踪缓冲区,以防止在用户空间程序启动前出现重叠。而且,启 + 动的程序会不断地将跟踪缓冲区转出,供以后分析,这将会改变系统的行为,会产生更多的 + 可能性,而不是仅仅保留在内存中,所以不利于调试。 + + 页面所有者也可以用于各种目的。例如,可以通过每个页面的gfp标志信息获得精确的碎片 + 统计。如果启用了page owner,它就已经实现并激活了。我们非常欢迎其他用途。 + + page owner在默认情况下是禁用的。所以,如果你想使用它,你需要在你的启动cmdline + 中加入"page_owner=on"。如果内核是用page owner构建的,并且由于没有启用启动 + 选项而在运行时禁用page owner,那么运行时的开销是很小的。如果在运行时禁用,它不 + 需要内存来存储所有者信息,所以没有运行时内存开销。而且,页面所有者在页面分配器的 + 热路径中只插入了两个不可能的分支,如果不启用,那么分配就会像没有页面所有者的内核 + 一样进行。这两个不可能的分支应该不会影响到分配的性能,特别是在静态键跳转标签修补 + 功能可用的情况下。以下是由于这个功能而导致的内核代码大小的变化。 + + - 没有page owner:: + + text data bss dec hex filename + 48392 2333 644 51369 c8a9 mm/page_alloc.o + + - 有page owner:: + + text data bss dec hex filename + 48800 2445 644 51889 cab1 mm/page_alloc.o + 6662 108 29 6799 1a8f mm/page_owner.o + 1025 8 8 1041 411 mm/page_ext.o + + 虽然总共增加了8KB的代码,但page_alloc.o增加了520字节,其中不到一半是在hotpath + 中。构建带有page owner的内核,并在需要时打开它,将是调试内核内存问题的最佳选择。 + + 有一个问题是由实现细节引起的。页所有者将信息存储到struct page扩展的内存中。这 + 个内存的初始化时间比稀疏内存系统中的页面分配器启动的时间要晚一些,所以,在初始化 + 之前,许多页面可以被分配,但它们没有所有者信息。为了解决这个问题,这些早期分配的 + 页面在初始化阶段被调查并标记为分配。虽然这并不意味着它们有正确的所有者信息,但至 + 少,我们可以更准确地判断该页是否被分配。在2GB内存的x86-64虚拟机上,有13343 + 个早期分配的页面被捕捉和标记,尽管它们大部分是由结构页扩展功能分配的。总之,在这 + 之后,没有任何页面处于未追踪状态。 + + 使用方法 + ======== + + 1) 构建用户空间的帮助:: + + cd tools/vm + make page_owner_sort + + 2) 启用page owner: 添加 "page_owner=on" 到 boot cmdline. + + 3) 做你想调试的工作。 + + 4) 分析来自页面所有者的信息:: + + cat /sys/kernel/debug/page_owner > page_owner_full.txt + ./page_owner_sort page_owner_full.txt sorted_page_owner.txt + + ``page_owner_full.txt`` 的一般输出情况如下(输出信息无翻译价值):: + + Page allocated via order XXX, ... + PFN XXX ... + // Detailed stack + + Page allocated via order XXX, ... + PFN XXX ... + // Detailed stack + + ``page_owner_sort`` 工具忽略了 ``PFN`` 行,将剩余的行放在buf中,使用regexp提 + 取页序值,计算buf的次数和页数,最后根据参数进行排序。 + + 在 ``sorted_page_owner.txt`` 中可以看到关于谁分配了每个页面的结果。一般输出:: + + XXX times, XXX pages: + Page allocated via order XXX, ... + // Detailed stack + + 默认情况下, ``page_owner_sort`` 是根据buf的时间来排序的。如果你想 + 按buf的页数排序,请使用-m参数。详细的参数是: + - 基本函数: ++ 基本函数:: + - Sort: ++ 排序: + -a 按内存分配时间排序 + -m 按总内存排序 + -p 按pid排序。 + -P 按tgid排序。 ++ -n 按任务命令名称排序。 + -r 按内存释放时间排序。 + -s 按堆栈跟踪排序。 + -t 按时间排序(默认)。 - - 其它函数: - - Cull: - -c 通过比较堆栈跟踪而不是总块来进行剔除。 - - Filter: ++ --sort 指定排序顺序。排序的语法是[+|-]key[,[+|-]key[,...]]。从 ++ **标准格式指定器**那一节选择一个键。"+"是可选的,因为默认的方向是数字或 ++ 词法的增加。允许混合使用缩写和完整格式的键。 ++ ++ 例子: ++ ./page_owner_sort --sort=n,+pid,-tgid ++ ./page_owner_sort --sort=at ++ ++ 其它函数:: ++ ++ 剔除: ++ --cull ++ 指定剔除规则。剔除的语法是key[,key[,...]]。从**标准格式指定器** ++ 部分选择一个多字母键。 ++ 是一个以逗号分隔的列表形式的单一参数,它提供了一种指定单个剔除规则的 ++ 方法。 识别的关键字在下面的**标准格式指定器**部分有描述。<规则>可以通过键的 ++ 序列k1,k2,...来指定,在下面的标准排序键部分有描述。允许混合使用简写和完整形 ++ 式的键。 ++ ++ Examples: ++ ./page_owner_sort --cull=stacktrace ++ ./page_owner_sort --cull=st,pid,name ++ ./page_owner_sort --cull=n,f ++ ++ 过滤: + -f 过滤掉内存已被释放的块的信息。 ++ ++ 选择: ++ --pid 按pid选择。这将选择进程ID号出现在中的块。 ++ --tgid 按tgid选择。这将选择其线程组ID号出现在 ++ 中的块。 ++ --name 按任务命令名称选择。这将选择其任务命令名称出现在 ++ 中的区块。 ++ ++ , , 是以逗号分隔的列表形式的单个参数, ++ 它提供了一种指定单个选择规则的方法。 ++ ++ ++ 例子: ++ ./page_owner_sort --pid=1 ++ ./page_owner_sort --tgid=1,2,3 ++ ./page_owner_sort --name name1,name2 ++ ++标准格式指定器 ++============== ++:: ++ ++ --sort的选项: ++ ++ 短键 长键 描述 ++ p pid 进程ID ++ tg tgid 线程组ID ++ n name 任务命令名称 ++ st stacktrace 页面分配的堆栈跟踪 ++ T txt 块的全文 ++ ft free_ts 页面释放时的时间戳 ++ at alloc_ts 页面被分配时的时间戳 ++ ator allocator 页面的内存分配器 ++ ++ --curl的选项: ++ ++ 短键 长键 描述 ++ p pid 进程ID ++ tg tgid 线程组ID ++ n name 任务命令名称 ++ f free 该页是否已经释放 ++ st stacktrace 页面分配的堆栈跟踪 ++ ator allocator 页面的内存分配器 diff --cc Documentation/translations/zh_CN/mm/vmalloced-kernel-stacks.rst index 0000000000000,0000000000000..d02a23f7f07ee new file mode 100644 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/vmalloced-kernel-stacks.rst @@@ -1,0 -1,0 +1,133 @@@ ++.. SPDX-License-Identifier: GPL-2.0 ++.. include:: ../disclaimer-zh_CN.rst ++ ++:Original: Documentation/mm/vmalloced-kernel-stacks.rst ++ ++:翻译: ++ ++ 司延腾 Yanteng Si ++ ++:校译: ++ ++==================== ++支持虚拟映射的内核栈 ++==================== ++ ++:作者: Shuah Khan ++ ++.. contents:: :local: ++ ++概览 ++---- ++ ++这是介绍 `虚拟映射内核栈功能 ` 的代码 ++和原始补丁系列的信息汇总。 ++ ++简介 ++---- ++ ++内核堆栈溢出通常难以调试,并使内核容易被(恶意)利用。问题可能在稍后的时间出现,使其难以 ++隔离和究其根本原因。 ++ ++带有保护页的虚拟映射内核堆栈如果溢出,会被立即捕获,而不会放任其导致难以诊断的损 ++坏。 ++ ++HAVE_ARCH_VMAP_STACK和VMAP_STACK配置选项能够支持带有保护页的虚拟映射堆栈。 ++当堆栈溢出时,这个特性会引发可靠的异常。溢出后堆栈跟踪的可用性以及对溢出本身的 ++响应取决于架构。 ++ ++.. note:: ++ 截至本文撰写时, arm64, powerpc, riscv, s390, um, 和 x86 支持VMAP_STACK。 ++ ++HAVE_ARCH_VMAP_STACK ++-------------------- ++ ++能够支持虚拟映射内核栈的架构应该启用这个bool配置选项。要求是: ++ ++- vmalloc空间必须大到足以容纳许多内核堆栈。这可能排除了许多32位架构。 ++- vmalloc空间的堆栈需要可靠地工作。例如,如果vmap页表是按需创建的,当堆栈指向 ++ 具有未填充页表的虚拟地址时,这种机制需要工作,或者架构代码(switch_to()和 ++ switch_mm(),很可能)需要确保堆栈的页表项在可能未填充的堆栈上运行之前已经填 ++ 充。 ++- 如果堆栈溢出到一个保护页,就应该发生一些合理的事情。“合理”的定义是灵活的,但 ++ 在没有记录任何东西的情况下立即重启是不友好的。 ++ ++VMAP_STACK ++---------- ++ ++VMAP_STACK bool配置选项在启用时分配虚拟映射的任务栈。这个选项依赖于 ++HAVE_ARCH_VMAP_STACK。 ++ ++- 如果你想使用带有保护页的虚拟映射的内核堆栈,请启用该选项。这将导致内核栈溢出 ++ 被立即捕获,而不是难以诊断的损坏。 ++ ++.. note:: ++ ++ 使用KASAN的这个功能需要架构支持用真实的影子内存来支持虚拟映射,并且 ++ 必须启用KASAN_VMALLOC。 ++ ++.. note:: ++ ++ 启用VMAP_STACK时,无法在堆栈分配的数据上运行DMA。 ++ ++内核配置选项和依赖性不断变化。请参考最新的代码库: ++ ++`Kconfig ` ++ ++分配方法 ++-------- ++ ++当一个新的内核线程被创建时,线程堆栈是由页级分配器分配的虚拟连续的内存页组成。这 ++些页面被映射到有PAGE_KERNEL保护的连续的内核虚拟空间。 ++ ++alloc_thread_stack_node()调用__vmalloc_node_range()来分配带有PAGE_KERNEL ++保护的栈。 ++ ++- 分配的堆栈被缓存起来,以后会被新的线程重用,所以在分配/释放堆栈给任务时,要手动 ++ 进行memcg核算。因此,__vmalloc_node_range被调用时没有__GFP_ACCOUNT。 ++- vm_struct被缓存起来,以便能够找到在中断上下文中启动的空闲线程。 free_thread_stack() ++ 可以在中断上下文中调用。 ++- 在arm64上,所有VMAP的堆栈都需要有相同的对齐方式,以确保VMAP的堆栈溢出检测正常 ++ 工作。架构特定的vmap堆栈分配器照顾到了这个细节。 ++- 这并不涉及中断堆栈--参考原始补丁 ++ ++线程栈分配是由clone()、fork()、vfork()、kernel_thread()通过kernel_clone() ++启动的。留点提示在这,以便搜索代码库,了解线程栈何时以及如何分配。 ++ ++大量的代码是在: ++`kernel/fork.c `. ++ ++task_struct中的stack_vm_area指针可以跟踪虚拟分配的堆栈,一个非空的stack_vm_area ++指针可以表明虚拟映射的内核堆栈已经启用。 ++ ++:: ++ ++ struct vm_struct *stack_vm_area; ++ ++堆栈溢出处理 ++------------ ++ ++前守护页和后守护页有助于检测堆栈溢出。当堆栈溢出到守护页时,处理程序必须小心不要再 ++次溢出堆栈。当处理程序被调用时,很可能只留下很少的堆栈空间。 ++ ++在x86上,这是通过处理表明内核堆栈溢出的双异常堆栈的缺页异常来实现的。 ++ ++用守护页测试VMAP分配 ++-------------------- ++ ++我们如何确保VMAP_STACK在分配时确实有前守护页和后守护页的保护?下面的 lkdtm 测试 ++可以帮助检测任何回归。 ++ ++:: ++ ++ void lkdtm_STACK_GUARD_PAGE_LEADING() ++ void lkdtm_STACK_GUARD_PAGE_TRAILING() ++ ++结论 ++---- ++ ++- vmalloced堆栈的percpu缓存似乎比高阶堆栈分配要快一些,至少在缓存命中时是这样。 ++- THREAD_INFO_IN_TASK完全摆脱了arch-specific thread_info,并简单地将 ++ thread_info(仅包含标志)和'int cpu'嵌入task_struct中。 ++- 一旦任务死亡,线程栈就可以被释放(无需等待RCU),然后,如果使用vmapped栈,就 ++ 可以将整个栈缓存起来,以便在同一cpu上重复使用。 diff --cc Documentation/translations/zh_CN/mm/zsmalloc.rst index 0000000000000,b5596ea08ae48..4c8c9b1006a92 mode 000000,100644..100644 --- a/Documentation/translations/zh_CN/mm/zsmalloc.rst +++ b/Documentation/translations/zh_CN/mm/zsmalloc.rst @@@ -1,0 -1,78 +1,78 @@@ -:Original: Documentation/mm/zs_malloc.rst ++:Original: Documentation/mm/zsmalloc.rst + + :翻译: + + 司延腾 Yanteng Si + + :校译: + + ======== + zsmalloc + ======== + + 这个分配器是为与zram一起使用而设计的。因此,该分配器应该在低内存条件下工作良好。特别是, + 它从未尝试过higher order页面的分配,这在内存压力下很可能会失败。另一方面,如果我们只 + 是使用单(0-order)页,它将遭受非常高的碎片化 - 任何大小为PAGE_SIZE/2或更大的对象将 + 占据整个页面。这是其前身(xvmalloc)的主要问题之一。 + + 为了克服这些问题,zsmalloc分配了一堆0-order页面,并使用各种"struct page"字段将它 + 们链接起来。这些链接的页面作为一个单一的higher order页面,即一个对象可以跨越0-order + 页面的边界。代码将这些链接的页面作为一个实体,称为zspage。 + + 为了简单起见,zsmalloc只能分配大小不超过PAGE_SIZE的对象,因为这满足了所有当前用户的 + 要求(在最坏的情况下,页面是不可压缩的,因此以"原样"即未压缩的形式存储)。对于大于这 + 个大小的分配请求,会返回失败(见zs_malloc)。 + + 此外,zs_malloc()并不返回一个可重复引用的指针。相反,它返回一个不透明的句柄(无符号 + 长),它编码了被分配对象的实际位置。这种间接性的原因是zsmalloc并不保持zspages的永久 + 映射,因为这在32位系统上会导致问题,因为内核空间映射的VA区域非常小。因此,在使用分配 + 的内存之前,对象必须使用zs_map_object()进行映射以获得一个可用的指针,随后使用 + zs_unmap_object()解除映射。 + + stat + ==== + + 通过CONFIG_ZSMALLOC_STAT,我们可以通过 ``/sys/kernel/debug/zsmalloc/`` + 看到zsmalloc内部信息。下面是一个统计输出的例子。:: + + # cat /sys/kernel/debug/zsmalloc/zram0/classes + + class size almost_full almost_empty obj_allocated obj_used pages_used pages_per_zspage + ... + ... + 9 176 0 1 186 129 8 4 + 10 192 1 0 2880 2872 135 3 + 11 208 0 1 819 795 42 2 + 12 224 0 1 219 159 12 4 + ... + ... + + + class + 索引 + size + zspage存储对象大小 + almost_empty + ZS_ALMOST_EMPTY zspage的数量(见下文)。 + almost_full + ZS_ALMOST_FULL zspage的数量(见下图) + obj_allocated + 已分配对象的数量 + obj_used + 分配给用户的对象的数量 + pages_used + 为该类分配的页数 + pages_per_zspage + 组成一个zspage的0-order页面的数量 + + 当n <= N / f时,我们将一个zspage分配给ZS_ALMOST_EMPTYfullness组,其中 + + * n = 已分配对象的数量 + * N = zspage可以存储的对象总数 + * f = fullness_threshold_frac(即,目前是4个) + + 同样地,我们将zspage分配给: + + * ZS_ALMOST_FULL when n > N / f + * ZS_EMPTY when n == 0 + * ZS_FULL when n == N diff --cc arch/loongarch/mm/tlbex.S index de19fa2d7f0d3,e36c2c07dee37..39743337999e9 --- a/arch/loongarch/mm/tlbex.S +++ b/arch/loongarch/mm/tlbex.S @@@ -80,10 -80,10 +80,10 @@@ vmalloc_done_load * see if we need to jump to huge tlb processing. */ andi t0, ra, _PAGE_HUGE - bne t0, $r0, tlb_huge_update_load + bnez t0, tlb_huge_update_load csrrd t0, LOONGARCH_CSR_BADV - srli.d t0, t0, (PAGE_SHIFT + PTE_ORDER) + srli.d t0, t0, PAGE_SHIFT andi t0, t0, (PTRS_PER_PTE - 1) slli.d t0, t0, _PTE_T_LOG2 add.d t1, ra, t0 @@@ -244,10 -244,10 +244,10 @@@ vmalloc_done_store * see if we need to jump to huge tlb processing. */ andi t0, ra, _PAGE_HUGE - bne t0, $r0, tlb_huge_update_store + bnez t0, tlb_huge_update_store csrrd t0, LOONGARCH_CSR_BADV - srli.d t0, t0, (PAGE_SHIFT + PTE_ORDER) + srli.d t0, t0, PAGE_SHIFT andi t0, t0, (PTRS_PER_PTE - 1) slli.d t0, t0, _PTE_T_LOG2 add.d t1, ra, t0 @@@ -411,10 -411,10 +411,10 @@@ vmalloc_done_modify * see if we need to jump to huge tlb processing. */ andi t0, ra, _PAGE_HUGE - bne t0, $r0, tlb_huge_update_modify + bnez t0, tlb_huge_update_modify csrrd t0, LOONGARCH_CSR_BADV - srli.d t0, t0, (PAGE_SHIFT + PTE_ORDER) + srli.d t0, t0, PAGE_SHIFT andi t0, t0, (PTRS_PER_PTE - 1) slli.d t0, t0, _PTE_T_LOG2 add.d t1, ra, t0 diff --cc drivers/nvdimm/pmem.c index f36efcc11f675,107c9cb3d57d7..7e88cd2423805 --- a/drivers/nvdimm/pmem.c +++ b/drivers/nvdimm/pmem.c @@@ -450,9 -450,24 +450,24 @@@ static void pmem_release_disk(void *__p put_dax(pmem->dax_dev); del_gendisk(pmem->disk); - blk_cleanup_disk(pmem->disk); + put_disk(pmem->disk); } + static int pmem_pagemap_memory_failure(struct dev_pagemap *pgmap, + unsigned long pfn, unsigned long nr_pages, int mf_flags) + { + struct pmem_device *pmem = + container_of(pgmap, struct pmem_device, pgmap); + u64 offset = PFN_PHYS(pfn) - pmem->phys_addr - pmem->data_offset; + u64 len = nr_pages << PAGE_SHIFT; + + return dax_holder_notify_failure(pmem->dax_dev, offset, len, mf_flags); + } + + static const struct dev_pagemap_ops fsdax_pagemap_ops = { + .memory_failure = pmem_pagemap_memory_failure, + }; + static int pmem_attach_disk(struct device *dev, struct nd_namespace_common *ndns) { diff --cc drivers/of/fdt.c index e02a30c927191,2c677e84c3f5d..d170c88359fe4 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@@ -529,11 -529,11 +529,11 @@@ static int __init __reserved_mem_reserv pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %lu MiB\n", uname, &base, (unsigned long)(size / SZ_1M)); if (!nomap) - kmemleak_alloc_phys(base, size, 0, 0); + kmemleak_alloc_phys(base, size, 0); } else - pr_info("Reserved memory: failed to reserve memory for node '%s': base %pa, size %lu MiB\n", - uname, &base, (unsigned long)(size / SZ_1M)); + pr_err("Reserved memory: failed to reserve memory for node '%s': base %pa, size %lu MiB\n", + uname, &base, (unsigned long)(size / SZ_1M)); len -= t_len; if (first) { diff --cc fs/xfs/xfs_notify_failure.c index 0000000000000,aa8dc27c599ce..69d9c83ea4b21 mode 000000,100644..100644 --- a/fs/xfs/xfs_notify_failure.c +++ b/fs/xfs/xfs_notify_failure.c @@@ -1,0 -1,220 +1,226 @@@ + // SPDX-License-Identifier: GPL-2.0 + /* + * Copyright (c) 2022 Fujitsu. All Rights Reserved. + */ + + #include "xfs.h" + #include "xfs_shared.h" + #include "xfs_format.h" + #include "xfs_log_format.h" + #include "xfs_trans_resv.h" + #include "xfs_mount.h" + #include "xfs_alloc.h" + #include "xfs_bit.h" + #include "xfs_btree.h" + #include "xfs_inode.h" + #include "xfs_icache.h" + #include "xfs_rmap.h" + #include "xfs_rmap_btree.h" + #include "xfs_rtalloc.h" + #include "xfs_trans.h" ++#include "xfs_ag.h" + + #include + #include + + struct failure_info { + xfs_agblock_t startblock; + xfs_extlen_t blockcount; + int mf_flags; + }; + + static pgoff_t + xfs_failure_pgoff( + struct xfs_mount *mp, + const struct xfs_rmap_irec *rec, + const struct failure_info *notify) + { + loff_t pos = XFS_FSB_TO_B(mp, rec->rm_offset); + + if (notify->startblock > rec->rm_startblock) + pos += XFS_FSB_TO_B(mp, + notify->startblock - rec->rm_startblock); + return pos >> PAGE_SHIFT; + } + + static unsigned long + xfs_failure_pgcnt( + struct xfs_mount *mp, + const struct xfs_rmap_irec *rec, + const struct failure_info *notify) + { + xfs_agblock_t end_rec; + xfs_agblock_t end_notify; + xfs_agblock_t start_cross; + xfs_agblock_t end_cross; + + start_cross = max(rec->rm_startblock, notify->startblock); + + end_rec = rec->rm_startblock + rec->rm_blockcount; + end_notify = notify->startblock + notify->blockcount; + end_cross = min(end_rec, end_notify); + + return XFS_FSB_TO_B(mp, end_cross - start_cross) >> PAGE_SHIFT; + } + + static int + xfs_dax_failure_fn( + struct xfs_btree_cur *cur, + const struct xfs_rmap_irec *rec, + void *data) + { + struct xfs_mount *mp = cur->bc_mp; + struct xfs_inode *ip; + struct failure_info *notify = data; + int error = 0; + + if (XFS_RMAP_NON_INODE_OWNER(rec->rm_owner) || + (rec->rm_flags & (XFS_RMAP_ATTR_FORK | XFS_RMAP_BMBT_BLOCK))) { + xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_ONDISK); + return -EFSCORRUPTED; + } + + /* Get files that incore, filter out others that are not in use. */ + error = xfs_iget(mp, cur->bc_tp, rec->rm_owner, XFS_IGET_INCORE, + 0, &ip); + /* Continue the rmap query if the inode isn't incore */ + if (error == -ENODATA) + return 0; + if (error) + return error; + + error = mf_dax_kill_procs(VFS_I(ip)->i_mapping, + xfs_failure_pgoff(mp, rec, notify), + xfs_failure_pgcnt(mp, rec, notify), + notify->mf_flags); + xfs_irele(ip); + return error; + } + + static int + xfs_dax_notify_ddev_failure( + struct xfs_mount *mp, + xfs_daddr_t daddr, + xfs_daddr_t bblen, + int mf_flags) + { + struct xfs_trans *tp = NULL; + struct xfs_btree_cur *cur = NULL; + struct xfs_buf *agf_bp = NULL; + int error = 0; + xfs_fsblock_t fsbno = XFS_DADDR_TO_FSB(mp, daddr); + xfs_agnumber_t agno = XFS_FSB_TO_AGNO(mp, fsbno); + xfs_fsblock_t end_fsbno = XFS_DADDR_TO_FSB(mp, daddr + bblen); + xfs_agnumber_t end_agno = XFS_FSB_TO_AGNO(mp, end_fsbno); + + error = xfs_trans_alloc_empty(mp, &tp); + if (error) + return error; + + for (; agno <= end_agno; agno++) { + struct xfs_rmap_irec ri_low = { }; + struct xfs_rmap_irec ri_high; + struct failure_info notify; + struct xfs_agf *agf; + xfs_agblock_t agend; ++ struct xfs_perag *pag; + - error = xfs_alloc_read_agf(mp, tp, agno, 0, &agf_bp); - if (error) ++ pag = xfs_perag_get(mp, agno); ++ error = xfs_alloc_read_agf(pag, tp, 0, &agf_bp); ++ if (error) { ++ xfs_perag_put(pag); + break; ++ } + - cur = xfs_rmapbt_init_cursor(mp, tp, agf_bp, agf_bp->b_pag); ++ cur = xfs_rmapbt_init_cursor(mp, tp, agf_bp, pag); + + /* + * Set the rmap range from ri_low to ri_high, which represents + * a [start, end] where we looking for the files or metadata. + */ + memset(&ri_high, 0xFF, sizeof(ri_high)); + ri_low.rm_startblock = XFS_FSB_TO_AGBNO(mp, fsbno); + if (agno == end_agno) + ri_high.rm_startblock = XFS_FSB_TO_AGBNO(mp, end_fsbno); + + agf = agf_bp->b_addr; + agend = min(be32_to_cpu(agf->agf_length), + ri_high.rm_startblock); + notify.startblock = ri_low.rm_startblock; + notify.blockcount = agend - ri_low.rm_startblock; + + error = xfs_rmap_query_range(cur, &ri_low, &ri_high, + xfs_dax_failure_fn, ¬ify); + xfs_btree_del_cursor(cur, error); + xfs_trans_brelse(tp, agf_bp); ++ xfs_perag_put(pag); + if (error) + break; + + fsbno = XFS_AGB_TO_FSB(mp, agno + 1, 0); + } + + xfs_trans_cancel(tp); + return error; + } + + static int + xfs_dax_notify_failure( + struct dax_device *dax_dev, + u64 offset, + u64 len, + int mf_flags) + { + struct xfs_mount *mp = dax_holder(dax_dev); + u64 ddev_start; + u64 ddev_end; + + if (!(mp->m_sb.sb_flags & SB_BORN)) { + xfs_warn(mp, "filesystem is not ready for notify_failure()!"); + return -EIO; + } + + if (mp->m_rtdev_targp && mp->m_rtdev_targp->bt_daxdev == dax_dev) { + xfs_warn(mp, + "notify_failure() not supported on realtime device!"); + return -EOPNOTSUPP; + } + + if (mp->m_logdev_targp && mp->m_logdev_targp->bt_daxdev == dax_dev && + mp->m_logdev_targp != mp->m_ddev_targp) { + xfs_err(mp, "ondisk log corrupt, shutting down fs!"); + xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_ONDISK); + return -EFSCORRUPTED; + } + + if (!xfs_has_rmapbt(mp)) { + xfs_warn(mp, "notify_failure() needs rmapbt enabled!"); + return -EOPNOTSUPP; + } + + ddev_start = mp->m_ddev_targp->bt_dax_part_off; + ddev_end = ddev_start + bdev_nr_bytes(mp->m_ddev_targp->bt_bdev) - 1; + + /* Ignore the range out of filesystem area */ + if (offset + len < ddev_start) + return -ENXIO; + if (offset > ddev_end) + return -ENXIO; + + /* Calculate the real range when it touches the boundary */ + if (offset > ddev_start) + offset -= ddev_start; + else { + len -= ddev_start - offset; + offset = 0; + } + if (offset + len > ddev_end) + len -= ddev_end - offset; + + return xfs_dax_notify_ddev_failure(mp, BTOBB(offset), BTOBB(len), + mf_flags); + } + + const struct dax_holder_operations xfs_dax_holder_operations = { + .notify_failure = xfs_dax_notify_failure, + }; diff --cc include/linux/pagevec.h index 6649154a2115e,b0e3540f3a4c6..215eb6c3bdc9f --- a/include/linux/pagevec.h +++ b/include/linux/pagevec.h @@@ -26,7 -26,16 +26,6 @@@ struct pagevec }; void __pagevec_release(struct pagevec *pvec); - void __pagevec_lru_add(struct pagevec *pvec); -unsigned pagevec_lookup_range(struct pagevec *pvec, - struct address_space *mapping, - pgoff_t *start, pgoff_t end); -static inline unsigned pagevec_lookup(struct pagevec *pvec, - struct address_space *mapping, - pgoff_t *start) -{ - return pagevec_lookup_range(pvec, mapping, start, (pgoff_t)-1); -} - unsigned pagevec_lookup_range_tag(struct pagevec *pvec, struct address_space *mapping, pgoff_t *index, pgoff_t end, xa_mark_t tag); diff --cc lib/Kconfig.debug index 35cd8287642ae,0b483a8da409d..403071ff0bcfb --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@@ -699,6 -699,15 +699,14 @@@ config DEBUG_OBJECTS_ENABLE_DEFAUL help Debug objects boot parameter default value + config SHRINKER_DEBUG - default y + bool "Enable shrinker debugging support" + depends on DEBUG_FS + help + Say Y to enable the shrinker debugfs interface which provides + visibility into the kernel memory shrinkers subsystem. + Disable it to avoid an extra memory footprint. + config HAVE_DEBUG_KMEMLEAK bool diff --cc mm/internal.h index ddd2d6a46f1b7,caebaeb2e5c99..785409805ed79 --- a/mm/internal.h +++ b/mm/internal.h @@@ -861,6 -862,22 +862,24 @@@ struct folio *try_grab_folio(struct pag DECLARE_PER_CPU(struct per_cpu_nodestat, boot_nodestats); +extern bool mirrored_kernelcore; + + static inline bool vma_soft_dirty_enabled(struct vm_area_struct *vma) + { + /* + * NOTE: we must check this before VM_SOFTDIRTY on soft-dirty + * enablements, because when without soft-dirty being compiled in, + * VM_SOFTDIRTY is defined as 0x0, then !(vm_flags & VM_SOFTDIRTY) + * will be constantly true. + */ + if (!IS_ENABLED(CONFIG_MEM_SOFT_DIRTY)) + return false; + + /* + * Soft-dirty is kind of special: its tracking is enabled when the + * vma flags not set. + */ + return !(vma->vm_flags & VM_SOFTDIRTY); + } + #endif /* __MM_INTERNAL_H */ diff --cc tools/testing/selftests/vm/Makefile index 108587cb327a9,6a34209379a47..d9fa6a9ea5844 --- a/tools/testing/selftests/vm/Makefile +++ b/tools/testing/selftests/vm/Makefile @@@ -93,7 -93,9 +93,8 @@@ TEST_PROGS := run_vmtests.s TEST_FILES := test_vmalloc.sh TEST_FILES += test_hmm.sh + TEST_FILES += va_128TBswitch.sh -KSFT_KHDR_INSTALL := 1 include ../lib.mk $(OUTPUT)/madv_populate: vm_util.c