一步步编写操作系统 71 直接操作显卡,编写自己的打印函数71-74

一直以来,我们在往屏幕上输出文本时,要么利用bios中断,要么利用系统调用,这些都是依赖别人的方法。咱们还用过一个稍微有点独立的方法,就是直接写显存,但这貌似又没什么含量。如今我们要写一个打印函数了,似乎,我们马上就要站起来了

之前我们讲述了有关显卡的知识,但当时怕影响兄弟们的学习积极性,我们并没有说把有关显卡的寄存器罗列出来。话说,出来混早晚要还的,躲得过初一躲不过十五。如今我们需要通过端口来控制显卡的行为,有些问题还是要面对的。

之前咱们对显卡的操作和对普通内存操作是一样的,打印字符时,就是往显存中mov一些字符的ascii码和属性,那还是我们在显存默认的文本模式下。您想,我们都爱看视频、电影,话说十年前第一次看到DVD版本的电影时我都被震撼到了,当时看的是《星河战队》,清晰到毛发可见的程度,何况大家现在都偏爱蓝光高清版本,咳咳,说远了,总之能够让我们看到如此炫丽的画面,这都是显卡的功劳,这说明显卡还可以工作在彩色图形模式。对于显卡的操作可不是咱们之前的mov来mov去就行了。不过我们也并不需要那么复杂的功能,咱们还是在80*25的文本模式下转悠,而且还只是简单的操作。

之前我们已经对硬盘有过端口操作了,无非就是用in和out指令加不同的端口号,对显卡也是如此。显卡中的寄存器很多,不,是非常多,这里按照它们在图形管线(位于cpu和video之间)中的位置的顺序给大家介绍下,见下表

 

如您所见,表中列出的寄存器的数量似乎没我说的那么恐怖,不要高兴的太早,马上就要让大伙儿难过了,其实这些只是寄存器的目录而已,这有没有让大家想起了周星驰主演的电影《鹿鼎记》中,天地会总舵主陈近南让韦小宝练武功时的场景,拿出了一本不算太厚的“武功秘密”,起初小宝还很高兴,但陈近南告诉他这只是个目录,而且是练了之后才九死一生,否则就十死无生^_^。

好了,下节到解释,本节到此,现来玩哦。

接上文,请见“一步步编写操作系统 71 直接操作显卡,编写自己的打印函数1” 下面解释下显卡寄存器的内容。

以上所说的目录其实就是寄存器分组,在这些寄存器中也不全是分组。前四组寄存器属于分组,它们有一个特征,就是被分成了两类寄存器,即Address Register和Data Register。这两个寄存器是干吗的呢?这得先从寄存器为什么要分成组开始说。

端口实际上就是IO接口电路上的寄存器,为了能访问到这些cpu外部的寄存器,计算机系统为这些寄存器统一编址,一个寄存器被赋予一个地址,这些地址可不是我们所说的内存地址,内存地址是用来访问内存用的,其范围取决于地址总线的宽度,而寄存器的地址范围是0~65535(Intel系统)。这些地址就是我们所说的端口号,用专门的IO指令in和out来读写这些寄存器。至于计算机内部访问端口怎么实现的,这是硬件工程师的事,咱们暂且奉行拿来主义,认同这个事实就够了。

IO接口电路上的寄存器数量有多有少,这要看具体的外设了,我这么说您就明白了,这里给寄存器分组的原因是,显卡(显示器的IO接口电路)上的寄存器太多了,如果一个寄存器就要占用一个系统端口的话,这得多浪费硬件资源,万一别的硬件也这么干,这63336个地址可就捉襟见肘了。所以计算机系统说了,我不管你们内部有多少寄存器,给你们的端口地址是有数的,你们自己内部协调吧。

计算机工程师是非常聪明的,把数据结构中数组的知识用到了硬件中。他们把每一个寄存器分组视为一个寄存器数组,提供个寄存器用于指定数组下标,再提供个寄存器用于对索引所指向的数组元素(也就是寄存器)进行输入输出操作。这样用这两个寄存器就能够定位寄存器数组中的任何寄存器啦。

这两个寄存器就是各组中的Address Register和Data Register。Address Register做为数组的索引(下标),Data Register做为寄存器数组中该索引对应的寄存器,它相当于所对应的寄存器的窗口,往此窗口读写的数据都作用在索引所对应的寄存器上。

所以,对这类分组的寄存器操作方法是,先在Address Register中指定寄存器的索引值,用来确定所操作的寄存器是哪个,然后在Data Register寄存器中对所索引的寄存器进行读写操作。

