启用分页机制

news/2024/12/29 8:37:46/

前言

本博客记录《操作系统真象还原》第五章第2个实验的操作~

实验环境:ubuntu18.04+VMware , Bochs下载安装

实验内容:启动内存分页机制

实验原理:内存分页机制

前置知识

前置知识可食用内存分页机制

代码

include/boot.inc


PAGE_DIR_TABLE_POS equ 0x100000 ;二级页目录表,页表放在内存中1M起始位置连续存放,尽可能简单...(省略)
;----------------   页表相关属性    --------------
PG_P  equ   1b
PG_RW_R	 equ  00b 
PG_RW_W	 equ  10b 
PG_US_S	 equ  000b 
PG_US_U	 equ  100b 

代码说明

  1. PAGE_DIR_TABLE_POS equ 0x100000;:PAGE_DIR_TABLE_POS 用于定义页目录表的物理地址,把页目录表放置到物理内存 0x100000。
  2. 注释下方的代码是用于页目录项 PDE 和页表项 PTE 中的属性,是用二进制直接定义的。

boot/loader.S

  %include "boot.inc"section loader vstart=LOADER_BASE_ADDRLOADER_STACK_TOP equ LOADER_BASE_ADDR;构建gdt及其内部的描述符GDT_BASE:   dd    0x00000000 dd    0x00000000CODE_DESC:  dd    0x0000FFFF dd    DESC_CODE_HIGH4DATA_STACK_DESC:  dd    0x0000FFFFdd    DESC_DATA_HIGH4VIDEO_DESC: dd    0x80000007	       ; limit=(0xbffff-0xb8000)/4k=0x7dd    DESC_VIDEO_HIGH4  ; 此时dpl为0GDT_SIZE   equ   $ - GDT_BASEGDT_LIMIT   equ   GDT_SIZE -	1 times 60 dq 0					 ; 此处预留60个描述符的空位(slot)SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0         ; 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0	 ; 同上SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	 ; 同上 ; total_mem_bytes用于保存内存容量,以字节为单位,此位置比较好记。; 当前偏移loader.bin文件头0x200字节,loader.bin的加载地址是0x900,; 故total_mem_bytes内存中的地址是0xb00.将来在内核中咱们会引用此地址total_mem_bytes dd 0					 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;以下是定义gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址gdt_ptr  dw  GDT_LIMIT dd  GDT_BASE;人工对齐:total_mem_bytes4字节+gdt_ptr6字节+ards_buf244字节+ards_nr2,共256字节ards_buf times 244 db 0ards_nr dw 0		      ;用于记录ards结构体数量loader_start:;-------  int 15h eax = 0000E820h ,edx = 534D4150h ('SMAP') 获取内存布局  -------xor ebx, ebx		      ;第一次调用时,ebx值要为0mov edx, 0x534d4150	      ;edx只赋值一次,循环体中不会改变mov di, ards_buf	      ;ards结构缓冲区
