内存_相关概念



buffer 与cache 的区别
A buffer is something that has yet to be "written" to disk. A cache is something that has been "read" from the disk and stored for later use.


NODE 

对应UMA系统来说就是一个NODE。对于NUMA系统来说,两个NODE,一个NODE给CPU访问,一个NODE给外设访问。

DDR中的BANK

DDR3中bank, 16bit和32bit等概念最近在看内存相关的东东。以前认为内存就是块资源,需要的时候,malloc出来一部分使用即可。对内部的东东没有深入了解过。刚开始看起来,感觉有点丈二和尚。通过各种查询,并请教牛人,对基本概念有了个初步了解,先总结一把。先说说bank。

看到bank首先想到了银行,然后是利率,然后是房贷...扯远了,这儿的bank是存储库的意思。也就是说,一块内存内部划分出了多个存储库,访问的时候指定存储库编号,就可以访问指定的存储库。具体内存中划分了多少个bank,要看地址线中有几位BA地址,如果有两位,说明有4个bank,如果有3位,说明有8个bank.DDR3的地址中有三个BA,即三个Bank Address,BA0, BA1, BA2。所以DDR3单块内存中都是8个bank.存储库里面的内存是怎么组织的呢?

存储库里面类似于一个矩阵。有很多点,没一个点就是一个cell,也就是一个存储点,有一个电容和一个晶体管组成,通过上电与否,来表示1或0.
每一个cell由一个行号和一个列号来唯一标识。也可以这么理解,bank中分成了很多行(row),每一行又有很多列。这样的话,给定bank编号,可以找到bank,给定row编号,可以找到所在行,给定 column 编号,可以找到所在列,也就找到了要访问的cell。一个bank中有多少行,多少列呢?
这要看行和列分别有多少位来表示。假如表示行的有A0~A14,那么单个bank中行的总量为2^15。列类似,如果表示列的有A0~A9,单个bank中列总量为2^10.

下面来看看16bit/32bit内存的概念。这儿所说的16bit/32bit,指的是内存中以多长为单位进行存储。16bit,即是说内存中是以16bit为单位访问内存的,也就是说,你给内存一个地址,内存会给你一个16bit的数据到数据线。32bit的与此类似。

下面来看一个具体例子。
该例子中,用两个16bit的DDR3内存拼成了一个32bit的DDR3.每块16bit DDR3的大小为512M Bytes.看看硬件上连接,我们这儿只关心地址线和数据线,CS, CK, CKE, CAS, RAS等暂时不考虑。
第一片16bit DDR3的BA0, BA1, BA2连接到了CPU的BA0, BA1, BA2。
第二片16bit DDR3的BA0, BA1, BA2也连接到了CPU的BA0, BA1, BA2。
第一片16bit DDR3的A0~A14连接到了CPU的A0~A14。
第二片16bit DDR3的A0~A14连接到了CPU的A0~A14。
第一片16bit DDR3的D0~D15连接到了CPU的D0~D15。
第二片16bit DDR3的D0~D15连接到了CPU的D16~D31。

分析下该实例。bank address有三个bit,所以单个16bit DDR3内部有8个bank.表示行的有A0~A14,共15个bit,说明一个bank中有2^15个行。
表示列的有A0~A9,共10个bit,说明一个bank中有2^10个行。来看看单块16bit DDR3容量:2^3*2^15*2^10=2^28=256M我们的内存是512M,到这儿怎么变成256M了?被骗了?呵呵,当然没有。忘了我们前面一直提到的16bit。16bit是2个byte对吧。访问一个地址,内存认为是访问16bit的数据,也就是两个字节的数据。256M个地址,也就是对应512M的数据了。真相大白。卖内存的没骗俺。

