← Documents Documentation/mm/split_page_table_lock.rst GitHub 원문 ↗

Linux 6.18.37 · Memory management

Split page table lock

PTE·PMD별 잠금, hugetlb 선택, 아키텍처 ctor 요구 사항과 page->ptl 저장 방식을 설명합니다.

Source pathDocumentation/mm/split_page_table_lock.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

split_page_table_lock.rst:1-107

분할 페이지 테이블 잠금은 `mm_struct` 전체의 단일 spinlock 경합을 PTE·PMD 테이블별 잠금으로 분산합니다. 상위 단계와 비활성화 구성은 여전히 `mm->page_table_lock`을 사용하며, hugetlb도 page size에 따라 PMD 분할 잠금 또는 mm 전체 잠금을 선택합니다.

페이지 테이블 단계별 잠금
대상사용 잠금조건
PTE tabletable별 split lock`CONFIG_SPLIT_PTLOCK_CPUS <= NR_CPUS`
PMD tabletable별 split lockPTE split + architecture 지원
PUD 이상`mm->page_table_lock`항상
Hugetlb `PMD_SIZE`PMD split lockPMD 크기 mapping
그 밖의 hugetlb`mm->page_table_lock`PUD 포함

분할 잠금이 활성화된 일반 구성을 기준으로 보호 범위를 나타냅니다.

주요 PTE·PMD helper
Helper매핑잠금핵심 반환
`pte_offset_map_lock()`PTE획득PTE + lock pointer
`pte_offset_map_ro_nolock()`PTE미획득PTE + lock pointer
`pte_offset_map_rw_nolock()`PTE미획득PTE + lock pointer + pmd 값
`pte_alloc_map_lock()`필요 시 할당획득PTE + lock pointer
`pmd_lock()`해당 없음획득PMD lock pointer
`pmd_lockptr()`해당 없음미획득PMD lock pointer

매핑, 잠금 획득 여부와 반환 정보를 구분합니다.

page->ptl 저장 방식 선택
`spinlock_t` 크기 확인`sizeof(spinlock_t) <= sizeof(long)``page->ptr`에 직접 저장간접 접근 없음
`spinlock_t` 크기 확인`sizeof(spinlock_t) > sizeof(long)``page->ptl`에 동적 pointer 저장cache line 1개 추가

