linux - 内核虚拟地址如何准确地转换为物理RAM?

标签 linux memory-management linux-kernel

从表面上看,这似乎是一个愚蠢的问题。请耐心点.. :-)
我将这个qs分为两部分:

第1部分:
我完全理解平台RAM已映射到内核段。尤其是在64位系统上,效果很好。因此,每个内核虚拟地址的确只是物理内存(DRAM)的偏移量。

另外,据我了解,由于Linux是现代的虚拟内存操作系统,(几乎)所有地址都被视为虚拟地址,必须在运行时通过硬件(TLB/MMU)“运行”,然后由TLB/MMU进行翻译通过内核分页表。再次,对于用户模式过程来说很容易理解。

但是,内核虚拟地址呢?为了提高效率,直接映射这些映射会更简单(并且确实从PAGE_OFFSET开始设置了身份映射)。但是仍然,在运行时,内核虚拟地址必须通过TLB/MMU进入并正确翻译?确实是这样吗?还是内核虚拟地址转换只是偏移量计算? (但是,如何才能实现,因为我们必须通过硬件TLB/MMU进行?)。作为一个简单的例子,让我们考虑:

char *kptr = kmalloc(1024, GFP_KERNEL);

现在,kptr是内核虚拟地址。
我知道virt_to_phys()可以执行偏移量计算并返回物理DRAM地址。
但是,这是一个实际问题:这不能通过软件以这种方式完成-令人遗憾的是,这太慢了!因此,回到我之前的观点:必须通过硬件(TLB/MMU)进行翻译。
真的是这样吗?

第2部分:
好的,可以说是这种情况,并且我们确实在内核中使用分页来执行此操作,当然,我们必须设置内核分页表。我了解它的根源是swapper_pg_dir。

(我也知道vmalloc()与kmalloc()不同,这是特例-它是一个纯虚拟区域,仅在页面错误时才由物理帧支持)。

如果(在第1部分中)我们确实得出结论,内核虚拟地址转换是通过内核分页表完成的,那么内核分页表(swapper_pg_dir)究竟是如何“附加”或“映射”到用户模式进程的呢?这应该在上下文切换代码中发生吗?怎么样?哪里?

例如。
在x86_64上,两个进程A和B处于 Activity 状态,即1 cpu。
A正在运行,因此它是规范较高的地址0xFFFF8000 00000000 through 0xFFFFFFFF FFFFFFFF“映射”到内核段,它是标准的下位地址0x0 through 0x00007FFF FFFFFFFF映射到它的私有(private)用户空间。

现在,如果我们上下文切换A-> B,则进程B的下规范区域是唯一的,但是
它必须“映射”到相同的内核!
这到底是怎么发生的?我们如何“自动”引用内核分页表时
在内核模式下?还是那是错误的说法?

多谢您的耐心配合,我们将不胜感激!

最佳答案

首先介绍一下背景。

