X86总体结构
地址总线:指的是CPU寻址通过哪些引脚寻址,比如图中只有10根引线,那么CPU只能寻址2^10的范围。注意这里是寻到内存地址,也就是找到哪一个存储单元上。x86地址总线是32位,因此可以寻址2^32的范围,即4GB。
这里有一个误区需要着重说明一下,要分清楚内存和物理内存的区别。内存是一个大的概念,而物理内存直观点说就是我们实际上插到主板上的内存条。地址总线和物理内存并不是一一对应的。从图2可以看到,地址总线实际上还连着外部输入设备内存,因此实际上的内存总线地址分配应该是同时分配给rom、某个外设的内存中和物理内存条上。CPU给地址总线输出一个地址,这个地址会根据地址分配电路进行转化,最后落入到哪个存储单元是事先已经分配好的,还得看地址总线把地址指向哪块内存了。这就是安装了 4GB 内存,电脑中只显示 3.8GB 左右的原因。以实模式下举例:
上图为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接口的功能如下:
- 设置数据缓冲,解决 CPU 与外设的速度不匹配
- 设置信号电平转换电路
- 设置数据格式转换
- 设置时序控制电路来同步 CPU 和外部设备
- 提供地址译码 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宇符。我们也在这个默认模式下工作了。
如图,在屏幕上显示的每个字符占两个字节,低字节为字符的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
以上是硬盘控制器主要端口寄存器,想要读写硬盘就得靠这些寄存器操作,下面主要介绍primary通道下的端口,secondary通道端口用法也是一样的
0x1F0:数据寄存器,无论是写入的数据还是读出的数据都在这个端口取。16位大小,因此一次能取两个字节内容
0x1F1:读模式下是错误信息寄存器,如果硬盘操作失败,这里会保存着错误代码。写模式下,这里可以指定额外参数
0x1F2:扇区寄存器,这里写入的内容表示待读取或待写入的扇区数。硬盘每操作完一个扇区,就会将此寄存器数-1。因此如果操作失败或者中断了,这里面的数值代表尚未完成的扇区数。此寄存器大小为8位,最大值为255,若指定为0则表示要操作256个扇区。
0x1F3~0x1F5:LBA寻址下的低,中,高八位,按顺序写入就能找到对应的柱面-磁头-扇区位置
0x1F6:divece寄存器,每位作用见下图
0x1F7:在读取内容时为status寄存器,每一位作用见下图。平常时为command寄存器,常用命令有:0xEC(硬盘识别),0x20(读扇区),0x30(写扇区)
读磁盘的一般操作顺序如下,举例说明:
; 首先明确要操作的通道,这里选择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),具体值如下
由于是32位系统,只会用到低32位属性。同时给出调用中断的输入和输出
操作示例如下:
; 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
; CF位1,代表出故障了
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打印出来的内容
可以利用的有x0~0x9fc00内存区域以及0x100000~0x3fe0000两块内存,其中最大一块内存容量为62MB,也就是后续我们需要进行内存管理的部分。一般操作系统的代码放在第一个内存块中。值得注意的是,第三块内存块0xf0000~0x100000这块内存是一个内存空洞,是历史上遗留下来的问题。主要是为了解决当时有些ISA设备需要将这块地区作为缓冲区,所以就一直把这片区域空出来了。
中断代理芯片
中断处理流程
中断处理流程主要如下:
CPU 外:外部设备的中断由中断代理芯片接收,处理后将该中断的中断向量号发送到 CPU
CPU 内: CPU 执行该中断向量号对应的中断处理程序。
- 处理器根据中断向量号定位中断门描述符。
- 处理器进行特权级检查。
- 执行中断处理程序。
中断压栈过程
中断压栈过程可分为跨特权级处理和不跨特权级处理。不跨特权级处理时压栈图如下
此时原代码和中断处理所用的栈是一样的,因此不区分新旧栈,栈中只压了EFLAGS,CS,EIP三个值。
跨特权级压栈如下
此时原代码和中断处理所用的栈是一样的,因此此时的栈为新栈(中断所用的栈),栈中压了SS,ES,EFLAGS,CS,EIP五个值。
通常能够压入错误码的中断属于中断向量号在0~32之内的异常,而外部中断(中断向量号在32~255之间)和int软中断并不会产生错误码。通常我们并不用处理错误码。
可编程中断控制器8259A
对于外部中断,并不是由CPU直接仲裁管理的,而是通过两根信号线接收
可屏蔽中断是通过时INTR引脚进入CPU的,外部设备如硬盘、网卡等发出的中断都是可屏蔽中断。8259A则是对这一系列外设进行管理,最后警告仲裁后将中断通过INTR输入CPU。8259A用于管理和控制可屏蔽中断,它表现在屏蔽外设中断,对它们实行优先级判决,向CPU提供中断向量号等功能。个人电脑上一般是两片8259A级联,结构如下。
8259A内部结构如下
其中的寄存器和信号说明如下:
- 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端口,结构如下。
- 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端口。结构如下
0~2为不需要填写,固定为000。3~7位为对应的起始中断向量号,结果需要左移三位。例如:设置ICW2为0x20,此时3~7位的值为0x4,对应的起始中断向量号需要*8,因此结果为0x4*8 = 0x20。
ICW3用来设置主片和从片用哪个IRQ接口互连,只在级联时候用(ICW1的SNGL为0时)。ICW3需要写入主片的Ox21端口及从片的OxAl端口,且两个片写法不一样。主片ICW3格式如下:
对于主片,ICW3中置1的那一位对应的IRQ接口用于连接从片,若为0则表示接外部设备。从片格式如下:
只需要在0~2位上输入主片做连接接口的IRQ位就行,例如:主片选择使用IRQ2位作为级联,那么从片的ICW3就是0b0000_0010
ICW4用于设置8259A的工作模式,当ICWl中的IC4时才需要ICW4。注意,ICW4需要写入主片的Ox21及从片的OxAl端口,结构如下:
- 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端口,结构如下:
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
----------待更新