Bran的内核开发教程(bkerndev)-05 打印到屏幕
打印到屏幕
现在, 我们需要尝试打印到屏幕上。为此, 我们需要管理屏幕滚动, 如果能允许使用不同的颜色就更好了。好在VGA视频卡为我们提供了一片内存空间, 允许同时写入属性字节和字符字节对, 可以更简单地在屏幕上显示信息。VGA控制器负责自动绘制屏幕上的更新。屏幕滚动由内核软件来管理。从技术上讲, 这将是我们写的第一个驱动程序。
如上所述, 文本空间只是我们地址空间的一块存储区域, 这片缓冲区位于物理内存的0xB800地址, 缓冲区的数据类型为"short"类型, 这意味着这片文本存储序列的每一项是16bit的, 而不是我们认为的8bit。每个16bit元素可以被分解为高8位和低8位。低8位用于告诉显示控制器在屏幕上绘制什么字符, 称为"字符字节(character byte)"; 高8位称为"属性字节(attribute byte)", 用于定义要绘制字符的前景色和背景色。

由于只有4位用来表示1种颜色, 所以最多只能表示16种不同颜色, 下面是默认的16色调色板:
| Value | Color | Value | Color |
|---|---|---|---|
| 0 | BLACK | 8 | DARK GREY |
| 1 | BLUE | 9 | LIGHT BLUE |
| 2 | GREEN | 10 | LIGHT GREEN |
| 3 | CYAN | 11 | LIGHT CYAN |
| 4 | RED | 12 | LIGHT RED |
| 5 | MAGENTA | 13 | LIGHT MAGENTA |
| 6 | BROWN | 14 | LIGHT BROWN |
| 7 | LIGHT GREY | 15 | WHITE |
最后, 文本模式存储器是一片线性的区域, 每行文本在内存中是连续的。但是视频控制器让它看上去像一个大小为80x25、值为16bit的矩阵(25行x80个字符)。为了把屏幕上的字符位置和内存索引对应, 我们需要用到下面的公式:
\]
举个例子, 如果我们要在屏幕(3, 4)的位置上显示字符character, 则它在内存中的索引为4 * 80 + 3为323, 程序的代码类似于这样:
unsigned short *where = (unsigned short *)0xB8000 + 323;
*where = character | (attribute << 8);
下面是"scrn.c"文件的内容, 该文件包含我们用于处理屏幕的所有函数。包含"system.h"方便我们调用outportb、memcpy、memset、memsetw和strlen函数。我们使用的滚动方法非常有趣: 我们从第1行开始获取文本存储块, 并将其复制到顶部的第0行。这就将整个屏幕向上移动了一行。 为了完成滚动, 我们通过写入空格来擦除文本的最后一行。 putch函数可能是此文件中最复杂的函数, 因为它需要处理换行符('\n'), 回车符('\r')和退格键('\ b')。 如果你愿意, 可以处理警报字符('\a'-ASCII值为7), 在遇到报警符时发出一声短促的哔声。 我还提供了一个设置屏幕颜色的函数settextcolor。
scrn.c
#include <system.h>
unsigned short *textmemptr; // 文本指针
int attrib = 0x0F; // 属性
int csr_x = 0, csr_y = 0; // x和y的坐标
/* 屏幕滚动 */
void scroll(void)
{
unsigned blank, temp;
/* 空格 */
blank = 0x20 | (attrib << 8);
/* 第25行是最后一行, 我们需要向上滚动了 */
if(csr_y >= 25)
{
/* 将24行往上平移一行 */
temp = csr_y - 25 + 1;
memcpy (textmemptr, textmemptr + temp * 80, (25 - temp) * 80 * 2);
/* 最后一行填充空格 */
memsetw (textmemptr + (25 - temp) * 80, blank, 80);
csr_y = 25 - 1;
}
}
/* 更新光标位置: 在最后一个字符下添加一条闪烁的下划线 */
void move_csr(void)
{
unsigned temp;
/* 虚拟坐标与物理地址转换的公式:
* Index = [(y * width) + x] */
temp = csr_y * 80 + csr_x;
/* 往VAG的CRT控制寄存器内发送命令, 设置光标的高地址和低地址
* 想了解更多细节, 需要查找VGA编程文档 */
outportb(0x3D4, 14); // 设置光标高8位地址
outportb(0x3D5, temp >> 8);
outportb(0x3D4, 15); // 设置光标低8位地址
outportb(0x3D5, temp);
}
/* 清空屏幕 */
void cls()
{
unsigned blank;
int i;
/* 带颜色的空格 */
blank = 0x20 | (attrib << 8);
/* 将整个屏幕用空格填充 */
for(i = 0; i < 25; i++)
memsetw (textmemptr + i * 80, blank, 80);
/* 更新虚拟坐标, 并移动光标 */
csr_x = 0;
csr_y = 0;
move_csr();
}
/* 打印单个字符 */
void putch(unsigned char c)
{
unsigned short *where;
unsigned att = attrib << 8;
/* 处理退格键, 往回移动光标一格 */
if(c == 0x08)
{
if(csr_x != 0) csr_x--;
}
/* 处理Tab键, 增大光标的x坐标, 并且只增加到x坐标可以整除8的位置 */
else if(c == 0x09)
{
csr_x = (csr_x + 8) & ~(8 - 1);
}
/* 处理回车键, 将光标回退到行首 */
else if(c == '\r')
{
csr_x = 0;
}
/* 处理换行, 光标移动到下一行行首 */
else if(c == '\n')
{
csr_x = 0;
csr_y++;
}
/* 所有ASCII值大于等于空格的字符都是可打印的字符 */
else if(c >= ' ')
{
where = textmemptr + (csr_y * 80 + csr_x);
*where = c | att; /* 设置字符和颜色 */
csr_x++;
}
/* 当光标到达屏幕又边界, 移动到下一行行首 */
if(csr_x >= 80)
{
csr_x = 0;
csr_y++;
}
/* 在需要的时候滚动屏幕, 并移动光标 */
scroll();
move_csr();
}
/* 使用putch来打印字符串 */
void puts(unsigned char *text)
{
int i;
for (i = 0; i < strlen(text); i++)
{
putch(text[i]);
}
}
/* 设置前景色和背景色 */
void settextcolor(unsigned char forecolor, unsigned char backcolor)
{
/* 前4bit为背景色, 后4bit为前景色 */
attrib = (backcolor << 4) | (forecolor & 0x0F);
}
/* 设置文本模式VGA指针, 并清屏 */
void init_video(void)
{
textmemptr = (unsigned short *)0xB8000;
cls();
}
接下来我们需要将它编译进内核中。首先需要在"build.bat"中添加一行gcc编译命令。复制编译"main.c"的那行命令, 在它的下面一行粘贴, 然后将里面的"main"修改为"scrn"即可。还有, 不要忘记把"scrn.o"添加到链接文件的列表中。为了让main函数能调用"scrn.c"文件中的这些函数, 需要将putch、puts、cls、init_video和settextcolor的函数原型添加到"system.h"中, 不要忘记"extern"关键字, 因为它们都是函数原型。
system.h
extern void cls();
extern void putch(unsigned char c);
extern void puts(unsigned char *str);
extern void settextcolor(unsigned char forecolor, unsigned char backcolor);
extern void init_video();
现在可以在main函数中调用我们的屏幕打印函数了。打开"main.c"文件, 添加一行调用init_video()函数, 然后使用puts()打印"Hello World!", 最后保存所有修改, 运行"build.bat"文件, 调试所有语法错误。将"kernel.bin"复制到你的GRUB软盘上, 如果一切顺利, 你将在你的黑底的屏幕上看见白色的"Hello World!"文本。

