操作系统OS-Lab1-100位大数除法NASM实现

OS-Lab1

问题答案

1、8086的五类寄存器,分别举例说明其作用

在这里插入图片描述

Intel 8086 CPU 有五种类型的寄存器,分别是通用寄存器、指针寄存器、段寄存器、标志寄存器和指令指针寄存器。

通用寄存器:通用寄存器用于存储数据和计算,共有 8 个,分别是 AX、BX、CX、DX、BP、SP、SI 和 DI。其中 AX、BX、CX 和 DX 是 16 位寄存器,可以存储 16 位的数据。BP、SP、SI 和 DI 是 16 位的指针寄存器,用于存储内存地址。

指针寄存器:指针寄存器用于存储内存地址,共有 4 个,分别是 BP、SP、SI 和 DI。BP 和 SP 分别用于存储堆栈底部和栈顶的地址,SI 和 DI 用于存储数据在数组中的地址。

段寄存器:段寄存器用于存储内存中的段地址,共有 4 个,分别是 CS、DS、ES 和 SS。CS 用于存储代码段的地址,DS 用于存储数据段的地址,ES 用于存储附加数据段的地址,SS 用于存储堆栈段的地址。

标志寄存器:标志寄存器用于存储 CPU 的状态标志,共有 16 位,可以分为多个标志位。其中常用的标志位包括 CF(进位标志)、ZF(零标志)、SF(符号标志)、OF(溢出标志)等。

指令指针寄存器:指令指针寄存器用于存储下一条要执行的指令的地址,共有 IP 和 CS 两个寄存器。IP 存储当前指令在代码段中的偏移地址,CS 存储当前代码段的地址。

举例说明:

AX 寄存器:可以用来存储 16 位整数,也可以拆分成两个 8 位寄存器 AH 和 AL,分别用于存储高 8 位和低 8 位数据。

BP 寄存器:用于存储当前堆栈底部的地址,通常在函数调用时使用。

DS 寄存器:用于存储数据段的地址,可以通过修改 DS 寄存器来访问不同的数据段。

CF 标志位:用于存储进位标志,当进行加法运算时,如果结果超出了寄存器的位数,CF 标志位就会被设置为 1。

IP 寄存器:用于存储下一条要执行的指令的偏移地址,可以通过修改 IP 寄存器来实现跳转。

在这里插入图片描述

2、段寄存器?作用?
NASM 支持以下四个段寄存器:

CS:存储代码段的段地址。
DS:存储数据段的段地址。
SS:存储堆栈段的段地址。
ES:存储附加数据段的段地址。
在实模式下,每个段寄存器可以指向 64 KB 的内存空间。通过组合段寄存器和通用寄存器,可以访问内存中的任意数据。

段寄存器的作用是将内存地址分成段和偏移两个部分来进行寻址。在实模式下,每个内存地址由一个 20 位的段地址和一个 16 位的偏移地址组成,总共可以寻址 1MB 的内存空间。通过不同的段寄存器来存储不同的段地址,程序可以访问不同的内存区域。
3、什么是寻址?8086寻址方式
寻址是指 CPU 访问内存中某个数据的过程。在计算机中,每个内存单元都有一个唯一的地址,CPU 可以通过地址来访问内存中的数据。寻址方式是指 CPU 访问内存时使用的地址计算方式。

Intel 8086 CPU 支持多种寻址方式,包括以下几种:

立即寻址(Immediate Addressing):直接将常数或变量的地址作为操作数,例如 mov ax, 1234h。

直接寻址: MOV AX [1234H] 直接给出了地址1234H,用[]符号取数

寄存器寻址(Register Addressing):将寄存器中的值作为操作数,例如 mov ax, bx。

寄存器间接寻址(Register Indirect Addressing):使用寄存器中存储的地址作为操作数,例如 mov ax, [bx]。

寄存器相对寻址: MOV AX [SI+3]。

基址加变址寻址(Base plus Index Addressing):将基址寄存器、变址寄存器和偏移量相加得到操作数的地址,例如 mov ax, [bx+si+10].

相对基址加变址 :MOV AX [BX+DI+3]
4、主程序和子程序如何传参?
在 NASM(Netwide Assembler)汇编语言中,主程序和子程序之间可以使用寄存器来传递参数。以下是一些常用的寄存器和它们对应的参数传递方式:

利用约定的地址传递参数

利用寄存器传递参数
• 缺点:能传递的参数有限,因为寄存器有限
AX、BX、CX、DX 寄存器:可以用于传递 16 位的整数参数。主程序将参数存储在寄存器中,然后调用子程序,并在子程序中使用相应的寄存器来获取参数。

SI、DI 寄存器:可以用于传递指针类型的参数。主程序将指针存储在寄存器中,然后调用子程序,并在子程序中使用相应的寄存器来获取指针。

BP 寄存器:可以用于传递参数和局部变量。在子程序中,BP 寄存器通常用作栈帧指针,指向当前子程序的栈帧底部。主程序可以将参数存储在栈中,并将 BP 寄存器指向参数所在的栈位置,然后调用子程序,在子程序中使用 BP 寄存器来获取参数。

利用堆栈传递参数(常用)
Stack:可以用于传递多个参数和大型数据结构。主程序将参数依次压入栈中,并在调用子程序时将栈顶指针传递给子程序。在子程序中,可以使用栈指针来获取参数。
5、boot.asm中 org 07c00h作用,如果去掉,程序应该怎么修改?
org 07c00h 指令的作用是告诉编译器程序将会被加载到内存的哪个地址,即告诉编译器该程序的起始地址是 07c00h。通过这个指令,编译器将会生成相应的二进制文件,以便在程序被加载到内存中时正确地执行。

如果去掉 org 07c00h 指令,程序需要做出以下修改:

修改程序的入口地址。由于没有指定起始地址,编译器将会使用默认的起始地址。因此,程序需要将入口地址调整为默认的起始地址,通常是 0000h。

修改程序中的跳转地址。由于程序的起始地址发生了变化,程序中的跳转地址也需要做相应的修改,以便程序能够正确地执行。
修改(PPT):
修改boot1.asm
mov ax,cs
mov ds,ax
mov es,ax
call DispStr
jmp $
DispStr:
mov ax,BootMessage +  07c00h
mov bp,ax
mov cx,16
mov ax,01301h
mov bx,000ch
mov dl,0
int 10h
ret
BootMessage: db "Hello, OS world!"
times 510-($-$$) db 0
dw 0xaa55
6、int 10h功能
在 NASM 中,int 10h 是一种 BIOS 中断,用于控制和管理屏幕的显示。int 10h 可以通过 AH 寄存器来指定要执行的功能号,以及通过其他寄存器来传递参数。以下是一些常用的功能号:

