`
836811384
  • 浏览: 547896 次
文章分类
社区版块
存档分类
最新评论

Linux内存管理_01_分段

 
阅读更多

原文我写在了github上:http://gaopenghigh.github.io/mm_01_segment/


06 November 2013

作者:gaopenghigh(gaopenghigh@gmail.com),转载请注明出处。

基础概念

逻辑地址

指程序中看到的地址,之后我们会看到,在Intel的分段架构中,每个逻辑地址都由一个 段(segment)加上一个偏移量(offset)组成。 对于每一个进程来说,它看到的都是一整个虚拟内存空间,它并不知道实际情况是很多个 进程共用一块小得多的物理内存。实现这种效果的技术,就是内存管理。

线性地址

或者说是线性地址空间,表示CPU能够寻址的整个地址空间。通常情况下线性地址空间 比物理内存大。对于32位系统,线性地址空间就有4G大小。

物理地址

就是真实的内存,通过芯片控制存储和读取,显然空间有限。CPU通过前端总线( Front Side Bus)连接到北桥(Northbridgh),再连接到内存条。在FSB中交换的内存 地址都是真实的内存地址。

程序使用逻辑地址,操作内存使用的是物理地址,所以必须把逻辑地址转换为物理地址。 主要的转换流程如下图(来自Memory Translation and Segmentation):

硬件中的分段–处理器怎么访问物理内存

分段(Segmentation)

Intel处理器以两种不同的方式进行地址转换:实模式(real-mode)和保护模式( protected-mode)。一般情况下处理器工作在protected-mode,real-mode存在的原因一是 与早期的处理器兼容,二是在系统启动(自举)的过程中会使用到。(参考计算机的启动

历史

原始的8086芯片具有16位的寄存器,于是它可以使用2^16=64K字节的内存。为了使用更多 的内存,Intel引入了“段寄存器(segment register)”,该寄存器的作用就是告诉CPU 到底应该使用哪个64K的“段”。段寄存器中指示了一个64K段的起始地址,于是程序中的16 位地址就相当于一个偏移(offset)。一共有4个段寄存器,一个给Stack使用(ss),一个 给程序代码使用(cs),还有两个给程序数据使用(ds, es)。

现在

段寄存器在现在仍然在使用,其中存着16位的Segment selectors。 其使用的方法在real-mode和protected-mode下不太一样。

real-mode

在real-mode下,段寄存器中的内容需要标识一个段的起始地址。基于硬件成本等的考虑, Intel使用段寄存器中的4位,这样就能分出2^4=16个64K的段,一共就是1M。

protected-mode

在32位的protected-mode下,段寄存器中的内容叫做段选择符(segment selectors), Segment selector代表了一个叫做“段描述符(segment descriptors)”的表的索引。 这个表实际上就是一个数组,每个元素有8个字节,每个元素(segment descriptor)表示 一个段。Segment descriptor的结构如下图:

其中,

  • Base Address中的32位地址代表了这个段的起始地址
  • Limit表示这个段有多大
  • DPL代表段描述符的等级,控制对段的访问。可以是0到3的数字。0代表最高等级 (内核态),3代表最低等级(用户态)

这些段描述符被存在两个表里面:GDT(Global Descriptor Table)和LDT (Local Descriptor Table)。每个CPU核都有一个叫做gdtr的寄存器,里面放着GDT的 地址。所以在16位的段寄存器中的内容(segment selector)就有如下的结构:

其中,

  • TI代表是GDT或者是LDT
  • Index代表这个段在表中的索引
  • PRL指Requested Privilege Level

总结下来就是,CPU通过段寄存器存储segment selector,通过segment selector在GDT表 或者LDT表中找到对应的segment 的segment descriptor,获取到这个段的Base Address、 大小等信息。如图:

途中的“Noprogrammable Reigster”表示一组非编程寄存器,它存储segment selector指定 的segment descriptor,这样不用每次都去查GDT,能够更快地进行逻辑地址到线性地址的 转换。

把Base Address和逻辑地址(logical address)相加,就得到了线性地址(linear address) 。也就是说,程序使用的是逻辑地址,经过分段机制,CPU把逻辑地址转换成了线性地址 (linear address),再经过分页机制,最后得到了物理地址。

整个过程可以通过下图表示:

分段单元(segment unit)执行的过程如下:

  • 从段寄存器获取segment selector
  • 根据segment selector的内容从GDT或者LDT中获取segment descriptor,GDT的位置由 gdtr寄存器存储
  • 根据segment descriptor获取到这个段的Base Address,把Base Address和逻辑地址 (logical address)相加,就得到了线性地址(linear address)。

Flat Mode

这里就有个问题:如果寄存器还在是16位的,那么每个程序的逻辑地址就只有64K大小。 但我们又想使用更大的物理内存,于是使用了分段机制,让逻辑地址加上了一个段地址, 得到一个线性地址,代表某个段中的内存,最后在通过分页机制转换为物理地址。但是 在32位CPU中,寄存器和指令本身就能对整个线性地址进行寻址,为什么 还要做这个分段呢?直接把Base Address置为0不就好了,这样逻辑地址和线性地址实际上 就相等了。事实上的确可以,Intel把这种模式叫做flat model,这种模式也正是内核所 使用的。

地址转换概览

下面的图代表了一个用户态中的程序,发出一个JMP命令时发生的情况:

Linux主要使用的就是4个段:用户态下的数据段和代码段,内核态下的数据段和代码段。 运行过程中,每个CPU内核都有自己的GDT。所以主要有4个GDT:两个给内核态的 code和data用,另外两个给用户态下的code和data使用。

值得注意的是,在GDT中的数据都是以cache line的大小对齐的。

底层的权限和等级

介绍

这里所指的权限和等级(privilege)不是root和普通用户这些等级,而是对系统底层资源 的权限控制。所谓底层资源,主要包括三种:内存、I/O端口、对特定指令的执行权限。

Intel架构中,包括了4个等级,用数字0到3代替,0的等级最高,3的等级最低。在任意一 个时刻,CPU都运行在某个等级中,这就限制了CPU不能做某些事情。

对于Linux内核以及大多数其它内核,事实上只使用了两个等级:0和3。

大概有15个指令处于等级0中,比如对内存和I/O端口的操作相关的指令。 试图在其他等级运行这些指令,比如当程序试图操作不属于它的内存时。就会导致一些错 误。

正是因为有这些限制,用户态的程序不能直接对内存、I/O等进行操作,而只能通过系统 调用实现。

CPU如何知道运行于哪个等级下

通过前面的讨论,我们知道,在段寄存器中存着叫做“段选择器(segment selector)的 内容,里面有个字段叫RPL或者CPL:

  • RPL,Requested Privilege Level,对于数据段寄存器ds或者stack段寄存器ss。RPL 的内容不能被mov等指令修改,而只能通过那些对程序运行流程进行修改的指令,比如 call等, 来进行设置。
  • CPL,Current Privilege Level,对于代码段寄存器cs。前面说到,RPL可以通过代码 进行修改,而CPL的内容被CPU自己维护,它总是等于CPU当前的等级。

CPU对内存的保护

当一个段选择器(segment selector)加载到寄存器,或者当通过线性地址访问一页内存 时,CPU都会对内存进行保护,其保护的原理如下图:

MAX()选择RPL和CPL中最小的一个等级,把它和段描述符(segment descriptor)中的DPL进 行比较,当大于DPL时(即当前的CPU的等级CPL或者需要的等级RPL小于这个段的等级时) ,就触发错误。

但由于现在的内核都是使用的是flat模式,意味着用户态的段可以使用整个线性地址 空间,所以CPU对内存真正的保护体现在分页,即线性地址转换为物理地址的时候。 内存以页为单位进行管理,每个页由“页表项(page table entry)进行描述,页表象中有 两个字段和保护有关系:一个是supervisor flag,另一个是read/write flag。 当supervisor flag被标记时,这页的内容就不能在等级3下进行访问。 Linux的fork使用“写时复制”技术,当子进程被fork出来时,父进程的内存页通过 read/write flag被标记为只读,并且和子进程共享,当任何一个进程试图修改其内容时, 就触发一个错误来通知内核,复制一份内容并标记为可可读/写。

等级的切换通过中断机制进行。

TODO: 细节


参考资料:


分享到:
评论

相关推荐

    Linux内存管理之分段机制

     如上图,Linux在内存管理上,把逻辑地址通过分段机制变化成线性地址,线性地址也是4G(32位系统)的程序地址。线性地址再通过分页机制转化成物理地址,后CPU去访问物理地址。  去年写个一篇关于IA32内存寻址的...

    linux x86内存管理之分段与分页

    主要分析了linux启动过程中,对分段与分页的理解,以及虚拟地址到物理地址的转换。

    linux 内存管理

    linux 内存管理 分段 分页 伙伴算法 Linux如何有效地利用x86的分段和分页机制把逻辑地址转换为物理地址 RAM的某些部分永久地分配给内核,用以存放内核代码以及静态数据

    linux内核开发-内存管理

    linux内核开发-内存管理 详细讲解分页管理与分段管理,国嵌培训教材

    Linux内存管理

    i386保护模式的分段与分页 Linux分页 线性地址空间分布 用户地址空间 内核地址空间 空闲物理内存管理 内核物理内存分配接口 共享存储

    疯狂内核之——Linux虚拟内存

    1.4 Linux内存布局 21 1.5 内核空间和用户空间 23 1.5.1 初始化临时内核页表 24 1.5.2 永久内核页表的初始化 32 1.5.3 第一次进入用户空间 41 1.5.4 内核映射机制实例 44 1.6 固定映射的线性地址 48 1.7 高端内存...

    Linux下内存寻址.pdf

    想写介绍了MMU机制中的分段分页机制,是学习linux系统内存管理比较好的资料

    嵌入式linux开发 (三十一) 内存管理2.0(2) freertos内存管理

    内存分段初始化部分 startup_stm32f407xx.s Reset_Handler 初始化栈 // 系统.stack段 // .code 段没做动作 // .bss 段没做动作 从flash 上 加载data 段入sram 清bss段 SystemInit __libc_init_array...

    Understanding the Linux Kernel

     第八章内存管理  页框管理  内存区管理  非连续内存区管理  第九章进程地址空间  进程的地址空间  内存描述符  线性区  缺页异常处理程序  创建和删除进程的地址空间  堆的管理  第十章系统调用  ...

    清华大学Linux操作系统原理与应用

    4.1 Linux的内存管理概述 67 4.1.1 虚拟内存、内核空间和用户空间 67 4.1.2 虚拟内存实现机制间的关系 69 4.2 进程用户空间的管理 70 4.2.1 进程用户空间的描述 71 4.2.2 进程用户空间的创建 74 4.2.3 虚存映射 76 ...

    Linux2.6内核标准教程(共计8-- 第1个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第6个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第8个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第3个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第7个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    第16章 存储管理1

    背景知识x86的分段机制的分段机制I386内存寻址的硬件支持内存寻址的硬件支持硬件及硬件及LinuxLinux的分段机制的分段机制硬件及硬件及LinuxLinu

    Linux2.6内核标准教程(共计8--第4个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第2个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第5个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

Global site tag (gtag.js) - Google Analytics