文章目录

  • IO接口
    • IO端口
  • 显卡
    • 显卡交互
  • 控制显示器
  • 实现库函数put_char
    • 退格符处理
    • 换行符&回车符
    • 打印可见字符
    • 滚屏操作
  • 参考文献

写在前面:自制操作系统Gos 第二章第三篇:主要内容是如何操纵外设,如何操纵显示器

IO接口

其实博主之前对和硬件打交道是非常恐惧的。一个是不了解,一个是外部设备种类繁多、原理各异,实在是没有太多的精力精通每一项了。其实,说白了就是没有一个统一的接口供我这种懒狗调用,兼容性太差。

但是,按照计算机哲学来看:任何不兼容的问题,其实都可以通过加一层来解决这个问题,这一层就是 —— IO接口。比如说,声卡就是驱动影响设备的;显卡就是驱动显示器的。所以,今天我们打交道的主要对象就是显卡了,通过显卡间接操作显示器。


什么是IO接口呢?
IO接口时连接CPU和外部设备的逻辑控制部件,其分为硬件和软件两个部分。硬件部分所做的都是一些实质具体的工作,其功能时协调CPU和外设之间的种种不匹配,如双方由于不匹配,那IO接口就实现数据缓存以减少等待时间;数据格式不匹配,那IO接口就可以负责数据转换的功能。
当然,IO接口的作用还有很多,总结如下:

  • 设置数据缓冲,解决CPU和外设速度不匹配的问题
  • 设置信号电平转换电路。CPU时TTL电平,而外设大多时机电设备,需要进行转换
  • 设置数据格式转换
  • 设置时序控制电路来同步CPU和外部设备
  • 提供地址译码

同一时刻,CPU只能和一个IO接口通信,当很多的IO接口同时想和CPU对话的时候。其实就导致了冲突的问题,那么如何解决呢?这个时候,我们再加一层去仲裁这个问题。而这一层就是大名鼎鼎的南桥

CPU通过内部总线连接到南桥芯片的内部。同时,在南桥内部集成了一些IO接口。除了集成之外,还有一些供其他非必要设备支持的PCI接口。

IO端口

IO接口在诞生之初,其就被设置为通过寄存器来和CPU交互。其内部也有专门的寄存器用于数据交互,称之为端口。IO接口是CPU和硬件的桥梁。一端是CPU,另一端是硬件。端口是IO接口开放给CPU的接口,一般的IO接口都有一组端口,而每个端口都有自己的用途。

刚刚也说了,端口的本质就是寄存器,其也有自己的数据宽度。所以我们需要合适的指令去访问,那么怎么访问呢?

#Intel版本的汇编语言格式
操作码 目的 源# 从端口读取数据 --> in
in al,dx    #dx☞端口号# 写数据到端口 --> out
out dx,al   #dx☞端口号

显卡

某些IO接口也叫适配器,适配器是驱动某一外部设备的功能模块。显卡也称为显示适配器,不够其本质就是IO接口,起了个高大上的名字罢了,不要害怕。

显卡呢,是PCI设备,其就是插在上面那个图中的插槽的。


PCI总线时并行架构,并行数据需要保证数据发送之后要同时到达目的地。否则就是一团乱麻。为了解决这个问题,其实就不得不采用一次一位这样的发送方式,而这也被称为串口显卡。有人说这样可能会很慢,但是其实也不一定。记住:速率=单次传送数据*频率

为了显示图像,我们就需要显示器。但是无论是什么牌子的显示器,其都是由显卡来控制的。无论是哪种显卡,它提供给我们的可编程接口都是一样的:IO端口和显存。所以,同学们大家不要对硬件交互过于恐惧。

显存是什么
显存是由显卡提供的,它是位于显卡内部的一块内存,所以称其为显存。

显卡的工作原理就是不断的读取显存,随后将其内容发送到显示器之中。所以,我们要做的工作就是往这块内存中不断写数据。写什么数据呢?

当然是一个约定啦,ASCII码,按照这个写就可以了:

显卡交互

那么问题来了,我们知道要写什么了,那么怎么写到缓存里面呢?

我们先来看一些显存的地址分布:

起始 结束 大小 用途
C0000 C7FFF 32KB 显示适配器BIOS
B8000 BFFFF 32KB 用于文本模式显示适配器
B0000 B7FFF 32KB 用于黑白显示适配器
A0000 AFFFF 64KB 用于彩色显示适配器


这里的地址是实模式哦

我们实现文本模式就可以了。也就是往上面0xB8000 ~0xBFFFF 这个地址范围写数据咯。
那么一个屏幕可以显示多少个文本呢?一本来说屏幕显示是行数*列数。常用规格有80×25、40×25、80×43 等等。我们这里就默认的 80×25 就好了。

