纯x86汇编实现的多线程操作系统实践 - 第三章 BSP的守护执行

本章我们将详细讲解BSP剩下的执行代码,它们被安排在bp_32.asm文件中。

bp_32.asm主要完成以下功能:

  1. 系统中断初始化

  1. 加载字符图形数据到内存区域

  1. 将AP的启动代码和32位保护模式下的代码分别加载到内存中

  1. 显示主界面以及系统启动信息

  1. 向所有AP群发启动命令

  1. 进入守护线程的大循环:

  • 接收并处理鼠标数据,在界面上显示鼠标

  • 接收并显示键盘输入

  • 显示两个AP用户线程的实时状态

  • 显示BSP已经执行的时间 时:分:秒

3.1 启动系统中断

3.1.1总体初始化

BSP这部分的32位代码从0x80016000地址处开始执行。cli指令关中断,再调用init_interrupt_function函数设置系统中断,最后sti指令开启系统中断。

我们使用APIC来设置和使能系统中断。APIC全称Advanced Programmable InterruptController。每个执行内核都有一个APIC,称为LocalAPIC(LAPIC)。每个LAPIC包含一系列的寄存器,这些寄存器控制LAPIC如何将中断信号发送到执行内核中。

LAPIC架构图

APIC根据实现不同,分成LAPIC和x2APIC,这两个APIC寄存器基本一样,唯一不同的是LAPIC通过内存访问APIC寄存器,x2APIC通过MSR寄存器访问APIC寄存器。

1. xAPIC:APIC寄存器被映射到4KB大小的MMIO内存区,操作系统通过MMIO内存访问APIC。

2. x2APIC: 一部分MSR地址区间为APIC寄存器预留,访问APIC是通过MSR。这样的好处是不用再担心内存地址的冲突问题。

LAPIC可以从以下几个来源接收到中断:

  • Locallyconnected I/O devices: 直接连接到处理器的本地中断引脚(LINT0和LINT1)的I/O设备触发的中断

  • Externallyconnected I/O devices :外部链接的I/O设备发出的中断

  • Inter-processor interrupts (IPIs) :执行内核可以使用IPI机制向系统总线上的其它执行内核发送中断

  • APICtimer generated interrupts:APIC定时器中断

  • Performancemonitoring counter interrupts :性能计数器中断

  • ThermalSensor interrupts :热传感器中断 (power)

  • APICinternal error interrupts :APIC内部异常中断

Local APIC的使能需要设置两个寄存器:IA32_APIC_BASE寄存器的第11位(置1)、SVR寄存器的第8位(置1)

IA32_APIC_BASE寄存器为MSR(Model-Specific Register),对应的编号为0x1B。需要使用RDMSR/WRMSR来读写,命令格式为:

mov ecx, 0x1B

rdmsr

操作MSR寄存器时,须先将MSR寄存器的编号写入ecx,读出的MSR寄存器值放在EDX:EAX中,高位在EDX,低位在EAX。

IA32_APIC_BASE寄存器的第12位以上还保存着APIC寄存器的物理基地址。默认情况下该物理基地址值为0xFEE00000

SVR寄存器地址为0xFEE00010

经过BIOS的初始化,IA32_APIC_BASE寄存器第11位以及SVR寄存器的第8位都为1,Local APIC默认就是启动的,我们无需再设。

下面是IO APIC的初始化。外部设备先与IO APIC对接,产生的中断由IO APIC通过总线发送到Local APIC,之后再发送到执行内核。

init_interrupt_function函数完成IO APIC的初始化。

对IO APIC进行操作,首先要做两件事:

  1. 设置IO APIC寄存器的默认地址(index/data/EOI这三个寄存器);

  1. 启动IO APIC功能。

IO APIC寄存器默认地址对应OIC寄存器的0~7bits位,IOAPIC的使能控制位于OIC寄存器的第8位。正常情况下,IO APIC三个寄存器的默认地址就是index-0xFEC00000data-0xFEC00010EOI-0xFEC00040

IO APIC其它寄存是都是通过index和data寄存器来间接访问,各寄存器的index值为:

index

寄存器

Size

0x0

ID

32位

0x1

Version

32位

0x2~0x0F

Reserved

0x10~0x11

Redirection table 0

64位

0x12~0x13

Redirection table 1

64位

0x3C~0x3D

Redirection table 22

64位

0x3E~0x3F

Redirection table 23

