요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
====================================================
pin_user_pages() and related calls
====================================================
.. contents:: :local:
Overview
========
This document describes the following functions::
pin_user_pages()
pin_user_pages_fast()
pin_user_pages_remote()
Basic description of FOLL_PIN
=============================
FOLL_PIN and FOLL_LONGTERM are flags that can be passed to the get_user_pages*()
("gup") family of functions. FOLL_PIN has significant interactions and
interdependencies with FOLL_LONGTERM, so both are covered here.
FOLL_PIN is internal to gup, meaning that it should not appear at the gup call
sites. This allows the associated wrapper functions (pin_user_pages*() and
others) to set the correct combination of these flags, and to check for problems
as well.
FOLL_LONGTERM, on the other hand, *is* allowed to be set at the gup call sites.
This is in order to avoid creating a large number of wrapper functions to cover
all combinations of get*(), pin*(), FOLL_LONGTERM, and more. Also, the
pin_user_pages*() APIs are clearly distinct from the get_user_pages*() APIs, so
that's a natural dividing line, and a good point to make separate wrapper calls.
In other words, use pin_user_pages*() for DMA-pinned pages, and
get_user_pages*() for other cases. There are five cases described later on in
this document, to further clarify that concept.
FOLL_PIN and FOLL_GET are mutually exclusive for a given gup call. However,
multiple threads and call sites are free to pin the same struct pages, via both
FOLL_PIN and FOLL_GET. It's just the call site that needs to choose one or the
other, not the struct page(s).
The FOLL_PIN implementation is nearly the same as FOLL_GET, except that FOLL_PIN
uses a different reference counting technique.
FOLL_PIN is a prerequisite to FOLL_LONGTERM. Another way of saying that is,
FOLL_LONGTERM is a specific case, more restrictive case of FOLL_PIN.
Which flags are set by each wrapper
===================================
For these pin_user_pages*() functions, FOLL_PIN is OR'd in with whatever gup
flags the caller provides. The caller is required to pass in a non-null struct
pages* array, and the function then pins pages by incrementing each by a special
value: GUP_PIN_COUNTING_BIAS.
For large folios, the GUP_PIN_COUNTING_BIAS scheme is not used. Instead,
the extra space available in the struct folio is used to store the
pincount directly.
This approach for large folios avoids the counting upper limit problems
that are discussed below. Those limitations would have been aggravated
severely by huge pages, because each tail page adds a refcount to the
head page. And in fact, testing revealed that, without a separate pincount
field, refcount overflows were seen in some huge page stress tests.
This also means that huge pages and large folios do not suffer
from the false positives problem that is mentioned below.::
Function
--------
pin_user_pages FOLL_PIN is always set internally by this function.
pin_user_pages_fast FOLL_PIN is always set internally by this function.
pin_user_pages_remote FOLL_PIN is always set internally by this function.
For these get_user_pages*() functions, FOLL_GET might not even be specified.
Behavior is a little more complex than above. If FOLL_GET was *not* specified,
but the caller passed in a non-null struct pages* array, then the function
sets FOLL_GET for you, and proceeds to pin pages by incrementing the refcount
of each page by +1.::
Function
--------
get_user_pages FOLL_GET is sometimes set internally by this function.
get_user_pages_fast FOLL_GET is sometimes set internally by this function.
get_user_pages_remote FOLL_GET is sometimes set internally by this function.
Tracking dma-pinned pages
=========================
Some of the key design constraints, and solutions, for tracking dma-pinned
pages:
* An actual reference count, per struct page, is required. This is because
multiple processes may pin and unpin a page.
* False positives (reporting that a page is dma-pinned, when in fact it is not)
are acceptable, but false negatives are not.
* struct page may not be increased in size for this, and all fields are already
used.
* Given the above, we can overload the page->_refcount field by using, sort of,
the upper bits in that field for a dma-pinned count. "Sort of", means that,
rather than dividing page->_refcount into bit fields, we simple add a medium-
large value (GUP_PIN_COUNTING_BIAS, initially chosen to be 1024: 10 bits) to
page->_refcount. This provides fuzzy behavior: if a page has get_page() called
on it 1024 times, then it will appear to have a single dma-pinned count.
And again, that's acceptable.
This also leads to limitations: there are only 31-10==21 bits available for a
counter that increments 10 bits at a time.
* Because of that limitation, special handling is applied to the zero pages
when using FOLL_PIN. We only pretend to pin a zero page - we don't alter its
refcount or pincount at all (it is permanent, so there's no need). The
unpinning functions also don't do anything to a zero page. This is
transparent to the caller.
* Callers must specifically request "dma-pinned tracking of pages". In other
words, just calling get_user_pages() will not suffice; a new set of functions,
pin_user_page() and related, must be used.
FOLL_PIN, FOLL_GET, FOLL_LONGTERM: when to use which flags
==========================================================
Thanks to Jan Kara, Vlastimil Babka and several other -mm people, for describing
these categories:
CASE 1: Direct IO (DIO)
-----------------------
There are GUP references to pages that are serving
as DIO buffers. These buffers are needed for a relatively short time (so they
are not "long term"). No special synchronization with folio_mkclean() or
munmap() is provided. Therefore, flags to set at the call site are: ::
FOLL_PIN
...but rather than setting FOLL_PIN directly, call sites should use one of
the pin_user_pages*() routines that set FOLL_PIN.
CASE 2: RDMA
------------
There are GUP references to pages that are serving as DMA
buffers. These buffers are needed for a long time ("long term"). No special
synchronization with folio_mkclean() or munmap() is provided. Therefore, flags
to set at the call site are: ::
FOLL_PIN | FOLL_LONGTERM
NOTE: Some pages, such as DAX pages, cannot be pinned with longterm pins. That's
because DAX pages do not have a separate page cache, and so "pinning" implies
locking down file system blocks, which is not (yet) supported in that way.
.. _mmu-notifier-registration-case:
CASE 3: MMU notifier registration, with or without page faulting hardware
-------------------------------------------------------------------------
Device drivers can pin pages via get_user_pages*(), and register for mmu
notifier callbacks for the memory range. Then, upon receiving a notifier
"invalidate range" callback , stop the device from using the range, and unpin
the pages. There may be other possible schemes, such as for example explicitly
synchronizing against pending IO, that accomplish approximately the same thing.
Or, if the hardware supports replayable page faults, then the device driver can
avoid pinning entirely (this is ideal), as follows: register for mmu notifier
callbacks as above, but instead of stopping the device and unpinning in the
callback, simply remove the range from the device's page tables.
Either way, as long as the driver unpins the pages upon mmu notifier callback,
then there is proper synchronization with both filesystem and mm
(folio_mkclean(), munmap(), etc). Therefore, neither flag needs to be set.
CASE 4: Pinning for struct page manipulation only
-------------------------------------------------
If only struct page data (as opposed to the actual memory contents that a page
is tracking) is affected, then normal GUP calls are sufficient, and neither flag
needs to be set.
CASE 5: Pinning in order to write to the data within the page
-------------------------------------------------------------
Even though neither DMA nor Direct IO is involved, just a simple case of "pin,
write to a page's data, unpin" can cause a problem. Case 5 may be considered a
superset of Case 1, plus Case 2, plus anything that invokes that pattern. In
other words, if the code is neither Case 1 nor Case 2, it may still require
FOLL_PIN, for patterns like this:
Correct (uses FOLL_PIN calls):
pin_user_pages()
write to the data within the pages
unpin_user_pages()
INCORRECT (uses FOLL_GET calls):
get_user_pages()
write to the data within the pages
put_page()
folio_maybe_dma_pinned(): the whole point of pinning
====================================================
The whole point of marking folios as "DMA-pinned" or "gup-pinned" is to be able
to query, "is this folio DMA-pinned?" That allows code such as folio_mkclean()
(and file system writeback code in general) to make informed decisions about
what to do when a folio cannot be unmapped due to such pins.
What to do in those cases is the subject of a years-long series of discussions
and debates (see the References at the end of this document). It's a TODO item
here: fill in the details once that's worked out. Meanwhile, it's safe to say
that having this available: ::
static inline bool folio_maybe_dma_pinned(struct folio *folio)
...is a prerequisite to solving the long-running gup+DMA problem.
Another way of thinking about FOLL_GET, FOLL_PIN, and FOLL_LONGTERM
===================================================================
Another way of thinking about these flags is as a progression of restrictions:
FOLL_GET is for struct page manipulation, without affecting the data that the
struct page refers to. FOLL_PIN is a *replacement* for FOLL_GET, and is for
short term pins on pages whose data *will* get accessed. As such, FOLL_PIN is
a "more severe" form of pinning. And finally, FOLL_LONGTERM is an even more
restrictive case that has FOLL_PIN as a prerequisite: this is for pages that
will be pinned longterm, and whose data will be accessed.
Unit testing
============
This file::
tools/testing/selftests/mm/gup_test.c
has the following new calls to exercise the new pin*() wrapper functions:
* PIN_FAST_BENCHMARK (./gup_test -a)
* PIN_BASIC_TEST (./gup_test -b)
You can monitor how many total dma-pinned pages have been acquired and released
since the system was booted, via two new /proc/vmstat entries: ::
/proc/vmstat/nr_foll_pin_acquired
/proc/vmstat/nr_foll_pin_released
Under normal conditions, these two values will be equal unless there are any
long-term [R]DMA pins in place, or during pin/unpin transitions.
* nr_foll_pin_acquired: This is the number of logical pins that have been
acquired since the system was powered on. For huge pages, the head page is
pinned once for each page (head page and each tail page) within the huge page.
This follows the same sort of behavior that get_user_pages() uses for huge
pages: the head page is refcounted once for each tail or head page in the huge
page, when get_user_pages() is applied to a huge page.
* nr_foll_pin_released: The number of logical pins that have been released since
the system was powered on. Note that pages are released (unpinned) on a
PAGE_SIZE granularity, even if the original pin was applied to a huge page.
Becaused of the pin count behavior described above in "nr_foll_pin_acquired",
the accounting balances out, so that after doing this::
pin_user_pages(huge_page);
for (each page in huge_page)
unpin_user_page(page);
...the following is expected::
nr_foll_pin_released == nr_foll_pin_acquired
(...unless it was already out of balance due to a long-term RDMA pin being in
place.)
Other diagnostics
=================
dump_page() has been enhanced slightly to handle these new counting
fields, and to better report on large folios in general. Specifically,
for large folios, the exact pincount is reported.
References
==========
* `Some slow progress on get_user_pages() (Apr 2, 2019) <https://lwn.net/Articles/784574/>`_
* `DMA and get_user_pages() (LPC: Dec 12, 2018) <https://lwn.net/Articles/774411/>`_
* `The trouble with get_user_pages() (Apr 30, 2018) <https://lwn.net/Articles/753027/>`_
* `LWN kernel index: get_user_pages() <https://lwn.net/Kernel/Index/#Memory_management-get_user_pages>`_
John Hubbard, October, 2019
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
pin_user_pages() 계열 개요
1-17SPDX 라이선스 식별자는 GPL-2.0입니다.
`pin_user_pages()`와 관련 호출
이 문서는 다음 함수들을 설명합니다.
pin_user_pages()
pin_user_pages_fast()
pin_user_pages_remote()
FOLL_PIN의 기본 설명
18-49`FOLL_PIN`의 기본 설명 (Basic description of FOLL_PIN)
`FOLL_PIN`과 `FOLL_LONGTERM`은 `get_user_pages*()` 함수군, 즉 gup 함수에 전달할 수 있는 플래그입니다. 두 플래그 사이에는 중요한 상호작용과 의존성이 있으므로 이 문서에서는 함께 다룹니다.
`FOLL_PIN`은 gup 내부용 플래그이므로 gup 호출 지점에 직접 나타나면 안 됩니다. 관련 래퍼 함수인 `pin_user_pages*()` 등이 올바른 플래그 조합을 설정하고 문제도 검사하도록 하기 위한 설계입니다.
반면 `FOLL_LONGTERM`은 gup 호출 지점에서 설정할 수 있습니다. 이는 `get*()`, `pin*()`, `FOLL_LONGTERM` 등의 모든 조합을 처리하는 래퍼를 대량으로 만들지 않기 위해서입니다. 또한 `pin_user_pages*()` API는 `get_user_pages*()` API와 분명히 구별되므로 별도의 래퍼 호출을 두기 좋은 경계입니다.
다시 말해 DMA로 고정할 페이지에는 `pin_user_pages*()`를 사용하고, 다른 경우에는 `get_user_pages*()`를 사용합니다. 뒤에서 이 구분을 더 명확히 하는 다섯 가지 사례를 설명합니다.
한 번의 gup 호출에서는 `FOLL_PIN`과 `FOLL_GET`을 함께 사용할 수 없습니다. 그러나 여러 스레드와 호출 지점이 같은 `struct page`를 각각 `FOLL_PIN`과 `FOLL_GET`으로 참조하는 것은 허용됩니다. 둘 중 하나를 선택해야 하는 주체는 각 호출 지점이지 `struct page` 자체가 아닙니다.
`FOLL_PIN` 구현은 `FOLL_GET`과 거의 같지만 서로 다른 참조 횟수 계산 기법을 사용합니다.
`FOLL_PIN`은 `FOLL_LONGTERM`의 전제 조건입니다. 달리 말하면 `FOLL_LONGTERM`은 `FOLL_PIN`보다 더 제한적인 특수 사례입니다.
래퍼별 플래그 설정
50-88각 래퍼가 설정하는 플래그
`pin_user_pages*()` 함수는 호출자가 제공한 gup 플래그에 `FOLL_PIN`을 OR 연산으로 추가합니다. 호출자는 NULL이 아닌 `struct page *` 배열을 전달해야 하며, 함수는 각 페이지의 참조값을 특수한 값 `GUP_PIN_COUNTING_BIAS`만큼 증가시켜 페이지를 고정합니다.
대형 folio에는 `GUP_PIN_COUNTING_BIAS` 방식을 사용하지 않습니다. 대신 `struct folio`에 남는 공간에 `pincount`를 직접 저장합니다.
대형 folio에 별도 `pincount`를 두면 뒤에서 설명할 계수 상한 문제를 피할 수 있습니다. Huge page에서는 각 tail page가 head page의 refcount를 증가시키므로 이 한계가 훨씬 심해집니다. 실제 시험에서도 별도 `pincount` 필드가 없을 때 일부 huge page 스트레스 테스트에서 refcount 오버플로가 관찰되었습니다.
이 방식 덕분에 huge page와 대형 folio는 뒤에서 언급하는 false positive 문제도 겪지 않습니다.
| 함수 | 내부 플래그 동작 |
|---|---|
| pin_user_pages | 이 함수는 내부에서 항상 FOLL_PIN을 설정합니다. |
| pin_user_pages_fast | 이 함수는 내부에서 항상 FOLL_PIN을 설정합니다. |
| pin_user_pages_remote | 이 함수는 내부에서 항상 FOLL_PIN을 설정합니다. |
`get_user_pages*()` 함수에서는 `FOLL_GET`이 아예 지정되지 않을 수도 있어 동작이 조금 더 복잡합니다. `FOLL_GET`이 지정되지 않았지만 호출자가 NULL이 아닌 `struct page *` 배열을 전달하면 함수가 대신 `FOLL_GET`을 설정하고 각 페이지의 refcount를 1씩 증가시켜 페이지를 고정합니다.
| 함수 | 내부 플래그 동작 |
|---|---|
| get_user_pages | 이 함수는 경우에 따라 내부에서 FOLL_GET을 설정합니다. |
| get_user_pages_fast | 이 함수는 경우에 따라 내부에서 FOLL_GET을 설정합니다. |
| get_user_pages_remote | 이 함수는 경우에 따라 내부에서 FOLL_GET을 설정합니다. |
DMA 고정 페이지 추적
89-124DMA로 고정된 페이지 추적
DMA로 고정된 페이지를 추적할 때의 핵심 설계 제약과 해결책은 다음과 같습니다.
- `struct page`마다 실제 참조 횟수가 필요합니다. 여러 프로세스가 같은 페이지를 고정하고 해제할 수 있기 때문입니다.
- 실제로는 DMA로 고정되지 않은 페이지를 고정되었다고 보고하는 false positive는 허용되지만, 고정된 페이지를 아니라고 보고하는 false negative는 허용되지 않습니다.
- 이를 위해 `struct page`의 크기를 늘릴 수 없고 기존 필드도 모두 사용 중입니다.
- 따라서 `page->_refcount`의 상위 비트를 DMA 고정 횟수처럼 활용합니다. 실제 bitfield로 나누는 대신 중간 정도로 큰 값인 `GUP_PIN_COUNTING_BIAS`, 초기값 1024 즉 10비트를 `page->_refcount`에 더합니다. `get_page()`가 1024번 호출된 페이지가 DMA 고정 1회로 보일 수 있지만 이는 허용되는 false positive입니다.
- 31비트 가운데 10비트 단위로 증가하는 카운터에 사용할 수 있는 비트가 `31-10==21`개뿐이라는 한계가 있습니다.
- 이 한계 때문에 `FOLL_PIN`을 사용할 때 zero page는 특별히 처리합니다. 영구 페이지이므로 실제 refcount나 pincount를 바꾸지 않고 고정한 것처럼 취급하며, 해제 함수도 zero page에는 아무 작업을 하지 않습니다. 이 동작은 호출자에게 투명합니다.
- 호출자는 '페이지의 DMA 고정 추적'을 명시적으로 요청해야 합니다. 단순히 `get_user_pages()`를 호출하는 것으로는 부족하며 `pin_user_page()`와 관련 함수군을 사용해야 합니다.
플래그 선택 사례
125-130`FOLL_PIN`, `FOLL_GET`, `FOLL_LONGTERM`: 어떤 플래그를 언제 사용할 것인가
다음 분류는 Jan Kara, Vlastimil Babka와 여러 -mm 개발자가 설명한 사례를 정리한 것입니다.
사례 1: Direct IO
131-142사례 1: Direct IO (DIO)
DIO 버퍼로 사용되는 페이지에 GUP 참조가 있는 경우입니다. 버퍼가 필요한 기간은 비교적 짧아서 long-term이 아니며 `folio_mkclean()` 또는 `munmap()`과의 특별한 동기화도 제공되지 않습니다. 호출 지점에 필요한 플래그는 다음과 같습니다.
FOLL_PIN
다만 호출 지점에서 `FOLL_PIN`을 직접 설정하지 말고, 내부에서 `FOLL_PIN`을 설정하는 `pin_user_pages*()` 함수 중 하나를 사용해야 합니다.
사례 2: RDMA
143-155사례 2: RDMA
DMA 버퍼로 사용되는 페이지에 GUP 참조가 있는 경우입니다. 이 버퍼는 오랫동안 필요하며 `folio_mkclean()` 또는 `munmap()`과의 특별한 동기화는 제공되지 않습니다. 호출 지점에 필요한 플래그는 다음과 같습니다.
FOLL_PIN | FOLL_LONGTERM
DAX 페이지 같은 일부 페이지는 long-term pin으로 고정할 수 없습니다. DAX 페이지에는 별도 page cache가 없어서 페이지 고정이 파일시스템 블록을 잠그는 효과를 내는데, 이러한 방식은 아직 지원되지 않기 때문입니다.
사례 3: MMU notifier 등록
156-174사례 3: Page fault 지원 하드웨어 유무와 관계없는 MMU notifier 등록
장치 드라이버는 `get_user_pages*()`로 페이지를 고정하고 해당 메모리 범위에 대한 MMU notifier 콜백을 등록할 수 있습니다. `invalidate range` 콜백을 받으면 장치가 그 범위를 사용하지 못하게 중지한 뒤 페이지 고정을 해제합니다. 보류 중인 I/O와 명시적으로 동기화하는 방식처럼 거의 같은 효과를 내는 다른 구성도 가능합니다.
하드웨어가 재실행 가능한 page fault를 지원한다면 드라이버가 페이지 고정을 완전히 피하는 것이 이상적입니다. 앞과 같이 MMU notifier 콜백을 등록하되, 콜백에서 장치를 중지하고 고정을 해제하는 대신 장치 page table에서 해당 범위만 제거합니다.
어느 방식이든 MMU notifier 콜백을 받을 때 드라이버가 페이지 고정을 해제하면 파일시스템 및 MM의 `folio_mkclean()`, `munmap()` 등과 올바르게 동기화됩니다. 따라서 어떤 플래그도 설정할 필요가 없습니다.
사례 4: struct page 조작
175-180사례 4: `struct page` 조작만을 위한 고정
페이지가 추적하는 실제 메모리 내용이 아니라 `struct page` 데이터만 영향을 받는다면 일반 GUP 호출이면 충분하고 어떤 플래그도 설정할 필요가 없습니다.
사례 5: 페이지 데이터 쓰기
181-198사례 5: 페이지 내부 데이터에 쓰기 위한 고정
DMA나 Direct IO가 없어도 단순히 페이지를 고정하고 데이터를 쓴 뒤 고정을 해제하는 패턴은 문제를 일으킬 수 있습니다. 사례 5는 사례 1과 사례 2, 그리고 이 패턴을 호출하는 모든 경우를 포괄하는 상위 사례로 볼 수 있습니다. 즉 코드가 사례 1이나 사례 2가 아니더라도 다음과 같은 패턴에는 `FOLL_PIN`이 필요할 수 있습니다.
Correct (uses FOLL_PIN calls):
pin_user_pages()
write to the data within the pages
unpin_user_pages()
INCORRECT (uses FOLL_GET calls):
get_user_pages()
write to the data within the pages
put_page()
folio_maybe_dma_pinned()의 목적
199-215`folio_maybe_dma_pinned()`: 페이지 고정의 핵심 목적
Folio를 'DMA-pinned' 또는 'gup-pinned'로 표시하는 목적은 해당 folio가 DMA로 고정되었는지 질의할 수 있게 하는 것입니다. 그러면 `folio_mkclean()`과 일반적인 파일시스템 writeback 코드가 이런 고정 때문에 folio를 unmap할 수 없을 때 어떻게 처리할지 판단할 수 있습니다.
이 상황에서 무엇을 해야 하는지는 수년간 이어진 논의와 논쟁의 주제이며 문서 끝의 참고 자료에 연결되어 있습니다. 구체적인 처리 방식은 결정된 뒤 보충해야 할 TODO 항목입니다. 그동안 다음 함수가 제공되는 것은 오래된 gup+DMA 문제를 해결하기 위한 전제 조건입니다.
static inline bool folio_maybe_dma_pinned(struct folio *folio)
플래그의 제한 단계
216-226`FOLL_GET`, `FOLL_PIN`, `FOLL_LONGTERM`을 이해하는 또 다른 방법
이 플래그들은 제한이 점점 강해지는 단계로 볼 수 있습니다. `FOLL_GET`은 `struct page`가 가리키는 데이터에는 영향을 주지 않고 구조체 자체만 조작할 때 사용합니다.
`FOLL_PIN`은 `FOLL_GET`을 대체하며 실제 데이터에 접근할 페이지를 짧은 기간 고정하는 데 사용하므로 더 강한 형태의 고정입니다. `FOLL_LONGTERM`은 `FOLL_PIN`을 전제로 하며, 데이터에 접근하면서 페이지를 장기간 고정하는 더 제한적인 사례입니다.
단위 테스트와 vmstat
227-246단위 테스트 (Unit testing)
새 `pin*()` 래퍼 함수를 시험하는 코드는 다음 파일에 있습니다.
tools/testing/selftests/mm/gup_test.c
이 파일에는 다음 시험 호출이 추가되었습니다.
- `PIN_FAST_BENCHMARK` (`./gup_test -a`)
- `PIN_BASIC_TEST` (`./gup_test -b`)
시스템 부팅 이후 획득하고 해제한 DMA 고정 페이지의 총수를 다음 두 `/proc/vmstat` 항목으로 관찰할 수 있습니다.
/proc/vmstat/nr_foll_pin_acquired
/proc/vmstat/nr_foll_pin_released
일반적인 상태에서는 장기 [R]DMA 고정이 존재하거나 pin/unpin 전환 중인 경우를 제외하면 두 값이 같습니다.
고정 횟수 통계의 의미
247-270- `nr_foll_pin_acquired`는 시스템 전원을 켠 뒤 획득한 논리적 pin 수입니다. Huge page에서는 head page와 각 tail page를 포함한 huge page 내부의 페이지마다 head page를 한 번씩 고정합니다. 이는 huge page에 `get_user_pages()`를 적용할 때 각 head 또는 tail page마다 head page의 refcount를 증가시키는 동작과 같습니다.
- `nr_foll_pin_released`는 시스템 전원을 켠 뒤 해제한 논리적 pin 수입니다. 원래 huge page 단위로 고정했더라도 페이지 해제는 `PAGE_SIZE` 단위로 수행됩니다. 앞서 설명한 획득 계수 방식 때문에 다음 예제 뒤에는 통계가 다시 균형을 이룹니다.
pin_user_pages(huge_page);
for (each page in huge_page)
unpin_user_page(page);
따라서 다음 조건이 성립할 것으로 예상합니다.
nr_foll_pin_released == nr_foll_pin_acquired
다만 기존에 장기 RDMA 고정 때문에 이미 균형이 맞지 않았다면 예외입니다.
기타 진단
271-277기타 진단 (Other diagnostics)
`dump_page()`는 새로운 계수 필드를 처리하고 대형 folio를 전반적으로 더 잘 보고하도록 개선되었습니다. 특히 대형 folio에 대해서는 정확한 `pincount`를 출력합니다.
참고 자료
278-286참고 자료 (References)
- Some slow progress on get_user_pages() (2019-04-02)
https://lwn.net/Articles/784574/ - DMA and get_user_pages() (LPC, 2018-12-12)
https://lwn.net/Articles/774411/ - The trouble with get_user_pages() (2018-04-30)
https://lwn.net/Articles/753027/ - LWN kernel index: get_user_pages()
https://lwn.net/Kernel/Index/#Memory_management-get_user_pages
John Hubbard, 2019년 10월
요약과 해설
pin_user_pages.rst:1-286`pin_user_pages*()`는 DMA 또는 데이터 접근을 위해 페이지를 고정할 때 사용하는 래퍼이며 내부에서 `FOLL_PIN`을 설정합니다. 일반 `get_user_pages*()` 참조와 구분하여 파일시스템 writeback 및 MM 코드가 고정 상태를 판단할 수 있게 합니다.
일반 페이지는 `GUP_PIN_COUNTING_BIAS`를 refcount에 더하는 방식으로 고정 횟수를 근사하고, 대형 folio는 별도 `pincount`를 사용합니다. False positive는 허용하지만 false negative는 허용하지 않는 설계이며 zero page는 실제 계수를 바꾸지 않습니다.
짧은 DIO 고정에는 `FOLL_PIN`, 장기 RDMA 고정에는 `FOLL_PIN | FOLL_LONGTERM`을 사용합니다. MMU notifier와 올바르게 동기화하거나 `struct page`만 조작하는 경우에는 별도 플래그가 필요 없지만, 실제 페이지 데이터에 쓰는 경우에는 DMA가 없어도 `FOLL_PIN`이 필요할 수 있습니다.