上面CRT Controller Registers寄存器组中的Address Register和Data Register的端口地址有些特殊,它的端口地址并不固定,具体值取决于Miscellaneous Output Register寄存器中的Input/Output Address Select字段,现在咱们看一下这个寄存器。

 

和大家坦白一点,显卡参数还需要专业人士来解释,由于咱们用不到这么高深的设置,加之我对显卡没有深入学习,所以这里面有好多参数术语,我不敢随意翻译成中文,担心误导大家,所以我直接把此寄存器各字段的英文描述搬过来了,至于中文的意思,大家仁者见仁智者见智吧,请您见谅。

 

好了,简直了,就这样吧,晚安。

 

 

万事开头难,我们先从简单的打印字符开始。这个功能类似c语言中的putchar,每次只打印一个字符,由于此函数咱们是在内核中实现的,暂且将其命名为put_char。

在这之前,为了开发方便,我们定义一些数据类型。主要是参考了linux的/usr/include/stdint.h文件,有环境的同学可以自行看下,没环境的同学,请看图

 

该文件在我目前的linux版本上是320行,这里只是冰山一角,里面各种宏显得好高大上啊,不过请放心,把这个图贴出来就是为了“吓唬”大家的^_^,咱们不会写这么复杂,不信请看代码:

 1	#ifndef __LIB_STDINT_H2	#define __LIB_STDINT_H3	typedef signed char int8_t;4	typedef signed short int int16_t;5	typedef signed int int32_t;6	typedef signed long long int int64_t;7	typedef unsigned char uint8_t;8	typedef unsigned short int uint16_t;9	typedef unsigned int uint32_t;
10	typedef unsigned long long int uint64_t;
11	#endif

怎么样,确实是很简单吧。以后我们采用的任何数据类型就要用这些定义好的啦。估计大家也注意到啦,咱们定义的stdint.h文件位于lib目录下,也就是说我新建了个lib目录做来专门存放各种库文件。不仅如此,在lib目录下还建立了user和kernel两个子目录,以后供内核使用的库文件就放在lib/kernel/下,lib/user/中是用户进程使用的库文件。

我们要实现的字符打印函数叫put_char,它是用汇编语言写的。因为要和显卡打交道啦,里面涉及到端口的读写操作,目前还是用纯汇编文件较方便,以后慢慢发展起来后,咱们会采取内联汇编的方式。

直接上代码啦,我们的打印函数统统在print.S文件中完成,该文件是各种打印函数的核心,重中之重,这里先给大家介绍下它的处理流程:

  1. 备份寄存器现场。
  2. 获取光标坐标值,光标坐标值是下一个可打印字符的位置。
  3. 获取待打印的字符。
  4. 判断字符是否为控制字符,若是回车符、换行符、退格符三种控制字符之一,则进入相应的处理流程。否则,其余字符都被粗暴地认为是可见字符,进入输出流程处理。
  5. 判断是否需要滚屏
  6. 更新光标坐标值,使其指向下一个打印字符的位置。
  7. 恢复寄存器现场,退出。

该文件相对来说又有点长,故需要将其拆分成3部分,先给大伙儿呈上其第一部分,代码:

 1 TI_GDT equ 02 RPL0 equ 03 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL045 [bits 32]6 section .text7 ;------------------------ put_char -----------------------------8 ;功能描述:把栈中的1个字符写入光标所在处9 ;-------------------------------------------------------------------10 global put_char11 put_char:12 pushad ;备份32位寄存器环境13 ;需要保证gs中为正确的视频段选择子,;为保险起见,每次打印时都为gs赋值14 mov ax, SELECTOR_VIDEO ; 不能直接把立即数送入段寄存器 15 mov gs, ax1617 ;;;;;;;;; 获取当前光标位置 ;;;;;;;;;18 ;先获得高8位19 mov dx, 0x03d4 ;索引寄存器20 mov al, 0x0e ;用于提供光标位置的高8位21 out dx, al22 mov dx, 0x03d5 ;通过读写数据端口0x3d5来获得或设置光标位置23 in al, dx ;得到了光标位置的高8位24 mov ah, al2526 ;再获取低8位27 mov dx, 0x03d428 mov al, 0x0f29 out dx, al30 mov dx, 0x03d531 in al, dx3233 ;将光标存入bx34 mov bx, ax35 ;下面这行是在栈中获取待打印的字符36 mov ecx, [esp + 36] ;pushad压入4×8=32字节,;加上主调函数4字节的返回地址,故esp+36字节37 cmp cl, 0xd ;CR是0x0d,LF是0x0a38 jz .is_carriage_return39 cmp cl, 0xa40 jz .is_line_feed4142 cmp cl, 0x8 ;BS(backspace)的asc码是843 jz .is_backspace44 jmp .put_other45 ;;;;;;;;;;;;;;;;;;