0x00:设置显示模式
0x02:设置光标位置
0x03:获取光标位置
0x05:清屏
0x06:滚屏
0x0e:在光标位置打印字符
0x0f:获取当前显示页面
以下是一些常用的寄存器和它们对应的参数:

AH:指定要执行的功能号
AL:传递字符或颜色信息
BH:传递页面号
CH、CL:传递光标位置的行号和列号
DH、DL:传递光标位置的行号和列号
7、boot.asm中times 510-( − - $) db 0作用
在 Bootloader 的汇编代码中,times 510-($-$$) db 0 指令的作用是填充二进制文件的末尾,使得整个文件大小达到 512 字节。

在 x86 架构中,Bootloader 通常被放置在硬盘的第一个扇区,即第 0 磁头、第 0 磁道、第 1 扇区。这个扇区的大小是 512 字节。当计算机启动时,BIOS 会将这个扇区加载到内存的 0x7c00 地址处,并将控制权交给这个扇区的代码。

因此,在编写 Bootloader 时,必须确保生成的二进制文件大小为 512 字节。如果生成的二进制文件大小小于 512 字节,那么 BIOS 加载这个文件时可能会发生错误。因此,如果代码大小不足 512 字节,需要在文件末尾填充一些字节,使得文件大小达到 512 字节。

$:表示当前行的地址或偏移量。也就是说,在代码中使用$时,它会被替换为当前行的地址或偏移量。

$$:表示当前模块的起始地址或偏移量。也就是说,在代码中使用$$时,它会被替换为当前模块的起始地址或偏移量。

times 510-($-$$) db 0 指令的作用是在当前位置填充 0,使得当前位置到文件末尾的空间大小为 510 字节。这个指令使用了一个特殊符号 $,表示当前位置的地址,以及一个特殊符号 $$,表示当前段的起始地址。因此,$-$$ 表示当前位置距离段的起始地址的偏移量。510-($-$$) 表示需要填充的字节数,即文件末尾距离当前位置的偏移量。最后,db 0 表示要填充的字节内容为 0。
PPT上的解释
当BIOS自检结束后会根据启动选项设置去选择启动设备,即检测软盘的第0磁头第0磁道第1扇区,是否以数值0x55和0xaa两字节作为结尾。如果是,那么BIOS就认为这个扇区是一个引导扇区,进而把此扇区的数据复制到物理内存地址0x7c00处,随后将处理器的执行权移交给这段程序(跳转至0x7c00地址处执行)
8、bochsrc中各参数含义
megs:32 
display_library: sdl2
floppya: 1_44=a.img, status=inserted 
boot: floppy
* display_library:Bochs使用的GUI库 
* megs:虚拟机内存大小 (MB) 
* floppya:虚拟机外设,软盘为a.img文件 
* boot:虚拟机启动方式,从软盘启动
* bochsrc与a.img以及boot.bin放在同一目录下
其中status是虚拟软盘镜像的状态,inserted的意思是已将虚拟软盘插入到虚拟软盘驱动器中。
9、boot.bin应该放在软盘的哪一个扇区?为什么
在将 Bootloader 代码写入软盘时,通常将生成的二进制文件 boot.bin 放在软盘的第一个扇区,即磁头 0、磁道 0、扇区 1 的位置。这是因为在 x86 架构的计算机中,BIOS 会在初始化硬件后,将软盘的第一个扇区加载到内存地址 0x7c00 处,并将控制权交给这个扇区的代码。因此,如果 Bootloader 的代码不在软盘的第一个扇区,BIOS 将无法正确加载并执行 Bootloader。

需要注意的是,软盘的第一个扇区除了 Bootloader 代码外,还需要存储一些引导扇区标识和分区表等信息。其中,引导扇区标识是 16 位的固定值 0xaa55,用于标识这个扇区是一个引导扇区。分区表则是用来记录硬盘的分区信息的。因此,在将 Bootloader 写入软盘的第一个扇区时,需要确保代码大小不超过 510 字节,以便为引导扇区标识和分区表留出 2 个字节的空间。
10、为什么不让boot程序直接加载内核,而是先加载Loader再加载内核?
处理器的实模式和保护模式切换问题:在操作系统启动过程中,处理器需要从实模式切换到保护模式。这个过程需要进行一些复杂的设置和初始化,而 Bootloader 通常只能以实模式运行。为了避免在 Bootloader 中处理这些复杂的操作,通常会编写一个 Loader 程序来完成这个任务。Loader 程序通常能够在实模式和保护模式之间进行平滑的切换,并负责加载内核。

内核的大小和复杂性:操作系统的内核通常比 Bootloader 更大和复杂,因此需要更复杂的加载和初始化过程。为了简化操作系统启动流程,通常会将内核划分为多个部分,每个部分都由 Loader 加载和初始化。

启动参数的传递:在启动操作系统时,通常需要将一些参数传递给内核,例如系统配置信息、启动选项、硬件信息等。为了实现这个功能,Bootloader 需要将这些参数传递给 Loader,然后 Loader 再将这些参数传递给内核。

因此,为了简化操作系统启动过程,通常会采用 Bootloader-Loader-Kernel 的架构。在这个架构中,Bootloader 负责加载 Loader,Loader 负责处理保护模式的初始化和加载内核的任务,内核则负责完成操作系统的启动和运行。这种架构能够有效地分离操作系统启动过程中的不同任务,并且使得整个启动过程更加简单和可维护。
书上的答案:
一个操作系统从开机到开始运行,大致经历“引导→加载内核入内存→跳入保护模式→开始执行内核”这样一个过程。

也就是说,在内核开始执行之前不但要加载内核,而且还有准备保护模式等一系列工作,如果全都交给引导扇区来做,512字节很可能是不够用的,所以,不妨把这个过程交给另外的模块来完成,我们把这个模块叫做Loader。

引导扇区负责把 Loader加载入内存并且把控制权交给它,其他工作放心地交给Loader来做,因为它没有512字节的限制,将会灵活得多。
11、Loader作用?
启动流程:
boot.bin --> loader.bin ---> loader.bin --> kernel.bin -->kernel
跳⼊保护模式
启动内存分⻚ 
从kernel.bin中读取内核,并放⼊内存,然后跳转到内核所在 的开始地址,运⾏内核 
跟boot类似,使⽤汇编直接在软盘下搜索kernel.bin 
但是,不能把整个kernel.bin放在内存,⽽是要以ELF⽂件的格式读取并提取代码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hSSdGecC-1683622650620)(./img/image-20230406143738643.png)]

12、Kernel作用?
在计算机操作系统中,Kernel(内核)是操作系统的核心部分,负责管理计算机的硬件和软件资源,并提供各种系统服务和功能。Kernel 的主要作用如下:

