# Kernel Model **Repository Path**: xcvk/kernelmodel ## Basic Information - **Project Name**: Kernel Model - **Description**: x86 抢占式多段分页模型 - **Primary Language**: Assembly - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-05-12 - **Last Updated**: 2023-04-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # x86抢占式多段分页模型内核 ## 介绍 x86抢占式多段分页模型,通常操作系统采用平坦模型。 ## 测试 bochs ## 主引导程序 ### 建立GDT ``` mov ds, eax ; ds = GDT的起始地址 mov ebx, edx ; ebx = GDT中的相对地址 ; 创建空描述符 mov dword [ebx], 0x0 mov dword [ebx+0x4], 0x0 ; 创建4G数据段描述符(0~ffffffff) mov dword [ebx+0x8], 0x0000FFFF mov dword [ebx+0xC], 0x00CF9200 ; 创建保护模式下MBR的代码段描述符(7c00~7dff) mov dword [ebx+0x10], 0x7C0001FF mov dword [ebx+0x14], 0x00409800 ; 创建保护模式下栈段描述符(向下扩展)(7bff~6c00) mov dword [ebx+0x18], 0x7C00FFFE mov dword [ebx+0x1C], 0x00CF9600 ; 创建保护模式下显示文本段描述符(b8000~bffff) mov dword [ebx+0x20], 0x80007FFF mov dword [ebx+0x24], 0x0040920B ; 加载到GDTR mov word cs:[GdtSize+0x7C00], 39 ; limit: 5 * 8 - 1 lgdt cs:[GdtSize+0x7C00] ``` ### 开启处理器保护机制,进入32位模式 ``` ; 开启A20地址线 in al, 0x92 or al, 00000010B out 0x92, al ; 关闭中断机制 cli ; 设置CR0.PE mov eax, CR0 or eax, 0x1 mov CR0, eax jmp 0x10:@32 ; cs = 7c00~7dff bits 32 @32: mov eax, 0x8 mov ds, eax ; ds = 0~FFFFFFFF mov eax, 0x18 mov ss, eax ; ss = 7c00~6c00 mov esp, 0 ``` ### 加载内核 ``` ; 将内核加载到内存 mov eax, LBA ; eax = 内核所在LBA mov ebx, ImageBase ; ebx = 内核加载的内存起始地址 call ReadDisk ; 读取剩余扇区 mov edi, ImageBase ; edi = 内核加载的内存起始地址 mov eax, [edi] ; eax = 内核程序的总大小 xor edx, edx mov ecx, 512 div ecx cmp edx, 0 jnz @1 dec eax @1: cmp eax, 0 jz @Setup mov ecx, eax ; ecx = 剩余扇区数 mov eax, LBA inc eax ; eax = 第二个扇区 @2: call ReadDisk inc eax ; eax = 下一个扇区 loop @2 ``` ### 为内核创建段描述符 ``` ; 为内核创建描述符 @Setup: mov esi, [GdtBase+0x7C00] ; 创建内核例程段描述符 mov eax, [edi+0x4] mov ebx, [edi+0x8] sub ebx, eax dec ebx ; ebx = 段界限 add eax, ImageBase ; eax = 内存中的例程段基址 mov ecx, 0x00409800 call MakeDescriptor mov [esi+0x28], eax mov [esi+0x2C], edx ; 创建内核数据段描述符 mov eax, [edi+0x8] mov ebx, [edi+0xC] sub ebx, eax dec ebx ; ebx = 段界限 add eax, ImageBase ; eax = 内存中的数据段基址 mov ecx, 0x00409200 call MakeDescriptor mov [esi+0x30], eax mov [esi+0x34], edx ; 创建内核代码段描述符 mov eax, [edi+0xC] mov ebx, [edi+0x0] sub ebx, eax dec ebx ; ebx = 段界限 add eax, ImageBase ; eax = 内存中的代码段基址 mov ecx, 0x00409800 call MakeDescriptor mov [esi+0x38], eax mov [esi+0x3C], edx mov word [GdtSize+0x7C00], 63 ; limit: 8 * 8 - 1 lgdt [GdtSize+0x7C00] ``` ### 跳转至内核代码段 ``` jmp far [edi+0x10] ``` ## 内核程序 ### 创建中断门 ``` ; 创建中断门描述符 mov eax, ExceptionHandler ; 段内偏移 mov bx, RoutineSel ; 段选择符 mov cx, 0x8E00 ; 描述符属性 call RoutineSel:MakeGateDescriptor ; 安装0~19号向量的中断描述符 mov ebx, IdtLinearBase ; ebx = 中断描述符表的起始线性地址 mov esi, 0 ; esi = 中断向量0 @setidt1: mov es:[ebx+esi*8], eax mov es:[ebx+esi*8+4], edx inc esi cmp esi, 19 jle @setidt1 ; 创建中断门描述符 mov eax, InterruptHandler ; 段内偏移 mov bx, RoutineSel ; 段选择符 mov cx, 0x8E00 ; 描述符属性 call RoutineSel:MakeGateDescriptor mov ebx, IdtLinearBase ; ebx = 中断描述符表的起始线性地址 @setidt2: mov es:[ebx+esi*8], eax mov es:[ebx+esi*8+4], edx inc esi cmp esi, 255 jle @setidt2 ; 创建0x70号中断门描述符 mov eax, InterruptHandler_0x70 mov bx, RoutineSel mov cx, 0x8E00 call RoutineSel:MakeGateDescriptor mov ebx, IdtLinearBase ; ebx = 中断描述符表的起始线性地址 mov es:[ebx+0x70*8], eax mov es:[ebx+0x70*8+4], edx ; 加载IDTR mov word [IdtSize], 256*8-1 mov dword [IdtBase], IdtLinearBase lidt [IdtSize] ; 由于主片的中断向量与处理器的冲突,因此重新初始化8259A芯片 mov al, 0x11 ; ICW1:级联+边沿触发 out 0x20, al mov al, 0x20 ; ICW2:设置起始中断向量 out 0x21, al mov al, 0x04 ; ICW3:S2连接从片 out 0x21, al mov al, 0x01 ; ICW4:非自动结束 out 0x21, al mov al, 0x11 ; ICW1:级联+边沿触发 out 0xA0, al mov al, 0x70 ; ICW2:设置起始中断向量 out 0xA1, al mov al, 0x04 ; ICW3:S2连接从片 out 0xA1, al mov al, 0x01 ; ICW4:非自动结束 out 0xA1, al mov al, 0x0B or al, 0x80 out 0x70, al ; 阻断NMI mov al, 0x12 out 0x71, al ; 开启更新结束后中断 in al, 0xA1 and al, 0xFE out 0xA1, al ; 清除IMR的位0 mov al, 0x0C out 0x70, al in al, 0x71 ; 寄存器C复位 ``` ### 创建内核页目录和页表 ``` ; 开启中断 sti ; 创建内核页目录和页表,低端1MB ; 初始化页目录项 mov ebx, 0x20000 ; ebx = PD起始地址 xor esi, esi mov ecx, 1024 @init_pd: mov dword [es:ebx+esi], 0x0 add esi, 4 loop @init_pd ; 最后一个PDE指向页目录自己 ; 之后可将PD当成PT来使用 mov dword [es:ebx+4092], 0x20003 ; 指向页表 mov dword [es:ebx+0], 0x21003 ; 初始化页表 mov ebx, 0x21000 ; ebx = PT起始地址 xor eax, eax ; eax = 物理页起始地址 xor esi, esi ; PTE索引 @init_pt_256: mov edx, eax ; edx = eax = 物理页起始地址 or edx, 0x3 mov dword [es:ebx+esi*4],edx; PTE = 物理页起始地址 + 属性 add eax, 0x1000 ; 下一个物理页 inc esi ; 索引+1 cmp esi, 256 jl @init_pt_256 @init_pt_1024: mov dword [es:ebx+esi*4], 0x0 inc esi cmp esi, 1024 jl @init_pt_1024 ; 将CR3指向PD mov eax, 0x20000 mov CR3, eax ; 关闭中断 cli ``` ### 开启分页机制 ``` ; 关闭中断 cli ; 开启分页机制 mov eax, CR0 or eax, 0x80000000 ; PG = 1 mov CR0, eax ; 在页目录内创建与线性地址0x80000000对应的PDE(PDE偏移:0x200*4) ; 由于开启了分页,任何地址都是线性地址需要进行转换 ; 线性地址0xFFFFF800:对应PD起始物理地址(CR3)+0x800偏移 ; # 0xFFFFF:目的是在转换时不断地访问自己(0x3FF*4),最后转换成物理地址 ; # 0x800:物理地址的偏移,同时也是80000000线性地址PDE的偏移(0x200*4) mov dword [es:0xFFFFF800], 0x21003 ``` ### 修改内核相关的段描述符与GDT起始地址为线性地址 ``` ; 修改内核相关的段描述符与GDT起始地址为线性地址 sgdt [GdtSize] mov ebx, [GdtBase] ; ebx = GDT起始线性地址 ; 代码段描述符 or dword [es:ebx+0x10+4], 0x80000000 ; 栈段描述符 or dword [es:ebx+0x18+4], 0x80000000 ; 显式文本段描述符 or dword [es:ebx+0x20+4], 0x80000000 ; 公共例程段描述符 or dword [es:ebx+0x28+4], 0x80000000 ; 内核数据段描述符 or dword [es:ebx+0x30+4], 0x80000000 ; 内核代码段描述符 or dword [es:ebx+0x38+4], 0x80000000 ; GDT起始地址 add dword [GdtBase], 0x80000000 ; 装载GDTR lgdt [GdtSize] ; 修改IDT的起始地址为线性地址 sidt [IdtSize] add dword [IdtBase], 0x80000000 lidt [IdtSize] ; 刷新CS段寄存器 jmp CodeSel:@flush_sreg @flush_sreg: ; 刷新SS段寄存器 mov eax, StackSel mov ss, eax ; 刷新DS段寄存器 mov eax, DataSel mov ds, eax ; 开启中断 sti ``` ### 创建调用门描述符 ``` ; 为导出函数创建调用门描述符 mov edi, ExportTable ; 导出函数表的起始地址 mov ecx, NumberOfItems ; 导出函数表的项数 @make_gate: push ecx mov eax, [edi+256] ; edi = 例程起始地址 mov bx, [edi+260] ; bx = 例程所在段的段选择符 mov cx, 0xEC00 ; cx = 调用门描述符的属性 call RoutineSel:MakeGateDescriptor call RoutineSel:SetupDescriptor mov [edi+260], cx ; 选择符回填 add edi, ItemSize pop ecx loop @make_gate ``` ### 初始化内核任务 ``` ; 为内核任务创建TCB mov ecx, CoreTcbBase mov word [es:ecx+0x4],0xFFFF; TCB.任务状态 = 0xFFFF mov dword [es:ecx+0x46], AllocAddr call AppendToTcb ; 加入TCB链表 mov esi, ecx ; esi = ecx = TCB的起始地址 ; 为内核任务分配TSS mov ecx, 104 call RoutineSel:AllocateMemory mov es:[esi+0x14], ecx ; 在TCB中保存TSS的起始地址 mov word [es:ecx+0], 0 ; 任务链 = 0 mov eax, CR3 mov dword [es:ecx+28], eax ; 保存CR3 mov word [es:ecx+96], 0 ; LDT选择符清零(内核任务不需要) mov word [es:ecx+102], 103 ; 没有IO位图 mov word [es:ecx+100], 0 ; 调试陷阱位清零 ; 创建TSS描述符和选择符,并装载到GDT中 mov eax, ecx ; eax = ecx = TSS起始地址 mov ebx, 103 ; ebx = 段界限 mov ecx, 0x00008900 ; ecx = 段属性 call RoutineSel:MakeDescriptor call RoutineSel:SetupDescriptor mov word [es:esi+0x18], cx ; 将段选择符保存到TCB中 ; 将段选择符装载至TR寄存器 ltr cx ``` ### 创建用户任务 ``` ; 创建用户程序任务1的TCB mov ecx, 0x4A call RoutineSel:AllocateMemory mov word [es:ecx+0x4], 0x0 ; 将任务设置为就绪 mov dword [es:ecx+0x46], 0x0 ; 加载程序 push dword 50 push ecx call LoadProgram call AppendToTcb ; 加入TCB链表 ; 创建用户程序任务2的TCB mov ecx, 0x4A call RoutineSel:AllocateMemory mov word [es:ecx+0x4], 0x0 ; 将任务设置为就绪 mov dword [es:ecx+0x46], 0x0 ; 加载程序 push dword 100 push ecx call LoadProgram call AppendToTcb ; 加入TCB链表 ``` ### 任务切换 ``` ; 任务管理 @task_switch: mov ebx, message_5 call RoutineSel:PutString call RoutineSel:TaskClean ; 清理已经终止的任务 hlt ; 会被中断信号唤醒 jmp @task_switch ``` ## 参考资料 - 《x86汇编语言:从实模式到保护模式》 - 《Combined Volume Set of Intel® 64 and IA-32 Architectures Software Developer’s Manuals》