You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在VESA VBE中绘制像素?kernel.cpp代码报错求助

VESA模式下内核绘图异常问题排查

问题背景

已在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 18:49:14