再来看看两个16bit是如何组成一个32bit的。有一个概念一定要清楚,这儿所说的两个16bit组成一个32bit,指的是数据,与地址没有关系。
我开始这一块没搞清楚,一直认为是两个16bit的地址组成了一个32bit的地址。然后高位地址,地位地址,七七八八。。。之后没一点头绪。
将16bit/32bit指的是数据宽度之后,就非常明了了。每一块16bit DDR3中有8个bank,2^15个row,2^10个column。也就是有256M个地址。
看前面的连线可知,两块16bit DDR3的BA0~BA2和D0~D14其实是并行连接到CPU。也就是说,CPU其实认为只有一块内存,访问的时候按照BA0~BA2和D0~D14给出地址。两块16bit DDR3都收到了该地址。它们是怎么响应的呢?两块内存都是16bit,它们收到地址之后,给出的反应是要么将给定地址上2个字节送到数据线上,要么是将数据线上的两个字节写入到指定的地址。再看数据线的连接,第一片的D0~D15连接到了CPU的D0~D15,第二片的D0~D15连接到了CPU的D16~D31。CPU认为自己访问的是一块32bit的内存,所以CPU每给出一个地址,将访问4个字节的数据,读取/写入。这4字节数据对应到CPU的D0~D31,又分别被连接到两片内存的D0~D15,这样一个32bit就被拆成了两个16bit.反过来,也就是两个16bit组成了一个32bit.CPU访问的内存地址有256M个,每访问一个地址,将访问4个字节,这样CPU能访问的内存即为1G.


kmalloc、vmalloc、kmap、malloc的区别

简单的说:

  1. kmalloc和vmalloc是分配的是内核的内存,malloc分配的是用户的内存
  2. kmalloc保证分配的内存在物理上是连续的,vmalloc保证的是在虚拟地址空间上的连续,malloc不保证任何东西(猜测的,不一定正确)
  3. kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相对较大
  4. 内存只有在要被DMA访问的时候才需要物理上连续
  5. vmalloc比kmalloc要慢
  6. kmalloc()是内核中最常见的内存分配方式,它最终调用伙伴系统的__get_free_pages()函数分配,根据传递给这个函数的flags参数,决定这个函数的分配适合什么场合,如果标志是GFP_KERNEL则仅仅可以用于进程上下文中,如果标志GFP_ATOMIC则可以用于中断上下文或者持有锁的代码段中。

    kmalloc返回的线形地址是直接映射的,而且用连续物理页满足分配请求,且内置了最大请求数(2**5=32页)。

  7. vmalloc优先使用高端物理内存,但性能上会打些折扣。

    vmalloc分配的物理页不会被交换出去
    vmalloc返回的虚地址大于(PAGE_OFFSET + SIZEOF(phys memory) + GAP),为VMALLOC_START----VMALLOC_END之间的线形地址

    vmalloc使用的是vmlist链表,与管理用户进程的vm_area_struct要区别,而后者会swapped;

  8. kmap()是主要用在高端存储器页框的内核映射中,一般是这么使用的:
    使用alloc_pages()在高端存储器区得到struct page结构,然后调用kmap(struct *page)在内核地址空间PAGE_OFFSET+896M之后的地址空间中(PKMAP_BASE到FIXADDR_STAR)建立永久映射(如果page结构对应的是低端物理内存的页,该函数仅仅返回该页对应的虚拟地址
    )
    kmap()也可能引起睡眠,所以不能用在中断和持有锁的代码中

    不过kmap 只能对一个物理页进行分配,所以尽量少用。

    对于高端物理内存(896M之后),并没有和内核地址空间建立一一对应的关系(即虚拟地址=物理地址+PAGE_OFFSET这样的关系),所以不能使用get_free_pages()这样的页分配器进行内存的分配,而必须使用alloc_pages()这样的伙伴系统算法的接口得到struct *page结构,然后将其映射到内核地址空间,注意这个时候映射后的地址并非和物理地址相差PAGE_OFFSET.

kmalloc和get_free_page申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。而vmalloc申请的内存则位于vmalloc_start~vmalloc_end之间,与物理地址没有简单的转换关系,虽然在逻辑上它们也是连续的,但是在物理上它们不要求连续。


简单的说:

  1. kmalloc和vmalloc是分配的是内核的内存,malloc分配的是用户的内存
  2. kmalloc保证分配的内存在物理上是连续的,vmalloc保证的是在虚拟地址空间上的连续,malloc不保证任何东西(这点是自己猜测的,不一定正确)
  3. kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相对较大
  4. 内存只有在要被DMA访问的时候才需要物理上连续
  5. vmalloc比kmalloc要慢

vmalloc() vs vmap()


