เหตุใดการคำนวณออฟเซ็ตหน่วยความจำวิดีโอของฉันจึงปิดไปทีละรายการ

ฉันได้อ่านและติดตามบทช่วยสอนเกี่ยวกับการเขียนระบบปฏิบัติการตั้งแต่เริ่มต้นโดย Nick Blundell ซึ่งสามารถพบได้ที่ 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) ไปยังที่อยู่หน่วยความจำวิดีโอของฉัน (พิมพ์อักขระถ่าน*) แต่ฉันก็ไม่แน่ใจอีกครั้ง

นอกจากนี้ ฉันยังลองฮาร์ดโค้ดเฉพาะตัวเลขสำหรับการชดเชยด้วย การใช้ 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 เพื่อเข้าถึงที่อยู่เฉพาะสำหรับอุปกรณ์ I/O ที่แมปหน่วยความจำ

การประกาศตัวแปร video_memory pointer ของฉันเป็น 'ระเหย' ช่วยให้มั่นใจได้ว่า "คอมไพเลอร์จะดำเนินการเข้าถึงหน่วยความจำเสมอ แทนที่จะปรับให้เหมาะสมที่สุด..." เห็นได้ชัดว่าตาม คำตอบนี้ บน 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
ไม่สมเหตุสมผลเลยถ้าคำสำคัญระเหยไม่ได้เปลี่ยนชุดประกอบ บางทีข้อผิดพลาดที่บัฟเฟอร์ถูกเขียนทับอาจเกิดขึ้นที่อื่น แต่คีย์เวิร์ดที่มีความผันผวนยังคงได้รับการปกป้อง - person selbie; 25.05.2018
comment
ใช่มันแปลก ทุกแหล่งที่ฉันตรวจสอบระบุว่ามีการใช้ volatile เพื่อให้แน่ใจว่ามีการตรวจสอบการอ้างอิงหน่วยความจำแทนที่จะแคชไว้ ตัวอย่างหลักที่ฉันเห็นคือส่วนประกอบฮาร์ดแวร์สามารถเปลี่ยนค่าของรีจิสเตอร์ IO ของมันได้ ซึ่งจะอยู่นอกเหนือการควบคุมของฉัน - person Jakeman582; 25.05.2018