首页 > 系统相关 >操作系统:保护模式(二)内存模型

操作系统:保护模式(二)内存模型

时间:2024-10-01 17:11:39浏览次数:8  
标签:保护模式 操作系统 eax mov 内核 物理地址 页表 内存

平坦内存模型

现代操作系统一般不会使用过于复杂的分段机制,而是采用平坦内存模型 + 分页模型来管理内存。

平坦内存模型(Flat Memory Model),这是现代操作系统(如 Linux 和 Windows)常用的内存模型。在这种模型中,所有段的段基址都为 0,段界限为 4GB,使得整个内存空间看起来像一个连续的内存块. 因此进程可以访问整个虚拟内存空间。这种模型简化了内存管理,使得代码更加接近于没有段式管理的模型。

在 32 位平坦内存模型中,虽然段的地址范围被设置为 0 到 4GB,但这并不意味着系统实际上必须有 4GB 的物理内存。

汇编定义

[SECTION .gdt]
CODE_DESC: dd 0x0000FFFF
		   dd 0x00CF9800
DATA_STACK_DESC: dd 0x0000FFFF
                 dd 0x00CF9200

代码段

数据段

获取物理内存容量

一般而言有以下办法:

  • INT 0x12 仅获取1MB以下的常规内存大小。
  • INT 0x15, AH=0x88 可以获取1MB以上的扩展内存大小,但不包括详细的内存布局。
  • INT 0x15, EAX=0xE820 是获取物理内存布局的最佳选择,适用于现代计算机。
  • 读取CMOS 可以获取扩展内存,但仅限于64MB以下。
  • UEFI 提供了更多现代化的服务来获取内存信息,但需要在EFI环境中运行。

现代一般使用 INT 0x15, EAX=0xE820 方法。

INT 0x15, EAX=0xE820 物理内存布局

一次中断只会返回一个 ARDS 也即一种布局,实际可用物理内存需要遍历 所有 Type == 1 的布局,累加并保存到缓冲区(因为保护模式下中断已不可使用)。

输入

  • EAX = 0xE820: 指定功能号,表示请求物理内存布局信息。
  • EBX: 指定调用的偏移量。第一次调用时设置为 0,后续调用时使用上一次调用返回的 EBX 继续查询,直到 EBX 返回 0 表示查询结束。
  • ES:DI: 指向保存结果的缓冲区的指针,通常为一个 ARDS 结构
struct ARDS /* 20 bits */ {
    uint64_t base_address;  // 内存区域的起始地址
    uint64_t length;        // 内存区域的长度(以字节为单位)
    uint32_t type;          // 内存类型, 操作系统一般只可以使用 type == 1 的内存
    uint32_t extended;      // 扩展属性,通常为 0
};
  • ECX: 指定 ARDS 结构的大小,通常为 20(字节数),表示缓冲区的大小。
  • EDX: 必须为签名 'SMAP'(ASCII),即 EDX = 0x534D4150

输出

  • EAX: 如果成功,则保持为 0x534D4150,即 'SMAP' 签名。
  • EBX: 如果 EBX = 0,表示已返回所有的内存区块信息,否则应将返回的 EBX 用于下一次调用。
  • CF(Carry Flag): 如果 CF = 0 表示成功,如果 CF = 1 表示错误(此时 EAX 可能包含错误代码)。
  • ES:DI: 包含内存区域描述结构 ARDS 的数据。

示例

    ; -------------------------
    ; Memory
    ; -------------------------
    xor ebx, ebx
    mov edx, 0x534D4150 ; "SMAP"
    mov di, ArdsBuf
.try_e820:
    mov eax, 0x0000E820
    mov ecx, 20
    int 0x15
    jc .e820_fail
    add di, cx
    inc word [ArdsCnt]
    cmp ebx, 0
    jnz .try_e820
    mov cx, word [ArdsCnt]
    mov ebx, ArdsBuf
    xor edx, edx
.find_usable_memory:
    mov eax, [ebx]
    add eax, [ebx + 8]
    add ebx, 20
    cmp edx, eax
    jge .next_ard
    mov edx, eax
.next_ard:
    loop .find_usable_memory
    jmp .success_get_memory
.e820_fail:
    mov ax, Message
    mov bx, 0x07
    mov cx, MessageLength
    xor dx, dx
    call printstr
    halt