硬件管理:Kernel 负责管理计算机的硬件资源,包括 CPU、内存、磁盘、网络设备等。Kernel 通过硬件驱动程序与硬件交互,控制硬件的访问和使用,保证各个设备的协调工作,提供稳定和高效的系统性能。

进程管理:Kernel 负责管理计算机中运行的进程,包括进程的创建、销毁、调度、通信等。Kernel 通过进程调度算法,决定哪些进程可以获得 CPU 的使用权,保证系统资源的合理分配和利用。

内存管理:Kernel 负责管理计算机中的内存资源,包括内存的分配、释放、保护、映射等。Kernel 通过虚拟内存管理机制,将物理内存划分为若干个虚拟内存页,实现进程之间的隔离和内存共享,提高系统的稳定性和安全性。

文件系统管理:Kernel 负责管理计算机中的文件系统,包括文件的创建、读取、写入、删除等。Kernel 通过文件系统驱动程序,实现对不同文件系统类型的支持,提供统一的文件系统接口和访问权限管理。

系统服务和功能:Kernel 提供各种系统服务和功能,包括网络通信、安全管理、设备驱动程序、系统调用等。Kernel 通过这些服务和功能,为用户和应用程序提供便捷、安全和高效的系统环境。

总之,Kernel 是操作系统的核心部分,负责管理计算机的各种资源和功能,为用户和应用程序提供稳定、高效、安全和便捷的系统环境。

Lab1-1

安装Bochs虚拟机
sudo apt-get install bochs
装载操作系统的存储设备:软盘
bximage 
1
fd:软盘映像
1.44M:映像大小 1.44MB
a.img:映像名称(在往映像中写入操作系统时以及配置计算机时会用到)

bximage工具不仅可以创建虚拟磁盘镜像文件,还可以查看虚拟磁盘镜像文件的硬件配置信息,如镜像类型、磁盘容量、磁头数、磁道数以及扇区数等。

安装NASM汇编
sudo apt-get install nasm
创建boot.asm
 org     07c00h                  ; 告诉编译器程序加载到7c00处
        mov     ax, cs
        mov     ds, ax
        mov     es, ax
        call    DispStr                 ; 调用显示字符串例程
        jmp     $                       ; 无限循环
DispStr:
        mov     ax, BootMessage
        mov     bp, ax                  ; ES:BP = 串地址
        mov     cx, 16                  ; CX = 串长度
        mov     ax, 01301h              ; AH = 13,  AL = 01h
        mov     bx, 000ch               ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)
        mov     dl, 0
        int     10h                     ; 10h 号中断
        ret
BootMessage:            db      "Hello, OS world!"
times   510-($-$$)      db      0       ; 填充剩下的空间,使生成的二进制代码恰好
为512字节
 
dw      0xaa55                          ; 结束标志
使用NASM来汇编boot.asm生成“操作系统”(boot.bin)的二进制代码
nasm boot.asm –o boot.bin
  • -o output
  • 编译结束后,便可将生成的二进制程序文件写入到虚拟软盘镜像文件内 请注意,此处说的是写 入到虚拟软盘镜像文件内,而不是复制到虚拟软盘镜像文件内
使用dd命令将boot.bin写入软盘中
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
  • if:代表输入文件
  • of:代表输出设备
  • bs:代表一个扇区大小 512字节
  • count:代表扇区数
  • conv:代表不作其它处理 规定在写入数据后不截断(改变)输出文件的尺寸大小
  • 使用dd命令把引导程序强制写入到虚拟软盘的固定扇区中,这种强制写入固定扇区的方法能够跳过文件系统的管理与控制,转而直接操作磁盘扇区
Bochs的配置文件
vi bochsrc
megs:32 
display_library: sdl2  
floppya: 1_44=a.img, status=inserted 
boot: floppy
  • display_library:Bochs使用的GUI库
  • megs:虚拟机内存大小 (MB)
  • floppya:虚拟机外设,软盘为a.img文件
  • boot:虚拟机启动方式,从软盘启动
  • bochsrc与a.img以及boot.bin放在同一目录下
启动虚拟机
bochs -f bochsrc
  • -f指定配置文件

  • 如果提示没有sdl,则需要用命令行下载sdl

    sudo apt-get install nasm bochs bochs-sdl
    
  • 默认第6开始模拟

  • 只要在终端命令行输入字符串 c/cont/continue 中的任意一种,即可使虚拟机运行

  • 显示出hello OS world

org 07c00h的作用

org是伪指令,伪指令是指,不生成对应的二进制指令,只是汇编器使用的。也就是说, boot.bin文件里面,没有07c00h这个东西,BIOS不是因为这条指令 才把代码放在07c00h的。 • mov这种指令,就会生成二进制代码,可以直接告诉CPU该做什么

告诉汇编器,当前这段代码会放在 07c00h处。所以,如果之后遇到需要绝对寻址的指令,那么绝 对地址就是07c00h加上相对地址

在第一行加上org 07c00h只是让编译器从相对地址07c00h处 开始编译第一条指令,相对地址被编译加载后就正好和绝对地址吻合

如果需要反汇编
ndisasm boot1.bin -o 0
指令集合
nasm boot.asm -o boot.bin
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
bochs -f bochsrc
c

Lab1.2

Loader

一个操作系统从开机到开始运行,大致经历“引导→加载内核入内存→跳入保护模式→开始执行内核”这样一个过程。

也就是说,在内核开始执行之前不但要加载内核,而且还有准备保护模式等一系列工作,如果全都交给引导扇区来做,512字节很可能是不够用的,所以,不妨把这个过程交给另外的模块来完成,我们把这个模块叫做Loader。

引导扇区负责把 Loader加载入内存并且把控制权交给它,其他工作放心地交给Loader来做,因为它没有512字节的限制,将会灵活得多。

FAT12
  • 扇区(Sector):磁盘上的最小数据单元。
  • 簇(Cluster):一个或多个扇区。
  • 分区(Partition):通常指整个文件系统。

在这里插入图片描述

在这里插入图片描述

扇区结构
  • 引导扇区:扇区号0,BPB(BIOS parameter Block),以BPB_开头的域属于BPB,以BS_开头的域不属于BPB,只是引导扇区一部分

在这里插入图片描述

  • 根目录区:根目录区位于第二个FAT表之后,开始的扇区号为19,它由若干个目录条目(Directory Entry)组成,条目最多有BPB_RootEntCnt个。由于根目录区的大小是依赖于BPB_RootEntCnt的,所以长度不固定。根目录区中的每一个条目占用32字节,它的格式如表所示。

