【汇编作业】-9-在屏幕中间显示文字

汇编实现屏幕居中显示文字

公粽号「专注Linux」,专注Linux内核开发

本期源码,欢迎star:
https://github.com/ZYKWLJ/assembly4-homework

1、题目

屏幕中间分别显示绿色绿底红色白底蓝色的字符串 “welcome to masm!”。


编程所需的知识通过阅读、分析下面的材料获得:

80×25彩色字符模式显示缓冲区(以下简称为显示缓冲区)的结构:

内存地址空间中,B8000H~BFFFFH32KB的空间,为80×25彩色字符模式的显示缓冲区。向这个地址空间写入数据,写入的内容将立即出现在显示器上。(这就是原理,至于为什么,自己不必问,底层就是做好这样的)。

在80×25彩色字符模式下,显示器可以显示25行每行80个字符,每个字符可以有256种属性(背景色、前景色、闪烁、高亮等组合信息)。

这样,一个字符在显示缓冲区中就要占两个字节,分别存放字符的ASCII码属性。80×25模式下,一屏的内容在显示缓冲区中共占4000个字节。显示缓冲区分为8页每页4KB(≈4000B),显示器可以显示任意一页的内容。一般情况下,显示第0页的内容。也就是说通常情况下,B8000H~B8F9FH中的4000个字节的内容将出现在显示器上。
一页显示缓冲区中:

偏移000~09F对应显示器上的第1行(80个字符占160个字节);

偏移0A0~13F对应显示器上的第2行;

偏移140~1DF对应显示器上的第3行;

依此类推,可知,偏移F00~F9F对应显示器上的第25行。
在一行中,一个字符占两个字节的存储空间(一个字),低位字节存储字符的ASCII码,高位字节存储字符的属性。 一行共有80个字符,占160个字节。

即在一行中:

00~01单元对应显示器上的第1列

02~03单元对应显示器上的第2列

04~05单元对应显示器上的第3列

依此类推,可知,9E~9F单元对应显示器上的第80列。