`struct page` 크기를 늘리지 않도록 spinlock 크기에 따라 저장법을 고릅니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 =====================
2 Split page table lock
3 =====================
4
5 Originally, mm->page_table_lock spinlock protected all page tables of the
6 mm_struct. But this approach leads to poor page fault scalability of
7 multi-threaded applications due to high contention on the lock. To improve
8 scalability, split page table lock was introduced.
9
10 With split page table lock we have separate per-table lock to serialize
11 access to the table. At the moment we use split lock for PTE and PMD
12 tables. Access to higher level tables protected by mm->page_table_lock.
13
14 There are helpers to lock/unlock a table and other accessor functions:
15
16 - pte_offset_map_lock()
17 maps PTE and takes PTE table lock, returns pointer to PTE with
18 pointer to its PTE table lock, or returns NULL if no PTE table;
19 - pte_offset_map_ro_nolock()
20 maps PTE, returns pointer to PTE with pointer to its PTE table
21 lock (not taken), or returns NULL if no PTE table;
22 - pte_offset_map_rw_nolock()
23 maps PTE, returns pointer to PTE with pointer to its PTE table
24 lock (not taken) and the value of its pmd entry, or returns NULL
25 if no PTE table;
26 - pte_offset_map()
27 maps PTE, returns pointer to PTE, or returns NULL if no PTE table;
28 - pte_unmap()
29 unmaps PTE table;
30 - pte_unmap_unlock()
31 unlocks and unmaps PTE table;
32 - pte_alloc_map_lock()
33 allocates PTE table if needed and takes its lock, returns pointer to
34 PTE with pointer to its lock, or returns NULL if allocation failed;
35 - pmd_lock()
36 takes PMD table lock, returns pointer to taken lock;
37 - pmd_lockptr()
38 returns pointer to PMD table lock;
39
40 Split page table lock for PTE tables is enabled compile-time if
41 CONFIG_SPLIT_PTLOCK_CPUS (usually 4) is less or equal to NR_CPUS.
42 If split lock is disabled, all tables are guarded by mm->page_table_lock.
43
44 Split page table lock for PMD tables is enabled, if it's enabled for PTE
45 tables and the architecture supports it (see below).
46
47 Hugetlb and split page table lock
48 =================================
49
50 Hugetlb can support several page sizes. We use split lock only for PMD
51 level, but not for PUD.
52
53 Hugetlb-specific helpers:
54
55 - huge_pte_lock()
56 takes pmd split lock for PMD_SIZE page, mm->page_table_lock
57 otherwise;
58 - huge_pte_lockptr()
59 returns pointer to table lock;
60
61 Support of split page table lock by an architecture
62 ===================================================
63
64 There's no need in special enabling of PTE split page table lock: everything
65 required is done by pagetable_pte_ctor() and pagetable_dtor(), which
66 must be called on PTE table allocation / freeing.
67
68 Make sure the architecture doesn't use slab allocator for page table
69 allocation: slab uses page->slab_cache for its pages.
70 This field shares storage with page->ptl.
71
72 PMD split lock only makes sense if you have more than two page table
73 levels.
74
75 PMD split lock enabling requires pagetable_pmd_ctor() call on PMD table
76 allocation and pagetable_dtor() on freeing.
77
78 Allocation usually happens in pmd_alloc_one(), freeing in pmd_free() and
79 pmd_free_tlb(), but make sure you cover all PMD table allocation / freeing
80 paths: i.e X86_PAE preallocate few PMDs on pgd_alloc().
81
82 With everything in place you can set CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK.
83
84 NOTE: pagetable_pte_ctor() and pagetable_pmd_ctor() can fail -- it must
85 be handled properly.
86
87 page->ptl
88 =========
89
90 page->ptl is used to access split page table lock, where 'page' is struct
91 page of page containing the table. It shares storage with page->private
92 (and few other fields in union).
93
94 To avoid increasing size of struct page and have best performance, we use a
95 trick:
96
97 - if spinlock_t fits into long, we use page->ptr as spinlock, so we
98 can avoid indirect access and save a cache line.
99 - if size of spinlock_t is bigger then size of long, we use page->ptl as
100 pointer to spinlock_t and allocate it dynamically. This allows to use
101 split lock with enabled DEBUG_SPINLOCK or DEBUG_LOCK_ALLOC, but costs
102 one more cache line for indirect access;
103
104 The spinlock_t allocated in pagetable_pte_ctor() for PTE table and in
105 pagetable_pmd_ctor() for PMD table.
106
107 Please, never access page->ptl directly -- use appropriate helper.
108

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

페이지 테이블별 잠금

1-14

분할 페이지 테이블 잠금

원래는 `mm->page_table_lock` spinlock 하나가 `mm_struct`의 모든 페이지 테이블을 보호했습니다. 그러나 이 방식은 잠금 경합이 커서 멀티스레드 응용의 page fault 확장성이 나빠집니다. 이를 개선하기 위해 분할 페이지 테이블 잠금이 도입되었습니다.

분할 잠금에서는 테이블마다 별도의 잠금을 두어 해당 테이블 접근을 직렬화합니다. 현재 PTE와 PMD 테이블에 분할 잠금을 사용하고, 더 높은 단계의 테이블은 `mm->page_table_lock`으로 보호합니다.

테이블 잠금·해제와 접근을 위한 helper들이 제공됩니다.

=====================
Split page table lock
=====================

Originally, mm->page_table_lock spinlock protected all page tables of the
mm_struct. But this approach leads to poor page fault scalability of
multi-threaded applications due to high contention on the lock. To improve
scalability, split page table lock was introduced.

With split page table lock we have separate per-table lock to serialize
access to the table. At the moment we use split lock for PTE and PMD
tables. Access to higher level tables protected by mm->page_table_lock.

There are helpers to lock/unlock a table and other accessor functions:

PTE·PMD helper와 활성화 조건

