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

如何修复双缓冲无法渲染至屏幕的问题?

双缓冲引入后屏幕无输出的排查与修复

问题背景

我正在开发一款小型操作系统,之前为解决屏幕撕裂问题引入了双缓冲机制,但修改渲染逻辑后屏幕完全无法输出内容。推测可能的原因:

  • 为让memcpy的void类型dst/src与u8类型d/s兼容做了不规范处理
  • 参考外部双缓冲实现时遗漏了适配当前系统的改动
  • 存在拼写错误(可能性低)

以下是我的代码:

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 320
#define SCREEN_HEIGHT 200
#define SCREEN_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT)
#define FPS 30
#define PIT_HERTZ 1193131.666
#define CLOCK_HIT (int)(PIT_HERTZ/FPS)
static uint8_t *BUFFER = (uint8_t *) 0xA0000;

// double buffers
uint8_t _sbuffers[2][SCREEN_SIZE];
uint8_t _sback = 0;

#define CURRENT (_sbuffers[_sback])
#define SWAP() (_sback = 1 - _sback)

#define screen_buffer() (_sbuffers[_sback])

#define screen_set(_p, _x, _y)\
    (_sbuffers[_sback][((_y) * SCREEN_WIDTH + (_x))]=(_p))

const unsigned char font[128-32][8] = {
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0020 (space)\
*deleted to help with length and readability of code...*\
    { 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 = (u8*)(&src);

    while (n-- > 0) {
        *d++ = *s++;
    }

    return d;
}

void screen_swap() {
    memcpy(BUFFER, &CURRENT, SCREEN_SIZE);
    SWAP();
}

static inline void outb(uint16_t port, uint8_t val)
{
    asm volatile ( "outb %0, %1" : : "a"(val), "Nd"(port) );
}

static inline uint8_t inb(uint16_t port)
{
    uint8_t ret;
    asm volatile ( "inb %1, %0"
                   : "=a"(ret)
                   : "Nd"(port) );
    return ret;
}

unsigned read_pit(void) {
    unsigned count = 0;
 
    // al = channel in bits 6 and 7, remaining bits clear
    outb(0x43,0b0000000);
 
    count = inb(0x40);          // Low byte
    count |= inb(0x40)<<8;      // High byte
 
    return count;
}
 
void draw_char(char c, int x, int y, unsigned char 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, unsigned char 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, unsigned char 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() {
    //draw_rect(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,0);
    draw_string("Hello, reader. This is written text.", 10, 18, 15);
    draw_string("If this is displayed, my code works.", 10, 10, 15);
}

extern "C" void main(){
    int clock = 0;
    while(true) {
        clock++;
        if(read_pit()!= 0 && clock == CLOCK_HIT) {
            clock = 0;
            render();
            screen_swap();
        }
    }

    return;
}

核心问题排查与修复

1. memcpy函数的致命错误

你的memcpy实现完全错误:

static inline void *memcpy(void *dst, const void *src, size_t n) {
    u8 *d = (u8*)(&dst);  // 错误:取了指针变量自身的地址,而非指向的内存区域
    const u8 *s = (u8*)(&src); // 同样错误

    while (n-- > 0) {
        *d++ = *s++;
    }

    return d;
}

正确实现应直接将void*转换为u8*,无需取地址:

static inline void *memcpy(void *dst, const void *src, size_t n) {
    u8 *d = static_cast<u8*>(dst);
    const u8 *s = static_cast<const u8*>(src);

    while (n-- > 0) {
        *d++ = *s++;
    }

    return dst; // 返回原始目标地址,符合标准memcpy语义
}

原代码把数据拷贝到了栈上的指针变量内存区域,而非显存地址0xA0000,这是屏幕无输出的核心原因。

2. screen_swap函数的参数错误

调用memcpy时,&CURRENT是多余的:

memcpy(BUFFER, &CURRENT, SCREEN_SIZE);

CURRENT本身是数组类型,会隐式转换为指向数组首元素的指针,&CURRENT得到的是指向整个数组的指针,虽地址一致但语义错误,修改为:

memcpy(BUFFER, CURRENT, SCREEN_SIZE);

3. PIT读取逻辑错误

read_pit中控制字设置有误:

outb(0x43,0b0000000);

正确的PIT通道0锁存命令是0x02(二进制0b00000010),原命令未正确锁存计数器,导致读取的计数值不可靠,修改为:

outb(0x43, 0x02); // 锁存通道0计数值,以便读取

4. 主循环触发条件逻辑混乱

原主循环中clock递增无时间基准,与PIT计数值的关联逻辑错误,建议改为基于PIT计数完成的触发逻辑:

extern "C" void main(){
    // 初始化PIT为模式0(终端计数),加载初始计数值
    outb(0x43, 0x30); // 通道0,模式0,二进制计数
    outb(0x40, CLOCK_HIT & 0xFF); // 低字节
    outb(0x40, (CLOCK_HIT >> 8) & 0xFF); // 高字节

    while(true) {
        // 等待PIT计数完成(输出引脚变高)
        while((inb(0x61) & 0x20) == 0);
        // 清除计数完成标志
        outb(0x61, inb(0x61) | 0x20);
        
        render();
        screen_swap();
        
        // 重新加载计数值
        outb(0x43, 0x30);
        outb(0x40, CLOCK_HIT & 0xFF);
        outb(0x40, (CLOCK_HIT >> 8) & 0xFF);
    }
}

这样可准确按照30FPS频率触发渲染与缓冲交换。

5. 其他潜在优化点

  • draw_string中的循环条件while(s[i] != false)建议改为while(s[i] != '\0'),语义更准确(字符串结束符是'\0')
  • 确认显存地址0xA0000与当前视频模式匹配(320x200 8位色模式下该地址正确)

内容的提问来源于stack exchange,提问作者user17400329

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 11:24:23