概览

本文介绍了internal_get_user_pages_fast、gup_pgd_range、gup_pte_range、try_grab_compound_head

internal_get_user_pages_fast

这是一个用于获取用户空间页面的函数,函数原型如下:

1
2
3
static int internal_get_user_pages_fast(unsigned long start, int nr_pages,
unsigned int gup_flags,
struct page **pages)

参数:

  • start:要获取的用户空间起始地址。
  • nr_pages:要获取的页面数量。
  • gup_flags:用于指定页面特性和行为的标志。
  • pages:指向一个struct page指针的数组,存储获取到的页面的指针。

函数返回获取到的页面数量,或者在出错时返回负的错误代码。

函数的主要步骤如下:

  1. 进行参数检查,确保传入的gup_flags是合法的组合。
  2. 如果设置了FOLL_PIN标记,将当前进程的内存管理结构中的has_pinned原子变量设为1。
  3. 如果没有设置FOLL_FAST_ONLY标记,使用might_lock_read宏提示可能会对mmap_lock读锁进行加锁。
  4. 计算起始地址、结束地址和地址范围长度,确保它们是有效的。
  5. 禁用中断,阻止页面表页面在操作期间被释放。
  6. 如果启用了CONFIG_HAVE_FAST_GUP配置,并且允许快速GUP(Get User Pages)操作,则调用gup_pgd_range函数获取页面,将获取到的页面数量存储在nr_pinned变量中。
  7. 如果没有获取到所有请求的页面并且没有设置FOLL_FAST_ONLY标记,则尝试使用__gup_longterm_unlocked函数获取剩余的页面。
  8. 根据已获取页面的数量和剩余页面尝试的结果,返回最终结果。

这个函数的主要作用是尝试使用快速GUP(Get User Pages)方法获取用户空间内存页面。如果快速方法不能获取所有请求的页面,它还可以尝试使用其他方法获取剩余的页面。这个函数会在访问用户空间内存时使用,例如在内核处理用户请求时。

gup_pgd_range

这是一个分析页全局目录(PGD)范围的函数,用于将虚拟地址范围映射到物理内存页面。这个函数被定义为 static void gup_pgd_range,它接收以下参数:

  1. unsigned long addr:起始虚拟地址。
  2. unsigned long end:结束虚拟地址。
  3. unsigned int flags:标志,用于指定处理方式。
  4. struct page **pages:指向页面结构数组的指针。
  5. int *nr:指向存储当前已处理页面数量的整数的指针。

函数首先通过调用 pgd_offset(current->mm, addr) 获取当前进程内存管理结构(current->mm)对应的页全局目录项指针 pgdp。接下来,它使用一个 do-while 循环遍历从 addr 到 end 的虚拟地址范围。

在循环中,首先获取当前 pgdp 指向的页全局目录项 pgd。然后调用 pgd_addr_end(addr, end) 计算下一个虚拟地址段的结束地址,存储在 next 变量中。

接下来,函数检查当前 pgd 是否为空(pgd_none(pgd))。如果为空,函数直接返回。

如果当前 pgd 是一个巨大的页表项(pgd_huge(pgd)),函数调用 gup_huge_pgd 处理巨大页表项。否则,如果当前 pgd 是一个巨大页表目录(is_hugepd(__hugepd(pgd_val(pgd)))),函数调用 gup_huge_pd 处理巨大页表目录。最后,如果都不是上述情况,函数调用 gup_p4d_range 处理下一级页表。

在任何情况下,如果处理失败(返回值为 0),函数立即返回。否则,在循环条件中更新 pgdpaddr 和 next,并继续遍历虚拟地址范围。

总的来说,这个函数遍历虚拟地址范围,查找并处理与虚拟地址对应的物理内存页面。根据不同的页表项类型(巨大页表项、巨大页表目录等),它会调用不同的函数来处理这些项。

gup_pte_range

