Linux内核设计笔记12——内存管理
内存管理学习笔记
页
页是内核管理内存的基本单位,内存管理单元(MMU,管理内存并把虚拟地址转化为物理地址的硬件)通常以页为单位进行处理,从虚拟内存的角度看,页就是最小单位。
struct page{
unsigned long flags;
atomic_t _count;
atomic_t _mapcoount;
unsigned long private;
struct address_space *mapping;
pgoff_t index;
struct list_head lru;
void *virtual
}
- flag成员:页的状态,比如页是不是脏的、是不是被锁在内存中。flag的每一位单独表示一种状态,也就是说最少可以表示32中状态(这取决于flag有多少位)
- _count成员:存放页的引用计数,内核通过page_count()函数对该成员进行检查,若函数返回0则表示页空闲,若返回一个正整数则表示页正咋被使用。
- virtual成员:是页的虚拟地址,通常情况下他就是页在虚拟内存中的地址,有些内存(比如 High Memory)并不会永久的映射到内核地址空间上,这事virtual成员的值为NULL
- 系统中每个页都被分配一个这样的结构体
区
由于硬件的限制,内核并不能对所有的页一视同仁,比如有的硬件只能在特定的地址上执行DMA操作,有的结构其物理地址寻址范围大于虚拟地址寻址范围,导致部分内存不能永久性映射到内核空间上。
Linux主要使用了四个区将页分为不同的类型
- ZONE_DMA:这个区包含用来执行DMA操作的页
- ZONE_DMA32:这个区与ZONE_DMA类似负责执行DMA操作,但是该区内的页只能被32位设备访问。
- ZONE_NORMAL:可以正常映射的页
- ZONE_HIGHMEM:这个区包含哪些不能被永久映射到内核地址空间的页。
分区是与体系结构相关的,有的体系结构中在所有的内存上执行DMA操作都没问题,那么ZONE_DMA就为空。同样,有的体系结构中不存在High Memory,即所有的内存都可以映射到内核空间上。
- 在x86结构上,ISA设备不能在整个32位地址空间中执行DMA操作,ISA只能使用地址空间的前24位,即16M地址空间,所以ZONE_DMA在x86上包含的页都在0-16M的范围内。
- 在32位X86系统上,虚拟地址分为两部分,低地址开始的3G(0x0000 0000-0xC000 000)属于用户空间,高地址的1G(0xC000 0000 - 0xFFFF FFFF)属于内核空间。
- 对于内核空间来说,较低的896M(0xC000 0000 - 0xF7FF FFFF)可以直接映射到内核空间的物理地址上,剩下的128M(0xF800 0000 - 0xFFFF FFFF)根据需求映射成高地址。
![](http://images2017.cnblogs.com/blog/1146526/201708/1146526-20170811105255370-1774256994.png)
某些分配必须从特定区中获取页,比如用于DMA的内存必须从ZONE_DMA区获取页,但是一般用途的内存可以从任何区内获取。需要注意的是,内存获取不能跨区进行,也就是说不能从两个区内获取页。
获取页
获取页
struct page * alloc_pages(gfp_t gfp_mask, unsigned int order);
// 返回page指针,该函数分配$2^order$个连续的物理页
void* page_address(struct page *page);
// 该函数返回当前所在的逻辑地址。
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
// 该返回获取 $2^order$个页,并返回第一个页的逻辑地址
struct page * alloc_page(gfp_t gfp_mask);
// 分配一个页,返回page指针
unsigned long __get_free_page(gfp_t gfp_mask);
// 分配一个页,返回页的逻辑地址
unsigned long get_zeroed_page(unsigned int gfp_mask);
// 获取填充为0的页
释放页
void __free_pages(struct page* page, unsigned int order);
void free_pages(unsigned long addr, unsigned int order);
void free_page(unsigned long addr);
unsigned long page;
page = __get_free_pages(GFP_KERNEL, 3);
if(!page){
return -ENOMEM;
}
// ....
// ....
free_pages(page,3);
gfp_mask标志
分配器标志可以分为三类:行为修饰符、区修饰符、类型。用于指定获得内存时的方式,包括怎么获取,从哪里获取等行为。
最常用的标志是GFP_KERNEL,这种分配可能会阻塞,该标志只能用在可以重新安排调度的进程上下文中(未被锁持有)。
GFP_ATOMIC标志表示不能睡眠的内存分配,如果当前的代码不能睡眠(如中断、软中断、tasklet等)那么可以用该标志获取内存。
GFP_NOIO表示分配内存时不会启动磁盘IO来帮助满足请求。
GFP_NOFS 表示它可能会启动磁盘IO,但是不会启动文件系统IO。
在绝大多数代码中用到的标志要么是GFP_KERNEL,要么是GFP_ATOMIC。
- 进程上下文,可以睡眠 => GFP_KERNEL
- 进程上下文,不可以睡眠 => GFP_ATOMIC
- 中断、软中断、tasklet => GFP_ATOMIC
- 用于DMA,可睡眠 => GFP_DMA|GFP_KERNEL
- 用于DMA, 不可睡眠 => GFP_DMA|GFP_ATOMIC
kmalloc() and vmalloc()
kmalloc()函数用来获得指定字节数的连续内存,其函数声明如下:
void * kmalloc(size_t size, gfp_t flags);
// 该函数返回一个指向内存块的指针,其内存块至少为size字节大小
// 新分配的内存区域在物理上是连续的。
void kfree(const void* ptr);
// 释放不需要的内存
vmolloc()函数类似于kmalloc(),不过该函数分配的内存在虚拟地址上是连续,而在物理地址上不连续。
void * vmalloc(unsigned long size);
// 分配size字节大小的内存,在逻辑上连续,物理地址不一定连续
void vfree(const void * addr);
// 释放不需要的虚拟内存
由molloc()分配的内存在进程的虚拟地址上也是连续的,但是不能保证其在物理地址上连续。
大多数情况下,只由硬件需要得到物理地址连续的内存,因为硬件设备一般存在于内存管理单元之外,它根本不知道啥是虚拟内存。对于内核而言,所有的内存看起来都是逻辑上连续的。
尽管某些情况下才需要物理上连续的内存,但是内核一般是用kmalloc()函数的,因为vmalloc()为了把不连续的物理地址映射为连续的虚拟地址,还需要做额外的工作。只有在获得大块内存时才会用vmalloc()函数来分配。
slab层
为了便于数据频繁的分配和回收,编程人员常会用到空闲链表作为缓存,空闲链表中包含已经分配好的数据结构块,可以直接获得,节省了分配内存的步骤。
Linux内核提供了slab层,slab分配器扮演了通用数据结构缓存层的角色。
每种类型的对象都对应一个高速缓存,每个高速缓存被分为不同的slab,slab由一个或多个物理上连续的页组成。一般情况下,每个slab由一个页组成。每个slab都包含一些对象成员(被缓存的数据结构)。每个slab处于三种状态的一种:满,空,部分。一个满的slab没有空闲对象,都被占用。空的slab则所有对象都是空闲的。
高速缓存使用kmem_cache结构表示,其包含三个链表:slabs_full, slabs_partial, slabs_empty,这三个链表均存放于kmem_list3结构内。
struct slab{
struct list_head list; // 满、空或部分满的链表
unsigned long colouroff; // slab着色偏移量
void *s_mem; // slab中的第一个对象
unsigned int inuse; // slab中已经分配的对象数
kmem_bufctl_t free; // 第一个空闲对象
}
slab描述符要么在slab之外另行分配,要么放下slab自身开始的地方。如果slab很小,或者slab内部有足够空间容纳slab描述符,那么描述符就放在slab里面。
slab分配器接口
一个新的高速缓存通过kmem_cache_create()函数类创建
struct kmem_cache * kmem_cache_create( const char *name,
size_t size,
size_t align,
unsigned long flags,
void (*ctor) (void*));
第一个参数是字符串,存放缓存的名字,第二个变量使缓存中每个元素的大小,第三个参数是slab内第一个对象的偏移,确保页内的特定对齐,flags表示特定的标志。
- SLAB_HWCACHE_ALIGN: 把一个slab内的所有对象按高速缓存行对齐
- SLAB_POISON: 使slab用已知的值(a5a5a5a5)填充slab
- SLAB_RED_ZONE: 这个标志导致slab层在已分配内存的周围插入红色警戒区。
- SLAB_PANIC: 这个标志当分配失败时提醒slab层。
- SLAB_CACHE_DMA: 这个标志命令slab层使用可以执行DMA的内存给slab分配空间。
最后一个参数ctor是构造函数,只有在新的页被追加到高速缓存时,这个函数才会被调用。实际上Linux内核的二高速缓存不使用构造函数。
要撤销一个高速缓存使用 kmem_cache_destroy(),调用该函数时要保证高速缓存中的所有页都是空,而且在调用该函数的过程中不能在访问该高速缓存。
int kmem_cache_destroy(struct kmem_cache *cachep)
缓存中分配与释放
void * kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);
void kmem_cache_free(struct kmem_cache * cachep, void *objp);
// slab分配器的使用
// 首先定义一个全局变量,存放指向高速缓存的指针
struct kmem_cache * task_struct_cachep;
// 在内核初始化期间,创建高速缓存
task_struct_cachep = kmem_cache_create("task_struct",
sizeof(struct task_struct),
ARCH_MIN_TASKSTRUCT,
SLAB_PANIC | SLAB_NOTRACK,
NULL);
//创建新的进程描述符
struct task_struct *tsk;
tsk = kmem_cache_alloc(task_struct_cachep, GFP_KERNEL);
if(!tsk)
//分配失败
return NULL;
//进程执行完后,释放缓存
kmem_cache_free(task_struct_cachep, tsk);
// 最后如果有需要的话,可以撤销该高速缓存
int err;
err = kmem_cache_destroy(task_struct_cachep);
if(err)
//...
;
Linux内核设计笔记12——内存管理的更多相关文章
- linux kernel学习笔记-5内存管理_转
void * kmalloc(size_t size, gfp_t gfp_mask); kmalloc()第一个参数是要分配的块的大小,第一个参数为分配标志,用于控制kmalloc()的行为. km ...
- Linux内核学习笔记-2.进程管理
原创文章,转载请注明:Linux内核学习笔记-2.进程管理) By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- Linux内核设计笔记10——内核同步
Linux内核同步笔记 几个基本概念 - 临界区(critical region):访问和操作共享数据的代码段: - 原子操作:操作在执行中不被打断,要么不执行,要么执行完: - 竞争条件: 两个线程 ...
- Linux内核入门到放弃-内存管理-《深入Linux内核架构》笔记
概述 内存管理的实现涵盖了许多领域: 内存中的物理内存页管理 分配大块内存的伙伴系统 分配较小内存块的slab.slub和slob分配器 分配非连续内存块的vmalloc机制 进程的地址空间 在IA- ...
- 分析linux内核中的slub内存管理算法
1. 分析的linux内核源码版本为4.18.0 2. 与slub相关的内核配置项为CONFIG_SLUB 3. 一切都从一个结构体数组kmalloc_caches开始,它的原型如下: ] __ro_ ...
- Linux内核设计笔记7——中断
中断与中断处理 何为中断? 一种由设备发向处理器的电信号 中断不与处理器时钟同步,随时可以发生,内核随时可能因为中断到来而被打断. 每一个中断都有唯一一个数字标志,称之为中断线(IRQ) 异常是由软件 ...
- linux内核(四)内存管理单元MMU
1,基本概念 一个程序运行时没必要全部都同时装入内存,只需要把当前需要运行的部分装入内存即可,这样就使得一个大程序可以在较小的内存中运行,也使得内存中可以同时装入更多的程序并发执行,从用户角度看,该系 ...
- Linux内核设计笔记14——块I/O层
块I/O层 基本概念 系统中可以随机访问固定大小数据片的硬件设备称做块设备,这些固定大小的数据片称之为块.还有一种基本的设备称之为字符设备,其需要按照顺序访问,比如键盘. 扇区:块设备中最小的寻址单元 ...
- Linux内核设计笔记13——虚拟文件系统
虚拟文件系统 内核在它的底层文件系统系统接口上建立一个抽象层,该抽象层使Linux可以支持各种文件系统,即便他们在功能和行为上存在很大差异. VFS抽象层定义了各个文件系统都支持的基本的.概念上的接口 ...
随机推荐
- OC录制小视频
OC录制小视频 用 AVCaptureSession + AVCaptureMovieFileOutput 来录制视频,并通过AVAssetExportSeeion 手段来压缩视频并转换为 MP4 格 ...
- HDU Ellipse(simpson积分)
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission( ...
- jQuery获取Select option 选择的Text和 Value
获取一组radio被选中项的值:var item = $('input[name=items][checked]').val();获取select被选中项的文本var item = $("s ...
- 抓包之Charles For Mac 4.0+破解版
前言:突然间发现好久没有写博客了,最近被公司的项目弄得脑壳疼
- 集合之HashMap、Hashtable
HashMap 基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 Hashtabl ...
- 关于org.apache.hadoop.io.nativeio.NativeIO$Windows.access0(Ljava/lang/String;I)Z报错
之前一直出现这个错误,使用的开发工具是IDEA 我感觉似乎是hadoop与windows的操作系统不太适合 于是在project创建 org.apache.hadoop.io.nativeio包,将N ...
- Spark运行模式_local(本地模式)
本地运行模式 (单机) 该模式被称为Local[N]模式,是用单机的多个线程来模拟Spark分布式计算,直接运行在本地,便于调试,通常用来验证开发出来的应用程序逻辑上有没有问题. 其中N代表可以使用N ...
- 动态链接库函数内的静态变量,奇妙的UNIQUE Bind
title: 动态链接库函数内的静态变量,奇妙的UNIQUE Bind date: 2018-09-28 09:28:22 tags: --- 介绍 模板函数和内敛函数中的静态变量,在跨so中的表现, ...
- Python学习:6.python内置函数
Python内置函数 python内置函数,是随着python解释器运行而创建的函数,不需要重新定义,可以直接调用,那python的内置函数有哪些呢,接下来我们就了解一下python的内置函数,这些内 ...
- 苏州Uber优步司机奖励政策(12月14日到12月20日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...