标签 8086CPU 下的文章

(以下内容将作为汇编教程系列的前言)
(阅读本教程系列如果有问题可直接于评论区提出,我长期提供免费解答)

一点感想
先说说我个人学习汇编语言的经历。首先,我是个怀旧的人,因而同时也对于那些古旧的技术心存向往。学习完C语言的指针之后,我希望更彻底的了解计算机底层原理,于是就决定学习汇编。我没有老师,身边的人也没有懂汇编的,当时就在淘宝上买了一本排在首位的王爽的《汇编语言》,开始自学起来。
当时我还是初一。看完书的简介和第一章节,觉得应该不很难,于是下了决心每周末啃一点,把整本书过掉。当时的我还是太天真了。到了第二章节,由于我没有系统的学习过计算机底层架构,到了CPU工作过程那里直接晕了,之后的内容也是看的云里雾里的,本站那篇《汇编中栈的原理》基本上就是直接抄书写的的。
当时觉得真的啃不下去了,又没人可以问,直接放弃。之后的一年时间里,每次心中重燃不死的希望,都去啃一点,居然一节节啃下来了!然后就是八下,竞赛冲刺。到了25年暑假,一气读完全书!到现在,就是复读、加深的过程。
个人对王爽《汇编语言》的一些认识:首先汇编语言不适合编程初学者,至少得会一门C/Pascal之类较底层的高级语言,才能去学习该书。这本书写的其实挺好的,包括作者提出的所谓“知识屏蔽”我也赞同,书中也充分体现了这一点。但是我觉得该书更适合作为老师上课的教材,对像我一样自学的人来说,前面部分对于8086CPU的底层原理介绍很生硬,需要长时间消化才能读懂。
我写这系列的教材,大体框架遵循了该书,但有些地方也根据我自认为更适合理解的方式进行了调换,目的在于更适合有C语言基础的人阅读。教程中部分需要专业术语的地方,直接用了书中原话,不再一一指出。

汇编语言简介
学习汇编语言,需要明白它作为“低级语言”,到底“低级”在什么地方。汇编语言,是一门直接对硬件编程的,也就是说,它的每一条指令(不包括伪指令,那是给编译器看的,类似高级语言中的宏),都是直接操纵硬件。计算机本质上是一堆电路,汇编语言的每一条指令所代表的功能,都代表了一块电路实现的功能。比如,mov ax,2表示将ax寄存器(寄存器是CPU中的一个电子元件,一个寄存器有16位,每一位能够以高低电平的形式存储一个1或0)的值改为2,即二进制下的0000000000000010,可以看出,直接改变了电平高低。所以,对于其中每一条指令的功能,不要有任何想法,它这个功能是由它对应的电路实现的,我们编程者并不能改变,只能使用。不要想着修改它,也不要提出“为什么一个内存单元是8位?”、“为什么不能直接改变段寄存器的值?”等问题,答案只有一个——电路就是这样接的。如果你真的想改变它的功能,请修改CPU的电路。
同时,汇编语言没有高级语言中那些抽象出来的“函数”“变量”或者“对象”这些概念,你可以操作的只有:CPU、内存、键盘、显示器等物理意义上的硬件。高级语言中的这些概念最终也是在汇编语言的基础上抽象得到,要实现这些概念,到最后仍然依赖于最底层的汇编语言。不过你可以自己在学汇编时思考高级语言中的这些概念可以如何通过汇编实现,这对于理解硬件原理帮助很大。

前言&端口简介
(本文有点长,相比之前的内容也更有意思,请耐心食用~)
电脑作为一个由各零件拼凑成的整体,以CPU为核心,管理其他的硬件。端口,就是联系CPU与其他硬件的一个中间规范。各硬件把功能封装好,CPU通过端口访问,实现交互。本文以CMOS RAM为例,介绍端口的使用方法。

in指令和out指令
在8086CPU中,只有in指令和out指令这两条,能够对端口进行操作,其使用格式如下:
in指令(从端口读入数据)

in al,端口号(若为0~255号直接用常数指明,若为256~65535号则先将号码放在dx中,再在此处填写dx);本指令用于访问8位端口
in ax,端口号(同上);本指令用于访问16位端口

out指令(输出数据到端口)

out 端口号(同上),al;本指令用于访问8位端口
out 端口号(同上),ax;本指令用于访问16位端口

需要注意的是,在使用in指令和out指令时,读入和读出的数据都存放在固定寄存器中(8位端口对应al,16位端口对应ax),如果改动会出错。

CMOS RAM简介
CMOS RAM是一种芯片,存放了电脑中的时间信息及其他一些系统配置,其中有一块128字节的RAM可以读取。其只有70h和71h两个端口。我们作为初学者可以用它作为端口学习的一个简单实例,今天我们写一个小程序,用端口读入其时间并显示在屏幕上。下面说明其使用:
70h:地址端口,存放要访问的CMOS RAM单元的地址。
71h:数据端口,存放70h端口中指定地址的数据。
由此可见,我们如果要读取CMOS RAM的x(x为0~255的正整数)号单元,只需要先将x送入端口70h,再从71h读取结果即可。
在CMOS RAM中,秒、分、时、日、月、年信息依次存放在0、2、4、7、8、9单元中。用BCD码的形式存放,即用8位二进制分割为两个4位,高4位存十位,低4位存个位。例如,00010100b表示14。

