当前位置: 首页 > news >正文

Linux 内存 --- get_user_pages/pin_user_pages函数

文章目录

  • 前言
  • 一、pin user pages in memory
    • 1.1 user pages
    • 1.2 in memory
  • 二、get_user_pages
    • 2.1 函数简介
    • 2.2 demo
  • 三、get_user_pages_unlocked
  • 四、get_user_pages_fast
  • 五、pin_user_pages

前言

一、pin user pages in memory

1.1 user pages

在 Linux 中,用户态程序看到的地址空间(虚拟地址)并不直接对应物理内存,而是通过内核维护的一系列结构映射出来的。

每个进程有一个:

struct mm_struct *mm;

其中保存了该进程所有的内存映射(VMA,虚拟内存区域),包括:

程序代码段 .text数据段 .data堆(malloc())栈动态库(mmap())文件映射、匿名页等

当你访问一个地址,比如:

int *p = malloc(4096);
*p = 123;

这个虚拟地址对应的页,会在页表(Page Table)中映射到一个物理页(struct page)。

这些被映射、可被访问的物理页,就是所谓的 user pages(用户页)。

1.2 in memory

“pin user pages in memory”(固定用户页面在内存中):固定用户页面是指将用户空间的内存页面锁定在物理内存中,防止它们被换出到磁盘或回收。

“in memory” 强调这些页面 当前已经被加载到 RAM 中,也就是说:

页表项(PTE)是有效的;对应的 struct page 存在于内核的物理页缓存;内核可以直接访问它;不需要再触发缺页异常(page fault)来将其调入内存

当一个用户页面被访问时,如果它不在内存中,会触发 缺页中断(page fault),由内核将其加载进物理内存,此时才成为 “in memory”。

页面固定前:

用户进程虚拟地址空间↓ (通过页表转换)
物理内存页面 ←→ 可能被换出到swap

页面固定后:

用户进程虚拟地址空间↓ (通过页表转换)
物理内存页面 [PINNED] ← 不会被换出↑引用计数增加

pin memoy会增加页面引用计数,因此:

// 当页面被固定时,内存管理器会:
1. 跳过这些页面的页面回收扫描
2. 不允许将这些页面换出到swap
3. 在内存压力时,这些页面保持驻留

默认情况下,Linux 的内存是可以被换出的:
内核可以把长时间不用的页写入 swap 或文件,然后回收物理内存。

但某些内核子系统需要访问用户空间内存的实际物理页内容,例如:

场景说明
DMA / RDMA 驱动硬件直接访问用户空间缓冲区,需要固定页以防被换出
零拷贝 I/O内核直接在用户缓冲区上读写
GPU 驱动 (OpenGL / Vulkan)用户显存映射
文件 I/O(AIO、splice)在内核中操作用户缓冲区

这些情况下,内核必须保证:

页不会被回收或移动;
页内容不会变化(COW);
可以直接得到物理地址。

pin memory的形式即使用mmap/malloc时提前将虚拟内存与对应物理内存锁定,以提高性能。

pin memory好处还有另外一个优势就是可以防止内存被swap out置换到存储器中,如果在进程切换时该物理内存被swap out磁盘中,下次读取还需要从磁盘加载到内存中,整个过程非常耗时,通过使用pin memory可以将一些主要常用的内存锁住,以防止被置换出去同时防止进行各种原因造成的页迁移,以提高程序性能。

pin memory最大坏处就是:如果每个程序都大量使用pin memory,那么最后将会导致没有物理内存可用,所以一般社区开发不建议在大量长期时候的内存使用pin memory类型内存。

二、get_user_pages

2.1 函数简介

// linux/v6.14/source/mm/gup.clong get_user_pages(unsigned long start, unsigned long nr_pages,unsigned int gup_flags, struct page **pages)
{int locked = 1;if (!is_valid_gup_args(pages, NULL, &gup_flags, FOLL_TOUCH))return -EINVAL;return __get_user_pages_locked(current->mm, start, nr_pages, pages,&locked, gup_flags);
}
EXPORT_SYMBOL(get_user_pages);