.e820_mem_get_loop:	      ;循环获取每个ARDS内存范围描述结构mov eax, 0x0000e820	      ;执行int 0x15后,eax值变为0x534d4150,所以每次执行int前都要更新为子功能号。mov ecx, 20		      ;ARDS地址范围描述符结构大小是20字节int 0x15jc .e820_failed_so_try_e801   ;若cf位为1则有错误发生,尝试0xe801子功能add di, cx		      ;使di增加20字节指向缓冲区中新的ARDS结构位置inc word [ards_nr]	      ;记录ARDS数量cmp ebx, 0		      ;若ebx为0且cf不为1,这说明ards全部返回,当前已是最后一个jnz .e820_mem_get_loop;在所有ards结构中,找出(base_add_low + length_low)的最大值,即内存的容量。mov cx, [ards_nr]	      ;遍历每一个ARDS结构体,循环次数是ARDS的数量mov ebx, ards_buf xor edx, edx		      ;edx为最大的内存容量,在此先清0
.find_max_mem_area:	      ;无须判断type是否为1,最大的内存块一定是可被使用mov eax, [ebx]	      ;base_add_lowadd eax, [ebx+8]	      ;length_lowadd ebx, 20		      ;指向缓冲区中下一个ARDS结构cmp edx, eax		      ;冒泡排序,找出最大,edx寄存器始终是最大的内存容量jge .next_ardsmov edx, eax		      ;edx为总内存大小
.next_ards:loop .find_max_mem_areajmp .mem_get_ok;------  int 15h ax = E801h 获取内存大小,最大支持4G  ------
; 返回后, ax cx 值一样,以KB为单位,bx dx值一样,以64KB为单位
; 在ax和cx寄存器中为低16M,在bx和dx寄存器中为16MB到4G。
.e820_failed_so_try_e801:mov ax,0xe801int 0x15jc .e801_failed_so_try88   ;若当前e801方法失败,就尝试0x88方法;1 先算出低15M的内存,ax和cx中是以KB为单位的内存数量,将其转换为以byte为单位mov cx,0x400	     ;cx和ax值一样,cx用做乘数mul cx shl edx,16and eax,0x0000FFFFor edx,eaxadd edx, 0x100000 ;ax只是15MB,故要加1MBmov esi,edx	     ;先把低15MB的内存容量存入esi寄存器备份;2 再将16MB以上的内存转换为byte为单位,寄存器bx和dx中是以64KB为单位的内存数量xor eax,eaxmov ax,bx		mov ecx, 0x10000	;0x10000十进制为64KBmul ecx		;32位乘法,默认的被乘数是eax,积为64位,高32位存入edx,低32位存入eax.add esi,eax		;由于此方法只能测出4G以内的内存,故32位eax足够了,edx肯定为0,只加eax便可mov edx,esi		;edx为总内存大小jmp .mem_get_ok;-----------------  int 15h ah = 0x88 获取内存大小,只能获取64M之内  ----------
.e801_failed_so_try88: ;int 15后,ax存入的是以kb为单位的内存容量mov  ah, 0x88int  0x15jc .error_hltand eax,0x0000FFFF;16位乘法,被乘数是ax,积为32位.积的高16位在dx中,积的低16位在ax中mov cx, 0x400     ;0x400等于1024,将ax中的内存容量换为以byte为单位mul cxshl edx, 16	     ;把dx移到高16位or edx, eax	     ;把积的低16位组合到edx,为32位的积add edx,0x100000  ;0x88子功能只会返回1MB以上的内存,故实际内存大小要加上1MB.mem_get_ok:mov [total_mem_bytes], edx	 ;将内存换为byte单位后存入total_mem_bytes处。;-----------------   准备进入保护模式   -------------------
;1 打开A20
;2 加载gdt
;3 将cr0的pe位置1;-----------------  打开A20  ----------------in al,0x92or al,0000_0010Bout 0x92,al;-----------------  加载GDT  ----------------lgdt [gdt_ptr];-----------------  cr0第0位置1  ----------------mov eax, cr0or eax, 0x00000001mov cr0, eaxjmp dword SELECTOR_CODE:p_mode_start	     ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,; 这将导致之前做的预测失效,从而起到了刷新的作用。
.error_hlt:		      ;出错则挂起hlt[bits 32]
p_mode_start:mov ax, SELECTOR_DATAmov ds, axmov es, axmov ss, axmov esp,LOADER_STACK_TOPmov ax, SELECTOR_VIDEOmov gs, ax; 创建页目录及页表并初始化页内存位图call setup_page;要将描述符表地址及偏移量写入内存gdt_ptr,一会用新地址重新加载sgdt [gdt_ptr]	      ; 存储到原来gdt所有的位置;将gdt描述符中视频段描述符中的段基址+0xc0000000mov ebx, [gdt_ptr + 2]  or dword [ebx + 0x18 + 4], 0xc0000000      ;视频段是第3个段描述符,每个描述符是8字节,故0x18。;段描述符的高4字节的最高位是段基址的31~24位;将gdt的基址加上0xc0000000使其成为内核所在的高地址add dword [gdt_ptr + 2], 0xc0000000add esp, 0xc0000000        ; 将栈指针同样映射到内核地址; 把页目录地址赋给cr3mov eax, PAGE_DIR_TABLE_POSmov cr3, eax; 打开cr0的pg位(第31位)mov eax, cr0or eax, 0x80000000mov cr0, eax;在开启分页后,用gdt新的地址重新加载lgdt [gdt_ptr]             ; 重新加载mov byte [gs:160], 'V'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:162], 'i'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:164], 'r'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:166], 't'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:168], 'u'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:170], 'a'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:172], 'l'     ;视频段段基址已经被更新,用字符v表示virtual addrjmp $;-------------   创建页目录及页表   ---------------
setup_page:
;先把页目录占用的空间逐字节清0mov ecx, 4096mov esi, 0
.clear_page_dir:mov byte [PAGE_DIR_TABLE_POS + esi], 0inc esiloop .clear_page_dir;开始创建页目录项(PDE)
.create_pde:				     ; 创建Page Directory Entrymov eax, PAGE_DIR_TABLE_POSadd eax, 0x1000 			     ; 此时eax为第一个页表的位置及属性mov ebx, eax				     ; 此处为ebx赋值,是为.create_pte做准备,ebx为基址。;   下面将页目录项0和0xc00都存为第一个页表的地址,
;   一个页表可表示4MB内存,这样0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的页表,
;   这是为将地址映射为内核地址做准备or eax, PG_US_U | PG_RW_W | PG_P	     ; 页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问.mov [PAGE_DIR_TABLE_POS + 0x0], eax       ; 第1个目录项,在页目录表中的第1个目录项写入第一个页表的位置(0x101000)及属性(7)mov [PAGE_DIR_TABLE_POS + 0xc00], eax     ; 一个页表项占用4字节,0xc00表示第768个页表占用的目录项,0xc00以上的目录项用于内核空间,; 也就是页表的0xc0000000~0xffffffff共计1G属于内核,0x0~0xbfffffff共计3G属于用户进程.sub eax, 0x1000mov [PAGE_DIR_TABLE_POS + 4092], eax	     ; 使最后一个目录项指向页目录表自己的地址;下面创建页表项(PTE)mov ecx, 256				     ; 1M低端内存 / 每页大小4k = 256mov esi, 0mov edx, PG_US_U | PG_RW_W | PG_P	     ; 属性为7,US=1,RW=1,P=1
.create_pte:				     ; 创建Page Table Entrymov [ebx+esi*4],edx			     ; 此时的ebx已经在上面通过eax赋值为0x101000,也就是第一个页表的地址 add edx,4096      ; edxinc esiloop .create_pte;创建内核其它页表的PDEmov eax, PAGE_DIR_TABLE_POSadd eax, 0x2000 		     ; 此时eax为第二个页表的位置or eax, PG_US_U | PG_RW_W | PG_P  ; 页目录项的属性US,RW和P位都为1mov ebx, PAGE_DIR_TABLE_POSmov ecx, 254			     ; 范围为第769~1022的所有目录项数量mov esi, 769
.create_kernel_pde:mov [ebx+esi*4], eaxinc esiadd eax, 0x1000loop .create_kernel_pderet