此文原创禁止转载,转载文章请联系博主并注明来源和出处,谢谢!
作者: Raina_RLN https://www.cnblogs.com/raina/
Bran的内核开发教程(bkerndev)-05 打印到屏幕的更多相关文章
- Bran的内核开发教程(bkerndev)-02 准备工作
准备工作 内核开发是编写代码以及调试各种系统组件的漫长过程.一开始这似乎是一个让人畏惧的任务,但是并不需要大量的工具集来编写自己的内核.这个内核开发教程主要涉及使用GRUB将内核加载到内存中.GR ...
- Bran的内核开发教程(bkerndev)-01 介绍
介绍 内核开发不是件容易的事,这是对一个程序员编程能力的考验.开发内核其实就是开发一个能够与硬件交互和管理硬件的软件.内核也是一个操作系统的核心,是管理硬件资源的逻辑. 处理器或是CPU是内核 ...
- Bran的内核开发教程(bkerndev)-04 创建main函数和链接C文件
目录 创建main函数和链接C文件 PS: 下面是我自己写的 Win10安装gcc编译器 本节教程对应的Linux下的编译脚本 _main的问题 创建main函数和链接C文件 一般C语言使用mai ...
- Bran的内核开发教程(bkerndev)-08 中断服务程序(ISR)
中断服务程序(ISR) 中断服务程序(ISR)用于保存当前处理器的状态, 并在调用内核的C级中断处理程序之前正确设置内核模式所需的段寄存器.而工作只需要15到20行汇编代码来处理, 包括调用C中的 ...
- Bran的内核开发教程(bkerndev)-06 全局描述符表(GDT)
全局描述符表(GDT) 在386平台各种保护措施中最重要的就是全局描述符表(GDT).GDT为内存的某些部分定义了基本的访问权限.我们可以使用GDT中的一个索引来生成段冲突异常, 让内核终止执行异 ...
- Bran的内核开发教程(bkerndev)-03 内核初步
目录 内核初步 内核入口 链接脚本 汇编和链接 PS: 下面是我自己写的 64位Linux下的编译脚本 内核初步 在这节教程, 我们将深入研究一些汇编程序, 学习创建链接脚本的基础知识以及使用它的 ...
- Bran的内核开发教程(bkerndev)-07 中断描述符表(IDT)
中断描述符表(IDT) 中断描述符表(IDT)用于告诉处理器调用哪个中断服务程序(ISR)来处理异常或汇编中的"int"指令.每当设备完成请求并需要服务事, 中断请求也会调用I ...
- Bran的内核开发指南_中文版
http://www.cnblogs.com/liloke/archive/2011/12/21/2296004.html 最近在看<orange’s>一书,有点想自己写一个轻量级OS的想 ...
- 基于全志A40i开发板——Linux-RT内核应用开发教程(1)
目录 1 Linux-RT内核简介 3 2 Linux系统实时性测试 3 3 rt_gpio_ctrl案例 10 4 rt_input案例 15 本文为Linux-RT内核应用开发教程的第一章节--L ...
随机推荐
- 明明有class为什么还是报ClassNotFoundException?
描述 我们修改接口时,习惯发布一个快照版本用于测试.我们的一个服务也是发布了快照版本,然后一个jar程序要依赖这个服务,修改pom文件打包部署后,通过 java -jar 命令执行这个jar程序,然后 ...
- 无法安装64位office,因为您的PC上有32位
场景:安装visio2013时,突然报以下错误 解决方案: 1. 单击开始--所有程序--附件--运行,在运行输入“regedit“ 2. 弹出注册表编辑器窗口,选择HKEY_CLASSES_ROOT ...
- 用代码触发testng实现并发测试
有时候希望测试用例能用代码触发,发现testng支持这种操作,于是记录一下: 首先添加testng依赖: <dependency> <groupId>org.testng< ...
- kafka经典入门
问题导读 1.Kafka独特设计在什么地方?2.Kafka如何搭建及创建topic.发送消息.消费消息?3.如何书写Kafka程序?4.数据传输的事务定义有哪三种?5.Kafka判断一个节点是否活着有 ...
- maven引入本地jar包的方法
maven作为包管理工具,好处不必多说 但是有些情况,比如需要引入第三方包,如快递鸟,支付宝,微信等jar包(当然有可能直接提供maven依赖) 如果直接下载到本地之后,怎么整合到自己的maven工程 ...
- [Leetcode] 第148题 排序链表
一.题目描述 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序. 示例 1: 输入: 4->2->1->3 输出: 1->2->3->4 示 ...
- unity - TileMap的注意事项
本文记述了一些在使用Tilemap绘制场景时的需要注意的细节问题. 关于Tilemap的创建及使用本文不做说明,但推荐佳作:Unity中使用Tilemap快速创建2D游戏世界 - feng 本文项目地 ...
- java数据结构——单链表、双端链表、双向链表(Linked List)
1.继续学习单链表,终于摆脱数组的魔爪了,单链表分为数据域(前突)和引用域(指针域)(后继),还有一个头结点(就好比一辆火车,我们只关心火车头,不关心其它车厢,只需知晓车头顺藤摸瓜即可),头结点没有前 ...
- 读《深入理解Elasticsearch》点滴-改善查询相关性
1.标准查询 query match _all query:"搜索字符串" operator:or 2.多匹配查询+区分权重 query multi_match "que ...
- java时间格式转换任意格式
例如:20180918/120023转换成2018-09-18 12:00:23 //时间格式转换 public String getNomalTime(String oldTime){ String ...