精华内容
下载资源
问答
  • 状态寄存器--CPSR寄存器CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理器,个数结构都可能不同)。这种寄存器在ARM中,被称为状态寄存器就是CPSR(current program status register)寄存器。CPSR其他寄存器...

    状态寄存器--CPSR寄存器

    CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理器,个数和结构都可能不同)。这种寄存器在ARM中,被称为状态寄存器就是CPSR(current program status register)寄存器。
    CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义。而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。
    也叫标记寄存器
    注:CPSR寄存器是32位的

    • CPSR的低8位(包括I、F、T和M[4:0])称为控制位,程序无法修改,除非CPU运行于特权模式下,程序才能修改控制位。
    • N、Z、C、V均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行,意义重大!

    N(Negative)标志

    CPSR的第31位是 N(符号标志位),它记录相关指令执行后,其结果是否为负数。
    如果结果为负数,那么 N = 1;如果结果是非负数,那么 N = 0

    注意:在ARM64的指令集中,有的指令的执行时影响状态寄存器的,比如adds / subs / ors等,它们大都是运算指令(进行逻辑或算数运算)


    Z(Zero)标志

    CPSR 的第30位是 Z(0标志位),它记录相关指令执行后,其结果是否为0。
    如果结果为0,那么 Z = 1;如果结果不为0,那么Z = 0

    对于Z的值,我们可以这样来看,Z 标记相关指令的计算结果是否为0。

    • 如果结果为0,则 N 要记录下"结果为0"这样的肯定信息。
      在计算机中1表示逻辑真,表示肯定。所以当结果为0的时候 Z = 1,表示"结果为0"

    • 如果结果不为0,则Z要记录下"结果不为0"这样的否定信息。
      在计算机中0表示逻辑假,表示否定,所以当结果不为0的时候 Z = 0,表示"结果不为0"。

    C(Carry)标志

    CPSR 的第29位是 C(进位标志位),一般情况下,进行无符号数的运算。
    加法运算:当运算结果产生了进位时(无符号数溢出),那么 C = 1,否则C = 0
    减法运算(包括CMP):当运算时产生了借位时(无符号数溢出),C=0,否则C=1。

    对于位数为 N 的无符号数来说,其对应的二进制信息的最高位,即第N - 1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。如下图所示:


    • 进位
      我们知道,当两个数据相加的时候,有可能产生从最高有效位向更高位的进位。比如两个32位数据:0xaaaaaaaa + 0xaaaaaaaa,将产生进位。由于这个进位值在32位中无法保存,我们就只是简单的说这个进位值丢失了。其实CPU在运算的时候,并不丢弃这个进位制,而是记录在一个特殊的寄存器的某一位上。ARM下就用C位来记录这个进位值。比如,下面的指令
    mov w0, #0xaaaaaaaa  ;0xa 的二进制是 1010 ==> 1010 1010 1010 ... 1010
    adds w0, w0, w0  ;执行后相当于 1010 << 1 进位1(无符号溢出)所以 C 标记 为 1
    adds w0, w0, w0  ;执行后相当于 0101 << 1 进位0(无符号没溢出)所以 C 标记 为 0
    adds w0, w0, w0  ;重复上面操作
    adds w0, w0, w0
    
    • 借位
      当两个数据做减法的时候,有可能向更高位借位。再比如,两个32位数据:0x00000000 - 0x000000ff,将产生借位,借位后,相当于计算0x100000000 - 0x000000ff。得到0xffffff01 这个值。由于借了一位,所以C位 用来标记借位。C = 0.比如下面指令:
    mov w0, #0x0
    subs w0, w0, #0xff 
    subs w0, w0, #0xff
    subs w0, w0, #0xff
    

    V(Overflow)溢出标志

    CPSR的第28位是V(溢出标志位),一般情况下,在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。

    • 正数 + 正数 = 值为负数 溢出 V = 1
    • 负数 + 负数 = 值为正数 溢出 V = 1
    • 正数 + 负数 不可能溢出 V = 0

    内存分布

    内存分布特性
    代码区可读可写
    存放参数和局部变量,可读可写
    动态申请,可读可写
    全局区可读可写
    常量区只读

    adrp 指令

    是计算指定的数据地址到当前PC值的相对偏移。

    adrp x0, 1
    /*
      假设当前的 PC 寄存器为 0x1002e6874
      1.先将1的值左移12位二进制位则为 1 0000 0000 0000 ==> 0x1000
      2.将 PC 寄存器的低12位清零,也就是 0x1002e6874 ==> 0x1002e6000
      3.最后将1和2的结果相加给 x0 寄存器,
        x0 = 0x000001000 + 0x1002e6000
        x0 = 0x1002e7000
    */

    补充:

    • 计算机存储数据它会分为有符号数和无符号数。
    • 有符号数是针对二进制来讲的。用最高位作为符号位,“0”代表“+”,“1”代表“-” ;其余数位用作数值位,代表数值。
    • 有符号数的表示:计算机中的数据用二进制表示,数的符号也只能用0/1表示。一般用最高有效位(MSB)来表示数的符号,正数用0表示,负数用1表示。

    32位 int 中 0x7FFFFFFF为最大值2^31 - 1 、0x80000000为最小值-2^31

    • 我们来看看0x80000000的输出

      • 0x80000000的二进制位原码为 1000 0000 0000 0000 0000 0000 0000 0000
      • 若最高位是符号位为1,为负数,则为 -0。
      • 可是在代码执行 int i = 0x80000000 ,却输出为 i = -(2^31)。
      • 原因是在十六进制中负数的二进制原码的最高位是符号位,后面的31位为序号位,不是值位。
      • 1后面的000 0000 0000 0000 0000 0000 0000 0000,表示序号1,表示负数中,从小到大的第一位。
      • 由于 int 的最小值为 -(2^31),排在负数从小到大的序号0,所以 int i = 0x80000000,输出为 i = -(2^31)。
    • 我们来看看0xFFFFFFFF的输出

      • 0xFFFFFFFF的二进制原码为 1111 1111 1111 1111 1111 1111 1111 1111
      • 最高位是符号位为1 ,为负数,序号位为第(2^31) - 1位 (111 1111 1111 1111 1111 1111 1111 1111=(2^31-1),
      • 所以 0xFFFFFFFF 为负数从小到大 第2^31-1位 ,即 -(2^31) + (2^31-1) = -1

    总结:数值 = 该符号下最小值 + 序号位表示数



    展开全文
  • 动态分配得到的内存附带有分配信息, 所以你能够 realloc free调它们. 全局,静态常量是分配在数据区中的。数据区包括bss初始化区。 堆向高内存地址生长 栈向低内存地址生长 堆栈相向而生,堆栈之间有个...

    堆和栈:

    栈主要用来存放局部变量, 传递参数, 存放函数的返回地址.esp 始终指向栈顶, 栈中的数据越多, esp的值越小.
    堆用于存放动态分配的对象, 当你使用 malloc , new 等进行分配时,所得到的空间就在堆中. 动态分配得到的内存附带有分配信息, 所以你能够 realloc 和 free调它们.
    全局,静态和常量是分配在数据区中的。数据区包括bss和初始化区。
    堆向高内存地址生长
    栈向低内存地址生长
    堆和栈相向而生,堆和栈之间有个临界点,称为stkbrk

    内存分配:

    Linux 的虚拟内存管理有几个关键概念: 

    1、每个进程都有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址; 
    2、虚拟地址可通过每个进程上的页表(在每个进程的内核虚拟地址空间)与物理地址进行映射,获得真正物理地址; 
    3、如果虚拟地址对应物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果此时物理内存已耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。 
       
    一、Linux 虚拟地址空间如何分布?
    Linux 使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别为: 
    1、只读段:该部分空间只能读,不可写;(包括:代码段、rodata 段(C常量字符串和#define定义的常量) )
    2、数据段:保存全局变量、静态变量的空间; 
    3、堆 :就是平时所说的动态内存, malloc/new 大部分都来源于此。其中堆顶的位置可通过函数 brk 和 sbrk 进行动态调整。 
    4、文件映射区域 :如动态库、共享内存等映射物理空间的内存,一般是 mmap 函数所分配的虚拟地址空间。 
    5、栈:用于维护函数调用的上下文空间,一般为 8M ,可通过 ulimit –s 查看。 
    6、内核虚拟空间:用户代码不可见的内存区域,由内核管理(页表就存放在内核虚拟空间)。

    下图是 32 位系统典型的虚拟地址空间分布(来自《深入理解计算机系统》)。

    32 位系统有4G 的地址空间::

          其中 0x08048000~0xbfffffff 是用户空间,0xc0000000~0xffffffff 是内核空间,包括内核代码和数据、与进程相关的数据结构(如页表、内核栈)等。另外,%esp 执行栈顶,往低地址方向变化;brk/sbrk 函数控制堆顶_edata往高地址方向变化。

    二、malloc和free是如何分配和释放内存?

    内存分配的原理

    从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。

    1、brk是将数据段(.data)的最高地址指针_edata往高地址推;

    2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。

         这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。


    在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。
    下面以一个例子来说明内存分配的原理:

    情况一、malloc小于128k的内存,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系),如下图:

     

    1、进程启动的时候,其(虚拟)内存空间的初始布局如图1所示。

          其中,mmap内存映射文件是在堆和栈的中间(例如libc-2.2.93.so,其它数据文件等),为了简单起见,省略了内存映射文件。

          _edata指针(glibc里面定义)指向数据段的最高地址。 
    2、进程调用A=malloc(30K)以后,内存空间如图2:

          malloc函数会调用brk系统调用,将_edata指针往高地址推30K,就完成虚拟内存分配。

          你可能会问:只要把_edata+30K就完成内存分配了?

          事实是这样的,_edata+30K只是完成虚拟地址的分配,A这块内存现在还是没有物理页与之对应的,等到进程第一次读写A这块内存的时候,发生缺页中断,这个时候,内核才分配A这块内存对应的物理页。也就是说,如果用malloc分配了A这块内容,然后从来不访问它,那么,A对应的物理页是不会被分配的。 
    3、进程调用B=malloc(40K)以后,内存空间如图3。

    情况二、malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0),如下图:

     

    4、进程调用C=malloc(200K)以后,内存空间如图4:

          默认情况下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存。

          这样子做主要是因为::

          brk分配的内存需要等到高地址内存释放以后才能释放(例如,在B释放之前,A是不可能释放的,这就是内存碎片产生的原因,什么时候紧缩看下面),而mmap分配的内存可以单独释放。

          当然,还有其它的好处,也有坏处,再具体下去,有兴趣的同学可以去看glibc里面malloc的代码了。 
    5、进程调用D=malloc(100K)以后,内存空间如图5;
    6、进程调用free(C)以后,C对应的虚拟内存和物理内存一起释放。

     

    7、进程调用free(B)以后,如图7所示:

            B对应的虚拟内存和物理内存都没有释放,因为只有一个_edata指针,如果往回推,那么D这块内存怎么办呢?

    当然,B这块内存,是可以重用的,如果这个时候再来一个40K的请求,那么malloc很可能就把B这块内存返回回去了。 
    8、进程调用free(D)以后,如图8所示:

            B和D连接起来,变成一块140K的空闲内存。

    9、默认情况下:

           当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。在上一个步骤free的时候,发现最高地址空闲内存超过128K,于是内存紧缩,变成图9所示。

    三、既然堆内内存brk和sbrk不能直接释放,为什么不全部使用 mmap 来分配,munmap直接释放呢? 

            既然堆内碎片不能直接释放,导致疑似“内存泄露”问题,为什么 malloc 不全部使用 mmap 来实现呢(mmap分配的内存可以会通过 munmap 进行 free ,实现真正释放)?而是仅仅对于大于 128k 的大块内存才使用 mmap ? 

            其实,进程向 OS 申请和释放地址空间的接口 sbrk/mmap/munmap 都是系统调用,频繁调用系统调用都比较消耗系统资源的。并且, mmap 申请的内存被 munmap 后,重新申请会产生更多的缺页中断。例如使用 mmap 分配 1M 空间,第一次调用产生了大量缺页中断 (1M/4K 次 ) ,当munmap 后再次分配 1M 空间,会再次产生大量缺页中断。缺页中断是内核行为,会导致内核态CPU消耗较大。另外,如果使用 mmap 分配小内存,会导致地址空间的分片更多,内核的管理负担更大。
            同时堆是一个连续空间,并且堆内碎片由于没有归还 OS ,如果可重用碎片,再次访问该内存很可能不需产生任何系统调用和缺页中断,这将大大降低 CPU 的消耗。 因此, glibc 的 malloc 实现中,充分考虑了 sbrk 和 mmap 行为上的差异及优缺点,默认分配大块内存 (128k) 才使用 mmap 获得地址空间,也可通过 mallopt(M_MMAP_THRESHOLD, <SIZE>) 来修改这个临界值。

    因为要了解多线程,自然少不了一些硬件知识的科普,我没有系统学习过硬件知识,仅仅是从书上以及网络上看来的,如果有错误请指出来。

    寄存器:

    CPU,全名Central Processing Unit(中央处理器)。这是一块超大规模的集成电路,包含上亿的晶体管,是一台计算机的运算核心(Core)和控制核心(ControlUnit)。它的功能主要是解释计算机指令以及处理计算机软件中的数据。
      它的主要构成是:运算器、控制器、寄存器
      运算器:可以执行定点或浮点算术运算操作、移位操作以及逻辑操作,也可执行地址运算和转换。
      控制器:主要是负责对指令译码,并且发出为完成每条指令所要执行的各个操作的控制信号。其结构有两种:一种是以微存储为核心的微程序控制方式;一种是以逻辑硬布线结构为主的控制方式。
      寄存器:寄存器部件,包括寄存器、专用寄存器和控制寄存器。通用寄存器又可分定点数和浮点数两类,它们用来保存指令执行过程中临时存放的寄存器操作数和中间(或最终)的操作结果。 通用寄存器是中央处理器的重要部件之一。

      工作过程:
      第一阶段,提取,从存储器或高速缓冲存储器中检索指令(为数值或一系列数值)。由程序计数器(Program Counter)指定存储器的位置。(程序计数器保存供识别程序位置的数值。换言之,程序计数器记录了CPU在程序里的踪迹。)
      第二阶段:解码(控制器)
      第三阶段:执行,算术逻辑单元(ALU,Arithmetic Logic Unit)将会连接到一组输入和一组输出。输入提供了要相加的数值,而输出将含有总和的结果。ALU内含电路系统,易于输出端完成简单的普通运算和逻辑运算(比如加法和位元运算)。如果加法运算产生一个对该CPU处理而言过大的结果,在标志暂存器里可能会设置运算溢出(Arithmetic Overflow)标志。
      第四阶段:回写,缓冲Cache或者更大更廉价的低俗存储器(内存、硬盘等等)

      寄存器:是集成电路中非常重要的一种存储单元,通常由触发器组成。在集成电路设计中,寄存器可分为电路内部使用的寄存器和充当内外部接口的寄存器这两类。内部寄存器不能被外部电路或软件访问,只是为内部电路的实现存储功能或满足电路的时序要求。而接口寄存器可以同时被内部电路和外部电路或软件访问,CPU中的寄存器就是其中一种,作为软硬件的接口,为广泛的通用编程用户所熟知。

      常见类型
      1)数据寄存器- 用来储存整数数字(参考以下的浮点寄存器)。在某些简单/旧的CPU,特别的数据寄存
      2)寄存器
      3)寄存器
      4)器是累加器,作为数学计算之用。
      5)地址寄存器- 持有存储器地址,用来访问存储器。在某些简单/旧的CPU里,特别的地址寄存器是索引寄存器(可能出现一个或多个)。
      6)通用目的寄存器(GPRs) - 可以保存数据或地址两者,也就是说它们是结合数据/地址 寄存器的功用。
      7)浮点寄存器(FPRs) - 用来储存浮点数字。
      8)常数寄存器- 用来持有只读的数值(例如0、1、圆周率等等)。
      9)向量寄存器- 用来储存由向量处理器运行SIMD(Single Instruction, Multiple Data)指令所得到的数据。
      10)特殊目的寄存器- 储存CPU内部的数据,像是程序计数器(或称为指令指针),堆栈寄存器,以及状态寄存器(或称微处理器状态字组)。
      11)指令寄存器(instruction register)- 储存现在正在被运行的指令。
      12)索引寄存器(index register)- 是在程序运行时用来更改运算对象地址之用。

    特点
    寄存器又分为内部寄存器与外部寄存器,所谓内部寄存器,其实也是一些小的存储单元,也能存储数据。但同存储器相比,寄存器又有自己独有的特点:
    ①寄存器位于CPU内部,数量很少,仅十四个
    ②寄存器所能存储的数据不一定是8bit,有一些寄存器可以存储16bit数据,对于386/486处理器中的一些寄存器则能存储32bit数据
    ③每个内部寄存器都有一个名字,而没有类似存储器的地址编号。

      作用
      1.可将寄存器内的数据执行算术及逻辑运算
      2.存于寄存器内的地址可用来指向内存的某个位置,即寻址
      3.可以用来读写数据到电脑的周边设备。

      简单的说:指令解析 - 数据/操作(寄存器)- 回写(cache/memory/disk)

      计算机的存储层次(memory hierarchy)之中,寄存器最快,内存其次,最慢的是硬盘。同样都是晶体管存储设备,为什么寄存器比内存快呢?Mike Ash写了一篇很好的解释,非常通俗地回答了这个问题,有助于加深对硬件的理解。

      原因一:距离不同
      距离不是主要因素,但是最好懂,所以放在最前面说。内存离CPU比较远,所以要耗费更长的时间读取。
    以3GHz的CPU为例,电流每秒钟可以振荡30亿次,每次耗时大约为0.33纳秒。光在1纳秒的时间内,可以前进30厘米。也就是说,在CPU的一个时钟周期内,光可以前进10厘米。因此,如果内存距离CPU超过5厘米,就不可能在一个时钟周期内完成数据的读取,这还没有考虑硬件的限制和电流实际上达不到光速。相比之下,寄存器在CPU内部,当然读起来会快一点。距离对于桌面电脑影响很大,对于手机影响就要小得多。手机CPU的时钟频率比较慢(iPhone 5s为1.3GHz),而且手机的内存紧挨着CPU。

      原因二:硬件设计不同(1 Byte表示一个字节, 1B=8bit)
      最新的iPhone 5s,CPU是A7,寄存器有6000多位(31个64位寄存器,加上32个128位寄存器)。而iPhone 6s的内存是1GB,约为80亿位(bit)。这意味着,高性能、高成本、高耗电的设计可以用在寄存器上,反正只有6000多位,而不能用在内存上。因为每个位的成本和能耗只要增加一点点,就会被放大80亿倍。事实上确实如此,内存的设计相对简单,每个位就是一个电容和一个晶体管,而寄存器的设计则完全不同,多出好几个电子元件。并且通电以后,寄存器的晶体管一直有电,而内存的晶体管只有用到的才有电,没用到的就没电,这样有利于省电。这些设计上的因素,决定了寄存器比内存读取速度更快。

      原因三:工作方式不同
      寄存器的工作方式很简单,只有两步:(1)找到相关的位,(2)读取这些位。
      内存的工作方式就要复杂得多:
      (1)找到数据的指针。(指针可能存放在寄存器内,所以这一步就已经包括寄存器的全部工作了。)
      (2)将指针送往内存管理单元(MMU),由MMU将虚拟的内存地址翻译成实际的物理地址。
      (3)将物理地址送往内存控制器(memory controller),由内存控制器找出该地址在哪一根内存插槽(bank)上。
      (4)确定数据在哪一个内存块(chunk)上,从该块读取数据。
      (5)数据先送回内存控制器,再送回CPU,然后开始使用。
      内存的工作流程比寄存器多出许多步。每一步都会产生延迟,累积起来就使得内存比寄存器慢得多。为了缓解寄存器与内存之间的巨大速度差异,硬件设计师做出了许多努力,包括在CPU内部设置缓存Cache、优化CPU工作方式,尽量一次性从内存读取指令所要用到的全部数据等等。

      上面说到”缓存“,大部分程序员都知道什么是软件架构中缓存的概念。这里所说的缓存是指硬件“高速缓冲存储器”,是存在于主存与CPU之间的一级存储器(常见于计算机cpu性能指标中:一级缓存、二级缓存,高配置的服务器会有三级缓存), 由静态存储芯片(SRAM)组成,容量比较小但速度比主存高得多, 接近于CPU的速度。在计算机存储系统的层次结构中,是介于中央处理器和主存储器之间的高速小容量存储器。它和主存储器一起构成一级的存储器。高速缓冲存储器和主存储器之间信息的调度和传送是由硬件自动进行的。高速缓冲存储器最重要的技术指标是它的命中率(一级缓存(a=n*80%) - 二级缓存(b=a*80%) - 三级缓存(c=b*80%))。所谓的命中就是在缓存上读取到指定的数据。
        既然是缓存,那么大小肯定是有局限,也就是说不是所有cpu需要的数据都能在缓存中命中,因为它有着自己的更新策略。如下
       1. 根据程序局部性规律可知:程序在运行中,总是频繁地使用那些最近被使用过的指令和数据。这就提供了替换策略的理论依据。综合命中率、实现的难易及速度的快慢各种因素,替换策略可有随机法、先进先出法、最近最少使用法等。
      (1).随机法(RAND法)
    随机法是随机地确定替换的存储块。设置一个随机数产生器,依据所产生的随机数,确定替换块。这种方法简单、易于实现,但命中率比较低。
      (2).先进先出法(FIFO法)
    先进先出法是选择那个最先调入的那个块进行替换。当最先调入并被多次命中的块,很可能被优先替换,因而不符合局部性规律。这种方法的命中率比随机法好些,但还不满足要求。先进先出方法易于实现,
      (3).最近最少使用法(LRU法)
    LRU法是依据各块使用的情况, 总是选择那个最近最少使用的块被替换。这种方法比较好地反映了程序局部性规律。 实现LRU策略的方法有多种。
      2 在多体并行存储系统中,由于 I/O 设备向主存请求的级别高于 CPU 访存,这就出现了 CPU 等待 I/O 设备访存的现象,致使 CPU 空等一段时间,甚至可能等待几个主存周期,从而降低了 CPU 的工作效率。为了避免 CPU 与 I/O 设备争抢访存,可在 CPU 与主存之间加一级缓存,这样,主存可将 CPU 要取的信息提前送至缓存,一旦主存在与 I/O 设备交换时, CPU 可直接从缓存中读取所需信息,不必空等而影响效率。
      3 目前提出的算法可以分为以下三类(第一类是重点要掌握的):
      (1)传统替换算法及其直接演化,其代表算法有 :①LRU( Least Recently Used)算法:将最近最少使用的内容替换出Cache ;②LFU( Lease Frequently Used)算法:将访问次数最少的内容替换出Cache;③如果Cache中所有内容都是同一天被缓存的,则将最大的文档替换出Cache,否则按LRU算法进行替换 。④FIFO( First In First Out):遵循先入先出原则,若当前Cache被填满,则替换最早进入Cache的那个。
      (2)基于缓存内容关键特征的替换算法,其代表算法有:①Size替换算法:将最大的内容替换出Cache②LRU— MIN替换算法:该算法力图使被替换的文档个数最少。设待缓存文档的大小为S,对Cache中缓存的大小至少是S的文档,根据LRU算法进行替换;如果没有大小至少为S的对象,则从大小至少为S/2的文档中按照LRU算法进行替换;③LRU—Threshold替换算法:和LRU算法一致,只是大小超过一定阈值的文档不能被缓存;④Lowest Lacency First替换算法:将访问延迟最小的文档替换出Cache。
      (3)基于代价的替换算法,该类算法使用一个代价函数对Cache中的对象进行评估,最后根据代价值的大小决定替换对象。其代表算法有:①Hybrid算法:算法对Cache中的每一个对象赋予一个效用函数,将效用最小的对象替换出Cache;②Lowest Relative Value算法:将效用值最低的对象替换出Cache;③Least Normalized Cost Replacement(LCNR)算法:该算法使用一个关于文档访问频次、传输时间和大小的推理函数来确定替换文档;④Bolot等人 提出了一种基于文档传输时间代价、大小、和上次访问时间的权重推理函数来确定文档替换;⑤Size—Adjust LRU(SLRU)算法:对缓存的对象按代价与大小的比率进行排序,并选取比率最小的对象进行替换。

    展开全文
  • 缓存寄存器都集成在 cpu中 内存集成在内存条中 cpu 只会读取寄存器中数据,寄存器从缓存和内存中取数据 2.我们用到的redis存储数据的缓存技术,实际是我们的数据库数据存储到内存中过程。数据库缓存或者说我们的...
    自己的理解:
    
    1.缓存和内存是有区别的。缓存寄存器都集成在 cpu中  内存集成在内存条中  cpu 只会读取寄存器中数据,寄存器从缓存和内存中取数据
    2.我们用到的redis存储数据的缓存技术,实际是我们的数据库数据存储到内存中过程。数据库缓存或者说我们的java缓存并不是cpu缓存,所以我们一般说的缓存
    都是指将硬盘数据变为我们的内存数据的一种。、
    3.我们的栈堆实际是java的一种结构。使用所有的java形式。都是在内存中的一种表现和分布方式。



    展开全文
  • 同类学习笔记总结: (一)、8086汇编学习之基础...我们主要分析一下在单个的程序与多个的程序中,每个段寄存器的值是如何安排的,的位置关系,内存大小等问题。一、只有一个的程序:程序实例: 利用栈将程

    同类学习笔记总结:
    (一)、8086汇编学习之基础知识、通用寄存器、CS/IP寄存器与Debug的使用
    (二)、8086汇编学习之DS寄存器、SS/SP寄存器
    (三)、8086汇编学习之[BX],CX寄存器与loop指令,ES寄存器等
    我们主要分析一下在单个段的程序与多个段的程序中,每个段寄存器的值是如何安排的,段的位置关系,内存大小等问题。

    一、只有一个段的程序:

    程序实例:
    利用栈将程序中数据段中前8个word的数据按字型进行逆转。

    assume cs:codeseg
    
    codeseg segment
        dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H  ;8word=16Byte
        dw 0,0,0,0,0,0,0,0,0,0  ;栈设置的比实际要用的大10word=20Byte
    
    start:
            mov ax,cs
            mov ss,ax
            mov sp,36   ;给栈设置初始值
    
            mov bx,0
            mov cx,8    ;给CX循环计数寄存器设置初值
    
            loop_push:
                push cs:[bx]
                add bx,2
                loop loop_push
    
            mov bx,0
            mov cx,8
    
            loop_pop:
                pop cs:[bx]
                add bx,2
                loop loop_pop
    
            mov ax,4C00H
            int 21H
    
    codeseg ends
    
    end start

    对于上面这段代码的分析:
    由于这段程序只有一个段,该段为代码段,但是代码段前面有一部分数据,其中第一行为数据(dw命令开辟空间以word为单位,一个数据2Byte),第二行为为栈开辟的空间,之后的便是指令。由于指令寄存器CS:IP存放第一条指令的地址,CPU根据CS:IP的值开始解析并执行指令,但是默认情况下只有一个段的程序,其第一行指令/数据的地址就是CS:IP的值,在编译连接生成可执行程序时,就将程序的入口写入可执行文件描述信息中。但是这里第一行是数据而不是指令,将数据解析成指令其程序是不能正确运行的。而这时,start标识就标识了一个地址,end伪指令与start标识结合后,编译器根据该伪指令的信息就将satrt标识的地址写入可执行文件的描述信息中,根据可执行文件描述信息获取正确的指令其实地址存入CS:IP寄存器,那么CPU读取CS:IP时就能从正确的入口地址读取解析并执行指令了。

    这里写图片描述
    上图为例子程序的debug调试结果与对应的寄存器内存图,CS:IP初始值为start的标识地址(IP不为0,因为有数据段、栈段存在于代码段),这就将我们CS:IP=CS:0为程序入口地址的传统观念就打破,在指令前存在数据总是与指令存在于一个段显得格格不入。
    我们可以看到当数据段、代码段、栈段存在于一个段时,会显得比较混乱,并且如果数据+栈+代码的量大于64K时就不能放在一个段中,所以我们将数据、代码、栈分别置于不同的段显得尤为迫切。

    最终运行结果:
    这里写图片描述

    二、多个段的程序:

    如何将code、data、stack置于不同段。其实很简单,我们再拿一个例子来说明,例子程序下:
    目的:通过栈将0:0~0:F的值覆盖到给定数据段(程序中的数据段)的位置

    assume cs:code,ds:data,ss:stack
    
    ;数据段,即覆盖该段数据
    data segment
        dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H
    data ends
    
    ;栈段
    stack segment
        dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0  ;36个字节的栈
    stack ends
    ;代码段
    code segment
        start:
        ;设置数据段与栈段寄存器
        mov ax,data
        mov ds,ax ;设置数据段的段地址
        mov ax,stack
        mov ss,ax ;设置栈段的栈顶地址
        mov sp,20H
    
        mov bx,0
        mov cx,8
    
        mov ax,0
        mov es,ax
    
        push_pop_loop:
            push es:[bx]
            pop ds:[bx]
            add bx,2
        loop push_pop_loop
    
        mov ax,4C00H
        int 21H
    code ends
    end start

    分析:段名相当于一个标号,它标识(标记)了该段的段地址,所以mov ax,stack、mov ax,data的含义就是:将栈段/或者数据段的段地址送入ax寄存器中,在mov ss,ax、mov ds,ax设置SS寄存器与DS寄存器,CPU就能识别内存中的一系列二进制哪些是数据、哪些是栈段、哪些是指令。
    而在code段用end start标识CS:IP从start开始为程序的入口且是指令,这个入口在编译器下被编译器将伪指令end解析,然后将start的地址存放到文件的描述信息中去,可执行程序在被加载到内存中去时先设置CS:IP,那么CPU就从CS:IP即我们设置的start开始执行执行程序指令。对于这种情况,我们可以看到CS:0就是指令首地址,而不是只有一个段时的数据首地址,数据首地址在ds:0开始的地址,栈在ss:sp,这是,我们发现cs、ss、ds的特点意义显示了出来。

    具体的寄存器的值与内存分布数据存储如下图所示:

    这里写图片描述

    测试结果:
    这里写图片描述

    (1)、关于一个段的大小问题:

    一个段的数据的大小为N个字节,那么程序在加载的过程中为该段分配的内存地址大小为:((N-1)/16+1)*16。例如:

    某code segment大小为16字节,那么它的装载占有空间为:16-1=15 ==> 15/16=0 ==> 0+1=1 ==> 1*16=16
    某stack segment大小为15字节,那么它的装载占有空间为:15-1=14 ==> 14/16=0 ==> 0+1=1 ==> 1*16=16
    某data segment大小为17字节,那么它的装载占有空间为:17-1=16 ==> 16/16=1 ==> 1+1=2 ==> 2*16=32
    小总结:实际大小为N,装载大小为M(M=n*16),则:M-16<=N<=M
    所以说一个程序加载到内存以后,它所占用的总内存一定是16字节的倍数。

    (2)关于end start:

    如果修改最后一行的end start为end。因为start只是标识地址,而end start则是告诉编译器start是入口,如果修改最后一行的end start为end的话,那么编译器就不会理睬start,将默认的入口地址写入可执行文件描述信息。而默认的入口地址就是codeseg+dataseg+stackseg三段地址中最前面的地址(低地址)如果代码段(code)在我们编码时本来就在第一段,那么不加end start,编译器将首段地址(cs)写入可执行文件描述信息。但是如果stack/data段是第一段,那写入可执行文件描述信息的地址就是(ss)/(ds),将数据解析成指令,程序肯定是无法正常运行的,所以只有代码段在前面才能正确运行,因此为了不出现默认与实际编程段顺序安排偏离的情况,我们就要记得设置start与end start。

    展开全文
  • 因此,编译器试图在寄存器中保留最常用的变量,并将其余的变量保存在内存中,使用负载和存储来在寄存器和内存之间移动变量。将较少使用的变量(或稍后需要的变量)放入内存的过程称为溢出寄存器。与大小和速度相关的...
  • 首先看一代码: 1 #include 2 3 int glob_val; //global uninitialized 4 int glob_val2 = 2; //global initialized 5 //int glob_val5 = 4; 6 //int glob_val6 = 4; 7 static in
  • 段寄存器CS指向存放程序的内存段,IP是用来存放下条待执行的指令在该段的偏移量,把它们合在一起可在该内存段内取到下次要执行的指令。 段寄存器SS指向用于堆栈的内存段,SP是用来指向该堆栈的栈顶,把它们合在...
  • Linux进程内存分布

    千次阅读 2019-02-12 13:14:03
    ​ 在多任务操作系统中, 每个进程都有自己的内存沙盘, 使用的内存地址都是自己内存中的相对地址,当...内存分布图 这个图是32位机器的, 232=4GB2^32 = 4GB232=4GB, 64位机器就有点大了, (4GB)2(4GB)^2(4GB)2 ...
  • 一、寄存器及数据存储 寄存器是CPU内部的信息...段寄存器:CS、SS、DS、ES 标志寄存器:PSW 共性:8086CPU中所有的寄存器都是16位的,可以存放两个字节 1.1 通用寄存器(以AX为例) 1.1.1 对数据的存储 一个16位寄存...
  • a.CPU 要读写一个内存单元的时候,必须先给出这个内存单元的地址,在8086PC中内存地址是由于CSIP组成。8086CPU中有一个DS寄存器,通常用来存放要访问的数据的地址。 思考? 为什么8086CPU不支持直接向DS(...
  • 内存分布,bss/Data/Text/堆/栈

    千次阅读 2013-05-29 17:41:41
    BSS:BSS(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started ...BSS属于静态内存分配。    数据:数据(data segment)通常是指用来存放程序中已
  • 有关内存分布

    2017-04-27 15:44:44
    linux下进程的内存分布:内核空间 栈(栈的下面有预留空间,栈内存方向向低地址延伸) 动态链接映射区 堆(堆的上方有预留空间,堆内存方向向高地址延伸) 读写部分 只读部分 预留空间栈 实例: 栈顶: 0...
  • 1.GC如何管理堆中对象 2.JAVA内存分配: 1.共享线程区有:方法区,堆 2.独占线程区:PC寄存器,JVM栈,本地方法栈
  • 寄存器详解

    千次阅读 2012-12-07 18:09:14
    AX/EAX: 累加器,算术通用寄存器,与端口的通讯也用这个寄存器. ...累加器结合表示双字长的数,其中累加器用于存低位数. [From]http://blog.csdn.net/huangwei1024/archive/2007/01/29/1496560.aspx 2
  • 内存寄存器、GPIO地址、存储器之间的关系1、如上图,0x0000 0000表示第一个地址字节,代码使用这个16进制时,指针会指向这个物理地址,对4字节的GPIO控制单元来说,这4个字节的物理地址就叫寄存器,当我们把IO口...
  • 内存分布之MMIO

    万次阅读 2017-03-07 20:53:16
    内存分布以及MMIO
  • java内存分布

    2018-02-13 14:30:59
    User.java---------编译------------User.class--------...java内存分布 :A:栈 存储局部变量。局部变量 :定义在方法声明上方法中的变量。B:堆存储new 出来的数组或对象。C:方法区D:本地方法区系统相关。E...
  • 通用寄存器

    2016-10-31 13:33:02
    计算的内置寄存器结构分布,以8086CPU的内部寄存器为例:16位CPU通用寄存器共有8个,AX,BX,CX,DX,SP,BP,SI,DI AHAL是将一个16位的寄存器分别作为两个独立的寄存器,高8位记作H,低8位记作L。 其中有的有特殊的...
  • 1 uboot内存分布 2 start.S 3 汇编知识 3.1 指令 3.2 多寄存器寻址指令 3.3 寄存器 1 uboot内存分布  对于uboot来说,DDR的地址并不是从0x0开始的。flashDDR是统一编址的,但是并不会包含...
  • c++内存分布

    千次阅读 2014-07-19 10:49:34
    为什么需要知道C/C++的内存布局在哪可以可以找到想要的数据?知道内存布局对调试程序非常有帮助,可以知道程序执行时,到底做了什么,有助于写出干净的代码。本文的主要内容如下: 源文件转换为可执行文件可...
  • java 内存分布

    2013-05-21 21:48:20
    首先明白,java创建一个对象,就是分配一块内存区域,然后把这个对象的一些具体信息,存放在这个区域里面。  虚拟机会把计算机中的许多内存管理起来。相当于一个大管家,不同的区域也有不同的应用。因此,也就构成...
  • C++ 内存分布

    千次阅读 2015-08-15 11:10:58
    C++内存分布 参考链接http://www.cnblogs.com/skynet/archive/2011/03/07/1975479.html# 作者 吴秦 为什么需要知道C/C++的内存布局在哪可以可以找到想要的数据?知道内存布局对调试程序非常有帮助,...
  • STM32之内存分布与总线

    万次阅读 2016-05-14 09:16:08
    编程空间(代码空间), 数据空间, 寄存器和I/O端口被组织在同一个线性的4Gb空间中。所有的数据都按照小端存储 可寻址内存空间被分为8个块, 每块包括512MB 所有没有被分配到片上内存或者片上外设的空间都被成为”...
  • 我们知道寄存器就是一些有特定功能的内存单元,所以要访问STM32 寄存器也就是操作STM32 的内存单元,根据C 语言指针的特点,可以使用指针来操作STM32 的内存单元。 首先介绍两个概念: 片内、外设,片内指做成芯片的...
  • 进程的内存分布

    千次阅读 2018-09-04 04:50:39
    一个进程的内存主要分为五部分: text(文本/代码),initialize data(数据),uninitialized data(未初始化数据),heap(堆),stack(栈), 如下图:   1.文本也叫代码,是对象文件或内...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,201
精华内容 8,080
关键字:

内存分布和段寄存器