get_user_pages() 内存管理函数用于将用户态虚拟地址对应的物理页固定(pin)在内存中,以便内核或驱动程序能直接访问这些页(例如 DMA、零拷贝、或者 GPU/显卡映射用户缓冲区等场景)。

参数说明:

参数类型说明
startunsigned long用户空间起始虚拟地址
nr_pagesunsigned long需要固定的页数
gup_flagsunsigned int控制行为的标志(如是否写入、是否触发缺页等)
pagesstruct page **用于返回物理页指针数组(可以为 NULL

返回值为成功固定的页数(>= 0),或负错误码(< 0)。

get_user_pages():
遍历并“固定”用户空间的虚拟页(对应物理页引用计数 +1)。
确保这些页在被使用期间不会被换出到 swap。

备注:
函数调用:必须在调用时持有 mmap_lock(读或写锁)。
引用计数:每个固定的页面都会增加引用计数。
需要清理:完成后必须在所有固定页面上调用 put_page()。

所以当内核函数 get_user_pages() 被调用时,它实际上:

查找用户空间虚拟地址对应的 pte_t;如果页不在内存,则触发缺页异常(fault in);找到或分配对应的 struct page;增加页引用计数,从而“固定”它在物理内存中;返回 struct page * 数组给调用者。

调用 get_user_pages() 后:

每个页的引用计数 page->_refcount +1;页不会被换出;页内容被锁定在 RAM;你可以通过 kmap() 映射到内核空间访问;用完后必须调用 put_page() 释放引用,否则内存泄漏。

内核调用链:

get_user_pages()-->__get_user_pages_locked()-->__get_user_pages()-->follow_page_mask()-->follow_p4d_mask()--> follow_pud_mask()-->follow_pmd_mask()-->follow_page_pte()
static struct page *follow_page_pte(struct vm_area_struct *vma,unsigned long address, pmd_t *pmd, unsigned int flags,struct dev_pagemap **pgmap)
{struct mm_struct *mm = vma->vm_mm;struct folio *folio;struct page *page;spinlock_t *ptl;pte_t *ptep, pte;int ret;/* FOLL_GET 和 FOLL_PIN 是互斥的,不能同时设置 */if (WARN_ON_ONCE((flags & (FOLL_PIN | FOLL_GET)) ==(FOLL_PIN | FOLL_GET)))return ERR_PTR(-EINVAL);/** 获取 PTE(页表项)指针,并加锁防止并发修改。* 这是遍历页表的标准方式:从 PMD 找到对应的 PTE。*/ptep = pte_offset_map_lock(mm, pmd, address, &ptl);if (!ptep)return no_page_table(vma, flags, address);  // 没有有效的页表项/** 读取 PTE 内容。* 使用 ptep_get() 确保原子性读取,避免架构相关问题。*/pte = ptep_get(ptep);/** 如果页面未驻留(不在物理内存中),比如被 swap 出去或尚未分配,* 则无法 follow,跳转到 no_page 处理。*/if (!pte_present(pte))goto no_page;/** 如果该 PTE 标记为 PROT_NONE(无访问权限),* 并且当前操作不允许访问此类页面(如非 dump 场景),则拒绝访问。*/if (pte_protnone(pte) && !gup_can_follow_protnone(vma, flags))goto no_page;/** 尝试从 VMA 和 PTE 中获取对应的 struct page。* 对于普通匿名页或文件映射页,这会返回正确的 page 结构。* 如果是特殊映射(如设备内存、zero page),可能返回 NULL。*/page = vm_normal_page(vma, address, pte);/** 如果请求的是写访问(FOLL_WRITE),但 PTE 不允许写,* 或者页面本身不支持写共享(如 COW 页面已被多个进程共享),* 则不能 follow,返回失败。*/if ((flags & FOLL_WRITE) &&!can_follow_write_pte(pte, page, vma, flags)) {page = NULL;goto out;}/** 如果 page 为 NULL,但这是一个设备映射页(device memory, devmap),* 并且调用者需要获取引用(FOLL_GET 或 FOLL_PIN),* 那么我们需要获取对应的 dev_pagemap 引用,以确保生命周期安全。*/if (!page && pte_devmap(pte) && (flags & (FOLL_GET | FOLL_PIN))) {*pgmap = get_dev_pagemap(pte_pfn(pte), *pgmap);if (*pgmap)page = pte_page(pte);  // 获取设备内存对应的 page 结构elsegoto no_page;  // 获取失败,说明设备映射无效或不可访问} else if (unlikely(!page)) {/** 特殊情况处理:当 page == NULL 时,可能是:* - zero page(全零页面,惰性分配)* - 其他特殊映射(如 HUGETLB,但这里不处理)*/if (flags & FOLL_DUMP) {/* 在 core dump 中避免包含特殊页面(如 zero page) */page = ERR_PTR(-EFAULT);goto out;}if (is_zero_pfn(pte_pfn(pte))) {/* 映射的是全零页面(例如 bss 段未初始化部分) */page = pte_page(pte);} else {/* 其他未知 PTE 类型,尝试 follow PFN(物理帧号) */ret = follow_pfn_pte(vma, address, ptep, flags);page = ERR_PTR(ret);goto out;}}/* 转换为 folio(新式内存管理单元,一个 folio 可能包含多个 page) */folio = page_folio(page);/** 检查是否需要写时分离(unshare)。* 如果请求写访问,但 PTE 不可写,且页面是共享的(如 COW),* 则不能直接 follow,应返回 -EMLINK 触发 unsharing。*/if (!pte_write(pte) && gup_must_unshare(vma, flags, page)) {page = ERR_PTR(-EMLINK);goto out;}/** 调试检查:如果使用 FOLL_PIN pin 了一个匿名页,* 它必须是独占的(PageAnonExclusive),否则存在竞争风险。*/VM_BUG_ON_PAGE((flags & FOLL_PIN) && PageAnon(page) &&!PageAnonExclusive(page), page);/** 尝试增加页面的引用计数(refcount)。* 只有在设置了 FOLL_GET 或 FOLL_PIN 时才会真正增加。* 如果失败(如页面正在释放),返回错误。*/ret = try_grab_folio(folio, 1, flags);if (unlikely(ret)) {page = ERR_PTR(ret);goto out;}/** 如果是 FOLL_PIN 请求,需要确保页面内容对 CPU 可访问。* 某些设备内存或加密内存可能默认不可访问,* 必须显式调用 arch_make_folio_accessible() 来激活。* 失败时需回滚:调用 unpin_user_page() 释放引用。*/if (flags & FOLL_PIN) {ret = arch_make_folio_accessible(folio);if (ret) {unpin_user_page(page);page = ERR_PTR(ret);goto out;}}/** 如果设置了 FOLL_TOUCH,表示要“访问”这个页面:* - 若是写访问且页面未标记为 dirty,则标记为 dirty* - 更新 accessed 位,防止被过早回收** 注意:虽然 pte_mkyoung() 更精确,但 atomic 操作复杂,* 因此使用 folio_mark_accessed() 更安全。*/if (flags & FOLL_TOUCH) {if ((flags & FOLL_WRITE) &&!pte_dirty(pte) && !folio_test_dirty(folio))folio_mark_dirty(folio);folio_mark_accessed(folio);}out:/* 释放页表锁,并取消映射 PTE */pte_unmap_unlock(ptep, ptl);return page;no_page:/* 页面未驻留或不可访问 */pte_unmap_unlock(ptep, ptl);/* 如果 PTE 不为空(如 swap entry),返回 NULL 表示缺页 */if (!pte_none(pte))return NULL;/* 否则可能是无效地址,调用 no_page_table 进一步处理 */return no_page_table(vma, flags, address);
}

follow_page_pte() 的作用是:
在给定的虚拟地址空间(vma)中,通过页表项(PTE)查找并返回对应的 struct page *,同时根据标志位(flags)进行权限检查、引用计数增加、页面状态更新等操作。

常见 flag(gup_flags)解释:

标志作用
FOLL_WRITE以写入方式获取页(触发 COW 时复制页)
FOLL_FORCE即使 VM 区不可访问也强制获取(例如 ptrace)
FOLL_PIN将页固定在内存中,禁止换出(推荐代替 FOLL_GET)
FOLL_GET仅增加引用计数(早期 API)
FOLL_TOUCH标记页已被访问(更新 LRU)
FOLL_UNLOCKABLE允许自动加锁/解锁 mmap_lock
FOLL_NOWAIT不等待缺页中断,立即返回

2.2 demo

内核态代码:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/highmem.h>
#include <linux/mman.h>#define DEVICE_NAME "gup_demo"
#define IOCTL_PIN_MEM   _IOW('g', 1, struct gup_user_buf)
#define IOCTL_UNPIN_MEM _IO('g', 2)struct gup_user_buf {unsigned long addr;unsigned long size;
};static dev_t dev_num;
static struct class *cls;
static struct cdev cdev_gup;static struct page **pages = NULL;
static int pinned_pages = 0;
static int nr_pages = 1;/* IOCTL: 固定用户内存页 */
static long gup_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{long ret = 0;int i;struct gup_user_buf user;pr_info("current comm = %s\n", current->comm);switch (cmd) {case IOCTL_PIN_MEM:if (pinned_pages) {pr_warn("Already pinned pages, unpin first\n");return -EBUSY;}/* 从用户空间读取地址 */if (copy_from_user(&user, (void __user *)arg, sizeof(user))){pr_err("copy_from_user failed\n");return -EFAULT;}pr_info("IOCTL_PIN_MEM: pinning user address 0x%lx\n", user.addr);nr_pages = DIV_ROUND_UP(user.size + (user.addr & ~PAGE_MASK), PAGE_SIZE);pr_info("pr_info = %d\n", nr_pages);pages = kcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL);if (!pages)return -ENOMEM;mmap_read_lock(current->mm);ret = get_user_pages(user.addr, nr_pages, FOLL_WRITE, pages);mmap_read_unlock(current->mm);if (ret < 0) {pr_err("get_user_pages failed: %ld\n", ret);kfree(pages);pages = NULL;return ret;}pinned_pages = ret;pr_info("pinned_pages = %d\n", pinned_pages);//get_user_pages(user.addr, 1, FOLL_WRITE, pages) 拿到的是整页;//而字符串起始位置是页内偏移 offset = user.addr & ~PAGE_MASK。unsigned long offset = user.addr & ~PAGE_MASK;for (i = 0; i < pinned_pages; i++){pr_info("page[%d]: PFN = %lu\n", i, page_to_pfn(pages[i]));//kmap_local_page() 得到的页映射上加上偏移量来正确访问字符串void *kaddr = kmap_local_page(pages[i]);if (kaddr && i == 0) {char *data = (char *)kaddr + offset;/* 打印页前16字节十六进制 */print_hex_dump(KERN_INFO, "gup_test data: ",DUMP_PREFIX_OFFSET, 16, 1,data, min_t(size_t, 64, user.size), true);pr_info("gup_test data: %s\n",data);             kunmap_local(kaddr);}}break;case IOCTL_UNPIN_MEM:if (!pinned_pages)return -EINVAL;pr_info("IOCTL_UNPIN_MEM: releasing %d pages\n", pinned_pages);for (i = 0; i < pinned_pages; i++)put_page(pages[i]);kfree(pages);pages = NULL;pinned_pages = 0;break;default:ret = -EINVAL;break;}return ret;
}static const struct file_operations gup_fops = {.owner = THIS_MODULE,.unlocked_ioctl = gup_ioctl,
};static int __init gup_init(void)
{int ret;ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);if (ret < 0)return ret;cdev_init(&cdev_gup, &gup_fops);cdev_add(&cdev_gup, dev_num, 1);cls = class_create(DEVICE_NAME);device_create(cls, NULL, dev_num, NULL, DEVICE_NAME);pr_info("/dev/%s created, use ioctl to pin/unpin memory\n", DEVICE_NAME);return 0;
}static void __exit gup_exit(void)
{if (pinned_pages && pages) {int i;pr_info("Cleanup: releasing %d pinned pages\n", pinned_pages);for (i = 0; i < pinned_pages; i++)put_page(pages[i]);kfree(pages);}device_destroy(cls, dev_num);class_destroy(cls);cdev_del(&cdev_gup);unregister_chrdev_region(dev_num, 1);pr_info("gup_demo unloaded\n");
}module_init(gup_init);
module_exit(gup_exit);MODULE_LICENSE("GPL");