例:在显示器的0行0列显示黑底绿色的字符串’ABCDEF’
('A’的ASCII码值为41H02H表示黑底绿色)
显示缓冲区里的内容为:

可以看出,在显示缓冲区中,偶地址存放字符,奇地址存放字符的颜色属性。

一个在屏幕上显示的字符,具有前景(字符色)和背景(底色) 两种颜色,字符还可以以高亮度闪烁的方式显示。前景色、背景色、闪烁、高亮等信息被记录在属性字节中。

可以按位设置属性字节,从而配出各种不同的前景色和背景色。
比如:

例:在显示器的0行0列显示红底高亮闪烁绿色的字符串’ABCDEF’
(红底高亮闪烁绿色,属性字节为:11001010B,CAH)
显示缓冲区里的内容为:

注意,闪烁的效果必须在全屏DOS方式下才能看到。

2、分析

首先我们明确,这里的"welcome to masm!"一共有16个字符,
因为我们的屏幕缓冲区一共有25rows*80columns:

1.定位行列

所以最中间的行是13行,所以这三行字符串分别在12、13、14行;


因为16个字符,2x+16=80,x=32,所以每行字符前面空出32个空字符,所以,每行字符应该是以该行的第33个字符,序号为32开始的。

2.定位字符写入内存中的位置

因为内存地址空间中,B8000H~BFFFFH共32KB的空间,为80×25彩色字符模式的显示缓冲区。又由题意可知,每行共80字符,也即160字节,那么第一行逻辑地址理所当然为:B8000H~B809FH,对应的分段地址和偏移地址为:
B800:00~B800:9F.所以,如果想要给第一行所有内容写入1,并且对应红底绿字,那么,对应的数据为:

如图,已知数字1的ascii为十进制的49,十六进制的31。红底闪烁绿字对应字节为11000010B,即十进制的128+64+2=194,十六进制的C2.

31 C2 31 C2 31 C2 31 C2 ......31 C2
;一共有80个`31 C2`循环

对应编码为:

;编程:第一行全部显示红底绿字闪烁的'1'
assume cs:codesg
    
codesg segment 
start:	
    ;从B800:00-B800:9F,一直循环80次,一行共80个字符
    mov cx, 80      ;十进制的80
    mov ax, 0B800h   ;基地址
    mov es, ax       ;es指向显存
    mov bx, 160     ;第二行开始显示,所以这里改为第二行的偏移地址160,因为会向上滑动,导致第一行看不见,所以这里实际上第二行就是第一行!
    
    mov ah, 11000010b ;红底绿字属性:11000010b
    mov al, 49      ;设置字符为'1',十进制的49
    
s0:
    mov es:[bx], ax  ;将字符和属性写入显存
    add bx, 2        ;每个字符占用两个字节
    loop s0
    
    mov ax, 4c00h 
    int 21h 
codesg ends 
end start 

成功显示(最上面的一行就是红底绿字的1):

再次皮一点,直接将满屏改为1,就像小广告,闪亮你的眼!
只需要将上述的mov cx, 80改为mov cx, 80*25

现在,我们用dehug查看是不是08B00开始的4000个字节全是31 C2的重复,也就是我们上述的猜想:

记住,一定是要在刚刚运行程序后,再查看,因为此时屏幕上显示的全部是目标内容,此时才正确!如果已经运行后,或者进行了其他操作,导致屏幕上出现其他内容,那么查询显存也会有偏差,这就是显存——“实实际际显示在屏幕上的内容”的魅力

**显存的本质直击!**显示在屏幕上的内容!

屏幕上进行了其他操作,再次查看时,发现更多的是乱码,但是也有一些目的字符,这是因为我们的屏幕上一共可以有4000字节,也就是约4KB,这上面目前还是有1的,所以能查看到


好了,有了上面的铺垫,我们再来回顾本题,要求

在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串 “welcome to masm!”。

无非是一个二维数组操作,会有两次循环罢了。

3、源码

;编程:在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串'welcome to masm!'
assume cs:codesg, ds:datasg
datasg segment 
    db 'welcome to masm!'    ; 字符串数据
    db 02h                   ; 绿色字(无背景)
    db 41h                   ; 绿底红字  
    db 14h                   ; 白底蓝字
datasg ends 
  
codesg segment
start:
    mov ax, datasg
    mov ds, ax              ; ds指向数据段
    mov ax, 0B800h
    mov es, ax              ; es指向显存
  
    ; 从第12行开始显示(屏幕中间)
    mov bx, 160 * 12 + 64   ; 第12行,第32列开始
    mov si, 0               ; 字符串偏移
    mov di, 16              ; 颜色属性偏移(从第一个颜色开始)
    mov cx, 3               ; 显示3行
; 行循环
row:
    push cx                 ; 保存行循环计数器
    push bx                 ; 保存当前行起始位置
    mov cx, 16              ; 字符串长度
    mov ah, [di]            ; 获取颜色属性
; 列循环
column:
    mov al, [si]            ; 获取字符
    mov es:[bx], ax         ; 将字符和属性写入显存
    add bx, 2               ; 每个字符占用两个字节
    inc si                  ; 下一个字符
    loop column             ; 至此,一行显示完成
;本行的列循环结束
    pop bx                  ; 恢复行起始位置
    add bx, 160             ; 下一行
    pop cx                  ; 恢复行循环计数器
    inc di                  ; 下一个颜色属性
    xor si, si              ; 重置字符串偏移(回到开头)
    loop row                ; 行循环中包含了列循环!
;行循环结束
    mov ax, 4c00h 
    int 21h 
codesg ends
end start  

源码图像

结果:

显示成功

4、总结

1、常量数据放在一块

这里的

datasg segment 
    db 'welcome to masm!'    ; 字符串数据
    db 02h                   ; 绿色字(无背景)
    db 41h                   ; 绿底红字  
    db 14h                   ; 白底蓝字
datasg ends 

就是将所有的数据常量放在一起了,这相当于const,方便我们直接操作。学习这种表达形式!

2.双重循环充分利用cx+堆栈

这里就是显然的双重循环,命名也很直接,同时通过push\pop命令充分保存不同阶段的cx值。

同时,双重循环就是采用先行后列的方式进行!记住了先行后列+堆栈CX!

; 行循环
row:
    push cx                 ; 保存行循环计数器
    push bx                 ; 保存当前行起始位置
    mov cx, 16              ; 字符串长度
    mov ah, [di]            ; 获取颜色属性

; 列循环
column:
    mov al, [si]            ; 获取字符
    mov es:[bx], ax         ; 将字符和属性写入显存
    add bx, 2               ; 每个字符占用两个字节
    inc si                  ; 下一个字符
    loop column             ; 至此,一行显示完成

;本行的列循环结束
    pop bx                  ; 恢复行起始位置
    add bx, 160             ; 下一行
    pop cx                  ; 恢复行循环计数器
    inc di                  ; 下一个颜色属性
    xor si, si              ; 重置字符串偏移(回到开头)
    loop row                ; 行循环中包含了列循环!
;行循环结束  


Linux102系列

本系列将精讲Linux0.11内核中的每一个文件,共计会发布100+文章。


0.一些辅助文件

😉【Linux102】1-Makefile

😉【Linux102】2-Makefile.header

😉【Linux102】3-system.map


1.内核引导启动程序

😉【Linux102】4-bootsect.s

😉【Linux102】5-setup.s

😉【Linux102】6-head.s

😉【Linux102-D】/boot


2.内核初始化过程

😉【Linux102】7-main.c


3.进程调度与系统调用

😉【Linux102】8-kernel/asm.s

😉【Linux102】9-kernel/traps.c

😉【Linux102】10-kernel/printk.c

😉【Linux102】11-kernel/vsprintf.c

😉【Linux102】12-include/stdarg.h

😉【Linux102】13-kernel/mktime.c

😉【Linux102】14-kernel/system_call.s

😉【Linux102】15-include/linux/sched.h

😉【Linux102】16-kernel/sched.c

😉【Linux102】17-kernel/signal.c

😉【Linux102】18-include/signal.h

😉【Linux102】19-include/sys/types.h

😉【Linux102】20-include/linux/kernel.h

😉【Linux102】21-include/asm/segment.h

😉【Linux102】22-include/linux/head.h

😉【Linux102】23-include/linux/mm.h

😉【Linux102】24-include/linux/fs.h

😉【Linux102】25-include/errno.h

😉【Linux102】26-include/sys/wait.h

😉【Linux102】27-include/inux/tty.h

😉【Linux102】28-include/termios.h

😉【Linux102】29-kernel/panic.c

😉【Linux102】30-include/sys/times.h

😉【Linux102】31-include/sys/utsname.h

😉【Linux102】32-include/stddef.h

😉【Linux102】33-include/linux/sys.h

😉【Linux102】34-kernel/sys.c

😉【Linux102】35-kernel/fork.c

😉【Linux102】36-include/asm/system.h

😉【Linux102】37-kernel/exit.c

😉【Linux102】38-include/linux/fdreg.h

😉【Linux102】39-include/asm/io.h


4.输入输出系统--块设备驱动程序

😉【Linux102】40-kernel/blk_drv/blk.h

😉【Linux102】41-kernel/blk_drv/hd.c

😉【Linux102】42-include/linux/config.h

😉【Linux102】43-include/linux/hdreg.h

😉【Linux102】45-kernel/blk_drv/ramdisk.c

😉【Linux102】46-include/asm/memory.h

😉【Linux102】47-include/string.h

😉【Linux102】48-kernel/blk_drv/floppy.c


5.输入输出系统——字符设备驱动程序

😉【Linux102】49-kernel/chr_drv/keyboard.S

😉【Linux102】50-kernel/chr_drv/console.c

😉【Linux102】51-kernel/chr_drv/serial.c

😉【Linux102】52-kernel/chr_drv/rs_io.s

😉【Linux102】53-kernel/chr_drv/tty_io.c

😉【Linux102】54-include/ctype.h

😉【Linux102】55-lib/ctype.c

😉【Linux102】56-kernel/chr_drv/tty_ioctl.c


6.文件系统

😉【Linux102】58-fs/buffer.c

😉【Linux102】59-fs/bitmap.c

😉【Linux102】60-fs/inode.c

😉【Linux102】61-fs/super.c

😉【Linux102】63-fs/file_table.c

😉【Linux102】64-fs/block_dev.c

😉【Linux102】90-include/fcntl.h

😉【Linux102】75-include/a.out.h

😉【Linux102】65-fs/file_dev.c

😉【Linux102】66-fs/pipe.c

😉【Linux102】67-fs/char_dev.c

😉【Linux102】68-fs/read_write.c

😉【Linux102】69-fs/truncate.c

😉【Linux102】70-fs/open.c

😉【Linux102】71-fs/exec.c

😉【Linux102】72-fs/stat.c

😉【Linux102】73-fs/fcntl.c

😉【Linux102】74-fs/ioctl.c


7.内存管理模块

😉【Linux102】77-mm/memory.c

😉【Linux102】78-mm/page.s


8.内核库文件

😉【Linux102】95-lib/dup.c

😉【Linux102】88-lib/write.c

😉【Linux102】87-lib/wait.c

😉【Linux102】86-lib/string.c

😉【Linux102】85-lib/setsid.c

😉【Linux102】84-lib/open.c

😉【Linux102】81-lib/errno.c

😉【Linux102】80-lib/close.c

😉【Linux102】79-lib/_exit.c

😉【Linux102】55-lib/ctype.c


Linux内核精讲系列

和Linux内核102系列不同,本系列将会从全局描绘Linux内核的各个模块,而非逐行源码分析,适合想对Linux系统有宏观了解的家人阅读。

😉【Linux】学习Linux前必备的知识点

😉【Linux】linux0.11操作系统概述

😉【Linux】Linux内核对进程的内存抽象

😉【Linux】Linux概述1-linux对物理内存的使用

😉【Linux】软件从写下到运行的全部流程

😉【Linux】CPU的三寻址:实模式、保护模式、长模式

😉【Linux】实模式与保护模式的寻址, 这次讲明白了!

😉【Linux】linux0.11的源码目录架构

😉【Linux】Makefile机制及基础详解

😉【Linux】编译并运行Linux0.11

😉【Linux】“进进内网文”—Linux的内核结构全貌

😉【Linux】linux的中断机制

😉【Linux】linux进程描述


汇编语言

本系列将带领大家从0开始循序渐进学习汇编语言,直至完全掌握这门底层语言。同时给出学习平台DOSBox的使用教程。

😉【汇编语言】1—基础硬件知识

😉【汇编语言】2—寄存器基础知识

😉【汇编语言】3-寄存器与内存的交互

😉【汇编语言】4-第一个完整的汇编程序

😉【汇编语言】5-[BX]和loop指令

😉【汇编语言】6-包含多个段的程序

😉【汇编语言】7-灵活的5大寻址方式

😉【汇编语言】8-1-数据的位置

😉【汇编语言】8-2-数据的长度

😉【汇编语言】8-数据处理的两个基本问题(位置与长度)

😉【DOSBox】1-debug

😉【DOSBox】2-debug可执行文件

😉【DOSBox】3-完整开发流程


C语言

本系列将直击C语言的本质基础,流利处理出各个易错、实用的实战点,并非从零开始学习C。

😉【C语言】GDB详解大全

😉【C语言】C Token(C89 C99 C11)

😉【C语言】指针基础

😉【C语言】数组基础

😉【C语言】结构体对齐

😉【C语言】华为C语言进阶测试

😉【C语言】触发刷新到磁盘的方式总结

😉【C语言】C语言文件操作的mode详解

😉【C语言】C语言文件知识全讲解

😉【C语言】从extern到头文件包含的最优实践

😉【C语言】C语言的关键字与重载机制

😉【C语言】长字符串的2种处理方式

😉【C语言】C语言嵌入汇编程序

😉【C语言】指针数组 VS 数组指针 原来这么简单!

😉【C语言】深浅拷贝、传参、赋值 本质剖析

😉【C语言】find-in-linux递归搜索文件名函数

😉【C陷阱与缺陷】-1-词法陷阱

😉【C陷阱与缺陷】-2-语法陷阱

😉【C陷阱与缺陷】-3-语义陷阱


关于小希

😉嘿嘿嘿,我是小希,专注Linux内核领域,同时讲解C语言汇编等知识。

我的微信:C_Linux_Cloud,期待与您学习交流!

加微信请备注哦


小希的座右铭:别看简单,简单也是难。别看难,难也是简单。我的文章都是讲述简单的知识,如果你喜欢这种风格:

不妨关注、评论、转发,让更多朋友看到哦~~~🙈

下一期想看什么?在评论区留言吧!我们下期见!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值