64位

0x40~0xFF

Reserved

IO APIC寄存器对应index值

启动IOC APIC:

OIC寄存器位于RCBA(root complex base address)寄存器的0x31FE偏移上。READ_PCI_DWORD 0,31,0,0x0f0 以及 and eax, 0xFFFFC000用于获取RCBA寄存器的地址。

之后,mov esi, [eax +0x31fe]获取OIC寄存器的值。然后第8位置1,使能IO APIC功能,同时0~7位置0,将IO APIC寄存器的基地址设置为0xFEC00000起始。最后再写入OIC寄存器,完成设置。

IO APIC的index寄存器写入0,选择IO APIC的ID寄存器。之后,IO APIC的data寄存器写入0x0F0000000,将BSP对应的IO APIC的ID号设置位0x0F

下面,开始设置操作系统需要使用到的各个中断,包括键盘、高精度HPET0时钟、鼠标。

对于该类外部中断,IO APIC都需要使用Redirection table寄存器进行设置。Redirection table寄存器格式如下:

中断在配置时需要至少提供4类数据:

  1. 中断服务程序的Vector值(中断向量号)

  1. 中断的Delivery mode:Fixed、lowestpriority、SMI、NMI、INIT或ExtINT模式

  1. 触发模式:edge或是level触发模式

  1. 目标执行内核:physical或logical目标模式,并在destionation中提供目标执行内核的ID号(BSP的ID号就是0)

同时,IO APIC对应的IRQ为:

IRQ

中断源

对应配置寄存器

0

连接8259中断控制器

Redirection table 0

1

Keyboard(键盘)

Redirection table 1

2

HPET timer0,8254 counter 0

Redirection table 2

3

Serial port A

Redirection table 3

4

Serial port B

Redirection table 4

5

通用/parallel port

Redirection table 5

6

Floppy

Redirection table 6

7

通用/parallel port

Redirection table 7

8

RTC, HPET timer 1

Redirection table 8

9

通用

Redirection table 9

10

通用

Redirection table 10

11

通用/HPET timer 2

Redirection table 11

12

鼠标/HPET timer 3

Redirection table 12

13

FERR#

Redirection table 13

14

SATA primary

Redirection table 14

15

SATA secondary

Redirection table 15

16

PIRQA#

Redirection table 16

17

PIRQB#

Redirection table 17

18

PIRQC#

Redirection table 18

19

PIRQD#

Redirection table 19

20

PIRQE#

Redirection table 20

21

PIRQF#

Redirection table 21

22

PIRQG#

Redirection table 22

23

PIRQH#

Redirection table 23

设置键盘中断。Index寄存器写入0x12,设置edirection table 1寄存器低32位为0x51;Index寄存器写入0x13,设置edirection table 1寄存器高32位为0。这段代码设置键盘中断:中断向量号0x51、Fixed delivery mode、目标模式为physical、触发模式为edge。高32位设置为0x0,表示目标执行内核的ID号为0(BSP对应的ID号)。

设置HPET timer 0中断。Index寄存器写入0x14,设置edirectiontable 2寄存器低32位为0x52;Index寄存器写入0x15,设置edirectiontable 2寄存器高32位为0。这段代码设置HPET timer 0中断:中断向量号0x52、Fixed delivery mode、目标模式为physical、触发模式为edge。高32位设置为0x0,表示目标执行内核的ID号为0(BSP对应的ID号)。

设置鼠标中断。Index寄存器写入0x28,设置edirection table 12寄存器低32位为0x53;Index寄存器写入0x29,设置edirection table 12寄存器高32位为0。这段代码设置鼠标中断:中断向量号0x53、Fixed delivery mode、目标模式为physical、触发模式为edge。高32位设置为0x0,表示目标执行内核的ID号为0(BSP对应的ID号)。

0xFEE00000+0x350对应的是Local APIC的LVT LINT0寄存器,该寄存器对应8259中断控制器。LVT LINT0寄存器的第16位置1意味着不再响应来自8259的中断请求。同时,通过向0x21端口写入0xFF,意味着向8259的OCW1写入全1,屏蔽所有8259的中断。之后,外部中断仅由IO APIC产生。

IDT段长度设置为256*8-1,表示IDT中已有256个有效的中断向量。

设置第0x30号中断向量为:接收AP1和AP2消息并处理。

设置第0x51号中断向量为:接收来自键盘的数据。