最后剩下最重要的信息DIR_FstClus,即文件开始簇号,它告诉我们文件存放在磁盘的什么位置,从而让我们可以找到它。由于一簇只包含一个扇区,所以简化了计算过程,而且下文中说到簇的地方,你也可以将它替换成“扇区”。需要注意的是,数据区的第一个簇的簇号是2,而不是0或者1。

在这里插入图片描述

FAT2可看做是FAT1的备份

指令集合
nasm boot.asm -o boot.bin
nasm loader.asm -o loader.bin
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc

sudo mount ./a.img /media/ -t vfat -o loop
sudo cp loader.bin /media/
sync 
sudo umount /media/

bochs -f bochsrc
c
指令解释
  • ‘-t vfat’ 表示要使用的文件系统类型为 VFAT
  • ‘-o loop’ 表示使用回环设备挂载,即将文件作为一个磁盘镜像挂载。
  • ‘cp’ 命令用于复制文件,即将文件复制到 “/media/” 目录中。
  • ‘sync’ 是一个 Linux/Unix 操作系统中的命令,用于将缓存中的数据写入磁盘中,以保证数据的持久化,并且刷新文件缓存和磁盘缓存中的数据,以确保数据的一致性。在执行文件系统操作前,可以使用 ‘sync’ 命令来确保数据已经被写入磁盘,防止因为掉电等异常情况导致数据的丢失或损坏。通常,在进行磁盘备份和还原操作时,也会使用 ‘sync’ 指令来刷写缓存,确保数据的完整性。
  • umount是解挂载
linux指令补充
  • ‘mount’ 命令用于将文件系统挂载到指定的目录中,从而使其在该目录下可见并访问。它的工作原理如下:

      1. 检查要挂载的设备或文件系统是否已经格式化并准备好被挂载。
      1. 指定挂载的目录,即创建一个目录作为挂载点或者使用一个已经存在的目录作为挂载点。
      1. 使用 ‘mount’ 命令将设备或文件系统挂载到指定的目录之下,并且可以指定一些可选的选项来定义挂载的行为。
      1. 操作完成后,可以使用 ‘umount’ 命令将设备或文件系统从挂载点卸载。
      1. 挂载点上的数据可以被其他程序或用户所访问和操作。
    • 例如,执行以下命令可以将文件系统 “/dev/sdb1” 挂载到目录 “/mnt” 中:

    sudo mount /dev/sdb1 /mnt 通过执行该命令,就可以访问 “/dev/sdb1” 中的数据,而这些数据在 “/mnt” 目录下可见和可访问。

  • 循环挂载(loop mount)是将一个普通文件作为块设备挂载为文件系统的方法,通过这种方式将文件作为虚拟磁盘来使用。 循环挂载常常用于安装操作系统镜像、读取磁盘映像、挂载加密文件系统等场景。这种方式不需要实际物理设备,只需要一个文件,可以避免使用磁盘空间,提供了更为灵活的数据存储方式。 执行循环挂载的命令格式如下: sudo mount /path/to/file.iso /mnt -o loop 其中,‘/path/to/file.iso’ 为要挂载的文件的路径,‘/mnt’ 为挂载目标目录的路径,‘-o loop’ 选项表示使用循环挂载方式。 执行上述命令后,文件系统将被挂载到 ‘/mnt’ 目录下,用户即可通过该目录进行访问和操作,这种方式可以视为将磁盘镜像或者其他文件作为 “虚拟磁盘” 来使用,访问的文件数据源也就是磁盘镜像或者其他文件。

LAB-1.2

加载loader入内存

要加载一个文件入内存的话,免不了要读软盘,这时候就用到BIOS中断int 13h。
BIOS 中断int 13h的用法

中断号寄存器作用
13hah=00h dl-驱动器号(0表示A盘)复位软驱
ah=02h
al=要读扇区数

ch=柱面(磁道)号
cl=起始扇区号
dh=磁头号
d1=驱动器号(0表示A盘)
es :bx→数据缓冲区
从磁盘将数据读入es :bx指向的缓冲区中

扇区号/每磁道扇区数(18)---->商Q \ 余数R→起始扇区号=R+1

商Q---->柱面号=Q>>1 磁头号=Q&1

boot.asm 64bitOS
	org	0x7c00	

BaseOfStack	equ	0x7c00
;这段程序中的代码BaseOfLoader equ 0x1000和offsetOfLoader equ 0x00组合成了Loader程序的起始物理地址,
;这个组合必须经过实模式的地址变换公式才能生成物理地址,即BaseOfLoader<<4 + offsetOfLoader = 0x10000。
BaseOfLoader	equ	0x1000
OffsetOfLoader	equ	0x00

RootDirSectors	equ	14
SectorNumOfRootDirStart	equ	19
SectorNumOfFAT1Start	equ	1
SectorBalance	equ	17	

	jmp	short Label_Start
	nop
	BS_OEMName	db	'MINEboot'
	BPB_BytesPerSec	dw	512
	BPB_SecPerClus	db	1
	BPB_RsvdSecCnt	dw	1
	BPB_NumFATs	db	2
	BPB_RootEntCnt	dw	224
	BPB_TotSec16	dw	2880
	BPB_Media	db	0xf0
	BPB_FATSz16	dw	9
	BPB_SecPerTrk	dw	18
	BPB_NumHeads	dw	2
	BPB_HiddSec	dd	0
	BPB_TotSec32	dd	0
	BS_DrvNum	db	0
	BS_Reserved1	db	0
	BS_BootSig	db	0x29
	BS_VolID	dd	0
	BS_VolLab	db	'boot loader'
	BS_FileSysType	db	'FAT12   '

Label_Start:

	mov	ax,	cs
	mov	ds,	ax
	mov	es,	ax
	mov	ss,	ax
	mov	sp,	BaseOfStack

;=======	clear screen

	mov	ax,	0600h
	mov	bx,	0700h
	mov	cx,	0
	mov	dx,	0184fh
	int	10h

;=======	set focus

	mov	ax,	0200h
	mov	bx,	0000h
	mov	dx,	0000h
	int	10h

;=======	display on screen : Start Booting......

	mov	ax,	1301h
	mov	bx,	0002h		;黑底绿字
	mov	dx,	0402h		;空4⾏2列
	mov	cx,	9
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	StartBootMessage
	int	10h

;=======	reset floppy

	xor	ah,	ah
	xor	dl,	dl
	int	13h

;=======	search loader.bin
;通过这段代码能够从根目录中搜索出引导加载程序(文件名为loader.bin )。

;一旦发现完全匹配的字符串,则跳转到Label_Filelame_Found处执行
;如果没有找到,那么就执行其后的Label_No_LoaderBin模块
;进而在屏幕上显示提示信息,通知用户引导加载程序不存在。

	mov	word	[SectorNo],	SectorNumOfRootDirStart

