了解64位Linux上的kmap

问题描述

首先让我承认,即使阅读了一些相关资源,Linux上的高内存和低内存的概念仍然不完全清楚。但是,据我对64位Linux的了解,无论如何都没有高内存(如果我错了,请纠正我)。

我试图了解kmap和地址空间如何在为arm64配置了defconfig的Linux内核版本5.8.1上工作。

我添加了以下系统调用:

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;
}

我从用户空间分配了整个内存页面(在页面边界上),并将其作为该系统调用的参数传递给内核。通过从内核内部解引用任何一个指针来修改该内存,都可以很好地工作。但是,两个指针具有不同的值:

[    4.493480] kernel: first: 0x4ff3000
[    4.493888] kernel: second: 0xffff000007ce9000

据我了解,get_user_pages返回与该用户地址相对应的物理页面(在当前地址空间中)。然后,由于没有足够的内存,我希望kmap从地址空间的用户部分返回完全相同的地址。

根据virtual memory layout of arm64kmap返回的地址位于描述为“内核逻辑内存映射”的范围内。这是kmap刚刚创建的新映射,还是同一页面的另一个先前存在的映射?

有人可以解释这里到底发生了什么吗?

解决方法

user_addr(或p1)和p2所引用的内存一旦被get_user_pages()实际固定到物理内存中,便是相同的物理内存页。 (在get_user_pages()调用之前,页面可能尚未在物理内存中。)但是,user_addr(和p1)是页面的用户空间地址,而{{1 }}是页面的内核空间地址。 p2将创建物理内存页面到内核空间的临时映射。

在arm64(以及amd64)上,如果将位63视为符号位,则用户空间地址为非负,而内核空间地址为负。因此,用户空间和内核空间地址的数值不可能相等。

大多数内核代码不应直接取消引用用户空间指针。应使用用户空间内存访问功能和宏,并应检查是否存在故障。示例的第一部分应该类似于:

kmap()

int __user *p1 = (int __user *)user_addr; if (put_user(1,p1)) return -EFAULT; pr_info("kernel: first: 0x%lx\n",(unsigned long)p1); 成功返回0或失败返回put_user()

-EFAULT将返回固定到内存中的页面数,或者如果无法固定所有请求的页面,则返回负errno值。 (如果请求的页面数为0,它将仅返回get_user_pages()。)实际固定的页面数可能少于请求的页面数,但是由于您的代码仅请求单个页面,因此返回值将为0或errno负值。您可以使用变量来捕获错误号。请注意,必须在锁定当前任务的mmap信号量的情况下调用它:

1

注意:您可以使用#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,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()的调用配对以释放临时映射:

kunmap()

p2 = kmap(pages[0]); /* do something with p2 here ... */ kunmap(p2); 固定的页面在完成后需要使用get_user_pages()进行“放置”。如果已将其写入,则首先需要使用put_page()将其标记为“脏”。您的示例的最后一部分应类似于:

set_page_dirty_lock()

上面的代码并不完全健壮。对于 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将用户空间地址区域锁定在内存中,并将页面映射到内核地址空间(如果需要)在某些情况下很有用。它允许从任意内核上下文(例如中断处理程序)访问内存。它允许往返于内存映射的I / O(get_user_pages()memcpy_toio())的批量复制。一旦被memcpy_fromio()锁定后,就可以在用户存储器上执行DMA操作。在这种情况下,页面将通过DMA API映射到“ DMA地址”。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...