X86架构(六)——硬盘访问与控制

news2024/11/25 9:54:04

在前面几节中,我们总是通过ROM-BIOS从硬盘的主引导扇区读取一段程序并加载到内存运行,但是处理器是如何访问硬盘呢?这是一个值得我们思考的问题
OK,我们先看一张图
总线系统
所有这些和计算机主机连接的设备,叫做外围设备,也叫IO设备。IO设备的控制与访问是通过总线技术将多个设备挂载在Bus上,然后通过输入输出控制设备集中器(I/O Controller Hub,ICH)芯片连接不同的总线,并协调各个I/O接口对处理器的访问。

I/O端口与端口访问

外围设备和处理器之间的通信是通过相应的I/O接口进行的。具体地说,处理器是通过端口(Port)来和外围设备打交道的。本质上,端口就是一些寄存器,类似于处理器内部的寄存器。不同之处仅仅在于,这些叫做端口的寄存器位于I/O 接口电路中。

端口是处理器和外围设备通过I/O接口交流的窗口,每一个I/O接口都可能拥有好几个端口,比如,连接硬盘的PATA/SATA 接口就有命令端口、状态端口、参数端口和数据端口

  • 命令端口:向该端口写入0x20 ,表明是从硬盘读数据;写入0x30 ,表明是向硬盘写数据
  • 状态端口:输出硬盘工作工作状态数据或操作执行情况
  • 参数端口:指定硬盘读写的扇区数量,起始的逻辑扇区号
  • 数据端口:数据传输

端口在不同的计算机系统中有着不同的实现方式。在一些计算机系统中,端口号是映射到内存地址空间的。而在另一些计算机系统中,端口是独立编址的,不和内存发生关系。如下图所示,在这种计算机中,处理器的地址线既连接内存,也连接每一个I/O接口。
端口访问
在这种模式下,处理器还有一个特殊的引脚M/IO#,==#表示低电平有效。当处理器访问内存时,M/IO#引脚呈高电平,和内存相关的电路就会打开;当处理器访问I/O端口时,M/IO#引脚呈低平,内存电路被禁止。处理器发出的地址和M/IO#==信号一起用于指定某个I/O 接口。
NOTE 重点说这个独立编址

硬盘访问

那现在来说独立编址下的硬盘访问,硬盘访问通过PATA/SATA接口实现,每个PATA和SATA接口分配了8个端口。ICH 芯片内部通常集成了两个PATA/SATA接口,分别是主硬盘接口和副硬盘接口。主硬盘接口分配的端口号是0x1f0~0x1f7,副硬盘接口分配的端口号是0x170~0x177

  • 0x1f0 16位数据端口
  • 0x1f1 错误状态端口
  • 0x1f2 操作扇区数量端口
  • 0x1f3~0x1f6 起始扇区端口
  • 0x1f7 命令端口

端口的访问不能使用类似于mov 这样的指令,取而代之的是in和out指令。

in al, dx
in ax, dx
;in 指令的目的操作数必须是寄存器AL或者AX
;in 指令的源操作数应当是寄存器DX
;in 指令不允许使用别的通用寄存器,也不允许使用内存单元作为操作数
out 0x37, al	;0x37端口 - 8位端口
out 0xf5, dx	;0xf5端口 - 16位端口
out dx, al		;端口号在dx寄存器中 - 8位端口
out dx, ax		;端口号在dx寄存器中 - 16位端口
;out 指令的目的操作数可以是8 位立即数或者寄存器DX
;out 指令的源操作数必须是寄存器AL或者AX

硬盘

硬盘读写的基本单位是扇区,读写以扇区为单位进行,使得主机和硬盘之间的数据交换是成块的,所以硬盘是典型的块设备。

访问流程

  • 设置读取扇区数量