Lable_Search_In_Root_Dir_Begin:
;在程序执行初期,程序会先保存根目录的起始扇区号,并依据根目录占用磁盘扇区数来确定需要搜索的扇区数,并从根目录中读入一个扇区的数据到缓冲区
;接下来,遍历读入缓冲区中的每个目录项,寻找与目标文件名字符串("LOADER BIN",0)相匹配的目录项,
;其中DX寄存器记录着每个扇区可容纳的目录项个数(512/32=16=Ox10 ),
;CX寄存器记录着目录项的文件名长度(文件名长度为11B,包括文件名和扩展名,但不包含分隔符“”)。
	cmp	word	[RootDirSizeForLoop],	0
	jz	Label_No_LoaderBin
	dec	word	[RootDirSizeForLoop]	
	mov	ax,	00h
	mov	es,	ax
	mov	bx,	8000h
	mov	ax,	[SectorNo]
	mov	cl,	1
	call	Func_ReadOneSector
	mov	si,	LoaderFileName
	mov	di,	8000h
	cld
	mov	dx,	10h
	
Label_Search_For_LoaderBin:

	cmp	dx,	0
	jz	Label_Goto_Next_Sector_In_Root_Dir
	dec	dx
	mov	cx,	11

Label_Cmp_FileName:
;在比对每个目录项文件名的过程中,使用了汇编指令LODSB,该命令的加载方向与DF标志位有关,
;因此在使用此命令时需用CLD指令清DF标志位。
	cmp	cx,	0
	jz	Label_FileName_Found
	dec	cx
	lodsb	
	cmp	al,	byte	[es:di]
	jz	Label_Go_On
	jmp	Label_Different

Label_Go_On:
	
	inc	di
	jmp	Label_Cmp_FileName

Label_Different:

	and	di,	0ffe0h
	add	di,	20h
	mov	si,	LoaderFileName
	jmp	Label_Search_For_LoaderBin

Label_Goto_Next_Sector_In_Root_Dir:
	
	add	word	[SectorNo],	1
	jmp	Lable_Search_In_Root_Dir_Begin
	
;=======	display on screen : ERROR:No LOADER Found

Label_No_LoaderBin:

	mov	ax,	1301h
	mov	bx,	008ch
	mov	dx,	0100h
	mov	cx,	21
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	NoLoaderMessage
	int	10h
	jmp	$

;=======	found loader.bin name in root director struct
;在Label_FileName_Found模块中,程序会先取得目录项DIR_FstClus字段的数值,
;并通过配置ES寄存器和BX寄存器来指定loader.bin程序在内存中的起始地址,

Label_FileName_Found:

	mov	ax,	RootDirSectors
	and	di,	0ffe0h
	add	di,	01ah
	mov	cx,	word	[es:di]
	push	cx
	add	cx,	ax
	add	cx,	SectorBalance
	mov	ax,	BaseOfLoader
	mov	es,	ax
	mov	bx,	OffsetOfLoader
	mov	ax,	cx
;再根据loader.bin程序的起始簇号计算出其对应的扇区号。
;接着,每读入一个扇区的数据就通过Func_GetFATEntry模块取得下一个FAT表项
;并跳转至Label_Go_On_Loading_File处继续读入下一个簇的数据,
;循环,直至Func_GetFATEntry模块返回的FAT表项值是offfh为止。

Label_Go_On_Loading_File:
	push	ax
	push	bx
	mov	ah,	0eh
	mov	al,	'.'
	mov	bl,	0fh
	int	10h
	pop	bx
	pop	ax

	mov	cl,	1
	call	Func_ReadOneSector
	pop	ax
	call	Func_GetFATEntry
	cmp	ax,	0fffh
	jz	Label_File_Loaded
	push	ax
	mov	dx,	RootDirSectors
	add	ax,	dx
	add	ax,	SectorBalance
	add	bx,	[BPB_BytesPerSec]
	jmp	Label_Go_On_Loading_File
;当loader.bin文件的数据全部读取到内存后,跳转至Label_File_Loaded处准备执行loader.bin程序。
Label_File_Loaded:
	
	jmp	BaseOfLoader:OffsetOfLoader

;=======	read one sector from floppy
;代码中的Func_ReadOnesector模块负责实现软盘读取功能,
;它借助BIOS中断服务程序INT 13h的主功能号AH=02h实现软盘扇区的读取操作,该中断服务程序的各寄存器参数说明如下。
;INT 13h,AH=02h功能:读取磁盘扇区。
;AL=读入的扇区数(必须非0 );
;CH=磁道号(柱面号)的低8位;
;CL=扇区号1~63 ( bit 0~5),磁道号(柱面号)的高2位( bit 6~7,只对硬盘有效);DH=磁头号;
;DL=驱动器号(如果操作的是硬盘驱动器,bit 7必须被置位);ES:BX=>数据缓冲区。
Func_ReadOneSector:
	
	push	bp
	mov	bp,	sp
	sub	esp,	2
	mov	byte	[bp - 2],	cl	;模块Func_ReadOneSector在读取软盘之前,会先保存栈帧寄存器和栈寄存器的数值,
	push	bx					;从栈中开辟两个字节的存储空间(将栈指针向下移动两个字节)
	mov	bl,	[BPB_SecPerTrk]		;由于此时代码bp - 2与ESP寄存器均指向同一内存地址,所以CL寄存器的值就保存在刚开辟的栈空间里。
	div	bl						;而后,使用AX寄存器(待读取的磁盘起始扇区号)除以BL寄存器(每磁道扇区数),
	inc	ah						;计算出目标磁道号(商:AL寄存器)和目标磁道内的起始扇区号(余数:AH寄存器),考虑到磁道内的起始扇区号从1开始计数,故此将余数值加1,即inc ah。
	mov	cl,	ah					;紧接着,再按照公式(3-1)计算出磁道号(也叫柱面号)与磁头号,将计算结果保存在对应寄存器内。
	mov	dh,	al
	shr	al,	1
	mov	ch,	al
	and	dh,	1
	pop	bx
	mov	dl,	[BS_DrvNum]
Label_Go_On_Reading:
	mov	ah,	2
	mov	al,	byte	[bp - 2]
	int	13h						;最后,执行INT 13h中断服务程序从软盘扇区读取数据到内存中,当数据读取成功(CF标志位被复位)后恢复调用现场。
	jc	Label_Go_On_Reading
	add	esp,	2
	pop	bp
	ret

