x86操作外部组件总结

418 阅读13分钟

X86总体结构

image.png

image.png

地址总线:指的是CPU寻址通过哪些引脚寻址,比如图中只有10根引线,那么CPU只能寻址2^10的范围。注意这里是寻到内存地址,也就是找到哪一个存储单元上。x86地址总线是32位,因此可以寻址2^32的范围,即4GB。

这里有一个误区需要着重说明一下,要分清楚内存和物理内存的区别。内存是一个大的概念,而物理内存直观点说就是我们实际上插到主板上的内存条。地址总线和物理内存并不是一一对应的。从图2可以看到,地址总线实际上还连着外部输入设备内存,因此实际上的内存总线地址分配应该是同时分配给rom、某个外设的内存中和物理内存条上。CPU给地址总线输出一个地址,这个地址会根据地址分配电路进行转化,最后落入到哪个存储单元是事先已经分配好的,还得看地址总线把地址指向哪块内存了。这就是安装了 4GB 内存,电脑中只显示 3.8GB 左右的原因。以实模式下举例:

1679420512798.png

上图为x86实模式下内存布局,x86有20条地址线,故其可以访问1MB的内存空间,其中只有0~0x9FFFF部分是映射到DRAM上的,也就是我们的物理内存上,因此实模式下内存条上只使用了640KB,剩下的地址放在了显存,ROM上。顺便说一下,0XF0000~0XFFFFF处是BIOS例程,被写入了ROM中,因此访问这块地址就是访问ROM了。

数据总线:CPU与内存之间交互数据的引线,通过地址总线找到了哪一块存储单元后,需要把这块内存里面的内容读入CPU中,这时候需要用数据总线传输数据,数据总线是双向的。x86数据总线是32位,因此一次传输可以传输2^32位数据,即4字节。

控制总线:CPU对外部器件的控制时通过控制总线进行的。这里控制总线是一个统称,控制总线是一些不同控制线的集合。有多少根控制总线,就意味这CPU提供了对外部器件的多少中控制。

寄存器类型

CPU 中的寄存器大致上分为两大类

第一类类是其内部使用的,对程序员不可见 “是否可见”不是说寄存器是否能看得见,是指程序员是否能使用 CPU 内部有其自己的运行机制,是按照某个预定框架进行的,为了 CPU 能够运行下去,必然会有 些寄存器来做数据的支撑,给 CPU 内部的数据提供存储空间。这 部分对外是不可见的,我们无法使用它们,比如全局描述符表寄存器 GDTR、中断描述符表寄存器 IDTR、局部描述符表寄存器 LDTR任务寄存器 TR、控制寄存器 CRO 、指令指针寄存器四、标志寄存器 flags 、调试寄存器 DR0~DR7

另一类是对程序员可见的寄存器 我们进行汇编语言程序设计时,能够直接操作的就是这些寄存器,如段 存器、通用寄存器

