X86 CPU如何将地址转换为IO文本缓冲区等IO?

mna*_*bil 12 c paging x86 memory-management

如果我想访问位于地址的X86中的VGA文本缓冲区 0xb8000:

uint16_t *VGA_buffer = (uint16_t*)0xb8000;
Run Code Online (Sandbox Code Playgroud)

然后我索引变量VGA_buffer作为一个正常的阵列,即VGA_buffer[0],VGA_buffer[1]

但是,我在x86中读到了内存映射,其中列出的地址是物理地址.

我的问题是:

CPU如何访问此地址?CPU是否知道在代码中明确写入的任何地址是物理地址,并且不应通过地址转换机制(逻辑地址 - >虚拟地址 - >到物理地址)?

提前致谢.

Pet*_*des 7

如果要在启用分页时访问特定物理地址,请将该物理地址映射到某处的虚拟内存中.如果您在现有操作系统下运行,则必须要求操作系统为您执行此操作.


您如何要求操作系统为您执行此操作当然是特定于操作系统的.

例如,在Linux上,您可以通过mmap()系统调用来执行此操作/dev/mem,这是一个特殊的设备文件,可以访问整个物理地址空间.见mem(4)男子页.你所做的任何事情/dev/mem实际上都是由内核设备驱动程序函数处理的; 它只是一个让你映射物理内存的API.另请参见mmap'ing/dev/mem如何在非特权模式下工作?(你需要是root用户,即便如此,它只是映射内存,而不是在内核模式下运行,你可以在其中运行指令lidt).

这个超级用户的回答提到Linux CONFIG_STRICT_DEVMEM将其限制为仅实际的设备内存,并且通常在真实内核中启用.

例如:

int fd = open("/dev/mem", O_RDWR);
volatile uint16_t *vgabase = mmap(NULL, 256 * 1024,
                             PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0xb8000);
close(fd);
// TODO: error checking on system-call return values.
// Run  strace ./a.out to see what happens (not recommended with an X server running...)

vgabase[1] = 'a' + (0x07<<8);  // lightgrey-on-black
Run Code Online (Sandbox Code Playgroud)

http://wiki.osdev.org/VGA_Hardware#Video_Memory_Layout表示VGA内存高达256kiB,因此我将其全部映射. 请注意,0xb8000它用作偏移量/dev/mem.这就是你告诉内核要映射哪个物理内存的方法. 您还可以使用/dev/mem读/写或pread/pwrite系统调用,例如将缓冲区blit到给定位置的物理内存中.

uint16_t*您可以为文本模式定义结构,而不仅仅是:

struct vgatext_char {
    char c;
    union {  // anonymous union so you can do .fg or .color
      struct {uint8_t fg:4,
                      bg:4;
      };
      uint8_t color;
    };
};
// you might want to use this instead of uint16_t, 
// or with an anonymous union of this and uint16_t.
Run Code Online (Sandbox Code Playgroud)

CPU是否知道在代码中明确写入的任何地址都是物理地址,并且不应通过地址转换机制

所有加载/存储指令都将地址视为虚拟.即使编译器想要做一些不同的事情,它也不可能.x86没有绕过地址转换和分页权限检查的"存储 - 物理"指令.

请记住,CPU运行编译器生成的机器代码.此时,在C源中作为整数常量出现的地址与字符串常量的地址之间没有区别.(例如puts("Hello World");可能编译为mov edi,0x4005c4/ call puts).

例如,看看这个函数是如何编译的:

#include <stdio.h>
int foo() {
    puts("hello world");
    char *p = 0xb8000;
    puts(p);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在编译器的asm输出中(来自gcc -O3 for x86-64 Linux,在Godbolt上),我们看到:

    sub     rsp, 8

    mov     edi, OFFSET FLAT:.LC0   # address of the string constant
    call    puts
    mov     edi, 753664             # 0xB8000
    call    puts

    xor     eax, eax          # return 0;
    add     rsp, 8
    ret
Run Code Online (Sandbox Code Playgroud)

我通过它puts只是为了说明处理来自整数常量的指针绝对没有什么不同.当我们得到机器代码(链接器输出)时,引用字符串常量的地址的标签已被编译为立即数,就像0xB8000来自同一编译器 - 资源管理器链接的:反汇编输出:

 sub    rsp,0x8

 mov    edi,0x4005d4             # address of the string constant
 call   400410 <puts@plt>
 mov    edi,0xb8000
 call   400410 <puts@plt>

 xor    eax,eax
 add    rsp,0x8
 ret    
Run Code Online (Sandbox Code Playgroud)

只有在地址映射到物理之后,硬件才会检查它是否是常规DRAM,MMIO或设备内存.(这发生在Intel CPU上的系统代理程序中,CPU中的芯片上,但在单个内核之外).

对于DRAM,它还会检查正在使用的内存类型:WB(回写),USWC(不可缓存的推测性写入组合)或UC(不可缓存)或其他类型.VGA内存通常是USWC,因此一次写入一个字符很慢,读取它也是如此.使用movnt存储和movntdqa加载来有效地访问整个块.