现代CPU都是多核系统,拥有多个执行内核(即计算引擎),可并发执行不同的代码。
在CPU众多的执行内核中,有一个为主执行内核(BSP),在CPU上电后,该主执行内核会率先启动,执行硬盘0扇区的启动代码;其它执行内核为从执行内核(AP),上电后,从执行内核会处于stand by状态,等待被唤醒的信号。
BSP从硬盘读取AP的启动代码装入内存,再将代码首地址写入唤醒信号的数据段中。BSP在完成自身的初始化后,会向所有AP群发启动信号。
启动流程见下图:
各执行内核拥有独立的执行环境(GDT、IDT、eip、esp、段寄存器、通用寄存器、段式管理、页式管理等),可以将一个执行内核简单对应为一个单核CPU。
宗上所述,我们需要分别为BSP和AP提供启动和运行代码。下表描述了我们准备的代码的名字、存放的位置、主要的功能:
序号 | 文件名 | 存放位置 | 主要功能 |
1 | mbr.bin | 0扇区 | BSP使用,使BSP从实模式进入32位保护模式 |
2 | bp_32.bin | 2~17扇区 | BSP使用,32位保护模式下,初始化中断,使能键盘和鼠标,最后作为系统的监护线程持续运行 |
3 | ap_16.bin | 18扇区 | AP使用,使AP从实模式进入32位保护模式 |
4 | ap_32.bin | 20~35扇区 | AP使用,32位保护模式下,初始化中断,再进入用户态执行用户代码 |
5 | task1.bin | 36~37扇区 | AP1的用户进程代码 |
6 | Task2.bin | 44~45扇区 | AP2的用户进程代码 |
整体系统的内存部署如下图所示。由于系统段代码的虚拟地址,在BSP进入32位保护模式之前,被映射到虚拟地址的2G处,所以下表中的各段地址有两个,一个是映射前的虚拟地址,一个是映射后的虚拟地址:
段命名 | 页面 | 虚拟地址 | 说明 |
bios_int_page | 0x0~0x0fff | 0x0/0x80000000 | 实模式下BIOS的中断向量 |
gdt_global_addr | 0x1000~0x1fff | 0x1000/0x80001000 | 存放全局GDT段数据,所有执行内核都使用该GDT段 |
idt_global_addr | 0x2000~0x2fff | 0x2000/0x80002000 | 存放IDT段数据,所有执行内核都使用该IDT段 |
page_dir_addr | 0x3000~0x3fff | 0x3000/0x80003000 | 该页为所有执行内核的页目录 |
page_t0_addr | 0x4000~0x4fff | 0x4000/0x80004000 | 该页为为所有执行内核的0号页表 |
page_t380_addr | 0x5000~0x5fff | 0x5000/0x80005000 | 该页为为所有执行内核的第0x380号页表 |
page_t3BF_addr | 0x6000~0x6fff | 0x6000/0x80006000 | 该页为为所有执行内核的第0x3BF号页表 |
bios_setup_page | 0x7000~0x7fff | 0x7000/0x80007000 | 该页的0x7c00~0x7e00为BSP实模式启动代码 |
sys_share_page | 0x8000~0x8fff | 0x8000/0x80008000 | 该页用于进程间通信,存放AP间信号量、高精度时钟timer0的ticks值、AP到AP间的控制命令队列等 |
sys_backup_page1 | 0x9000~0x9fff | 0x9000/0x80009000 | 备份页(1页),等待后续扩充 |
ap1_sys_stack_page | 0xa000~0xafff | 0xa000/0x8000a000 | 该页为AP1系统代码使用的堆栈 |
ap1_usr_page_dir | 0xb000~0xbfff | 0xb000/0x8000b000 | 该页为AP1用户进程的页目录 |
ap1_usr_page_t0 | 0xc000~0xcfff | 0xc000/0x8000c000 | 该页为AP1用户进程的页表0 |
ap1_usr_sys_stack | 0xd000~0xdfff | 0xd000/0x8000d000 | 该页为AP1用户进程的系统堆栈 |
ap1_usr_code_page | 0xe000~0xefff | 0xe000/0x8000e000 | 该页为AP1用户进程的用户代码页 |
ap1_usr_stack_page | 0xf000~0xffff | 0xf000/0x8000f000 | 该页为AP1用户进程的用户堆栈页 |
ap2_sys_stack_page | 0x10000~0x10fff | 0x10000/0x80010000 | 该页为AP2线程系统代码使用的堆栈 |
ap2_usr_page_dir | 0x11000~0x11fff | 0x11000/0x80011000 | 该页为AP2用户进程的页目录 |
ap2_usr_page_t0 | 0x12000~0x12fff | 0x12000/0x80012000 | 该页为AP2用户进程的页表0 |
ap2_usr_sys_stack | 0x13000~0x13fff | 0x13000/0x80013000 | 该页为AP2用户进程的系统堆栈 |
ap2_usr_code_page | 0x14000~0x14fff | 0x14000/0x80014000 | 该页为AP2用户进程的用户代码页 |
ap2_usr_stack_page | 0x15000~0x15fff | 0x15000/0x80015000 | 该页为AP2用户进程的用户堆栈页 |
bp_sys_code_page | 0x16000~0x16fff | 0x16000/0x80016000 | 该页为BSP线程的32位系统代码 |
bp_sys_code_page2 | 0x17000~0x17fff | 0x17000/0x80017000 | 该页为BSP线程的32位系统代码备份页 |
bp_sys_stack_addr | 0x18000~0x18fff | 0x18000/0x80018000 | 该页用于BSP 32位保护模式下系统代码栈 |
bp_sys_mouse_data | 0x19000~0x19fff | 0x19000/0x80019000 | 该页为BSP线程使用,保存鼠标输入数据 |
bp_sys_keyboard | 0x1a000~0x1afff | 0x1a000/0x8001a000 | 该页为BSP线程使用,保存键盘输入数据 |
ap_setup_page | 0x1b000~0x1bfff | 0x1b000/0x8001b000 | 该页为AP线程实模式启动代码,由所有AP共用 |
ap_sys_code_page | 0x1c000~0x1cfff | 0x1c000/0x8001c000 | 该页为AP线程32位保护模式下系统代码,由所有AP共用 |
ap_sys_code_page2 | 0x1d000~0x1dfff | 0x1d000/0x8001d000 | 该页为AP线程32位保护模式下系统代码备份页 |
sys_pic_save_addr | 0x1e000~0x45fff | 0x1e000/0x8001e000 | 保存字符图像数据的起始地址,当前使用了40K |
sys_backup_page3 | 0x46000~0x9ffff | 0x46000/0x80046000 | 备份页(90页,360K) |
bios_sys_page | 0xa0000~0xfffff | 0xa0000/0x800a0000 | BIOS使用。其中,文本模式显示区域起始地址0xb8000 |
该操作系统中,BSP和各个AP都使用了相同的GDT段、IDT段、页目录,因此,在BSP和AP向GDT、IDT以及页目录中写入数据时,一定要使用原子操作,否则写入错误数据的概率极高!
该操作系统支持一个BSP加上2个AP的内核模式,可自行在VirtualBox中设置:
本章对操作系统的整体结构进行了简要的说明,具体的执行代码将在后面的章节进行详细讲解。
《纯x86汇编实现的多线程操作系统实践》全书及全源代码下载:https://download.csdn.net/download/hanspruce_bird/87502318