요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
=====================
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_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 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
===================================================
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 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.
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-62Hugetlb와 분할 페이지 테이블 잠금
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-87PTE 분할 잠금을 별도로 켤 필요는 없습니다. 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.
요약·해설
split_page_table_lock.rst:1-107분할 페이지 테이블 잠금은 `mm_struct` 전체의 단일 spinlock 경합을 PTE·PMD 테이블별 잠금으로 분산합니다. 상위 단계와 비활성화 구성은 여전히 `mm->page_table_lock`을 사용하며, hugetlb도 page size에 따라 PMD 분할 잠금 또는 mm 전체 잠금을 선택합니다.
분할 잠금이 활성화된 일반 구성을 기준으로 보호 범위를 나타냅니다.
매핑, 잠금 획득 여부와 반환 정보를 구분합니다.
`struct page` 크기를 늘리지 않도록 spinlock 크기에 따라 저장법을 고릅니다.