Ответ 1
Если вы хотите получить доступ к определенному физическому адресу во время подкачки, сопоставить этот физический адрес в виртуальной памяти где-нибудь. Если вы работаете под существующей ОС, это то, что вы должны попросить ОС сделать для вас.
Как вы просите ОС сделать это для вас, конечно, зависит от ОС.
Например, в 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
http://wiki.osdev.org/VGA_Hardware#Video_Memory_Layout говорит, что VGA-память до 256киB, поэтому я отобразил все. Обратите внимание, что 0xb8000
используется как смещение в /dev/mem
. То, как вы указываете ядру, какую физическую память вы хотите отобразить. Вы также можете использовать /dev/mem
с чтением/записью или pread
/pwrite
, например чтобы разбить буфер в физическую память в заданной позиции.
Вместо просто 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.
Знает ли CPU, что любой адрес, явно указанный в коде, является физическим адресом и не должен проходить через механизмы преобразования адресов.
Все инструкции загрузки/хранения будут обрабатывать адреса как виртуальные. Даже если компилятор хотел сделать что-то другое, он не смог. x86 не имеет инструкции "store-physical", которая обходит проверки разрешений адресов и подкачки.
Помните, что 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;
}
В выводе asm компилятора (из gcc -O3 для 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
Я передал его puts
, чтобы проиллюстрировать, что абсолютно ничего не изменилось в том, как обрабатывается указатель, который исходит из целочисленной константы. К тому времени, когда мы получаем машинный код (вывод компоновщика), метка, ссылающаяся на адрес строковой константы, была скомпилирована с непосредственной константой, как и вывод 0xb8000
: disassembly из той же самой ссылки для компилятора-проводника:
sub rsp,0x8
mov edi,0x4005d4 # address of the string constant
call 400410 <[email protected]>
mov edi,0xb8000
call 400410 <[email protected]>
xor eax,eax
add rsp,0x8
ret
Это только после того, как адреса сопоставлены с физическим, что аппаратное обеспечение проверяет, является ли оно регулярным DRAM, MMIO или память устройства. (Это происходит в системном агенте на процессорах Intel, на чипе в CPU, но вне отдельного ядра).
И для DRAM он также проверяет, какой тип памяти используется: WB (обратная связь), USWC (несовместимое умозрительное объединение записи) или UC (несовместимый) или другие. VGA-память обычно USWC, поэтому запись в нее одного char в то же время медленная, и поэтому читает ее. Используйте movnt
магазины и movntdqa
для эффективного доступа к целым блокам.