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是否知道在代码中明确写入的任何地址是物理地址,并且不应通过地址转换机制(逻辑地址 - >虚拟地址 - >到物理地址)?
提前致谢.
如果要在启用分页时访问特定物理地址,请将该物理地址映射到某处的虚拟内存中.如果您在现有操作系统下运行,则必须要求操作系统为您执行此操作.
您如何要求操作系统为您执行此操作当然是特定于操作系统的.
例如,在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
加载来有效地访问整个块.