菜鸟笔记
提升您的技术认知

操作系统笔记

进程,线程,协程与并行,并发进程线程协程的区别死锁进程,线程,多线程i++的线程安全性同步和异步孤儿进程和僵尸进程/proc进程信息linux中的分段和分页互斥量 mutex线程进程间通信进程创建进程优先级进程的基础知识进程与线程的区别(面试题)线程的控制(创建,终止,等待,分离)可重入 VS 线程安全死锁的概念一级缓存和二级缓存的理解一句话解说内存屏障 Memory barrierbrk(), sbrk() 用法详解malloc/free函数的简单实现一文讲透 “进程、线程、协程”Linux进程状态线程池的陷阱linux内核学习之进程和线程进程与线程的区别和联系内存寻址linux IO子系统和文件系统读写流程Page cache和buffer cache的区别与联系漫谈linux文件IO多线程和多进程的区别内存泄漏字节、字、位、比特的概念和关系如何避免死锁ANSI是什么编码?CPU寻址范围(寻址空间)CPU 使用率低高负载的原因创建多少个线程合适操作系统下spinlock锁解析、模拟及损耗分析线程堆栈堆和栈的内存分配堆和栈的概念和区别堆和栈的区别,申请方式,程序的内存分配什么是 POD 数据类型Linux内存分配小结--malloc、brk、mmap系统调用与内存管理(sbrk、brk、mmap、munmap)进程描述和控制CPU执行程序的原理编译的基本概念Linux虚拟地址空间布局一个程序从源代码到可执行程序的过程程序的运行机制——CPU、内存、指令的那些事分页内存管理——虚拟地址到物理地址的转换深刻理解Linux进程间通信fork之后父子进程的内存关系fork之后,子进程继承了父进程哪些内容关于协程及其锁的一些认识对协程的一点理解std::thread join和detach区别CAS和ABA问题CAS算法锁和无锁无锁队列的实现Lock-Free 编程锁开销优化以及CAS

内存寻址

阅读 : 409

最近研究了一下内存寻址,没有一份资料能讲的透彻,不是不细致,而是缺乏整体感,都不全面,让人看完后没有一个整体模型,现就我关心的问题记录如下,如果要很全面很细致的记录的话会花费我很多精力,所以只是记录大概流程,以后再慢慢修正吧。

一.分段的由来

1.8086

分段的产生主要是因为 8086 处理器,不了解的可以去查一下资料。因为 8086 有 20 根地址线,能访问 2 的 20 次方共 1MB 的地址空间,但是其寄存器确是 16 位 , 无法保存 20 位的地址。这时就将 20 位地址分为两部分,一部分是前 16 位 , 保存在 cpu 的段寄存器中 , 剩下的 4 位保存在通用寄存器中,这样就形成了所谓的 段地址 : 偏移地址 形式。把 1MB 地址空间分成了 16 份,每份 64KB ,当需要寻址时就要把两部分合并,显然合并方法就是 完整地址 = 段地址 <<4 + 偏移地址 , 那么左移四位就相当于乘以 16.

这里需要注意 :

理论上每段是 64KB ,但实际偏移地址是 16 位,故其偏移有可能超出本段的范围,那么实际上 8086 是允许这样的,这样不同的地址可以有不同的段地址 : 偏移地址 的组合方法,亦即两个段可以覆盖同一区域。这里加完之后的 20 位地址即为物理地址。

2.80386

1 )实模式

         Cpu 刚加电或复位时便进入实模式,此模式下工作方式同 8086 基本一致,同样是只能访问 1MB 地址空间,并且分段。

2 )保护模式

         实模式初始化完成后便进入保护模式,此模式下便有了虚拟地址(逻辑地址)、线性地址、物理地址的区分。

逻辑地址,即为程序代码中使用的地址,分为段基址和段内偏移地址,但其合成方法有别于 8086 ,不再是简单的加法,而要复杂得多,就是通过所谓段描述符的机构进行转换。

线性地址,即逻辑地址转换完成后形成的一个 32 位的地址 ,注意 8086 中此处是 20 位, 8086 中线性地址即为物理地址,但此处不一定是,还有分页机制。

 

物理地址,真正的物理内存的地址,要得到他可真不容易。

二.段描述符表

1.GDT 、 LDT 和 IDT 定义

描述符表有三种 , 分别为全局描述符表 GDT 、局部描述符表 LDT 和中断描述符表 IDT 。

