【问题标题】:Why is my video memory offset calculation off by one?为什么我的视频内存偏移量计算减一?
【发布时间】:2018-10-31 17:50:35
【问题描述】:

我一直在阅读并遵循 Nick Blundell 编写的关于从头开始编写操作系统的教程,该教程可以在 https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf 找到

我已经成功编写了一个可以调用 C 代码的引导加载程序,因此我开始用 C 编写我的内核。我现在正在尝试编写可以在屏幕上打印字符和字符串的函数。当我开始执行 C 代码时,我处于 ​​32 位保护模式,所以我正在尝试从视频内存地址 0xb8000 正确计算内存偏移量。

当我尝试使用计算出的偏移量访问视频内存的特定区域时,我的问题就开始了。由于文本区域是 25 行 x 80 列,我使用公式 ((row * 80) + column) * 2 因为我必须有一个字符字节和一个属性字节。当我设置 row = 0 和 column = 0 时,我尝试打印的 X 不存在。设置row = 0,column = 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;
}

这是当 row = 0 和 column = 0 时显示的控制台: The console when row and column are 0. No X appears.

这是行 = 0 列 = 1 时的控制台: The console when row is 0 and column is 1. An X appears.

这是我上面的 kernel.c 文件的 objdump:

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 可以根据需要工作。

【问题讨论】:

  • 与您的问题无关,但不要修改video_memory 及其指向的位置。请改用普通数组索引。如video_memory[row * 80 + column] = 'X';
  • 你提到这一点很有趣。我的第一次尝试使用数组索引,但无论出于何种原因,结果都不是我所期望的,因此切换到直接修改。我确实打算在解决这个特殊问题后回到数组​​索引。
  • 在这段代码的最后一步写一个'X'和0x0F后,调试器指示的字节值实际占用了0xB8000和0xB80001?
  • 你试过写其他值吗?其他角色?其他属性?去其他地方?到 0xB8000、0xB8001、0xB8002、0xB8003?这个想法是建立一个模式。也许您的计算没有关闭,可能是视频内存关闭了一个,因为第一个字节是例如一个全局属性。
  • @selbie 在运行 Bochs 直到我的屏幕被“清除”后,Bochs 报告的值为 0x0f200f20。

标签: c operating-system off-by-one video-memory


【解决方案1】:

经过更多搜索,我在ARM Information Center 上找到了一篇文章,描述了使用 C 指针访问内存映射 I/O 设备的特定地址。

将我的 video_memory 指针变量声明为 'volatile' 可确保“编译器始终执行内存访问,而不是优化它们......”。显然,根据this answer on Quora,编译器可以生成指令,在数据刷新到内存之前覆盖写入缓冲区中的数据,这就是我的问题所在。

因此,将我的变量声明为 volatile unsigned char* video_memory = 0xB8000; 会产生预期的结果。

【讨论】:

  • 这对编译器生成的汇编代码有何影响?您在原始问题中发布的程序集似乎并不意味着有任何优化。很想知道发生了什么变化。
  • @selbie,抱歉花了这么长时间才回复。使用在线差异检查器后,似乎唯一的差异发生在我首先声明变量的地方。所有的组装说明都是一样的。
  • 如果 volatile 关键字没有改变程序集就没有多大意义。也许缓冲区被覆盖的错误发生在其他地方,但 volatile 关键字仍在保护。
  • 是的,这很奇怪。我检查的每个来源都声明 volatile 用于确保检查内存引用而不是缓存。我看到的主要示例是硬件组件可以更改其 IO 寄存器的值,这超出了我的控制范围。
猜你喜欢
  • 1970-01-01
  • 2016-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多