mov dx, 0x1f2	;0x1f2端口
mov al, 0x01	;1个扇区
out dx, al
;如果写入的值为0,则表示要读取256个扇区。每读一个扇区,这个数值就减一。
  • 设置起始LBA 扇区号
    扇区的读写是连续的,因此只需要给出第一个扇区的编号就可以了。28 位(LBA28模式)的扇区号太长,需要将其分成4 段,分别写入端口0x1f3、0x1f4、0x1f5 和0x1f6 号端口。其中,0x1f3 号端口存放的是0~7 位;0x1f4 号端口存放的是8~15 位;0x1f5 号端口存放的是16~23 位,最后4 位在0x1f6 号端口。
;起始扇区号为2
mov dx, 0x1f3
mov al, 0x02
out dx, al		;LAB[7:0]
inc dx			;0x1f4
mov al, 0x00
out dx, al		;LAB[15:8]
inc dx			;0x1f5
out dx, al		;LAB[23:16]
inc dx			;0x1f6
mov al, 0xe0	;4位用于指定模式等,低四位为0
out dx, al
;每个PATA/SATA接口允许挂接两块硬盘,分别是主盘和从盘。
;0x1f6端口的低4位用于存放逻辑扇区号的2427位,第4位用于指示硬盘号,0表示主盘,
;1表示从盘。高3 位是“111”,表示LBA模式

1f6各位

  • 读硬盘
mov dx, 0x1f7	;0x1f7端口 - 8位端口
mov al, 0x20	;读命令
out dx, al
  • 等待读操作完成
    端口0x1f7 既是命令端口,又是状态端口。发送读写命令后,它将0x1f7 端口的第7位置1,表明忙。一旦硬盘系统准备就绪,它将此位清零,并将第3 位置1,请求主机发送或者接收数据
    0xf7各位
;检测硬盘是否准备数据就绪
	mov dx, 0x1f7
__wait:
	in 	al, dx
	and al, 0x88	;取端口0x1f7的第3位和第7位
	cmp al, 0x08
	jnz __wait		;al不等于0x08跳转
  • 连续取数据
    0x1f0是硬盘接口的数据端口,一旦硬盘控制器准备数据就绪,从这个端口写入或者读取数据。
	mov  cx, 256	;读取字数
	mov  dx, 0x1f0	;16位数据端口
__read:
	in   ax, dx		;读取数据
	mov  [bx], ax	;数据传送到bx指向的偏移地址
	add  bx, 2
	loop __read		;cx不为0跳转

完整读取扇区子程序

;-----------------------------------------------------------------------
;从硬盘读取一个逻辑扇区
;输入:DI:SI=起始逻辑扇区号
;DS:BX=目标缓冲区地址
read_hard_disk_0:                                    
	push ax
	push bx
	push cx
	push dx			;保存现场
      
	mov dx,0x1f2
	mov al,1
	out dx,al		;读取的扇区数

    inc dx			;0x1f3
    mov ax,si		;si在被read_hard_disk_0被调用前已被初始化
    out dx,al		;LBA地址7~0

    inc dx			;0x1f4
    mov al,ah
    out dx,al		;LBA地址15~8

    inc dx			;0x1f5
    mov ax,di
    out dx,al		;LBA地址23~16

    inc dx			;0x1f6
    mov al,0xe0		;LBA28模式,主盘
    or al,ah		;ah = 0x00(LBA地址27~24) al = 0xe0		
    out dx,al

    inc dx			;0x1f7
    mov al,0x20		;读命令
    out dx,al

__waits:
	in al,dx
	and al,0x88
	cmp al,0x08
	jnz __waits		;不忙,且硬盘已准备好数据传输 

	mov cx,256		;总共要读取的字数
	mov dx,0x1f0
__readw:
	in ax,dx
	mov [bx],ax		;目标内存
	add bx,2
	loop __readw
	
	;恢复现场
	pop dx
	pop cx
	pop bx
	pop ax

	ret		;返回指令 返回时自动恢复IP寄存器的值

