linux内存管理模块一些函数介绍
概览
本文介绍了internal_get_user_pages_fast、gup_pgd_range、gup_pte_range、try_grab_compound_head
internal_get_user_pages_fast
这是一个用于获取用户空间页面的函数,函数原型如下:
1 | static int internal_get_user_pages_fast(unsigned long start, int nr_pages, |
参数:
start
:要获取的用户空间起始地址。nr_pages
:要获取的页面数量。gup_flags
:用于指定页面特性和行为的标志。pages
:指向一个struct page
指针的数组,存储获取到的页面的指针。
函数返回获取到的页面数量,或者在出错时返回负的错误代码。
函数的主要步骤如下:
- 进行参数检查,确保传入的
gup_flags
是合法的组合。 - 如果设置了
FOLL_PIN
标记,将当前进程的内存管理结构中的has_pinned
原子变量设为1。 - 如果没有设置
FOLL_FAST_ONLY
标记,使用might_lock_read
宏提示可能会对mmap_lock
读锁进行加锁。 - 计算起始地址、结束地址和地址范围长度,确保它们是有效的。
- 禁用中断,阻止页面表页面在操作期间被释放。
- 如果启用了
CONFIG_HAVE_FAST_GUP
配置,并且允许快速GUP(Get User Pages)操作,则调用gup_pgd_range
函数获取页面,将获取到的页面数量存储在nr_pinned
变量中。 - 如果没有获取到所有请求的页面并且没有设置
FOLL_FAST_ONLY
标记,则尝试使用__gup_longterm_unlocked
函数获取剩余的页面。 - 根据已获取页面的数量和剩余页面尝试的结果,返回最终结果。
这个函数的主要作用是尝试使用快速GUP(Get User Pages)方法获取用户空间内存页面。如果快速方法不能获取所有请求的页面,它还可以尝试使用其他方法获取剩余的页面。这个函数会在访问用户空间内存时使用,例如在内核处理用户请求时。
gup_pgd_range
这是一个分析页全局目录(PGD)范围的函数,用于将虚拟地址范围映射到物理内存页面。这个函数被定义为 static void gup_pgd_range
,它接收以下参数:
unsigned long addr
:起始虚拟地址。unsigned long end
:结束虚拟地址。unsigned int flags
:标志,用于指定处理方式。struct page **pages
:指向页面结构数组的指针。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),函数立即返回。否则,在循环条件中更新 pgdp
、addr
和 next
,并继续遍历虚拟地址范围。
总的来说,这个函数遍历虚拟地址范围,查找并处理与虚拟地址对应的物理内存页面。根据不同的页表项类型(巨大页表项、巨大页表目录等),它会调用不同的函数来处理这些项。
gup_pte_range
这段代码实现了一个函数 gup_pte_range()
,用于获取一个虚拟地址范围内的物理页框,并返回这些页框所对应的 struct page
数组和页框数量。下面是对这段代码的分析:
- 首先定义了一些变量,包括
pgmap
、nr_start
和ret
等。其中pgmap
是一个指向dev_pagemap
结构体的指针,用于映射设备内存。nr_start
表示获取页框的起始数量,ret
表示函数的返回值。 - 然后通过
pte_offset_map()
函数获取一个pte_t
类型的指针ptep
,该指针指向一个页表项(pte
)的地址。同时将ptep
赋值给ptem
,作为循环中的初始值。 - 使用一个
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。
- 循环结束后,如果函数没有被跳转到
pte_unmap
标签处,则将ret
的值设为 1,表示函数执行成功。然后调用put_dev_pagemap()
函数释放pgmap
占用的资源,并调用pte_unmap()
函数解除页面映射。 - 最后返回
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,因此还会发出警告。
函数的主体包含几个条件分支:
- 如果设置了FOLL_GET,调用
try_get_compound_head
函数,参数为page
和refs
。 - 如果设置了FOLL_PIN,进行以下操作:
- 检查是否同时设置了FOLL_LONGTERM,并且页面是CMA类型,如果是,则返回NULL。
- 如果页面的order大于1(由
hpage_pincount_available
函数检查),则将refs
乘以GUP_PIN_COUNTING_BIAS。 - 调用
try_get_compound_head
函数,参数为page
和refs
。 - 如果页面的order大于1,调用
hpage_pincount_add
函数增加页面的pin计数。 - 更新节点状态(
mod_node_page_state
)。 - 返回适当增加引用计数的头页面。
- 如果既没有设置FOLL_GET,也没有设置FOLL_PIN,发出警告,并返回NULL。
try_get_compound_head
这是一个静态内联函数,返回一个struct page
类型的指针,并接受两个参数:
struct page *page
- 一个指向struct page
类型的指针,表示我们要获取其头部的复合页。int refs
- 一个整数,表示我们希望增加的引用计数。
try_get_compound_head()
函数的目的是获取复合页的头部,并将其引用计数适当地增加。如果成功,函数返回头部的指针,否则返回NULL
。