;=======	get FAT Entry
;这段程序首先会保存FAT表项号,并将奇偶标志变量(变量[odd] )置0。
;因为每个FAT表项占1.5 B,所以将FAT表项乘以3除以2(扩大1.5倍)来判读余数的奇偶性并保存在[odd]中(奇数为1,偶数为0 ),
;再将计算结果除以每扇区字节数,商值为FAT表项的偏移扇区号,余数值为FAT表项在扇区中的偏移位置。


Func_GetFATEntry:

	push	es
	push	bx
	push	ax
	mov	ax,	00
	mov	es,	ax
	pop	ax
	mov	byte	[Odd],	0
	mov	bx,	3
	mul	bx
	mov	bx,	2
	div	bx
	cmp	dx,	0
	jz	Label_Even
	mov	byte	[Odd],	1
;接着,通过Func_ReadOnesector模块连续读入两个扇区的数据,此举的目的是为了解决FAT表项横跨两个扇区的问题。
;最后,根据奇偶标志变量进一步处理奇偶项错位问题,即奇数项向右移动4位。
Label_Even:

	xor	dx,	dx
	mov	bx,	[BPB_BytesPerSec]
	div	bx
	push	dx
	mov	bx,	8000h
	add	ax,	SectorNumOfFAT1Start
	mov	cl,	2
	call	Func_ReadOneSector
	
	pop	dx
	add	bx,	dx
	mov	ax,	[es:bx]
	cmp	byte	[Odd],	1
	jnz	Label_Even_2
	shr	ax,	4

Label_Even_2:
	and	ax,	0fffh
	pop	bx
	pop	es
	ret

;=======	tmp variable

RootDirSizeForLoop	dw	RootDirSectors
SectorNo		dw	0
Odd			db	0

;=======	display messages

StartBootMessage:	db	"Hello OS!"
NoLoaderMessage:	db	"ERROR:No LOADER Found"
LoaderFileName:		db	"LOADER  BIN",0

;=======	fill zero until whole sector

	times	510 - ($ - $$)	db	0
	dw	0xaa55


loader.asm 64bitOS
;这段代码主要的功能是将堆栈指针(SP)的初始值设置为0x7c00,这是BIOS加载引导扇区的位置,以便接下来的代码可以顺利地运行。此外,它还将数据和附加段寄存器的值设置为代码段寄存器的值,以便程序可以访问和修改内存中的数据。

org	10000h

	mov	ax,	cs
	mov	ds,	ax
	mov	es,	ax
	mov	ax,	0x00
	mov	ss,	ax
	mov	sp,	0x7c00

;=======	display on screen : Start Loader......

	mov	ax,	1301h
	mov	bx,	004fh		;红底白字
	mov	dx,	0810h		;row 8,col 16
	mov	cx,	13
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	StartLoaderMessage
	int	10h

	jmp	$

;=======	display messages

StartLoaderMessage:	db	"Hello Loader!"

Lab-1.3

指令集合
nasm -felf32 -g main.asm -o main.o
ld -g -melf_i386 main.o -o main
./main
gdb main

指令解释


[section .data] ; 数据段