15-45
  • `pte_offset_map_lock()`은 PTE를 매핑하고 PTE 테이블 잠금을 획득한 뒤 PTE와 잠금의 포인터를 돌려줍니다. PTE 테이블이 없으면 `NULL`을 반환합니다.
  • `pte_offset_map_ro_nolock()`은 PTE를 매핑하고 PTE 및 획득하지 않은 잠금의 포인터를 돌려줍니다. PTE 테이블이 없으면 `NULL`입니다.
  • `pte_offset_map_rw_nolock()`은 PTE, 획득하지 않은 잠금, 해당 pmd 엔트리 값을 돌려줍니다. PTE 테이블이 없으면 `NULL`입니다.
  • `pte_offset_map()`은 PTE를 매핑하여 포인터를 돌려주며 테이블이 없으면 `NULL`입니다.
  • `pte_unmap()`은 PTE 테이블 매핑을 해제합니다.
  • `pte_unmap_unlock()`은 PTE 테이블 잠금을 풀고 매핑도 해제합니다.
  • `pte_alloc_map_lock()`은 필요하면 PTE 테이블을 할당하고 잠금을 획득한 뒤 PTE와 잠금 포인터를 돌려줍니다. 할당 실패 시 `NULL`입니다.
  • `pmd_lock()`은 PMD 테이블 잠금을 획득하고 그 포인터를 돌려줍니다.
  • `pmd_lockptr()`은 PMD 테이블 잠금의 포인터를 돌려줍니다.

PTE 테이블용 분할 잠금은 보통 4인 `CONFIG_SPLIT_PTLOCK_CPUS`가 `NR_CPUS`보다 작거나 같을 때 compile time에 활성화됩니다. 비활성화되면 모든 테이블을 `mm->page_table_lock`이 지킵니다.

PMD 테이블용 분할 잠금은 PTE 분할 잠금이 활성화되어 있고 아키텍처도 이를 지원할 때 활성화됩니다.


 - pte_offset_map_lock()
        maps PTE and takes PTE table lock, returns pointer to PTE with
        pointer to its PTE table lock, or returns NULL if no PTE table;
 - pte_offset_map_ro_nolock()
        maps PTE, returns pointer to PTE with pointer to its PTE table
        lock (not taken), or returns NULL if no PTE table;
 - pte_offset_map_rw_nolock()
        maps PTE, returns pointer to PTE with pointer to its PTE table
        lock (not taken) and the value of its pmd entry, or returns NULL
        if no PTE table;
 - pte_offset_map()
        maps PTE, returns pointer to PTE, or returns NULL if no PTE table;
 - pte_unmap()
        unmaps PTE table;
 - pte_unmap_unlock()
        unlocks and unmaps PTE table;
 - pte_alloc_map_lock()
        allocates PTE table if needed and takes its lock, returns pointer to
        PTE with pointer to its lock, or returns NULL if allocation failed;
 - pmd_lock()
        takes PMD table lock, returns pointer to taken lock;
 - pmd_lockptr()
        returns pointer to PMD table lock;

Split page table lock for PTE tables is enabled compile-time if
CONFIG_SPLIT_PTLOCK_CPUS (usually 4) is less or equal to NR_CPUS.
If split lock is disabled, all tables are guarded by mm->page_table_lock.

Split page table lock for PMD tables is enabled, if it's enabled for PTE
tables and the architecture supports it (see below).

Hugetlb 잠금 선택

46-62

Hugetlb와 분할 페이지 테이블 잠금

Hugetlb는 여러 page size를 지원할 수 있습니다. 분할 잠금은 PMD 단계에만 사용하며 PUD 단계에는 사용하지 않습니다.

  • `huge_pte_lock()`은 `PMD_SIZE` 페이지에는 PMD 분할 잠금을, 그 밖의 경우에는 `mm->page_table_lock`을 획득합니다.
  • `huge_pte_lockptr()`은 사용할 테이블 잠금의 포인터를 돌려줍니다.

다음은 아키텍처가 분할 페이지 테이블 잠금을 지원하는 방법입니다.


Hugetlb and split page table lock
=================================

Hugetlb can support several page sizes. We use split lock only for PMD
level, but not for PUD.

Hugetlb-specific helpers:

 - huge_pte_lock()
        takes pmd split lock for PMD_SIZE page, mm->page_table_lock
        otherwise;
 - huge_pte_lockptr()
        returns pointer to table lock;

Support of split page table lock by an architecture
===================================================

아키텍처 구현 요구 사항

63-87