这段代码实现了一个函数 gup_pte_range(),用于获取一个虚拟地址范围内的物理页框,并返回这些页框所对应的 struct page 数组和页框数量。下面是对这段代码的分析:

  1. 首先定义了一些变量,包括 pgmapnr_start 和 ret 等。其中 pgmap 是一个指向 dev_pagemap 结构体的指针,用于映射设备内存。nr_start 表示获取页框的起始数量,ret 表示函数的返回值。
  2. 然后通过 pte_offset_map() 函数获取一个 pte_t 类型的指针 ptep,该指针指向一个页表项(pte)的地址。同时将 ptep 赋值给 ptem,作为循环中的初始值。
  3. 使用一个 do-while 循环,遍历地址范围内的所有页表项。循环内部对每个页表项进行处理,具体操作如下:
  • 使用 gup_get_pte() 函数获取页表项的值,并赋值给 pte 变量。
  • 如果 pte 的保护位(protnone)被设置,说明这个页表项不可访问,直接跳转到 pte_unmap 标签处清理页面映射。
  • 如果 flags 中包含 FOLL_WRITE 标志位,但 pte 中的写入权限被禁用,则也跳转到 pte_unmap 标签处清理页面映射。
  • 如果 pte 是设备映射页,则进一步处理。如果 flags 中包含 FOLL_LONGTERM 标志位,则也跳转到 pte_unmap 标签处清理页面映射。否则,调用 get_dev_pagemap() 函数获取设备映射的 dev_pagemap 结构体,并将其赋值给 pgmap 变量。如果获取失败,则跳转到 pte_unmap 标签处清理页面映射。
  • 如果 pte 是一个特殊页,则也跳转到 pte_unmap 标签处清理页面映射。
  • 检查 pte 所对应的物理页框是否有效,如果无效则会触发 BUG,程序会停止运行。
  • 调用 pte_page() 函数获取 pte 所对应的 struct page 结构体,并将其赋值给 page 变量。
  • 如果 page 是一个复合页,则调用 try_grab_compound_head() 函数获取该页的头部页框,并将其赋值给 head 变量。如果获取失败,则跳转到 pte_unmap 标签处清理页面映射。
  • 检查 pte 的值是否与 ptep 的值相等,如果不相等,则说明页表项已经被修改过,直接跳转到 pte_unmap 标签处清理页面映射。
  • 检查 compound_head(page) 是否等于 head,如果不等于,则说明 page 不是一个有效的复合页,直接触发 BUG,程序会停止运行。
  • 如果 flags 中包含 FOLL_PIN 标志位,则调用 arch_make_page_accessible() 函数使 page 可访问。如果操作失败,则调用 unpin_user_page() 函数解除页面锁定,并跳转到 pte_unmap 标签处清理页面映射。
  • 调用 SetPageReferenced() 函数标记 page 已被引用过。
  • 将 page 存储到 pages 数组中,并将 nr 的值增加 1。
  1. 循环结束后,如果函数没有被跳转到 pte_unmap 标签处,则将 ret 的值设为 1,表示函数执行成功。然后调用 put_dev_pagemap() 函数释放 pgmap 占用的资源,并调用 pte_unmap() 函数解除页面映射。
  2. 最后返回 ret 的值,表示函数执行结果的状态。

try_grab_compound_head

它的作用是尝试根据标志位(flags)增加一个给定页面(page)的引用计数。在此文件中,“grab”表示:根据标志位决定是使用FOLL_PIN还是FOLL_GET行为来增加页面的引用计数。

函数的参数如下:

  • struct page *page:要操作的页面。
  • int refs:当前页面的引用计数。
  • unsigned int flags:控制行为的标志位,包括FOLL_GET和FOLL_PIN。

在这个函数中,FOLL_GET和FOLL_PIN(或者它们都没有)必须被设置,但不能同时设置。它们的含义如下:

  • FOLL_GET:页面的引用计数将增加1。
  • FOLL_PIN:页面的引用计数将增加GUP_PIN_COUNTING_BIAS。

根据flags的设置,函数会返回适当增加引用计数的头页面(head page),如果失败则返回NULL。如果没有设置FOLL_GET或FOLL_PIN,那么这被认为是失败,同时也可能是调用者的bug,因此还会发出警告。

函数的主体包含几个条件分支:

  1. 如果设置了FOLL_GET,调用try_get_compound_head函数,参数为pagerefs
  2. 如果设置了FOLL_PIN,进行以下操作:
    • 检查是否同时设置了FOLL_LONGTERM,并且页面是CMA类型,如果是,则返回NULL。
    • 如果页面的order大于1(由hpage_pincount_available函数检查),则将refs乘以GUP_PIN_COUNTING_BIAS。
    • 调用try_get_compound_head函数,参数为pagerefs
    • 如果页面的order大于1,调用hpage_pincount_add函数增加页面的pin计数。
    • 更新节点状态(mod_node_page_state)。
    • 返回适当增加引用计数的头页面。
  3. 如果既没有设置FOLL_GET,也没有设置FOLL_PIN,发出警告,并返回NULL。

try_get_compound_head

这是一个静态内联函数,返回一个struct page类型的指针,并接受两个参数:

  1. struct page *page - 一个指向struct page类型的指针,表示我们要获取其头部的复合页。
  2. int refs - 一个整数,表示我们希望增加的引用计数。

try_get_compound_head()函数的目的是获取复合页的头部,并将其引用计数适当地增加。如果成功,函数返回头部的指针,否则返回NULL