Почему мой расчет смещения видеопамяти отличается на единицу?

Я читал и следовал руководству Ника Бланделла по написанию операционной системы с нуля, которое можно найти по адресу https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11./lectures/os-dev.pdf

Я успешно написал загрузчик, который может вызывать код C, и поэтому я начал писать свое ядро ​​​​на C. Сейчас я пытаюсь написать функции, которые могут выводить символы и строки на экран. К тому времени, когда я начинаю выполнять код C, я нахожусь в 32-битном защищенном режиме, поэтому я пытаюсь правильно вычислить смещения памяти от адреса видеопамяти 0xb8000.

Моя проблема начинается, когда я пытаюсь получить доступ к определенным областям видеопамяти, используя вычисленное смещение. Поскольку текстовая область составляет 25 строк на 80 столбцов, я использую формулу ((строка * 80) + столбец) * 2, так как мне нужно иметь байт символа и байт атрибута. Когда я устанавливаю строку = 0 и столбец = 0, X, который я пытаюсь напечатать, отсутствует. Установив строку = 0 и столбец = 1, X появится в верхнем левом углу.

Начиная с char* video_memory = 0xb8000 и неоднократно выдавая video_memory++, я могу правильно обращаться к каждому байту и печатать пробел на черном фоне.

Это мой основной код:

#include "../drivers/screen.h"

void main() {

   //clear_screen();
   //print_character('X', 0, 0, 0);

   // Helper variables.
   int row;
   int column;

   // We need to point at 0xB8000, where video memory resides.
   unsigned char* video_memory = (unsigned char*)0xB8000;
   for(row = 0; row < 25; row++) {
      for(column = 0; column < 80; column++) {
         // Clear the screen by printing a space on a black background.
         *video_memory = ' ';
         video_memory += 1;
         *video_memory = 0x0F;
         video_memory += 1;
      }
   }

   // Test the offset calculation by printing at row 0, column 0 (the upper 
   // left corner of the screen).
   row = 0;
   column = 0;

   // For an 80 by 25 grid. Multiply by 2 to account for the need of two bytes 
   // to display a character with given attributes.
   int offset = ((row * 80) + column) * 2;

   // Reset memory location after the loop.
   video_memory = (unsigned char*)0xB8000;

   // Add the offset to get the desired cell.
   // THIS IS WHERE THE PROBLEM IS! Setting column = 1 prints in the first cell
   // of video memory instead of the second.
   video_memory += offset;

   // Set character and its attributes.
   *video_memory = 'X';
   video_memory++;
   *video_memory = 0x0F;
}

Это консоль, отображаемая, когда строка = 0 и столбец = 0: Консоль, когда строка и столбец равны 0. X не отображается.

Это консоль, когда строка = 0 и столбец = 1: Консоль, когда строка равна 0, а столбец - 1. . Появится значок X.

Это objdump моего файла kernel.c выше:

kernel.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
#include "../drivers/screen.h"