Linux 2.6 also features a vmap( ) function, which maps page frames already allocated in a noncontiguous memory area: essentially, this function receives 
as its parameter an array of pointers to page descriptors, invokes get_vm_area( ) to get a new vm_struct descriptor, and then invokes map_vm_area( ) to 
map the page frames. The function is thus similar to vmalloc( ), but it does not allocate page frames.
也就是说vmap它将已经映射了的物理地址,又映射到了一些线性地址,所以对于这部分物理地址,现在有两个或两个以上的线性地址与其对应。

       

页大小

内核把物理页作为内存管理的基本单位;内存管理单元(MMU)把虚拟地址转换为物理地址,通常以页为单位进行处理。MMU以页大小为单位来管理系统中的也表。32位系统:页大小4KB。64位系统:页大小8KB。

kzalloc

      kzalloc实现了kmalloc以及memset的功能,一个函数起到了两个函数的作用。即原来我们每次申请内存的时候都会这么做 , 先是用 kmalloc() 申请空间 , 然后用 memset() 来初始化 而现在省事了 , 一步到位 , 直接调用 kzalloc(), 效果等同于原来那两个函数 , 所有申请的元素都被初始化为 0。

kmalloc,get_free_page

 

 kmalloc和get_free_page申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系,virt_to_phys()可以实现内核虚拟地址转化为物理地址。

    kmalloc() 分配连续的物理地址,用于小内存分配。kmalloc() 函数本身是基于 slab 实现的。slab 是为分配小内存提供的一种高效机制。但 slab 这种分配机制又不是独立的,它本身也是在页分配器的基础上来划分更细粒度的内存供调用者使用。也就是说系统先用页分配器分配以页为最小单位的连续物理地址,然后 kmalloc() 再在这上面根据调用者的需要进行切分。

    kmalloc的第一个参数是要分配的块的大小,第二个参数是分配标志(flags)。

以下分配内存的方法参见:<linux/gfp.h>

方法

描述

alloc_page(gfp_mask)只分配一页,返回指向页结构的指针
alloc_pages(gfp_mask, order)分配 2^order 个页,返回指向第一页页结构的指针
__get_free_page(gfp_mask)只分配一页,返回指向其逻辑地址的指针
__get_free_pages(gfp_mask, order)分配 2^order 个页,返回指向第一页逻辑地址的指针
get_zeroed_page(gfp_mask)只分配一页,让其内容填充为0,返回指向其逻辑地址的指针

 

alloc** 方法和 get** 方法的区别在于,一个返回的是内存的物理地址,一个返回内存物理地址映射后的逻辑地址。

如果无须直接操作物理页结构体的话,一般使用 get** 方法。

 

相应的释放内存的函数如下:也是在 <linux/gfp.h> 中定义的

extern void __free_pages(struct page *page, unsigned int order);
extern void free_pages(unsigned long addr, unsigned int order);
extern void free_hot_page(struct page *page);

在请求内存时,参数中有个 gfp_mask 标志,这个标志是控制分配内存时必须遵守的一些规则。

gfp_mask 标志有3类:(所有的 GFP 标志都在 <linux/gfp.h> 中定义)

  1. 行为标志 :控制分配内存时,分配器的一些行为
  2. 区标志   :控制内存分配在那个区(ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM 之类)
  3. 类型标志 :由上面2种标志组合而成的一些常用的场景

 

 

区标志主要以下3种:

区标志

描述

__GFP_DMA从 ZONE_DMA 分配
__GFP_DMA32只在 ZONE_DMA32 分配 (注1)
__GFP_HIGHMEM从 ZONE_HIGHMEM 或者 ZONE_NORMAL 分配 (注2)

注1:ZONE_DMA32 和 ZONE_DMA 类似,该区包含的页也可以进行DMA操作。
         唯一不同的地方在于,ZONE_DMA32 区的页只能被32位设备访问。
注2:优先从 ZONE_HIGHMEM 分配,如果 ZONE_HIGHMEM 没有多余的页则从 ZONE_NORMAL 分配。

 

行为标志主要有以下几种:

 

