如何映射 get_user_pages_fast 固定的页面以将它们用作驱动程序中几乎连续的缓冲区?
How to map pages pinned by get_user_pages_fast to use them as a virtually continuous buffer in the driver?
我正在编写 Linux 内核驱动程序,为用户 space 应用程序提供异步通信。实施工作如下:
- 应用程序通过
ioctl
传递接收数据的缓冲区地址
- 驱动使用
get_user_pages_fast
保证内核访问输出缓冲区,vmap
用于在内核中创建虚拟内存区域访问缓冲区
- 驱动程序在中断中填充了接收到的数据
- 接收到所有数据后,内核删除映射(使用
vunmap
)并放回用户页面,最后通知应用程序数据可用。
下面是代码的基本部分(从我的驱动程序中提取并简化):
struct page ** pages = NULL;
long pinned = 0;
int i;
void * vbuf = NULL;
void * vresp = NULL;
if (!access_ok(VERIFY_WRITE, buffer, max_data_length)) {
pr_alert("wrong access");
res = -EFAULT;
goto error1;
}
const unsigned long offset = ((unsigned long) buffer) & (PAGE_SIZE-1);
int nr_pages = DIV_ROUND_UP(offset + max_data_length, PAGE_SIZE);
pages = (struct page **) kzalloc(sizeof(struct page)*nr_pages, GFP_KERNEL);
if(!pages) {
pr_alert("can't alloc pages");
res = -EFAULT;
goto1;
}
pinned = get_user_pages_fast(((unsigned long) buffer ) & PAGE_MASK,nr_pages,1,pages);
if(pinned != nr_pages) {
for(i=0; i<pinned; i++) {
put_page(pages[i]);
}
kfree(pages);
pr_alert("can't pin pages");
res = -EFAULT;
goto error1;
}
vbuf = vmap(pages,nr_pages,VM_MAP, pgprot_writecombine(PAGE_KERNEL));
vresp = vbuf + offset;
if(!vbuf) {
pr_alert("can't vmap pages");
res = -EFAULT;
goto error1;
}
以上代码执行后,vresp指针用于存放接收到的数据。传输后,页面被释放:
int i;
vunmap(vbuf);
for(i=0; i < nr_pages; i++) {
set_page_dirty(pages[i]);
put_page(pages[i]);
}
kfree(pages);
原始代码在一些架构中工作,但在多核 ARM 机器上失败。看起来写入 vresp
指向的缓冲区的数据在用户 space 应用程序的 buffer
中不可见。我在代码中添加了控制打印并验证了地址是否正确。使用 vmap 为 get_user_pages_fast
交付的页面创建连续映射是否正确?
也许我应该使用 VM_MAP
以外的其他标志或 pgprot_writecombine(PAGE_KERNEL)
以外的其他保护?
今天我找到了答案。事实上,问题与 vmap
函数中的 prot
参数有关。它应该设置为 PAGE_KERNEL
而不是 pgprot_writecombine(PAGE_KERNEL))
。
在用户 space 应用程序中,此内存是通过缓存访问的,因此如果我创建映射时通过 pgprot_writecombine 部分禁用缓存,则会导致对内存的访问不一致。
我已将映射行修改为:
vbuf = vmap(pages,nr_pages,VM_MAP,PAGE_KERNEL);
代码甚至在多处理器 ARM 上也能正常工作。
我正在编写 Linux 内核驱动程序,为用户 space 应用程序提供异步通信。实施工作如下:
- 应用程序通过
ioctl
传递接收数据的缓冲区地址
- 驱动使用
get_user_pages_fast
保证内核访问输出缓冲区,vmap
用于在内核中创建虚拟内存区域访问缓冲区 - 驱动程序在中断中填充了接收到的数据
- 接收到所有数据后,内核删除映射(使用
vunmap
)并放回用户页面,最后通知应用程序数据可用。
下面是代码的基本部分(从我的驱动程序中提取并简化):
struct page ** pages = NULL;
long pinned = 0;
int i;
void * vbuf = NULL;
void * vresp = NULL;
if (!access_ok(VERIFY_WRITE, buffer, max_data_length)) {
pr_alert("wrong access");
res = -EFAULT;
goto error1;
}
const unsigned long offset = ((unsigned long) buffer) & (PAGE_SIZE-1);
int nr_pages = DIV_ROUND_UP(offset + max_data_length, PAGE_SIZE);
pages = (struct page **) kzalloc(sizeof(struct page)*nr_pages, GFP_KERNEL);
if(!pages) {
pr_alert("can't alloc pages");
res = -EFAULT;
goto1;
}
pinned = get_user_pages_fast(((unsigned long) buffer ) & PAGE_MASK,nr_pages,1,pages);
if(pinned != nr_pages) {
for(i=0; i<pinned; i++) {
put_page(pages[i]);
}
kfree(pages);
pr_alert("can't pin pages");
res = -EFAULT;
goto error1;
}
vbuf = vmap(pages,nr_pages,VM_MAP, pgprot_writecombine(PAGE_KERNEL));
vresp = vbuf + offset;
if(!vbuf) {
pr_alert("can't vmap pages");
res = -EFAULT;
goto error1;
}
以上代码执行后,vresp指针用于存放接收到的数据。传输后,页面被释放:
int i;
vunmap(vbuf);
for(i=0; i < nr_pages; i++) {
set_page_dirty(pages[i]);
put_page(pages[i]);
}
kfree(pages);
原始代码在一些架构中工作,但在多核 ARM 机器上失败。看起来写入 vresp
指向的缓冲区的数据在用户 space 应用程序的 buffer
中不可见。我在代码中添加了控制打印并验证了地址是否正确。使用 vmap 为 get_user_pages_fast
交付的页面创建连续映射是否正确?
也许我应该使用 VM_MAP
以外的其他标志或 pgprot_writecombine(PAGE_KERNEL)
以外的其他保护?
今天我找到了答案。事实上,问题与 vmap
函数中的 prot
参数有关。它应该设置为 PAGE_KERNEL
而不是 pgprot_writecombine(PAGE_KERNEL))
。
在用户 space 应用程序中,此内存是通过缓存访问的,因此如果我创建映射时通过 pgprot_writecombine 部分禁用缓存,则会导致对内存的访问不一致。
我已将映射行修改为:
vbuf = vmap(pages,nr_pages,VM_MAP,PAGE_KERNEL);
代码甚至在多处理器 ARM 上也能正常工作。