【Linux内核笔记】内核内存管理

内核管理内存的基本单位
每个物理页都由struct page表示,位于<linux/mm_types.h>。 假设每个结构体40byte, 物理页8KB, 4GB物理内存。则有 2 18 2^{18} 218页,结构体占用20MB,并不大。

struct page{
    flags //页状态
    _count//引用计数,-1当前内核没有引用这一页。
    _mapcount
    struct list_head lru
    *virtual//虚拟地址
    ...
}

  • 一些硬件只能通过直接内存访问,即只能访问特定内存地址
  • 一些体系结构的内存的物理寻址范围比虚拟范围大,即有些内存不能永久映射到内核空间中。
    所以Linux使用四种区:
    • ZONE_DMA:直接内存访问
    • ZONE_DMA32: 32位设备的直接内存访问
    • ZONE_NORMAL:正常映射的页
    • ZONE_HIGHMEM:高端内存,其中的页不能永久的映射到内核地址空间

实际分类与体系结构相关
结构体很大,但是内存中只有三个区,结构体

struct zone{
    watermark[NR_WMARK]//持有该区的最小值,最低和最高 内存消耗基准,随着空闲内存多少而变化
    lock//自旋锁,保护结构体被并发访问
    pageset[NR_CPUS]
    struct zone_lru{
        struct list_head list
        unsigned long nr_saved_scan
    }lru[NR_LRU_LISTS]
    *name //区名字,DMA,Normal,HighMem
    ...
}
内核内存
ZONE_DMA
ZONE_NORMAL
ZONE_HIGHMEM
struct zone
struct page
struct page
struct page
物理页
物理页
...
struct zone
struct page
struct page
struct zone
struct page

获得释放低级页

以页为单位分配内存<linux/gfp.h>,最核心函数为

struct page * alloc_pages(gfp_t gfp_mask , unsigned int order)

分配 2 o r d e r 2^{order} 2order个连续物理页,返回指向第一页struct page的指针。

若无需用到struct page。使用如下返回指向给定物理页的当前逻辑地址

unsigned long  __get_free_pages(gfp_t gfp_mask , unsigned int order)

分配给用户空间的页为了保证数据安全,全部数据填充为0

unsigned long get_zeroed_page(unsigned int gfp_mask)

释放页

void __free_pages(struct page *page, unsigned int order)
void free_pages(unsigned long addr,unsigned)
kmalloc()

比用户控件的malloc多了flags参数。用于获取字节为单位的一块内核内存。<linux/slab.h> 物理地址连续

void * kmalloc(size_t size, gfp_t flags)
void kfree(const void *ptr)

kmalloc申请的内存早就被释放或者释放的内存不是kmalloc申请的,则kfree可能导致严重后果

gfp_mask标志
  • 行为修饰符:如何分配所需内存
  • 区修饰符: 哪个区分配内存
  • 类型:组合了以上,方便
    • GFP_KERNEL 可让调用者睡眠、交换、刷新一些页到硬盘
    • GFP_ATOMIC 失败率较高,调用者无法睡眠。中断处理程序、软中断和tasklet必须用到
    • GFP_NOIO 介于以上两者
    • GFP_NOFS 介于以上两者
vmalloc()

虚拟地址连续,物理地址无需连续。硬件设备需要得到连续的物理地址内存,软件使用的内存块就可以使用该类内存申请函数。很多内核代码使用kmalloc,处于性能考虑,因为vmalloc需要对页进行映射,因此只有在获得大块内存时才使用

void * vmalloc(unsigned long size)
void vfree(const void *addr)

slab层

若需要频繁申请和是释放数据结构块,常常想到空闲链表(包含可供使用的、已经分配好的数据结构块)。但是由于空闲链表无法全局控制且内核不知道有空闲链表,因此slab分配器担任通用数据结构缓存层的角色
kmalloc()接口建立在slab层上,使用一组通用高速缓存

  • 不同的对象划分不同高速缓存组,每个组存放一种对象类型
  • 这些高速缓存被划分为slab
  • slab由一个或多个物理上连续的页组成
  • 每个slab处于三种状态之一:满,部分满或空。当内核的某一部分需要一个新对象时,先从部分满的slab进行分配,若没有部分满则从空的slab分配,若没有空的slab则新建。