用户程序

通过前面的内容,我们知道ROM-BIOS将读取主引导扇区的内容,并将它加载到内存地址0x0000:0x7c00处,然后通过jmp指令跳转到那里执行。通常,主引导扇区的程序的功能是从硬盘的其他部分读取更多的内容加以执行,像Windows这样的操作系统就是一步一步运行起来的。
现在,假如有一个段分配如下图所示的程序存储在硬盘中,等待处理器加载运行。
应用程序段分配

用户程序头部

加载器与用户程序之间的协议
如上图所示,通常应用程序的头部需要包含一下信息:

  • 用户程序尺寸
  • 应用程序的入口点
    包括段地址和偏移地址。加载器并不清楚用户程序的分段情况,更不知道第一条要执行的指令在用户程序中的位置。因此,必须在头部给出第一条指令的段地址和偏移地址,这就是所谓的应用程序入口点(Entry Point)。
  • 段重定位表
    程序加载到内存后,段的地址必须根据加载地址重新确定,而在用户程序头部的段重定位表,将帮助加载器确定每个段在用户程序内的位置
  • 表项数

加载器会根据这些信息进行扇区读取、段重定位并跳转到指定入口运行程序

该程序涉及到的汇编指令如下:

  • SECTION/SEGMENT
    定义段指令
    格式:SECTION 段名称
    说明:一旦使用SECTION定义段,后面的内容就都属于该段,直至出现另一个段的定义
    子句:align=: 指令某个段的汇编地址的对齐方式
    vstart=:段中元素的汇编地址的计算方式
    NOTE
    Intel 处理器要求段在内存中的起始物理地址起码是16 字节对齐
    使用SECTION定义段时,段中元素的汇编地址是从整个程序开头计算的,当使用vstart=子句后,段中元素的汇编地址是从段开头计算的
  • section.段名称.start
    取得该段相对于整个程序开头的汇编地址
	;文件说明:用户程序头部
;=======================================================================
SECTION header vstart=0                     ;定义用户程序头部段
	;程序总长度[0x00] 
    program_length  dd program_end
    
    ;用户程序入口点
    code_entry      dw start                ;偏移地址[0x04]
                    dd section.code_1.start ;段地址[0x06] 

    ;段重定位表项个数[0x0a]
    realloc_tbl_len dw (header_end-code_1_segment)/4
    
    ;段重定位表           
    code_1_segment  dd section.code_1.start ;[0x0c]
    code_2_segment  dd section.code_2.start ;[0x10]
    data_1_segment  dd section.data_1.start ;[0x14]
    data_2_segment  dd section.data_2.start ;[0x18]
    stack_segment   dd section.stack.start  ;[0x1c]
    
    header_end:                

加载器(BootLoader)

那么我们接下来就来看看如何通过主引导扇区中的BOOT程序加载其他程序并读取到内存中运行!
可用空间
加载用户程序需要确定一个内存物理地址。如上图所示,物理地址0x0FFFF以下,是加载器及其栈的势力范围;物理地址A0000以上,是BIOS和外围设备的势力范围,有很多传统的老式设备将自己的存储器和只读存储器映射到这个空间。故可用的空间就位于0x10000~9FFFF,那我们就把用户程序加载到0x10000这个16字节对齐的地址运行吧。

;代码清单8-1
;文件名:c08_mbr.asm
;文件说明:硬盘主引导扇区代码(加载程序)
	
	;equ伪指令用于声明常数,作用类似于'define'       
	app_lba_start equ 100