![}G29[]4`YRM0%GBS}O9I_O.png

x86与外设通信

CPU与外设进行通信是通过IO接口实现的,IO 接口是连接 CPU 与外部设备的逻辑控制部件。IO接口的功能如下:

  1. 设置数据缓冲,解决 CPU 与外设的速度不匹配
  2. 设置信号电平转换电路
  3. 设置数据格式转换
  4. 设置时序控制电路来同步 CPU 和外部设备
  5. 提供地址译码 CPU 同多个硬件打交道,每个硬件要反馈的信息很多,所以一个 IO 接口必须包含多个端口,即 IO接口上的寄存器来存储这些信息内容 但同 时刻,只能有一个端口和 CPU 数据交换,这就需要IO端口提供地址译码电路,使 CPU 可以选中某个端口,使其可以访问数据总线。

同一时刻CPU只能与一个IO接口通信,因此需要通过南桥芯片来决定与哪个接口通信。桥内部集成了 IO 接口,如井口硬盘 PATA (就是我们平时所说的 IDE 硬盘)、串口硬盘 SAIA USBPCI 设备、电源管理,PCI(就是主板上可以插的槽)等接口 。

IO接口在诞生之初,就被设计成要通过寄存器的方式同CPU信,其内部有专用于数据交互的寄存器,为了区别于CPU内部的寄存器,IO接口中的寄存器就称为端口。IO接口是连接CPU和硬件的桥梁,一端是CPU,另一端是硬件,端口是IO接口开放给CPU的接口,一般的IO接口都有一组端口,端口也是寄存器,寄存器就有数据宽度,有8位、16位、32位,各个设备是不一样的,看厂商自己安排了。

IA32体系系统(32位操作系统)中,因为用于存储端口号的寄存器是16位的,所以最大有65536个端口,即65535(0xff)。8088/8086CPU采用独立编址方式管理I/O端口,内存空间00000~HFFFFFH(1MB),使用A0A19全部20根地址线寻址。它使用地址信号线A0~A15,最多能够管理64K个端口, 端口地址范围0000H~FFFFH,有专用的控制信号 IOR 和 IOW 或IO/ M 、 RD 和 WR ; 专用指令IN和OUT。

由于IO映射方式是独立编址的,没办法采用访问内存的方式寻址(没有做内存映射),只能通过端口寄存器的方式寻址,因此CPU提供in/out方式访问IO空间(外设)。访问端口,实际上就是操作对应的端口寄存器。CPU与I/O接口之间通过系统总线传输信息,包括地址信息、控制信息和 数据信息。I/O接口与设备之间通过串行或并行方式交换信息,包括数据 信息、控制信息和状态信息。

显卡、显存、显示器

无底是哪种显卡,它提供给我们的可编程接口都是样的:IO端口和显存。

显存模式

显存的映射位置为0XC0000~0XC7FFF,支持三种模式,文本模式、黑白图形模式、彩色图形模式。从起始地址OxB8000~OxBFFFF ,这片32KB大小的内存区域是用于文本显示,这也是linux黑框框里主要使用的内存区域,也是我们要操作的区域。

显卡在加电后,默认就置为模式80x25,80x25表示80字符,共25行。也就是一屏可以打印2000宇符。我们也在这个默认模式下工作了。

1679502218880.png

如图,在屏幕上显示的每个字符占两个字节,低字节为字符的ASCII码,低4位是字符前景色,高4位是字符的背景色。 例如,我要往屏幕上写上第一个A字符,那么可以这样:

; 设置段寄存器gs为0xb800,此时段基址为0xb8000
mov ax, 0xb800
mov gs, ax

; 设置段基址为gs寄存器,段偏移为0,此时写入的物理地址为OxB8000,也就是黑框框最左上角的位置
; 写入内容为0x41,字符‘A’, 0x07表示黑底白字
mov word ptr [gs:0000h],  0x0741

IO模式

硬盘

硬盘工作原理

硬盘分为机械硬盘与固态硬盘

IO模式

硬盘也是外设的一种,因此CPU也需要通过IO端口才能操作硬盘。因此有了硬盘控制器。

一块主板上能插4块IDE(PATA)硬盘,所以主板上提供两个IDE插槽。这两个接口也是以0为起始编号的,1个称为 IDEO ,另一个称为IDEl 。不过按ATA说法,这两个插槽称为通道,IDEO叫作Primary通道,IDEI叫作Secondary通道。例如在bochs中的配置:

ata0: enabled=true,ioaddr1=Ox1f0,ioaddr2=0x3f0,irq=14
ata0-master: type=disk, path=".. .", mode=flat
ata0-slave: type=none
ata1: enabled=true,ioaddr1=0x170,ioaddr2=0x370, irq=15
ata1-master: type=none
ata1-slave: type=none

这里表现了共有两个IDE插槽ata0和ata1,其中ata0插槽的master槽已经插了硬盘,其io操作端口为0x1f0,ata1插槽没有插硬盘,但是操作端口为0x170

7L38``1QG23XFC$Y_1RF6LR.png

以上是硬盘控制器主要端口寄存器,想要读写硬盘就得靠这些寄存器操作,下面主要介绍primary通道下的端口,secondary通道端口用法也是一样的

0x1F0:数据寄存器,无论是写入的数据还是读出的数据都在这个端口取。16位大小,因此一次能取两个字节内容

0x1F1:读模式下是错误信息寄存器,如果硬盘操作失败,这里会保存着错误代码。写模式下,这里可以指定额外参数

0x1F2:扇区寄存器,这里写入的内容表示待读取或待写入的扇区数。硬盘每操作完一个扇区,就会将此寄存器数-1。因此如果操作失败或者中断了,这里面的数值代表尚未完成的扇区数。此寄存器大小为8位,最大值为255,若指定为0则表示要操作256个扇区。

0x1F3~0x1F5:LBA寻址下的低,中,高八位,按顺序写入就能找到对应的柱面-磁头-扇区位置

0x1F6:divece寄存器,每位作用见下图

1RFPWKPC}BRHOTB_IVU_C`5.png