加载内核模块:

$ make
$ sudo insmod get_user_pages.ko

用户态程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>#define IOCTL_PIN_MEM   _IOW('g', 1, struct gup_user_buf)
#define IOCTL_UNPIN_MEM _IO('g', 2)struct gup_user_buf {unsigned long addr;unsigned long size;
};int main(void)
{int fd;void *buf;struct gup_user_buf ubuf;buf = malloc(4096);if (!buf) {perror("malloc");return 1;}printf("Allocated user buffer: %p\n", buf);strcpy(buf, "Hello from user space! This buffer will be pinned.\n");ubuf.addr = (unsigned long)buf;ubuf.size = 4096;fd = open("/dev/gup_demo", O_RDWR);if (fd < 0) {perror("open");return 1;}printf("Pinning user memory via ioctl...\n");if (ioctl(fd, IOCTL_PIN_MEM, &ubuf) < 0)perror("ioctl PIN");printf("Press ENTER to unpin and exit...\n");getchar();ioctl(fd, IOCTL_UNPIN_MEM);close(fd);free(buf);return 0;
}
$ sudo ./a.out 
Allocated user buffer: 0x5b79617d22a0
Pinning user memory via ioctl...
Press ENTER to unpin and exit...

查看结果:

$ sudo dmesg -c
[12186.225596] gup_demo unloaded
[12191.031194] /dev/gup_demo created, use ioctl to pin/unpin memory
[12192.876449] current comm = a.out
[12192.876454] IOCTL_PIN_MEM: pinning user address 0x5b79617d22a0
[12192.876456] pr_info = 2
[12192.876458] pinned_pages = 2
[12192.876459] page[0]: PFN = 1805595
[12192.876460] gup_test data: 00000000: 48 65 6c 6c 6f 20 66 72 6f 6d 20 75 73 65 72 20  Hello from user 
[12192.876462] gup_test data: 00000010: 73 70 61 63 65 21 20 54 68 69 73 20 62 75 66 66  space! This buff
[12192.876463] gup_test data: 00000020: 65 72 20 77 69 6c 6c 20 62 65 20 70 69 6e 6e 65  er will be pinne
[12192.876464] gup_test data: 00000030: 64 2e 0a 00 00 00 00 00 00 00 00 00 00 00 00 00  d...............
[12192.876464] gup_test data: Hello from user space! This buffer will be pinned.[12192.876465] page[1]: PFN = 1924760
[12193.894884] current comm = a.out
[12193.894891] IOCTL_UNPIN_MEM: releasing 2 pages

