linux内存,存储,linux的内存开辟

内存开辟

谨以此文纪念过往的岁月。

内存的分配和管理在linux的内核中是一个巨头。在此仅记录个人理解,如有错误请指正。

1.kmalloc

在内存开辟中kmalloc的使用概率很高,在通常的内存开辟中均会使用该函数来开辟内存。但是分配的区域仍然保持原有的数据,一般需要清零。

函数原型:

void *kmalloc(size_t size ,int flags);

参数size很好理解,即是分配多大的内存,以字节为单位。flags也很明白,即是分配的标志,来控制kmalloc的行为。

flags一般常用的标志有GFP_KERNEL,GFP_ATOMIC,__GFP_DMA。简单说一下,GFP_KERNEL指由内核进程来执行的。使用这个flag时,函数会被阻塞,因为kmalloc允许当空闲页比较少的时候,会睡眠等待空闲页。而GFP_ATOMIC则不然,他会立即调用内核预留的一些空闲页,如果所有的预留都没有了,就会立即返回错误。

该标志一般用于中断处理中。kmalloc一般最下处理32或64字节,最大一般不超过128K。

kmalloc的源码如下

static __always_inline void *kmalloc(size_t size, gfp_t flags)

{

if (__builtin_constant_p(size)) {   --检测size是否为常量

if (size > PAGE_SIZE)             --如果开辟的空间大于1页的话,则会采用(void *)__get_free_pages(flags | __GFP_COMP, get_order(size));的办法开辟

return kmalloc_large(size, flags);

if (!(flags & SLUB_DMA)) {           --如果flags并没有被指定为__GFP_DMA,即是在DMA区开辟的话,则采用从已经创建了对象的高速缓存中分配

struct kmem_cache *s = kmalloc_slab(size);   --这个则是查询适合大小的内存对象

if (!s)

return ZERO_SIZE_PTR;

return kmem_cache_alloc(s, flags); --分配对应的内存对象

}

}

return __kmalloc(size, flags);    --如果不是常量的话

}

void *__kmalloc(size_t size, gfp_t flags)  --  这个函数与上面的类似可以说几乎是一样的,不知道这样设计的含义

{

struct kmem_cache *s;

if (unlikely(size > PAGE_SIZE))

return kmalloc_large(size, flags);

s = get_slab(size, flags);

if (unlikely(ZERO_OR_NULL_PTR(s)))  -- unlikely这个东西有没有是一样的,只不过说明大多数情况下条件是为否的。这个是用于编译加速的。这个是告诉编译器,这个条件大多数是否

return s;

return slab_alloc(s, flags, -1, _RET_IP_);  --kmem_cache_alloc的函数直接会调用这个函数。

}

至于__get_free_pages和如何从后备高速缓存中开辟对象,这个在理解内存管理的时候再去理解。到此可以说对于小页的ARCH(即PAGE_SIZE=4K),kmalloc的size小于PAGE_SIZE时,会采用从已经创建好了的高速缓存中分配内存对象。如果size不是2^n的话,会多开辟内存,有时最多开辟两倍的内存,比如size=2^n+1时,最后开辟的内存会是2^(n+1),不过这种情况出现在size大于256时。为什么这么说呢?看下面的源码就可以理解了在kmalloc_slab函数中会调用kmalloc_index来查询size所对应的高速缓存对象。至于为什么会出现这种情况,以后在看内存预分配高速缓冲时再说。源码如下:

static __always_inline int kmalloc_index(size_t size)

