操作系统实现之内存分页机制.虚拟空间

内存虚拟存储主要是为了将一个进程分为不同页.存储到不同物理页中.然而不同进程的虚拟地址是可以相同的.因为MMU把进程的虚拟地址映射到各个不同的物理地址中.

以下操作系统采用二级分页.一开始CS:IP寄存器.将CS的基地址跟IP的偏移地址进行相加.得到线性地址.接着.线性地址的高10位用于当作页目录表的索引.页目录表保存的是页表的物理地址.接着.线性地址的低10位用于当作页表的索引.页表保存的是4k大小的页块.线性地址低10位那就是页块的偏移地址.由此虚拟地址->物理地址转换结束.当然操作系统在创建页目录表的时候.要把页目录表的物理地址给加载到cr3寄存器中.

开启分页机制分为3件事.

(1)设置页目录表跟页表

(2)把页目录表的地址加载到cr3寄存器中

(3)将寄存器的cr0的PG位置为1表示开启了分页机制

规划页目录表跟页表位置

在用户进程.高3G部分给操作系统.而0~3G是用户的空间.每个进程都有页表.然后一个用户进程的功能完成是需要内核配合的.于是所有用户进程应该共享内核.也就是等于3G-4g的所有用户进程空间指向的都是同一个物理页地址

