41 内核中的屏幕打印
参考
https://blog.51cto.com/13475106/category6.html及狄泰软件相关课程
一.屏幕的打印-使用C语言设计一组打印函数,为后续进一步开发内核功能做准备
内核中的屏幕打印模块
这里有三个不同的入口,在screen.模块中是提供保护模式下在屏幕下打印文本的功能,该模块的具体实现是
如图所示有7个接口需要设计,最重要的一个函数为void PrintChar(char c)-打印字符串的函数,由此总结了以上七个函数接口的关系,如下图所示
如图所示中心函数就为上文所说的函数,由此可以得出上图的关系
二.void PrintChar(char c)函数的实现
可以得出最重要的函数为void PrintChar(char c),所以来实现该函数
1.功能定义:在屏幕上打印一个字符
2.实现原理:直接在显存对应的位置写入数据
由上图可知,写入数据的部分可以用四行汇编的语言来实现,但是通过之前的学习,应该是C语言来实现内核开发的,原因是在屏幕打印字符串使用汇编语言较简单,同时汇编的指令有时是必须由汇编语言来实现,之后在C语言调用。
三.语言中的内嵌汇编-内嵌汇编的语法格式
代码示例
从代码示例可以看到该函数是C语言来编写的,但是通过asm volatile关键字来实现汇编语言的内嵌,在这里需要注意的是因为使用GCC编译器,所以在内嵌汇编时使用的时AT&T汇编格式(与nasm汇编格式不同)
三.屏幕打印的实现
1.首先创建screen.c与screen.h的文件,同时在makefile中进行改动,同时在编译后需要将编译之后的文件保存在后面,所以改代的地方如下
2.screen.h头文件的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #ifndef SCREEN_H #define SCREEN_H #define SCREEN_WIDTH 80//定义的宽与高-横坐标与纵坐标 #define SCREEN_HEIGHT 25 typedef enum //颜色枚举类型的定义 { SCREEN_GRAY = 0x07, SCREEN_BLUE = 0x09, SCREEN_GREEN = 0x0A, SCREEN_RED = 0x0C, SCREEN_YELLOW = 0x0E, SCREEN_WHITE = 0x0F } PrintColor; //接口的声明 void ClearScreen(); int SetPrintPos( short w, short h); void SetPrintColor(PrintColor c); int PrintChar( char c); int PrintString( const char * s); int PrintIntDec( int n); int PrintIntHex(unsigned int n); #endif |
3.screen.c的实现-int SetPrintPos与void SetPrintColor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | static int gPosW = 0; static int gPosH = 0; //全局变量的定义 static char gColor = SCREEN_WHITE; int SetPrintPos( short w, short h) { int ret = 0; if ( ret = ((0 <= w) && (w <= SCREEN_WIDTH) && (0 <= h) && (h <= SCREEN_HEIGHT)) ) { unsigned short bx = SCREEN_WIDTH * h + w; gPosW = w; gPosH = h; asm volatile ( "movw %0, %%bx\n" "movw $0x03D4, %%dx\n" "movb $0x0E, %%al\n" "outb %%al, %%dx\n" "movw $0x03D5, %%dx\n" "movb %%bh, %%al\n" "outb %%al, %%dx\n" "movw $0x03D4, %%dx\n" "movb $0x0F, %%al\n" "outb %%al, %%dx\n" "movw $0x03D5, %%dx\n" "movb %%bl, %%al\n" "outb %%al, %%dx\n" : : "r" (bx) : "ax" , "bx" , "dx" ); } return ret; } void SetPrintColor(PrintColor c) { gColor = c; } |
4.PrintChar函数的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | int PrintChar( char c) { int ret = 0; if ( (c == '\n' ) || (c == '\r' ) ) //需要注意这两个字符,当出现这俩个时换行,不进行打印 { ret = SetPrintPos(0, gPosH + 1); //重新换行 } else { int pw = gPosW; int ph = gPosH; if ( (0 <= pw) && (pw <= SCREEN_WIDTH) && (0 <= ph) && (ph <= SCREEN_HEIGHT) ) { int edi = (SCREEN_WIDTH * ph + pw) * 2; //位置的确定 char ah = gColor; //颜色的确定 char al = c; //具体打印的字符 //汇编的嵌入 l四个字节,b为一个字节,w为俩个字节 asm volatile ( "movl %0, %%edi\n" "movb %1, %%ah\n" "movb %2, %%al\n" "movw %%ax, %%gs:(%%edi)" "\n" : : "r" (edi), "r" (ah), "r" (al) : "ax" , "edi" ); pw++; if ( pw == SCREEN_WIDTH ) { pw = 0; ph = ph + 1; } ret = 1; } SetPrintPos(pw, ph); } return ret; } |
5.需要将loader.asm进行设置,因为是在32位的保护模式下运行的,选择子也需要改动
6.PrintString的实现
实现方式-循环调用PrintChar直到遇到0结束符
int PrintIntHex的实现
实现方式-将参数n转换为对应的16进制数字字符串,调用PrintString打印转换得到的字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | int PrintString( const char * s) //需f要对参数进行判断,如果参数为空,返回-1 { int ret = 0; if ( s != NULL ) { while ( *s ) { ret += PrintChar(*s++); //不为空时进行循环,直到遇见结束符结束 } } else { ret = -1; } return ret; } //在这里需要单独对NULL进行定义 kernel.h #ifndef KERNEL_H #define KERNEL_H #define NULL ((void*)0) #endif |
屏幕清空的操作如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | void ClearScreen() //二重循环对屏幕每一个位置打印空字符 { int h = 0; int w = 0; SetPrintPos(0, 0); for (h=0; h<SCREEN_HEIGHT; h++) { for (w=0; w<SCREEN_WIDTH; w++) { PrintChar( ' ' ); } } SetPrintPos(0, 0); } |
PrintIntHex()的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | int PrintIntHex(unsigned int n) { char hex[11] = { '0' , 'x' , 0}; //将整数进行转换 int i = 0; for (i=9; i>=2; i--) { int p = n & 0xF; //位与判断 if ( p < 10 ) { hex[i] = ( '0' + p); //将0-9数字转换为字符 } else { hex[i] = ( 'A' + p - 10); //当低四位大于10,A B C } n = n >> 4; //将N右移四位 } return PrintString(hex); //16进制的字符串打印 } |
在之前实现了其中的函数接口,接下来对剩下的函数接口进行实现
int PrintIntDec(int c)--递归推导
代码实现-十进制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | int PrintIntDec( int n) { int ret = 0; if ( n < 0 ) { ret += PrintChar( '-' ); //多打印一个字符 n = -n; ret += PrintIntDec(n); } else { if ( n < 10 ) { ret += PrintChar( '0' + n); } else { ret += PrintIntDec(n/10); ret += PrintIntDec(n%10); } } return ret; } |
在保护模式下的光标跟踪-两个不同指定端口的操作
上图所示的是高八位与低八位的光标设置,代码实现已经在int SetPrintPos(short w, short h)函数中进行实现,将光标设置高8位与低8位的汇编代码内嵌到C语言代码中
实验与结果-符合预期的结果
与该实验相关的代码在该地址进行保存,需要可以进行下载https://down.51cto.com/13465106/up
小结
1.GCC编译器只支持AT&T格式的内嵌汇编
2.通过汇编的方式可实现PrintCha()函数
3.PrintCha()是其它屏幕打印函数的基础
4.通过操作0x03D4与0x03D5端口对光标位置进行设置
posted on 2021-04-08 09:05 lh03061238 阅读(426) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2020-04-08 嵌入式杂谈之文件系统(转)
2020-04-08 linux VFS