如何输出内容到屏幕上
今天我们即将写出第一个输出结果到屏幕上的程序~下面介绍显示缓冲区:
在80*25彩色字符模式下,内存中B8000h~BFFFFh为显示缓冲区,分为8页,通常情况下显示第0页,即B8000h~B8F9Fh中的字符,显示时会将显示缓冲区中的内容转为ASCLL码后输出。
一个字符的信息存在一个字中,其中前一个字节放字符,后一个字节放属性
下面为一个属性字节所包含的信息:
位数(二进制位) 7 6 5 4 3 2 1 0

                  含义   闪烁  背景    背景    背景    高亮    前景    前景    前景

          对应颜色               红        绿        蓝                    红        红        红

例如:
红底绿字:01000010B 红底闪烁黄字:11000110B
紫底黑字:01010000B 蓝底高亮白字:01001111B
以此类推,可以根据光学三原色互相合成调出8种颜色。

移位指令
shl和shr为逻辑移位指令,其中shl为左移,shr为右移。功能类似C语言的<<及>>。使用格式:

shl/shr 原数据(存放在的寄存器名),移位量(0~8)。

程序编写
终于介绍完全部的知识了,下面整理一下思路:

  1. 从CMOS RAM中读出一个字节
  2. 将该字节以十进制输出(原数+30h)
  3. 重复以上步骤,并将其格式化为“年/月/日 时:分:秒”的格式(前面日期用红底白字,后面时间用黑底高亮黄字)

以下为代码:

assume cs:code
code segment
start:
      mov bx,0b800h
      mov es,bx
      mov cl,4
      
      mov al,9
      out 70h,al
      in al,71h
      mov ah,al
      shr ah,cl
      and al,00001111b
      add ah,30h
      add al,30h
      mov byte ptr es:[0],ah
      mov byte ptr es:[1],01000111b
      mov byte ptr es:[2],al
      mov byte ptr es:[3],01000111b
      
      mov al,2Fh
      mov byte ptr es:[4],al
      mov byte ptr es:[5],01000111b
      
      mov al,8
      out 70h,al
      in al,71h
      mov ah,al
      shr ah,cl
      and al,00001111b
      add ah,30h
      add al,30h
      mov byte ptr es:[6],ah
      mov byte ptr es:[7],01000111b
      mov byte ptr es:[8],al
      mov byte ptr es:[9],01000111b
      
      mov al,2Fh
      mov byte ptr es:[10],al
      mov byte ptr es:[11],01000111b
      
      mov al,7
      out 70h,al
      in al,71h
      mov ah,al
      shr ah,cl
      and al,00001111b
      add ah,30h
      add al,30h
      mov byte ptr es:[12],ah
      mov byte ptr es:[13],01000111b
      mov byte ptr es:[14],al
      mov byte ptr es:[15],01000111b
      
      mov al,4
      out 70h,al
      in al,71h
      mov ah,al
      shr ah,cl
      and ah,00001111b
      add ah,30h
      add al,30h
      mov byte ptr es:[20],ah
      mov byte ptr es:[21],00001110b
      mov byte ptr es:[22],al
      mov byte ptr es:[23],00001110b
      
      mov al,3Ah
      mov byte ptr es:[24],al
      mov byte ptr es:[25],00001110b
      
      mov al,2
      out 70h,al
      in al,71h
      mov ah,al
      shr ah,4
      and 00001111b
      add ah,30h
      add al,30h
      mov byte ptr es:[26],ah
      mov byte ptr es:[27],00001110b
      mov byte ptr es:[28],al
      mov byte ptr es:[29],00001110b
      
      mov al,3Ah
      mov byte ptr es:[30],al
      mov byte ptr es:[31],00001110b
      
      mov al,0
      out 70h,al
      in al,71h
      mov ah,al
      shr ah,cl
      and ah,00001111b
      add ah,30h
      add al,30h
      mov byte ptr es:[32],ah
      mov byte ptr es:[33],00001110b
      mov byte ptr es:[34],al
      mov byte ptr es:[35],00001110b
      
      mov ax,4c00h
      int 21h

code ends
end start

(没有用循环,导致出现了挺多冗余代码的,各位可以试试用循环+子程序简化程序~)

结语
本文为综合性较强的一课,同时因为有输出内容,相比之前的文章更有趣,希望读者能认真理解,我是faryou,下期见!

前言
在8086CPU中,有一个特殊的寄存器,用来存放各种指令产生的临时信息,它被称为flag,即标志寄存器。本文将介绍该寄存器的使用,在编程时应灵活使用其特性。

标志寄存器简介
标志寄存器可以视为几个单二进制位的寄存器所拼凑出来的一个寄存器(几个单二进制位的寄存器挤在同一个寄存器里面),存放一些指令执行时产生的附加