如何在VESA VBE中绘制像素?kernel.cpp代码报错求助
问题背景
已在bootloader中成功实现VESA图形初始化,代码如下:
mov ax, 0x4F02 ; 设置VBE模式 mov bx, 0x4118 ; VBE模式编号(1024x768x32bpp) int 0x10 ; 调用VBE BIOS cmp ax, 0x004F ; 检查是否执行成功 jne error ; 获取视频模式信息 mov ax, 4f01h mov cx, 105h mov di, modeInfo int 10h
但在kernel.cpp中实现像素绘制时出现各类异常(例如屏幕顶部出现随机色带),而bootloader端绘图功能正常,问题集中在kernel代码逻辑中。
内核代码及关键问题点
以下是kernel.cpp代码,其中**screen_set函数为核心问题点**:
// VIDEO MODE IN 1024x768x32bpp typedef unsigned char uint8_t; typedef unsigned char u8; typedef unsigned short uint16_t; typedef unsigned int u32; typedef u32 size_t; #define SCREEN_WIDTH 1024 #define SCREEN_HEIGHT 768 #define BPP 32 #define SCREEN_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT) #define FPS 30 #define PIT_HERTZ 1193131.666 #define CLOCK_HIT (int)(PIT_HERTZ/FPS) #define KEY_LEFT 0x4B #define KEY_UP 0x48 #define KEY_RIGHT 0x4D #define KEY_DOWN 0x50 static u32 *BUFFER = (u32 *) 0xA0000; // 双缓冲 u32 _sbuffers[2][SCREEN_SIZE]; u32 _sback = 0; #define CURRENT (_sbuffers[_sback]) #define SWAP() (_sback = 1 - _sback) #define screen_buffer() (_sbuffers[_sback]) static inline void outb(uint16_t port, uint8_t val) { asm volatile ( "outb %0, %1" : : "a"(val), "Nd"(port) ); } // ---------- 存在问题的函数 ---------- void screen_set(u32 color,int x,int y) { _sbuffers[_sback][y * SCREEN_WIDTH + (x * (BPP/8))]=color; } // ----------------------------------- static inline uint8_t inb(uint16_t port) { uint8_t ret; asm volatile ( "inb %1, %0" : "=a"(ret) : "Nd"(port) ); return ret; } const unsigned char font[128-32][8] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (空格) /* 省略其余字体数据 */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F }; static inline void *memcpy(void *dst, const void *src, size_t n) { u8 *d = (u8*)dst; const u8 *s = (const u8*)src; while (n-- > 0) { *d++ = *s++; } return d; } void screen_swap() { memcpy(BUFFER, CURRENT, SCREEN_SIZE); SWAP(); } unsigned read_pit(void) { unsigned count = 0; // al = channel in bits 6 and 7, remaining bits clear outb(0x43,0b0000000); count = inb(0x40); // 低字节 count |= inb(0x40)<<8; // 高字节 return count; } void draw_char(char c, int x, int y, u32 color) { const unsigned char *glyph = font[(int)c-32]; for(int cy=0;cy<8;cy++){ for(int cx=0;cx<8;cx++){ if(((int)glyph[cy]&(1<<cx))==(1<<cx)){ screen_set(color,x+cx,y+cy); } } } } void draw_string(const char * s, int x, int y, u32 color) { int i = 0; while(s[i] != false) { draw_char(s[i],x+(i*8),y,color); i++; } } void draw_rect(int pos_x, int pos_y, int w, int h, u32 color) { for(int y = 0; y<h; y++) { for(int x = 0; x<w; x++) { screen_set(color,x+pos_x,y+pos_y); } } } static void render(int c0, int c1) { //draw_rect(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,0x09); //draw_string("Hello, reader. This is written text.", 100, 180, 16777215); //draw_string("If this is displayed, my code works.", 100+c0, 100+c1, 16777215); } extern "C" void main(){ int clock = 0; int incC1 = 0; int incC0 = 0; while(true) { uint16_t scancode = (uint16_t) inb(0x60); clock++; if(read_pit()!= 0 && clock == CLOCK_HIT) { if(scancode == KEY_LEFT) { incC0--; }else if(scancode == KEY_RIGHT) { incC0++; } if(scancode == KEY_DOWN) { incC1++; }else if(scancode == KEY_UP) { incC1--; } clock = 0; render(incC0,incC1); screen_swap(); } } return; }
错误原因及修正方案
1. 像素索引计算错误
screen_set函数中,_sbuffers是u32类型数组,每个元素对应一个32位像素(刚好4字节,和BPP匹配),因此像素索引应为y * SCREEN_WIDTH + x,不需要额外乘以BPP/8——原代码的计算会导致索引越界,出现随机色带或乱码。
修正后的函数:
void screen_set(u32 color,int x,int y) { // 增加边界检查,避免越界访问 if(x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT) { _sbuffers[_sback][y * SCREEN_WIDTH + x] = color; } }
2. 显存基址错误
VESA线性帧缓冲的物理地址不是固定的0xA0000,需要从bootloader读取的modeInfo结构体中的PhysBasePtr字段获取,并传递给内核。例如在bootloader中将该地址存入指定内存位置,内核再读取使用:
// 假设bootloader将PhysBasePtr存入0x10000地址 static u32 *BUFFER = (u32 *)*(uint32_t*)0x10000;
3. 双缓冲拷贝长度错误
screen_swap中的memcpy仅传递了像素数作为长度,但每个像素是4字节,实际需要拷贝的字节数应为SCREEN_SIZE * sizeof(u32),否则只会复制四分之一的缓冲数据,导致屏幕内容异常。
修正后的函数:
void screen_swap() { memcpy(BUFFER, CURRENT, SCREEN_SIZE * sizeof(u32)); SWAP(); }
4. PIT读取命令错误
read_pit中的命令字0b0000000不正确,正确的命令应为0b00000010(表示读取通道0,先读低字节再读高字节),否则读取的计数值无效,导致帧率控制逻辑混乱。
修正后的函数:
unsigned read_pit(void) { unsigned count = 0; // 设置读取通道0,先低字节后高字节 outb(0x43, 0b00000010); count = inb(0x40); // 低字节 count |= inb(0x40) << 8; // 高字节 return count; }
5. 键盘扫描码处理错误
inb(0x60)读取的是8位扫描码,转成uint16_t无意义;同时未处理按键释放码(扫描码最高位为1),会导致按键重复触发异常。
修正后的主循环键盘处理逻辑:
extern "C" void main(){ int clock = 0; int incC1 = 0; int incC0 = 0; while(true) { uint8_t scancode = inb(0x60); // 仅处理按键按下事件(释放码最高位为1,取低7位判断) if((scancode & 0x80) == 0) { switch(scancode) { case KEY_LEFT: incC0--; break; case KEY_RIGHT: incC0++; break; case KEY_UP: incC1--; break; case KEY_DOWN: incC1++; break; } } clock++; if(read_pit() != 0 && clock == CLOCK_HIT) { clock = 0; render(incC0,incC1); screen_swap(); } } return; }
内容的提问来源于stack exchange,提问作者user17400329