实验操作

1.修改文件

(base) user@ubuntu:/home/cooiboi/bochs/include$ sudo vim boot.inc
(base) user@ubuntu:/home/cooiboi/bochs/boot$ sudo vim  loader.S

2.编译loader.S

sudo nasm -I include/ -o boot/loader.bin boot/loader.S
(base) user@ubuntu:/home/cooiboi/bochs$ sudo nasm -I include/ -o boot/loader.bin boot/loader.S

3.将load写入磁盘中

sudo dd if=/home/cooiboi/bochs/boot/mbr.bin of=/home/cooiboi/bochs/boot/hd60M.img bs=512 count=1 conv=notrunc
sudo dd if=/home/cooiboi/bochs/boot/loader.bin of=/home/cooiboi/bochs/boot/hd60M.img bs=512 count=3 seek=2 conv=notrunc
(base) user@ubuntu:/home/cooiboi/bochs/boot$ sudo dd if=/home/cooiboi/bochs/boot/mbr.bin of=/home/cooiboi/bochs/boot/hd60M.img bs=512 count=1 conv=notrunc
1+0 records in
1+0 records out
512 bytes copied, 0.000156073 s, 3.3 MB/s
(base) user@ubuntu:/home/cooiboi/bochs/boot$ 
(base) user@ubuntu:/home/cooiboi/bochs/boot$ sudo dd if=/home/cooiboi/bochs/boot/loader.bin of=/home/cooiboi/bochs/boot/hd60M.img bs=512 count=3 seek=2 conv=notrunc
2+1 records in
2+1 records out
1237 bytes (1.2 kB, 1.2 KiB) copied, 0.000296966 s, 4.2 MB/s

4.启动Bochs

sudo bin/bochs -f boot/bochsrc.disk
(base) user@ubuntu:/home/cooiboi/bochs$ sudo bin/bochs -f boot/bochsrc.disk

在这里插入图片描述
在这里插入图片描述

info gdtinfo 是用来查看各种数据的命令)

在这里插入图片描述