msg1 db '请输入第一个数:' 	; 提示信息1 
msg2 db '请输入第二个数:' 	; 提示信息2 
msg3 db '商为:' 			; 提示信息3 
msg4 db '余数为:' 			; 提示信息4
msg5 db '长度为:'           ;`
zero db '0'
ediis db ' edi is:'
yushuBigger db 'yushuBigger   '
chushuBigger db 'chushuBigger   '
equalmesg db 'equal   '
invalid db "invalid"
invalid_len equ $-invalid	;由当前位置标记$减去output所在位置计算而得



[section .bss] ; 未初始化数据段	.bss 是指 block started by symbol 的缩写,是一种专门存放未初始化数据的数据段。在该段中,所有变量都被初始化为0或空。

num1 resb 105 	; 存储第一个数 resb 指令用于为变量分配指定长度的连续空间,但不会将该空间中的任何位置初始化为具体的值。
num2 resb 105	; 存储第二个数 
ans resb 105 	; 存储商 
num1_len resd  1 ; 第一个数的长度
num2_len resd  1   ; 第二个数的长度
pointer resd 1  ; edi value
weishu resd 1      ;judge whether num1 = num2
ans_len resd 1
[section .text] ; 代码段

global _start


_start: 

; 第一个数输入 
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, msg1 	; 指向要输出的信息的指针 
    mov edx, 22 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息

; 读取第一个数并存储
    mov eax, 3 		; 系统调用号
    mov ebx, 0 		; 输入从标准输入的文件句柄(stdin)
    mov ecx, num1 	; 存储输入的缓冲区
    mov edx, 105 	; 存储缓冲区的大小
    int 0x80 		; 调用系统中断,读取输入的数  

; 第二个数输入
    mov eax, 4 		; 系统调用号
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout)
    mov ecx, msg2 	; 指向要输出的信息的指针
    mov edx, 22 	; 要输出的字符数
    int 0x80 		; 调用系统中断,输出信息

; 读取第二个数并存储
    mov eax, 3 		; 系统调用号
    mov ebx, 0 		; 输入从标准输入的文件句柄(stdin)
    mov ecx, num2 	; 存储输入的缓冲区
    mov edx, 105 	; 存储缓冲区的大小
    int 0x80 		; 调用系统中断,读取输入的数

; 确定第一个数和第二个数的长度
    mov eax, num1     ;
    call strlen
    mov dword[num1_len], eax ; 将eax的值移入num_len1变量中
    
    mov eax, num2     ;
    call strlen
    mov dword[num2_len], eax ; 将eax的值移入num_len2变量中
     

;计算商的位数
    
    mov ebx, dword[num1_len]
    cmp ebx, dword[num2_len]    ;len1 < len2
    jb div.Bbigger
    sub ebx, dword[num2_len]
    inc ebx
	mov dword[ans_len], ebx
    call _initialize
    

; 两个数相除处理
div: 
    cmp dword[num1_len], 0 ; 检查输入是否合法(num1的长度为0则为非法)
	je .print_invalid

	cmp dword[num2_len], 0 ; 检查输入是否合法(num2的长度为0则为非法)
	je .print_invalid

	cmp byte[num2], '0'   ; 检查输入是否合法(num2的第一个字符不能为0)
	je .print_invalid ; 如果不合法则输出错误信息并退出程序

    mov ebx, dword[num1_len]      ; bl = len1
	cmp dword[num2_len], ebx      ;if (len1 < len2) -> compare destination source  ;无符号大于 len2 > len1
	ja .Bbigger             
	jmp .Abigger

	mov cl, 0                   ;cl = 0  loop->i

.Abigger:
    call _CompareBit    ;;按位比较被除数和除数的大小
    cmp esi, 1          ;num1 >= num2
    ja .Bbigger         ;num1_len = num2_len && num1 < num2
    ;;num1 > num2
    ;;for loop sub

    mov edi, 0          ;edi = 0 ---> i
    
    
    .loopsub:
    ;;循环减法ans_len - 1次 ebx为i
    ;;edi中存放着这是第几次外循环,总计ans_len次循环, 0-ans_len - 1

    inc edi         ;edi++ start from 1
    cmp edi, dword[ans_len]  ;
    ja .printAns        ;edi > ans_len
	call _subBig        ;sub

    jmp .loopsub         ;最外层的for


.Bbigger:        
    ; 商为0 
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, msg3 	; 指向要输出的信息的指针 
    mov edx, 7 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, zero 	; 指向要输出的信息的指针 
    mov edx, 1 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息
    ;余数为被除数
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, msg4 	; 指向要输出的信息的指针 
    mov edx, 10 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, num1 	; 指向要输出的信息的指针 
    mov edx, 105 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息
    jmp .exit

.printAns:
    ; 商为ans
    mov eax, 4 		
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, msg3 	
    mov edx, 7
    int 0x80 
    ;remove 0
    mov edi, dword[ans_len] 
    mov esi, ans
    

    cmp edi, 0
    je .print0
    .loopremove:
    cmp byte[esi], '0'
    jne .printnowans
    cmp edi, 1
    je .printnowans
    inc esi
    dec edi
    jmp .loopremove


.printnowans:
    mov eax, 4 		
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, esi 	
    mov edx, edi 	
    int 0x80 



    ;余数为被除数
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, msg4 	; 指向要输出的信息的指针 
    mov edx, 10 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息
    ;remove 0
    mov edi, dword[num1_len] 
    mov esi, num1
    
    .loopremoveyushu:
    cmp byte[esi], '0'
    jne .printnowyushu
    cmp edi, 1
    je .printnowyushu
    inc esi
    dec edi
    jmp .loopremoveyushu

.printnowyushu:
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, esi 	; 指向要输出的信息的指针 
    mov edx, edi	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息
    jmp .exit

.print0:
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, zero 	; 指向要输出的信息的指针 
    mov edx, 1 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息

.exit:
	mov eax, 1
	mov ebx, 0
	int 80h

	mov edx, 1
	mov ecx, [0ah]
	mov ebx, 1
	mov eax, 4
	int 80h

; invalid input
.print_invalid:
    mov eax, 4
    mov ebx, 1  
    mov ecx, invalid
    mov edx, invalid_len
    int 80h
    jmp .exit



_printtest:
    push edx
    push ecx
    push ebx
    push eax

    ; 商为ans
    mov eax, 4 		
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, msg3 	
    mov edx, 7
    int 0x80 		
    mov eax, 4 		
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, ans 	
    mov edx, dword[ans_len] 	
    int 0x80 		
    ;被除数
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, msg4 	; 指向要输出的信息的指针 
    mov edx, 10 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, num1 	; 指向要输出的信息的指针 
    mov edx, 105 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息


    ;edi
    add edi, '0'
    mov dword[pointer], edi
    sub edi, '0'
    
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, ediis 	; 指向要输出的信息的指针 
    mov edx, 8 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息
    mov eax, 4 		; 系统调用号 
    mov ebx, 1 		; 输出到标准输出的文件句柄(stdout) 
    mov ecx, pointer 	; 指向要输出的信息的指针 
    mov edx, 1 	; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    int 0x80 		; 调用系统中断,输出信息
    
    

.End:
    pop eax
    pop ebx
    pop ecx
	pop edx
	ret

strlen: 
    push ebx
    mov ebx, eax
    nextchar:
    cmp byte [eax], 0
    jz finished
    inc eax
    jmp nextchar
    finished:
    sub eax, ebx    ; eax = eax - ebx(origin eax)
    sub eax, 1
    pop ebx
    ret
_initialize:
    push edx
	push ecx
	push ebx
	push eax

    mov eax, dword[ans_len]
    mov ebx, ans
    mov ecx, 0
.initAns:
    mov byte[ebx], '0';ans[i] = '0'
    inc ebx         ;ebx++  --> i++
    inc ecx         ;ecx++
    cmp ecx, eax    ;ecx != ans_len
    jne .initAns   ;
.End:
    pop eax
    pop ebx
    pop ecx
	pop edx
	ret 


_CompareBit:
    ;按位比较num1和num2的大小,num1已经确定和num2同样长度
    push edx
	push ecx
	push ebx
	push eax
    
    mov ebx, dword[num2_len]      ; ebx = len2
	cmp dword[num1_len], ebx      ;if (len1 > len2) -> set
    ja .set1bigger
    mov eax, num1           ;eax = &num1
    mov ebx, num2           ;ebx = &num2
    mov ecx, 0              ;ecx = 0 --> i
    mov esi, 0



    .Compareloop:
    inc ecx
    cmp ecx, dword[num2_len]
    jnb .set1bigger     ; ecx >= num1_len
    mov dl, byte[ebx]   ;bl = num2[i]
    cmp byte[eax], dl
    jb .set2bigger          ;num1[i] < num2[i]
    inc eax
    inc ebx
    jmp .Compareloop

    .set2bigger:
    mov esi, 2
    jmp .CompareBitEnd
    .set1bigger:
    mov esi, 1
    jmp .CompareBitEnd

    .CompareBitEnd:
    pop eax
    pop ebx
    pop ecx
	pop edx 
    ret 




_subBig:
    ;;只负sub,call则默认被除数比除数大,一定可以sub,并且直接操作被除数num1 
    ;;edi中存放着这是第几次外循环,总计ans_len次循环, 0-ans_len - 1
    ;;外call for edi用于计算位数,右移借位即是10*上一位+这一位,edi同时指示rem中位数
    ;;;call后loop负责将这一次可减的减到不可减为止

    push edx
    push ecx
    push ebx
    push eax
	push edi

	mov eax, num1       ;eax = &num1
	mov ebx, num2       ;ebx = &num2
    mov ecx, 0          ;ecx用于比较
	mov edx, 0          ;edx =  0     edx---->j
    ;esi为标志位flag
    ;如果函数compareInSub发现当前无法再sub就置flag = 1 
    ;esi == 2表示不需要借位, esi == 3表示需要借位                   
    mov esi, 0 
    add eax,edi         ;eax = &num1[i]
    dec eax             ;eax = &num1[i - 1]         

.subLoop:
	
    call _compareInSub      ;比较能否sub,是否被除数需要左移借位
	cmp esi, 2  
    ;sub   
	jna .subLoopBig          ;num1从edi(i)开始的前len2位比num2的大
	ja .subLoopSmallBorrow  ;num1从edi(i)开始的前len2位比num2的xiao
    
.subLoopBig:
    call _compareInSub
    cmp esi, 2              ;3需要借位
    ja .subLoopSmallBorrow

    call _sub
    jmp .subLoopBig
	
.subLoopSmallBorrow:
    cmp edi, 1
    je .subEnd

    mov ebx, num1
    add ebx, edi
    dec ebx             ;&num1[i] = ebx

    sub ebx, 1             ;ebx = &num1[i - 1]
    cmp byte[ebx], '0'  
    jna .subEnd         ;num1[i - 1] <= '0'

    mov al, byte[ebx]   ;al = num1[i - 1] + '0'
    sub al, 48         ; borrow all from num1[i - 1]
    mov byte[ebx], 48  ;num1[i - 1] = 0
    mov ecx, 10         ;ecx = 10
    inc ebx             ;ebx = &num1[i]

    .mulloop:
    add byte[ebx], al   ;num1[i] += al
    dec ecx             ;ecx--
    cmp ecx, 0          ;ecx != 0
    je _subBig.subLoopBig  ;call not need borrowed
    jmp .mulloop

.subEnd:
    jmp .End
.End:
    pop edi
    pop eax
    pop ebx
    pop ecx
	pop edx
	ret 
_sub:
    push edx
    push ecx
    push ebx
    push eax
	push edi

    mov eax, num1       ;eax = &num1
	mov ebx, num2       ;ebx = &num2
    add eax, edi        
    dec eax             ;eax = &num1[i - 1]  ebx = &num2[0]
    add eax, dword[num2_len];eax = &num1[i - 1 + num2_len]
    sub eax, 1          ;eax = &num1[i - 1 + num2_len - 1]
    add ebx, dword[num2_len];ebx = &num2[num2_len]
    sub ebx, 1          ;ebx = &num2[num2_len -1] 
    mov ecx, 0          ;count

.subByteLoop:
    
    mov dl, byte[eax]       ;dl = num1[i]
    cmp dl, byte[ebx]       
    jb .subborrow           ;num1[i] < num2[j]

    add dl, '0'             ;dl(num1[i]) -> char-> dl  保存的时候也需要以ASCII码
    sub dl, byte[ebx]       ;dl(num1[i]) -= num2[j]
	mov byte[eax], dl       ;num1[i] = dl
	
    inc ecx         ;count++
    dec eax         ;eax = &num1[i--]
    dec ebx         ;ebx = &num2[j--]
    cmp ecx, dword[num2_len]
    jnb .End                ;ecx = count = num2_len
	jmp .subByteLoop 
.subborrow:
    add dl, 10     ;borrow 10
    sub dl, byte[ebx]       ;dl(num1[i]) -= num2[j]
    add dl, '0'
	mov byte[eax], dl       ;num1[i] = dl


    inc ecx         ;count++
    dec eax         ;eax = &num1[i--]
    dec byte[eax]   ;num1[i--]--  :borrowed
    dec ebx         ;ebx = &num2[j--]
    cmp ecx, dword[num2_len]
    jnb .End                ;ecx = count = num2_len
	jmp .subByteLoop 
.End:
    mov dl, 00h             ;dl = 0
    mov edx, ans            ;edx = &ans[0]
    add edx, edi            ;edx = &ans[edi]
    sub edx, 1              ;edx = &ans[edi - 1]
    inc byte[edx]           ;ans[edi - 1]++   '0'++


    pop edi
    pop eax
    pop ebx
    pop ecx
	pop edx
	ret 

_compareInSub:
    push edx
    push ecx
    push ebx
    push eax
	push edi

	mov eax, num1       ;eax = &num1
	mov ebx, num2       ;ebx = &num2
    mov ecx, 0          ;ecx用于比较
	mov edx, 0          ;edx =  0     weishu---->j
    ;esi为标志位flag
    ;如果函数compareInSub发现当前无法再sub就置flag = 1 
    ;esi == 2表示不需要借位, esi == 3表示需要借位                   
    mov esi, 0          
	add eax, edi    ;eax = &num1[i]
    dec eax         ;eax = &num1[i- 1]
	mov byte[weishu], 0 ;weishu = 0

.Loop:
	mov cl, byte[num2_len]  ;cl = len2
	cmp byte[weishu], cl    ;weishu is for num1 == num2  ->outloop
	je .equal
	mov cl, byte[ebx]       ;cl = ebx = num2[j]
	cmp byte[eax], cl       
	ja .num1Bigger          ;num1[i + j] > num2[j] 
	jb .num2Bigger          ;num1[i + j] < num2[j]
	inc eax                 ;i++
	inc ebx                 ;j++
	inc byte[weishu]        ;weishu++
	jmp .Loop

.num1Bigger:
    mov esi, 2

    jmp .End
.num2Bigger:
    ;需要借位
    mov esi, 3

    jmp .End
.equal:
    mov esi, 1

    jmp .End
.End:
    
    pop edi
    pop eax
    pop ebx
    pop ecx
	pop edx
	ret 

= 0
mov edx, ans ;edx = &ans[0]
add edx, edi ;edx = &ans[edi]
sub edx, 1 ;edx = &ans[edi - 1]
inc byte[edx] ;ans[edi - 1]++ ‘0’++

pop edi
pop eax
pop ebx
pop ecx
pop edx
ret 

_compareInSub:
push edx
push ecx
push ebx
push eax
push edi

mov eax, num1       ;eax = &num1
mov ebx, num2       ;ebx = &num2
mov ecx, 0          ;ecx用于比较
mov edx, 0          ;edx =  0     weishu---->j
;esi为标志位flag
;如果函数compareInSub发现当前无法再sub就置flag = 1 
;esi == 2表示不需要借位, esi == 3表示需要借位                   
mov esi, 0          
add eax, edi    ;eax = &num1[i]
dec eax         ;eax = &num1[i- 1]
mov byte[weishu], 0 ;weishu = 0

.Loop:
mov cl, byte[num2_len] ;cl = len2
cmp byte[weishu], cl ;weishu is for num1 == num2 ->outloop
je .equal
mov cl, byte[ebx] ;cl = ebx = num2[j]
cmp byte[eax], cl
ja .num1Bigger ;num1[i + j] > num2[j]
jb .num2Bigger ;num1[i + j] < num2[j]
inc eax ;i++
inc ebx ;j++
inc byte[weishu] ;weishu++
jmp .Loop

.num1Bigger:
mov esi, 2

jmp .End

.num2Bigger:
;需要借位
mov esi, 3

jmp .End

.equal:
mov esi, 1

jmp .End

.End:

pop edi
pop eax
pop ebx
pop ecx
pop edx
ret 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值