#define __GFP_WAIT  ((__force gfp_t)0x10u)  //表示分配内存的请求可以中断。也就是说,调度器在该请求期间可随意选择另一个过程执行,或者该请求可以被另一个更重要的事件中断。   
#define __GFP_HIGH  ((__force gfp_t)0x20u)  //如果请求非常重要,则设置__GFP_HIGH,即内核急切的需要内存时。在分配内存失败可能给内核带来严重得后果时,一般会设置该标志  can access emergency pool   
#define __GFP_IO    ((__force gfp_t)0x40u)  //在查找空闲内存期间内核可以进行I/O操作。这意味着如果内核在内存分配期间换出页,那么仅当设置该标志时,才能将选择的页写入磁盘。   
#define __GFP_FS    ((__force gfp_t)0x80u)  //允许内核执行VFS操作   
#define __GFP_COLD  ((__force gfp_t)0x100u) //如果需要分配不在CPU高速缓存中的“冷”页时,则设置__GFP_COLD。   
#define __GFP_NOWARN    ((__force gfp_t)0x200u) //在分配失败时禁止内核故障警告。   
#define __GFP_REPEAT    ((__force gfp_t)0x400u) //在分配失败后自动重试,但在尝试若干次之后会停止。   
#define __GFP_NOFAIL    ((__force gfp_t)0x800u) //在分配失败后一直重试,直至成功。   
#define __GFP_NORETRY   ((__force gfp_t)0x1000u)//不重试,可能失败   
#define __GFP_COMP  ((__force gfp_t)0x4000u)//增加复合页元数据   
#define __GFP_ZERO  ((__force gfp_t)0x8000u)//在分配成功时,将返回填充字节0的页。   
#define __GFP_NOMEMALLOC ((__force gfp_t)0x10000u) //不适用紧急分配链表   
#define __GFP_HARDWALL   ((__force gfp_t)0x20000u) //只在NUMA系统上有意义。它限制只在分配到当前进程的各个CPU所关联的结点分配内存。如果进程允许在所有的CPU上运行(默认情况下),该标志是没有意义的。只有进程可以运行的CPU受限时,该标志才有意义。   
#define __GFP_THISNODE  ((__force gfp_t)0x40000u)//页只在NUMA系统上有意义,如果设置该比特位,则内存分配失败的情况下不允许使用其他结点作为备用,需要保证在当前结点或者明确指定的结点上成功分配内存。   
#define __GFP_RECLAIMABLE ((__force gfp_t)0x80000u) //将分配的内存标记为可回收   
#define __GFP_MOVABLE   ((__force gfp_t)0x100000u)  //将分配的内存标记为可移动   
  
#define __GFP_BITS_SHIFT 21 /* Room for 21 __GFP_FOO bits */   
#define __GFP_BITS_MASK ((__force gfp_t)((1 << __GFP_BITS_SHIFT) - 1))     

 

 


 

 

类型标志是编程中最常用的,在使用标志时,应首先看看类型标志中是否有合适的,如果没有,再去自己组合 行为标志和区标志。
由于这些标志总是组合使用,内核做了一些分组,包含了用于各种标准情形的适当地标志。

/* This equals 0, but use constants in case they ever change */  
#define GFP_NOWAIT  (GFP_ATOMIC & ~__GFP_HIGH) 

#define GFP_ATOMIC  (__GFP_HIGH)//用于原子分配,在任何情况下都不能中断,可能使用紧急分配链表中的内存   
#define GFP_NOIO    (__GFP_WAIT)//明确禁止IO操作,但可以被中断   
#define GFP_NOFS    (__GFP_WAIT | __GFP_IO)//明确禁止访问VFS层操作,但可以被中断   
#define GFP_KERNEL  (__GFP_WAIT | __GFP_IO | __GFP_FS)//内核分配的默认配置   
#define GFP_TEMPORARY   (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE)  

 

 


#define GFP_USER    (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL)     //分配用于用户进程的页面。与GFP_KERNEL相比,增加__GFP_HARDWALL __GFP_HARDWALL表示可以在进程能够运行的CPU节点上分配内存。
#define GFP_HIGHUSER    (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | \   
             __GFP_HIGHMEM)//是GFP_USER的一个扩展,页用于用户空间,它允许分配无法直接映射的高端内存,使用高端内存页是没有坏处的,因为用户过程的地址空间总是通过非线性页表组织的   
#define GFP_HIGHUSER_MOVABLE    (__GFP_WAIT | __GFP_IO | __GFP_FS | \   
                 __GFP_HARDWALL | __GFP_HIGHMEM | \  
                 __GFP_MOVABLE)//类似于GFP_HIGHUSER,但分配是在虚拟内存域ZONE_MOVABLE中进行   