{

if (size <= KMALLOC_MIN_SIZE)

return KMALLOC_SHIFT_LOW;

#if KMALLOC_MIN_SIZE <= 64     KMALLOC_MIN_SIZE在arm平台下为 8

if (size > 64 && size <= 96)

return 1;

if (size > 128 && size <= 192)

return 2;

#endif

if (size <=          8) return 3;

if (size <=         16) return 4;

if (size <=         32) return 5;

if (size <=         64) return 6;

if (size <=        128) return 7;

if (size <=        256) return 8;

if (size <=        512) return 9;

if (size <=       1024) return 10;

if (size <=   2 * 1024) return 11;

if (size <=   4 * 1024) return 12;

--下面是为了支持大页的

if (size <=   8 * 1024) return 13;

if (size <=  16 * 1024) return 14;

if (size <=  32 * 1024) return 15;

if (size <=  64 * 1024) return 16;

if (size <= 128 * 1024) return 17;

if (size <= 256 * 1024) return 18;

if (size <= 512 * 1024) return 19;

if (size <= 1024 * 1024) return 20;

if (size <=  2 * 1024 * 1024) return 21;

return -1;

}

这这里主要是理解kmalloc时,size的大小与真实内核分配内存间的关系,以及对于不同的开辟不同的内存大小则采用不同的办法去开辟。对于大于PAGE_SIZE的size采用__get_free_pages开辟,而小于PAGE_SIZE的则采用从已经预创建好的高速缓存对象中开辟。

2.vmalloc

vmalloc分配的内存在虚拟空间是连续的,而在物理空间可能是不连续的。适用于开辟大块连续的,仅仅在软件中存在,用于缓冲的内存。vmalloc需要查询内核虚拟空间、的空闲节点,在空闲区域内创建页表。其所消耗的资源远比kmalloc多。所以不适合适用vmalloc去开辟小的内存,这种情况下,效率会很低。至于vmalloc如何去开辟内存空间以及创建新的页表,还是来看源码的实现。在这里不怎么考虑标志符的影响,先考虑一般情况下的vmalloc的实现 vmalloc函数实现在vmalloc.c中vmalloc->__vmalloc_node,__vmalloc_node除去检测的代码,其主要功能就是调用下面的两个函数。

static void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot,int node, void *caller)

{

struct vm_struct *area;

area = __get_vm_area_node(size, VM_ALLOC, VMALLOC_START, VMALLOC_END,node, gfp_mask, caller);

--- #define VMALLOC_OFFSET  (8*1024*1024)

---  #define VMALLOC_START  (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1))   查询的虚拟地址主要是从高地址开始查询,不过

arm平台high_memory为0。

---  VMALLOC_END  0xE0000000

return __vmalloc_area_node(area, gfp_mask, prot, node, caller);

}

__get_vm_area_node主要是查询内核虚拟空间中的空闲节点,从start到end之间查找一个空闲的vm块

static struct vm_struct *__get_vm_area_node(unsigned long size,unsigned long flags, unsigned long start, unsigned long end,int node, gfp_t gfp_mask, void *caller)

{

static struct vmap_area *va;

struct vm_struct *area;

struct vm_struct *tmp, **p;

unsigned long align = 1;

size = PAGE_ALIGN(size);

area = kmalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);   --开辟内存

--需要多开辟一段信息页

size += PAGE_SIZE;

va = alloc_vmap_area(size, align, start, end, node, gfp_mask);  --根据从内核虚拟空间中查询出一段大小大于size的虚拟内存空间。这个是怎么实现的会在理解虚拟内存的时候去理解。这儿就略过。

--将对虚拟空间块的描述赋值 vm_struct

area->flags = flags;

area->addr = (void *)va->va_start;

area->size = size;

area->pages = NULL;

area->nr_pages = 0;

area->phys_addr = 0;

area->caller = caller;

va->private = area;

va->flags |= VM_VM_AREA;

write_lock(&vmlist_lock);

for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) {

if (tmp->addr >= area->addr)

break;

}

area->next = *p;    --将该vm块添加到链表

*p = area;

write_unlock(&vmlist_lock);

return area;

}

__get_vm_area_node从内核空间查询到大小满足size,一般是大于size的,并且地址连续的一个vm块,并且对vm_struct进行赋值。而在__vmalloc_area_node中则是将上面查找的vm_struct的内存进行创建新页。在上面函数中只是查询到一段连续的内存,并没有将内存分页,创建新的页链表

