十五、转入内核
2015-12-08 00:00
330 查看
在 linux 下编译 ELF 格式,和书上一样,简单多了!
首先是 ELF 格式的几个结构:
const.inc 里加上程序入口物理地址:
FAT12.inc 里把 SearchFile 函数中比较字符串部分单独作为一个函数提出来了—— SearchFile 实在太长了:
PMode.inc 把段描述符结构中第四个字段(16 位)拆成两个字段(各 8 位),这样看起来更清晰:
BootSector.asm 上来就先把中断屏蔽掉:
loader.asm 中增加了取内存容量,再按照 ELF 文件格式把 kernel 各段重定位部分,并且把内存容量和显存相关参数保存到全局变量段了:
由于转到了新程序,回不去 loader 了,原来的 GDT 也失效了,要重新实现一次
把初始化 GDT 的函数引出来,供 kernel 调用
kernel.c 不再是文本文件了,把全局变量取出来,并且试验了直接写显存。
用到了 memcpy 函数,很简单,自己实现就行:
用到了两个个汇编指令, C 语言无能为力,只能汇编实现了:
Makefile 文件也贴出来吧
贴上运行效果图:
首先是 ELF 格式的几个结构:
; ELF.inc ; ELF 文件格式说明及结构 ; 四彩 ; 2015-12-08 %ifndef _ELF_INC %define _ELF_INC ; ELF 格式可执行文件结构: ; ELF 文件头(Elf header) ; 程序头表(program header table) ; 第 1 段(segment) ; 第 2 段 ; 其他段 ; 节头表(section header table) ; ======================================================================================== ; ---------------------------------------------------------------------------------------- ; ELF 文件头结构 ; e_type 字段可取值及说明 ET_NONE equ 0 ; 未指明文件类型 ET_REL equ 1 ; 重定位文件 ET_EXEC equ 2 ; 可执行文件 ET_DYN equ 3 ; 动态连接库 EI_NIDENT equ 16 struc Tag_ELF_Header ; 字节名 说明 .e_ident resb EI_NIDENT ; ELF 文件标识(魔数、字长、字节序、版本) .e_type resw 1 ; 文件类型 .e_machine resw 1 ; CPU 属性要求 .e_version resd 1 ; 版本 .e_entry resd 1 ; 程序入口地址 .e_phoff resd 1 ; 程序头表在文件中的偏移量 .e_shoff resd 1 ; 节头表在文件中的偏移量 .e_flags resd 1 ; 处理器特定标志,IA32 为 0 .e_ehsize resw 1 ; 文件头长度 .e_phentsize resw 1 ; 程序头表中一个条目的长度 .e_phnum resw 1 ; 程序头表条目数目 .e_shentsize resw 1 ; 节头表中一个条目的长度 .e_shnum resw 1 ; 节头表条目个数 .e_shstrndx resw 1 ; 节头表字符索引 endstruc ; ---------------------------------------------------------------------------------------- ; 程序头结构 ; 描述一个段在文件中的位置、大小以及它被放进内存后所在的位置和大小。 ; p_type 字段可取值及说明 PT_NULL equ 0 ; 未使用段 PT_LOAD equ 1 ; 可装载段 struc Tag_Program_Header ; 字节名 说明 .p_type resd 1 ; 段的类型 .p_offset resd 1 ; 段在文件中的偏移 .p_vaddr resd 1 ; 段在内存中的虚拟地址 .p_paddr resd 1 ; 段的物理地址(在物理内存定位的系统中使用) .p_filesz resd 1 ; 段在文件中的长度 .p_memsz resd 1 ; 段在内存中的长度 .p_flags resd 1 ; 段的标记 .p_align resd 1 ; 段在文件及内存的对齐标记 endstruc ; ---------------------------------------------------------------------------------------- ; 节头结构 struc Tag_Section_Header ; 字节名 说明 .sh_name resd 1 ; 小节名在字符表中的索引 .sh_type resd 1 ; 小节的类型 .sh_flags resd 1 ; 小节属性 .sh_addr resd 1 ; 小节在运行时的虚拟地址 .sh_offset resd 1 ; 小节的文件偏移 .sh_size resd 1 ; 小节的大小(字节) .sh_link resd 1 ; 链接的另外一小节的索引 .sh_info resd 1 ; 附加的小节信息 .sh_addralign resd 1 ; 小节的对齐 .sh_entsize resd 1 ; 一些sections保存着一张固定大小入口的表。就像符号表 endstruc ; **************************************************************************************** %endif
const.inc 里加上程序入口物理地址:
; const.inc ; 常量 ; 四彩 ; 2015-12-08 %ifndef _CONSTANT_INC %define _CONSTANT_INC ; ======================================================================================== ; 内存中 0x0500 ~ 0x7BFF 段(29.75 KB)和 0x7E00 ~ 0x9FBFF 段(607.5 KB)可自由使用。 ; 引导扇区段在加载完 Loader 后也可使用,即整个 0x500 ~ 0x9FBFF 段(637.25 KB)都可自由使用。 ; STACKSIZE equ 0x1000 ; 堆栈大小(4K) SEGMENTOFGPARAM equ 0x50 ; 存放全局数据的段基址 SEGMENTOFTEMP equ 0x7E ; 临时数据被加载到内存的段基址 SEGMENTOFLOADER equ 0x9000 ; LOADER.SYS 被加载到内存的段基址 SEGMENTOFKERNEL equ 0x8000 ; KERNEL.EXE 被加载到内存的段基址 KERNELENTRYPHYADDR equ 0x30400 ; kernel 的程序入口的物理地址 ; 必须与 Makefile 中 -Ttext 参数的值相等 ; **************************************************************************************** %endif
FAT12.inc 里把 SearchFile 函数中比较字符串部分单独作为一个函数提出来了—— SearchFile 实在太长了:
; FAT12.inc ; FAT12 文件系统常量、宏及子函数定义 ; 四彩 ; 2015-12-08 %ifndef _FAT12_INC %define _FAT12_INC ; ======================================================================================== BYTESPERSECTOR equ 512 ; 每扇区字节数 IFATFIRSTSECTOR equ 1 ; FAT 表的起始逻辑扇区号 IROOTDIRECTORYFIRSTSECTOR equ 19 ; 根目录区的起始逻辑扇区号 IDATAFIRSTSECTOR equ 33 ; 数据区的起始逻辑扇区号 ; **************************************************************************************** ; ======================================================================================== ; FAT12 文件系统的引导扇区头部格式宏 ; 调用格式:FAT12Head RealEntry, OEMName, VolLab ; RealEntry : 入口地址标签 ; OEMName : 厂商名称(8 字节长,不够的填空格) ; VolLab : 卷标(11 字节长,不够的填空格) %macro FAT12Head 3 ; 名称 偏移 长度 说明 3.5英寸软盘内容 jmp %1 ; 0x00 3 跳转指令,指向程序入口 jmp RealEntry nop BS_OEMName db %2 ; 0x03 8 厂商名称 自行定义 BPB_BytsPerSec dw 512 ; 0x0B 2 每扇区字节数 512 BPB_SecPerClus db 1 ; 0x0D 1 每簇扇区数 1 BPB_RsvdSecCnt dw 1 ; 0x0E 2 保留扇区数 1 BPB_NumFATs db 2 ; 0x10 1 FAT表份数 2 BPB_RootEntCnt dw 224 ; 0x11 2 根目录中最多容纳的文件数 224 BPB_TotSec16 dw 2880 ; 0x13 2 扇区总数 (FAT12、16) 2880 BPB_Media db 0xF0 ; 0x15 1 介质描述符 0xF0 BPB_FATSz16 dw 9 ; 0x16 2 每个FAT表所占的扇区数 9 BPB_SecPerTrk dw 18 ; 0x18 2 每磁道扇区数 18 BPB_NumHeads dw 2 ; 0x1A 2 磁头数 2 BPB_HiddSec dd 0 ; 0x1C 4 隐藏扇区数 0 BPB_TotSec32 dd 2880 ; 0x20 4 扇区总数(FAT32) 2880 BS_DrvNum db 0 ; 0x24 1 磁盘驱动器号 0 BS_Reserved1 db 0 ; 0x25 1 保留(供NT使用) 0 BS_BootSig db 0x29 ; 0x26 1 扩展引导标记 0x29 BS_VolD dd 0 ; 0x27 4 卷标序列号 0 BS_VolLab db %3 ; 0x2B 11 卷标 自行定义 BS_FileSysType db 'FAT12' ; 0x36 8 文件系统类型名 FAT12 ; 0x3E 448 引导代码及其他填充字符 ; 0x1FE 2 结束标志 0xAA55 ; ; BPB:BIOS Parameter Block,BIOS 参数块 ; BS:Boot Sector,引导扇区 %endmacro ; **************************************************************************************** ; ======================================================================================== ; 目录表项结构 struc DirectoryItem ; 字段名 偏移 长度 说明 .DIR_Name resb 11 ; 0x00 11 文件名 8 + 3(大写,不够长度末尾填空格) .DIR_Attr resb 1 ; 0x0B 1 文件属性 resb 10 ; 0x0C 10 保留 .DIR_WrtTime resw 1 ; 0x16 2 最后修改时间 .DIR_WrtDate resw 1 ; 0x18 2 最后修改日期 .DIR_FstClus resw 1 ; 0x1A 2 此条目对应的开始簇号(即 FAT 表项序号) .DIR_FileSize resd 1 ; 0x1C 4 文件大小 endstruc ; **************************************************************************************** ; ======================================================================================== ; 从软盘A(FAT12 格式)装载文件到内存用到的子函数 %macro IncludeFAT12Function 1 ; 调用格式 :IncludeFAT12Function SEGMENTOFTEMP ; 参数 :SEGMENTOFTEMP = 暂存临时数据的段基址 ; ; ---------------------------------------------------------------------------------------- ; 函数功能:查找文件位置 ; 入口参数:ds : si = 文件名(字符串)地址 ; 出口参数:ax = 起始 FAT 表项序号 SearchFile: push bp mov bp, sp sub sp , 2 * 2 ; 为局部变量分配空间 push es push cx push bx push %1 ; 存放根目录数据要用到 es pop es ; 待读取的根目录区逻辑扇区号 mov word[bp - 2], IROOTDIRECTORYFIRSTSECTOR ; 待查找的根目录区扇区数 mov word[bp - 2 * 2], IDATAFIRSTSECTOR - IROOTDIRECTORYFIRSTSECTOR ; 逐个扇区寻找 .NextSector: mov ax, [bp - 2] xor bx, bx call Read1Sector ; 逐项匹配 mov cx, 16 ; 一个扇区的总项数 = [BPB_BytsPerSec] / DirectoryItem_size .ThisSector: mov ax, 11 ; 比较文件名 call StrnCmp cmp ax, 0 jz .Found add bx, DirectoryItem_size ; 下一个表项 loop .ThisSector ; 判断是否读完根目录区所有扇区:读完说明没找到,没读完就继续下一个 dec word[bp - 2 * 2] jz .NotFound inc word[bp - 2] jmp .NextSector .NotFound: xor ax, ax jmp .Return .Found: mov ax, word[es : bx + DirectoryItem.DIR_FstClus] .Return: pop bx pop cx pop es mov sp, bp pop bp ret ; ---------------------------------------------------------------------------------------- ; 函数功能:复制文件到内存 ; 入口参数:ax = 起始 FAT 表项序号 ; es : bx = 起始内存地址 ; 出口参数:无 LoadFile: push bp mov bp, sp push bx push ax .Load: push bx push ax add ax, IDATAFIRSTSECTOR - 2 ; FAT 表项序号转换为逻辑扇区号 call Read1Sector pop ax call GetEntryValue pop bx cmp ax, 0xFF8 ; FAT 表项的值大于等于 0xFF8,表示文件结束 jae .Return ; 未检查坏扇区 —— 虚拟的不会坏的 add bx, BYTESPERSECTOR jmp .Load .Return: POP ax pop bx mov sp, bp pop bp ret ; ---------------------------------------------------------------------------------------- ; 函数功能:取得 FAT 表中指定序号表项的值(即下一个扇区的 FAT 表项序号) ; 入口参数:ax = FAT 表项序号 ; 出口参数:ax = 对应的 FAT 表项值 GetEntryValue: push bp mov bp, sp push es ; 读取 FAT 表时要使用 es 暂存数据 push dx push cx push bx ; 计算该表项序号所在的逻辑扇区号和在该扇区的偏移量 xor dx, dx ; 字节号(ax * 12 / 8) mov bx, 3 mul bx mov bx, 2 div bx mov cx, dx ; 保存表项序号的奇偶性(0 = 偶数,1 = 奇数) xor dx, dx mov bx, BYTESPERSECTOR div bx add ax, IFATFIRSTSECTOR ; 逻辑扇区号 push dx ; 保存在该扇区的偏移量 ; 读取连续 2 个扇区(表项可能跨扇区) push cx ; Read1Sector 函数改变了 cx、ax push ax push %1 pop es xor bx, bx call Read1Sector pop ax inc ax mov bx, BYTESPERSECTOR call Read1Sector pop cx ; 读出 16 位,奇数项取高 12 位、偶数项取低 12 位(低低高高存放原则),得到项值 pop bx ; 偏移量(上面压进去的 dx 值) mov ax, [es : bx] jcxz .Even shr ax, 4 .Even: and ax, 0b0000111111111111 ; 奇数项高 4 位已为 0,执行此操作值也不变 pop bx pop cx pop dx pop es mov sp, bp pop bp ret ; ---------------------------------------------------------------------------------------- ; 函数功能:读取一个逻辑扇区到内存 ; 入口参数:ax = 逻辑扇区号 ; es : bx = 起始内存地址 ; 出口参数:ax = 同 ah = 2、int 0x13 Read1Sector: push bp mov bp, sp push dx push cx ; 由 LBA 计算 CHS mov dl, 18 div dl mov ch, al mov dh, al mov cl, ah shr ch, 1 inc cl and dh, 1 ; 读一个扇区 mov ax, 0x0201 xor dl, dl int 0x13 ; 未检读取失败 —— 虚拟的不会失败 pop cx pop dx mov sp, bp pop bp ret ; ---------------------------------------------------------------------------------------- ; 函数功能:字符串比较 ; 入口参数:ds : si = 字符串1地址 ; es : bx = 字符串2地址 ; ax = 字符串长度 ; 出口参数:ax = 0:字符串相等,非 0 :不等 StrnCmp: push bp mov bp, sp push si push cx push bx mov cx, ax jcxz .Return .Compare: lodsb cmp al, [es : bx] jnz .Return inc bx loop .Compare .Return: mov ax, cx pop bx pop cx pop si mov sp, bp pop bp ret ; ---------------------------------------------------------------------------------------- ; 函数功能:在光标当前显示字符串,光标跟随移动 ; 入口参数:ds : si = 字符串地址 ; 出口参数:无 PrintStr: push bp mov bp, sp push si push ax mov ah, 0x0E ; 功能号,0x0E:显示一个字符,光标跟随字符移动 .Print: lodsb cmp al, 0 ; 字符串以 0 结尾 je .Return int 0x10 jmp .Print .Return: pop ax pop si mov sp, bp pop bp ret %endmacro ; **************************************************************************************** %endif
PMode.inc 把段描述符结构中第四个字段(16 位)拆成两个字段(各 8 位),这样看起来更清晰:
; PMode.inc ; 保护模式。内存分段管理机制的说明、宏及常量定义 ; 四彩 ; 2015-12-08 %ifndef _PROTECTEDMODE_INC %define _PROTECTEDMODE_INC ; ======================================================================================== ; ---------------------------------------------------------------------------------------- ; 存储器(Storage):存放程序和数据的器件,是用于保存信息的记忆设备。 ; 存储元(Storage Unit):也称存储位、记忆单元,是存放一个二进制位的单元。 ; 是存储器内部储存数据的最小单位。 ; 任何具有双稳态(两个稳定状态)的物理器件都可以来做存储元。 ; 存储单元(Storage Cell):存储器中有大量的存储元,把它们按相同的位划分为组,组内所有的 ; 存储元同时进行读出或写入操作,这样的一组存储元称为一个存储单元。 ; 一个存储单元通常可以存放一个字节;存储单元是 CPU 访问存储器的基本单位。 ; 存储单元地址(Storage Cell Address):存储单元的唯一的固定编号。 ; 物理存储器(Physical Storage):实际存在的、具有实物形式的存储器。 ; 内存(Memory):即内部存储器,也叫主存。是 CPU 的地址线可以直接进行寻址的存储器。 ; 用于暂时存放 CPU 中的运算数据,以及与硬盘等外部存储器交换的数据。 ; 分为两种: ; 物理内存(Physical Memory):通过物理上真实存在的内存条获得的内存。 ; 虚拟内存(Virtual Memory):在硬盘上开出一个区域或文件模拟的物理内存。 ; ; ; 内存地址(Address):内存中每个用于数据存取的基本单位(字节),都被赋予的一个唯一的序号。 ; 逻辑地址(Logical Address):机器语言指令中,用来指定一个操作数或一条指令的相对地址。 ; 也叫虚拟地址(Virtual Address),是与段相关的偏移地址部分。 ; 线性地址(Linear Address):逻辑地址(段中的偏移地址)加上相应段基地址生成的地址。 ; 是逻辑地址到物理地址变换之间的中间层。 ; 物理地址(Physical Address):内存单元的真实地址。 ; 实际是出现在 CPU 外部地址总线上寻址物理内存的地址信号。可以理解成把插在机器上的 ; 物理内存看做一个从 0 到最大容量、逐字节编号的大数组,这个数组的下标就叫做物理地址。 ; ; ; 物理存储空间:物理地址的集合,就是硬件的存储空间。也称为物理空间。 ; 地址空间:是指编码地址(对每一个存储单元分配一个号码)的范围。 ; 存储器地址空间:对存储器编码地址的范围。 ; 内存地址空间(Address Space):CPU 在操控物理存储器的时候,把物理存储器都当作内存来对待, ; 把它们总的看作一个由若干存储单元组成的逻辑存储器,这个逻辑存储器就是内存地址空间。 ; 内存地址空间是为了避免物理地址暴露给进程带来的严重问题,创造的一种内存抽象。 ; 是一个进程可用于寻址全部内存的地址的集合,是一段表示内存位置的地址范围。 ; 内存地址空间的大小受 CPU 地址总线宽度的限制。32 位地址总线宽度的内存地址空间最大 4GB。 ; 逻辑地址空间(Logical Address Space):也称虚拟地址空间,是指程序中指令和数据所用的 ; 所有相对地址的编码的范围。 ; 线性地址空间(Linear Address Space):线性地址的编码范围。 ; CPU 将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址( ; 段内偏移量),CPU 利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其 ; 页式内存管理单元,转换为最终物理地址。 ; ; ; 内核空间:操作系统内核运行的线性地址空间。 ; 内核线性地址空间由所有进程共享,但只有运行在内核态的进程才能访问,用户进程可以通过 ; 系统调用切换到内核态访问内核空间,进程运行在内核态时所产生的地址都属于内核空间。 ; 用户空间:普通应用程序运行的线性地址空间。 ; 每个进程都有一个独立的用户空间,用户空间由每个进程独有。 ; 但是内核线程没有用户空间,因为它不产生用户空间地址。另外子进程共享(继承)父进程的 ; 用户空间,只是使用与父进程相同的用户线性地址到物理内存地址的映射关系,而不是共享 ; 父进程用户空间。 ; ; ; 寻址(Addressing):由地址寻找数据,从地址对应的存储单元中访存数据。 ; 物理上就是磁头在盘片上定位数据的过程。 ; 寻址方式(Addressing Mode):在存储器中,指令、操作数写入或读出的方式,分为地址指定方式、 ; 相联存储方式和堆栈存取方式。计算机内存都采用地址指定方式。当采用地址指定方式时,处理器 ; 根据指令中给出的地址信息来寻找物理地址的方式称为寻址方式。 ; 指令寻址方式:形成指令的有效地址的方法。分为两类: ; 顺序寻址方式:指令地址在内存中按顺序安排,执行程序时,指令一条一条地顺序执行。 ; 跳跃寻址方式:下条指令的地址码不是由程序计数器给出,而是由本条指令给出,程序 ; 转移执行的顺序的过程。 ; 操作数寻址方式:形成操作数的有效地址的方法。分很多种,常见的有: ; 隐含寻址:不明显地给出操作数的地址。而是在指令中隐含着操作数的地址。 ; 立即寻址:指令的地址字段指出的不是操作数的地址,而是操作数本身。 ; 直接寻址(Direct Addressing):在指令中直接给出参与运算的操作数及运算结果所 ; 存放的有效地址、不需要经过某种变换的寻址方式。 ; 间接寻址:指令地址字段中的形式地址不是操作数的真正地址,而是操作数地址的指示器, ; 或者说此形式地址单元的内容才是操作数的有效地址。 ; 相对寻址方式:把当前指令的地址加上指令格式中的形式地址而形成操作数的有效地址。 ; 基址寻址方式:将基址寄存器的内容,加上变址寄存器的内容而形成操作数的有效地址。 ; 变址寻址方式:把变址寄存器的内容与偏移量相加来形成操作数有效地址。 ; 块寻址方式(Block Addressing):在指令中指出数据块的起始地址(首地址)和数据块 ; 的长度(字数或字节数)。 ; ; ; 段(Segment):将用户作业的逻辑地址空间依照相应的逻辑信息组的长度划分成若干个连续的段。 ; 由三个参数定义: ; 段基地址(Segment Base Address):线性地址空间中段的起始地址。 ; 段界限(Segment Limit):段的大小。 ; 段属性(Segment Attributes):段的主要特性。 ; 分 2 类: ; 存储段(Memory Segment):存放可由程序直接进行访问的代码和数据。分 2 类: ; 代码段(Code Segment): ; 数据段(Data Segment): ; 系统段(System Segment):分 2 类: ; 任务状态段(Task State Segment):保存任务的重要信息,通过它实现任务的挂起和恢复。 ; 任务:可以理解成线程,每个线程需要一个描述符来描述。 ; 局部描述符表段:保存局部描述符表的段。 ; !!用分段机制隔离 OS 核心和应用程序,用分页机制隔离进程。只需要两个代码段和两个数据段。 ; ———用分段把整个系统空间分为系统空间和用户空间,再用分页将用户空间划分为不同的进程空间。 ; ; 描述符(Descriptor):描述一个段所需要的三个参数(B、L、A)组成的数据结构。分 3 类: ; 存储段描述符:段寄存器使用的描述符。分 2 类:代码段描述符、数据段描述符。 ; 系统段描述符,分 2 类:TSS 段描述符、LDT 段描述符。 ; 门描述符(Gate Descriptor):描述控制转移的入口点。 ; 通过门实现任务内特权级的变换和任务间的切换。 ; 分 4 类: ; 调用门(Call Gate):描述子程序的人口。 ; 任务门(Task Gate):指示任务。 ; 中断门(Interrupt Gate):描述中断处理程序的入口。 ; 陷阱门(Trap Gate):描述异常处理程序的入口。 ; ; 描述符表(Descriptor Table):由描述符组成的线性表。分 3 类: ; 全局描述符表(Global Descriptor Table): ; 中断描述符表(Interrupt Descriptor Table): ; 局部描述符表(Local Descriptor Table):。 ; LDT 只是一个可选的数据结构,完全可以不用它。使用它带来方便性,也带来复杂性。 ; 如果你想让你的 OS 内核保持简洁性、可移植性,则最好不要使用它。 ; ; ---------------------------------------------------------------------------------------- ; 存储段(代码段和数据段)描述符格式(8 字节 64 位) ; ; ------ ┏━━┳━━┓内存高地址 ; ┃ 7 ┃ 段 ┃ ; ┣━━┫ 基 ┃ ; ┆ ┆ 址 ┆ ; 字节 ┆ ┆ 高 ┆ ; 7 ┣━━┫ 8 ┃ ; ┃ 0 ┃ 位 ┃ ; ------ ┣━━╋━━┫ ; ┃ 7 ┃ G ┃ ; ┣━━╉━━┨ ; ┃ 6 ┃D/B ┃ ; ┣━━╉━━┨ ; ┃ 5 ┃ 未 ┃ ; ┣━━┫ 定 ┃ ; ┃ 4 ┃ 义 ┃ ; 字节 ┣━━╉━━┨ ; 6 ┃ 3 ┃ ┃ ; ┣━━┫ 段 ┃ ; ┃ 2 ┃ 界 ┃ ; ┣━━┫ 限 ┃ ; ┃ 1 ┃ 高 ┃ ; ┣━━┫ 4 ┃ ; ┃ 0 ┃ 位 ┃ ; ------ ┣━━╋━━┫ ; ┃ 7 ┃ P ┃ ; ┣━━╉━━┨ ; ┃ 6 ┃ D ┃ ; ┣━━┫ P ┃ ; ┃ 5 ┃ L ┃ ; ┣━━╉━━┨ ; ┃ 4 ┃ S ┃ ; 字节 ┣━━╉━━┨ ; 5 ┃ 3 ┃ ┃ ; ┣━━┫ T ┃ ; ┃ 2 ┃ Y ┃ ; ┣━━┫ P ┃ ; ┃ 1 ┃ E ┃ ; ┣━━┫ ┃ ; ┃ 0 ┃ ┃ ; ------ ┣━━╋━━┫ ; ┃ 23 ┃ ┃ ; ┣━━┫ ┃ ; ┃ 22 ┃ 段 ┃ ; ┣━━┫ 基 ┃ ; ┆ ┆ 址 ┆ ; 字节 ┆ ┆ 低 ┆ ; 2,3,4 ┣━━┫ 24 ┃ ; ┃ 1 ┃ 位 ┃ ; ┣━━┫ ┃ ; ┃ 0 ┃ ┃ ; ------ ┣━━╋━━┫ ; ┃ 15 ┃ ┃ ; ┣━━┫ ┃ ; ┃ 14 ┃ 段 ┃ ; ┣━━┫ 界 ┃ ; ┆ ┆ 限 ┆ ; 字节 ┆ ┆ 低 ┆ ; 0,1 ┣━━┫ 16 ┃ ; ┃ 1 ┃ 位 ┃ ; ┣━━┫ ┃ ; ┃ 0 ┃ ┃ ; ------ ┗━━┻━━┛内存低地址 ; ; 存储段描述符定义宏:(看不明白的话,把 16 进制换成 2 进制就很清楚了) ; 调用格式:Descriptor Base, Limit, Attribute ; Base : dd ; 基址,32 位 ; Limit : dd ; 界限,32 位,低 20 位有效,高 12 位无效 ; Attribute : dw ; 属性,16 位,高 4 位和低 8 位有效,中间 4 位无效。 %macro Descriptor 3 dw %2 & 0xFFFF ; 界限低 16 位 dw %1 & 0xFFFF ; 基址低 16 位 db (%1 >> 16) & 0xFF ; 基址中间 8 位 db %3 & 0xFF ; 属性低 8 位 db ((%2 >> 12) & 0xF0) | (%3 >> 12); 界限高 4 位 + 属性高 4 位 db (%1 >> 24) & 0xFF ; 基址高 8 位 %endmacro ; **************************************************************************************** ; ; ; ======================================================================================== ; 描述符属性: ; 描述符属性是一个字型数值,但是只有高 4 位和低 8 位有效,中间 4 位无效。 ; ┏━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┓ ; ┃15┃14┃13┃12┃11┃10┃09┃08┃07┃06┃05┃04┃03┃02┃01┃ 0┃ ; ┣━┻━┻━┻━╋━┻━┻━┻━╋━╋━┻━╋━╋━┻━┻━┻━┫ ; ┃G ┃DB┃R AVL┃ 无效位 ┃P ┃ DPL ┃S ┃ TYPE ┃ ; ┗━┻━┻━┻━┻━━━━━━━┻━┻━━━┻━┻━━━━━━━┛ ; 11、G:Granularity,界限粒度位 ; G = 0 界限粒度为 1 字节; ; G = 1 界限粒度为 4K 字节。 ; 注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。 ; ; 10、DB:Default operation size / default stack pointer size and/or upper bound ; 默认操作大小/默认栈指针大小和/或上界限位,根据描述的段不同,功能不同。 ; 对于 32 位代码和数据段,应该总是设置为 1;对于 16 位代码和数据段,应该总是设置为 0。 ; ⑴ 可执行代码段(D):指明指令引用有效地址和操作数的默认长度。 ; ① D = 1 默认为 32 位代码段,指令使用 32 位地址及 32 或 8 位操作数; ; ② D = 0 默认为 16 位代码段,指令使用 16 位地址及 16 或 8 位操作数。 ; 可以使用指令前缀 0x66 来选择非默认值的操作数大小、0x67 来选择非默认值的地址大小。 ; ⑵由 SS 寄存器指向的数据段,通常为堆栈段(B):指明堆栈操作指令默认栈指针大小。 ; ① D = 1 使用 32 位堆栈指针寄存器 ESP; ; ② D = 0 使用 16 位堆栈指针寄存器 SP。 ; ⑶ 向下扩展数据段(B):指明段的上界限。 ; ① D = 1 段的上界限为 4G; ; ② D = 0 段的上界限为 64K。 ; ; 09、R:Reserved,保留位 ; 未定义,应该总是设置为 0。 ; ; 08、AVL:Available,可用位 ; 未定义,可供系统软件使用。 ; ; 07、P:Present,段存在位 ; P = 1 该段在内存中,即该段存在,或者说描述符对地址转换是有效的; ; P = 0 该段不在内存中,即该段不存在,或者说描述符对地址转换无效。 ; 把指向这个段描述符的选择符加载进段寄存器将导致产生一个段不存在异常。 ; 内存管理软件可以使用这个标志来控制在某一给定时间实际需要把那个段加载进内存中。 ; 这个功能为虚拟存储提供了除分页机制以外的控制。 ; 操作系统可以使用该描述符来保存其他数据,如不存在段实际在什么地方。 ; ; 06 05、DPL:Descriptor Privilege level,特权级位 ; 规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。 ; 特权级范围从 0 到 3,0 级特权级最高,3 级最低。 ; ; 04、S:Descriptor type flag,描述符类型位 ; S = 1 存储段 ; S = 0 系统段和门 ; ; 03 02 01 00、TYPE:说明存储段描述符所描述的存储段的具体属性。 ; 值 说明 ; ------------------------------------------ ; 系 0 未定义 ; 1 可用 286TSS ; 2 局部描述符表 ; 3 忙的 286TSS ; 4 286 调用门 ; 5 任务门 ; 6 286 中断门 ; 统 7 286 陷阱门 ; 8 未定义 ; 9 可用 386TSS ; A 未定义 ; B 忙的 386TSS ; C 386 调用门 ; D 未定义 ; E 386 中断门 ; 段 F 386 陷阱门 ; ------------------------------------------ ; 数据段都可读、非一致 ; 数 0 只读 ; 1 只读、已访问 ; 2 读/写 ; 据 3 读/写、已访问 ; 4 只读、向下扩展 ; 5 只读、向下扩展、已访问 ; 6 读/写、向下扩展 ; 段 7 读/写、向下扩展、已访问 ; ------------------------------------------ ; 代码段都可执行 ; 代 8 只执行 ; 9 只执行、已访问 ; A 执行/读 ; 码 B 执行/读、已访问 ; C 只执行、一致 ; D 只执行、一致、已访问 ; E 执行/读、一致 ; 段 F 执行/读、一致、已访问 ; ; 关于一致(Conforming)、非一致(Non-conforming): ; 同级间代码、数据都可互相访问。 ; 特权级高的不允许访问特权级低的代码:系统不会调用用户代码。 ; 特权级高的可以访问特权级低的数据,特权级低的不允许访问特权级高的数据: ; 系统可以访问用户数据,用户不能访问系统数据。 ; 一致代码段,特权级低的可以访问特权级高的代码(特权级不会改变): ; 用户可以调用系统共享的代码。 ; 非一致代段(普通的代码段)不同级间不能访问: ; 防止用户调用受保护的系统代码。 ; ; ---------------------------------------------------------------------------------------- ; 描述符属性常量定义: ; G 位,默认 1 字节粒度 DA_4K equ 0x8000 ; 4K 字节粒度,0b 1 000 0000 0000 0000 ; ; DB 位,默认 16 位 DA_32 equ 0x4000 ; 32 位,0b 1 00 0000 0000 0000 ; ; DPL 位,默认特权级 0 DA_DPL_1 equ 0x20 ; DPL = 1,0b 01 0 0000 DA_DPL_2 equ 0x40 ; DPL = 2,0b 10 0 0000 DA_DPL_3 equ 0x60 ; DPL = 3,0b 11 0 0000 ; ;P + S + TYPE 位,存在:+ 0x80(0b 1 000 0000) ; 系统段 DA_SS_LDT equ 0x82 ; 局部描述符表 DA_SS_TSKG equ 0x85 ; 任务门 DA_SS_TSKSS equ 0x89 ; 可用 386 TSS(任务状态)段 DA_SS_CALLG equ 0x8C ; 386 调用门 DA_SS_INTG equ 0x8E ; 386 中断门 DA_SS_TRPG equ 0x8F ; 386 陷阱门 ; 存储段:+ 0x10(0b 1 0000) DA_DS_R equ 0x90 ; 只读数据段 DA_DS_RW equ 0x92 ; 可读写数据段 DA_DS_RWA equ 0x93 ; 可读写、已访问数据段 ; DA_CS_E equ 0x98 ; 只执行代码段 DA_CS_ER equ 0x9A ; 可执行、可读代码段 DA_CS_EC equ 0x9C ; 可执行、一致代码段 DA_CS_ERC equ 0x9E ; 可执行、可读、一致代码段 ; ; **************************************************************************************** ; ======================================================================================== ; 选择子: ; ---------------------------------------------------------------------------------------- ; ┏━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┓ ; ┃15┃14┃13┃12┃11┃10┃09┃08┃07┃06┃05┃04┃03┃02┃01┃0 ┃ ; ┣━┻━┻━┻━┻━┻━┻━┻━┻━┻━┻━┻━┻━╋━╋━┻━┫ ; ┃ 描述符索引 ┃TI┃ RPL ┃ ; ┗━━━━━━━━━━━━━━━━━━━━━━━━━┻━┻━━━┛ ; TI:Table Indicator,引用描述符表位 ; TI = 0 从全局描述符表(GDT)中读取描述符; ; TI = 1 从局部描述符表(LDT)中读取描述符。 ; ; RPL:Requested Privilege Level,请求特权级位 ; 用于特权检查。 ; ; ---------------------------------------------------------------------------------------- ; 选择子属性常量定义: ; TI 位,默认为全局描述符表 SA_LDT equ 4 ; 局部描述符表,0b 1 00 ; ; RPL 位,默认请求特权级 0 SA_RPL_1 equ 1 SA_RPL_2 equ 2 SA_RPL_3 equ 3 ; ; **************************************************************************************** ; 分页机制使用的常量说明 ;---------------------------------------------------------------------------- PG_P EQU 1 ; 页存在属性位 PG_RWR EQU 0 ; R/W 属性位值, 读/执行 PG_RWW EQU 2 ; R/W 属性位值, 读/写/执行 PG_USS EQU 0 ; U/S 属性位值, 系统级 PG_USU EQU 4 ; U/S 属性位值, 用户级 %endif
BootSector.asm 上来就先把中断屏蔽掉:
; BootSectot.asm ; 引导扇区 ; 四彩 ; 2015-12-08 ; ======================================================================================== ; 电脑的启动过程: ; 1、80x86 CPU 启动后(加电或复位),CS : IP 被设置为 0xFFFF : 0x0,CPU 从此处读取指令 ; 开始执行。该单元在基本输入输出系统(Basic Input/Output System,BIOS)的地址范围内, ; 这里是一条跳转到 BIOS 中真正启动代码处的指令。 ; 2、BIOS 首先进行加电自检(Power-On Self-Test,POST),然后进行更完整的硬件检测,并加载 ; 相关设备。 ; 3、接下来按启动顺序(Boot Sequence)读取第一个设备的第一个扇区,如果该扇区最后两个字节 ; 是 0x55 和 0xAA,表明这个设备可以用于引导;如果不是,表明这个设备不能用于引导,BIOS ; 继续读取启动顺序中的下一个设备……直到找到启动设备。BIOS 把第一个启动设备的第一个扇区 ; 读到内存 0x7C00 处,然后把控制权交给该处。 ; 4、操作系统通过改写启动设备的第一个扇区,被读入内存后,从内存 0x7C00 处开始接管电脑。 ; **************************************************************************************** ; ======================================================================================== ; ---------------------------------------------------------------------------------------- ; 头文件 %include "./boot/inc/const.inc" %include "./boot/inc/FAT12.inc" ; ---------------------------------------------------------------------------------------- org 0x7C00 ; **************************************************************************************** ; ======================================================================================== ; FAT12 文件系统引导扇区的头部(前 62 字节) FAT12Head main, "NASM+GCC", "TestX_v0.01" ; **************************************************************************************** ; ======================================================================================== ; FAT12 文件系统引导扇区的引导代码(从第 62 字节开始) ; ---------------------------------------------------------------------------------------- ; 程序入口 main: ; 屏蔽中断 cli ; 初始化寄存器 mov ax, cs mov ds, ax mov ss, ax mov ax, 0x7C00 mov bp, ax mov sp, ax ; 寻找 Loader mov si, FileNameOfLoader call SearchFile cmp ax, 0 jnz .Found mov si, strNotFoundLoader call PrintStr jmp $ .Found: ; 加载 Loader push SEGMENTOFLOADER pop es mov bx, STACKSIZE call LoadFile ; 控制权交给已加载到内存的 Loader jmp SEGMENTOFLOADER : STACKSIZE ; ---------------------------------------------------------------------------------------- ; 包含 FAT12 子函数 IncludeFAT12Function SEGMENTOFTEMP ; **************************************************************************************** ; ======================================================================================== ; FAT12 文件系统引导扇区引导数据部分 strNotFoundLoader db "Not found " ; 提示信息字符串(连接着下面的文件名) FileNameOfLoader db "LOADER SYS", 0, 0 ; Loader 文件名,8 + 3格式 ; **************************************************************************************** ; ======================================================================================== ; FAT12 文件系统引导扇区引导代码的剩余部分用 0 填满,最后两个字节置结束标志(0xAA55) times 510 - ($ - $$) db 0 dw 0xAA55 ; ****************************************************************************************
loader.asm 中增加了取内存容量,再按照 ELF 文件格式把 kernel 各段重定位部分,并且把内存容量和显存相关参数保存到全局变量段了:
; loader.asm ; 加载程序 ; 四彩 ; 2015-12-08 ; ======================================================================================== ; ---------------------------------------------------------------------------------------- ; 头文件 %include "./boot/inc/const.inc" %include "./boot/inc/FAT12.inc" %include "./boot/inc/PMode.inc" %include "./boot/inc/ELF.inc" ; ---------------------------------------------------------------------------------------- org STACKSIZE jmp RMode_main ; **************************************************************************************** ;========================================================================================= [SECTION .data] ; ---------------------------------------------------------------------------------------- ; 地址范围描述符结构(Address Range Descriptor Structure) struc T_AddrRngDscStruc ; 字段名 ;偏移 长度 说明 .dwBaseAddrLow resd 1 ; 0x00 4 基地址的低 32 位 .dwBaseAddrHigh resd 1 ; 0x04 4 基地址的高 32 位(未使用,为 0) .dwLengthLow resd 1 ; 0x08 4 长度(字节)的低32位 .dwLengthHigh resd 1 ; 0x0C 4 长度(字节)的高32位(未使用,为 0) .dwType resd 1 ; 0x10 4 地址类型:1 = 可用段, 2 = 正用或保留段, endstruc ; 全局变量结构 struc T_Global_Param ; 字段名 ;偏移 长度 说明 .dwMemorySize resd 1 ; 0x00 4 内存总容量 .dwPhyAddrOfVideo resd 1 ; 0x04 4 显存基址 .wScreenX resw 1 ; 0x08 2 分辨率 X .wScreenY resw 1 ; 0x0A 2 分辨率 Y .bBitsPerPixel resb 1 ; 0x0C 1 颜色数 endstruc ; ---------------------------------------------------------------------------------------- ; 出错提示信息及 kernel 文件名 strCheckMemoryFail db "Failed to check memory", 0 strNotSupportVESA db "Not support VESA", 0 strNotFoundKernel db "Not found " ; 连接着下面的文件名 FileNameOfKernel db "KERNEL SYS", 0, 0 ; kernel 文件名,8 + 3 格式 ; ---------------------------------------------------------------------------------------- tArds istruc T_AddrRngDscStruc ; 地址范围描述符结构实体 times T_AddrRngDscStruc_size db 0 iend ; ---------------------------------------------------------------------------------------- ; GDT ; Loader 把全部内存都作为一个段使用,分为两类:代码段、数据段 ; 基址 界限 属性 Desc_Begin : Descriptor 0, 0, 0 ; 空描述符 Desc_Code : Descriptor 0, 0xFFFFF, DA_CS_ER + DA_32 + DA_4K ; 代码段 Desc_Data : Descriptor 0, 0xFFFFF, DA_DS_RW + DA_32 + DA_4K ; 数据段 Desc_End : ; ---------------------------------------------------------------------------------------- ; GDTPtr GDTPtr dw Desc_End - Desc_Begin - 1 ; 界限 dd SEGMENTOFLOADER * 0x10 + Desc_Begin ; 基址 ; ---------------------------------------------------------------------------------------- ; 选择子 SelectorCode equ Desc_Code - Desc_Begin SelectorData equ Desc_Data - Desc_Begin ; **************************************************************************************** ; ======================================================================================== [SECTION .code16] [BITS 16] ; 实模式代码段:取得内存容量、复制 kernel 到内存、设定画面模式、开启保护模式 ; ---------------------------------------------------------------------------------------- RMode_main: ; 初始化寄存器 mov ax, cs mov ds, ax mov ss, ax mov ax, STACKSIZE mov bp, ax mov sp, ax ; 获取内存信息 ; BIOS 中断 int 0x15 ; 入口参数:eax = 0xE820 功能号 ; ebx = ARDS 所需的后续值,第一个为 0 ; es : di = 内存缓冲区基址,指向一个地址范围描述符结构(ARDS) ; ecx = ARDS 的大小,通常 BIOS 总是填充 20 字节的信息到 ARDS 中 ; edx = “SMAP” 的 ASCII 码(0x0534D4150) ; 出口参数:CF 置 1 表明调用出错,否则无错 ; eax = "SMAP" 的 ASCII 码 ; ebx = 下一 ARDS 所需的后续值,如果为 0 则说明已到最后一个 ARDS。 push SEGMENTOFGPARAM pop fs xor ebx, ebx mov di, tArds .MemChkLoop: mov eax, 0xE820 mov ecx, T_AddrRngDscStruc_size mov edx, 0x534D4150 int 15h jnc .MemChkOK mov si, strCheckMemoryFail call PrintStr jmp $ .MemChkOK: mov eax, [tArds + T_AddrRngDscStruc.dwLengthLow] ; 累加内存总容量 add [fs : T_Global_Param.dwMemorySize], eax cmp ebx, 0 jne .MemChkLoop ; 复制 Kernel 到内存 mov si, FileNameOfKernel ; 寻找 call SearchFile cmp ax, 0 jnz .Found mov si, strNotFoundKernel call PrintStr jmp $ .Found: push SEGMENTOFKERNEL ; 复制 pop es xor bx, bx call LoadFile ; 设置图形模式 push SEGMENTOFTEMP ; 取得 VBE 信息 pop es xor di, di mov ax, 0x4F00 int 0x10 mov di, 0x200 mov ax, 0x4F01 ; 取得 800*600*16 模式信息 mov cx, 0x114 int 0x10 cmp ax, 0x004F jz .Support mov si, strNotSupportVESA call PrintStr jmp $ .Support: mov ax, 0x4F02 ; 设置画面模式 mov bx, 0x4114 ; 800 * 600,16 位色,线性帧缓冲区 int 0x10 ; mov ax, 0x0013 ; 320 * 200,8 位色 ; int 0x10 ; 保存显存相关信息到全局变量 mov eax, [es : 0x200 + 0x28] mov [fs : T_Global_Param.dwPhyAddrOfVideo], eax mov ax, [es : 0x200 + 0x12] mov [fs : T_Global_Param.wScreenX], ax mov ax, [es : 0x200 + 0x14] mov [fs : T_Global_Param.wScreenY], ax mov al, [es : 0x200 + 0x19] mov [fs : T_Global_Param.bBitsPerPixel], al ; 开启保护模式 lgdt [GDTPtr] ; 加载 GDT in al, 0x92 ; 打开地址线 A20 or al, 0b10 out 0x92, al mov eax, cr0 ; 置保护模式标志位 or eax, 1 mov cr0, eax ; 跳转至保护模式代码段 jmp dword SelectorCode : (SEGMENTOFLOADER * 0x10 + PMode_main) ; ---------------------------------------------------------------------------------------- ; 包含 FAT12 子函数 IncludeFAT12Function SEGMENTOFTEMP ; **************************************************************************************** ; ======================================================================================== [SECTION .code32] [BITS 32] ; 保护模式代码段,由实模式跳入:重定位 kernel,跳转到 kernel ; ---------------------------------------------------------------------------------------- PMode_main: ; 初始化寄存器 mov ax, SelectorData mov ds, ax mov ss, ax mov es, ax mov fs, ax mov gs, ax mov eax, SEGMENTOFLOADER * 0x10 + STACKSIZE mov ebp, eax mov esp, eax ; 装载 kernel 各段(重定位) xor ecx, ecx mov cx, [SEGMENTOFKERNEL * 0x10 + Tag_ELF_Header.e_phnum] ; 段数 mov esi, [SEGMENTOFKERNEL * 0x10 + Tag_ELF_Header.e_phoff] ; 指向程序头表 add esi, SEGMENTOFKERNEL * 0x10 .Load: mov eax, [esi + Tag_Program_Header.p_type] ; 只装载可装载段 cmp eax, PT_LOAD jnz .NotNeedLoad push dword[esi + Tag_Program_Header.p_filesz] mov eax, [esi + Tag_Program_Header.p_offset] add eax, SEGMENTOFKERNEL * 0x10 push eax push dword[esi + Tag_Program_Header.p_vaddr] call memcpy add esp, 3 * 4 .NotNeedLoad: add esi, Tag_ELF_Header.e_phentsize loop .Load ; 跳转至 kernel(修改 cs 和 eip) jmp SelectorCode : KERNELENTRYPHYADDR ; ---------------------------------------------------------------------------------------- ; 函数功能:内存复制 ; void memcpy(void *pDest, void *pSrc, DWORD dwSize); ; 入口参数:es : pDest = 目标内存地址 ; ds : pSrc = 源内存地址 ; dwSize = 数据长度(字节) ; 出口参数:无 memcpy: push ebp mov ebp, esp push esi push edi push ecx mov edi, [ebp + 2 * 4] ; pDest mov esi, [ebp + 3 * 4] ; pSrc mov ecx, [ebp + 4 * 4] ; dwSize jecxz .Return .Copy: lodsb stosb loop .Copy .Return: pop ecx pop edi pop esi mov esp, ebp pop ebp ret ; ****************************************************************************************
由于转到了新程序,回不去 loader 了,原来的 GDT 也失效了,要重新实现一次
/* PMode.c 保护模式相关函数 四彩 2015-12-09 */ #include "../kernel/include/PMode.h" // ======================================================================================= // 段描述符(Segment Descriptor) /* Base : dd ; 基址,32 位 Limit : dd ; 界限,32 位,低 20 位有效,高 12 位无效 Attribute : dw ; 属性,16 位,高 4 位和低 8 位有效,中间 4 位无效。 */ typedef struct tag_Segment_Descriptor { unsigned short LLimit; // 界限低 16 位(0 ~ 15) unsigned short LBase; // 基址低 16 位(0 ~ 15) unsigned char MBase; // 基址中间 8 位(16 ~ 23) unsigned char LAccess; // 属性低 8 位 /* unsigned char Type : 4; // 属性 Type 位(属性位) unsigned char S : 1; // 属性 S 位(类型位) unsigned char DPL : 2; // 属性 DPL 位(特权级位) unsigned char P : 1; // 属性 P 位(存在位) */ unsigned char HLimit_HAccess; // 界限高 4位(16 - 19)+ 属性高 4 位() /* unsigned char HLimit : 4; // 界限高 4 位(16 ~ 19) unsigned char Avl : 1; // 属性 AVL 位(可用位) unsigned char Res : 1; // 属性 Res 位(保留位,必须为 0) unsigned char DB : 1; // 属性 D/B 位(32 位位) unsigned char G : 1; // 属性 G 位(界限粒度位) */ unsigned char HBase; // 基址高 8 位(24 ~ 31) } SEGMENT_DESCRIPTOR; // 全局描述符表结构(GDT, Global segment Descriptor Table) // 把全部内存都作为一个段使用,分为两类:代码段、数据段 typedef struct tag_Global_Descriptor_Table { SEGMENT_DESCRIPTOR first; // 空段 SEGMENT_DESCRIPTOR code; // 代码段 SEGMENT_DESCRIPTOR data; // 数据段 } GDT; // GDT 描述符(描述 GDT 的描述符) typedef struct tag_GDT_Ptr { unsigned short limit; unsigned base; } GDT_PTR; // ======================================================================================= // --------------------------------------------------------------------------------------- // 描述符属性常量定义: // G 位,默认 1 字节粒度 #define DA_4K 0x8000 // 4K 字节粒度,0b 1 000 0000 0000 0000 // DB 位,默认 16 位 #define DA_32 0x4000 // 32 位,0b 1 00 0000 0000 0000 // DPL 位,默认特权级 0 #define DA_DPL_1 0x20 // DPL = 1,0b 01 0 0000 #define DA_DPL_2 0x40 // DPL = 2,0b 10 0 0000 #define DA_DPL_3 0x60 // DPL = 3,0b 11 0 0000 // P + S + TYPE 位,存在:+ 0x80(0b 1 000 0000) // 系统段 #define DA_SS_LDT 0x82 // 局部描述符表 #define DA_SS_TSKG 0x85 // 任务门 #define DA_SS_TSKSS 0x89 // 可用 386 TSS(任务状态)段 #define DA_SS_CALLG 0x8C // 386 调用门 #define DA_SS_INTG 0x8E // 386 中断门 #define DA_SS_TRPG 0x8F // 386 陷阱门 // 存储段:+ 0x10(0b 1 0000) // 数据段 #define DA_DS_R 0x90 // 只读数据段 #define DA_DS_RW 0x92 // 可读写数据段 #define DA_DS_RWA 0x93 // 可读写、已访问数据段 // 代码段 #define DA_CS_E 0x98 // 只执行代码段 #define DA_CS_ER 0x9A // 可执行、可读代码段 #define DA_CS_EC 0x9C // 可执行、一致代码段 #define DA_CS_ERC 0x9E // 可执行、可读、一致代码段 // ======================================================================================= extern void asm_lgdt(GDT_PTR *tGdtPtr); // 类似 PMode.ini 中的结构宏,将段描述符的三个属性拆开存放到合适的位置 void SetSegmentDesc(SEGMENT_DESCRIPTOR *psd, unsigned base, unsigned limit, unsigned short access) { psd->LLimit = limit & 0xFFFF; psd->LBase = base & 0xFFFF; psd->MBase = (base >> 16) & 0xFF; psd->LAccess = access & 0xFF; psd->HLimit_HAccess = ((limit >> 12) & 0xF0) | (access >> 12); psd->HBase = (base >> 24) & 0xFF; } // 初始化 GDT void InitGdt() { GDT *pGdt; GDT_PTR tGdtPtr; pGdt = (GDT *)PHYADDROFGDT; SetSegmentDesc(&(pGdt->first), 0, 0, 0); SetSegmentDesc(&(pGdt->code), 0, 0xFFFFF, DA_CS_ER + DA_4K + DA_32); SetSegmentDesc(&(pGdt->data), 0, 0xFFFFF, DA_DS_RW + DA_4K + DA_32); tGdtPtr.limit = sizeof(GDT); tGdtPtr.base = PHYADDROFGDT; asm_lgdt(&tGdtPtr); }
把初始化 GDT 的函数引出来,供 kernel 调用
/* PMode.h 保护模式 四彩 2015-12-08 */ #ifndef _PROTECTEDMODE_H_ #define _PROTECTEDMODE_H_ // GDT 表存放的物理内存地址 #define PHYADDROFGDT 0x270000 // 初始化 GDT void InitGdt(); #endif
kernel.c 不再是文本文件了,把全局变量取出来,并且试验了直接写显存。
/* kernel.c 内核 whoozit 2012-12-08 */ #include "../kernel/include/PMode.h" #include "../lib/include/string.h" // 显存相关结构 typedef struct tag_Graphics_Param { unsigned dwPhyAddrOfVideo; // 显存基址 unsigned short wScreenX; // 分辨率 X unsigned short wScreenY; // 分辨率 Y unsigned char bBitsPerPixel; // 颜色数 } GRAPHICS_PARAM; // 全局变量结构 typedef struct tag_Global_param { unsigned int dwMemorySize; GRAPHICS_PARAM tGraphics; } GLOBAL_PARAM; // 存放全局变量的物理地址(const.inc 中的 SEGMENTOFGPARAM * 0x10) #define PHYADDROFGPARAM 0x500 extern void asm_hlt(); void c_main() { unsigned i, j; unsigned short *pVideo; unsigned short Screen_X, Screen_Y; GLOBAL_PARAM g_Param; // 初始化 GDT 表 InitGdt(); // 取全局变量 memcpy((void *)&g_Param, (void *)(PHYADDROFGPARAM), sizeof(GLOBAL_PARAM)); pVideo = (unsigned short *)g_Param.tGraphics.dwPhyAddrOfVideo; Screen_X = g_Param.tGraphics.wScreenX; Screen_Y = g_Param.tGraphics.wScreenY; for(i = 0; i < Screen_X; i++) for(j = 0; j < Screen_Y; j++) pVideo[Screen_X * j + i] = (Screen_X * j + i) & 0xFFF; while(1) asm_hlt(); }
用到了 memcpy 函数,很简单,自己实现就行:
/* string.h 字符串处理函数 四彩 2015-12-08 */ #ifndef _STRING_H #define _STRING_H // 模拟同名C库函数 void *memcpy(void *pDst, const void *pSrc, unsigned int nSzie); #endif
/* string.c 字符串处理函数 四彩 2015-12-08 */ #include "../include/string.h" void *memcpy(void *pDst, const void *pSrc, unsigned int nSzie) { // 不清楚来源结构,只能逐字节复制 unsigned char *p1 = (unsigned char *)pDst; unsigned char *p2 = (unsigned char *)pSrc; // 若目标内存与源内存无重叠,或目标内存尾部与源内存头部重叠,则顺序复制 if(p1 < p2 || p1 >= (p2 + nSzie)) while(nSzie--) *p1++ = *p2++; // 若目标内存头部与源内存尾部重叠,则逆序复制 else if(p1 > p2 && p1 < (p2 + nSzie)) { p1 += nSzie - 1; p2 += nSzie - 1; while(nSzie--) *p1-- = *p2--; } // 若目标内存与源内存完全一致,则无需复制 // else if(p1 == p2) return(pDst); return(pDst); }
用到了两个个汇编指令, C 语言无能为力,只能汇编实现了:
; instr.asm ; 汇编指令函数 ; 四彩 ; 2015-12-08 ; 导出函数 global asm_hlt, asm_lgdt [SECTION .text] ; ======================================================================================== ; ---------------------------------------------------------------------------------------- ; void asm_hlt(); ; 函数功能:hlt 指令 ; 参数表:无 ; 返回值:无 asm_hlt: hlt ret ; void asm_lgdt(GDT_PTR *pGDTPtr); ; 函数功能:lgdt 指令 ; 参数表:pGDTPtr = GDT_PTR 结构地址 ; 返回值:无 asm_lgdt: push ebp mov ebp, esp push eax mov eax, [ebp + 2 * 4] lgdt [eax] pop eax mov esp, ebp pop ebp ret ; *********************************************************************************************
Makefile 文件也贴出来吧
# Makefile of TestX # 四彩 # 2012-12-08 .PHONY: all cls # kernel 入口地址(物理内存地址,必须与 loader 装载 kernel 的地址一致) KERNELENTRYPHYADDR = 0x30400 BootPath = ./boot KrlPath = ./kernel LibPath = ./lib RlsPath = ./release ImgPath = ./tool/img KrlIncPath = $(KrlPath)/include LibIncPath = $(LibPath)/include KrlIncFile = $(KrlIncPath)/PMode.h LibIncFile = $(LibIncPath)/string.h KrlNeedFile = $(RlsPath)/PMode.oc $(RlsPath)/string.obj $(RlsPath)/instr.oasm ImgNeedFile = $(RlsPath)/img $(RlsPath)/BootSector.sys $(RlsPath)/loader.sys $(RlsPath)/kernel.sys all: @[ -d $(RlsPath) ] || mkdir $(RlsPath) make vFloppy.img cls: @ -rm -rf $(RlsPath) @ -rm -f ./vFloppy.img vFloppy.img: $(ImgNeedFile) Makefile $(RlsPath)/img -n $@ $(RlsPath)/img -c $@ $(RlsPath)/BootSector.sys; $(RlsPath)/img -a $@ $(RlsPath)/loader.sys $(RlsPath)/kernel.sys $(RlsPath)/kernel.sys : $(RlsPath)/kernel.oc $(KrlNeedFile) ld -s -Ttext $(KERNELENTRYPHYADDR) -e c_main -o $@ $^ # .oc 是 C语言的核心文件 $(RlsPath)/%.oc : $(KrlPath)/%.c $(KrlIncFile) gcc -c -fno-builtin -fno-stack-protector -o $@ $< # .obj 是 C语言的库文件 $(RlsPath)/%.obj : $(LibPath)/%.c $(KrlIncFile) $(LibIncFile) gcc -c -fno-builtin -fno-stack-protector -o $@ $< # .oasm 是汇编语言的库文件 $(RlsPath)/%.oasm : $(LibPath)/%.asm nasm -felf -o $@ $< # .sys 是汇编语言的引导文件 $(RlsPath)/%.sys : $(BootPath)/%.asm nasm -o $@ $< # img $(RlsPath)/img: $(ImgPath)/img.c $(ImgPath)/FAT12.c gcc -o $@ $^
贴上运行效果图:
相关文章推荐
- 学习 AngularJS (三) module
- 学习 AngularJS (四) directive 指令
- Object-C NSTimer 使用
- iOS9横竖屏设置的处理方法和实例讲解
- 天下数据浅谈服务器网络级验证的条件及作用
- 安卓加载图片
- 创业的第八十三天
- Java实现RSA数字签名算法
- UEFI
- 一步步创建自己的npm插件
- myeclipse(eclipse)乱码处理
- 源码编译安装分离式LAMP平台
- DNS(一)服务基础知识
- Httpd简介和基本配置
- 文本处理三剑客之awk常见用法
- 硬盘与文件系统
- DNS(三)高级应用
- YUM前端程序包管理器
- Vsftpd基于MySQL实现用户认证
- Linux系统启动流程