备注:
(1)kmap_local_page
作用:将一个物理内存页(struct page *)临时映射到内核虚拟地址空间,返回其可访问的虚拟地址。

典型用途:当内核需要直接读写某个页面内容时(比如来自 get_user_pages() 的用户页或高端内存页),但该页面没有永久内核映射(direct mapping)。

返回值:映射后的内核虚拟地址(如 void *addr = kmap_local_page(page);),之后可通过 addr 读写页面数据。

kmap_local_page(page) 是一个高效、安全、可在任何上下文调用的接口,用于临时映射任意物理页面到内核地址空间,适用于现代内核编程中的页面直接访问场景。

// 本地映射,性能好
void *addr = kmap_local_page(page);// 特点:
// - 映射在本地地址空间(每CPU)
// - 只能在当前上下文中使用
// - 无锁操作,性能优异
// - 基于栈管理,支持嵌套

而kmap:

// 全局映射,开销大
void *addr = kmap(page);// 特点:
// - 映射在全局地址空间
// - 可以跨上下文使用
// - 需要全局锁,性能较差
// - 数量有限(通常最多64个同时映射)

性能关键代码应优先使用 kmap_local_page()。
确保 kunmap_local() 调用配对且顺序正确。
不要长期持有映射,尽快使用后释放。