.success_get_memory:
    mov [TotalMemoryBytes], edx

分页机制

对于编译器而言,地址本身是连续的,被称作线性地址。在只分段的情况下,CPU 认为线性地址等同于物理地址,这种传统模型有以下缺陷:

  • 分段模式要求每个段的内存是连续的,因此当需要分配大块内存时,可能会因为内存碎片的问题导致无法找到足够大的连续空间。这在程序运行过程中,特别是随着内存分配和释放的频繁进行,会造成内存的碎片化。
  • 分段模式无法有效实现进程间的完全隔离。不同的进程共享相同的内存空间模型,如果段描述符配置错误,可能导致进程之间的内存冲突或数据泄露。

分页机制是通过将内存分为固定大小的页来进行管理,每个页在物理内存中的位置可以不连续。分页机制是 CPU 从硬件层面就支持的功能。因此一旦启用分页机制,汇编代码的线性的地址都会被CPU根据页表自动转化为物理地址。

一级页表

CPU 规定一页的长度为 4KB, 于是 4GB 空间被分为 1 M 页,32位的线性地址被分为两部分:高20位为页表索引,低12位为页内偏移。

二级页表

一个页表项为 4个字节,完整的物理内存映射至少需要 4MB 空间以建立一级页表映射。每个进程都需要自己独立的地址空间,因此一级页表方案的内存消耗过大。

x86 默认使用二级页表分页,将1M个页平均放置到1K个页表中,每个页表包含1K个页表项,每个页表项4字节,即二级页表这个大小恰好是4KB大小,即一个页。与一级页表不同的是一级页表必须提前建立,每个进程都需要 4MB 空间进行映射,但是二级页表除了页目录表以外,其二级页表是动态建立的。极大的节约了空间。