#define GFP_NOFS_PAGECACHE  (__GFP_WAIT | __GFP_IO | __GFP_MOVABLE)   
#define GFP_USER_PAGECACHE  (__GFP_WAIT | __GFP_IO | __GFP_FS | \   
                 __GFP_HARDWALL | __GFP_MOVABLE)  
#define GFP_HIGHUSER_PAGECACHE  (__GFP_WAIT | __GFP_IO | __GFP_FS | \   
                 __GFP_HARDWALL | __GFP_HIGHMEM | \  
                 __GFP_MOVABLE)  
  
#ifdef CONFIG_NUMA   
#define GFP_THISNODE    (__GFP_THISNODE | __GFP_NOWARN | __GFP_NORETRY)   
#else   
#define GFP_THISNODE    ((__force gfp_t)0)   
#endif   

 

#define GFP_IOFS    (__GFP_IO | __GFP_FS)

/**

 * 分配的页面用于透明巨页。

 */

#define GFP_TRANSHUGE       (GFP_HIGHUSER_MOVABLE | __GFP_COMP | \

                             __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | \

                             __GFP_NO_KSWAPD)

 

/**

 * 只允许在当前节点中分配。

 */

#ifdef CONFIG_NUMA

#define GFP_THISNODE  (__GFP_THISNODE | __GFP_NOWARN | __GFP_NORETRY)

#else

#define GFP_THISNODE  ((__force gfp_t)0)

#endif

 

 

 

 

以上各种类型标志的使用场景总结:

场景

相应标志

进程上下文,可以睡眠使用 GFP_KERNEL
进程上下文,不可以睡眠使用 GFP_ATOMIC,在睡眠之前或之后以 GFP_KERNEL 执行内存分配
中断处理程序使用 GFP_ATOMIC
软中断使用 GFP_ATOMIC
tasklet使用 GFP_ATOMIC
需要用于DMA的内存,可以睡眠使用 (GFP_DMA|GFP_KERNEL)
需要用于DMA的内存,不可以睡眠使用 (GFP_DMA|GFP_ATOMIC),或者在睡眠之前执行内存分配

 


    get_free_page() 分配连续的物理地址,用于整页分配。 __get_free_page() 是页面分配器提供给调用者的最底层的内存分配函数。它分配连续的物理内存。__get_free_page() 函数本身是基于 buddy 实现的。在使用 buddy 实现的物理内存管理中最小分配粒度是以页为单位的。


    kmalloc() / __get_free_page() 分配的是物理地址,而返回的则是虚拟地址(虽然这听上去有些别扭)。正是因为有了这种映射关系,所以需要将它们的返回地址减去 PAGE_OFFSET 才可以得到真正的物理地址。 

vmalloc申请的内存则位于vmalloc_start~vmalloc_end之间,与物理地址没有简单的转换关系,虽然在逻辑上它们也是连续的,但是在物理上它们不要求连续。

 

SLUB

  

     2.6.26中的内存管理大概分为3个层次 SLUB,伙伴系统和ZONE,其中SLUB在最高层,SLUB主要对1页以下的内存进行管理,将1页内存分成相同大小的块,SLUB将这些块称为object,内核进行内存申请时则分配1个块,也就是1个object。在SLAB的高速缓存中有普通高速缓存和专用高速缓存,平时用kmem_cache_create创建的是专用高速缓存,比如存放task_struct,mm_struct的高速缓存。普通高速缓存主要供kmalloc使用。第一个高速缓存叫kmem_cache,存放在cache_cache变量中,这个cache专门用于为其他cache分配描述符。 

     内存区大小的范围一般包括13个几何分布的内存区。一个叫做malloc_sizes的表(其元素类型为cache_sizes)分别指向26个高速缓存描述符,与其相关的内存区大小为32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072。


 

mem_cache


