• 第三章-完善MBR


    这一章节的主要任务是,完善MBR,文件一mbr.S任务为加载到内存0x7c00位置,文件二loader.S任务为完成内核初始化和加载硬盘上的内核文件到内存。因此主要核心功能为硬盘读取操作。
    整理了硬盘读取操作过程:
    1.硬盘读取操作接口寄存器地址表
    2.确当硬盘LBA/CHS地址,配置接口寄存器
    3.确认硬盘状态,放入数据
    
    • 1
    • 2
    • 3
    • 4
    • 5
    为什么mbr编译时设置数据的起始地址vstart=0x7c00,就可以保证程序加载器能将MBR加载到内存的0x7c00?

    程序加载器负责将根据编译后的程序地址加载到内存中,mbr 用 vstart=0x7c00 来修饰的原因,是开发人员知道 mbr 要被加载器(BIOS)加载到物理地址 0x7c00,mbr 中后续的物理地址都是 0x7c00+。

    Part1:如何设置程序的起始地址为0x7c00?

    当下处于实模式下,利用汇编指令,控制编译器编译的起始地址vstart,让编译器从vstart开始为mbr section指定一个虚拟的起始地址,此起始地址为虚拟内存地址,通过该地址访存是找不到section的。

    源码地址(byte)反汇编代码
    section code vstart=0x7c00
    mov ax,$$00000000mov ax,0x7c00
    mov ax,section.code.start00000003mov ax,0x10
    mov ax,section.data.start00000006mov ax,0x14
    mov ax,$00000009mov ax,0x7c09
    mov ax,[var1]0000000Cmov ax,[0x900]
    mov ax,[var2]0000000Fmov ax,[0x904]
    jmp $00000012jmp -2
    section data vstart=0x900
    var1 dd 0x400000014
    00000016
    var2 dw 0x9900000018
    00000019

    补充:$$指向程序的编译起始地址,$指向当前指令的偏移地址。

    Ⅰ.汇编中的section

    实模式下,操作系统将内存中的用户数据根据段来划分,那么编译器在编译时也是根据section来划分汇编文件的数据块。也可以视为C语言的函数。

    Ⅱ.CPU寻址方式

    寄存器寻址:mov ax,cs
    立即数寻址:mov ax,0x10
    内存变址寻址:mov ax,[cs:0x10]
    
    • 1
    • 2
    • 3

    Ⅲ.ASSEMBLY指令

    1. ret命令&call指令

    本质是更改了CS:IP指向的代码段,跳转到待执行的内存地址

    ret是返回原程序,既然有返回,那么一定有转移,assembly language转移包括jmp和call,jmp是无返回的直接冲,call是需要有ret的,因此ret和call搭配使用。

    与中断的保护现场一样,assembly process的call也是需要进行保护现场的,借助栈的先入后出原理,在调用call时将指令信息保存到栈中,实现多层call调用时,能正确ret。

    call分为近调用和远调用,对应ret和retf(return far)。具体原理为:ret指令将栈顶指针[ss:sp]的两个字节取出,赋值给IP寄存器,不需要改变CS寄存器值;retf将栈顶的4个字节取出,前两个字节赋值给IP寄存器,后两个字节赋值给CS寄存器。具体的选择需要程序员根据应用需求自动调整。

    1. jmp指令
    转移指令条 件意 义英文助记
    jz/jeZF=1相减结果等于0/相等时转移Jump if Zero/Equal
    jnz/jneZF=0不等于0/不相等时转移Jump if Not Zero/Not Equal
    JSSF=1负数时转移Jump if Sign
    jnsSF=0正数时转移Jump if Not Sign
    joOF=1溢出时转移Jump if Overflow
    jnoOF=0未溢出时转移Jump if Not Overflow
    jp/jpePF=1低字节中有偶数个1时转移Jump if Parity/Parity Even
    jnp/jpoPF=0低字节中有奇数个1时转移Jump if Not Parity/Parity Odd
    jbe/jnaCF=1或 ZF=1小于等于/不大于时转移Jump if Below or Equal/Above
    jnbe/jaCF=ZF=0不小于等于/大于时转移Jump if Not Below or Equal/Above
    jc/jb/jnaeCF=1进位/小于/小于等于时转移Jump if Carry/Below/Not Above Equal
    jnc/jnb/jaeCF=0未进位/不小于/大于等于时转移Jump if Not Carry/Not Below/Above Equal
    jl/jingeSF!=OF小于/不大于等于时转移Jump Less/Not Great Equal
    jnl/jgeSF=OF不小于/大于等于时转移Jump if Not Less/Great Equal
    jle/jngZF!=OF 或 ZF=1小于等于/不大于Jump if Less or Equal/Not Great
    jnle/jgSF=OF 且 ZF=O不小于等于/大于时转移Jump Not Less Equal/Great
    JcxzCX寄存器值=0CX 寄存器值为0时转移Jump if register CX’s vaJue is Zero
    abcegjlnop
    表示 above表示 below表示 carry表示 equal表示 great表示 jmp表示 less表示not表示 overflow表示 parity

    Part2:保护模式

    实模式最终会被保护模式替代掉,本质是安全问题。

    上述过程均在实模式下完成,实模式将所有的内存空间暴露给用户,用户通过ds<<0x1+偏移地址可以访问内存任意位置,可能会影响操作系统的稳定。因此衍生出了保护模式,保护模式对用户的访存操作加入了特权判断,那谁来分配特权呢???

    用户编写程序时,只允许有两种特权分配,内核态的特权0或用户态的特权3,用户程序通过系统调用进入内核态,访问系统硬件和内核。

    Part3:显示器操作

    IO 接口是连接 CPU 与外部设备的逻辑控制部件 ,分为硬件和软件两部分:

    • 硬件部分的工作是协调 CPU 和外设。如数据缓冲和数据格式转换。
    • 软件部分的工作是控制接口电路工作的驱动程序以及完成内部数据传输所需要的程序。

    1.CPU与外设通过I/O接口实现通信,主要解决的问题:

    (1)据缓冲问题。CPU数据处理速率较外设快很多,如果直接将CPU与外设相连,CPU阻塞等待导致系统性能降低,因此通过建立I/O接口建立缓冲区,当缓冲区满了才中断CPU响应。

    (2)据格式不一致问题。CPU只处理数字信号,而外设信号包括数字信号、模拟信号等,I/O接口搭载有A/D转换电路和D/A转换电路,完成CPU的数字信号到外设的模拟信号转换、外设的模拟信号到CPU的数字信号转换。

    (3)信号电平不一致问题。CPU信号为TTL电平,外设大多是机电设备,采用CMOS电平,两个接口电平不一致,直接对接可能会烧坏器件,因此I/O接口设置有信号电平转换电路。

    TTL电平与CMOS电平的区别
    
    (一)TTL高电平3.6~5V,低电平0V~2.4V
    CMOS电平Vcc可达到12V
    CMOS电路输出高电平约为0.9Vcc,而输出低电平约为0.1Vcc。
    CMOS电路不使用的输入端不能悬空,会造成逻辑混乱。
    TTL电路不使用的输入端悬空为高电平**
    另外,CMOS集成电路电源电压可以在较大范围内变化,因而对电源的要求不像TTL集成电路那样严格。
    用TTL电平他们就可以兼容
    (二)TTL电平是5V,CMOS电平一般是12V。
    因为TTL电路电源电压是5V,CMOS电路电源电压一般是12V。
    5V的电平不能触发CMOS电路,12V的电平会损坏TTL电路,因此不能互相兼容匹配。
    (三)TTL电平标准
    输出 L: <0.4V ; H:>2.4V。
    输入 L: <0.8V ; H:>2.0V
    TTL器件输出低电平要小于0.4V,高电平要大于2.4V。输入,低于0.8V就认为是0,高于2.0就认为是1。
    
    CMOS电平:
    输出 L: <0.1Vcc ; H:>0.9Vcc。
    输入 L: <0.3Vcc ; H:>0.7Vcc.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    (4)信号时序不一致问题。一些外设拥有自己的晶振时序,直接接收或发送CPU的数据,会导致数据丢失或数据污染。因此需要I/O接口设计时序转换电路。

    (5)支持多个外设地址译码。由于多个外设公用一个接口,因此CPU需要明确数据来源,同时,I/O接口需要确定CPU的数据转发地址。

    2.南桥和北桥

    南桥用于连接低速外设,北桥用于连接内存等高速外设,为了提高访存速率,某些厂商将北桥集成在CPU内部。

    CPU通过专门的in,out指令完成对接口数据的读取。按照Intel指令规范,操作码 目的操作数,源操作数的格式。则in指令(input)从接口读取数据,格式为:

    in al,dx	;当源操作数dx为8bit时
    in ax,dx	;当源操作数dx为16bit时
    
    • 1
    • 2

    out指令向接口中写入数据,格式为:

    out dx,al
    out dx,ax
    out 立即数,al
    out 立即数,ax
    
    • 1
    • 2
    • 3
    • 4

    3.碎碎念

    实模式存在中断向量表,而保护模式没有中断向量表,因此保护模式无法通过BIOS中断实现打印输出功能。但是系统不是从刚开始就进入保护模式,首先要进入实模式执行BIOS初始化,再开启保护模式。

    CLI关中断,禁止中断发生;STI开中断,允许中断发生。

    Part4:操作硬盘

    从磁盘读写数据,首先需要确定硬盘访问地址,与CHS(柱面-磁头-扇区,Cylinder Head Sector)地址需要确定几盘几道几扇区的访问方式不同,LBA地址将硬盘视为一个整体,从0开始编址。然后根据in,out命令向LBA地址执行读写操作。

    在这里插入图片描述

    具体过程如下:

    写入前,需要确定磁盘的起始地址、写入地址、待写入的扇区数。

    mov eax,LOADER_START_SECTOR ;起始扇区LBA地址,0x900,这里对应的是loader.S文件位置,也是硬盘读写操作
    mov bx,LOADER_BASE_ADDR    ;写入的地址,0x2
    mov cx,1					;待写入的扇区数
    
    • 1
    • 2
    • 3

    1.向磁盘sector count端口0x1f2传入待写入的磁盘数

    执行的操作:out dx,[待写入的扇区数]

    mov dx,0x1f2
    mov al,cl
    out dx,al
    
    • 1
    • 2
    • 3

    2.向磁盘的LBA地址端口写入地址

    在这里插入图片描述

    ;将LBA地址(逻辑地址)存入0x1f3-0x1f6,小端存储
    	;LBA地址7-0位写入端口0x1f3
    	mov dx,0x1f3
    	out dx,al
    
    	;LBA地址15-8位写入端口0x1f4
    	mov cl,8
    	shr eax,cl
    	mov dx,0x1f4
    	out dx,al
    	
    	;LBA地址23-16位写入端口0x1f6
    	shr eax,cl
    	mov dx,0x1f5
    	out dx,al
    	
    	;设置LBA地址模式
    	shr eax,cl
    	;取LBA 24-27位
    	and al,0x0f
    	;设置7-4位为1110,表示LBA模式
    	or al,0xe0
    	mov dx,0x1f6
    	out dx,al
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3.向0x1f7端口写入status寄存器配置信息,确定磁盘状态是否满足当下读写要求。

    status寄存器控制命令主要有三个:

    (1)硬盘识别:0xEC

    (2)读数据:接口写入0x20

    (3)写数据:接口写入0x30

    	mov dx,0x1f7
    	mov ax,0x20		;写数据0x20
    	out dx,ax
    
    • 1
    • 2
    • 3

    4.当硬盘稳定后,执行读数据命令

    	.not_ready:
    		nop
            	in al,dx 	;将端口中的信息读到al中,注意此时dx=0x1f7不变,此时是status寄存器,也就是状态端口
            	and al,0x88 
            	cmp al,0x08  	;第7位为1,表示占用;第8位为1,表示空闲
            	jnz .not_ready 	;如果被占取了,就循环  jnz=jmp not equal
    
            	mov ax,di  	;di=1
            	mov dx,256 
            	mul dx     	;dx=ax*dx 每次读取1个字,也就是两字节,一共512字节,所以需要256次
            	mov cx,ax  	;cx指定循环的次数
            	mov dx,0x1f0 	;数据端口,终于开始读取数据了
            
        	.go_on_read:     
            	in ax,dx      	;将端口中指定的数据,也就是指定的扇区的数据读入到ax中
            	mov [bx],ax   	;bx寄存器存储的就是0x900也就是loader的内存地址
            	add bx,2      	;每次读两字节
            	loop .go_on_read
    
            	ret            	;返回后就会执行jmp跳转到0x900去了,此时机会执行loader.bin
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    	.go_on_read:     
        	in ax,dx      	;将端口中指定的数据,也就是指定的扇区的数据读入到ax中
        	mov [bx],ax   	;bx寄存器存储的就是0x900也就是loader的内存地址
        	add bx,2      	;每次读两字节
        	loop .go_on_read
    
        	ret            	;返回后就会执行jmp跳转到0x900去了,此时机会执行loader.bin`
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    (十五)Spring之面向切面编程AOP
    python--由wrfouput的数据计算位势涡度,并插值到指定压力层
    大模型Tuning分类
    【Rust】Rust环境配置与语法基础
    图像识别技术在不同场景下有哪些应用?
    GUI:贪吃蛇
    [NOIP2013 普及组] 计数问题
    OC-消息转发
    [山东科技大学OJ]1214 Problem B: 编写函数:字符串的连接 之二 (Append Code)
    Redis实战篇(六)附近商铺、用户签到、UV统计
  • 原文地址:https://blog.csdn.net/zhengmmm1999/article/details/133761908