void main() {
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   // Helper variables.
   int row;
   int column;

   // We need to point at 0xB8000, where video memory resides.
   unsigned char* video_memory = (unsigned char*)0xB8000;
   4:   48 c7 45 f8 00 80 0b    mov    QWORD PTR [rbp-0x8],0xb8000
   b:   00 
   for(row = 0; row < 25; row++) {
   c:   c7 45 ec 00 00 00 00    mov    DWORD PTR [rbp-0x14],0x0
  13:   eb 2f                   jmp    44 <main+0x44>
      for(column = 0; column < 80; column++) {
  15:   c7 45 f0 00 00 00 00    mov    DWORD PTR [rbp-0x10],0x0
  1c:   eb 1c                   jmp    3a <main+0x3a>
         // Clear the screen by printing a space on a black background.
         *video_memory = ' ';
  1e:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  22:   c6 00 20                mov    BYTE PTR [rax],0x20
         video_memory += 1;
  25:   48 83 45 f8 01          add    QWORD PTR [rbp-0x8],0x1
         *video_memory = 0x0F;
  2a:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  2e:   c6 00 0f                mov    BYTE PTR [rax],0xf
         video_memory += 1;
  31:   48 83 45 f8 01          add    QWORD PTR [rbp-0x8],0x1
   int column;

   // We need to point at 0xB8000, where video memory resides.
   unsigned char* video_memory = (unsigned char*)0xB8000;
   for(row = 0; row < 25; row++) {
      for(column = 0; column < 80; column++) {
  36:   83 45 f0 01             add    DWORD PTR [rbp-0x10],0x1
  3a:   83 7d f0 4f             cmp    DWORD PTR [rbp-0x10],0x4f
  3e:   7e de                   jle    1e <main+0x1e>
   int row;
   int column;

   // We need to point at 0xB8000, where video memory resides.
   unsigned char* video_memory = (unsigned char*)0xB8000;
   for(row = 0; row < 25; row++) {
  40:   83 45 ec 01             add    DWORD PTR [rbp-0x14],0x1
  44:   83 7d ec 18             cmp    DWORD PTR [rbp-0x14],0x18
  48:   7e cb                   jle    15 <main+0x15>
      }
   }

   // Test the offset calculation by printing at row 0, column 0 (the upper 
   // left corner of the screen).
   row = 0;
  4a:   c7 45 ec 00 00 00 00    mov    DWORD PTR [rbp-0x14],0x0
   column = 0;
  51:   c7 45 f0 00 00 00 00    mov    DWORD PTR [rbp-0x10],0x0

   // For an 80 by 25 grid. Multiply by 2 to account for the need of two bytes 
   // to display a character with given attributes.
   int offset = ((row * 80) + column) * 2;
  58:   8b 55 ec                mov    edx,DWORD PTR [rbp-0x14]
  5b:   89 d0                   mov    eax,edx
  5d:   c1 e0 02                shl    eax,0x2
  60:   01 d0                   add    eax,edx
  62:   c1 e0 04                shl    eax,0x4
  65:   89 c2                   mov    edx,eax
  67:   8b 45 f0                mov    eax,DWORD PTR [rbp-0x10]
  6a:   01 d0                   add    eax,edx
  6c:   01 c0                   add    eax,eax
  6e:   89 45 f4                mov    DWORD PTR [rbp-0xc],eax

   // Reset memory location after the loop.
   video_memory = (unsigned char*)0xB8000;
  71:   48 c7 45 f8 00 80 0b    mov    QWORD PTR [rbp-0x8],0xb8000
  78:   00 

   // Add the offset to get the desired cell.
   // THIS IS WHERE THE PROBLEM IS! Setting column = 1 prints in the first cell
   // of video memory instead of the second.
   video_memory += offset;
  79:   8b 45 f4                mov    eax,DWORD PTR [rbp-0xc]
  7c:   48 98                   cdqe   
  7e:   48 01 45 f8             add    QWORD PTR [rbp-0x8],rax

   // Set character and its attributes.
   *video_memory = 'X';
  82:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  86:   c6 00 58                mov    BYTE PTR [rax],0x58
   video_memory++;
  89:   48 83 45 f8 01          add    QWORD PTR [rbp-0x8],0x1
   *video_memory = 0x0F;
  8e:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  92:   c6 00 0f                mov    BYTE PTR [rax],0xf
}
  95:   90                      nop
  96:   5d                      pop    rbp
  97:   c3                      ret    

Я проследил и вручную проверил фактические инструкции по сборке для моего расчета смещения, и они кажутся правильными. Я подозреваю, что проблема возникает, когда я пытаюсь добавить свое смещение (введите int) к адресу моей видеопамяти (введите unsigned char*), но опять же я не совсем уверен.

Кроме того, я попытался жестко закодировать определенные числа для смещений. Использование video_memory += 0 вместо video_memory += offset работает по желанию.


person Jakeman582    schedule 22.05.2018    source источник
comment
Не имеет отношения к вашей проблеме, но не изменяйте video_memory и то, куда он указывает. Вместо этого используйте обычную индексацию массива. Как в video_memory[row * 80 + column] = 'X';   -  person Some programmer dude    schedule 22.05.2018
comment
Интересно, что вы это упомянули. Мои первые попытки использовали индексацию массива, но по какой-то причине результаты никогда не были такими, как я ожидал, поэтому я переключился на прямую модификацию. Я намерен вернуться к индексации массива после того, как решу эту конкретную проблему.   -  person Jakeman582    schedule 22.05.2018
comment
Какое значение байта, как указывает отладчик, на самом деле занимает 0xB8000 и 0xB80001 после того, как вы написали «X» и 0x0F на последних шагах этого кода?   -  person selbie    schedule 22.05.2018
comment
Вы пробовали писать другие значения? Другие персонажи? Другие атрибуты? В другие места? На 0xB8000, 0xB8001, 0xB8002, 0xB8003? Идея состоит в том, чтобы установить образец. Возможно, ваш расчет не на единицу, это может быть видеопамять отключена на единицу, потому что первый байт, например. глобальный атрибут.   -  person Yunnosch    schedule 22.05.2018
comment
@selbie После запуска Bochs до тех пор, пока мой экран не «очистится», Bochs сообщает значение 0x0f200f20.   -  person Jakeman582    schedule 23.05.2018
comment
@Yunnosch Да. В первом двойном цикле я заменил байт атрибута на 0xAF, чтобы получить зеленый фон, и заменил пробел символом @. Каждый квадрат окрашен в зеленый цвет с белым символом @. Обычно при запуске каждый квадрат в эмуляторе Bochs имеет черный фон. Это заставляет меня думать, что первый байт используется для отображения символа с атрибутами, а не как какой-то глобальный атрибут, как вы предложили.   -  person Jakeman582    schedule 23.05.2018
comment
@Yunnosch, еще одна вещь: установка video_memory = 0xB8001 после двойного вложенного цикла (все остальное то же самое) приводит к тому, что первая ячейка имеет пурпурный фон с пробелом, а вторая ячейка теперь имеет черный фон с белым * .   -  person Jakeman582    schedule 23.05.2018


Ответы (1)


После дополнительных поисков я нашел статью на Информационный центр ARM, описывающий использование указателей C для доступа к определенным адресам устройств ввода-вывода с отображением памяти.

Объявление моей переменной-указателя video_memory как «изменчивой» гарантирует, что «компилятор всегда выполняет доступ к памяти, а не оптимизирует их ...». Судя по всему, в соответствии с этот ответ на Quora компиляторы могут генерировать инструкции, перезаписывающие данные в буфере записи до того, как эти данные будут сброшены в память, и именно здесь возникает моя проблема.

Таким образом, объявление моей переменной как volatile unsigned char* video_memory = 0xB8000; дает ожидаемые результаты.

person Jakeman582    schedule 23.05.2018
comment
Как это повлияло на сгенерированный компилятором ассемблерный код? Сборка, которую вы разместили в своем исходном вопросе, похоже, не предполагает оптимизации чего-либо. Хотелось бы узнать, что изменилось. - person selbie; 23.05.2018
comment
@selbie, извините, что так долго не отвечал. После использования онлайн-проверки различий оказалось, что различия возникают только там, где я объявляю свою переменную в первую очередь. Все инструкции по сборке одинаковые. - person Jakeman582; 25.05.2018
comment
Не имеет особого смысла, если ключевое слово volatile не изменило сборку. Возможно, ошибка, из-за которой буфер перезаписывался, происходила где-то еще, но ключевое слово volatile все еще защищало. - person selbie; 25.05.2018
comment
Да, это странно. В каждом источнике, который я проверяю, говорится, что volatile используется, чтобы убедиться, что ссылки на память проверяются, а не кэшируются. Основной пример, который я вижу, это то, что аппаратный компонент может изменить значения своих регистров ввода-вывода, что было бы вне моего контроля. - person Jakeman582; 25.05.2018