传统x86二级分页,将线性空间分为:

  • 高10位:用来定位页目录表中的一个页目录项 (PDE)(页目录项中包含页表的物理地址
  • 中间10位:用于在某个页表中定位页表项 (PTE)
  • 低12位:页内偏移量

由于 PDE,PTE 的均为 4 字节长,在访问一个线性地址时:

  • 用虚拟地址的高10位乘以4,再加上页目录表的物理地址,便是页目录项的物理地址,读取该物理地址处的内容,获得页表的物理地址
  • 用虚拟地址的中间10位乘以4,再加上一步获得的页表的物理地址,便是页表项的的物理地址,读取页表项的内容,便可从页表项的数据结构中获取我们需要访问的物理地址
  • 将该物理地址再加上虚拟地址的低12位,便是最终我们要访问的物理地址

页表项与页目录项一致:

位位置 属性位名称 含义 常见取值
0 P (Present) 页是否存在 0: 不存在,1: 存在
1 R/W (Read/Write) 页面读写权限 0: 只读,1: 可读写
2 U/S (User/Supervisor) 用户模式和内核模式的访问权限 0: 内核模式,1: 用户模式
3 PWT (Page Write-Through) 写策略 0: 回写(Write-back),1: 写通(Write-through)
4 PCD (Page Cache Disable) 缓存策略 0: 允许缓存,1: 禁止缓存
5 A (Accessed) 该页是否被访问过 0: 未访问,1: 已访问
6 D (Dirty) 该页是否被修改过(仅页表项有效) 0: 未修改,1: 已修改
7 PS (Page Size) 页的大小 0: 4KB 页,1: 4MB 页
8 G (Global) 是否为全局页面(TLB切换时不刷新) 0: 非全局,1: 全局
9-11 AVL (Available) 保留位,操作系统可用 未定义,操作系统自定义使用
12-31 Base Address 页表或页的物理地址(对齐到4KB,低12位为0) 页表或物理页的基地址

特别的,唯一的页目录物理地址需要提前存放到 CR3 寄存器(页目录基址寄存器):

PCD, PWT 位一般都取0. 因此低12位均为0.

多进程与分页机制

  • 页表是多进程操作系统实现虚拟内存的基础,每个进程有独立的页表,保证了内存隔离。
  • 操作系统通过按需分配内存、共享内存、分页换页等机制,动态管理进程的虚拟地址空间。
  • 虚拟地址空间的划分通常包括用户空间和内核空间,进程通过页表实现虚拟地址到物理地址的映射。

用户进程通常依赖操作系统的系统调用。也即内核空间的代码,操作系统在划分地址空间时,通常会将内核映射到高位地址。所有进程的内核空间都实际上对应同一片物理地址。

示例: 类 Linux 的地址空间映射

startup_page:
    mov ecx, 4096
    mov esi, 0
.clear_page_dir:
    mov byte [PAGE_DIR_TABLE_BASE + esi], 0
    inc esi
    loop .clear_page_dir
.create_pde:
    mov eax, PAGE_DIR_TABLE_BASE
    add eax, 0x1000
    mov ebx, eax
    or eax, PG_US_U | PG_RW_W | PG_P
    ; 第 0 PDE 和 第 768 PDE 都指向同一个页表(第0PTE)
    ; 第 0 PDE 是为了将 0x00000000 - 0x003FFFFF 映射到 0x00000000 - 0x003FFFFF.
    ; 第 768 PDE 是为了将 0x00000000 - 0x003FFFFF 映射到 0xC0000000 - 0xC03FFFFF. 
    ; 因为我们内核和 loader 位于 低端 4 MB 之内, 而我们规定内核将会映射到虚拟地址的高 3GB 以上 (0xC0000000 - 0xFFFFFFFF)
    ; 至于 0 PDE 是为了保证,对于 loader 代码 (0 - 0xfffff) ,线性地址和物理地址是一样的。 
    mov dword [PAGE_DIR_TABLE_BASE + 0x0], eax
    mov dword [PAGE_DIR_TABLE_BASE + 0xc00], eax
    ; 将最后 PDE 设为页目录表的物理地址,这是为了动态操作页表
    sub eax, 0x1000
    mov dword [PAGE_DIR_TABLE_BASE + 4092], eax

    ; 创建第 0 PTE
    mov ecx, 1024 ; map 4MB
    mov esi, 0
    mov edx, PG_US_U | PG_RW_W | PG_P
.create_pte:
    mov dword [ebx + esi * 4], edx
    add edx, 4096
    inc esi
    loop .create_pte

    ; 创建内核其它 PDE
    mov eax, PAGE_DIR_TABLE_BASE
    add eax, 0x2000
    or eax, PG_RW_W | PG_US_U | PG_P
    mov ebx, PAGE_DIR_TABLE_BASE
    mov ecx, 254 ; 769 - 1022 PDE
    mov esi, 769
.create_kernel_pde:
    mov [ebx + esi * 4], eax
    inc esi
    add eax, 0x1000
    loop .create_kernel_pde
    ret

在 Bochs 下的映射

0x00000000-0x003fffff -> 0x000000000000-0x0000003fffff
0xc0000000-0xc03fffff -> 0x000000000000-0x0000003fffff
# 后面三项是由于最后 PDE 设为页目录表的物理地址,bochs 将 PDE 表本身当作了 256 项 PTE 表导致的。
0xffc00000-0xffc00fff -> 0x000000101000-0x000000101fff
0xfff00000-0xffffefff -> 0x000000101000-0x0000001fffff
0xfffff000-0xffffffff -> 0x000000100000-0x000000100fff
  • 访问页目录项:0xfffffxxx, 其中 xxx 是页目录项的索引 * 4

内核加载

用 GNU C 套件开发生成的内核本质上是一个 ELF 文件头可执行文件。这意味着:

  • 生成内核时,需要手动将可重定向文件链接时指定代码段所在线性地址 (高位 0xc0000000-0xc03fffff)
  • Loader 将原始 kernel 映像从磁盘读出,存放到内核可用内存的 (较高处) 内核本身不会触及的位置。
  • Loader 根据 ELF 文件格式,将 ELF 中的各个段展开到内核空间。
  • Loader 将控制权转移到内核。

内核加载地址的策略

  • 内核加载地址可以任意在内核空间选定,但不能破坏 loader (0x900 - 0x1500) 所在区域,扩展 BIOS 数据区 (0x9FC00-0x9FFFF), 原始 kernel 映像所在区域。
  • 内核加载完毕后可以选择覆盖原始kernel映像,但还是不能破坏 loader (0x900 - 0x1500) 所在区域,扩展 BIOS 数据区 (0x9FC00-0x9FFFF)
; ---------------------------------------------------
; KERNEL
; ---------------------------------------------------
PAGE_DIR_TABLE_BASE equ     0x500000
KERNEL_ENTRY_POINT equ    0xc0100000
KERNEL_BIN_SECTOR equ            0x9
KERNEL_BIN_ADDR equ         0x300000
KERNEL_STACK_BOTTOM equ   0xc0400000

注意:此处策略与书中不同

标签:保护模式,操作系统,eax,mov,内核,物理地址,页表,内存
From: https://www.cnblogs.com/himu-qaq/p/18442985

相关文章

  • 操作系统:保护模式(五)特权
    特权机制特权特权级数值越大,级别越小。通常,因为操作系统是为所有程序服务的,可靠性最高,而且必须对软硬件有完全的控制权,所以它的主体部分必须拥有特权级0,并处于整个环形结构的中心。也正是因为这样,操作系统的主体部分通常又被称做内核(Kernel、Core)。特权级1和2通常赋予那......
  • CUDIMM DDR5内存突然爆火!七彩虹上来就是9600MHz
    最近,本土内存品牌突然集体发力CUDIMMDDR5内存,全何、佰维、阿斯加特纷纷发布新品,频率一个比一个高,现在七彩虹也加入了战局。七彩虹首款CDUIMMDDR5内存采用SK海力士原厂颗粒,单条容量24GB,频率高达9600MHz,时序为CL44-56-56-154,和阿斯加特的一模一样。佰维和全何的频率都是9200MHz,......
  • 浮点数在内存中的存储
    1简介常见的浮点数:3.14159、1E10等1E10==1*10的10次方浮点数家族包括:float、double、longdouble类型。浮点数表示的范围:float.h中定义2问题提出我们首先来看一串代码#include<stdio.h>intmain(){intn=9;float*pFloat=(float*......
  • [rCore学习笔记 028] Rust 中的动态内存分配
    引言想起我们之前在学习C的时候,总是提到malloc,总是提起,使用malloc现场申请的内存是属于堆,而直接定义的变量内存属于栈.还记得当初学习STM32的时候CubeIDE要设置stack和heap的大小.但是我们要记得,这么好用的功能,实际上是操作系统在负重前行.那么为了实现动态内存分配功......
  • STM32出现HardFault_Handler故障的原因1. **内存溢出或访问越界** 2. **堆栈溢出**
    STM32出现HardFault_Handler故障的原因STM32系统中,HardFault_Handler故障主要有两个方面的原因:内存溢出或访问越界堆栈溢出最近我遇到的问题是栈溢出,情况如下:staticchardata[10000];voidfun1(unsignedchar*buf){inti=0;for(i=0;i<5000;i++)......
  • 阿里面试:说说 jvm 锁的膨胀过程?锁内存怎么变化的?
    文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录博客园版为您奉上珍贵的学习资源:免费赠送:《尼恩Java面试宝典》持续更新+史上最全+面试必备2000页+面试必备+大厂必备+涨薪必备免费赠送:《尼恩技术圣经+高并发系列PDF》,帮你实现技术自由,完成职业升级,薪......
  • [操作系统]线程上下文切换
    单核处理器能够支持多线程执行代码就是因为线程的上下文切换。具体是如何做到的呢?CPU通过给每个线程分配CPU时间片来实现这个机制。什么是时间片?CPU分配给每个线程的时间。时间片非常短一般几十ms。CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的。什么是线程上下......
  • AMD Instinct™ MI200 GPU内存空间概述
    AMDInstinct™MI200GPUmemoryspaceoverview—ROCmBlogs注意: 本博客之前是AMD实验室笔记博客系列的一部分。HIPAPI支持在加速系统上为主机和设备内存提供多种分配方式。在本文中,我们将:1.介绍一组常用的内存空间2.识别每种内存空间的独特之处3.讨论......
  • redis的数据结构,内存处理,缓存问题
    redisObjectredis任意数据的key和value都会被封装为一个RedisObject,也叫redis对象:这就redis的头信息,占有16个字节redis中有两个热门数据结构1.SkipList,跳表,首先是链表,和普通链表有以下差异:元素按照升序排列存储节点可能包含多个指针,指针跨度不同那么跳表的特点有以下:......
  • 操作系统1
    重点:操作系统的基本概念、地位、特征、功能、体系结构、常用的操作系统结构设计方法和作用(发展过程)难点:操作系统与硬件的关系、操作系统与应用程序的关系----------------------------------------------------------------------------------------------------------------......