这三个表是在内存中 由操作系统或系统程序员 所建,并不是固化在哪里,所以从理论上是可以被读写的。这三个表都是描述符表格 。描述符表是由若干个描述符组成 ,个描述符占用 8 个字节的内存空间 ,每个描述符表内最多可以有 8129 个描述符。描述符是描述一个段的大小,地址及各种状态的

 

 

1.       全局描述符表 GDT

全局描述符表在系统中只能有一个 , 且可以被每一个任务所共享 . 大部分描述符都可以放在 GDT 中,能被多个任务共享的内存区就是通过 GDT 完成的 ,

2.       局部描述符表 LDT:

局部描述符表在系统中可以有多个 , 通常情况下是与任务的数量保持对等 , 但任务可以没有局部描述符表 . 任务间不相干的部分也是通过 LDT 实现的 . 这里涉及到地址映射的问题 . 和 GDT 一样 , 中断门和陷阱门放在 LDT 中是不会起作用的 .

3.       中断描述符表 IDT:

和 GDT 一样 , 中断描述符表在系统最多只能有一个 , 中断描述符表内可以存放 256 个描述符 , 分别对应 256 个中断 . 因为每个描述符占用 8 个字节 , 所以 IDT 的长度可达 2K. 中断描述符表中可以有任务门、中断门、陷阱门三个门描述符 ,其它的描述符在中断描述符表中无意义。

4.       GDTR 寄存器

GDTR 是一个长度为 48bit 的寄存器,内容为一个 32 位的基地址和一个 16 位的段限。其中 32 位的基址是指 GDT 在内存中的地址。由系统设置初始化时设置,之后不会改变。

5.       LDTR 寄存器

LDTR 是局部描述符寄存器,由一个可见的 16 位寄存器(段选择子)和一个不可见的描述符寄存器组成(描述符寄存器实际上是一个不可见的高速缓冲区)。

注意 GDTR 和 LDTR 有区别,原因是每个 LDT 都在 GDT 中存在索引,所以 LDTR 有个段选择符,用来记录当前 LDT 在 GDT 中的索引位置,任务切换时,先将新任务的 LDT 在 GDT 中的索引存入 LDTR 的段选择符,然后再把相应的描述符内容加载到 LDTR 的段描述符寄存器,这样此任务以后的指令就不需要再次查找 GDT 表了。

2.LDT 、 TSS 和 GDT 的关系

每个任务都有自己 LDT 和 TSS ,这是确定的,但是所有任务的 LDT 表和 TSS 和 GDT 的关系是什么?

 

3. 任务切换

在这里还要引入一个段选择子的概念。段选择子是一个段寄存器 ,高 13 位用来指示描述符在描述符表中的索引号 ,低两位是表示使用描述符的特权级别;另外一位( T1 )是 GDT 和 LDT 的信号量,如果 T1=0 ,则使用 GDTR ,如果 T1=1 ,则使用 LDTR 。

系统中的段寄存器共有六个: CS 、 SS 、 DS 、 ES 、 FS 和 GS 。每个段寄存器都有其相应的一个隐藏描述符寄存器,用来保存此寄存器所选段的段描述符。当选择子被装入段寄存器时,微处理器会自动将其对应的描述符装入描述符寄存器。

系统任务切换时, LDT 切换,而 GDT 不切换(因为真个系统只有一个 GDT ),这时新任务的 LDT 描述符的选择子就被装入到 LDTR 中。然后根据其索引号在 GDT 表中查找到相应的 LDT 描述符项,把此描述符的内容加载到 LDTR 的段描述符寄存器中,这样下次就不用查找 GDT 表了。

4. 地址映射

大致流程:

A. 查看段选择符,如果其中 T1=0 ,说明是查找 GDT 表,如果 T1=1 ,则查找 LDT 表。

B. 如果是 GDT ,则从 GDTR 中取出 GDT 表在内存中的基地址,然后和段选择符的索引 index*8 相加,得到描述符项在 GDT 中的地址,然后从此项中取出段基址,再把段基址和偏移量运算得到线性地址。

C. 如果是 LDT ,则从 LDTR 中的描述符寄存器中取出 LDT 表在内存中的地址,然后和段选择符的索引 index*8 相加,得到描述符项在 LDT 中的地址,然后从此项中取出段基址,再把段基址和偏移量运算得到线性地址。

D. 如果不采用分页机制那么线性地址就已经是物理地址了。