KMALLOC  PK VMALLOC 

  • kmalloc是基于slab的,所以速度比较快。vmalloc的内部会调用到kmalloc,但是只是分配vm_struct描述符,和分配nr_pages指针数组,这个数组里的每个元素指向vm_struct对应的非连续内存区域的每一个页描述符结构。真正的分页是通过alloc_page一页一页的从buddy system分配。所以物理地址是不连续的,一页一页分配物理地址不一定啥地方了。kmalloc分配的页已经映射好了,而vmalloc分配后一级一级建立页表很是麻烦。
  • 高端内存使用的是小页,所以使用vmalloc的时候不会打印出"Use Large Page PSE = 1"。
  • 在中断上下文中可以使用kmalloc,前提是使用GFP_ATOMIC标志,而中断上下文中不能用vmalloc替代kmalloc,vmalloc调用kmalloc和alloc_page都使用了GFP_KERNEL标志,这个标志可能因此进程休眠。
  • 用vmalloc分配页修改的是内核页表部分,并没有修改进程的相关项,在访问的时候需要通过page fault来同步,而kmalloc就不需要这一过程。
  • kmalloc和 vmalloc 是分配的是内核的内存,malloc分配的是用户的内存                                                     
  • 但是kmalloc最多只能开辟大小为32XPAGE_SIZE的内存,一般的PAGE_SIZE=4kB,也就是128kB的大小的内存。注意kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用。

 

 ioremap

一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。最后,我们要特别强调驱动程序中mmap函数的实现方法。用mmap映射一个设备,意味着使用户空间的一段地址关联到设备内存上,这使得只要程序在分配的地址范围内进行读取或者写入,实际上就是对设备的访问。笔者在Linux源代码中进行包含"ioremap"文本的搜索,发现真正出现的ioremap的地方相当少。所以笔者追根索源地寻找I/O操作的物理地址转换到虚拟地址的真实所在,发现Linux有替代ioremap的语句,但是这个转换过程却是不可或缺的下面的程序在启动的时候保留一段内存,然后使用ioremap将它映射到内核虚拟空间,同时又用remap_page_range映射到用户虚拟空间,这样一来,内核和用户都能访问。如果在内核虚拟地址将这段内存初始化串"abcd",那么在用户虚拟地址能够读出来:



vmalloc VS ioremap

 

vmalloc 是一个基本的 Linux 内存分配机制,它在虚拟内存空间分配一块连续的内存区,尽管这些页在物理内存中不连续 (使用一个单独的 alloc_page 调用来获得每个页),但内核认为它们地址是连续的。 应当注意的是:vmalloc 在大部分情况下不推荐使用。因为在某些体系上留给 vmalloc 的地址空间相对小,且效率不高。kmalloc 和 _get_free_pages 返回的内存地址也是虚拟地址,其实际值仍需 MMU 处理才能转为物理地址。vmalloc和它们在使用硬件上没有不同,不同是在内核如何执行分配任务上:kmalloc 和 __get_free_pages 使用的(虚拟)地址范围和物理内存是一对一映射的, 可能会偏移一个常量 PAGE_OFFSET 值,无需修改页表。而vmalloc 和 ioremap 使用的地址范围完全是虚拟的,且每次分配都要通过适当地设置页表来建立(虚拟)内存区域。 vmalloc 可获得的地址在从 VMALLOC_START 到 VAMLLOC_END 的范围中,定义在 <asm/patable.h> 中。vmalloc 分配的地址只在处理器的 MMU 之上才有意义。当驱动需要真正的物理地址时,就不能使用 vmalloc。 调用 vmalloc 的正确场合是分配一个大的、只存在于软件中的、用于缓存的内存区域时。注意:vamlloc 比 __get_free_pages 要更多开销,因为它必须即获取内存又建立页表。因此, 调用 vmalloc 来分配仅仅一页是不值得的。vmalloc 的一个小的缺点在于它无法在原子上下文中使用。因为它内部使用 kmalloc(GFP_KERNEL) 来获取页表的存储空间,因此可能休眠。ioremap 也要建立新页表,但它实际上不分配任何内存,其返回值是一个特殊的虚拟地址可用来访问特定的物理地址区域。为了保持可移植性,不应当像访问内存指针一样直接访问由 ioremap 返回的地址,而应当始终使用 readb 和 其他 I/O 函数。ioremap 和 vmalloc 是面向页的(它们会修改页表),重定位的或分配的空间都会被上调到最近的页边界。ioremap 通过将重映射的地址下调到页边界,并返回第一个重映射页内的偏移量来模拟一个非对齐的映射。


 高速缓存