(2)

nr_pages = DIV_ROUND_UP(user.size + (user.addr & ~PAGE_MASK), PAGE_SIZE);

在 Linux 内核中,页(page)是内存管理的最小单位,一般大小为 4KB(即 PAGE_SIZE = 4096)。

一个用户缓冲区可能 从任意地址开始,并且长度也不是页对齐的。

但 get_user_pages() 固定的单位是 整页。

因此,必须计算这个缓冲区到底覆盖了多少个完整页。

举例说明:
假设:

user.addr = 0x1003;   // 用户缓冲区起始地址 (非对齐)
user.size = 5000;     // 用户缓冲区长度
PAGE_SIZE = 4096;     // 页大小

分步计算:
计算页内偏移量

user.addr & ~PAGE_MASK

等价于:

user.addr % PAGE_SIZE

在例子中:

0x1003 & 0xFFF = 0x003 = 3

说明这个缓冲区从页的第3个字节开始。

计算总共覆盖的内存范围:

user.size + (user.addr & ~PAGE_MASK)

在例子中:

5000 + 3 = 5003

也就是这个缓冲区从 页内偏移3 开始,总共跨越5003字节。

向上取整到页数:

DIV_ROUND_UP(5003, 4096) = 2

说明这段内存跨越 两页:
第一页从 0x1000 到 0x1FFF
第二页从 0x2000 到 0x2FFF

