【问题标题】:how to avoid caching when writing to mmio registers?写入 mmio 寄存器时如何避免缓存?
【发布时间】:2016-10-02 23:34:21
【问题描述】:

我正在 virtualbox 中编写自定义操作系统,但在从 IOAPIC mmio 寄存器中成功写入和读取时遇到问题。即它似乎忽略了索引寄存器写入。在使用 IOAPIC 基地址(根据 ACPI 枚举确定为 0xFEC00000)加载 R8 后,我使用以下例程进行读/写:

; -----------------------------------------------------------------------------
; IN :  RAX = ioapic address, EBX = index register
; OUT:  ECX = return value
ioapic_read:
    mov [r8], ebx
    mov ecx, [r8 + 0x10]
    ret
; -----------------------------------------------------------------------------
; IN :  RAX = ioapic address, EBX = index register, ECX = value
; OUT:  -
ioapic_write:
    mov [r8], ebx
    mov [r8 + 0x10], ecx
    ret        

但 ioapic_read 将始终返回最后写入的值(由 ioapic_write),而与使用的索引无关。我有身份分页设置来使用 0x9B,我认为应该禁用缓存。

我尝试在每个movs 之后使用pause。没有帮助。在movs 之间尝试了mfences。没有帮助。

我已确认0xFEC00000 地址已成功进行身份映射。

看起来仍有一些缓存正在进行。我错过了什么?

编辑

我发现这不是缓存问题,而是一些很陌生的东西——至少对我无知的大脑来说是这样。我的身份分页按需工作,因此页面错误将在表中生成正确的物理页面。

这似乎有效,但在 IOAPIC mmio 寄存器的情况下,我需要在尝试使用 0xFEC00000 地址之前通过对 0xFEC00000 地址进行虚拟读取或写入来导致页面错误。更奇怪的是,我需要先做这个假人阅读足够的说明,否则它不起作用。例如

这行得通!

 mov eax, [os_IOAPICAddress]
 mov dword[rax], 0
 mov r8, rax
 .
 .
 .
 call ioapic_read

...这不是!

 mov eax, [os_IOAPICAddress]
 mov r8, rax
 mov dword[rax], 0
 .
 .
 .
 call ioapic_read

我怀疑存在流水线/序列化问题,但我真的很想了解为什么我需要在 MMIO 寄存器中使用地址之前将地址分页到表中,以及为什么我需要提前足够远的时间做这件事。在后一种情况下,如何修复它以便它被序列化,所以我不需要担心它。

我的身份寻呼例程:

pageFault_identity_0x0E:
    pop r8
    push rsi rdi rax rcx rdx r9

    test r8, 1
    jnz exception_gate_14
    mov rdx, cr2                                   ; faulting address
    shr rdx, 39
    and rdx, 0x1FF                                 ; get 9 bit index      

    mov rdi, cr3
    lea rsi, [rdi + rdx*8]
    mov rdi, [rsi]
    test rdi, 1
    jnz @f
    call set_new_page_table                                               
@@:
    shr rdi, 12                                     ; get rid of flags
    shl rdi, 12

    mov rdx, cr2
    shr rdx, 30                                     ; get 9 bit index    
    and rdx, 0x1FF

    lea rsi, [rdi + rdx*8]
    mov rdi, [rsi]
    test rdi, 1
    jnz @f
    call set_new_page_table                                               
@@:
    shr rdi, 12                                     ; get rid of flags
    shl rdi, 12

    mov rdx, cr2
    shr rdx, 21
    mov rax, rdx
    and rdx, 0x1FF                                  ; get 9 bit index    
    lea rsi, [rdi + rdx*8]

    shl rax, 21
    or rax, 0x83
    mov [rsi], rax

    shr rax, 21
    shl rax, 21

    pop r9 rdx rcx rax rdi rsi
    iretq
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;
; IN:   rsi = address of blank entry
; OUT:  rdi = base address of new table, changes rax & rcx
;
set_new_page_table:                                ; make table, get it, zero it, insert base into previous table
    movzx rdi, [page_table_count]
    shl rdi, 12
    add rdi, NEW_PAGE_TABLES

    CLEAR_BLOCK rdi, 0x200                     ; clears 4096 bytes in rdi, returns rdi + 4096

    sub rdi, 0x1000
    lea rax, [rdi + 0x3]                              ; table base address
    mov [rsi], rax
    inc [page_table_count]
    ret