A类对象的kmem_cache
slabs_full
slabs_partial
slabs_empty
对象
对象
对象
对象
对象
B kmem_cache
slab1
slab2
slab3
对象
对象
对象
对象
对象
对象
struct kmem_cache * kmem_cache_create( const char *name, size_t  size, size_t align, unsigned long flags, void (*ctor)(void *));
//获取新的页
kmem_getpages()
//撤销高速缓存
int kmem_cache_destroy(struct kmem_cache *cachep)
//从缓存中获取对象
void * kmem_cache_alloc(struct kmem_cache * cachep, gfp_t flags)
//释放对象返回给slab
void kmem_cache_free(struct kmem_cache * cachep, void *objp)

在栈上静态分配

  • 每个进程的内核栈小且固定,两个物理页大小,32位8KB 64位16KB
  • 随着开机时间的增加,寻找两个连续的未分配的页开始变的困难。并且由于中断处理程序也使用内核栈,使得内核栈的约束条件越来越苛刻。因此在2.6系列早期,可使用单页内核栈,此时中断处理程序不再使用内核栈,另起炉灶使用中断栈。每个进程拥有一个中断处理程序的栈。
  • 栈的内存很宝贵,所以需要控制局部变量大小不超过几百字节,由于栈溢出悄无声息,首当其冲受害即是thread_info结构

高端内存的映射

高端内存alloc_pages() __GFP_HIGHMEM 获得的页没有逻辑地址
x86体系,高于896mb的物理内存的范围大都是高端内存,一但这些内存被分配必须映射到内核的逻辑地址空间上,3GB~4GB

void * kmap(struct page * page) //可睡眠,只能用在进程上下文中。低端内存返回虚拟地址,高端内存建立永久映射,再返回地址。
void kunmap(struct page * page)

void * kmap_atomic(struct page *page, enum km_type type)//临时映射, 当前上下文不能睡眠的情况,如中断处理程序
void kunmap_atomic(void *kvaddr, enum km_type type)

每个CPU数据

CPU可有自己独立的数据,存放在一个数组中。数组每一项对应上一个存在的处理器,按当前处理器号确定这个数组的当前元素。2.4内核

unsigned long my_percpu[NR_CPUS]
//访问
int cpu;
cpu=get_cpu();//该函数禁止内核抢占,因此此段代码不需要锁
my_percpu[cpu];
put_cpu();//激活抢占

2.6内核引入了新的CPU接口

DEFINE_PER_CPU(type,name)
get_cpu_var(name)//禁止内核抢占
put_cpu_var(name)//激活抢占
per_cpu(name,cpu)//获得别的处理器上的cpu数据,该函数没有同步保护!记得上锁

申请释放CPU数据

void *alloc_percpu(type);//宏,分配每个处理器一个指定类型对象的实例
void *__alloc_percpu(size_t size, size_t align);
void free_percpu(const void *);//释放所有处理器上的CPU数据
get_cpu_var(ptr)//alloc函数返回一个指针.该函数返回void类型指针指向ptr的拷贝
put_cpu_var(ptr)//完成,激活内核抢占

使用每个CPU数据的原因:

  • 减少数据锁定。不再需要任何锁,只有这个处理器能访问
  • 大大减少缓存失效。持续不断的缓存失效称为缓存抖动,影响性能。percpu接口缓存-对齐(cache-align)所有数据
    使用每个CPU数据会省去许多(或最小化)数据上锁。唯一的要求是禁止内核抢占,不能在访问每个CPU数据过程中睡眠

总结:分配函数的选择

连续的物理页——低级页分配器或kmalloc() 常用方式
高端内存进行分配——alloc_pages()该函数返回指向struct page结构的指针,而非逻辑地址的指针。之后kmap()映射后获得逻辑地址空间
仅需虚拟地址上连续的页——vmalloc()kmalloc有一定性能损失
创建和撤销很多大的数据结构——slab高速缓存

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值