get_user_pages() 必须传入整页的数量,即便用户缓冲区只占其中一部分。
上述例子

user.addrsize实际覆盖范围页数
0x10004096正好一页1
0x10034096末尾多出部分跨页2
0x1FFF1跨越页边界2
0x20008192对齐跨两页2

(3)用户态的地址0x649f062702a0不是一个页对齐的,因此在内核态根据该用户态地址得到的是一整个页,调用kmap_local_page()获取的是该页映射的起始地址,是一个页大小对齐的,所以用户态的地址相对与kmap_local_page()获取的是该页映射的起始地址是有一个偏移量的,处理好偏移量才能读取到正确的数据。
代码如下:

unsigned long offset = user.addr & ~PAGE_MASK;
void *kaddr = kmap_local_page(pages[0]);
if (kaddr) {char *data = (char *)kaddr + offset;pr_info("user string: %s\n", data);kunmap_local(kaddr);
}

三、get_user_pages_unlocked

/*** get_user_pages_unlocked() - 无需手动加锁的获取用户页面函数* @start: 起始用户空间地址* @nr_pages: 要获取的页面数量* @pages: 接收页面指针的数组* @gup_flags: FOLL_* 标志位,控制获取行为** 这个函数旨在替换以下代码模式:**     mmap_read_lock(mm);*     get_user_pages(mm, ..., pages, NULL);*     mmap_read_unlock(mm);** 替换为:**     get_user_pages_unlocked(mm, ..., pages);** 在功能上它等同于 get_user_pages_fast(),所以如果不需要特定的 gup_flags* (例如 FOLL_FORCE),应该使用 get_user_pages_fast()。** 返回值: 成功返回获取的页面数量,失败返回错误码*/
long get_user_pages_unlocked(unsigned long start, unsigned long nr_pages,struct page **pages, unsigned int gup_flags)
{int locked = 0;/* 验证参数有效性,自动添加 FOLL_TOUCH 和 FOLL_UNLOCKABLE 标志 */if (!is_valid_gup_args(pages, NULL, &gup_flags,FOLL_TOUCH | FOLL_UNLOCKABLE))return -EINVAL;/* 调用内部函数,locked=0 表示由内部管理锁 */return __get_user_pages_locked(current->mm, start, nr_pages, pages,&locked, gup_flags);
}
EXPORT_SYMBOL(get_user_pages_unlocked);