我们要注意一个问题:彩色字符是怎么实现的呢?
是的,即便在文本模式下面,也是可以打印彩色字符的。实现原理就是用两个字节表示一个字符,其中1个字节表示文本内容,一个字节表示文本属性。其内存布局如下:

图示说的很明白我这里就不赘述了。
下图显示了RGB三原色不同组合的效果:

R G B 不高亮 高亮
0 0 0
0 0 1 浅蓝
0 1 0 绿 浅绿
0 1 1 浅青
1 0 0 浅红
1 0 1 品红 浅品红
1 1 0
1 1 1 亮白

所以说,我们要打印出黑底亮白字的效果就需要往显存写入:0000 1111,即0x0F

控制显示器

所以,我们现在已经了解所有原理的。我们可以上手写代码了:

; 显卡主引导程序
;
; LOADER_BASE_ADDR equ 0xA000
; LOADER_START_ADDR equ 0x2SECTION MBR vstart=0x7c00mov ax,cs           ;初始化mov ds,axmov es,axmov ss,axmov sp,0x7c00mov ax,0xb800       ;0xb800 段基址mov gs,ax; 清屏利用0x06号功能,上卷全部行,进行清屏
; int 0x10  功能号:0x60    功能描述:上卷窗口
; 输入:
; AH 功能号: 0x06
; AL = 上卷的行数(0代表全部)
; BH = 上卷的行属性
; (CL,CH) = 窗口左上角(x,y) 的位置
; (DL,DH) = 窗口右下角(x,y)的位置
; 无返回值!mov ax,0600hmov bx,0700hmov cx,0        ;左上角(0,0)mov dx,0x184f   ;右下角(80,25);VAG文本模式中,一行只能容纳80个字符,总共25行;下标从0开始,所以0x18=24,0x4f=79int 10h
; 以下是操纵显示器打印字符mov byte [gs:0x00],'h'mov byte [gs:0x01],0x0F     ;黑底亮白不闪烁mov byte [gs:0x02],'e'mov byte [gs:0x03],0x0Fmov byte [gs:0x04],'l'mov byte [gs:0x05],0x0Fmov byte [gs:0x06],'l'mov byte [gs:0x07],0x0Fmov byte [gs:0x08],'o'mov byte [gs:0x09],0x0Fmov byte [gs:0x0a],','mov byte [gs:0x0b],0x0Fmov byte [gs:0x0c],'G'mov byte [gs:0x0d],0x0Fmov byte [gs:0x0e],'o'mov byte [gs:0x0f],0x0Fmov byte [gs:0x10],'s'mov byte [gs:0x11],0x0Fmov byte [gs:0x12],'!'mov byte [gs:0x13],0x0Fjmp $times 510-($-$$) db 0 ;总共512字节,需要有510个占位db 0x55,0xaa

很简单的汇编代码,做的事情也都写到注释里面了,起始也就是对MBR的代码的更改了一下。我们直接来看一下效果:

实现库函数put_char

我们刚刚讲了如何操作显示器。那我们现在来实现库函数put_char试试,至于为什么要写put_char,而不是要实现我们用的最多的put_str,是因为不管是put_int还是put_str最终其实本质调用的都是put_char,实现了这个,其他都触类旁通,对我们理解操作系统底层大有裨益。

用过的朋友都知道,put_char会往当前光标的位置写入一个字符,然后光标后移等待输入下个输入。

这个过程的逻辑很明晰了:

  1. 备份寄存器现场
pushad      ;备份32位寄存器环境
  1. 获取光标的位置
;获取当前光标位置;先获得高8位mov dx, 0x03d4  ;索引寄存器mov al, 0x0e      ;用于提供光标位置的高8位out dx, almov dx, 0x03d5  ;通过读写数据端口0x3d5来获得或设置光标位置 in al, dx       ;得到了光标位置的高8位mov ah, al;再获取低8位mov dx, 0x03d4mov al, 0x0fout dx, almov dx, 0x03d5 in al, dx


我们要获取光标的值,首先就需要往端口地址为0x3d4的寄存器写入寄存器的索引0x0e,之后再从端口地址为0x3d5的地方读写数据。详情见CRT Controller寄存器规约。

  1. 获取待打印的字符
   ;下面这行是在栈中获取待打印的字符mov ecx, [esp + 36]       ;pushad压入4×8=32字节,加上主调函数的返回地址4字节,故esp+36字
  1. 判断该字符是否是控制字符(回车、换行、或者退格这三个的处理不同),如果不是就打印出来了
   cmp cl, 0xd                  ;如果是回车符jz .is_carriage_returncmp cl, 0xa             ;如果是换行符jz .is_line_feedcmp cl, 0x8                 ;BS(backspace)的asc码是8jz .is_backspacejmp .put_other           ;正常打印字符
  1. 更新光标寄存器的值
  2. 恢复寄存器现场