下节我们再解释代码吧,再来玩哦。

 

 

接前文,下面把代码解释一下。

 1 TI_GDT equ 02 RPL0 equ 03 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL045 [bits 32]6 section .text7 ;------------------------ put_char -----------------------------8 ;功能描述:把栈中的1个字符写入光标所在处9 ;-------------------------------------------------------------------10 global put_char11 put_char:12 pushad ;备份32位寄存器环境13 ;需要保证gs中为正确的视频段选择子,;为保险起见,每次打印时都为gs赋值14 mov ax, SELECTOR_VIDEO ; 不能直接把立即数送入段寄存器 15 mov gs, ax1617 ;;;;;;;;; 获取当前光标位置 ;;;;;;;;;18 ;先获得高8位19 mov dx, 0x03d4 ;索引寄存器20 mov al, 0x0e ;用于提供光标位置的高8位21 out dx, al22 mov dx, 0x03d5 ;通过读写数据端口0x3d5来获得或设置光标位置23 in al, dx ;得到了光标位置的高8位24 mov ah, al2526 ;再获取低8位27 mov dx, 0x03d428 mov al, 0x0f29 out dx, al30 mov dx, 0x03d531 in al, dx3233 ;将光标存入bx34 mov bx, ax35 ;下面这行是在栈中获取待打印的字符36 mov ecx, [esp + 36] ;pushad压入4×8=32字节,;加上主调函数4字节的返回地址,故esp+36字节37 cmp cl, 0xd ;CR是0x0d,LF是0x0a38 jz .is_carriage_return39 cmp cl, 0xa40 jz .is_line_feed4142 cmp cl, 0x8 ;BS(backspace)的asc码是843 jz .is_backspace44 jmp .put_other45 ;;;;;;;;;;;;;;;;;;

put_char函数中以后我们任何一个打印功能的核心,所以光它的实现就要112行,这似乎是我们目前写过的最长的一个函数了,我保证以后也没有这么长的啦。好啦,长归长,不过也没什么难度,下面咱们开讲啦。

put_char的打印原理是直接写显存,在32位保护模式下对内存的操作是“[段基址(选择子):段内偏移量]”,所以这就涉及到视频段选择子啦。一直以来我们都是用段寄存器gs来存储视频段选择子,以后也是,所以得保证在写显存之前,gs中的值是正确的选择子。第14~15行是我们为GS寄存器赋值的代码,别小看这两行,大有来头,可不亚于摊上大事呢,吼吼,待咱们把put_char函数说完再跟大家好好说道说道吧,大家要做好心理准备。咱们先说别的。

第1~3行是定义了视频段的选择子,由于只需要这三行,专门定义个配置文件有点不值当的,所以直接在这定义了,好的习惯是放在配置文件中,大家在实践中不要学我。

第10行是通过关键字global把函数put_char导出为全局符号,这样对外部文件便可见了,外部文件通过声明便可以调用。

第11行开始定义函数put_char。

第12行是用pushad指令备份32位寄存器的环境,按理说用到哪些寄存器就要备份哪些,我这里是偷懒行为,将8个32位全部备份了。PUSHAD是push all double,该指令压入所有双字长的寄存器,这里的“所有”一共是8个,它们的入栈先后顺序是: EAX->ECX->EDX->EBX->ESP->EBP->ESI->EDI,EAX是最先入栈。

第14~15行是为gs安装正确的选择子,原因如前所述完事再说。

我们在打印字符时,通常都不用指定字符显示的坐标位置,大家也没觉得有什么奇怪,原因是字符是在当前光标的位置处显示的,而且光标的位置会一直更新顺延,我们的字符一直跟着光标走,似乎光标就是字符的导航一样,而我们已经习惯了跟随光标。我想大伙儿已经清楚了光标和字符的关系了,对,它们的关系就是没有任何关系^_^。“光标在哪字符就在哪”,这是我们人为有意设置的,我们是在光标处打印字符。也就是说,我们也可以不在光标处打印字符,让光标和字符的位置分开。这一点在理论上就能证明,我们知道打印字符本质上就是把字符写入在显存中的某个地址处。在文本模式80*25下的显存可以显示80*25=2000个字符,每个字符占2字节,低字节是字符的ascii码,高字节是前景色和背景色属性,所以在4000字节的显存空间中,只要起始地址为偶数的任意2字节我们都可以写入字符,您看,这哪里是光标能限制的。光标只是个亮点,用来吸引用户眼球的,它能够帮助咱们快速找到屏幕上的活跃位置,它本身与字符显示的位置没有关系。

