memory-management - 了解 64 位 Linux 上的 kmap

标签 memory-management linux-kernel 32bit-64bit arm64 virtual-address-space

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

我试图了解 kmap 和地址空间如何在使用 defconfig 为 arm64 配置的 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)是用户-页面的空间地址,p2是页面的内核空间地址。 kmap() 将创建物理内存页到内核空间的临时映射。

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

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

    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() 成功时返回 0,失败时返回 -EFAULT

get_user_pages() 将返回固定到内存中的页面数,或者如果无法固定任何请求的页面,则返回负 errno 值。 (如果请求的页面数为0,则只会返回0。)实际固定的页面数可能会小于请求的数量,但由于您的代码只请求单个页面,因此返回值在这种情况下,将是 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() 将临时将页面映射到内核地址空间。它应该与对 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 可能跨越页面边界。健壮的代码需要处理这种情况。

由于通过用户空间地址访问内存需要通过特殊的用户空间访问函数和宏来完成,可能会因页面错误而休眠(除非页面已被锁定到物理内存中),并且仅有效(如果在单个进程中,使用 get_user_pages() 将用户空间地址区域锁定到内存中并将页面映射到内核地址空间(如果需要)在某些情况下很有用。它允许从任意内核上下文(例如中断处理程序)访问内存。它允许在内存映射 I/O 之间进行批量复制(memcpy_toio()memcpy_fromio())。一旦用户内存被 get_user_pages() 锁定,就可以在用户内存上执行 DMA 操作。在这种情况下,页面将由 DMA API 映射到“DMA 地址”。

关于memory-management - 了解 64 位 Linux 上的 kmap,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63440519/

相关文章:

c# - 显示 AnyCPU 应用程序中的所有进程

ios - A64 : objc_msgSend crashes while performSelector:withObject: works

linux - 为什么 sizeof(spinlock_t) 在单处理器上大于零?

linux - 将 32 位库链接到 64 位程序

c - 找到程序中分配的内存?

ios - 我可以释放添加到NSMutableArray中的对象吗?这会导致崩溃吗?

c - 如何在 Linux 中调试多线程挂起的进程?

linux - .ko 文件未创建

c - 为什么 valgrind 在一个简单的 while 循环中检测到如此多的内存分配

c - 为什么 malloc 分配的内存空间比我要求的多?