退格符处理

平常我们输入退格符之后,操作系统给我们的反馈就是光标前移,之后当前字符被删掉。光标前移很好实现,但是当前字符被删掉其实是在原来的位置打印了一个空格,还是有字符的,只是我们 “不可见” 罢了。

所以分为两步:

  1. 原来的字符替换为空格
   dec bx                       ;bx为当前该打印的字符位置shl bx,1                     ;bx*2是光标在显存的相对地址mov byte [gs:bx], 0x20          ;将待删除的字节补为0或空格皆可inc bx                       ;bx+1 转到控制字符属性的地方mov byte [gs:bx], 0x07       ;打印不加亮的白色shr bx,1                     ;bx的位置回退
  1. 设置光标值为当前值减一,这要注意的是bx此时的位置在空格字符的前面,大概这个情况:
;;;;;;; 1 先设置高8位 ;;;;;;;;mov dx, 0x03d4            ;索引寄存器mov al, 0x0e              ;用于提供光标位置的高8位out dx, almov dx, 0x03d5           ;通过读写数据端口0x3d5来获得或设置光标位置 mov al, bhout dx, al;;;;;;; 2 再设置低8位 ;;;;;;;;;mov dx, 0x03d4mov al, 0x0fout dx, almov dx, 0x03d5 mov al, blout dx, al

换行符&回车符

这两个控制字符要达到的目的是一样的,光标移到下一行的开始。

   xor dx, dx                  ; dx是被除数的高16位,清0.mov ax, bx                 ; ax是被除数的低16位.mov si, 80                div si                      ; bx除80,余数是当前行字符的个数,在dx中存储sub bx, dx                  ; 光标值移到当前的行首add bx, 80                 ; bx换到下一行

打印可见字符

这个其实就是put_other函数,目的就是打印当前输入的字符,这个之前我们读入过了,存储在寄存器ecx之中。

   shl bx, 1               ; 光标位置是用2字节表示,将光标值乘2,表示对应显存中的偏移字节mov [gs:bx], cl            ; ascii字符本身inc bxmov byte [gs:bx],0x07      ; 字符属性shr bx, 1                 ; 恢复老的光标值inc bx                     ; 下一个光标值

滚屏操作

但是这就完了么,我们试想假如我们打印到了2000这个位置(80*25),那么我们的下个字符该怎么打印呢?

回想一下Linux对此的处理,其会把当前显存中的内容整体上移一行,这样第一行就消失啦,之后下面就会空出一行位置,大概是下面的这个过程:

那么其实也是可以分两步的:

  1. 搬运已存在内容上移一行
   cld  mov ecx, 960               ; 一共有2000-80=1920个字符要搬运,共1920*2=3840字节.一次搬4字节,共3840/4=960次 mov esi, 0xc00b80a0           ; 第1行行首mov edi, 0xc00b8000              ; 第0行行首rep movsd
  1. 光标移到最后一行,将最后一行填充为空白,之后重置光标为最后一行行首
;将最后一行填充为空白mov ebx, 3840           ; 最后一行首字符的第一个字节偏移= 1920 * 2mov ecx, 80                 ;一行是80字符(160字节),每次清理1字符(2字节),一行需要移动80次.cls:mov word [gs:ebx], 0x0720        ;0x0720是黑底白字的空格键add ebx, 2loop .cls mov bx,1920                 ;将光标值重置为1920,最后一行的首字符.

参考文献

[1] 操作系统真相还原
[2] 百度图片