有点长,下节再说吧。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/439534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【CodeForces - 208C 】Police Station(单源最短路条数,起点终点建图,枚举顶点)

题干&#xff1a; The Berland road network consists of n cities and of m bidirectional roads. The cities are numbered from 1 to n, where the main capital city has number n, and the culture capital — number 1. The road network is set up so that it is possi…

【Chrome浏览器】常用快捷键整理

标签页和窗口快捷键 1. Ctrl n 打开新窗口 2. Ctrl t 打开新的标签页&#xff0c;并跳转到该标签页 3. Ctrl Shift t 重新打开最后关闭的标签页&#xff0c;并跳转到该标签页 4. Ctrl Tab 跳转到下一个打开的标签页 5. Ctrl Shift Tab 跳转到上一个打开的标签页 6.…

【人工智能课程实验】 - 利用贝叶斯分类器实现手写数字 的识别

读入数据与预处理 因为老师给的文件无法直接读取&#xff0c;故从官网导入数据&#xff1a; 官网链接&#xff1a;http://www.cs.nyu.edu/~roweis/data.html 导入数据之后要对MATLAB文件进行读入&#xff1a; datasio.loadmat(trainfile) 对文件type一下&#xff1a; ty…

一步步编写操作系统 77 内联汇编与ATT语法简介

内联汇编 之前和大家介绍过了一种汇编方法&#xff0c;就是C代码和汇编代码分别编译&#xff0c;最后通过链接的方式结合在一起形成可执行文件。 另一种方式就是在C代码中直接嵌入汇编语言&#xff0c;强大的GCC无所不能&#xff0c;咱们本节要学习的就是这一种&#xff0c;它…

【Python学习】内置函数(不断更新)

关于常用在for循环中的range函数 python range() 函数可创建一个整数列表&#xff0c;一般用在 for 循环中。 函数语法 range(start, stop[, step]) 参数说明&#xff1a; start: 计数从 start 开始。默认是从 0 开始。例如range&#xff08;5&#xff09;等价于range&#…

【Python学习】 简单语法与常见错误(持续更新)

关于单引号和双引号 当输出的字符串内部没有单引号的时候&#xff0c;外面可以用单引号&#xff0c; 但是如果内部有了单引号&#xff0c;那么外部只能用双引号。 dict {Name: Zara, Age: 7, Class: First} print(dict) print (dict[Name]: , dict[Name]) print ("dic…

一步步编写操作系统 78 intel汇编与ATT汇编语法区别

本节咱们介绍下intel汇编语法和at&t汇编语法的区别。 以上表中未列出这两种语法在内存寻址方面的差异&#xff0c;个人觉得区别还是很大的&#xff0c;下面单独说说。 在Intel语法中&#xff0c;立即数就是普通的数字&#xff0c;如果让立即数成为内存地址&#xff0c;需要…

重读经典:《Masked Autoencoders Are Scalable Vision Learners》

MAE 论文逐段精读【论文精读】这一次李沐博士给大家精读的论文是 MAE&#xff0c;这是一篇比较新的文章&#xff0c;2021年11月11日才上传到 arXiv。这篇文章在知乎上的讨论贴已经超过了一百万个 view&#xff0c;但是在英文社区&#xff0c;大家反应比较平淡一点&#xff0c;R…

【Python学习日志】 - Numpy包

NumPy是什么&#xff1f; 使用Python进行科学计算的基础包&#xff0c;在数据分析的时候比较常用到矩阵计算。这时太多的Np属性不记得&#xff0c;所以方便自己使用把一些常用的Np属性汇总记录一下使用的时候方便查找。 ndarray.ndim 阵列的轴数&#xff08;尺寸&#xff09;…

详解协同感知数据集OPV2V: An Open Benchmark Dataset and Fusion Pipeline for Perception with V2V Communication

