内存管理学习笔记

页是内核管理内存的基本单位,内存管理单元(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——内存管理的更多相关文章

  1. linux kernel学习笔记-5内存管理_转

    void * kmalloc(size_t size, gfp_t gfp_mask); kmalloc()第一个参数是要分配的块的大小,第一个参数为分配标志,用于控制kmalloc()的行为. km ...

  2. Linux内核学习笔记-2.进程管理

    原创文章,转载请注明:Linux内核学习笔记-2.进程管理) By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...

  3. Linux内核设计笔记10——内核同步

    Linux内核同步笔记 几个基本概念 - 临界区(critical region):访问和操作共享数据的代码段: - 原子操作:操作在执行中不被打断,要么不执行,要么执行完: - 竞争条件: 两个线程 ...

  4. Linux内核入门到放弃-内存管理-《深入Linux内核架构》笔记

    概述 内存管理的实现涵盖了许多领域: 内存中的物理内存页管理 分配大块内存的伙伴系统 分配较小内存块的slab.slub和slob分配器 分配非连续内存块的vmalloc机制 进程的地址空间 在IA- ...

  5. 分析linux内核中的slub内存管理算法

    1. 分析的linux内核源码版本为4.18.0 2. 与slub相关的内核配置项为CONFIG_SLUB 3. 一切都从一个结构体数组kmalloc_caches开始,它的原型如下: ] __ro_ ...

  6. Linux内核设计笔记7——中断

    中断与中断处理 何为中断? 一种由设备发向处理器的电信号 中断不与处理器时钟同步,随时可以发生,内核随时可能因为中断到来而被打断. 每一个中断都有唯一一个数字标志,称之为中断线(IRQ) 异常是由软件 ...

  7. linux内核(四)内存管理单元MMU

    1,基本概念 一个程序运行时没必要全部都同时装入内存,只需要把当前需要运行的部分装入内存即可,这样就使得一个大程序可以在较小的内存中运行,也使得内存中可以同时装入更多的程序并发执行,从用户角度看,该系 ...

  8. Linux内核设计笔记14——块I/O层

    块I/O层 基本概念 系统中可以随机访问固定大小数据片的硬件设备称做块设备,这些固定大小的数据片称之为块.还有一种基本的设备称之为字符设备,其需要按照顺序访问,比如键盘. 扇区:块设备中最小的寻址单元 ...

  9. Linux内核设计笔记13——虚拟文件系统

    虚拟文件系统 内核在它的底层文件系统系统接口上建立一个抽象层,该抽象层使Linux可以支持各种文件系统,即便他们在功能和行为上存在很大差异. VFS抽象层定义了各个文件系统都支持的基本的.概念上的接口 ...

随机推荐

  1. Dynamic Ambient Occlusion and Indirect Lighting

    This sample was presented on the Nvida witesite, which detail a new idea to calculate the ambient oc ...

  2. Zabbix——部署(agent和proxy安装)

    前提条件: 已经完成对Zabbix-server的安装 已经完成对Mysql的安装 并且相互和正常使用和访问 配置agent服务器: rpm -Uvh https://repo.zabbix.com/ ...

  3. centos7.3上编译安装percona5.7.18

    一,删除操作系统自带mariadb yum remove mariadb 二,下载需要的安装包 percona-toolkit-3.0.3-1.el7.x86_64.rpm boost_1_59_0. ...

  4. echarts 多图任意布局案例

    随着文档的更新,布局越来越个性化,完全可以替代整个元素界面,随意布局展示数据,下面发布一个小的文件 源码文件:https://files.cnblogs.com/files/mobeisanghai/ ...

  5. PHP中使用foreach时加&符号的用法

    foreach时加&符号:遍历的同时改变原数组即修改数据或者增加数据. $arr = ['a', 'b', 'c']; foreach ($arr as $key => &$va ...

  6. Hive(9)-自定义函数

    一. 自定义函数分类 当Hive提供的内置函数无法满足你的业务处理需要时,此时就可以考虑使用用户自定义函数. 根据用户自定义函数类别分为以下三种: 1. UDF(User-Defined-Functi ...

  7. C语言中结构体的访问方法解读

    在C语言中,对结构体的访问一般有两种常规方式:"."访问和"->"访问.那么两者有什么区别呢?对C语言有一定了解的同学应该知道,我们新建一个结构体的时候, ...

  8. Quartus II 项目文件分类及内容

  9. Android零碎知识点

    1.android:foreground="?attr/selectableItemBackground"   ###设置水波纹效果 2.android:contentDescri ...

  10. dfs 队列

    题目来源  poj 1562 Description The GeoSurvComp geologic survey company is responsible for detecting unde ...