1、内存寻址的三种模型 1. 地址的种类首先明确一下逻辑地址和线性地址这两个概念:1. 逻辑地址2. 线性地址3. 物理地址1.1 逻辑地址:逻辑地址是编译器生成的,我们使用在 linux 环境下,使用 C 语言指针时,指针的值就是逻辑地址。对于每个进程而言,他们都有一样的进程地址空间,类似的逻辑地址,甚至很可能相同。1.2 线性地址:线性地址是由分段机制将逻辑地址转化而来的,如果没有分段机制作用,那么程序的逻辑地址就是线性地址了。1.3 物理地址物理地址是 CPU 在地址总线上发出的电平信号,要得到物理地址,必须要将逻辑地址经过分段,分页等机制转化而来。2. 三种寻址模型x86 体系结构下,使用
2、的较多的内存寻址模型主要有三种:1. 实模式扁平模型 real mode flat model2. 实模式分段模型 real mode segment model3. 保护模式扁平模型 protected mode flat model下面是对这三种模型的描述实模式和保护模式相对,实模式运行于 20 位地址总线,保护模式则启用了 32 位地址总线,地址使用的是虚拟地址,引入了描述符表;虽然二者都引入了段这样一个概念,但是实模式的段是 64KB 固定大小,只有 16KB 个不同的段,CS,DS 等存储的是段的序号(想想为什么?)。保护模式则引入了 GDT 和 LDT 段描述符表的数据结构来定义每
3、个段。扁平模型和分段模型相对,区别在于程序的线性地址是共享一个地址空间还是需要分成多个段,即为多个程序同时运行在同一个 CS,DS 的范围内还是每个程序都拥有自己的CS,DS:前者(flat)指令的逻辑地址要形成线性地址,不需要切换 CS,DS ;后者的逻辑地址,必须要经过段选择子去查找段描述符,切换 CS,DS,才能形成线性地址。3. 实模式扁平模型该模式只有在 386 及更高的处理器中才能出现!80386 的实模式,就是指 CPU 可用的地址线只有 20 位,能寻址 01MB 的地址空间。注意:80386 的实模式并不等同于 8086/8088 的实模式,后者的实模式其实就是实模式分段模型
4、!扁平模型,意味着我们这里不使用任何的分段寄存器。(其实还是使用了 CS,DS,只是不用程序员去显示地为该寄存器赋值,jmp 指令时就已经将 CS, DS 设置好了)Linux 启动代码 arch/i386/boot/bootsect.S 就工作在该模式下。逻辑地址 - 物理地址4. 实模式分段模型对于 8086/8088 运行在实模式的程序,其实就是运行在实模式分段模型中。对于不同的程序,有不同的 CS,DS 值,每个程序的段起始地址都不同。对于这样的程序而言,偏移地址 16 位的特性决定了每个段只有 64KB 大小。详细过程可以用下图来描述:该图来自 http:/duartes.org/g
5、ustavo/blog/5. 保护模式扁平模型我们的 Linux, Window XP/7 采用的内存寻址模型,Linux 中,段主要分为 4 种,即为内核代码段,内核数据段,用户代码段,用户数据段。对于内核代码段和数据段而言,CS,DS 的值是 0xC00000000,而用户代码和数据段的CS,DS 的值是 0x00000000当 CPU 运行于 32 位模式时,不管怎样,寄存器和指令都可以寻址整个线性地址空间,所以根本就不需要再去使用基地址。基址可以设为一个统一的值。下面是一个示例,该图来自 http:/duartes.org/gustavo/blog/扁平模型在这里得到了很好的体现,这个
6、用户空间的程序基地址是 0,。事实上,所有的用户空间的程序,其 GDT 指向的基地址都是 0。posted 2011-12-01 16:39 李洛克 阅读(12) 评论(0) 编辑2011 年 11 月 20 日Linux Cache 机制在阅读文章前,您应该具备基本的存储器层次结构知识,至少要了解局部性原理。要详细了解 cache 基本原理,可以参考本书 深入理解计算机系统中存储器体系结构一章:带着疑问来看文章,cache 对于程序员是不可见的,它完全是由硬件控制的,为什么在linux 内核中还有 cache.h 这个头文件,定义了一些关于 cache 的结构?1. cache 概述cach
7、e,中译名高速缓冲存储器,其作用是为了更好的利用局部性原理,减少 CPU 访问主存的次数。简单地说,CPU 正在访问的指令和数据,其可能会被以后多次访问到,或者是该指令和数据附近的内存区域,也可能会被多次访问。因此,第一次访问这一块区域时,将其复制到 cache 中,以后访问该区域的指令或者数据时,就不用再从主存中取出。2. cache 结构假设内存容量为 M,内存地址为 m 位:那么寻址范围为 00000FFFF(m 位)倘若把内存地址分为以下三个区间:深入理解计算机系统p305 英文版 beta draft tag, set index, block offset 三个区间有什么用呢?再来
8、看看 Cache 的逻辑结构吧:将此图与上图做对比,可以得出各参数如下:B = 2bS = 2s现在来解释一下各个参数的意义:一个 cache 被分为 S 个组,每个组有 E 个 cacheline,而一个 cacheline 中,有 B 个存储单元,现代处理器中,这个存储单元一般是以字节(通常 8 个位)为单位的,也是最小的寻址单元。因此,在一个内存地址中,中间的 s 位决定了该单元被映射到哪一组,而最低的 b 位决定了该单元在 cacheline 中的偏移量。valid 通常是一位,代表该cacheline 是否是有效的(当该 cacheline 不存在内存映射时,当然是无效的)。tag
9、就是内存地址的高 t 位,因为可能会有多个内存地址映射到同一个 cacheline 中,所以该位是用来校验该 cacheline 是否是 CPU 要访问的内存单元。当 tag 和 valid 校验成功是,我们称为 cache 命中,这时只要将 cache 中的单元取出,放入 CPU 寄存器中即可。当 tag 或 valid 校验失败的时候,就说明要访问的内存单元(也可能是连续的一些单元,如 int 占 4 个字节, double 占 8 个字节)并不在 cache 中,这时就需要去内存中取了,这就是 cache 不命中的情况(cache miss)。当不命中的情况发生时,系统就会从内存中取得该
10、单元,将其装入 cache 中,与此同时也放入 CPU 寄存器中,等待下一步处理。注意,以下这一点对理解 linux cache 机制非常重要:当从内存中取单元到 cache 中时,会一次取一个 cacheline 大小的内存区域到 cache中,然后存进相应的 cacheline 中。例如:我们要取地址 (t, s, b) 内存单元,发生了 cache miss,那么系统会取 (t, s, 00000) 到 (t, s, FFFFF)的内存单元,将其放入相应的 cacheline 中。下面看看 cache 的映射机制:当 E=1 时, 每组只有一个 cacheline。那么相隔 2(s+b)
11、个单元的 2 个内存单元,会被映射到同一个 cacheline 中。 (好好想想为什么?)当 1 C, E 是 cacheline 的行数,即为 C/B。 看看会发生什么:在访问a00aE-10时,每次都会造成 Cache miss,然后访问 aE0aM-10时,又会把第 0M-E-1 行 cacheline 给覆盖掉,因此当访问 a00aM-10时总是会造成 Cache miss。在访问 a01aM-11时,分为 2 个过程,前 0M-E-1 行由于被覆盖了,故而 Cache 又会不命中,而在第 M-EE-1 行中, 也就是访问aM-E1aE-11时,由于没有被覆盖,这些行将会命中。因此总共
12、有 M+2*(M-E)*(N-1)次 cache miss。不命中率可算得:2-2E/M-1/N+2E/(M*N)。可见,当M=2E 时,不命中率=1 。当赋予 M,N 较大值时,测试结果将会是列优先访问程序的运行时间远远大于行优先访问程序运行时间。多核环境下,只要不同的线程或者进程访问同一 cacheline 的不同内容,就会发生“伪共享问题”。这样的问题较为隐蔽,难以发现。6. GCC Attribute7. 头文件解读 代码就不贴了a. L1_CACHE_ALIGN(x)这个宏该宏返回所指向的内存区域的起始的 cacheline 的边界地址。b. _cacheline_aligned 宏
13、该宏是 gcc 属性,对定义的数据结构做空间对齐,使之起始位置对齐 cachelinec. _cacheline_aligned 宏把数据分配到 data 段的 cacheline_aligned 子段里面,并且数据的起始位置对齐cacheline。.datacacheline_aligned 的定义在 arch/XXX/kernel/vmlinuz.lds.S下面,有兴趣的读者可以自行查阅代码b 和 c 宏看起来很类似,只差了 2 个下划线而已,区别在于前者用于局部数据的声明,后者声明于全局数据,可以放在.data 段有一些在多处理器体系结构下的关键数据结构,就是用 cacheline_al
14、igned 来声明的,譬如:这样能够避免每个 CPU 在对属于自己的那个 map 读写时造成 false sharing 问题posted 2011-11-20 04:32 李洛克 阅读(50) 评论(0) 编辑深入理解 Linux 网络技术内幕 读书笔记(2) 关键数据结构一. sk_buffLinux 网络协议栈中主要的缓存类型。定义在 include/linux/skbuff.h 中,它是一个双向链表的数据结构。它的字段和函数主要分为四大类:1. 布局字段:与整个 sk_buff 链表结构相关的字段2. 通用信息字段:3. 特性相关字段:4. 管理功能函数:二. sk_buff 布局字段
15、为了很快地找到 sk_buff 链表中的表头,每个链表中都有一个这样的字段:struct sk_buff_head *list;这个域是指向 sk_buff 链表头的指针。struct sk_buff_head /* These two members must be first. */struct sk_buff *next;struct sk_buff *prev;_u32 qlen;spinlock_t lock;其中 qlen 为链表长度, lock 是与链表管理相关的字段整个链表的结构如下:其他字段如下:struct sock *sk; L4 需要的字段,存储与之相关的 socket
16、 信息,在其他层该字段为NULLunsigned int len; buffer 中数据的长度,包括 head-next 指向的主长度和分片长度unsigned int data_len; 不同于上,该字段只指分片数据的长度unsigned int mac_len; mac 头部长度atomic_t users; 引用计数,防止在有其他程序引用该 buffer 时被释放unsigned int truesize; 表示 buffer 的所有长度,包括 sk_buff 结构自身的长度unsigned char *head; unsigned char *end; unsigned char *d
17、ata; unsigned char *tail; 这四个指针功能如下void (*destructor)(.) 当 buff 被释放时,函数指针可以进行某些工作三. 通用信息字段struct timeval stamp; 报文接收的时间struct net_device *dev; 根据接收到了包或者是要发送包,该指针指向的设备所扮演的角色不同struct net_device *input_dev; 包是从哪个设备接收的struct net_device *real_dev; 对于虚拟设备这个字段才是有意义的union . h; union . nh; union . mac h 是 L4
18、 的头部,nh 是 L3 的头部,mac 是 L2 的头部。在包向上或者向下传递过程中,这些域会被依次有序地设置struct dst_entry dst; 路由相关的字段char cb40; 存放每层私有的控制数据unsigned int csum; unsigned char ip_summed 检验和相关状态的字段unsigned char cloned; 是否是从别的 buffer 复制过来的unsigned char pkt_type; 报文类型,详见 include/linux/if_packet.h四. 特性相关字段如果希望为 Linux 内核配置额外支持防火墙或者 QoS 的功能
19、,那么就需要在配置文件中配置相关信息,以便在编译阶段把特性相关的字段引入到 sk_buff 中五. 管理函数posted 2011-11-20 04:13 李洛克 阅读(99) 评论(1) 编辑Assembly Language Step by Step笔记 第四章 寻址(1)1. 汇编语言学习的难点在尝试学习汇编语言之前,你至少应当理解整个计算机的体系结构。阅读这些关于现代计算机系统资料是必要的:1. 英文版 http:/duartes.org/gustavo/blog/category/internals 2. 中文翻译 http:/ 的真正工作,也是汇编语言编程的最大挑战,在于对内存中指
20、令和数据的寻址寻址是一件困难的事情,在 x86 体系结构下尤甚x86 体系下内存模型分为三种:1. 最古董的实模式扁平模型 (real mode flat model)2. 已经退休了的实模式分段模型(real mode segmented model)3. 在 Win XP/Vist/7 还有 Linux 上盛行的保护模式扁平模型 (protected mode flat model)如果你对以上三种模型还搞不清楚的话,先回去仔细看看下面这篇文章吧:http:/ 历史的回眸要理解为虾米会有这么多模型出现,那话说起来就长啦!第 2 节从历史的深处说起,主要讲述 x86 体系结构下各种模式出现的
21、缘由2.1 Intel 8080 处理器让我们回退到 70 年代吧!1974 年 Intel 推出了 8080 处理器,8080 处理器是一款 8位处理器,也就是说,一次只能处理 8 个比特的信息。但是呢,它的地址总线却有 16位!那个年代,16 位的地址总线有点吓人哦!因为在那个 RAM 贵的吓死人的年代,大多数的机器,内存也就是 4K 到 8K 之间,16 位的总线寻址能力达到了 64K!所以说,Intel 设计师的设计总是会超前的,就像现在的 Intel Core2 处理器,地址总线有 33 位,可以寻址 64GB 的内存,但是现在大部分计算机内存不也就 24G 吗?话说回来,那会使用
22、8080 的操作系统主要是 CP/M-80,CP/M-80 有点与众不同,操作系统内核是加载到内存顶部的(高址处),这样做很大程度是为了瞬时程序(Transient Program)能有一个一致的内存起点( 类似于用户程序,在需要时才从磁盘中加载进来运行),好比我们 Linux 的用户程序,其线性地址的起点都是 0x00000000,不过这里的起点是 0x100H 就是第 256 字节处。而从 0 开始的头 256 个字节用于存储其他信息比如程序的磁盘 I/O 缓冲区。示例图如下:2.2 Intel 8086CP/M-80 的内存模型已经够简单了,瞬时程序的编写者只要将程序开头声明为从0100
23、H 地址开始就行。然后 Intel 严格地按照摩尔定律的指示,制造出了 16 位的处理器 8086,这个 16 位指的是数据引脚的位数,一次能处理 16 比特位的信息。但是,这个处理器有 20 条地址引脚,能寻址达 1MB 空间。当 Intel 制造出第一款 16 位的处理器 8086 时,为了更以前保持兼容,能让 8080 上的程序很容易地移植在 8086 上,就不得不做些 trick 了,你想,以前的内存地址是 16 位,现在是 20 位,差了整整 4 位耶!一个公司的产品,总是需要向后兼容的吧,总不能说你今天买了台 Intel Core 2 的机器,昨天在你奔 3 上跑的魔兽世界现在却怎
24、么也起不来了吧?这怎么行。让我们来看看这些 trick 是怎么做的。首先先来了解一下“段”这个概念。嗯,1MB 的寻址空间,刚好是 16 个 64K 的内存区块 ,随便把 CP/M-80 的程序放在这 16 个块中的任意一个不就可以了吗?而且,内存区块起始地址也不用非得刚好和这 16 个 64K 内存区块对齐,长度也不用非得是 64K,只要满足了 CP/M-80 程序要求的长度就行了嘛。这些区块,就可以称之为段!既然提到了段,那么让我们来看看段的本质是什么:段就是在 CPU 能寻址和使用的内存区中的一个块。在实模式分段模型环境下,一个段以 16 字节为对齐边界,段长至少一个字节,至多64K。一
25、个 1M 的内存区,最多可以分为 64K 个段,每段长 16 字节,最少也有 16 个段,每段长 64K。为什么是这样,下面会有解释。了解完“段” 的概念,再让我们看一下 8086 中 CS 寄存器的作用吧。 CS 寄存器是 16 位的,里面存的值就是指向 1M 内存地址空间中某一个段的起始地址值。假设当前 CS 的值是 0x1000,当前程序用逻辑地址 0x1234 去访问一条指令时,会将CS 向左移 4 位,同时加上 0x1234,这时真实物理地址就是 0x11234。物理地址 = 段值*16 + 逻辑地址(偏移量)这样,只要精心设计好 CS 的值,原来的 CP/M-80 程序就可以畅通无
26、阻地运行在 8086机器上啦。而且,每一个段都必定是 16 字节对齐的(段值 * 16),长度最大 64K(16 位逻辑地址,最大 64K 寻址空间)。这种寻址模式,就成为实模式分段模型啦。计算之后的地址也是物理地址,就是说实模式了,又用到了分段,那就是分段模型了。虽然这样做从短期来看无疑是很好的,可是长远来看真是个糟糕透顶的设计!因为,在不改变 CS 内容的情况下,CPU 只能寻址 64K 大小的范围,当程序大小超过 64K,需要在不同的段之间跳来跳去的时候,这个设计真让开发者抓狂蛋疼啊!在这里不得不提的一个地方,就是人们很少能注意到,段是可以重叠的!比如起始地址 0x00000,长度为 6
27、4K 的段,和起始地址 0x00010,长度为 32K 的段,后者被前者包含,但它们 2 个是可以并存的。下面来看看 MyByte 指向的那个字节,由于段可以重叠,那么这个字节的地址表示方式就有多种多样了。如果用 “段值:偏移”来表示的话,就可能有以下 3 种表达方式了2.3 段寄存器及其作用我们知道,从 8086 开始到 286,386 及其后续处理器,都有一些段寄存器陆续登上历史舞台,它们分别是:CS code segment 代码段寄存器。这是用来对机器指令寻址的,也就是说该段寄存器指向的内存块存储的是机器指令。DS data segment 数据段寄存器。这是用来对变量或者其他数据寻址
28、用的。SS stack segment 栈区段寄存器。ES extra segment 额外段寄存器,一般用来存储额外的段信息的寄存器FS,GS 和 ES 作用一样,不过它们是从 386 才开始出现的2.4 通用寄存器最开始在 8086 上,通用寄存器只有 AX, BX, CX, DX, SP, BP, SI, DI 8 个,AX, BX, CX, DX 这四个寄存器。在 1986 年 Intel 为 x86 体系结构扩展到了 32 位,这些寄存器也就成了 EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDI, 不过,你还是可以通过 AX, BX 这些符号去访问 EAX,EBX 寄存器的低 16 位,就像以前那样。写到这里发现日志已经过长了,不符合大家的阅读习惯,下篇文章再介绍其他几个寄存器