这里我们将把页目录表放在0x100000处.页表也挨着页目录表放在0x101000处(第二个页表.当然在此之前应该把物理内存给算出来.这里可以使用bios中断来获取物理内存

%include "boot.inc"
section loader vstart=loader_base_addr
;------------全局描述符表的定义
gdt_base:
	 dd 0x00000000    ;全局描述表.第一个描述符要为空
     dd 0x00000000
code_base:
     dd 0x0000FFFF
     dd desc_code_high4
data_stack_desc:
	dd 0x0000ffff
	dd desc_data_high4
video_desc:
	dd 0x80000007   ;段界限 limit=0x7fff.基址位于0xb8000
	dd desc_video_high4
;---------GDT的属性
gdt_size  equ $-gdt_base  ;GDT大小
gdt_limit equ gdt_size    ;GDT限制
times 60 dq 0  ;预留60个描述符的空位置
;以下定义一个数用来保存笔记本的内存大小,以上一共0x200个字节(64*8=0x200)
total_mem_bytes  dd 0
;----------定义段选择子-------------
selector_code equ (0x0001<<3)+ti_gdt+rpl0
selector_data equ (0x0002<<3)+ti_gdt+rpl0
selector_video equ (0x0003<<3)+ti_gdt+rpl0
;定义gdt的指针,前2个字节为gdt界限,后4个字节为gdt起始地址
gdt_ptr dw gdt_limit
        dd gdt_base
;-----定义ards结构体数量-------------
 ards_buf times 244 db 0
 ards_nr dw 0		      ;用于记录ards结构体数量

;以上一共0x300个字节------------------
loader_start:
;-------  int 15h eax = 0000E820h ,edx = 534D4150h ('SMAP') 获取内存布局  -------
   xor ebx, ebx		      ;第一次调用时,ebx值要为0
   mov 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 0x15
   jc .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_low
   add eax, [ebx+8]	      ;length_low
   add ebx, 20		      ;指向缓冲区中下一个ARDS结构
   cmp edx, eax		      ;冒泡排序,找出最大,edx寄存器始终是最大的内存容量
   jge .next_ards
   mov edx, eax		      ;edx为总内存大小
.next_ards:
   loop .find_max_mem_area
   jmp .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,0xe801
   int 0x15
   jc .e801_failed_so_try88   ;若当前e801方法失败,就尝试0x88方法
;1 先算出低15M的内存,ax和cx中是以KB为单位的内存数量,将其转换为以byte为单位
   mov cx,0x400	     ;cx和ax值一样,cx用做乘数
   mul cx
   shl edx,16
   and eax,0x0000FFFF
   or edx,eax
   add edx, 0x100000 ;ax只是15MB,故要加1MB
   mov esi,edx	     ;先把低15MB的内存容量存入esi寄存器备份
;2 再将16MB以上的内存转换为byte为单位,寄存器bx和dx中是以64KB为单位的内存数量
   xor eax,eax
   mov ax,bx
   mov ecx, 0x10000	;0x10000十进制为64KB
   mul 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, 0x88
   int  0x15
   jc .error_hlt
   and eax,0x0000FFFF

   ;16位乘法,被乘数是ax,积为32位.积的高16位在dx中,积的低16位在ax中
   mov cx, 0x400     ;0x400等于1024,将ax中的内存容量换为以byte为单位
   mul cx
   shl 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处。
;-----进入保护模式----------
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
.error_hlt:		      ;出错则挂起
   hlt
[bits 32]
p_mode_start:
	mov ax,selector_data
	mov ds,ax
	mov es,ax
	mov ss,ax
	mov esp,loader_stack_top
	mov ax,selector_video
	mov gs,ax

;创建页目录表
call set_page
sgdt [gdt_ptr]  ;加载GDT
mov ebx,[gdt_ptr+2]
or  dword [ebx+0x18+4] ,0xc0000000  ;+4是因为.段描述的高4字节的段基址是31-24.
;将gdt的基址加上0xc00000000使其成为内核的高地址
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
lgdt[gdt_ptr]
mov byte [gs:160],'V'
jmp $
;------------创建页目录表以及页表
set_page:
	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:
	mov eax,page_dir_table_pos
	add eax,0x1000  ;页表地址
	mov ebx,eax   ;ebx指向第一个页表地址

	or eax, pg_us_u | pg_rw_w | pg_p

	mov [page_dir_table_pos +0x0],eax ;将第一个页表写到页目录表的第一项
	mov [page_dir_table_pos+0xc00],eax ;一个页表项占用4字节,0xc00表示第768个页表占用的目录项,0xc00以上的目录项用于内核空间
	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
.create_pte:
  mov [ebx+esi*4],edx
  add edx,4096
  inc esi
  loop .create_pte
;--------创建内核跟页表的pde
mov eax,page_dir_table_pos
add eax,0x2000 ;指向第二个页表
or eax,pg_us_u | pg_rw_w | pg_p
mov ebx,page_dir_table_pos
mov esi,769
mov ecx,254
.create_kernel_pde:
	mov [ebx+esi*4],eax
	inc esi
	add eax,0x1000
	loop .create_kernel_pde
	ret

时间: 2024-10-09 20:52:39

操作系统实现之内存分页机制.虚拟空间的相关文章

32机的内存分页机制

在实模式下寻址的时候,"段寄存器+偏移地址"进过转换计算以后得到的地址是"物理地址",也就是在物理内存中的实际地址,而在保护模式下,"段选择器+偏移地址"转换后的地址被称为"线性地址"而不是"物理地址",那么线性地址就是物理地址吗? 答案可能是,也可能不是,这取决于80386的内存分页机制是否被使用. 为什么有内存分页机制? 我们回顾一下,单任务的DOS系统中,一个应用程序可以使用所有的空闲内存,程序退出以

轻量级操作系统FreeRTOS的内存管理机制(三)

本文由嵌入式企鹅圈原创团队成员朱衡德(Hunter_Zhu)供稿. 轻量级操作系统FreeRTOS的内存管理机制(二)中讲到,heap2.c的内存管理机制会导致内存碎片的问题,系统运行久后会出现无法分配大块内存的情况,heap4.c中的管理机制提供了解决方法,它是在heap2.c的基础上添加了地址相邻空闲块间合并的功能,而heap5.c是对heap4.c的进一步扩展,它能够支持多块不连续分布的RAM空间作为堆使用,本篇将对heap4.c.heap5.c中的管理机制进行分析. 一.heap4.c

轻量级操作系统FreeRTOS的内存管理机制(一)

本文由嵌入式企鹅圈原创团队成员朱衡德(Hunter_Zhu) 近几年来,FreeRTOS在嵌入式操作系统排行榜中一直位居前列,作为开源的嵌入式操作系统之一,它支持许多不同架构的处理器以及多种编译工具链,具有轻量级.容易移植和使用的特点.本篇文章将会对FreeRTOS提供的几种内存分配策略进行介绍,FreeRTOS允许开发者根据自己的项目实际需要选择不同的内存分配策略或者自定义分配内存策略. 一.FreeRTOS内存分配源码 FreeRTOS在创建任务.队列.互斥量.信号量.软件定时以及事件组的时

轻量级操作系统FreeRTOS的内存管理机制(二)

本文由嵌入式企鹅圈原创团队成员朱衡德(Hunter_Zhu)供稿. 上一篇文章中介绍了FreeRTOS多种内存管理机制中最简单的一种:全局声明一个静态数组ucHeap,然后通过指针偏移记录空间的分配情况,在这种内存机制下无法对内存进行释放.同时也介绍了内存操作过程中字节对齐的细节,本篇文章将会对FreeRTOS源码中第二种内存管理机制heap2.c进行讲解,在heap2.c中同样使用一个全局静态数组ucHeap来表示内存,heap2.c内存管理机制较heap1.c而言增加了内存释放的功能,通过使

【自制操作系统05】开启内存分页机制

通过前四章的努力,我们成功将控制权转交给了 loader.asm 这个程序,并且从实模式跨越到了保护模式.第四章讲保护模式的时候我说过,这是我们操作系统的第一个精彩之处.但其实这只是针对之前我们进行的只是无意义的输出,以及硬盘的加载等工作.但到了这一章,之前一步步的努力进入到了保护模式,也只能说是做了很多苦力,其实很多代码都是固定的,给我们发挥的空间也不大. 但是到了本章,可以说终于有能体现出我们设计能力的地方了. 一.实现分页要做哪些事 还是先直接简单说要做的事,再说为什么,实现分页要做以下三

CPU内存管理和linux内存分页机制

一.基本概念 物理地址(physical address) 用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应. ——这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到 最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样.所以,说它是“与 地址总线相对应”,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与

32位机内存管理机制(上)

一直有看linux内核的冲动,内核有些部分是汇编编写的,无奈汇编不大懂,所以利用五一三天假期大概走了一边8086CPU架构的汇编,8086CPU还是16位的,我们现在都进入64位时代了,这两者之间有很大的区别,但是看看16位的CPU汇编还是很重要的,这有助于理解32位的80386CPU.这篇文章来分析下80386的内存管理的一些基础知识,包括实模式.保护模式和内存寻址等等. 1.实模式 处理器被复位或者加电的时候以实模式启动.这时候处理器中各寄存器以实模式的初始化值工作. 80386处理器在实模

Intel微处理器学习笔记(四) 内存分页

内存分页机制(memory paging mechanism)是从386开始的.线性地址通过分页机制透明转换为物理地址. 线性地址(linear address)为程序产生的地址: 物理地址(physical address)为程序访问的实际存储器地址. 与分页机制相关的寄存器有CR0.CR1.CR2.CR3和CR4,且都是32位寄存器. 图一 控制寄存器 1. CR0的PG位为1时,分页启动.否则,线性地址即物理地址.在实模式和保护模式下分页机制都可工作. 2. CR3包括页目录基地址以及PC

内存分页学习笔记(一)

内存分页机制(memory paging mechanism)允许为任何线性地址分配任何物理存储器地址.线性地址(linear address)定义为由程序产生的地址,而物理地址(physical address)是程序访问的实际存储器地址. 通过内存分页机制,线性地址透明地转换为任何物理地址(通过分页机制重定位). 分页寄存器:CR0~CR4 .CR0的最左一位(PG)置位(1)时,就选择分页.如果PG位被清0,则程序产生的线性地址就是用于访问存储器的物理地址. 实模式和保护模式下分页机制都可