了解 64 位上的 kmap Linux
Understanding kmap on 64-bit Linux
首先承认 Linux 上的高端内存和低端内存的概念,即使阅读了几个相关资源,我的脑海中仍然不是很清楚。但是,根据我对 64 位 Linux 的理解,无论如何都没有高内存(如果我错了请纠正我)。
我想了解 kmap 和地址 spaces 如何在 Linux 内核版本 5.8.1 上工作,为 arm64 配置 defconfig
。
我添加了以下系统调用:
SYSCALL_DEFINE1(mycall, unsigned long __user, user_addr)
{
struct page *pages[1];
int *p1, *p2;
p1 = (int *) user_addr;
*p1 = 1; /* this works */
pr_info("kernel: first: 0x%lx", (long unsigned) p1);
if (get_user_pages(user_addr, 1, FOLL_WRITE, pages, NULL) != 1)
return -1;
p2 = kmap(pages[0]);
*p2 = 2; /* this also works */
pr_info("kernel: second: 0x%lx", (long unsigned) p2);
return 0;
}
从 user-space 我分配了整个内存页面(在页面边界上),我将其作为该系统调用的参数传递给内核。通过从内核中取消引用任一指针来修改该内存工作得很好。但是,这两个指针具有不同的值:
[ 4.493480] kernel: first: 0x4ff3000
[ 4.493888] kernel: second: 0xffff000007ce9000
据我了解get_user_pages
return是对应于该用户地址的物理页面(在当前地址space)。然后,由于没有高内存,我希望 kmap
到 return 与地址 space.
的用户部分完全相同的地址
根据 virtual memory layout of arm64,由 kmap
编辑的地址 return 位于描述为“内核逻辑内存映射”的范围内。这是 kmap
刚刚创建的新映射还是同一页面之前存在的另一个映射?
有人可以解释一下这里到底发生了什么吗?
user_addr
(或p1
)和p2
引用的内存将是相同的物理内存页,一旦它们被[=18]实际固定到物理内存中=]. (在 get_user_pages()
调用之前,页面可能还不在物理内存中。)但是,user_addr
(和 p1
)是页面的 user-space 地址,并且 p2
是页面的 kernel-space 地址。 kmap()
将创建物理内存页到 kernel-space 的临时映射。
在 arm64(以及 amd64)上,如果第 63 位被视为符号位,则 user-space 地址为 non-negative,内核 space 地址为负。所以 user-space 和 kernel-space 地址的数值不可能相等。
大多数内核代码不应直接取消引用 user-space 指针。应使用 user-space 内存访问函数和宏,并应检查是否存在故障。你的例子的第一部分应该是这样的:
int __user *p1 = (int __user *)user_addr;
if (put_user(1, p1))
return -EFAULT;
pr_info("kernel: first: 0x%lx\n", (unsigned long)p1);
put_user()
成功时 return 0 或失败时 -EFAULT
。
get_user_pages()
将 return 固定到内存中的页面数,或者如果可以固定所请求页面的 none 个负错误值。 (如果请求的页面数为 0,它只会 return 0
。)实际固定的页面数可能少于请求的数量,但由于您的代码仅请求单个页面,因此 return 值在这种情况下要么是 1
要么是一个负的 errno 值。您可以使用变量来捕获错误号。请注意,必须在当前任务的 mmap 信号量锁定的情况下调用它:
#define NR_REQ 1
struct page *pages[NR_REQ];
long nr_gup;
mmap_read_lock(current->mm);
nr_gup = get_user_pages(user_addr, NR_REQ, FOLL_WRITE, pages, NULL);
mmap_read_unlock(current->mm);
if (nr_gup < 0)
return nr_gup;
if (nr_gup < NR_REQ) {
/* Some example code to deal with not all pages pinned - just 'put' them. */
long i;
for (i = 0; i < nr_gup; i++)
put_page(pages[i]);
return -ENOMEM;
}
注意:您可以使用 get_user_pages_fast()
而不是 get_user_pages()
。如果使用 get_user_pages_fast()
,则必须删除上面对 mmap_read_lock()
和 mmap_read_unlock()
的调用:
#define NR_REQ 1
struct page *pages[NR_REQ];
long nr_gup;
nr_gup = get_user_pages_fast(user_addr, NR_REQ, FOLL_WRITE, pages);
if (nr_gup < 0)
return nr_gup;
if (nr_gup < NR_REQ) {
/* Some example code to deal with not all pages pinned - just 'put' them. */
long i;
for (i = 0; i < nr_gup; i++)
put_page(pages[i]);
return -ENOMEM;
}
kmap()
会临时映射一个页面到内核地址space。它应该与对 kunmap()
的调用配对以释放临时映射:
p2 = kmap(pages[0]);
/* do something with p2 here ... */
kunmap(p2);
由 get_user_pages()
固定的页面在完成后需要使用 put_page()
'put'。如果它们已被写入,则首先需要使用 set_page_dirty_lock()
将它们标记为 'dirty'。你的例子的最后一部分应该是这样的:
p2 = kmap(pages[0]);
*p2 = 2; /* this also works */
pr_info("kernel: second: 0x%lx\n", (unsigned long)p2);
kunmap(p2);
set_page_dirty_lock(pages[0]);
put_page(pages[0]);
以上代码并不完全健壮。指针 p2
可能因 *p2
取消引用而未对齐,或者 *p2
可能跨越页面边界。健壮的代码需要处理这种情况。
由于通过 user-space 地址访问内存需要通过特殊的 user-space 访问函数和宏来完成,可能会因页面错误而休眠(除非页面已被锁定到物理内存中),并且仅在单个进程中有效(如果有的话),使用 get_user_pages()
将 user-space 地址区域锁定到内存并将页面映射到内核地址 space (如果需要)在一些情况。它允许从任意内核上下文(例如中断处理程序)访问内存。它允许批量复制到和从内存映射 I/O(memcpy_toio()
或 memcpy_fromio()
)。一旦被 get_user_pages()
锁定,就可以在 user-memory 上执行 DMA 操作。在那种情况下,页面将被 DMA API.
映射到“DMA 地址”
首先承认 Linux 上的高端内存和低端内存的概念,即使阅读了几个相关资源,我的脑海中仍然不是很清楚。但是,根据我对 64 位 Linux 的理解,无论如何都没有高内存(如果我错了请纠正我)。
我想了解 kmap 和地址 spaces 如何在 Linux 内核版本 5.8.1 上工作,为 arm64 配置 defconfig
。
我添加了以下系统调用:
SYSCALL_DEFINE1(mycall, unsigned long __user, user_addr)
{
struct page *pages[1];
int *p1, *p2;
p1 = (int *) user_addr;
*p1 = 1; /* this works */
pr_info("kernel: first: 0x%lx", (long unsigned) p1);
if (get_user_pages(user_addr, 1, FOLL_WRITE, pages, NULL) != 1)
return -1;
p2 = kmap(pages[0]);
*p2 = 2; /* this also works */
pr_info("kernel: second: 0x%lx", (long unsigned) p2);
return 0;
}
从 user-space 我分配了整个内存页面(在页面边界上),我将其作为该系统调用的参数传递给内核。通过从内核中取消引用任一指针来修改该内存工作得很好。但是,这两个指针具有不同的值:
[ 4.493480] kernel: first: 0x4ff3000
[ 4.493888] kernel: second: 0xffff000007ce9000
据我了解get_user_pages
return是对应于该用户地址的物理页面(在当前地址space)。然后,由于没有高内存,我希望 kmap
到 return 与地址 space.
根据 virtual memory layout of arm64,由 kmap
编辑的地址 return 位于描述为“内核逻辑内存映射”的范围内。这是 kmap
刚刚创建的新映射还是同一页面之前存在的另一个映射?
有人可以解释一下这里到底发生了什么吗?
user_addr
(或p1
)和p2
引用的内存将是相同的物理内存页,一旦它们被[=18]实际固定到物理内存中=]. (在 get_user_pages()
调用之前,页面可能还不在物理内存中。)但是,user_addr
(和 p1
)是页面的 user-space 地址,并且 p2
是页面的 kernel-space 地址。 kmap()
将创建物理内存页到 kernel-space 的临时映射。
在 arm64(以及 amd64)上,如果第 63 位被视为符号位,则 user-space 地址为 non-negative,内核 space 地址为负。所以 user-space 和 kernel-space 地址的数值不可能相等。
大多数内核代码不应直接取消引用 user-space 指针。应使用 user-space 内存访问函数和宏,并应检查是否存在故障。你的例子的第一部分应该是这样的:
int __user *p1 = (int __user *)user_addr;
if (put_user(1, p1))
return -EFAULT;
pr_info("kernel: first: 0x%lx\n", (unsigned long)p1);
put_user()
成功时 return 0 或失败时 -EFAULT
。
get_user_pages()
将 return 固定到内存中的页面数,或者如果可以固定所请求页面的 none 个负错误值。 (如果请求的页面数为 0,它只会 return 0
。)实际固定的页面数可能少于请求的数量,但由于您的代码仅请求单个页面,因此 return 值在这种情况下要么是 1
要么是一个负的 errno 值。您可以使用变量来捕获错误号。请注意,必须在当前任务的 mmap 信号量锁定的情况下调用它:
#define NR_REQ 1
struct page *pages[NR_REQ];
long nr_gup;
mmap_read_lock(current->mm);
nr_gup = get_user_pages(user_addr, NR_REQ, FOLL_WRITE, pages, NULL);
mmap_read_unlock(current->mm);
if (nr_gup < 0)
return nr_gup;
if (nr_gup < NR_REQ) {
/* Some example code to deal with not all pages pinned - just 'put' them. */
long i;
for (i = 0; i < nr_gup; i++)
put_page(pages[i]);
return -ENOMEM;
}
注意:您可以使用 get_user_pages_fast()
而不是 get_user_pages()
。如果使用 get_user_pages_fast()
,则必须删除上面对 mmap_read_lock()
和 mmap_read_unlock()
的调用:
#define NR_REQ 1
struct page *pages[NR_REQ];
long nr_gup;
nr_gup = get_user_pages_fast(user_addr, NR_REQ, FOLL_WRITE, pages);
if (nr_gup < 0)
return nr_gup;
if (nr_gup < NR_REQ) {
/* Some example code to deal with not all pages pinned - just 'put' them. */
long i;
for (i = 0; i < nr_gup; i++)
put_page(pages[i]);
return -ENOMEM;
}
kmap()
会临时映射一个页面到内核地址space。它应该与对 kunmap()
的调用配对以释放临时映射:
p2 = kmap(pages[0]);
/* do something with p2 here ... */
kunmap(p2);
由 get_user_pages()
固定的页面在完成后需要使用 put_page()
'put'。如果它们已被写入,则首先需要使用 set_page_dirty_lock()
将它们标记为 'dirty'。你的例子的最后一部分应该是这样的:
p2 = kmap(pages[0]);
*p2 = 2; /* this also works */
pr_info("kernel: second: 0x%lx\n", (unsigned long)p2);
kunmap(p2);
set_page_dirty_lock(pages[0]);
put_page(pages[0]);
以上代码并不完全健壮。指针 p2
可能因 *p2
取消引用而未对齐,或者 *p2
可能跨越页面边界。健壮的代码需要处理这种情况。
由于通过 user-space 地址访问内存需要通过特殊的 user-space 访问函数和宏来完成,可能会因页面错误而休眠(除非页面已被锁定到物理内存中),并且仅在单个进程中有效(如果有的话),使用 get_user_pages()
将 user-space 地址区域锁定到内存并将页面映射到内核地址 space (如果需要)在一些情况。它允许从任意内核上下文(例如中断处理程序)访问内存。它允许批量复制到和从内存映射 I/O(memcpy_toio()
或 memcpy_fromio()
)。一旦被 get_user_pages()
锁定,就可以在 user-memory 上执行 DMA 操作。在那种情况下,页面将被 DMA API.