这个区域之间存在很大的潜在差异
建筑,但是原始海报表明他主要是
对x86和ARM感兴趣,它们具有几个特征:

  • 没有硬件段或虚拟地址空间的类似分区(当Linux使用时)
  • 硬件页表遍历
  • 多种页面大小
  • 物理标记的缓存(至少在现代ARM上)

  • 因此,如果我们将自己限制在那些系统上,它将使事情变得更简单。

    一旦启用了MMU,通常就不会关闭它。所以所有的CPU
    地址是虚拟的,并将转换为物理地址
    使用MMU。 MMU首先会在
    TLB,并且只有在TLB中找不到它时,它才会引用
    页表-TLB是页表的缓存-因此我们可以
    忽略TLB进行此讨论。

    页表
    描述整个虚拟的32或64位地址空间,并包括
    像这样的信息:
  • 虚拟地址是否有效
  • 处理器必须处于哪种模式才能使其有效
  • 特殊属性,用于内存映射的硬件寄存器
  • 和要使用
  • 的物理地址

    Linux将虚拟地址空间分为两部分:下半部分是
    用于用户流程,并且虚拟的与物理的不同
    每个过程的映射。上半部分用于内核,
    并且即使在不同用户之间切换时,映射也相同
    流程。这使事情变得简单,因为地址明确地在
    用户或内核空间,在以下情况下无需更改页表
    进入或离开内核,内核可以简单地取消引用
    指向用户空间的指针
    当前的用户进程。通常在32位处理器上,拆分为3G
    user/1G内核,尽管这可能有所不同。内核部分的页面
    仅当处理器时,地址空间的标记为可访问
    处于内核模式,以防止用户进程可以访问它们。
    内核地址空间中标识映射到RAM的部分
    (内核逻辑地址)将在可能的情况下使用大页面进行映射,
    这可以使页表变小,但更重要的是
    减少了TLB丢失的次数。

    内核启动时,它将为自己创建一个单页表
    (swapper_pg_dir),它仅描述了
    虚拟地址空间,并且没有针对用户部分的映射
    地址空间。然后,每次用户进程创建一个新页面
    该过程将生成表格,其中描述的部分
    每个这些页表中的内核内存都相同。这可能是
    通过复制swapper_pg_dir的所有相关部分来完成,但是
    因为页表通常是树形结构,所以内核是
    经常能够嫁接描述树的部分
    swapper_pg_dir到每个页表的内核地址空间
    用户流程,只需复制一些内容即可
    页表结构。以及更有效的存储(可能
    缓存)的使用情况,可以更轻松地保持映射的一致性。这个
    是内核和用户虚拟机之间 split 的原因之一
    地址空间只能出现在某些地址。

    要了解如何针对特定架构完成此操作,请参阅pgd_alloc()的实现。例如ARM
    (arch/arm/mm/pgd.c)使用:
    pgd_t *pgd_alloc(struct mm_struct *mm)
    {
        ...
        init_pgd = pgd_offset_k(0);
        memcpy(new_pgd + USER_PTRS_PER_PGD, init_pgd + USER_PTRS_PER_PGD,
                   (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t));
        ...
    }
    

    要么
    x86(arch/x86/mm/pgtable.c)pgd_alloc()调用pgd_ctor():
    static void pgd_ctor(struct mm_struct *mm, pgd_t *pgd)
    {
        /* If the pgd points to a shared pagetable level (either the
           ptes in non-PAE, or shared PMD in PAE), then just copy the
           references from swapper_pg_dir. */
            ...
            clone_pgd_range(pgd + KERNEL_PGD_BOUNDARY,
                    swapper_pg_dir + KERNEL_PGD_BOUNDARY,
                    KERNEL_PGD_PTRS);
        ...
    }
    

    因此,回到原始问题:

    第1部分:TLB/MMU是否真的翻译了内核虚拟地址?

    是。

    第2部分:swapper_pg_dir如何“附加”到用户模式进程。

    所有页表(swapper_pg_dir还是用于用户进程的页表)
    对于用于内核虚拟的部分具有相同的映射
    地址。因此,随着内核上下文在用户进程之间切换,
    更改当前页表,内核部分的映射
    的地址空间保持不变。

    关于linux - 内核虚拟地址如何准确地转换为物理RAM?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36639607/

    相关文章:

    linux - 输出列格式排序 linux

    php - 如何在 Linux 服务器上使用 php 获取客户端的 MAC 地址

    ruby-on-rails - 如何使用 MongoMapper 追踪内存泄漏?

    ios - ARC 及其工作原理。

    c - 在 Linux 中,如何确定 optmem_max 的最佳值?

    linux-kernel - Linux针对执行shellcode的安全措施

    linux-kernel - 如何将dts Linux设备树源文件编译为dtb?

    linux - 使用 sed 查找和替换带有 2 个分隔符的字符的好方法

    linux - 在 Unity,Ubuntu 13.04 中捕获关机事件的方法

    c - 静态变量的内存映射