从注释可以看到自动锁管理:

// 传统方式需要手动管理锁:
mmap_read_lock(mm);
get_user_pages(mm, start, nr_pages, gup_flags, pages, NULL);
mmap_read_unlock(mm);// 使用 unlocked 版本简化:
get_user_pages_unlocked(start, nr_pages, pages, gup_flags);

四、get_user_pages_fast

/*** get_user_pages_fast() - 快速固定用户页面在内存中* @start:      起始用户地址* @nr_pages:   从起始地址开始要固定的页面数量* @gup_flags:  修改固定行为的标志位* @pages:      接收指向固定页面的指针数组*              应该至少是 nr_pages 长度** 尝试在不获取 mm->mmap_lock 的情况下固定用户页面在内存中。* 如果不成功,它将回退到获取锁并调用 get_user_pages()。** 返回值: 成功固定的页面数量。这可能少于请求的数量。* 如果 nr_pages 为 0 或负数,返回 0。如果没有页面被固定,返回 -errno。*/
int get_user_pages_fast(unsigned long start, int nr_pages,unsigned int gup_flags, struct page **pages)
{/** 调用者可能显式设置了 FOLL_GET,也可能没有设置;两种方式都可以。* 然而,在内部(在 mm/gup.c 中),gup 快速变体必须设置 FOLL_GET,* 因为 gup fast 始终是一个"固定页面并增加页面引用计数"的请求。*/if (!is_valid_gup_args(pages, NULL, &gup_flags, FOLL_GET))return -EINVAL;return gup_fast_fallback(start, nr_pages, gup_flags, pages);
}
EXPORT_SYMBOL_GPL(get_user_pages_fast);

get_user_pages_fast() 函数是 GUP(Get User Pages)机制中的高性能路径,用于快速锁定(pin)用户空间页面,避免在常见情况下持有重量级锁 mmap_lock。

核心目标:无锁快速获取用户页面。
正常的 get_user_pages() 必须先持有 mmap_read_lock(mm),这会阻塞其他线程修改内存映射,影响性能。

get_user_pages_fast() 尝试 不加锁 地遍历页表(PTE),直接查找物理页。

如果失败(比如遇到复杂 VMA、缺页、权限不足等),则退化为传统方式:加锁 + 调用 get_user_pages()。

备注:
get_user_pages_fast必须设置 FOLL_GET。

即:
get_user_pages_fast() 总是意味着“我要 pin 这些页面并增加引用计数”。
FOLL_GET 表示:为每个 page 增加 page->_refcount。
这是为了确保页面不会被意外释放。

五、pin_user_pages

/*** pin_user_pages() - 为用户页面在内存中固定,供其他设备使用** @start:	起始用户地址* @nr_pages:	从起始地址开始要固定的页面数量* @gup_flags:	修改查找行为的标志位* @pages:	接收指向固定页面的指针数组*		应该至少是 nr_pages 长度** 几乎与 get_user_pages() 相同,除了不设置 FOLL_TOUCH,而是设置 FOLL_PIN。** FOLL_PIN 表示这些页面必须通过 unpin_user_page() 释放。* 详情请参阅 Documentation/core-api/pin_user_pages.rst。** 注意:如果返回的页面中包含零页面(zero_page),它不会有固定计数,* 并且 unpin_user_page*() 不会从中移除固定。*/
long pin_user_pages(unsigned long start, unsigned long nr_pages,unsigned int gup_flags, struct page **pages)
{int locked = 1;if (!is_valid_gup_args(pages, NULL, &gup_flags, FOLL_PIN))return 0;return __gup_longterm_locked(current->mm, start, nr_pages,pages, &locked, gup_flags);
}
EXPORT_SYMBOL(pin_user_pages);

pin_user_pages() 的语义:
“从用户态地址空间中,获取并长期固定一段物理页,使得外设或内核能够在一段时间内安全地访问它们,直到调用者显式释放(unpin)。”

