本期源码,欢迎star:
https://github.com/ZYKWLJ/assembly4-homework
1、题目
在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串 “welcome to masm!”。
编程所需的知识通过阅读、分析下面的材料获得:
80×25彩色字符模式显示缓冲区(以下简称为显示缓冲区)的结构:
内存地址空间中,B8000H~BFFFFH共32KB的空间,为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码值为41H,02H表示黑底绿色)
显示缓冲区里的内容为:

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

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

例:在显示器的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的重复,也就是我们上述的猜想:

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

好了,有了上面的铺垫,我们再来回顾本题,要求
在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串 “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 ; 行循环中包含了列循环!
;行循环结束

本系列将精讲Linux0.11内核中的每一个文件,共计会发布100+文章。
😉【Linux102】11-kernel/vsprintf.c
😉【Linux102】12-include/stdarg.h
😉【Linux102】14-kernel/system_call.s
😉【Linux102】15-include/linux/sched.h
😉【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】26-include/sys/wait.h
😉【Linux102】27-include/inux/tty.h
😉【Linux102】28-include/termios.h
😉【Linux102】30-include/sys/times.h
😉【Linux102】31-include/sys/utsname.h
😉【Linux102】32-include/stddef.h
😉【Linux102】33-include/linux/sys.h
😉【Linux102】36-include/asm/system.h
😉【Linux102】38-include/linux/fdreg.h
😉【Linux102】39-include/asm/io.h
😉【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
😉【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】56-kernel/chr_drv/tty_ioctl.c
和Linux内核102系列不同,本系列将会从全局描绘Linux内核的各个模块,而非逐行源码分析,适合想对Linux系统有宏观了解的家人阅读。
😉【Linux】Linux概述1-linux对物理内存的使用
本系列将带领大家从0开始循序渐进学习汇编语言,直至完全掌握这门底层语言。同时给出学习平台DOSBox的使用教程。
本系列将直击C语言的本质基础,流利处理出各个易错、实用的实战点,并非从零开始学习C。
关于小希
😉嘿嘿嘿,我是小希,专注Linux内核领域,同时讲解C语言、汇编等知识。
我的微信:C_Linux_Cloud,期待与您学习交流!

加微信请备注哦
小希的座右铭:
别看简单,简单也是难。别看难,难也是简单。我的文章都是讲述简单的知识,如果你喜欢这种风格:
下一期想看什么?在评论区留言吧!我们下期见!

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

被折叠的 条评论
为什么被折叠?