PTE 분할 잠금을 별도로 켤 필요는 없습니다. PTE 테이블을 할당할 때 `pagetable_pte_ctor()`, 해제할 때 `pagetable_dtor()`를 호출하면 필요한 설정이 이루어집니다.

아키텍처가 페이지 테이블 할당에 slab allocator를 사용하지 않는지 확인해야 합니다. slab은 페이지의 `page->slab_cache`를 사용하는데, 이 필드는 `page->ptl`과 같은 저장 공간을 공유합니다.

PMD 분할 잠금은 페이지 테이블 단계가 둘보다 많을 때만 의미가 있습니다. 활성화하려면 PMD 테이블 할당 시 `pagetable_pmd_ctor()`, 해제 시 `pagetable_dtor()`를 호출해야 합니다.

보통 할당은 `pmd_alloc_one()`, 해제는 `pmd_free()`와 `pmd_free_tlb()`에서 일어납니다. 하지만 X86_PAE가 `pgd_alloc()`에서 PMD 몇 개를 미리 할당하는 사례처럼 모든 할당·해제 경로를 빠짐없이 다뤄야 합니다.

준비가 끝나면 `CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK`을 설정할 수 있습니다. `pagetable_pte_ctor()`와 `pagetable_pmd_ctor()`는 실패할 수 있으므로 반드시 오류를 올바르게 처리해야 합니다.


There's no need in special enabling of PTE split page table lock: everything
required is done by pagetable_pte_ctor() and pagetable_dtor(), which
must be called on PTE table allocation / freeing.

Make sure the architecture doesn't use slab allocator for page table
allocation: slab uses page->slab_cache for its pages.
This field shares storage with page->ptl.

PMD split lock only makes sense if you have more than two page table
levels.

PMD split lock enabling requires pagetable_pmd_ctor() call on PMD table
allocation and pagetable_dtor() on freeing.

Allocation usually happens in pmd_alloc_one(), freeing in pmd_free() and
pmd_free_tlb(), but make sure you cover all PMD table allocation / freeing
paths: i.e X86_PAE preallocate few PMDs on pgd_alloc().

With everything in place you can set CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK.

NOTE: pagetable_pte_ctor() and pagetable_pmd_ctor() can fail -- it must
be handled properly.

page->ptl

page->ptl 저장 방식

88-107

`page->ptl`

`page->ptl`은 테이블을 담은 `struct page`에서 분할 페이지 테이블 잠금에 접근할 때 사용합니다. 이 필드는 `page->private` 및 union 안의 몇몇 다른 필드와 저장 공간을 공유합니다.

`struct page` 크기를 늘리지 않으면서 성능을 높이기 위해 두 가지 저장 방식을 선택합니다.

  • `spinlock_t`가 `long` 안에 들어가면 `page->ptr` 자체를 spinlock으로 사용합니다. 간접 접근을 없애고 cache line 하나를 절약합니다.
  • `spinlock_t`가 `long`보다 크면 `page->ptl`을 동적으로 할당한 `spinlock_t`의 포인터로 사용합니다. `DEBUG_SPINLOCK`이나 `DEBUG_LOCK_ALLOC`을 켠 상태에서도 분할 잠금을 쓸 수 있지만 간접 접근 때문에 cache line 하나가 더 필요합니다.

spinlock은 PTE 테이블의 `pagetable_pte_ctor()`와 PMD 테이블의 `pagetable_pmd_ctor()`에서 할당됩니다. `page->ptl`에 직접 접근하지 말고 반드시 적절한 helper를 사용하십시오.

=========

page->ptl is used to access split page table lock, where 'page' is struct
page of page containing the table. It shares storage with page->private
(and few other fields in union).

To avoid increasing size of struct page and have best performance, we use a
trick:

 - if spinlock_t fits into long, we use page->ptr as spinlock, so we
   can avoid indirect access and save a cache line.
 - if size of spinlock_t is bigger then size of long, we use page->ptl as
   pointer to spinlock_t and allocate it dynamically. This allows to use
   split lock with enabled DEBUG_SPINLOCK or DEBUG_LOCK_ALLOC, but costs
   one more cache line for indirect access;

The spinlock_t allocated in pagetable_pte_ctor() for PTE table and in
pagetable_pmd_ctor() for PMD table.

Please, never access page->ptl directly -- use appropriate helper.