pin_user_pages() 函数是 Linux 内核中用于 安全、长期固定用户页面(尤其是为设备如 DMA 使用)的现代接口。它是传统 get_user_pages() 的演进版本,专为解决“长期 pinning 问题”而设计。

与 get_user_pages() 的区别:

// get_user_pages() 使用 FOLL_TOUCH | FOLL_GET
// pin_user_pages() 使用 FOLL_PIN// 主要区别:
// get_user_pages():            pin_user_pages():
// - FOLL_TOUCH (标记访问)      - 无 FOLL_TOUCH
// - FOLL_GET (增加引用计数)    - FOLL_PIN (长期固定)
// - put_page() 释放           - unpin_user_page() 释放
// - 短期使用                  - 长期设备使用
特性get_user_pages()pin_user_pages()
引用类型普通引用 (get_page())固定引用 (pin_user_page())
标志通常包含 FOLL_TOUCH不包含 FOLL_TOUCH,但强制包含 FOLL_PIN
释放方式put_page()unpin_user_page()
用途短期访问(如 copy_to_user)长期 DMA、RDMA、GPU buffer 等场景
与零页的关系可增加引用计数零页不会被真正 pin

核心目标:专为设备使用而设计
主要用途:让设备(如 GPU、网卡、FPGA)通过 DMA 访问用户空间内存。
强调“其他设备”,意味着这些页面将被内核之外的实体访问。

这是它与普通 get_user_pages() 的根本区别:语义上声明“这是为了外部设备长期使用”。

FOLL_PIN 是一个语义标记,表示:

这是一个长期 pin 操作(long-term pinning)
页面将被设备使用(而非仅内核短暂访问)
必须使用专用释放函数 unpin_user_page() 来解 pin

pin_user_pages也有上述一系列函数:

pin_user_pages()
pin_user_pages_fast()
pin_user_pages_unlocked()
pin_user_pages_remote()
http://www.dtcms.com/a/516755.html

相关文章:

  • 《探索 Docker+Neko+cpolar 实现跨网共享浏览器的无限可能》
  • 调用百度云语音识别服务——实现c++接口识别语音
  • 浅谈常见的八类数据库加密技术
  • Beyond Compare 5 for Mac 好用的文件对比工具
  • 济南专业做网站公司哪家好做爰全过程免费的视频凤凰网站
  • 顶俏模式分析:低价洗衣液与三级分销机制的市场争议
  • 云服务器网站解析专业拓客团队怎么收费
  • 嘉兴网站建设网站海报设计兼职平台
  • 标准网站建设费用阿里企业邮箱怎么用
  • 西宁市城乡规划建设局网站海外网络推广方案
  • VMware替代 | ZStack ZSphere镜像与模板管理在迁移与运维中的关键作用
  • 【C++ 入门】:从语言发展脉络到实战细节 —— 详解命名空间、输入输出、缺省参数与函数重载
  • 搜网站首页不见了seowordpress 简书主题
  • 【开题答辩全过程】以 冰雪装备租赁平台系统设计与实现为例,包含答辩的问题和答案
  • 解锁数字时代安全密码:ICP许可证安全测评解析
  • 简约网站建设常见搜索引擎有哪些
  • 宁波定制网站建设自己做广告用什么软件
  • 经典文献阅读之--UniScene(统一的以占用为中心的驾驶场景生成)
  • Java学习之旅第三季-14:代理
  • 深度学习YOLO实战:7、摄像头实时目标检测完整实践
  • AI智能体编程的关键特性有哪些?
  • 何苦做游戏网站北京外贸推广
  • 装修网站建设策划方案如何给网站添加ico
  • 如何做网站二级域名个人网页包括哪些内容
  • AI、机器人、具身智能等领域顶级会议与学术组织解析
  • 提高运行效率的智慧物流开源了
  • Linux中批量压缩文件夹下的图片脚本命令
  • 郑州网站建设如何中山响应式网站
  • 沈阳哪里可以做网站营销石家庄制作网站
  • uniApp使用支付宝云开发问题集合