设置第0x52号中断向量为:接收来自HPET timer 0的消息。

设置第0x53号中断向量为:接收来自鼠标的数据。

设置第0x80号中断向量为:系统调用中断,在界面上画一个点。

设置第0x81号中断向量为:系统调用中断,获取界面上某个点的颜色。

设置第0x82号中断向量为:系统调用中断,界面上显示一个字符。

设置第0x83号中断向量为:系统调用中断,获取界面上鼠标的X坐标和Y坐标。

初始化键盘。

系统ticks值初始化为0,并配置HPET timer 0的各类寄存器。

install_interrupt_handle函数用于安装系统使用的中断处理接口,而install_syscall_handle用于安装用户进程使用的系统调用。它们都是往IDT段中写入中断描述符,区别是:系统中断处理接口对应的中断描述符中,描述符的特权级为0,而用户进程使用的系统调用对应的中断描述符,其特权级为3。

3.1.2初始化键盘

0x60端口为键盘数据端口,0x64为命令端口。从0x64端口读取的状态字的含义如下:

先反复读取状态字,判断键盘输入缓冲区是否已清空。

之后向0x64端口写入0x60命令字,表示下一个命令是:将发到0x60端口的参数写入i8042控制器。

键盘输入缓冲区清空后,立即发送0x47命令字到0x60端口,意思是:使能键盘和鼠标接口及中断。之后,就可以通过中断接收键盘输入的数据了。

3.1.3初始化高精度HPET timer 0

init_hpet函数用于初始化高精度HPET timer 0,使之产生高精度的时钟中断。该时钟中断将用于时间计数、进程切换等操作。

HPET的配置寄存器位于RCBA(root complex base address)的0x3404偏移上,和前面说的OIC寄存器处于同一个配置空间。

HPET的配置寄存器的第7位用于使能HPET的寄存器空间,第0和第1位用于选择寄存器的起始基地址。

以上的代码设置HPET配置寄存器的bit7为1,bit0~bit1为00:启动HPET的寄存器空间,并设置寄存器的基地址为0xFED00000

HPET的configure寄存器设置为0x3:允许HPET counter进行计数、timer 0固定使用IO APIC的IRQ2或8259的IRQ0。

Timer 0使用64位的size,以及periodic周期模式。

设置Timer 0的comparator寄存器,为每10ms产生一次中断(14318179为一秒),再设置HPET的计数器寄存器,启动Timer 0。

再解释一下HPET timer 0高精度时钟在中断时调用的中断函数do_hpet_timer0_handler:

系统已执行的tick值加一。判断tick值是否已经超过100(BSP是否已经启动超过1秒)。若未超过1秒,退出中断函数。

若tick值已经超过100(BSP已启动超过1秒),每次产生中断后,都向AP1和AP2发送IPI信号。

综上所述,HPET timer 0每产生一次中断,BSP的tick值加一。同时,BSP的在启动1秒后,会每隔10ms向AP1和AP2发送IPI消息。

ICR寄存器(0xFEE00300)结构

ICR寄存器可用于发送IPI(inter-processor interrupt)进行执行内核间通信。其中,地址0xFEE00300对应寄存器的低32位,地址0xFEE00310对应寄存器的高32位。先写入高32位,再写入低32位。

mov DWORD[0xFEE00310], 0x01000000指令代表:向ID号为1(AP1的ID号)的执行内核发送消息。mov DWORD [0xFEE00300],0x00004031指令代表:中断向量号为0x31、指定发送的目标、目标模式为physical、交付模式为Fixed、触发模式为edge

mov DWORD [0xFEE00310], 0x02000000指令代表:向ID号为2(AP2的ID号)的执行内核发送消息。mov DWORD [0xFEE00300], 0x00004032指令代表:中断向量号为0x32、指定发送的目标、目标模式为physical、交付模式为Fixed、触发模式为edge

3.1.4初始化鼠标

鼠标的初始化与键盘初始化类似。

先反复读取状态字,判断键盘输入缓冲区是否已清空。

之后向0x64端口写入0xd4命令字,表示下一个命令是:将发到0x60端口的参数发给鼠标。

键盘输入缓冲区清空后,立即发送0xf4命令字到0x60端口:允许鼠标向主机发送数据包。之后,就可以通过中断接收鼠标发送的数据了。

3.1.5接收其它AP发来的IPI消息