;SECTION 用于定义段,后面跟段名称
;align = 16,用于指定段的对齐方式为16字节对齐
;Intel 处理器要求段在内存中的起始物理地址起码是16 字节对齐的
;'vstart='子句用于指定段内元素的偏移地址的计算起始地址
SECTION mbr align=16 vstart=0x7c00                                     

	;设置堆栈段和栈指针 
	mov ax,0      
	mov ss,ax
	mov sp,ax					;ss = sp = 0x0000
	;phy_base dd 0x00010000,定义在段末尾,减少对程序的影响			
	mov ax,[cs:phy_base]		;计算用于加载用户程序的逻辑段地址 
	mov dx,[cs:phy_base+0x02]	;phy_base是双字数据,用dx存储高16位	
	mov bx,16        
	div bx						;商在ax中,余数在dx中 ax = 0x1000            
	mov ds,ax					;DSES指向该段以进行操作
	mov es,ax                        
    
	;以下读取程序的起始部分 
	xor di,di					;LBA2812位
	mov si,app_lba_start		;程序在硬盘上的起始逻辑扇区号 
	xor bx,bx 					;加载到DS:0x0000;过程调用 
	call read_hard_disk_0		;读取一个扇区
      
	;以下判断整个程序有多大
	mov dx,[2]					;长度高16位 
	mov ax,[0]					;长度低16位
	mov bx,512					;512字节每扇区
	div bx						;32位除法-商在ax中,余数在dx中
	cmp dx,0
	jnz __read_b1				;未除尽,因此结果比实际扇区数少1 
	dec ax						;已经读了一个扇区,扇区总数减1 
__read_b1:
	cmp ax,0					;考虑实际长度小于等于512个字节的情况 
	jz __direct					;全部读取完成,执行重定位
         
	;读取剩余的扇区
	push ds                     ;以下要用到并改变DS寄存器 
	mov cx,ax                   ;循环次数(剩余扇区数)
__read_b2:
	mov ax,ds
	add ax,0x20                 ;得到下一个以512字节为边界的段地址
	mov ds,ax  
                              
	xor bx,bx                   ;每次读时,偏移地址始终为0x0000 
	inc si                      ;下一个逻辑扇区 
	call read_hard_disk_0
	loop __read_b2              ;循环读,直到读完整个功能程序 

	pop ds                      ;恢复数据段基址到用户程序头部段 
      
;计算入口点代码段基址 
__direct:
	mov dx,[0x08]				;入口地址高16位
    mov ax,[0x06]				;入口地址低16位
    call calc_segment_base
    mov [0x06],ax              	;回填修正后的入口点代码段基址 
      
    ;开始处理段重定位表
    mov cx,[0x0a]               ;需要重定位的项目数量
    mov bx,0x0c                 ;重定位表首地址
          
__realloc:
    mov dx,[bx+0x02]            ;32位地址的高16位 
    mov ax,[bx]
    call calc_segment_base
    mov [bx],ax                 ;回填段的基址
    add bx,4                    ;下一个重定位项(每项占4个字节) 
    loop __realloc 
      
	jmp far [0x04]                  ;转移到用户程序  
 
;-----------------------------------------------------------------------
;从硬盘读取一个逻辑扇区
;输入:DI:SI=起始逻辑扇区号
;DS:BX=目标缓冲区地址
read_hard_disk_0:                                    
	push ax
	push bx
	push cx
	push dx			;保存现场
      
	mov dx,0x1f2
	mov al,1
	out dx,al		;读取的扇区数

    inc dx			;0x1f3
    mov ax,si		;si在被read_hard_disk_0被调用前已被初始化
    out dx,al		;LBA地址7~0

    inc dx			;0x1f4
    mov al,ah
    out dx,al		;LBA地址15~8

    inc dx			;0x1f5
    mov ax,di
    out dx,al		;LBA地址23~16

    inc dx			;0x1f6
    mov al,0xe0		;LBA28模式,主盘
    or al,ah		;ah = 0x00(LBA地址27~24) al = 0xe0		
    out dx,al

    inc dx			;0x1f7
    mov al,0x20		;读命令
    out dx,al

__waits:
	in al,dx
	and al,0x88
	cmp al,0x08
	jnz __waits		;不忙,且硬盘已准备好数据传输 

	mov cx,256		;总共要读取的字数
	mov dx,0x1f0