1.对访问内存的缓冲(CACHE),原本意义上的高速缓存。包含对数据和命令的缓存,硬件上分为L1和L2,对软件上是透明的。2.页表缓存TLB,其实是高速缓存的一部分。3.写入缓存,如果系统总线被锁,其他CPU正在装入或者写回cache line 。将要写入的内容缓存。等待条件允许,缓冲区硬件将缓存写入内存。

 

内存模式

由CPU相关寄存器控制可以设置为多种模式1.不缓存2.缓存,穿透模式3,缓存,回写模式4.写保护模式,只读。

 



内存管理学习之内存分配和地址重定位

内存的划分分为静态划分和动态划分。都是连续内存存储技术,而非连续内存存储技术有分页、页段式分段。

静态划分预先将物理内存划分为固定分区,当新进程装进内存,选择合适的空闲分区分配给进程。又称为固定分区法,分区的长度可以不同。

管理分区使用分区使用表,表项的内容是分区编号(线性的)、分区大小、分区使用情况。按照分区大小从小到大方式排列。

一种特殊的固定分区法:分页式划分。将内存划分为很小的页框,其中页框远小于上述的分区。两者的区别是:固定分区要求进程存进连续的内存空间,而分页式划分允许进程可以不连续存储在不同的页框。页框的管理使用空闲页框表,将连续空闲的页框集中管理。

静态划分的缺点是会产生大量的内零头,这些内零头不能再被使用,因为已经分配给既定的进程了,这样就大大浪费了内存空间。

为了解决内零头,可以采用动态划分技术,将真个用户内存看做整体,动态的将内存划分给需要的进程。

动态分区划分需要一定的划分算法保证外零头最少,主要的划分算法有:FFA首次适应算法、BFA最佳适应算法、WFA最差适应算法和伙伴分配算法,这就解决了为进程分配内存空间的问题。并不解决是否一次性装入进程的问题。

FFA算法:查询空闲分区表,将第一个适合的空闲分区分给进程,若剩余分区大于阈值则作为新的空闲分区更新分区表,否则全部分给进程。该算法的缺点是造成过多的小外零头,使得进程不能再多装。

提出一种解决方案:紧凑技术将进程移动全部放在内存的一段,这样就将外零头合并,该技术需要采用地址动态重定位技术,又称为碎片整理技术。

BFA算法:将空闲分区从小到大排列,选择第一个适合的分区分配给进程,这样保证外零头最小。优点是保证不分割大分区,但是形成过多的无法分割的小的外零头。

WFA算法:将空闲分区从大到小排列,选择第一个适合的分区分配给进程,这样保证了外零头不至于过小能够再次分割,但是总是分割大分区,使得不能装下稍微大的进程。

伙伴算法:将静态分区和动态分区法结合在一起,降低了内零头和外零头的浪费。该算法将内存分为2的幂次方,当第一次划分的时候根据分半的方法划分内存,将两个相邻的同大小的分区成为兄弟分区,当内存回收的时候可以讲兄弟分区合并,这样分区的大小就可以动态变化。

---------------------------------------------------------------------------------------------------------------------------------------------

地址重定位:静态重定位和动态重定位

静态重定位是指在进程装入内存阶段就将进程的逻辑地址转化为了物理地址,这种方法要求程序必须完整的一次性装入内存,且进程的位置不能发生改变,及时使用交换技术在换回是仍然要在原始的位置,所以一般交换技术采用动态重定位。

动态重定位是指在进程运行时,每次读取指令的时候进行动态地址转换,需要硬件的支持,称为地址转换器在cpu中。该过程实在执行期间完成的。

---------------------------------------------------------------------------------------------------------------------------------------------

覆盖技术是指大的程序无法一次性装入内存,可以在知道程序的架构的时候按模块进行覆盖,原则是不互相调用的同等级的模块可以相互覆盖内存区域。运用该方法的前提是明确知道程序的逻辑架构。覆盖区的长度由覆盖段的最大长度决定。

---------------------------------------------------------------------------------------------------------------------------------------------

交换技术是将进程的全部或者一部分暂时不用的换出内存缓解内存压力,一般交换技术用于不连续存储中,使用动态重定位,因为若使用静态重定位换出的部分在换入时必须换到原始的位置。


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值