BSP安装的0x30号中断用于接收来自其它AP发来的IPI消息,对应的中断处理函数为do_ipi_handler:

原子操作地址ap_num,每次中断ap_num保存的值加一。由于AP1和AP2在初始化完成后都会向BSP发送“初始化完毕”的消息,因此该中断应该会被执行两次,ap_num中保存的值为当前已经接收到的IPI消息的个数。

接收到IPI消息后,会在界面的738:22处显示已接收到的IPI消息的个数。

同时,当[ap_num]的值为2,说明两个AP都已经初始化完毕,此时,我们将页目录的第0项清零,这样,实地址0x0~0x3FFFFF将不再被映射到虚拟地址0x0~0x3FFFFF处,而是只被映射到虚拟地址0x80000000~0x803FFFFF处。(我们在mbr.asm中将0x0~0x3FFFFF同时映射到虚拟地址0x0~0x3FFFFF和0x80000000~0x803FFFFF处

注意:整个系统被设计为共享页目录的内存结构,BSP和不同AP使用相同的页目录和内存空间。这样的设计主要是为了BSP和AP之间的数据共享方便,只要操作相同的页目录、页表就可以轻易实现

3.1.6其它系统调用

1. 在界面上画点

0x80号系统调用用于在界面上画一个点,对应的中断处理函数为do_syscall_handler:

设置线程锁,避免不同AP同时调用该中断导致错误。之后调用draw_point在界面上画点。0xFEE000B0是Local APIC的EOI寄存器。这行代码向Local APIC发送EOI指令,表示中断结束

draw_point函数:

先判断要画点的位置是否在界面之外。之后计算输入的X轴和Y轴对应的点的序号,再乘以4后得到该点对应的显存位置,写入点的颜色数据。

  1. 获取界面上某个点的颜色

0x81号系统调用用于获取界面上某个点的颜色,对应的中断处理函数为do_int81h_handler:

调用get_point函数获取对应点的颜色。0xFEE000B0是LocalAPIC的EOI寄存器。这行代码向Local APIC发送EOI指令,表示中断结束。

get_point函数:

与draw_point函数类似,先判断点的位置是否在界面之外。之后计算输入的X轴和Y轴对应的点的序号,再乘以4后得到该点对应的显存位置,读取点的颜色数据。

  1. 显示字符

0x82号系统调用用于在界面上显示一个字符,对应的中断处理函数为do_int82h_handler。该中断处理函数与draw_key类似,具体解释请参考下一节的内容。

  1. 获取点击鼠标时的X坐标和Y坐标

0x83号系统调用用于获取点击鼠标时的X坐标和Y坐标,对应的中断处理函数为do_int83h_handler:

mouse_x_click中保存上一次鼠标点击后记录的X坐标,mouse_y_click中保存上一次鼠标点击后记录的Y坐标。它们的值在mouse_run_time函数中被设置(请参考后面对mouse_run_time函数的解释)。

本次坐标值被取出后,将被清零。

3.2 显示数据和AP启动代码的加载

将各字符的位图数据从硬盘载入内存,这些字符包括“0”~“9”、“a”~“z”等,一共40个。上面的代码,是从从第52扇区开始,连续读取80个扇区、40K字节到地址0x8001E000处。每个字符为12╳16的点阵,32位的位图,因此,一个字符占用12╳16╳4=768字节。我们用两个扇区保存一个字符的位图数据。字符位图按照以下顺序保存在0x8001E000开始的内存段中:“0123456789abcdefghijklmnopqrstuvwxyz.: ”

系统中,显示一个字符的函数是draw_key。对draw_key的解释如下:

al寄存器中保存的是要显示字符的键盘扫描码,key_code_mem数组以字符的扫描码作为下标,保存各字符在0x8001E000内存段中的序号。例如字符‘a’的键盘扫描码为0x1e,key_code_mem[0x1e]的数值为0x0a,正好是字符’a’在0x8001E000内存段中的序号10。

mov bl, [key_code_mem+ eax]是将字符的序号放入bl。shl ebx, 10的意思是字符的序号乘以1024,得到字符位图数据在0x8001E000内存段中的偏移。最后加上0x8001E000,ebx中为字符位图数据的起始地址。

ecx为显示字符左下角的Y坐标,ecx*1152+ecx(后面弹出的X坐标)得到字符左下角的点在显存中序号,再乘以4,得到字符左下角的点在显存中的偏移。加上显存首址video_mem_addr(0xE0000000),得到字符左下角点对应的显存地址。

我们使用的字符位图为BMP格式的,而BMP格式的位图数据大概率都是倒向的,因此我们在往显存中填写位图数据时,也是从下往上填写的,这样显示的图形就是正向的。

上面的代码就是从字符的左下角开始,从下往上的填写位图数据。

这里的代码是将AP的启动代码从16号扇区搬运到虚拟地址0x8001B000,其实对应的实地址就是0x0001B000。拷贝的数据长度为1扇区,512字节。

将AP的32位保护模式下的代码从20号扇区拷贝到0x8001C000处,拷贝16个扇区,8K字节。

3.3 线程锁的定义

这里,我们定义了一个线程锁。该锁在实地址的0x00008000处。BSP在发送AP的唤醒信号后,两个AP先后启动。由于两个AP几乎会在同一个时间往GDT段中写入代码段和数据段,如果不加控制,极易造成写入GDT的数据错误,导致AP运行时宕机。因此,我们通过设置线程锁,保证后续对GDT段的操作为原子操作,避免写入错误数据。

3.4整体界面的显示

draw_surface函数用于显示主界面,将界面整体分为4个区间,左上角的区间显示键盘输入,右上角的区间打印各类运行信息,中间的大区间用于两个AP用户进程的绘画,最下方的区域打印系统的名字和版本信息。draw_surface函数通过调用draw_square函数在界面上绘制多个不同的矩形来划分区域。

通过draw_string函数,在X坐标为582,Y坐标为22处,显示一串字符,字符串中各字符的扫描码保存在BP_up_str数组中。

整体界面如下图所示:

3.5 启动AP线程

这里,通过APIC的ICR(interruptcommand register)向所有AP群发启动信号。

第一个命令,0x000c4500:向除了自己以外的所有执行内核发送INIT(初始化)模式命令,中断向量号为0(INIT模式下vector必须为0)。

第二个命令,0x000c461b:向除了自己以外的所有执行内核发送Start-Up(启动)模式命令,启动代码的基地址为0x1b000(对应AP的启动代码,已经从16号扇区拷贝过来)。0x000c461b连续发送两次,避免有AP没有及时接收到该命令。

通过draw_string函数,在X坐标为400,Y坐标为850处,显示一串字符,为系统的名字和版本号。字符串中各字符的扫描码保存在Down_log_str数组中。

0x80008008和0x8000800C这两处的32位数作为标志,控制AP1和AP2两个用户进程的执行。通过键盘输入“stop ap1”命令时,0x80008008处的数值被置为0,AP1的用户进程停止执行;输入“runap1”命令时,0x80008008处的数值被置为1,AP1的用户进程恢复执行。

3.6 BSP的守护循环

下面,将进入BSP线程的大循环中。在该循环中,BSP将轮流检测鼠标、键盘的输入,执行相关命令。BSP将不断显示两个AP的执行状态以及系统已经经过的时间。同时,HPTE timer 0高精度时钟中断每10ms产生一次中断,BSP将在中断处理函数中向AP0和AP1发送IPI信号,AP0和AP1接到IPI信号后将进行进程切换。

3.6.1处理鼠标

1. 鼠标的中断处理

鼠标的中断向量号为0x53,中断服务接口为do_mouse_handler,其功能如下:

从0x60端口获取鼠标数据。先判断鼠标启动标志是否为0。如果为0,表示本次中断为鼠标的首次中断,接收到的数据为0xFA,是鼠标回复给CPU的“激活成功”消息。该数据0xFA不用保存到数据缓冲中,直接将鼠标启动标志设置为0x55即可,表示后续接收到的数据都保存到鼠标数据缓冲区中。

取鼠标缓冲区写指针,该指针指向缓冲区中下一个空闲字节地址。保存鼠标数据到缓冲区,写指针加一。我们设置缓冲区大小为4096个字节(一页),一旦写指针超过4095,就将写指针复位,重新指向缓冲区头部。这里要注意的是,我们并没有判断写指针和读指针之间的位置,因此存在写指针覆盖读指针的风险(若系统一直不从缓冲区读取鼠标数据)。

0xFEE000B0是Local APIC的EOI寄存器。这行代码向Local APIC发送EOI指令,表示中断结束。中断在Local APIC中设置为Fixed delivery模式时,中断处理程序执行完必须发送EOI到Local APIC。

0xFEC00040是IOAPIC的EOI寄存器。在IO APIC的level-trigger中断结束后,需要向IO APIC的EOI写入命令,表示中断处理完毕。IO APIC的EOI寄存器,仅使用在level触发模式上,对edge触发模式的中断无影响。因此这一句代码在这里是多余的。

2. 鼠标的界面显示

mouse_run_time函数在BSP线程的大循环中被调用,它从鼠标缓冲区读取数据,判断鼠标在界面上的显示位置,显示数据。

鼠标缓冲区的读写指针是否相同,如果相同,说明缓冲区中没有数据,本函数执行结束;否则,从读指针处获取一个字节数据,读指针加一。如果读指针已到了缓冲区末尾,复位读指针到缓冲区头部。

鼠标通常按照一组3个字节的方式返回数据,其格式为:[命令字节]-[X轴字节]-[Y轴字节]。命令字节记录鼠标上的各个键是否被点击或者滑动,X轴字节记录鼠标在X轴上滑动的方向和距离,Y轴记录鼠标在Y轴上滑动的方向和距离。

通过mouse_state标志判断本次读取字节为鼠标的命令字节、X轴字节还是Y轴字节。

处理鼠标的命令字节。如果命令字节的第0位被置位,说明鼠标左键被点击,鼠标被点击标志置位。将mouse_state标志改为X轴字节,下一个被读取字节将被作为X轴字节来处理。

处理鼠标的X轴字节。先保存X轴的老坐标,再判断X轴字节是否大于0x80。如果字节大于0x80,说明鼠标是向左移动。通过对X轴字节的数值取反再加一,得到左移距离的绝对值。如果左移距离的绝对值大于当前X轴的坐标,说明鼠标已经左移到了屏幕以外,本次移动不做处理;否则新X坐标的值为老X坐标减去左移距离的绝对值。

如果X轴字节的值小于0x80,说明鼠标向右移动。新X坐标为老X坐标加上X轴字节的值。若新X坐标的值大于1148,我们将新X坐标值写为1148,避免画新鼠标时超出界面。

将mouse_state标志改为Y轴字节,下一个被读取字节将被作为Y轴字节来处理。

处理鼠标Y字节。先保存Y轴的老坐标,再判断Y轴字节是否大于0x80,大于0x80说明鼠标向下移动。通过对Y轴字节的数值取反再加一,得到下移距离的绝对值。将老Y坐标值加上下移距离的绝对值,得到Y坐标新值。如果新值大于860,我们将新Y坐标值写为860,避免画新鼠标时超出界面。如果Y轴字节小于0x80,鼠标向上移动。先判断向上移动的距离是否大于老的Y坐标值,如果大于,本次不调整Y坐标值;否则,新Y坐标值为老Y坐标值减去Y轴字节值。

调用show_run_mouse函数在界面上画出新坐标下的鼠标。如果鼠标左键被点击了,保存鼠标点击时的坐标,该坐标在AP的用户进程中被使用。鼠标被点击标志复位。

将mouse_state标志改为命令字节,下一个被读取字节将被作为命令字节来处理。

show_run_mouse函数用于在界面上显示鼠标。

先在鼠标的老坐标处,将被鼠标覆盖的10个点恢复。这10个点原来的颜色数据保存在mouse_old_da这个缓冲区中。

在鼠标的新坐标处,保存将被鼠标覆盖的10个点,这些点的颜色数据保存到mouse_old_da缓冲区中。

鼠标的10个点的颜色都设置为白色。将该颜色值写入新坐标下鼠标的10个点中。

通过以上这三个步骤:

1. 老坐标处,恢复原来被鼠标覆盖的区域;

2. 新坐标处,保存将要被鼠标覆盖的区域;

3. 新坐标处,鼠标颜色覆盖。

来实现界面上鼠标的移动效果。

3.6.2处理键盘

  1. 键盘的中断处理

键盘的中断向量号为0x51,中断服务接口为do_keyboard_handler,其功能如下:

读取端口号为0x64的键盘状态寄存器,判断键盘缓冲区是否有数据可被读取。从键盘字符寄存器(端口号0x60)读取输入字符,并判断是否大于0x80。若大于0x80,说明是按键弹起时产生的中断,该字符不用存入键盘数据缓冲区。

有效的字符通过调用save_keyboard_data函数保存到键盘数据缓冲区中。

0xFEE000B0是Local APIC的EOI寄存器。这行代码向Local APIC发送EOI指令,表示中断结束。中断在Local APIC中设置为Fixed delivery模式时,中断处理程序执行完必须发送EOI到Local APIC。

0xFEC00040是IOAPIC的EOI寄存器。在IO APIC的level-trigger中断结束后,需要向IO APIC的EOI写入命令,表示中断处理完毕。IO APIC的EOI寄存器,仅使用在level触发模式上,对edge触发模式的中断无影响。这一句代码在这里是多余的。

save_keyboard_data函数用来保存键盘数据到缓冲区:

获取键盘写指针,将键盘数据存入缓冲区。写指针超过4096时将被复位,指向缓冲区头部。

  1. 键盘输入的显示

keyboard_running函数在BSP线程的大循环中被调用,它从键盘缓冲区读取数据,在界面上显示键盘输入的字符,并判断是否是合法的命令。这些命令用于控制两个AP用户进程的执行和挂起。

判断读写指针是否相同。相同说明键盘数据缓冲区中没有新的数据,跳出本函数。

从读指针处读取键盘输入,读指针加一。如果读指针到了缓冲区末尾,复位读指针指向缓冲区头部。

将读取的键盘数据放入命令行缓冲区。命令行中数据的长度不能超过100,否则命令行中数据的长度直接清0。

键盘输入的字符有专门的显示区域,在界面的左上角。在这里,先在界面上调用draw_key函数显示输入字符,再调整显示下一个字符的坐标。向右X坐标不能超过550,向下Y坐标不能超过68。

判断命令行缓冲区的最后一个字节,是否是Enter键,不是就先退出本函数;否则:

先使用space键,将本行后面涂黑。

换到新的一行,调用do_keyboard_cmd来判断和处理命令行缓冲区中的数据。

do_keyboard_cmd的代码如下:

如果输入的命令为“ap1 stop”,ap1_usr_pro_enable标志复位为0。AP1的系统执行代码会判断该标志,如果为0,便Pending AP1的用户进程。

如果输入的命令为“ap1 run”,ap1_usr_pro_enable标志置为1。AP1的系统代码执行时会判断该标志,如果为1,便继续为用户进程分配执行的时隙。

如果输入的命令为“ap2 stop”,ap2_usr_pro_enable标志复位为0。AP2的系统代码执行时会判断该标志,如果为0,便Pending AP2的用户进程。

如果输入的命令为“ap2 run”,ap2_usr_pro_enable标志置为1。AP2的系统代码执行时会判断该标志,如果为1,便继续为用户进程分配执行的时隙。

3.6.3 AP的状态显示

回到BSP的主循环。BSP调用do_ap_statue函数来显示两个AP用户线程的当前状态。

通过ap1_usr_pro_enable标志判断AP1当前运行状态。若ap1_usr_pro_enable标志不为0,在坐标582:44处显示字符串:“ap1 running”。

执行这里的代码,说明ap1_usr_pro_enable标志为0,AP1未执行,在坐标582:44处显示字符串:“ap1 stopped”。

通过ap2_usr_pro_enable标志判断AP2当前运行状态。若ap2_usr_pro_enable标志不为0,在坐标834:44处显示字符串:“ap2 running”。

执行这里的代码,说明ap2_usr_pro_enable标志为0,AP2未执行,在坐标834:44处显示字符串:“ap2 stopped”。

字符串通过draw_string函数进行显示:

eax寄存器保存要显示字符串的地址。若该地址中的数为0,说明已经到了字符串的最后,退出该函数。

调用draw_key函数显示字符。显示字节的地址加一,同时显示位置的X轴加12(一个字符的长度为12个点)。

3.6.4 BSP执行时间显示

BSP在主循环中调用do_bp_exe_time函数来显示已经执行的时间:

取BSP的tick值,并减去上次记录的tick值,如果大于100(相隔1秒),则继续执行;否则退出函数。

将tick值除以100,得到BSP已执行的秒数;再除以60,得到已执行的分钟数,之后再得到已执行的小时数。

在界面的右上角显示BSP已经执行的时:分:秒。

以上是对bp_32.asm文件中大部分代码的详细解读。

bp_32.asm的源文件地址:https://download.csdn.net/download/hanspruce_bird/87502144

头文件源文件地址:https://download.csdn.net/download/hanspruce_bird/87502152

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值