在《详解自动驾驶仿真框架OpenCDA: An Open Cooperative Driving Automation Framework Integrated with Co-Simulation》 一文中介绍了自动驾驶仿真框架 OpenCDA。本文将介绍论文作者另一篇最新工作 OPV2V&#xff0c;论文收录于 ICRA2022。 OPV2V 数据集主要 feature 有&…

【Python学习】 - 如何在Spyder中弹出plot绘图窗口而不是在Console中绘图

依次选择这几项&#xff1a; 点击ok确认。 注意&#xff1a;点击ok之后不会立即生效&#xff0c;重启Spyder之后才会生效

mysql系列:加深对脏读、脏写、可重复读、幻读的理解

关于相关术语的专业解释&#xff0c;请自行百度了解&#xff0c;本文皆本人自己结合参考书和自己的理解所做的阐述&#xff0c;如有不严谨之处&#xff0c;还请多多指教。 **不可重复读的重点是修改: **同一事务&#xff0c;两次读取到的数据不一样。 幻读的重点在于新增或者…

重读经典(点云深度学习开山之作):《Deep learning on point clouds for 3D scene understanding》(持续更新中)

本文介绍的是 PointNet 作者的博士论文&#xff1a;3D场景理解中的点云深度学习。从上图可以看到&#xff0c;整个博士论文主要贡献有两块&#xff1a;一是点云深度学习的网络架构&#xff08;PointNet 和 PointNet&#xff09;&#xff1b;二是在3D场景理解中的应用&#xff0…

Coursera自动驾驶课程第17讲:An Autonomous Vehicle State Estimator

在第16讲《Coursera自动驾驶课程第16讲&#xff1a;LIDAR Sensing》我们学习了自动驾驶目前常用的3D 传感器&#xff0c;激光雷达&#xff0c;了解了激光雷达的工作原理&#xff0c;掌握了对点云数据的操作以及如何使用点云配准方法来进行汽车定位。 回顾一下&#xff0c;在本…

!何为脏读、不可重复读、幻读

2.0、前言 事务的隔离性是指多个事务并发执行的时候相互之间不受到彼此的干扰的特性&#xff0c;隔离性是事务ACID特性中的I&#xff0c;根据隔离程度从低到高分为Read Uncommitted&#xff08;读未提交&#xff09;&#xff0c;Read Committed&#xff08;读已提交&#xff0…

【转】JPA、Hibernate和Mybatis区别和总结

很多人都用过java的数据库连接池C3P0&#xff0c;但官方没有说明名称的由来。 据传闻&#xff1a;连接池作者是《星球大战》迷&#xff0c;C3P0就是其中的一个机器人&#xff0c;并且这个名称中包涵connection 和pool的单词字母。因此叫这个名字&#xff08;根据网友提醒&…

详解3D物体检测模型: Voxel Transformer for 3D Object Detection

本文介绍一个新的的3D物体检测模型&#xff1a;VoTr&#xff0c;论文已收录于ICCV 2021。 这是第一篇使用 voxel-based Transformer 做3D 主干网络&#xff0c;用于点云数据3D物体检测。由于有限的感受野&#xff0c;传统的 3D 卷积网络检测器&#xff08;voxel-based&#xff…

一步步编写操作系统 65 标准调用约定stdcall 汇编实战

因为c语言遵循的调用约定是cdecl&#xff0c;咱们也自然要遵守cdecl约定了。不过为了起到对比的作用&#xff0c;除了介绍cdecl外&#xff0c;也会介绍下stdcall。 既然咱们用的是调用约定是cdecl&#xff0c;那对它的介绍最好让它离下一节的内容近一些&#xff0c;所以先说一…

Coursera自动驾驶课程第18讲:The Planning Problem

在第17讲《Coursera自动驾驶课程第17讲&#xff1a;An Autonomous Vehicle State Estimator》 我们学习了如何使用多传感器融合进行自车定位&#xff0c;以及传感器的内外参标定和时间同步&#xff0c;我们还讨论了在实际应用中常遇到的问题。 从本讲开始我们将学习一个新的模…

详解3D物体检测模型:Focal Sparse Convolutional Networks for 3D Object Detection

用于3D目标检测的焦点稀疏卷积神经网络【CVPR2022】【3D检测】本文介绍一篇新的 3D 物体检测模型&#xff1a;Focals Conv&#xff0c;论文收录于 CVPR2022。在 3D 检测任务中&#xff0c;点云或体素数据不均匀地分布在3维空间中&#xff0c;不同位置的数据对物体检测的贡献是不…