移入和移出裝置記憶體
===================
因為 CPU 不能直接存取裝置記憶體,所以裝置驅動程式必須
使用硬體 DMA 或裝置特定的 load/store 指令來遷移資料。
migrate_vma_setup()、migrate_vma_pages()
和 migrate_vma_finalize() 函數旨在使驅動程式更易於撰寫
以及集中通用的跨驅動程式程式碼。
在將分頁遷移到裝置私有記憶體前,需要創建特殊的、裝置私有的 "struct page"。
這些將用作特殊的 “swap” 分頁表項,使 CPU 程序在嘗試存取
已遷移到裝置私有記憶體上的分頁會產生錯誤。
可以通過以下方式分配和釋放::
struct resource *res;
struct dev_pagemap pagemap;
res = request_free_mem_region(&iomem_resource, /* number of bytes */,
"name of driver resource");
pagemap.type = MEMORY_DEVICE_PRIVATE;
pagemap.range.start = res->start;
pagemap.range.end = res->end;
pagemap.nr_range = 1;
pagemap.ops = &device_devmem_ops;
memremap_pages(&pagemap, numa_node_id());
memunmap_pages(&pagemap);
release_mem_region(pagemap.range.start, range_len(&pagemap.range));
當資源可以綁定到 “struct device” 時,
還有 devm_request_free_mem_region()、devm_memremap_pages()、
devm_memunmap_pages() 和 devm_release_mem_region() 可用。
整體遷移步驟類似於在系統記憶體上遷移 NUMA 分頁
(見:ref:`頁面遷移 <page_migration>`)只不過步驟是分開在
裝置驅動程式特定的程式碼和共享通用的程式碼之間:
1. ``mmap_read_lock()``
裝置驅動程式必須傳遞一個 ``struct vm_area_struct``
到 migrate_vma_setup() 所以必須在遷移中持有 mmap_read_lock()
或 mmap_write_lock()。
2. ``migrate_vma_setup(struct migrate_vma *args)``
裝置驅動程式初始化 ``struct migrate_vma`` 欄位
並傳遞該指標到 migrate_vma_setup()。
``args->flags`` 欄位用於篩選應遷移那些分頁。
例如,設置 ``MIGRATE_VMA_SELECT_SYSTEM`` 只會遷移系統記憶體;
而 ``MIGRATE_VMA_SELECT_DEVICE_PRIVATE`` 只會遷移駐留在
裝置上的私有記憶體。如果設置了後者,則 ``args->pgmap_owner`` 欄位
是用於標識驅動程式擁有的裝置私有分頁。
這樣能夠避免嘗試遷移駐留在其他設備中的裝置私有分頁。
目前只有匿名私有 VMA 範圍能夠從在系統記憶體和裝置記憶體之間做遷移。
migrate_vma_setup() 所做的第一步是使用
``mmu_notifier_invalidate_range_start()``
和 ``mmu_notifier_invalidate_range_end()``
在解析分頁表(page table walk) 附近,
用要被遷移的 PFNs 填充 ``args->src`` 陣列,
來使其他裝置的 MMU 被無效化(invalidate)。
``invalidate_range_start()`` 回呼函數被傳遞一個
``struct mmu_notifier_range``
其中 ``event`` 欄位被設置為 ``MMU_NOTIFY_MIGRATE``
還有 ``owner`` 欄位被設置為
傳遞給 migrate_vma_setup() 的 ``args->pgmap_owner``。
這使裝置驅動程式能跳過失效回呼函數,
僅使實際遷移的裝置私有 MMU 映射無效。
這將在下一節中詳細解釋。
在遍歷分頁表時,``pte_none()`` 或 ``is_zero_pfn()`` 項
導致一個有效的 “zero” PFN 被存在 ``args->src`` 陣列中。
這讓驅動程式分配裝置私有記憶體並將之它清零而非複製一個分頁的零。
系統記憶體中的有效 PTE 項或裝置私有 struct page 將
被 “lock_page()” 鎖上(locked)、
被獨立於 LRU(系統記憶體因為裝置私有分業並不在 LRU 上)、
被程序中取消映射,
一個特殊遷移的 PTE 會被插入到原始 PTE 的位置。
migrate_vma_setup() 也會清空 ``args->dst`` 陣列。
3. 裝置驅動程式分配目標分頁,並將來源分頁複製到目標分頁。
驅動程式檢查每個 ``src`` 項以查看 ``MIGRATE_PFN_MIGRATE``位元
有沒有被設置,並跳過沒有在遷移的項。
裝置驅動程式也可以選擇不填寫該分頁 ``dst`` 陣列,
來跳過遷移該分頁。
然後驅動程式分配一個裝置私有的 struct page
或一個系統記憶體分頁,用 ``lock_page()`` 鎖上該頁,
並填入 ``dst`` 陣列項::
dst[i] = migrate_pfn(page_to_pfn(dpage)) | MIGRATE_PFN_LOCKED;
現在驅動程式知道這個頁面正在被遷移,
它可以使裝置私有 MMU 映射無效並複製裝置私有記憶體
到系統記憶體或其他裝置的私有分頁。
Linux 核心主體會處理 CPU 分頁表無效化,
因此裝置驅動程式只需使其自己的 MMU 映射無效。
驅動程式可以使用 “migrate_pfn_to_page(src[i])” 來取得來源的 ``struct page``,
然後將來源分頁複製到目標上,
或如果指標是 ``NULL`` 表示來源分頁並未被填充到系統記憶體中,
而清空目標裝置私有記憶體。
4. ``migrate_vma_pages()``
這一步是實際遷移 "被執行" 的地方。
如果來源分頁是 ``pte_none()`` 或 ``is_zero_pfn()`` 分頁,
這步驟是將新分配的分頁插入 CPU 頁表的位置。
如果 CPU 執行續在同一分頁上出現錯誤,這可能會失敗。
然而,分頁表被鎖上,只有一個新頁面將被插入。
如果裝置驅動程式輸掉競爭(race),它將看到 “MIGRATE_PFN_MIGRATE” 位元被清除。
如果來源分頁被鎖定、隔離等,
來源的 ``struct page`` 訊息被複製到目標 ``struct page``
在 CPU 端完成遷移。
If the source page was locked, isolated, etc. the source ``struct page``
information is now copied to destination ``struct page`` finalizing the
migration on the CPU side.
5. 裝置驅動程式為仍在遷移的分頁更新裝置 MMU 分頁表,回溯不遷移的分頁。
如果 src 項仍然設置了 MIGRATE_PFN_MIGRATE 位元,
則裝置驅動程式可以更新裝置 MMU 並,
如果 ``MIGRATE_PFN_WRITE`` 位已設置,就設置 write enable 位元。
6. ``migrate_vma_finalize()``
這一步將特殊的遷移分頁表項替換為新分頁的頁表項
並釋放對來源和目標 ``struct page`` 的引用。
7. ``mmap_read_unlock()``
鎖現在可以被釋放了。
Exclusive access memory
=======================
某些裝置具有諸如原子 PTE 位元之類的功能,
可用於實做系統記憶體上的原子操作。
支援對共享記憶體分頁的原子操作,
這樣的裝置需要對該分頁有,單獨於其他使用者空間 CPU 存取的能力。
``make_device_exclusive_range()`` 函數
可使使用者空間無法存取該記憶體範圍。
這將以特殊的 swap 項替換掉該範圍內所有分頁的映射。
任何存取 swap 項的嘗試都會導致錯誤,
而這個錯誤會透過用原始映射來替換這個 swap 項來解決。
裝置驅動程式會被 MMU notifier 通知,映射已被更改,
此後它將不再具有對該分頁的單獨存取權限。
單獨存取會被保證持續到驅動程序取消分頁鎖定(page lock)和分頁參考(page reference),
在這之後的任何 CPU 錯誤都可以按照描述進行處理。
記憶體 cgroup (memcg) 和 rss 帳務紀錄
====================================
目前,裝置記憶體在 rss 計數器中被視為一般分頁
(如果裝置分頁用於匿名分頁,則匿名,如果裝置分頁用於文件儲存的分頁,則是文件,
如果設備頁面用於共享記憶體,則為 shmem)。
這是一個有意為之的選擇,讓可能開始運用裝置記憶體的現有應用程式,
不知道有這樣的存在,使其運行不受影響。
有一個缺點是 OOM 可能會清掉使用大量裝置記憶體的應用程式,
而非使用很多一般系統記憶體的,
因此不會釋放太多系統記憶體。
我們希望在決定以不同的方式計算裝置記憶體之前,
能收集更多應用程式和系統在有裝置記憶體的情況下,
對於記憶體資源壓力反應的實際經驗。
並且對記憶體 cgroup 做出了相同的決定。
裝置記憶體分頁和一般分頁是用相同的記憶體 cgroup 來做計算。
這確實簡化了裝置記憶體的遷移。
這也意味著從裝置記憶體遷移到一般記憶體不會失敗,
因為這並不在記憶體 cgroup 的限制。
在我們得到更多關於裝置記憶體是如何使用,且它對記憶體資源控制的影響後,
可能會重新審視這個選擇。
請注意,裝置記憶體永遠不能由裝置驅動程式或通過 GUP 固定(pinned),
因此這種記憶體在程序退出時會被釋放。或在共享記憶體及文件儲存的記憶體的情況下,
會在最後一次的存取時被刪除(dropped)。
This is allows the device driver
多了一個 isis to invalidate other device's MMUs with the ``mmu_notifier_invalidate_range_start(()`` and ``mmu_notifier_invalidate_range_end()`` calls around the page table walks to fill in the ``args->src`` array with PFNs to be migrated.
也不太確定是在 page table walk 的時候,還是是在 page table walk 的附近,可能要再研究一下~This also means that migration back from device memory to regular memory cannot fail because it would go above memory cgroup limit.
不是很確定這裡的 go above 是指 "超過" 還是 "bypass" 的意思,感覺要是 bypass 比較合理~今天先翻譯完,明天來整理一下這整份文件的摘要!
所以其實 typo 還是蠻多的嘛!30 天過後多送幾個 PR 吧~
OKOK!就是打算 30 天過後來整理一下送 patch!
(( 我可以回文了!!XDDD