0x1F7:在读取内容时为status寄存器,每一位作用见下图。平常时为command寄存器,常用命令有:0xEC(硬盘识别),0x20(读扇区),0x30(写扇区)

MO_XDTA4UV2G75CQ%1CTD.png

读磁盘的一般操作顺序如下,举例说明:

; 首先明确要操作的通道,这里选择primary通道,因此选择0x1f*的端口
; 1、通过0x1f2端口指定要读的扇区,这里读一个扇区
    mov dx, 0x1f2
    mov al, 1
    out dx, al

; 2、通过0x1f3~0x1f5端口输入LBA寻址,确认读的扇区,这里读0柱面0磁道1扇区的数据,即MBR区
    mov ecx, 1
    inc dx
    mov al, cl
    out dx, al

    inc dx
    mov al, ch
    out dx, al

    inc dx
    shr ecx, 16
    mov al, cl
    out dx, al

; 3、通过0x1f6端口指定参数
    inc dx
    shr ecx, 8
    mov al, 0b1110_0000
    or al, cl
    out dx, al

; 4、通过0x1f7输入命令,操作硬盘(此时0x1f7作为command寄存器)
    inc dx
    mov al, 0x20
    out dx, al

; 5、此时硬盘已经开始读取工作,0x1f7成为status寄存器,需要不断循环判它来进行读取
.check:
    in al, dx
    and al, 0x08     ; 第4为表示硬盘控制器已经准备好传输数据
    cmp al, 0x08
    jnz .check
 
; 6、硬盘已经准备好,可以读取数据了
    mov dx, 0x1f0
    mov cl, 256              ;一个扇区大小是512字节,每次0x1f0只能读2字节,因此需要循环256次
.lp:
    in ax, dx
    mov [edi], ax           ; edi寄存器里存的是你需要读取到的地址
    add edi, 2
    
    loop .lp

操作内存

内存也是一种外设,需要我们去手动获取物理内存有哪些。可以通过BIOS中断去进行操作。由于BIOS中断在进入保护模式后就会被覆盖,因此需要在实模式时就进行中断操作。具体做法为利用BIOS中断Ox15子功能Oxe820获取内存。

BIOS中断Ox15的子功能0xE820能够获取系统的内存布局,检查会返回地址范围描述符(ARDS),具体值如下

1679668275307.png

由于是32位系统,只会用到低32位属性。同时给出调用中断的输入和输出

image.png

操作示例如下:

; BIOS中断只在实模式下起作用
[BITS 16]
    ; 计数有几个ARDS
    ARDS_COUNT equ 0x1200
    ; 搜索出来的ARDS存储在这片内存中
    ARDS_BUFFER equ 0x1201

start:
    ; 设置ds,es段寄存器
    mov ax, 0
    mov ds, ax
    mov es, ax
    
    ; 设置di段偏移
    xor ebx, ebx
    mov di, ARDS_BUFFER

.loop:
    ; 标准设置
    mov eax, 0xE820
    mov ecx, 20
    mov edx, 0x534d4150
    ; 开启中断
    inc 0x15
    
    ; CF1,代表出故障了
    jc memory_check_error
    
    ; 检索结果已经放入di地址内,需要+20字节到下一个地址
    add di, 20
    ; ARDS个数+1
    inc dword [ARDS_COUNT]
    
    cmp ebx, 0
    jne loop

memory_check_error:
    jmp $

最后展示在64M内存系统下,将ARDS打印出来的内容

