草庐IT

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

coder 2023-06-16 原文

从表面上看,这似乎是一个愚蠢的问题。请耐心点.. :-)
我将这个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 - 内核虚拟地址如何准确地转换为物理RAM?的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

      我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

    4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    5. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    6. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    7. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    8. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

      我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

    9. ruby - 如何指定 Rack 处理程序 - 2

      Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

    10. ruby - 将数组的内容转换为 int - 2

      我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

    随机推荐