__readw:
	in ax,dx
	mov [bx],ax		;目标内存
	add bx,2
	loop __readw
	
	;恢复现场
	pop dx
	pop cx
	pop bx
	pop ax

	ret		;返回指令 返回时自动恢复IP寄存器的值

;-----------------------------------------------------------------------
;计算16位段地址
;输入:DX:AX=32位物理地址
;返回:AX=16位段基地址 
calc_segment_base:                                                        
	push dx                          
	
	add ax,[cs:phy_base]
	adc dx,[cs:phy_base+0x02]
	shr ax,4
	ror dx,4
	and dx,0xf000
	or ax,dx
         
	pop dx
	ret

;-----------------------------------------------------------------------
phy_base dd 0x10000             ;用户程序被加载的物理起始地址
         
times 510-($-$$) db 0
                 db 0x55,0xaa

难受,后面再补充吧,晚安Bro

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2092943.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

240831-Qwen2-VL-7B/2B部署测试

A. 运行效果 B. 配置部署 如果可以执行下面就执行下面: pip install githttps://github.com/huggingface/transformers accelerate否则分开执行 git clone https://github.com/huggingface/transformers cd transformers pip install . accelerate随后&#xff0…

8.27FLEX,BISON

RC ParseStage::handle_request(SQLStageEvent *sql_event) 这个意思是返回类型是RC,然后用到的函数来自 ParseStage,::就是用来标识作用域的,函数名是handle_request,是ParseStage里的函数 FLEX BISON

vue.js项目实战案例详细源码讲解

​ 大家好,我是程序员小羊! 前言: 为帮助大家更好地掌握Vue.js项目的开发流程,我将为你讲解一个完整的Vue.js实战案例,并提供详细的源码解析。这个案例将涵盖从项目创建到实现各种功能模块的全过程,适合用于…

组织培训如何分组?

在组织培训活动时,合理分组是提高效率和参与度的关键。云分组小程序提供了一个简单而有效的解决方案,帮助组织者快速、公平地将参与者分配到不同的小组中。以下是使用云分组小程序进行培训分组的详细步骤:一、创建分组 1. 打开云分组小程序。…

入坑大模型18个月的反思与贩私

前几天开完一个有高层参加的会议,会后组里的技术大佬直接就开喷“要规划没规划,整天只知道对着几个糊弄老板的榜使劲刷”。我下意识地赶紧去拉住他,低声对他讲“你声音太小了,老板听不到的,回头我领你去大厦的保安室&a…

Docker容器技术(下)超多好上手的实验,保姆级教程

文章目录 Docker数据卷管理及优化为什么要使用数据卷bind mount数据卷docker managed数据卷Data Volume Container(数据卷容器)bind mount数据卷 VS docker managed数据卷备份与迁移数据卷 Docker的安全优化Docker的资源限制限制CPU的使用限制CPU的使用量…

RAG重磅升级:DSF带来特定领域精准提升的全新方案!

检索增强生成(Retrieval-Augmented Generation, RAG)是一种结合了检索(Retrieval)和生成(Generation)能力的框架,通过从背景数据中检索相关信息来增强模型的生成输出。在当前的大型语言模型&…

Linux 安装mysql 数据库通用教程(rpm傻瓜安装)

通用教程:Centos7.9安装mysql8.0.39(使用rpm 安装) 目录 前言 下载镜像源 删除或查看旧版本 安装mysql 启动mysql mysql授权远程登录 前言 在本篇博客中,我将向您展示如何在CentOS 7.9系统上通过RPM包安装特定版本的MySQL…

神经网络搭建实战与Sequential的使用

一、需要处理的图像 二、对上述图片用代码表示: import torch from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linearclass SUN(nn.Module):def __init__(self):super(SUN, self).__init__()self.conv1 Conv2d(3, 32, 5, padding2)self…

