从网上搜罗一个很详细注释的boot.s版本,加了小小一点点自己的理解,不太多.

用 as86, ld86 可以编译,   ubuntu下可以通过 apt install bin86 来安装好像.

    ; 程序描述:
; boot.s程序编译出的代码共512字节,将被存放在软盘映像文件的第一个扇区中。PC在加电启动时,
; BIOS程序会把启动盘上第一个扇区加载到物理内存0x7c00位置开始处,
; 然后跳转到0x7c00处开始执行boot.s程序代码。
; 本程序(boot.s程序)将内核代码(head.s代码)加载到0x10000处,然后再移动到0x0处0,
; 注意;加载到0x0处是为了设置GDT表时可以简单一些,因而也可以让head.s程序尽量短一些,
; 但不能一开始就加载到0x0处是因为加载操作需要使用BIOS提供的中断过程,而BIOS使用的
; 中断向量表正处于内存0开始的地方,并且在内存1KB开始处是BIOS程序使用的数据区,所以
; 若直接将head代码加载于此将导致BIOS中断过程不能正常运行。
; 最后进入保护模式,并转到0x0处继续运行。
; 其实跟 http://oldlinux.org/Linux.old/bochs/ 里面的那个 linux-0.00-050613.zip ->里的 linux-0.00-rh9.tar.gz
; 里的boot.s小有区别,不过只是写法上的不同, 逻辑上没有差异
; ###############################################################################; BOOTSEG = 0x07c0 ; 前面讲到PC加电启动时会加载本程序到0x7c00处,那为什么这里却是0x7c0,而不
; 是0x7c00呢,因为8086CPU刚启动时是处在实地址模式,实模式下最多寻址1M,
; 并将1MB存储空间分成许多逻辑段,每个段的长度被固定为64K。这样每个存储
; 单元就可以用“段基地址+段内偏移地址”表示。段基地址由16位段寄存器
; 值左移4位表达,段内偏移表示相对于某个段起始位置的偏移量。所以这里的
; 0x07c0实际上是段基地址。
; 所以,是BOOT SEGMENT 启动 段  SYSSEG = 0x1000 ; 将head.s加载于此(这里为什么是0x1000而不是0x10000原因同上) , 系统 段 地址 SYSLEN = ; 内核占用的最大磁盘扇区数,为了简化程序,这里只能加载长度不超过16个扇区
; 的内核,这个作为BIOS的读扇区功能的参数。问:既然仅限制16个扇区,为何以
; 17作为读入扇区数? 其实这里设置成16系统也能照常运行,多拷贝一个扇区可
; 能出于安全考虑。 只要你的head.s 编译链接后, 不要超过定义的 [扇区数] * 512 个字节就没什么问题 entry start ; 这个的作用是什么?汇编器汇编时必须有一个start指明入口地址,否则出现汇编错误 as86 用的, Gnu as 又是别的伪指令了
start:
jmpi go, #BOOTSEG ; jmpi是段间跳转指令,执行的结果是CS寄存器值变为0x7c0,接下来执
; 行“BOOTSEG:go”处的指令。 为什么需要这句话呢?不写不也是从下
; 面顺序执行吗? 答:刚开机时所有段寄存器(包括CS)的值为0,数据传
; 送指令是不能把数据传送给CS的,因为CS是代码段寄存器,CS如果被修
; 改程序就无法执行。所以必须用jmpi把cs改为0x7c0。
; 这里要特别注意的是, 加电后, Bios最后一步,只是把 磁盘第一扇区为
; AA55 那512字节的内容,加载到 7c00处而已,CS段,依然是 0,所以才需要这里的jmpi 来这只CS   go: mov ax, cs ; ax是属于通用寄存器之一的累加寄存器
mov ds, ax
mov ss, ax ; 让两个段寄存器ds和ss指向0x7c0段,问:1.为何需要让这两个段寄存
; 器指向这里? 2.为何要通过ax间接传递数据而不能直接赋值呢?答:
; 1.这里是为了让DS和SS指向和代码段一致的段,ss里面存放堆栈段的
; 段地址,sp存放偏移地址,物理地址=ss* 10H+ sp。这样结合下面一
; 句,堆栈从物理地址0x7c00+0x400开始,留1K的代码空间
; 2. 80x86 中规定不能对段寄存器(CS,DS等)直接给立即数
; 说明: ebp 栈底指针, esp-栈顶指针,ebp不知道啥时候用, 但是esp是从高-低发展的, push ax;入栈操作 ,会导致 esp-2. mov sp, #0x400 ; 设置临时栈指针。其值大于程序末端并有一定空间即可。问:1.为何需要
; 一个临时栈指针? 2.这个值怎么定,程序末端在哪里如何计算?1.中断
; 需要使用到堆栈 2.8086堆栈的生长方向为向下增长。boot.s占用512字节
; ,这里设置成远大于512的任意值就可以。
; 至于中断如何使用堆栈, 可以看看head.s. 0x400 = 1024 , 这里 boot.o 占用了512个字节,所以,这里 从 1024 -> 513 ,就留给了栈空间.
; head.s被加载到了 0x10000 ,所以,更加不会影响到这个区域,再加上,是临时用的话,只要 head.s没有被拷贝到0位置,就不碍事 ; 现在加载内核代码(head.s程序)至0x10000开始处
load_system: ; 问:标号有没有实际作用?标号指明其所在位置的地址
; 首先介绍一下BIOS的0x13的0x02号功能
; BIOS INT 0x13的0x02号功能 - 读扇区 ; INT 0x13/AH=0x02 - 将磁盘上的扇区读入内存
; AH = 0x02
; AL = 要读入的扇区数
; CH = 柱面(磁道)号的低8位
; CL = 位7、6是柱面(磁道号)高2位,位5-0是读入的起始扇区号(从1计,
; 第一扇区存放的是boot.s,第二扇区开始放的是head.s,这里要
; 读的是head.s所以从第二个扇区开始读)
;  DH = 磁头号
; DL = 驱动器号
; ES:BX = 缓冲区(用于保存读入扇区)的位置
; 返回值:
; AH = 状态码
; AL = 读到的扇区数
; CF = 失败为1,成功为0
; 这里只是操作硬盘IO的功能函数,了解一下即可.不会对系统的理解造成影响
mov ch, #0x00 ; 磁道号的 低八位
mov cl, #0x02 ; 问:为什么是加载2号扇区?答:因为磁盘的第一扇区放置的即是本程序
; (引导启动程序boot.s),而紧邻的第二扇区开始则放置内核代码head.s
; 。扇区号从1开始计算。
; 这里,是通过Makefile 的 dd 命令把 boot 和 head放到一起的,并不是一起编译的, as86 ld86 编译链接 boot , Gnu as ,Gnu ld 编译链接的 head --> system,
; 从Makefile 很容易就能看出来
 mov dh, #0x00 ; 磁头号
