自制OS内核中无法将char*字符串输出至VGA内存的问题
我用C++编写自制OS内核,正在开发打印功能。单个字符的print_char函数可以正常将字符输出到VGA内存(0xB8000),但调用print_str(const char* str)函数时,无论输入的字符串内容和长度如何,屏幕上都会固定输出三个乱码符号:
- 无法描述的随机符号
- 平方根符号
- 笑脸符号
相关代码
print_char与print_str函数
void print_char(const char character) { Char* buffer = reinterpret_cast<Char*>(VGA_ADDRESS); // 处理换行符 if(character == '\n') { print_newline(); return; } if(col >= NUM_COLS) { print_newline(); } buffer[col + NUM_COLS * row] = {static_cast<uint8_t>(character), color}; col++; // 增加当前行的字符计数 } // 打印字符串到屏幕 void print_str(const char* str) { // 按字符串长度调用print_char for(size_t i = 0; str[i] != '\0'; i++) { print_char(str[i]); } }
链接脚本
OUTPUT_FORMAT("binary") OUTPUT_ARCH(i386) ENTRY(_start) SECTIONS { /* Bootloader段在0x7C00(BIOS将引导加载程序加载到此处) */ . = 0x7C00; .boot : { *(.boot) /* 包含所有.boot段 */ *(.text) /* 包含引导加载程序代码的所有.text段 */ } /* 对齐到1 MiB(内核起始位置) */ . = 0x100000; /* 内核段 */ .text : ALIGN(4096) { __kernel_start = .; /* 这是内核的起始地址 */ *(.text) } .rodata : ALIGN(4096) { *(.rodata) } .data : ALIGN(4096) { *(.data) } .bss : ALIGN(4096) { *(COMMON) *(.bss) } __kernel_end = .; /* 这是内核的结束地址 */ }
问题排查与解决思路
1. 全局光标变量未初始化
print_char依赖全局的col(列)和row(行)变量定位VGA缓冲区的写入位置,但如果这两个变量没有被显式初始化,内核启动后它们的值是内存中的随机垃圾数据。第一次调用print_str时,col可能是一个超出范围的数值,触发换行逻辑,但row的随机值会导致写入位置完全错误,从而输出乱码。
解决方法:在全局作用域初始化col = 0、row = 0,或者在内核启动入口函数中显式设置光标初始位置。
2. VGA字符结构体对齐错误
VGA缓冲区的每个字符占2字节(1字节ASCII码 + 1字节颜色属性)。如果你的Char结构体因为C++的默认对齐规则被填充为4字节,那么写入缓冲区时会跳过字节,导致字符和属性错位,出现乱码。
解决方法:给Char结构体添加强制对齐属性,比如:
struct __attribute__((packed)) Char { uint8_t c; uint8_t color; };
或者使用编译指令强制1字节对齐:
#pragma pack(push, 1) struct Char { uint8_t c; uint8_t color; }; #pragma pack(pop)
3. 字符串缺少空终止符
如果传入print_str的字符串没有以\0结尾,循环会持续读取内存中的垃圾数据,直到碰到\0,导致输出多余的乱码。
解决方法:用双引号定义字符串(编译器会自动添加\0);如果是手动构造的字符串,要手动添加\0终止符。
4. 内核加载地址不匹配
链接脚本将内核放在0x100000(1MiB)地址,但如果bootloader没有将内核正确加载到这个地址,全局变量(比如col/row)的实际内存位置会和预期不符,导致读取错误的值。
解决方法:检查bootloader的内核加载代码,确保内核被加载到0x100000地址;同时确认链接脚本中__kernel_start和__kernel_end的定义正确,bootloader加载时覆盖了正确的内存范围。
内容的提问来源于stack exchange,提问作者Ioane Baidoshvili

