Mengapa penghitungan offset memori video saya meleset satu?

Saya telah membaca dan mengikuti tutorial menulis sistem operasi dari awal oleh Nick Blundell yang dapat ditemukan di https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf

Saya telah berhasil menulis boot loader yang dapat memanggil kode C, jadi saya mulai menulis kernel saya dalam C. Sekarang saya mencoba menulis fungsi yang dapat mencetak karakter dan string di layar. Pada saat saya mulai mengeksekusi kode C, saya berada dalam mode terlindungi 32-bit, jadi saya mencoba menghitung offset memori dengan benar dari alamat memori video 0xb8000.

Masalah saya dimulai ketika saya mencoba mengakses wilayah tertentu dari memori video menggunakan offset yang dihitung. Karena area teksnya 25 baris kali 80 kolom, saya menggunakan rumus ((baris * 80) + kolom) * 2 karena saya harus memiliki byte karakter dan byte atribut. Saat saya menyetel baris = 0 dan kolom = 0, X yang saya coba cetak tidak ada. Setting baris = 0 dan kolom = 1, muncul tanda X di pojok kiri atas.

Dimulai dengan char* video_memory = 0xb8000 dan berulang kali mengeluarkan video_memory++ memungkinkan saya mengunjungi setiap byte dengan benar dan mencetak spasi dengan latar belakang hitam.

Ini adalah kode utama saya:

#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;
}

Ini adalah konsol yang ditampilkan ketika baris = 0 dan kolom = 0: Konsol ketika baris dan kolom adalah 0. Tidak ada X yang muncul.

Ini adalah konsol ketika baris = 0 dan kolom = 1: Konsol ketika baris adalah 0 dan kolom adalah 1 . Tanda X muncul.

Ini adalah objek dari file kernel.c saya di atas:

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    

Saya telah menelusuri dan memeriksa langsung instruksi perakitan sebenarnya untuk perhitungan offset saya, dan tampaknya instruksi tersebut benar. Saya menduga masalah ini muncul ketika saya mencoba menambahkan offset saya (ketik int) ke alamat memori video saya (ketik unsigned char*), tapi sekali lagi saya tidak sepenuhnya yakin.

Juga, saya mencoba mengkodekan angka-angka tertentu untuk offset. Menggunakan video_memory += 0 alih-alih video_memory += offset berfungsi sesuai keinginan.


person Jakeman582    schedule 22.05.2018    source sumber
comment
Tidak terkait dengan masalah Anda, tetapi jangan ubah video_memory dan tujuannya. Gunakan pengindeksan array normal sebagai gantinya. Seperti di video_memory[row * 80 + column] = 'X';   -  person Some programmer dude    schedule 22.05.2018
comment
Menarik sekali Anda menyebutkan hal itu. Upaya pertama saya menggunakan pengindeksan array, tetapi karena alasan apa pun, hasilnya tidak seperti yang saya harapkan, maka beralih ke modifikasi langsung. Saya bermaksud untuk kembali ke pengindeksan array setelah saya menyelesaikan masalah khusus ini.   -  person Jakeman582    schedule 22.05.2018
comment
Berapa nilai byte yang ditunjukkan oleh debugger yang sebenarnya menempati 0xB8000 dan 0xB80001 setelah Anda menulis 'X' dan 0x0F pada langkah terakhir kode ini?   -  person selbie    schedule 22.05.2018
comment
Sudahkah Anda mencoba menulis nilai lain? Karakter lain? Atribut lainnya? Ke lokasi lain? Ke 0xB8000, 0xB8001, 0xB8002, 0xB8003? Idenya adalah untuk membangun sebuah pola. Mungkin perhitungan anda tidak meleset satu, bisa jadi memori videonya meleset satu karena byte pertama misal. atribut global.   -  person Yunnosch    schedule 22.05.2018
comment
@selbie Setelah menjalankan Bochs hingga layar saya 'dibersihkan', Bochs melaporkan nilai 0x0f200f20.   -  person Jakeman582    schedule 23.05.2018
comment
@Yunnosch Ya. Pada double loop pertama, saya mengganti atribut byte dengan 0xAF untuk memberikan latar belakang hijau, dan saya mengganti spasi dengan simbol @. Setiap kotak diberi warna hijau dengan simbol @ berwarna putih. Biasanya saat startup, setiap kotak di emulator Bochs memiliki latar belakang hitam. Ini membuat saya berpikir bahwa byte pertama digunakan untuk menampilkan karakter dengan atribut, bukan sebagai atribut global seperti yang Anda sarankan.   -  person Jakeman582    schedule 23.05.2018
comment
@Yunnosch, satu hal lagi, Mengatur video_memory = 0xB8001 setelah loop bersarang ganda (semuanya sama) menghasilkan sel pertama memiliki latar belakang magenta dengan spasi, dan sel kedua sekarang memiliki latar belakang hitam dengan putih * .   -  person Jakeman582    schedule 23.05.2018


Jawaban (1)


Setelah pencarian lebih lanjut, saya menemukan artikel di Pusat Informasi ARM menjelaskan penggunaan pointer C untuk mengakses alamat spesifik untuk perangkat I/O yang dipetakan memori.

Mendeklarasikan variabel penunjuk video_memory saya sebagai 'volatile' memastikan bahwa "kompiler selalu melakukan akses memori, daripada mengoptimalkannya...". Rupanya, menurut jawaban ini di Quora, kompiler dapat menghasilkan instruksi yang menimpa data dalam buffer tulis sebelum data tersebut dimasukkan ke memori, di situlah masalah saya terjadi.

Jadi mendeklarasikan variabel saya sebagai volatile unsigned char* video_memory = 0xB8000; menghasilkan hasil yang diharapkan.

person Jakeman582    schedule 23.05.2018
comment
Bagaimana hal itu memengaruhi kode Majelis yang dihasilkan oleh kompiler? Majelis yang Anda posting di pertanyaan awal Anda sepertinya tidak menyiratkan apa pun yang dioptimalkan. Ingin tahu apa yang berubah. - person selbie; 23.05.2018
comment
@selbie, maaf lama sekali membalasnya. Setelah menggunakan pemeriksa perbedaan online, tampaknya satu-satunya perbedaan terjadi saat saya mendeklarasikan variabel saya pertama kali. Semua instruksi perakitannya sama. - person Jakeman582; 25.05.2018
comment
Tidak masuk akal jika kata kunci volatil tidak mengubah Majelis. Mungkin bug yang menyebabkan buffer ditimpa terjadi di tempat lain, tetapi kata kunci yang mudah menguap masih melindungi. - person selbie; 25.05.2018
comment
Ya, itu aneh. Setiap sumber yang saya periksa menyatakan bahwa volatil digunakan untuk memastikan referensi memori diperiksa, bukan di-cache. Contoh utama yang saya lihat adalah komponen perangkat keras dapat mengubah nilai register IO-nya, yang berada di luar kendali saya. - person Jakeman582; 25.05.2018