如何修复双缓冲无法渲染至屏幕的问题?
双缓冲引入后屏幕无输出的排查与修复
问题背景
我正在开发一款小型操作系统,之前为解决屏幕撕裂问题引入了双缓冲机制,但修改渲染逻辑后屏幕完全无法输出内容。推测可能的原因:
- 为让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
相关产品推荐
相关产品推荐

