我正在将操作系统开发为个人爱好,因此我可以了解软件工程和计算机体系结构。
当文本超过底部或VGA_HEIGHT时,我试图使VGA端子“滚动”。我正在使用OSDev Wiki中的代码以及自己的代码。
我的目标是复制每一行,然后将其写入到其上方的行中。
这是我正在使用的代码:
void terminal_putentryat(unsigned char c, uint8_t color, size_t x, size_t y) {
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = vga_entry(c, color);
}
void terminal_putchar(char c) {
unsigned char uc = c;
switch(c) {
case NEWLINE:
terminal_row++;
terminal_column = 0;
terminal_putentryat(' ', terminal_color, terminal_column, terminal_row);
update_cursor(terminal_column + 1, terminal_row);
break;
case '\t':
/* TODO: Implement tab */
terminal_column += 4;
break;
default:
terminal_putentryat(uc, terminal_color, terminal_column, terminal_row);
update_cursor(terminal_column + 1, terminal_row);
if (++terminal_column == VGA_WIDTH) {
terminal_column = 0;
if (++terminal_row == VGA_HEIGHT)
terminal_row = 0;
}
}
if(terminal_row >= VGA_HEIGHT) {
terminal_print_error();
terminal_buffer[(15 * VGA_WIDTH) + 15] = terminal_buffer[(0 * VGA_WIDTH) + 4];
size_t i, j;
for(i = 0; i < VGA_WIDTH-1; i++) {
for(j = VGA_HEIGHT-2; j > 0; j--)
terminal_buffer[(j * VGA_WIDTH) + i] = terminal_buffer[((j+1) * VGA_WIDTH) + i];
}
}
}
但是此功能仅部分起作用。特别是本节:
if(terminal_row >= VGA_HEIGHT) {
terminal_print_error();
terminal_buffer[(15 * VGA_WIDTH) + 15] = terminal_buffer[(0 * VGA_WIDTH) + 4];
size_t i, j;
for(i = 0; i < VGA_WIDTH-1; i++) {
for(j = VGA_HEIGHT-2; j > 0; j--)
terminal_buffer[(j * VGA_WIDTH) + i] = terminal_buffer[((j+1) * VGA_WIDTH) + i];
}
}
它仅部分复制数据。例如,当我使用'printf()'向终端写入数据时,如果字符串比正在滚动的数据长,则它将不会滚动。
/*
* This is the screen driver. It contains functions which print
* characters and colors to the screen
* using the VGA controller.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <kernel/tty.h>
#include "vga.h"
#define REG_SCREEN_CTRL 0x3D4
#define REG_SCREEN_DATA 0x3D5
#define NEWLINE 0x0A
#define TAB 0x09
static const size_t VGA_WIDTH = 80;
static const size_t VGA_HEIGHT = 25;
static uint16_t *const VGA_MEMORY = (uint16_t *)0xC03FF000;
static size_t terminal_row;
static size_t terminal_column;
static uint8_t terminal_color;
static uint16_t *terminal_buffer;
int get_offset(int col, int row) {
return 2 * (row * VGA_WIDTH + col);
}
int get_offset_row(int offset) {
return offset / (2 * VGA_WIDTH);
}
int get_offset_col(int offset) {
return (offset - (get_offset_row(offset) * 2 * VGA_WIDTH)) / 2;
}
static void scroll() {
if(terminal_row >= VGA_HEIGHT) {
}
}
void terminal_print_error(void) {
if(terminal_row >= VGA_HEIGHT) {
terminal_row = 0;
/* print white/red E to bottom right corner of screen */
terminal_putentryat('E', vga_entry_color(VGA_COLOR_RED, VGA_COLOR_WHITE),
VGA_WIDTH - 1, VGA_HEIGHT - 1);
}
}
void terminal_initialize(void) {
terminal_row = 0;
terminal_column = 0;
terminal_color = vga_entry_color(VGA_COLOR_BLACK, VGA_COLOR_CYAN);
terminal_buffer = VGA_MEMORY;
for (size_t y = 0; y < VGA_HEIGHT; y++) {
for (size_t x = 0; x < VGA_WIDTH; x++) {
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = vga_entry(' ', terminal_color);
}
}
}
void terminal_setcolor(uint8_t color) {
terminal_color = color;
}
void terminal_putentryat(unsigned char c, uint8_t color, size_t x, size_t y) {
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = vga_entry(c, color);
}
void terminal_putchar(char c) {
unsigned char uc = c;
switch(c) {
case NEWLINE:
terminal_row++;
terminal_column = 0;
terminal_putentryat(' ', terminal_color, terminal_column, terminal_row);
update_cursor(terminal_column + 1, terminal_row);
break;
case '\t':
/* TODO: Implement tab */
terminal_column += 4;
break;
default:
terminal_putentryat(uc, terminal_color, terminal_column, terminal_row);
update_cursor(terminal_column + 1, terminal_row);
if (++terminal_column == VGA_WIDTH) {
terminal_column = 0;
if (++terminal_row == VGA_HEIGHT)
terminal_row = 0;
}
}
if(terminal_row >= VGA_HEIGHT) {
terminal_print_error();
terminal_buffer[(15 * VGA_WIDTH) + 15] = terminal_buffer[(0 * VGA_WIDTH) + 4];
size_t i, j;
for(i = 0; i < VGA_WIDTH-1; i++) {
for(j = VGA_HEIGHT-2; j > 0; j--)
terminal_buffer[(j * VGA_WIDTH) + i] = terminal_buffer[((j+1) * VGA_WIDTH) + i];
}
}
}
void terminal_write(const char *data, size_t size) {
for (size_t i = 0; i < size; i++)
terminal_putchar(data[i]);
}
void terminal_writestring(const char *data) {
terminal_write(data, strlen(data));
}
/* inb */
unsigned char port_byte_in(unsigned short port) {
unsigned char result;
asm ("in %%dx, %%al" : "=a" (result) : "d" (port));
return result;
}
void port_byte_out(unsigned short port, unsigned char data) {
asm ("out %%al, %%dx" : : "a" (data), "d" (port));
}
int get_cursor_offset() {
/* Use the VGA ports to get the current cursor position
* 1. Ask for high byte of the cursor offset (data 14)
* 2. Ask for low byte (data 15)
*/
port_byte_out(REG_SCREEN_CTRL, 14);
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
port_byte_out(REG_SCREEN_CTRL, 15);
offset += port_byte_in(REG_SCREEN_DATA);
return offset * 2; /* Position * size of character cell */
}
void set_cursor_offset(int offset) {
/* Similar to get_cursor_offset, but instead of reading we write data */
offset /= 2;
port_byte_out(REG_SCREEN_CTRL, 14);
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
port_byte_out(REG_SCREEN_CTRL, 15);
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
}
void update_cursor(int x, int y) {
uint16_t pos = y * VGA_WIDTH + x;
port_byte_out(REG_SCREEN_CTRL, 15);
port_byte_out(REG_SCREEN_DATA, (uint8_t)(pos & 0xFF));
port_byte_out(REG_SCREEN_CTRL, 14);
port_byte_out(REG_SCREEN_DATA, (uint8_t)(pos >> 8) & 0xFF);
}
/*
void enable_cursor(uint8_t cursor_start, uint8_t cursor_end) {
outb(0x3D4, 0x0A);
outb(0x3D, (inb(0x3D5) & 0xC0) | cursor_start);
outb(0x3D4, 0x0B);
outb(0x3D5, (inb(0x3D5) & 0xE0) | cursor_end);
}
*/
答案 0 :(得分:4)
我相信循环滚动的工作原理主要是正确的,但是在此之前,很多代码会导致您遇到一些错误。
switch语句中的默认情况是,当光标宽度超过屏幕的右边缘时,将增加光标行。然后,if (++terminal_row == VGA_HEIGHT)
会将光标行重置为0(如果行增量超过了屏幕的底部边缘)。这样可以防止滚动代码运行。您应该删除if (++terminal_row == VGA_HEIGHT) terminal_row = 0;
并仅替换为terminal_row++;
,因为紧随开关之后的逻辑将处理终端行变量。
我建议将修改terminal_row
和terminal_column
的逻辑与验证,重置和滚动这些变量的逻辑分开。例如,如果您对'\ t'字符的处理(如果放在一行的最后3个字符之内),将使字符溢出到下一行,而不会将terminal_row
和terminal_column
var更新为应保留的位置
terminal_putentryat
。实际上,您不必对任何换行符进行terminal_putentryat
,因为显然没有字符改变,只有光标位置改变了。\t
的处理以调用terminal_write(' ');
,而不是直接修改column变量。这简化了实际更新终端的逻辑。上面的第二段详细介绍了此更改可以解决的一些问题。update_cursor()
仅应在terminal_putchar()
的末尾被调用一次,因为您输入的每个字符都应更新游标。如果您希望terminal_putchar()
处理宽度为0的字符,则此方法可能会更改,但这对我来说是违反直觉的,因为此功能专门用于处理显示的字符。函数底部处理terminal_row >= VGA_HEIGHT
的逻辑永远不会将terminal_row重置为有效值。它确实调用了terminal_print_error()
,但是当您要将行保留在底部时,此函数会将行重置为0。
void terminal_putchar(char c) {
unsigned char uc = c;
// Handle character output and terminal_row/column modification
switch(c) {
case NEWLINE:
terminal_row++;
terminal_column = 0;
break;
case '\t':
terminal_write(' ');
break;
default:
terminal_putentryat(uc, terminal_color, terminal_column, terminal_row);
terminal_column++;
}
// Handle validation on terminal_column before terminal_row, since the logic in terminal_column can update terminal_row
if(terminal_column >= VGA_WIDTH) {
terminal_column = 0;
terminal_row++;
}
// Handle validating terminal_row, and scrolling the screen upwards if necessary.
if(terminal_row >= VGA_HEIGHT) {
// You shouldn't need terminal_print_error() since you are handling the case where terminal_row >= VGA_HEIGHT
// terminal_print_error();
// What does this line do? Appears to set the 16th character of the 16th row to the same value as the 5th character of the 1st row.
// terminal_buffer[(15 * VGA_WIDTH) + 15] = terminal_buffer[(0 * VGA_WIDTH) + 4];
size_t i, j;
for(i = 0; i < VGA_WIDTH-1; i++) {
for(j = VGA_HEIGHT-2; j > 0; j--) {
terminal_buffer[(j * VGA_WIDTH) + i] = terminal_buffer[((j+1) * VGA_WIDTH) + i];
}
}
// Also clear out the bottom row
for(i = 0; i < VGA_WIDTH-1; i++) {
terminal_putentryat(' ', terminal_color, i, VGA_HEIGHT-1);
}
terminal_row = VGA_HEIGHT-1;
}
update_cursor(terminal_column, terminal_row);
}