info tab(tab表示页表)查看虚拟地址映射情况。

在这里插入图片描述

说明: cr3 寄存器显示的是页目录表的物理地址。按->分成左右两列,左边列出的是 32 位虚拟地址范围,右边是虚拟地址对应的物理地址

总结

  • 获取页目录表物理地址:让虚拟地址的高 20 位为 0xfffff,低 12 位为 0x000,即 0xfffff000,这也
    是页目录表中第 0 个页目录项自身的物理地址。
  • 访问页目录中的页目录项,即获取页表物理地址:要使虚拟地址为 0xfffffxxx,其中 xxx 是页目录
    项的索引乘以 4 的积。
  • 访问页表中的页表项:要使虚拟地址高 10 位为 0x3ff,目的是获取页目录表物理地址。中间 10 位为页表的索引,因为是 10 位的索引值,所以这里不用乘以 4。低 12 位为页表内的偏移地址,用来定位页表项,它必须是已经乘以 4 后的值。

参考资料

  • 《操作系统真象还原》
  • 《操作系统真象还原》第四章 ---- 剑指Loader 刃刺GDT 开启新纪元保护模式 解放32位

http://www.ppmy.cn/news/10132.html

相关文章

【达梦8】sql语句学习笔记

目录达梦角色说明权限说明表创建角色创建用户创建模式创建表空间创建序列创建表插入数据到表中查询表数据查询sql执行计划方式1方式2执行计划说明&#xff1a;案例案例1&#xff1a;达梦角色说明 角色名称角色简单说明DBADM 数据库系统中对象与数据操作的最高权限集合&#xf…

408数据结构考点总结

数据结构考点 第一章 绪论 考点 1&#xff1a;时间复杂度与空间复杂度 时间复杂度 定义&#xff1a;将算法中基本运算的执行次数的数量级作为时间复杂度&#xff0c;记为O(n)O(n)O(n)。 计算原则 加法法则&#xff1a;T(n)T1(n)T2(n)O(f(n))O(g(n))O(max⁡(f(n),g(n)))T(…

2023-01-07:hyper/docker-registry-web是registry的web界面工具之一。请问部署在k3s中,yaml如何写?

2023-01-07&#xff1a;hyper/docker-registry-web是registry的web界面工具之一。请问部署在k3s中&#xff0c;yaml如何写&#xff1f; 答案2023-01-07&#xff1a; yaml如下&#xff1a; apiVersion: apps/v1 kind: Deployment metadata:labels:app: docker-registry-webna…

如何将python脚本打包成可执行exe文件

如何将python脚本打包成可执行exe文件 前提条件 1. 新建一个python项目&#xff0c;并且配置虚拟环境 2. 安装pyinstaller 打包EXE文件 写一个支持入参的python脚本&#xff0c;打包成exe文件 找一张图片作为exe文件的图标 百度搜索” 在线jpg转cio”,将图片转换成cio格式 …

【服务器搭建个人网站】教程五:手把手教你怎样进行公安备案 快来学~

前言 购买一台服务器&#xff0c;再来个域名&#xff0c;搭建一个自己的个人博客网站&#xff0c;把一些教程、源码、想要分享的好玩的放到网站上&#xff0c;供小伙伴学习玩耍使用。我把这个过程记录下来&#xff0c;想要尝试的小伙伴&#xff0c;可以按照步骤&#xff0c;自己…

[C++] vector 用法

leetcode 上刷题的时候&#xff0c;vector 是最常用的容器&#xff0c;记录一下用法。 初始化 // 空数组 vector<int> array;// 长度为 10 的数组 vector<int> array(10);// 长度为 10 的数组&#xff0c;每个元素初始值为 1 vector<int> array(10, 1);// …

SpringMVC 定义 Controller 的几种简单方式

实现 Controller 接口 可以通过实现 Controller 接口定义 Controller &#xff0c;代码如下&#xff1a; Controller("/controller") public class HelloBeanNameUrlController implements Controller {Overrideprotected ModelAndView handleRequest(HttpServletRe…

软测复习01:软件测试概述

文章目录软件测试的目的软件测试的定义软件测试与软件开发软件测试发展软件测试的目的 基于不同的立场&#xff0c;存在着两种完全不同的测试目的 从用户的角度出发&#xff0c;希望通过软件测试暴露软件中隐藏的错误和缺陷&#xff0c;以考虑是否可接受该产品。从软件开发者的…