mov dl, #0x00 ; 问:驱动器是指什么?这里的驱动器号是0,表示floppya,即第一个软盘驱动器。
mov ax, #SYSSEG ; 不能直接执行mov es, #SYSSEG,编译时会出现illegal immediate mode
mov es, ax ; 错误,因为80x86中规定不能对段寄存器(CS,DS等)直接给值/立即数
; 上面介绍了 es:bx 指定了数据到从磁盘中读取到内存的哪儿, 所以 es = 0x1000, bx = 0,就是我们想要的 0x10000 处了.
 
xor bx, bx ; 将内核放置于1000:0000位置处 , xor 异或, 只是为了复位为0
mov ah, #0x02
mov al, #SYSLEN
int 0x13 ; 设置好各项参数后即可调用BIOS的0x13功能 , 如果出错 CF会被置位 1
jnc ok_load ; jnc(jump not c)是一跳转指令,当进位标记C为0时跳转,为1时执行后面的指令
die: jmp die ; 这里其实可以打印出错结果的 Todo: ?????? ; 到目前为止我们已将内核代码从磁盘读入到内存中指定位置了,下面就开始将内核
; 代码转移到0x0这个内存开始位置。共移动8K字节((16个扇区*512B/每扇区)/1024=8KB)。
;
ok_load:
cli ; 关中断 问:为何在开始移动时要关中断,是为了防止什么事件的发生吗?
; 若不关会怎样?
; 在搬移之前先介绍一下REP指令及MOVW指令
; REP:重复前缀,字符串操作本身每次只处理一个内存值,但如果使用重复前
; 缀的话,该指令就会使用ECX作为计数器进行重复。换句话说,就是可以用一
; 条指令处理整个数组。
; MOVW:将DS:SI(源变址寄存器)的内容送至ES(附加段数据寄存器):DI
; (目的变址寄存器),是复制过去,原来的代码还在。
; 附:ES和DS的功能相同,程序中设有多个数据段时,可以选用ES寄存器。一般
; 在串处理时用得比较多。比如将一段内存空间存储的数据复制到另一段空间,可
; 以分别设置DS:SI指向源存储数据的地址,ES:DI指向目的存储数据的地址
#
mov ax, #SYSSEG ; 移动开始位置:DS:SI=0x1000:0;目的位置:ES:DI=0:0
mov ds, ax ; DS = 0x1000
xor ax, ax ; 清零
mov es, ax ; 目标 0
mov cx, #0x1000 ; 设置共移动4K次,每次1个字(即移动16个扇区的代码)。 cx 计数寄存器, 0x1000 = 4096
sub si, si ; si 清零
sub di, di ; di 清零
rep
movw ; 执行重复移动指令 , 貌似as86 需要换行,才会编译通过
; ##################################################################################; ; 加载IDT和GDT基地址寄存器IDTR和GDTR
mov ax, #BOOTSEG
mov ds, ax ; 让DS重新指向0x7c0段(问:1.不一定要让数据段指向这个位置吧? 答:这里必
; 须让ds重新指向0x07c0段,因为lidt和lgdt隐含的完整格式上是ds:idt_operand和
; ds:gdt_operand,会在ds:operand这个位置去寻找它们的六字节操作数。2.这时
; 的情况是不是:0x0-0x2000:简单内核的代码区;0x7c00:数据段起始地址;  lidt idt_48 ; 加载IDTR。6字节操作数:2字节表长度,4字节线性基地址
lgdt gdt_48 ; 加载GDTR。6字节操作数:2字节表长度,4字节线性基地址 ; 设置好了中断描述符表IDT和全局描述符表GDT,并且加载好IDTR和GDTR后,准备进入保护模式
; 设置控制寄存器CR0(即机器状态字),进入保护模式。段选择符值8对应GDT表中第2个段描述符
; 控制寄存器(CR0、CR1、CR2和CR3)用于控制和确定处理器的操作模式以及当前执行任务的特性
; CR0中含有控制处理器操作模式和状态的系统控制标志
; CR1保留不用
; CR2含有导致页错误的线性地址
; CR3含有页目录表物理内存基地址(因此该寄存器也被称为页目录基地址寄存器PDBR) mov ax, #0x0001 ; (操作数的第四位是0x1=0001,将传给CR0)
; 先介绍一下LMSW指令:LMSW: Load Machine Status Word(置处理器状态字) 只有操作数的低4
; 位被存入CR0,只有PE(位0),MP(位1)和EM(位2)和TS(位3)被改写,CR0其他位不受影响。
lmsw ax ; 设置CR0,进入保护模式。
; 先介绍下JMPI指令(段间跳转指令),在实模式下JMPI B, A 是跳到以A为段基地址,以B为偏
; 移地址处执行,在保护式下JMPI B,A是跳转到以A为段选择符,偏移为B处执行。JMP是段内的跳转
  jmpi , ; 然后跳转至段选择符指定的段中,偏移0处 jmpi: 偏移, 段,其实就是设置了 cs= 0x8, ip = 0x0
; 注意此时段值已是段选择符。该段的线性基地址是0。
; 这个8是怎么来的?这个8(实际是 0x0008 ,二进制:0000000000001 0 00)是段选择符,由
; 段选择符的定义可知,该选择符选择的是RPL为0,TI=0,表示索引GDT,描述符索引=0x1, 索引为1的GDT描述符,前面的lgdt指令已
; 经在GDTR寄存器中存放了GDT表的位置跟长度,每个段描述符固定占用8字节,所以根据索引
; 值就可以找到GDT表中的段描述1,可以看到该段基地址是0x0000000,加上偏移值0,上面只是置位了PE为1, PG没动, 所以只开启了保护,没有开启分页.
; 这里没有开启分页保护,所以是跳到物理地址0处执行。   ;下面是全局描述符表GDT的内容。其中包含3个段描述符。第1个不用,另2个是代码和数据段描述符
gdt: .word ,,, ; 段描述符0,不用。每个描述符项占8字节。 .word 0x07FF ; 段描述符1,7ff是段限长的0-15位, =8M,(2048*4096 = 8M)
.word 0x0000 ; 段基地址是0x00000000
.word 0x9A00 ;9A= 1001 1010 基地址16-23=0, type=1010, P/DPL/S = 1 00 1, s=1 代码/数据段, TYPE=1010 = 可读可执行的代码段
.word 0x00C0 ; C0 = 1100 0000 颗粒度:4K 0000段限长的一部分, =0,不用在意 1100 G/DB/0/AVL, G 粒度,1=4K,DB=1 ,32位段
; 所以,当jmpi 0,8 执行后, 通过cs,会找到gdt的这个段选择符, 段基址是 0x0,段长度8M,然后执行(因为ok_load,已经把 0x10000出的head.s代码,都拷贝到了0x0出, 所以,可以顺利执行head.s代码了)
.word 0x07FF ; 段描述符2
.word 0x0000 ; 同上
.word 0x9200 ; 92 1001 0010, 还是 P存在, S=1,代码/数据段 TYPE=0010 , 可读写数据段
.word 0x00C0 ;同上 ; 下面分别是LIDT和LGDT指令的6字节操作数
idt_48: .word ; idt 段限长 0
.word , ; 基地址 0x0
gdt_48: .word 0x7ff ; gdt 段限长 2047, 一个描述符8个字节, 大约存 256个描述符, 实际上面就存了 3个, 第一个CPU规定,必须默认是 null
.word 0x7c00+gdt, ; 基地址 这里gdt的偏移不会太大,所以,可以理解成, 高四位全是0, 低四位 0x7c00 +gdt,说白了, 就是相对于启动地址7c00再偏移到 gdt标签定义的那里
; 这里的段限长,其实主要是用来检查的, 如果非要读取gdt的 第3项之后的东西, 肯定是有问题的,毕竟就定义了3个项,但是有限制长度是256,不出错才有鬼 .org ; 问:这是什么?
; 答:.org指令表示以后的内容从510字节开始存放,下面的AA55是引导
; 扇区的结束标志,占二字节,这就是为什么boot.s刚好占512字节的原因,
; 如果该标志错误系统就不能启动。 .word 0xAA55 ; 启动扇区标识, 第一扇区512的最后两个字节是AA55即可

001. 注释过的boot.s的更多相关文章

  1. 构建你的spring boot代码

    Spring boot不需要任何特定的代码布局来工作.然而,有一些最佳实践可以帮助您. 1.避免使用缺省包 当一个类不包含包声明时,它被认为是在“缺省包”中.“默认包”的使用通常是不鼓励的,应该避免. ...

  2. Spring Boot 历史

    2012年10月,Mike Youngstrom在Spring jira中创建了一个功能需求,要求在Spring框架中支持无容器Web应用程序体系结构.他建议通过main方法引导的Spring容器内配 ...

  3. Spring-Boot快速搭建web项目详细总结

    最近在学习Spring Boot 相关的技术,刚接触就有种相见恨晚的感觉,因为用spring boot进行项目的搭建是在太方便了,我们往往只需要很简单的几步,便可完成一个spring MVC项目的搭建 ...

  4. Linux内核移植

    实验步骤:(1)准备工作(2)修改顶层Makefile(3)修改falsh 分区(4)配置编译内核 下面以Linux2.6.30.4内核移植到gec2440为例: 一.准备工作:建立工作目录,下载内核 ...

  5. latex建立参考文献的超链接

    在Latex生成的pdf文档中建立超链接(如从正文到参考文献,从目录到相应内容,从页码编号到实际页面等),有利于读者快速定位当前阅读的信息. 如何在生成的pdf文件中包含超链接呢?需要注意一下两点: ...

  6. SpringCloud微服务实战-Zuul-APIGateway(十)

    本文转自:http://blog.csdn.net/qq_22841811/article/details/67637786#准备工作 1 API Gateway 2 Zuul介绍 2.1 zuul的 ...

  7. 0601-Zuul构建API Gateway-API gateway简介、基础使用、路由配置、负载配置

    一.API Gateway简介 参看:http://www.cnblogs.com/bjlhx/p/8794437.html 二.zuul简介[路由器和过滤器:Zuul] 在微服务架构的组成部分进行路 ...

  8. SpringBoot之响应式编程

    一 Spring WebFlux Framework说明 Spring WebFlux 是 Spring Framework 5.0 中引入的新 reactive web framework.与 Sp ...

  9. SpringBoot学习(四)开发web应用

    Spring Boot非常适合web应用程序开发.可以使用嵌入式Tomcat.Jetty.Undertow或Netty创建自包含的HTTP服务器.大多数web应用程序使用spring-boot-sta ...

随机推荐

  1. PAT甲级——A1075 PAT Judge

    The ranklist of PAT is generated from the status list, which shows the scores of the submissions. Th ...

  2. Delphi XE10百集视频教程计划

    1. 前言 本人现在的职业是Java程序员,一直想学习一个做桌面应用的编程语言,几年前无意中接触到Delphi,比VB功能强大,比C++语法更容易理解,加上Oracle的PL/SQL的底子,最终决定学 ...

  3. DSP using MATLAB》Problem 8.16

    代码: %% ------------------------------------------------------------------------ %% Output Info about ...

  4. 05_jQuery对象初识(三)登录案例

    1.案例需求:点击登录按钮验证用户名和密码都不为空,为空就在对应的input标签下面显示一个错误的提示信息. 1.给登录的按钮绑定点击事件 2.点击事件要做的事情 1.找到input标签.取值.判断是 ...

  5. <每日一题>题目20:简单python练习题(11-20)

    #11.编写程序,输入一个自然数,输出它的二进制.八进制.十六进制表示形式 Num = input("请输入任性自然数:") Num = eval(Num) print(" ...

  6. vue 引入css及注意事项

    组件中: <style scoped> @import '../../static/css/xx.css'; // “ :”必须有 </style> 注:若用以下方法,全部组件 ...

  7. 嘴巴题6 BZOJ3450JoyOI1952 Easy

    Time Limit: 10 Sec Memory Limit: 128 MB Submit: 936 Solved: 698 [Submit][Status][Discuss] Descriptio ...

  8. 廖雪峰Java11多线程编程-1线程的概念-2创建新线程

    Java语言内置多线程支持: 一个Java程序实际上是一个JVM进程 JVM用一个主线程来执行main()方法 在main()方法中又可以启动多个线程 1.创建新线程 1.1 方法一:使用Thread ...

  9. HZOI20190823模拟31题解

    题面:https://www.cnblogs.com/Juve/articles/11425141.html math:仔细看看其实是个水题 #include<iostream> #inc ...

  10. valueStack(值栈)

    值栈 值栈(ValueStack)就是 OGNL 表达式存取数据的地方.在一个值栈中,封装了一次请求所需要的所有数据. 在使用 Struts2 的项目中,Struts2 会为每个请求创建一个新的值栈, ...