英语

拆分页表锁

最初,mm->page_table_lock自旋锁保护 mm_struct 的所有页表。但是,这种方法会导致多线程应用程序的页错误可伸缩性较差,因为锁上的争用很高。为了提高可伸缩性,引入了拆分页表锁。

使用拆分页表锁,我们为每个表设置单独的锁,以序列化对表的访问。目前,我们对 PTE 和 PMD 表使用拆分锁。对更高层级表的访问由 mm->page_table_lock 保护。

有一些辅助函数用于锁定/解锁表和其他访问器函数

  • pte_offset_map_lock()

    映射 PTE 并获取 PTE 表锁,返回指向 PTE 的指针和指向其 PTE 表锁的指针,如果没有 PTE 表则返回 NULL;

  • pte_offset_map_ro_nolock()

    映射 PTE,返回指向 PTE 的指针和指向其 PTE 表锁的指针(未获取),如果没有 PTE 表则返回 NULL;

  • pte_offset_map_rw_nolock()

    映射 PTE,返回指向 PTE 的指针和指向其 PTE 表锁的指针(未获取)以及其 pmd 条目的值,如果没有 PTE 表则返回 NULL;

  • pte_offset_map()

    映射 PTE,返回指向 PTE 的指针,如果没有 PTE 表则返回 NULL;

  • pte_unmap()

    取消映射 PTE 表;

  • pte_unmap_unlock()

    解锁并取消映射 PTE 表;

  • pte_alloc_map_lock()

    如果需要,分配 PTE 表并获取其锁,返回指向 PTE 的指针和指向其锁的指针,如果分配失败则返回 NULL;

  • pmd_lock()

    获取 PMD 表锁,返回指向获取的锁的指针;

  • pmd_lockptr()

    返回指向 PMD 表锁的指针;

如果 CONFIG_SPLIT_PTLOCK_CPUS(通常为 4)小于或等于 NR_CPUS,则在编译时启用 PTE 表的拆分页表锁。如果禁用拆分锁,则所有表都由 mm->page_table_lock 保护。

如果为 PTE 表启用拆分页表锁,并且架构支持它(见下文),则启用 PMD 表的拆分页表锁。

Hugetlb 和拆分页表锁

Hugetlb 可以支持多种页面大小。我们仅对 PMD 级别使用拆分锁,而不对 PUD 使用。

Hugetlb 特定的辅助函数

  • huge_pte_lock()

    获取 PMD_SIZE 页面的 pmd 拆分锁,否则获取 mm->page_table_lock;

  • huge_pte_lockptr()

    返回指向表锁的指针;

架构对拆分页表锁的支持

无需特别启用 PTE 拆分页表锁:所有必需的操作都由 pagetable_pte_ctor() 和 pagetable_pte_dtor() 完成,它们必须在 PTE 表分配/释放时调用。

确保架构不使用 slab 分配器进行页表分配:slab 使用 page->slab_cache 用于其页面。此字段与 page->ptl 共享存储空间。

如果页表级别超过两个,则 PMD 拆分锁才有意义。

PMD 拆分锁启用需要在 PMD 表分配时调用 pagetable_pmd_ctor(),在释放时调用 pagetable_pmd_dtor()。

分配通常在 pmd_alloc_one() 中发生,释放在 pmd_free() 和 pmd_free_tlb() 中发生,但请确保涵盖所有 PMD 表分配/释放路径:例如,X86_PAE 在 pgd_alloc() 上预分配少量 PMD。

完成所有设置后,您可以设置 CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK。

注意:pagetable_pte_ctor() 和 pagetable_pmd_ctor() 可能会失败 -- 必须正确处理。

page->ptl

page->ptl 用于访问拆分页表锁,其中 'page' 是包含该表的页面的 struct page。它与 page->private(以及联合中的其他几个字段)共享存储空间。

为了避免增加 struct page 的大小并获得最佳性能,我们使用了一个技巧

  • 如果 spinlock_t 可以放入 long 中,我们使用 page->ptr 作为自旋锁,这样我们可以避免间接访问并节省缓存行。

  • 如果 spinlock_t 的大小大于 long 的大小,我们使用 page->ptl 作为指向 spinlock_t 的指针并动态分配它。这允许在启用 DEBUG_SPINLOCK 或 DEBUG_LOCK_ALLOC 的情况下使用拆分锁,但对于间接访问会多花费一个缓存行;

在 pagetable_pte_ctor() 中为 PTE 表分配 spinlock_t,在 pagetable_pmd_ctor() 中为 PMD 表分配。

请不要直接访问 page->ptl -- 使用适当的辅助函数。