【问题讨论】:

  • 这些函数ioapic_readioapic_write是从C调用的吗?
  • 不,整件事情都在组装中
  • 如果您在写入寄存器之前读取它们,您会得到合理的值吗?大多数 IOAPIC 寄存器都是完全可写的,您是否尝试过编写像 IOAPICVER 或 IOAPICARB 这样的 RO 寄存器?
  • 我知道您要么有错误,要么您使用的文件大于 4kb。你的问题并没有说它只是说 0x9b 没有真正的上下文。
  • 查看您的页面错误处理程序。我是不是弄错了,但你是在破坏 R8 的内容而不恢复它吗?您将错误代码弹出到 R8 中,但这意味着 R8 的先前值已​​被丢弃。当您的故障处理程序终止时,R8 很可能不再具有原始值。

标签: caching assembly x86-64 osdev apic


【解决方案1】:

给定原始代码,看起来好像您正在正确设置页面目录条目位以将 MMIO 区域标记为不可缓存。我确信还有其他问题。在您随后的编辑中,您向我们展示了您的页面错误处理程序pageFault_identity_0x0

pageFault_identity_0x0E:
    pop r8
    push rsi rdi rax rcx rdx r9

当处理器将控制权转移到此页面错误异常处理程序时,它将在堆栈顶部传递一个错误代码作为参数。问题是您将 R8 的内容替换为错误号,而没有保存然后恢复寄存器。

您必须修改您的异常处理程序以保留 R8,将内容从错误编号所在的正确堆栈偏移量移动到 R8。请记住确保错误号不再位于 IRETQ 之前的堆栈顶部。

您遇到的奇怪行为可能与 R8 在从页面错误返回时未正​​确恢复直接相关。


一个可行的解决方案是:

pageFault_identity_0x0E:
    push rsi
    push rdi
    push rax
    push rcx
    push rdx
    push r9
    push r8

    mov r8, [rsp+7*8]    ; Error Code is at offset RSP+7*8 after all the pushes
    ; Do exception handling work here

    pop r8
    pop r9
    pop rdx
    pop rcx
    pop rax
    pop rdi
    pop rsi

    add rsp, 8           ; Remove the error code
    iretq

【讨论】:

  • 我浪费了 2 天的时间试图找到这个。我彻底调试了那个分页程序,我确信它不是罪魁祸首。但不知何故,我错过了那个流行的 R8。这是我的负担:)
  • 没问题,我很高兴您添加了页面错误处理程序代码。我怀疑这是问题所在。很高兴它现在对你有用。内核开发和调试可能是一件棘手的事情。你可能只是需要一双全新的眼睛来看待它。
【解决方案2】:

Michael 解决了它,但为了完整起见,我将发布我的最终实现。

pageFault_identity_0x0E:
    test qword[rsp], 1
    jnz exception_gate_14
    add rsp, 8

    push rsi rdi rax rcx rdx
    mov rdx, cr2                                   ; faulting address
    .
    .
    .
    pop rdx rcx rax rdi rsi
    iretq

编辑:已编辑删除xchg

【讨论】:

  • 我避免将 XCHG 与内存操作数一起使用,主要是因为它会导致隐式锁定,从而导致相应缓存行的独占所有权和性能损失。带有两个寄存器的 XCHG 虽然没有这个问题。英特尔优化指南有这个建议“尽量减少在内存位置上使用 xchg 指令”
  • 干杯。我不知道。已经修好了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-25
  • 2021-12-25
  • 2011-12-29
相关资源
最近更新 更多