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

2024-09-01 00:12
文章标签 访问 x86 架构 控制 硬盘

本文主要是介绍X86架构(六)——硬盘访问与控制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在前面几节中,我们总是通过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, dxand al, 0x88	;取端口0x1f7的第3位和第7位cmp al, 0x08jnz __wait		;al不等于0x08跳转
  • 连续取数据
    0x1f0是硬盘接口的数据端口,一旦硬盘控制器准备数据就绪,从这个端口写入或者读取数据。
	mov  cx, 256	;读取字数mov  dx, 0x1f0	;16位数据端口
__read:in   ax, dx		;读取数据mov  [bx], ax	;数据传送到bx指向的偏移地址add  bx, 2loop __read		;cx不为0跳转

完整读取扇区子程序

;-----------------------------------------------------------------------
;从硬盘读取一个逻辑扇区
;输入:DI:SI=起始逻辑扇区号
;DS:BX=目标缓冲区地址
read_hard_disk_0:                                    push axpush bxpush cxpush dx			;保存现场mov dx,0x1f2mov al,1out dx,al		;读取的扇区数inc dx			;0x1f3mov ax,si		;si在被read_hard_disk_0被调用前已被初始化out dx,al		;LBA地址7~0inc dx			;0x1f4mov al,ahout dx,al		;LBA地址15~8inc dx			;0x1f5mov ax,diout dx,al		;LBA地址23~16inc dx			;0x1f6mov al,0xe0		;LBA28模式,主盘or al,ah		;ah = 0x00(LBA地址27~24) al = 0xe0		out dx,alinc dx			;0x1f7mov al,0x20		;读命令out dx,al__waits:in al,dxand al,0x88cmp al,0x08jnz __waits		;不忙,且硬盘已准备好数据传输 mov cx,256		;总共要读取的字数mov dx,0x1f0
__readw:in ax,dxmov [bx],ax		;目标内存add bx,2loop __readw;恢复现场pop dxpop cxpop bxpop axret		;返回指令 返回时自动恢复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,axmov 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,0jnz __read_b1				;未除尽,因此结果比实际扇区数少1 dec ax						;已经读了一个扇区,扇区总数减1 
__read_b1:cmp ax,0					;考虑实际长度小于等于512个字节的情况 jz __direct					;全部读取完成,执行重定位;读取剩余的扇区push ds                     ;以下要用到并改变DS寄存器 mov cx,ax                   ;循环次数(剩余扇区数)
__read_b2:mov ax,dsadd ax,0x20                 ;得到下一个以512字节为边界的段地址mov ds,ax  xor bx,bx                   ;每次读时,偏移地址始终为0x0000 inc si                      ;下一个逻辑扇区 call read_hard_disk_0loop __read_b2              ;循环读,直到读完整个功能程序 pop ds                      ;恢复数据段基址到用户程序头部段 ;计算入口点代码段基址 
__direct:mov dx,[0x08]				;入口地址高16位mov ax,[0x06]				;入口地址低16位call calc_segment_basemov [0x06],ax              	;回填修正后的入口点代码段基址 ;开始处理段重定位表mov cx,[0x0a]               ;需要重定位的项目数量mov bx,0x0c                 ;重定位表首地址__realloc:mov dx,[bx+0x02]            ;32位地址的高16位 mov ax,[bx]call calc_segment_basemov [bx],ax                 ;回填段的基址add bx,4                    ;下一个重定位项(每项占4个字节) loop __realloc jmp far [0x04]                  ;转移到用户程序  ;-----------------------------------------------------------------------
;从硬盘读取一个逻辑扇区
;输入:DI:SI=起始逻辑扇区号
;DS:BX=目标缓冲区地址
read_hard_disk_0:                                    push axpush bxpush cxpush dx			;保存现场mov dx,0x1f2mov al,1out dx,al		;读取的扇区数inc dx			;0x1f3mov ax,si		;si在被read_hard_disk_0被调用前已被初始化out dx,al		;LBA地址7~0inc dx			;0x1f4mov al,ahout dx,al		;LBA地址15~8inc dx			;0x1f5mov ax,diout dx,al		;LBA地址23~16inc dx			;0x1f6mov al,0xe0		;LBA28模式,主盘or al,ah		;ah = 0x00(LBA地址27~24) al = 0xe0		out dx,alinc dx			;0x1f7mov al,0x20		;读命令out dx,al__waits:in al,dxand al,0x88cmp al,0x08jnz __waits		;不忙,且硬盘已准备好数据传输 mov cx,256		;总共要读取的字数mov dx,0x1f0
__readw:in ax,dxmov [bx],ax		;目标内存add bx,2loop __readw;恢复现场pop dxpop cxpop bxpop axret		;返回指令 返回时自动恢复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,4ror dx,4and dx,0xf000or ax,dxpop dxret;-----------------------------------------------------------------------
phy_base dd 0x10000             ;用户程序被加载的物理起始地址times 510-($-$$) db 0db 0x55,0xaa

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

这篇关于X86架构(六)——硬盘访问与控制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1125394

相关文章

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

系统架构设计师: 信息安全技术

简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 系统架构设计师: 信息安全技术前言信息安全的基本要素:信息安全的范围:安全措施的目标:访问控制技术要素:访问控制包括:等保

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者

控制反转 的种类

之前对控制反转的定义和解释都不是很清晰。最近翻书发现在《Pro Spring 5》(免费电子版在文章最后)有一段非常不错的解释。记录一下,有道翻译贴出来方便查看。如有请直接跳过中文,看后面的原文。 控制反转的类型 控制反转的类型您可能想知道为什么有两种类型的IoC,以及为什么这些类型被进一步划分为不同的实现。这个问题似乎没有明确的答案;当然,不同的类型提供了一定程度的灵活性,但

NGINX轻松管理10万长连接 --- 基于2GB内存的CentOS 6.5 x86-64

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=190176&id=4234854 一 前言 当管理大量连接时,特别是只有少量活跃连接,NGINX有比较好的CPU和RAM利用率,如今是多终端保持在线的时代,更能让NGINX发挥这个优点。本文做一个简单测试,NGINX在一个普通PC虚拟机上维护100k的HTTP