static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,pgprot_t prot, int node, void *caller)

{

struct page **pages;

unsigned int nr_pages, array_size, i;

nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;

array_size = (nr_pages * sizeof(struct page *));

area->nr_pages = nr_pages;

/* Please note that the recursion is strictly bounded. */

if (array_size > PAGE_SIZE) {       --如果所需要存储页的信息超过了4K,则需要对页信息指针进行二级处理。因为在查询内核空间时,仅多开辟一页的内存用于存储页信息,如果页信息超过了一页,则需要重新去内核虚拟内存中开辟一个新页来查询,这其实是一个嵌套,使用二级页目录。

pages = __vmalloc_node(array_size, gfp_mask | __GFP_ZERO,PAGE_KERNEL, node, caller);

area->flags |= VM_VPAGES;

} else {

pages = kmalloc_node(array_size,(gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,node);       --否则则直接开辟空间,在node节点内。

}

area->pages = pages;

area->caller = caller;

if (!area->pages) {

remove_vm_area(area->addr);

kfree(area);

return NULL;

}

for (i = 0; i < area->nr_pages; i++) {

struct page *page;

if (node < 0)

page = alloc_page(gfp_mask);

else

page = alloc_pages_node(node, gfp_mask, 0);  --这儿就是在node节点中创建新页。不过这儿的node应该不是0

area->pages[i] = page;

}

return area->addr;

}

到此vmalloc就开辟出新的虚拟并连续的内存。vmalloc其实是在内核虚拟空间中找出满足size的内存节点,然后在该节点中创建新的页表。这也是为什么在开辟小的内存时采用vmalloc是不划算的。而且vmalloc对于物理内存而言并没有什么意义,因为在物理内存中,vmalloc开辟的内存并不连续。其主要是对软件有意义。

3.__get_free_pages

在上面也提到__get_free_pages,该函数会在kmalloc开辟空间大于PAGE_SIZE时调用。

__get_free_pages ->alloc_pages ->alloc_pages_node->__alloc_pages->__alloc_pages_internal 经过七拐八拐的最终会分配新页这个函数,这个函数是zone区分配器的核心。

__alloc_pages_internal这个函数以后再了解内存管理时,再好好理解,现在很难。

4.kmem_cache_create与kmem_cache_alloc

这两个函数是组合起来一起用,kmem_cache_create用于创建一个高速缓冲对象,而kmem_cache_alloc用于分配高速缓冲对象内存。这个一般用于需要频繁开辟和注销同一种结构的对象,这个也是用于加速。

5.dma_alloc_coherent

这个函数分配缓冲区并且重新映射。分配的内存可以用于DMA的传输。

6. alloc_bootmem

有时候某些内核模块需要开辟较大的内存如几M的内存空间,如果在内存管理建立后去开辟容易流于失败,通常会在引导程序时提前开辟,这样会跳过内核的内存管理,对内存管理而言,这样的空间是不可见的。这样会减少留给操作系统的RAM。这段内存也需要自己去管理。这个我喜欢!!

7. ioremap

ioremap这个函数不能是一个真正的内存分配,不过他也是一个内存分配,为什么这么说呢?因为ioremap的内存可以将内存管理系统看不见的硬件内存映射到虚拟内存中,说他是内存分配,因为他是分配纯虚拟空间。说他不是,因为他不分配真实的物理空间,他是一种映射。

例如:现在物理内存128M,但是在传替给内核参数时却是 “mem = 124M”,那对用linux系统而言,真实的内存就是124M那还有的4M就无法使用了。这个时候就可以采用ioremap的办法,将在流离在外的4M给弄来,ioremap(0x7C00000,0x400000);这样就可以将那4M给映射到内存中,不过这个还是需要自己去管理的。

以上就是一些内存分配的常用的机制。至于具体的实现办法以后再学习内存管理机制的时候,在去看看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值