Gos —— 显示器控制相关推荐

  1. C++编程实现多显示器控制(复制、横屏、纵屏,显示器个数)等

    需求的提出: 最近做了个三维的程序,部署到客户机器上,客户看了后,现场提出这样的一个需求:程序能智能探测接入的显示器个数,当有新的显示器接入时,现有的只在一个显示器上显示的三维场景能投递到新插入的显示 ...

  2. lcd显示屏c语言程序设计,基于单片机的LCD显示器控制的设计.doc

    基于单片机的LCD显示器控制的设计 PAGE 17基于单片机的LCD显示器控制设计摘要:LCD液晶显示已经是人机界面的关键技术.本文对基于单片机的LCM液晶显示模块控制系统进行了研究.首先在绪论中介绍 ...

  3. unity 多台 显示器 控制_设计专业显示器,哪些参数重要?明基PD2700U显示器给你答案...

    掐指一算,马上就要到女神节了!很多男同胞们都在绞尽脑汁为女朋友挑选礼物,口红包包衣服这些想必大家都了解了,但其实送礼物还是要对症下药.如果你的女神是设计师,那这件礼物非常不错,是专业为设计师打造的,远 ...

  4. unity 多台 显示器 控制_AB罗克韦尔自动化Micro820可编程逻辑控制器系统型号及功能介绍...

    产品介绍 我们的 Micro820® 可编程逻辑控制器系统体积极小,专用于需要灵活通信和 I/O 功能的小型独立设备控制和远程自动化应用.这些控制器支持最多 36 个 I/O 点,具有以太网等众多嵌入 ...

  5. unity 多台 显示器 控制_LC1724:17寸24口CAT5 LED KVM控制平台

    LC1724 CAT5 LED KVM整合LED屏.超薄键盘.Touch Pad触摸鼠标板,集成在1U高度单元内,抽屉式安装方式,彻底解决空间不足的问题.采用高品质A级17"LED(无亮点) ...

  6. VC++编程实现多显示器控制(复制、横屏、纵屏,显示器个数)

    最近做了个三维的程序,部署到客户机器上,客户看了后,现场提出这样的一个需求:程序能智能探测接入的显示器个数,当有新的显示器接入时,现有的只在一个显示器上显示的三维场景能投递到新插入的显示器上显示.类似 ...

  7. unity 多台 显示器 控制_飞利浦292E2E评测丨宽屏显示器中的多面手

    ■本文来自中关村在线 屏幕比例21:9或32:9的显示器,因为横向屏幕很长,所以被人形象的称为带鱼屏.相较于16:9的显示器,屏幕拥有更多显示面积,我们能获得更多信息,大大提升办公效率.同时带鱼屏打游 ...

  8. kvm显示器怎么切换服务器,kvm切换器一套键盘鼠标显示器的热键切换方法教程

    kvm切换器,在我们的生活中已经是很常见的一种机器:当我们拥有多个信号源却只有一显示设备的时候,我们不妨考虑一下kvm切换器这种产品,一套键盘鼠标显示器控制多台计算机设备:它能让你省时更省力. 今天给 ...

  9. 三星M8 智能显示器 评测

    三星 M8 智能显示器,搭载 32 英寸 4K 屏,支持作为智能家居中控,以及云游戏. 三星M8 显示器的厚度为 11.4 毫米,三星称其厚度比之前的型号要薄得多.该显示器还配备磁吸的可移动 Slim ...

  10. 2021 显示器购买科普 10月更新 哪些品牌显示器值得入手?显示器面板选择 显示器相关参数解析...

    都 2021 年了: 你还不会自己给自己选择合适的显示器吗? 当需要购买显示器的时候还需要找朋友推荐吗? 还不知道在哪里买显示器能避免被坑吗? 重要‼️‼️:双十一 预售开始了,现在可先领红包,双十一 ...

最新文章

  1. ess用户名和密码_陈ess洁如何从摄影系学生转变为成功的自由职业者和内容创作者(播客)...
  2. AFDetV2:重新思考点云检测方法中第二阶段检测器的必要性(CVPR2021)
  3. ubuntu系统安装mysql(deb-bundle包)
  4. 【转】iPhone4清理垃圾文件的方法
  5. 码农何苦为难码农:谈谈程序员面试那些事
  6. Four ugliness
  7. java版数据结构解迷宫问题_C语言数据结构之迷宫问题
  8. springboot启动过程_spring5/springboot2源码学习 -- spring boot 应用的启动过程
  9. JAVA通过JCO连接SAP例子
  10. python的基本功能_二.Python的基本数据类型及常用功能
  11. C#OOP之一面向对象简介
  12. Python中表达式int('0x10, 36)的值是。。。
  13. sshd iptable 傻瓜配置
  14. SAP 修改字段长度
  15. Verilog实现38译码器
  16. 谷歌浏览器 如何设置在新标签页打开链接
  17. HTTP/2和HTTP/3
  18. 数字电视电子节目指南(EPG)的实现原理分析
  19. 【菜鸟学Python】爬取果壳问答
  20. Python学习笔记(八)爬虫基础(正则和编解码)

热门文章

  1. MySQLworkbench中PK,NN,UQ意思详解
  2. mysql workbench中PK,NN,UQ,BIN,UN,ZF,AI字段类型标识说明
  3. Windows 桌面应用开发入门
  4. 计算机专业外出交流方案,公开学院计算机系外出考察方案.doc
  5. OPENWRT-LUCI开发总结-LUCI目录结构介绍
  6. stm32项目实战ST7735环境质量检测仪
  7. Elasticsearch索引yellow修复
  8. 计算机 不识u盘,电脑不认u盘了怎么办?
  9. 无卷积结构(那就纯ransformer)的参考图像分割:ReSTR: Convolution-free Referring Image Segmentation Using Transformers
  10. 闲鱼最新选品技巧,快速帮你找到爆款!