(6)打造简单OS-内存分页
好长时间没有更新了,最近比较忙。。。。。。
内存分页可以放在C代码中,这样比较方便编写!即loader执行完后进入kernel_main函数之后在分配内存分页!
一、地址
讲到内存必然要讲到计算机中经常提到的一些地址。物理、线性、虚拟、逻辑、有效地址 (点击可查看具体内容)
再需要了解内存的分段和分页机制:内存管理分段和分页机制
保护模式下地址转换原理图:(可以先了解最后在深入)
原理简述过程:
保护模式下(保护模式下必分段,只不过现在寄存器和地址线位数都很大,可以进行平坦模式,即段地址为0!32位地址线可以直接寻到4G,32位寄存器可以直接装下)开始分页后,都是逻辑地址--》线性地址--》物理地址,本质,CPU最终还是会在物理内存中取值和赋值。
如上图中执行命令【mov 0X1A478BFF,%ebx】,我们编写程序的逻辑地址[只是段内偏移地址]如(0X1A478BFF,因为要取这个地址在内存中的值,然后在存储到EBX存储器中) ,要取这个地址在内存中的值,需要找到段基址,那么通过DS寄存器存储的段选择子和GDTR找到GDT表中的对应的段描述符 ,这样就找到了段基址,在加上0X1A478BFF,那么此时 线性地址= 段基址+0X1A478BFF,这样过程就由逻辑地址--》线性地址 了。【当然实际中 CPU内部有缓存寄存器,这样速度就快很多了】
首先逻辑地址--》线性地址,在第五讲中提到GDT,它是全局段描述符表,内部是一个段描述符形成的数组,GDTR存放内存中GDT表的起始地址[实模式下是真实的物理地址,保护模式下则是线性地址,开启了分页才出现了虚拟地址,分页下线性地址和虚拟地址是一个概念],第五讲中在进入保护模式前,加载了GDTR寄存器,此时存储的是物理地址!本节内存分页后还需要将重新刷新GDTR,使其变为虚拟地址【线性地址】!
需要通过分页机制来映射的线性地址便有了一个高大上的名字,虚拟地址 !
分页机制的作用有两方面
1. 将线性地址转换成物理地址
2. 用大小相等的页代替大小不等的段
总结:X86两种模式下地址到底是怎么回事?(重新在回顾一下)
线性地址给CPU看的 。CPU不需要知道有多少外设,什么种类的外设,反正它都是用地址来访问,线性地址能让CPU把任何设备当成内存。
逻辑地址是给程序的,不需要知道硬件是怎么设计的。
物理地址是给实际的硬件看的。
虚拟地址:内存的思想是程序、数据、堆栈的总大小可以超过实际可用物理内存的大小,操作系统把程序当前使用的那部分保存在物理内存当中,而其他部分保存在硬盘上。虚拟内存的实现基于分页技术。
逻辑地址要经过操作系统转换成线性地址给CPU,CPU发出线性地址给MMU再将线性地址进行转换得到物理地址去访问设备。
当CPU在实模式下时,物理地址 = 段寄存器:偏移地址
当CPU在保护模式下时,必分段,在由实模式转到保护模式前,必须设置段描述符表和GDT,段描述符存在段描述符表中,此时GDTR使用的是肯定是物理地址【实模式下都是段:偏移形成物理地址,GDTR中32位基址可以直接存储实模式下任何地址】;然后在开启CR0寄存器标志位则进入保护模式;因为保护模式要用到段描述符表GDT,用GDTR找到它,最终才能找到段基址。
进入保护模式后:【开启分页后需要再重新更新GDT,此时GDTR使用的是线性地址】
1、未开启分页时,在保护模式下的 线性地址= 段描述符表中的基址【段选择子】 + 偏移【逻辑地址】= 物理地址;
2、开启分页时,在保护模式下的 线性地址= 段描述符中的基址【段选择子】 + 偏移【逻辑地址】,由于大部分段描述符中的基地址为0,所以此时的线性地址=程序的逻辑地址,这时候CPU需要将线性地址经过CPU内部MMU转换为物理地址才能访问内存;
CR3存储页目录地址【物理地址】,如果存储的是线性地址的话,这样的话就会递归,因为线性地址转物理地址必须要用到CR3才能找到页目录首地址。
现代系统中GDT表中基地址都设置为0,那么这样就是平坦模式!这样的话逻辑地址就等于线性地址了!!
二、实模式下内存分布图
因为我们此时编写系统还处在实模式下,那么我们需要简单了解下实模式下的内存分布图。实模式下的内存布局
三、内存分页
这里我们先将其提前,用汇编方式进行简单内存分页。步骤如下:
1.初始化页目录所占的空间为0;
2.给这个页目录所在的空间中所占的第0目录项、第768目录项、第1023目录项赋初值;
3.再将物理地址0~1M内存分给页目录0项的第0~255页表项
汇编源码如下:
;开始创建页目录项(PDE)
.create_pde: ; 创建Page Directory Entry
mov eax, PAGE_DIR_TABLE_POS ; PAGE_DIR_TABLE_POS = 0x100000
add 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,表示用户属性,所有特权级别都可以访问.
; 第1个目录项,在页目录表中的第0个目录项写入第一个页表的位置(0x101000)及属性(7)
mov [PAGE_DIR_TABLE_POS + 0x0], eax
; 一个页表项占用4字节,0xc00表示第768个页表占用的目录项,0xc00以上的目录项用于内核空间,
;页目录第768项指向第一个页表
mov [PAGE_DIR_TABLE_POS + 0xc00], eax
; 也就是页表的0xc0000000~0xffffffff共计1G属于内核,0x0~0xbfffffff共计3G属于用户进程.
sub eax, 0x1000
; 使最后一个目录项指向页目录表自己的地址
mov [PAGE_DIR_TABLE_POS + 4092], eax
;下面创建页表项(PTE)
;把1M低端内存存储在第一页目录所指向的页表0~页表255内,每页4K
mov ecx, 256 ; 1M低端内存 / 每页大小4k = 256
mov esi, 0
mov edx, PG_US_U | PG_RW_W | PG_P ; 属性为7,US=1,RW=1,P=1
.create_pte: ; 创建Page Table Entry
mov [ebx+esi*4],edx ; 此时的ebx已经在上面通过eax赋值为0x101000,也就是第一个页表的地址
add edx,4096
inc esi
loop .create_pte
;创建内核其它页表的PDE
mov eax, PAGE_DIR_TABLE_POS
add eax, 0x2000 ; 此时eax为第二个页表的位置
or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性US,RW和P位都为1
mov ebx, PAGE_DIR_TABLE_POS
mov ecx, 254 ; 范围为第769~1022的所有目录项数量
mov esi, 769
.create_kernel_pde:
mov [ebx+esi*4], eax
inc esi
add eax, 0x1000
loop .create_kernel_pde
ret
指定了物理地址1M的页表 ,具体示意图如下:
程序代码如下;
1.mbr.s跟上一节中一样。
2.loader.s
;-------------- gdt描述符属性 -----------
DESC_G_4K equ 1_00000000000000000000000b
DESC_D_32 equ 1_0000000000000000000000b
DESC_L equ 0_000000000000000000000b ; 64位代码标记,此处标记为0便可。
DESC_AVL equ 0_00000000000000000000b ; cpu不用此位,暂置为0
DESC_LIMIT_CODE2 equ 1111_0000000000000000b
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2 equ 0000_000000000000000b
DESC_P equ 1_000000000000000b
DESC_DPL_0 equ 00_0000000000000b
DESC_DPL_1 equ 01_0000000000000b
DESC_DPL_2 equ 10_0000000000000b
DESC_DPL_3 equ 11_0000000000000b
DESC_S_CODE equ 1_000000000000b
DESC_S_DATA equ DESC_S_CODE
DESC_S_sys equ 0_000000000000b
;x=1,c=0,r=0,a=0 代码段是可执行的,非依从的,不可读的,已访问位a清0.
DESC_TYPE_CODE equ 1000_00000000b
;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0.
DESC_TYPE_DATA equ 0010_00000000b
DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00
DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00
DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0b
;-------------- 选择子属性 ---------------
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
;--------------------------------------------
;---------------- 页表相关属性 --------------
PG_P equ 1b
PG_RW_R equ 00b
PG_RW_W equ 10b
PG_US_S equ 000b
PG_US_U equ 100b
PAGE_DIR_TABLE_POS equ 0X100000
;----------------------------------------------
section loader vstart=0x900
jmp loader_start
;构建gdt及其内部的描述符===============================================
GDT_BASE: dd 0x00000000
dd 0x00000000
CODE_DESC: dd 0x0000FFFF
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000FFFF
dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007 ; limit=(0xbffff-0xb8000)/4k=0x7
dd DESC_VIDEO_HIGH4 ; 此时dpl为0
GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
times 60 dq 0 ; 此处预留60个描述符的空位(slot)
SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0 ; 8[1000] 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0 ; 16[10000] 同上
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0 ; 24[11000] 同上
;以下是定义gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
;======================================================================
loader_start:
;----------------- 准备进入保护模式 -------------------
;1 打开A20
;2 加载gdt
;3 将cr0的pe位置1
;----------------- 打开A20 ----------------
in al,0x92
or al,0000_0010B
out 0x92,al
;----------------- 加载GDT ----------------
lgdt [gdt_ptr]
;----------------- cr0第0位置1 ----------------
mov eax, cr0
or eax, 0x00000001
mov cr0, eax
jmp dword SELECTOR_CODE:p_mode_start ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,
; 这将导致之前做的预测失效,从而起到了刷新的作用。
[bits 32]
p_mode_start:
mov ax, SELECTOR_DATA
mov ds, ax
mov es, ax
mov ss, ax
mov esp,0x900
mov ax, SELECTOR_VIDEO
mov gs, ax
; 创建页目录及页表并初始化页内存位图
call setup_page
;要将描述符表地址及偏移量写入内存gdt_ptr,一会用新地址重新加载
sgdt [gdt_ptr] ; 存储到原来gdt所有的位置
;将gdt描述符中视频段描述符中的段基址+0xc0000000
mov ebx, [gdt_ptr + 2]
or dword [ebx + 0x18 + 4], 0xc0000000 ;视频段是第3个段描述符,每个描述符是8字节,故0x18。
;段描述符的高4字节的最高位是段基址的31~24位
;将gdt的基址加上0xc0000000使其成为内核所在的高地址
add dword [gdt_ptr + 2], 0xc0000000
add esp, 0xc0000000 ; 将栈指针同样映射到内核地址
; 把页目录地址赋给cr3
mov eax, PAGE_DIR_TABLE_POS
mov cr3, eax
; 打开cr0的pg位(第31位)
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
;在开启分页后,用gdt新的地址重新加载
lgdt [gdt_ptr] ; 重新加载
;160=80个字符[80*2]
mov byte [gs:160*8], 'J'
mov byte [gs:160*8+1],0x02
mov byte [gs:160*8+2], 'a'
mov byte [gs:160*8+3],0x02
mov byte [gs:160*8+4], 'd'
mov byte [gs:160*8+5],0x02
mov byte [gs:160*8+6], 'e'
mov byte [gs:160*8+7],0x02
mov byte [gs:160*8+8], ' '
mov byte [gs:160*8+9],0x00
mov byte [gs:160*8+10], 'O'
mov byte [gs:160*8+11],0x02
mov byte [gs:160*8+12], 'S'
mov byte [gs:160*8+13],0x02
;在开启分页后,用gdt新的地址重新加载
lgdt [gdt_ptr] ; 重新加载
mov byte [gs:320], 'V' ;视频段段基址已经被更新,用字符v表示virtual addr
mov byte [gs:321], 0x02
mov eax, 0xb8142 ;0XB8142=gs:322 段基址是0XB8000 偏移为0X142=322
mov byte [eax], 'S'
jmp $
;------------- 创建页目录及页表 ---------------
setup_page:
;先把页目录占用的空间逐字节清0
mov ecx, 4096
mov esi, 0
.clear_page_dir:
mov byte [PAGE_DIR_TABLE_POS + esi], 0
inc esi
loop .clear_page_dir
;开始创建页目录项(PDE)
.create_pde: ; 创建Page Directory Entry
mov eax, PAGE_DIR_TABLE_POS
add 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, 0x1000
mov [PAGE_DIR_TABLE_POS + 4092], eax ; 使最后一个目录项指向页目录表自己的地址
;下面创建页表项(PTE)
mov ecx, 256 ; 1M低端内存 / 每页大小4k = 256
mov esi, 0
mov edx, PG_US_U | PG_RW_W | PG_P ; 属性为7,US=1,RW=1,P=1
.create_pte: ; 创建Page Table Entry
mov [ebx+esi*4],edx ; 此时的ebx已经在上面通过eax赋值为0x101000,也就是第一个页表的地址
add edx,4096
inc esi
loop .create_pte
;创建内核其它页表的PDE
mov eax, PAGE_DIR_TABLE_POS
add eax, 0x2000 ; 此时eax为第二个页表的位置
or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性US,RW和P位都为1
mov ebx, PAGE_DIR_TABLE_POS
mov ecx, 254 ; 范围为第769~1022的所有目录项数量
mov esi, 769
.create_kernel_pde:
mov [ebx+esi*4], eax
inc esi
add eax, 0x1000
loop .create_kernel_pde
ret
程序说明图:
(1)是将硬盘中的MBR加载到内存0X7C00开始的位置,由BIOS完成
(2)mbr.s中将loader加载到内存0X900开始的位置
执行流程 BIOS[CS:IP 0XFFF0:FFF0]--》CS:IP[0:0X7C00]----(MBR将loader加载到0X900)--->CS:IP[0:0X900]---(loader中)
最后进行汇编编译
nasm mbr.S -o mbr 编译产生mbr文件
nasm loader.S -o loader 编译产生loader文件
编译后产生的两个文件放入C++工程中,在用上节中的C++代码进行编译结果生成boot.img文件,最后直接用Bochs进行测试,前面章节都有介绍如何操作,就不做讲解了!
实验结果:
(6)打造简单OS-内存分页的更多相关文章
- 使用Vs2005打造简单分页浏览器(1)原创
原文:使用Vs2005打造简单分页浏览器(1)原创 使用Vs2005打造简单分页浏览器(1)原创1引言2功能3实现过程以及关键点4总结5不足之处6其他7 代码下载 1 引言很早就有搞一个浏览器的 ...
- 一段简单简介的JAVA内存分页代码
1.原因 工作中有的时候我们要处理的分页是无法全部用数据库去处理的,因为有些业务数据需要计算,所以我们需要把数据拿到程序中去分页 2.代码 //前端传入分页参数 Pageable pageable = ...
- (4.1)打造简单OS-小实验[图形显示]
主要是实现<简单打造OS>第四小节说到的一个图形界面的实验项目 1.mbr boot.inc ;------------- loader和kernel ---------- LOADER_ ...
- 【操作系统之十】内存分页管理与swap
一.虚拟内存电脑里内存分内存条(这里我们叫物理内存)和硬盘,内存条保存程序运行时数据,硬盘持久保存数据.那么虚拟内存是什么? 程序运行会启动一个进程,进程里有程序段.全局数据.栈和堆,这些都会加载到内 ...
- 从内存管理原理,窥探OS内存管理机制
摘要:本文将从最简单的内存管理原理说起,带大家一起窥探OS的内存管理机制,由此熟悉底层的内存管理机制,写出高效的应用程序. 本文分享自华为云社区<探索OS的内存管理原理>,作者:元闰子 . ...
- JVM优化之调整大内存分页(LargePage)
转自:http://cjjwzs.iteye.com/blog/1059381 本文将从内存分页的原理,如何调整分页大小两节内容,向你阐述LargePage对JVM的性能有何提升作用,并在文末点明了大 ...
- 纯手工打造简单分布式爬虫(Python)
前言 这次分享的文章是我<Python爬虫开发与项目实战>基础篇 第七章的内容,关于如何手工打造简单分布式爬虫 (如果大家对这本书感兴趣的话,可以看一下 试读样章),下面是文章的具体内容. ...
- Linux的内存分页管理
作者:Vamei 出处:http://www.cnblogs.com/vamei 严禁转载 内存是计算机的主存储器.内存为进程开辟出进程空间,让进程在其中保存数据.我将从内存的物理特性出发,深入到内存 ...
- Java的大内存分页支持
原文:http://kilik.iteye.com/blog/677253 最近在研究java的性能调优,顺手写了一个小程序来测试性能问题.这个程序用来进行矩阵乘法运算,如下: for (int i ...
随机推荐
- puppet工作原理及部署redis主从篇
一.简介 1.国际惯例什么是puppet puppet是一种Linux.Unix.windows平台的集中配置管理系统,使用自有的puppet描述语言,可管理配置文件.用户.cron任务.软件包.系统 ...
- SQL查看数据库中每张表的数据量和总数据量
查看所有表对应的数据量 SELECT a.name AS 表名, MAX(b.rows) AS 记录条数 FROM sys.sysobjects AS a INNER JOIN sys.sysinde ...
- npm查看包版本
点击跳转 ~ 会匹配最近的小版本依赖包,比如~1.2.3会匹配所有1.2.x版本,但是不包括1.3.0 ^ 会匹配最新的大版本依赖包,比如^1.2.3会匹配所有1.x.x的包,包括1.3.0,但是不包 ...
- echarts使用结合时间轴timeline动态刷新案例
1.echarts简介 ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Fire ...
- stm32 内部flash
嵌入式闪存 闪存存储器有主存储块和信息块组成 大容量产品主存储块最大为64K×64位,每个存储块划分为256个2K字节的页 编程和擦除闪存 闪存编程一次可以写入16位(半字) 闪存擦除操作可以按页面擦 ...
- CoAP协议
CoAP(Constrained Application Protocol) CoAP是6LowPAN协议栈中的应用层协议 CoAP是超轻量型协议 CoAP的默认UDP端口号为5683 1. 四种消息 ...
- for循环的耗时问题
结论——用变量来缓存数组长度,效率会更高
- GitLab初识以及代码迁移
目录 一.理论概述 1.什么是gitlib 2.GitLab服务构成 3.Git对比SVN 二.部署 1.简单操作GitLab 三.项目实践:SVN代码迁移至GitLab 环境 1.Linux下部署S ...
- KubeEdge,一个Kubernetes原生边缘计算框架
KubeEdge成为第一个Kubernetes原生边缘计算平台,Edge和云组件现已开源! 开源边缘计算正在经历其业界最具活力的发展阶段.如此多的开源平台,如此多的整合以及如此多的标准化举措!这显示 ...
- Maven配置环境变量
Windows: 1:新建系统M2_HOME变量,并把安装maven路径拷贝上去 2:配置path变量,并把maven路径拷贝上去,这次的maven路径到bin 3:测试maven环境是否配置 ...