第12章 内存管理

第12章 内存管理

内核空间分配空间比较复杂,但是从程序开发角度来说,只是和用户空间中内存分配不太一样而已。

内核把物理页作为内存管理的基本单元。
内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址的硬件)也通常以页为单位来处理。
内核用struct page结构表示系统中的每个物理页,位于<linux/mm_types.h>,简化定义如下:

struct page
{
	/* 页的状态,脏的,锁定在内存中,每一位一个状态,位于<linux/page-flags.h> */
	unsigned long flags;
	/* 页的引用计数,被使用了多少次,-1表示没被引用,空闲,使用page_count进行检查 */
	atomic_t _count;
	atomic_t _map_count;
	unsigned long private;
	struct address_sapce *mapping;
	pgoff_t index;
	struct list_head lru;
	/* 页的虚拟地址 */
	void *virtual;
};

page结构和物理页相关,而并非和虚拟页先关。

每个页都需要一个结构体来进行管理,其实消耗掉的内存是很小的一部分。

由于硬件的限制,内核并不能对所有的页一视同仁,有些页位于内存中特定的物理地址上,不能将其用于一些特定的任务。
ZONE_DMA
ZONE_DMA32
ZONW_NORMAL
ZONE_HIGHMEM
区的实际使用和体系结构相关的。
Linux把系统的页划分为区,形成不同的内存池,这样就可以根据用途进行分配了。
区的划分没有任何物理意义,是内核为了管理页而采取的一种逻辑上的分组。

获得页

以页为单位进行获取,<linux/gfp.h>。

/* 申请2^order(1<<order)个连续的物理页 */
struct page* alloc_page(gfp_t gfp_mask,unsigned int order);
/* 将页转换成逻辑地址 */
void* page_address(struct page *page);
/* 直接获得逻辑地址 */
unsigned long __get_free_pages(gfp_t gfp_mask,unsigned int order);
/* 只需要一个页 */
struct page* alloc_page(gfp_t gfp_mask);
unsigned long __get_free_page(gfp_t gfp_mask);
/* 页被填充为零,信息可能并不是完全随机的,有可能包含敏感数据。用户空间的页在返回之前,所有数据必须填充为0 */
unsigned long get_zerod_page(unsigned int gfp_mask);

释放页

__free_pages();
free_pages();
free_page();

只能释放属于自己的页。

kmalloc()

获得一块以字节为单位的内核内存。

gfp_mask标志

可分为三类。
行为修饰符,如何分配所需内存,如能否睡眠。
区修饰符,从哪分配内存。
类型标志,前面两种的组合。

GFP_KERNEL
GFP_ATOMIC

kfree()

释放kmalloc申请的内存。

vmalloc()

只确保页在逻辑地址空间内是连续的,也是用户空间malloc的工作方式。而kmalloc在物理上是连续的(虚拟地址因此也是连续的)。
通过修改页表实现。为了将不连续的物理页转换为虚拟地址空间上连续的页,必须建立页表项,必须一个个进行映射,TLB抖动较大。
仅在不得已时候用,比如为了获得大块内存的时候使用。当模块动态插入到内核时,就把模块装载到vmalloc分配的内存上。
vfree释放。

slab层

空闲链表包含可以使用的、已经分配好的数据结构块。需要的时候从里面取,不需要的时候放进去。减少申请和释放的次数,对象高速缓存。但是不能全局控制。

slab分配器扮演了通用数据结构缓存层的角色。

slab层的设计

slab层把不同的对象划分为高速缓存组,其中每个高速缓存组存放不同类型的对象。进程描述符,索引节点对象。kmalloc建立在slab之上,使用了一组通用高速缓存。

slab由一个或多个物理上连续的页组成。三个状态,满、部分满、空,分配顺序部分满 优先,空其次。没有空的话,需要创建一个slab。可以减少碎片内存。
inode结构。
高速缓存用kmem_cache结构表示。
创建新的slab是通过__get_free_pages()实现的。

slab分配器的接口

创建

/* 名字,每个元素的大小,第一个对象的偏移(用于对齐) */
struct kmem_cache* kmem_cache_create(const char *name,
	size_t size,
	size_t align,
	unsigned long flags,
	void (*ctor)(void*));

撤销

int kmem_cache_destory(struct kmem_cache *cachep);
  1. 从缓冲中分配
/* 获取对象 */
void kmem_cache_allockmem_cache *cachep,gfp_t flags);
/* 释放对象 */
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
  1. 实例
    进程描述符,kernel/fork.c。
    一个指向高速缓存的全局变量指针,fork_init()中会创建高速缓存,元素大小为sizeof(struct task_struct)。每次fork时,从高速缓存中获取,进程结束在将其释放。
    进程描述符是内核核心组成部分,不应该也不能去撤销。
    slab层负责内存紧缺情况下所有底层的对齐、着色、分配、释放和回收等。如果需要频繁创建很多相同类型的对象,应该使用slab高速缓存,不要自己去实现空闲链表。

在栈上的静态分配

与用户态不同,内核栈小而且固定。
历史上,每个进程都有两页的内核栈。

单页内核栈

让每个进程减少内存消耗。
随着时间进行,内存碎片化,寻找两个连续的页比较困难。
中断处理程序和中断进程共享一个内核栈。为了优化,添加了中断栈。

在栈上光明正大的工作

在任意一个函数中,必须尽量节省占空间。在具体的函数中让所有局部变量所占空间不要超过几百字节。在栈上进行大量静态分配时很危险的。

高端内存的映射

在高端内存中的页不能永久地映射到内核地址空间上。

永久映射

void *kmap(struct page *page);
void kunmap(struct page *page);

高端或者低端都可以用。可以睡眠,只能用在进程上下文中。
永久映射的数量是有限的。

临时映射

必须创建一个映射而且不能睡眠时,使用临时映射(原子映射)。有一组保留的映射,可以存放新创建的临时映射。

void* kmap_atomic(struct page *page,enum km_type tpye);
void kunmap_atomic(void* kvaddr,enum km_type type);

每个CPU的分配

一般来说,每个CPU的数据存放在一个数组中。数组中的每一项对应着系统上一个存在的处理器。

get_cpu();
put_cpu();

新的每个CPU接口

percpu();

编译时的每个CPU数据

静态创建

DEFINE_PER_CPU(type,name);
DECLARE_PER_CPU(type,name);
get_cpu_var(name)++;
put_cpu_var(name);
per_cpu(name,cpu)++;

运行时每个CPU数据

动态,类似于kmalloc()。

alloc_percpu();
__alloc_percpu();
free_percpu();

使用每个CPU数据的原因

减少了数据锁定,每个处理器访问特定位置的数据。
减少缓存失效。
安全要求是禁止内核抢占,比上锁代价下。在中断和进程上下文中都很安全,但是过程中不能睡眠(挂起),因为唤醒后可能在其他cpu上。

分配函数的选择

连续的物理页,kmalloc。
高端内存,alloc_pages()+kmap()。
仅仅需要虚拟地址上连续的页,vmalloc()。
创建和撤销很多大的数据结构,slab高速缓存。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值