解决移动端使用Vant van-overlay 遮罩层导致的弹窗不可滚动问题

项目场景 在游戏门户网站需要根据弹出层列举出自己背包的饰品,然后进行选择置换。 问题描述 例如:在PC端的时候能物品过多的时候能正常左右滚动,而且启用Google的开发者工具进行查看的时候也是能正常滚动,但是在手机端访问的时候…

持续集成与持续部署(CI/CD)的深入探讨

在现代软件开发中,持续集成(CI)和持续部署(CD)已成为不可或缺的实践。这些方法旨在加快软件交付的速度,同时提高软件的质量和稳定性。通过CI/CD,开发团队可以频繁地将代码更改集成到主分支&…

Mate 60、Mate X5和Pocket 2新增AI修图功能:AI消除能力效果惊艳

你有没有试过拍照的时候不小心把路人拍进来,或者拍风景的时候有煞风景的事物闯入镜头中?有些美好的画面稍纵即逝、有些景点不复存在,看着略带瑕疵的照片,多少会感觉有点遗憾。 最近Mate 60、Mate X5和Pocket 2三款机型都进行了鸿…

Python sys.path与-m参数的作用

文章目录 Python sys.path与-m参数的作用sys.path作用验证结论 Python sys.path与-m参数的作用 sys.path作用 sys.path‌当试图导入一个模块时,Python解释器会按照sys.path中列出的路径顺序搜索对应的模块文件。 sys.path的组成包括当前目录(即包含你…

二叉树相关练习

二叉树相关oj题: 对称二叉树 解题思路:判断一棵树是否轴对称,先判断左右子树结构是否相同,结构相同的情况下再判断对应的val是否轴对称,判断根节点的左右子树,再判断根节点的左右子树的左右子树是否轴对称…

《潮骚》爱恋的心如海潮般骚动,又如大海般广袤平静

《潮骚》爱恋的心如海潮般骚动,又如大海般广袤平静 三岛由纪夫(1925-1970),日本当代小说家、剧作家、记者、电影制作人和电影演员,右翼分子。主要作品有《金阁寺》《潮骚》《丰饶之海》等。曾3次获诺贝尔文学奖提名,属…

类图的关联关系

类图关联关系分为单向关联关系,双向关联关系,自关联关系 单向关联关系 是一个类的属性或方法被另外一个类引用,二者之间用一个箭头表示 比如顾客类和地址类,每个顾客都有一个地址 单向关联关系在UML图中两个类之间用单向箭头表…

AI绘图提示词/咒语/词缀/关键词使用指南(Stable Diffusion Prompt 最强提示词手册)

一、为什么学习AI绘画关键词 在人工智能技术飞速发展的今天,AI绘画已成为艺术领域的一大热点。学习AI绘画关键词,不仅有助于我们掌握这一新兴技术,还能拓宽我们的创作思路,实现艺术与技术的完美融合。以下是学习AI绘画关键词的几…

基于python的人力资源管理系统/基于django的OA系统的设计与实现

摘 要 随着当今社会的发展,时代的进步,各行各业也在发生着变化,比如人力资源管理这一方面,利用网络已经逐步进入人们的生活。传统的人力资源管理,都是员工去公司查看部门信息、招聘信息,这种传统方式局限性…

C++入门基础知识44——【关于C++ 判断】

成长路上不孤单😊【14后,C爱好者,持续分享所学,如有需要欢迎收藏转发😊😊😊😊😊😊😊!!!!&#xff…

【MYSQL】5 性能优化

1步骤 2查看系统性能参数 在MySQL中,可以使用 SHOW STATUS 语句查询一些MySQL数据库服务器的 性能参数 、 执行频率 。 SHOW STATUS语句语法如下: SHOW [GLOBAL|SESSION] STATUS LIKE ‘参数’; 一些常用的性能参数如下: • Connections&…