好长时间没有更新了,最近比较忙。。。。。。

内存分页可以放在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-内存分页的更多相关文章

  1. 使用Vs2005打造简单分页浏览器(1)原创

    原文:使用Vs2005打造简单分页浏览器(1)原创 使用Vs2005打造简单分页浏览器(1)原创1引言2功能3实现过程以及关键点4总结5不足之处6其他7 代码下载 1    引言很早就有搞一个浏览器的 ...

  2. 一段简单简介的JAVA内存分页代码

    1.原因 工作中有的时候我们要处理的分页是无法全部用数据库去处理的,因为有些业务数据需要计算,所以我们需要把数据拿到程序中去分页 2.代码 //前端传入分页参数 Pageable pageable = ...

  3. (4.1)打造简单OS-小实验[图形显示]

    主要是实现<简单打造OS>第四小节说到的一个图形界面的实验项目 1.mbr boot.inc ;------------- loader和kernel ---------- LOADER_ ...

  4. 【操作系统之十】内存分页管理与swap

    一.虚拟内存电脑里内存分内存条(这里我们叫物理内存)和硬盘,内存条保存程序运行时数据,硬盘持久保存数据.那么虚拟内存是什么? 程序运行会启动一个进程,进程里有程序段.全局数据.栈和堆,这些都会加载到内 ...

  5. 从内存管理原理,窥探OS内存管理机制

    摘要:本文将从最简单的内存管理原理说起,带大家一起窥探OS的内存管理机制,由此熟悉底层的内存管理机制,写出高效的应用程序. 本文分享自华为云社区<探索OS的内存管理原理>,作者:元闰子 . ...

  6. JVM优化之调整大内存分页(LargePage)

    转自:http://cjjwzs.iteye.com/blog/1059381 本文将从内存分页的原理,如何调整分页大小两节内容,向你阐述LargePage对JVM的性能有何提升作用,并在文末点明了大 ...

  7. 纯手工打造简单分布式爬虫(Python)

    前言 这次分享的文章是我<Python爬虫开发与项目实战>基础篇 第七章的内容,关于如何手工打造简单分布式爬虫 (如果大家对这本书感兴趣的话,可以看一下 试读样章),下面是文章的具体内容. ...

  8. Linux的内存分页管理

    作者:Vamei 出处:http://www.cnblogs.com/vamei 严禁转载 内存是计算机的主存储器.内存为进程开辟出进程空间,让进程在其中保存数据.我将从内存的物理特性出发,深入到内存 ...

  9. Java的大内存分页支持

    原文:http://kilik.iteye.com/blog/677253 最近在研究java的性能调优,顺手写了一个小程序来测试性能问题.这个程序用来进行矩阵乘法运算,如下: for (int i ...

随机推荐

  1. Spring Cloud Alibaba学习笔记(12) - 使用Spring Cloud Stream 构建消息驱动微服务

    什么是Spring Cloud Stream 一个用于构建消息驱动的微服务的框架 应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream 中binder 交互, ...

  2. hdu 1075 map的使用 字符串截取的常用手段 以及string getline 使用起来的注意事项

    首先说字符串的截取套路吧 用坐标一个一个的输入 用遍历的方式逐个去检查字符串中的字符是否为符合的情况 如果是的话 把该字符放入截取string 中 让后坐标前移 如果不是的话 截取结束 坐标初始化 然 ...

  3. idea for mac 快捷键整理

    ⌘O 查找类文件 ⌘⌥O 前往指定的变量 / 方法 ⌘⇧O 查找所有类型文件.打开文件.打开目录,打开目录需要在输入的内容前面或后面加一个反斜杠/ ⌘⌥← / ⌘⌥→ 退回 / 前进到上一个操作的地方 ...

  4. 查询慢SQL

      可以查看当前时间访问库的所有请求SQL SELECT COUNT(*) AS c,state,info  FROM `information_schema`.processlist GROUP B ...

  5. C# 用Redis实现的分布式锁

    Redis实现分布式锁(悲观锁/乐观锁) 对锁的概念和应用场景在此就不阐述了,网上搜索有很多解释,只是我搜索到的使用C#利用Redis的SetNX命令实现的锁虽然能用,但是都不太适合我需要的场景. 基 ...

  6. 你不知道的 JSON.stringify() 的威力

    掘进:https://juejin.im/post/5decf09de51d45584d238319?utm_source=gold_browser_extension#heading-19 gith ...

  7. 【转载】C#中使用OrderBy和ThenBy等方法对List集合进行排序

    在C#的List操作中,针对List对象集合的排序我们可以使用OrderBy.OrderByDescending.ThenBy.ThenByDescending等方法按照特定的对象属性进行排序,其中O ...

  8. python day2:python的基本数据类型及其方法

    目录 python day2 1. 编码转换 2. python的基本数据类型 3. for 迭代遍历 4. 列表list 5. 元组tuple 6. 字典dict 7. 枚举enumerate 8. ...

  9. 解决mysql登录警告问题

    一.前言 我们在登录mysql的时候经常会看到一句警告: Warning: Using a password on the command line interface can be insecure ...

  10. CentOS 7中调整默认开启终端数量

    #vim /etc/securetty #然后将不需要使用到的进行注释掉就可以了 就这么简单,在强大的百度上找了半天愣是没找到