VPE1(26$ML2{UQP)Y8@BWRG.png

可以利用的有x0~0x9fc00内存区域以及0x100000~0x3fe0000两块内存,其中最大一块内存容量为62MB,也就是后续我们需要进行内存管理的部分。一般操作系统的代码放在第一个内存块中。值得注意的是,第三块内存块0xf0000~0x100000这块内存是一个内存空洞,是历史上遗留下来的问题。主要是为了解决当时有些ISA设备需要将这块地区作为缓冲区,所以就一直把这片区域空出来了。

中断代理芯片

中断处理流程

中断处理流程主要如下:

CPU 外:外部设备的中断由中断代理芯片接收,处理后将该中断的中断向量号发送到 CPU

CPU 内: CPU 执行该中断向量号对应的中断处理程序。

  1. 处理器根据中断向量号定位中断门描述符。
  2. 处理器进行特权级检查。
  3. 执行中断处理程序。

1679918866938.png

中断压栈过程

中断压栈过程可分为跨特权级处理和不跨特权级处理。不跨特权级处理时压栈图如下

1679919002667.png

此时原代码和中断处理所用的栈是一样的,因此不区分新旧栈,栈中只压了EFLAGS,CS,EIP三个值。

跨特权级压栈如下

1679919111317.png

此时原代码和中断处理所用的栈是一样的,因此此时的栈为新栈(中断所用的栈),栈中压了SS,ES,EFLAGS,CS,EIP五个值。

通常能够压入错误码的中断属于中断向量号在0~32之内的异常,而外部中断(中断向量号在32~255之间)和int软中断并不会产生错误码。通常我们并不用处理错误码。

可编程中断控制器8259A

对于外部中断,并不是由CPU直接仲裁管理的,而是通过两根信号线接收

1679921455510.png

可屏蔽中断是通过时INTR引脚进入CPU的,外部设备如硬盘、网卡等发出的中断都是可屏蔽中断。8259A则是对这一系列外设进行管理,最后警告仲裁后将中断通过INTR输入CPU。8259A用于管理和控制可屏蔽中断,它表现在屏蔽外设中断,对它们实行优先级判决,向CPU提供中断向量号等功能。个人电脑上一般是两片8259A级联,结构如下。

C2E13VCN6_5_@@D$RZ7982O.png

8259A内部结构如下

8E_5FQ2QUA6QLUJREN16`{6.png

其中的寄存器和信号说明如下:

  • INT: 8259A选出优先级最高的中断请求后,发信号通知CPU
  • INTA: INT Acknowledge,中断响应信号位于8259A中的INTA接收来自CPU的INTA接口的中断响应信号。
  • IMR:Interrupt Mask Register ,中断屏蔽寄存器,宽度是8位,用来屏蔽某个外设的中断
  • IRR: Interrupt Request Register,中断请求寄存器,宽度是8位。它的作用是接受经过IMR寄存器过滤后的中断信号并锁存,此寄存器中全是等待处理的中断,“相当于” 5259A维护的未处理中断信号队列
  • PR: Priority Resolver,优先级仲裁器 当有多个中断同时发生,或当有新的中断请求进来时,将它与当前正在处理的中断进行比较,找出优先级更高的中断。
  • ISR: In-Servi Register ,中断服务寄存器,宽度是8位。当某个中断正在被处理时,保存在此寄存器中。

简单介绍下8259A工作原理:外设发出的中断会通过IRQ送入8259A,内部先通过IMR进行屏蔽判断(被屏蔽的中断位会被置1),然后进入IRR中,将对应的IR位置1。优先权判断器会根据中断的优先级进行判断,然后通过INT接口发送INTR信号(8259A第一次给CPU发送信号)。CPU会通过INTA给8259A一个收到回复信号。然后CPU再次发送一个INTA信号给8259A,这次请求真正的中断向量号,而8259A就通过数据总线发送中断向量号给CPU。此时如果设置了自动EOI,那么在CPU发完第二个INTA后,8259A会自动将此中断ISR对应位置0,否则中断处理完需要手动发送EOI。

8259A被称为可编程中断控制器,其拥有两组可以操作的寄存器,一组是初始化命令寄存器组,叫ICW1~ICW4;另一组寄存器是操作命令寄存器组,叫OCW1~OCW3。因此8259A编程也分为初始化和操作两部分。由于我们使用两片8259A级联,因此需要同时操作主片和从片的寄存器组。

初始化

ICWl用来初始化8259A的连接方式和中断信号的触发方式。注意,ICWl需要写入到主片的Ox20端口和从片的OxAO端口,结构如下。

1679933673405.png

  • IC4表示是否要写入ICW4,这表示并不是所有的ICW初始化控制字都需要用到。IC4时表示需要在后面写入ICW4 ,为0则不需要。注意,x86系统IC4必须为1。
  • SNGL表示是否级联,1表示单片,0表示级联
  • ADI表示call address interval ,用来设置8085的调用时间间隔,x86不需要设置。
  • LTIM表示level/edge triggered mode,用来设置中断检测方式,LTIM 表示边沿触发,LTIM表示电平触发。
  • 4~7位固定为0001

ICW2用来设置起始中断向量号,注意ICW2需要写入到主片的Ox21端口和从片的OxAl端口。结构如下

1679933973326.png

0~2为不需要填写,固定为000。3~7位为对应的起始中断向量号,结果需要左移三位。例如:设置ICW2为0x20,此时3~7位的值为0x4,对应的起始中断向量号需要*8,因此结果为0x4*8 = 0x20。

ICW3用来设置主片和从片用哪个IRQ接口互连,只在级联时候用(ICW1的SNGL为0时)。ICW3需要写入主片的Ox21端口及从片的OxAl端口,且两个片写法不一样。主片ICW3格式如下:

1679934309517.png

对于主片,ICW3中置1的那一位对应的IRQ接口用于连接从片,若为0则表示接外部设备。从片格式如下:

1679934384257.png

只需要在0~2位上输入主片做连接接口的IRQ位就行,例如:主片选择使用IRQ2位作为级联,那么从片的ICW3就是0b0000_0010

ICW4用于设置8259A的工作模式,当ICWl中的IC4时才需要ICW4。注意,ICW4需要写入主片的Ox21及从片的OxAl端口,结构如下:

1679934869565.png

  • 5~7位固定为0
  • SFNM表示特殊全嵌套模式(Special Fully Nested Mode),若SFNM,则表示全嵌套模式,若SFNM为1,则表示特殊全嵌套模式。
  • BUF表示本8259A芯片是否工作在缓冲模式。BUF为0则工作非缓冲模式,BUF为1则工作在缓冲模式。
  • AEOI表示自动结束中断(Auto End Of interrupt)
  • µPM在x86下固定为1

操作

这里只介绍OCW1,因为这个比较重要。OCWl用来屏蔽连接在8259A上的外部设备的中断信号,实际上就是把OCWl写入了即IMR寄存器。注意,OCWl要写入主片的Ox21或从片的OxAl端口,结构如下:

1679935203332.png

M0~M7对应8259A的IRQO~IRQ7。某位为1,对应的IRQ上的中断信号就被屏蔽了。否则某位为0的话,对应的IRQ中断信号则被放行。

下面给出示例代码

    ; 向主ICW1发送0b0001_0001
    mov al, 11h
    out 20h, al

    ; 向从ICW1发送0b0001_0001
    out 0a0h, al

    ; 向主ICW2发送0b0010_0000
    mov al, 20h
    out 21h, al

    ; 向从ICW2发送0b0010_0000
    mov al, 28h
    out 0a1h, al

    ; 向主ICW3发送0b0000_0100,代表用IRQ2级联
    mov al, 04h
    out 21h, al

    ; 向从ICW3发送0b0000_0010
    mov al, 02h
    out 0A1h , al

    ; 向主ICW4发送0b0000_0011
    mov al, 03h
    out 021h, al

    ; 向从ICW4发送0b0000_0011
    out 0A1h, al

    ; 屏蔽所有中断,只接收键盘中断和时钟中断
    mov al, 11111100b
    out 21h, al

    ; 屏蔽从芯片所有中断响应
    mov al, 11111111b
    out 0A1h, al
    

----------待更新