内存管理

内核把物理页作为内存管理的基本单位。内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址)通常以页为单位进行处理。MMU以页大小为单位来管理系统中的页表。

从虚拟内存的角度看,页就是最小单位。

32位系统:页大小4KB

64位系统:页大小8KB

在支持4KB页大小并有1GB物理内存的机器上。物理内存会被划分为262144个页。

内核用 struct page 结构表示系统中的每一个物理页。

struct page {

page_flags_t flags;   /* 表示页的状态。每一位表示一种状态*/

atomic_t _count;       /* 存放页的引用计数,0代表没有被引用 */

atomic_t _mapcount;

unsigned long private;

strcut address_space *mapping;

pgoff_t index;

struct list_head lru;

void *virtual;    /* 页在虚拟内存中的地址,动态映射物理页 */

}

以下,我们来解释下当中的重要字段。

flags:这个字段用于存放页的状态。这些状态包含页是不是脏的,是不是被锁定在内存中等。 flag 的每一位单独表示一种状态。所以,它至少能够同一时候表示出32种不同的状态。

_count:这个字段存放页的使用计数,也就是这个页被引用了多少次。非常奇怪。技术值变为 -1 时,就说明当前内核并没有引用这一页。于是,在新的分配中就能够使用它,注意,这个字段使用的是 -1 代表未使用,而不是 0 。

virtual:这个字段是页的虚拟地址。

mapping:这个域指向和这个页关联的address_space 对象。

private:这个依据名字就能够看得出,它指向私有数据。

内核通过这种数据结构管理系统中全部的页。由于内核须要知道一个页是否空暇,谁有拥有这个页。拥有者可能是:用户空间进程、动态分配的内核数据、静态内核代码、页快速缓存等等。系统中每个物理页都要分配这样一个结构体,进行内存管理。

因为硬件的限制,内核并不能对全部的页一视同仁。Linux必须处理例如以下两种因为硬件存在缺陷而引起的内存寻址问题:

1)一些硬件仅仅能用某些特定的内存地址来运行DMA(直接内存訪问)。

2)一些体系结构其内存的物理寻址范围比虚拟寻址范围大得多。

这样,就有一些内存不能永久地映射到内核空间上。

因为存在这样的限制,内核把具有相似特性的页划分为不同的区(ZONE):

1)ZONE_DMA——这个区包括的页能用来运行DMA操作。

2)ZONE_NORMAL——这个区包括的都是能正常地映射网页。

3)ZONE_DMA32——同上,只是仅仅能被32位设备訪问

4)ZONE_HIGHMEM——这个区包括“高端内存”,当中的页并能不永久地映射到内核地址空间。

Linux把系统的页划分为区,形成不同的内存池,这样就能够依据用途进行分配。

注意。区的划分没有不论什么物理意义。这仅仅是内核为了管理页而採取的一种逻辑上的分组。用于DMA的内存必须从ZONE_DMA中进行分配。可是一般用途的内存却既能从ZONE_DMA分配,也能从ZONE_NORMAL分配。

获得页

内核提供了一种请求内存的底层机制,并提供了对它进行訪问的几个接口。全部这些接口都以页为单位分配内存。定义于<linux/gfp.h>。

最核心的函数是:

structpage *alloc_pages( unsigned int gfp_mask, unsigned int order );

该函数分配 2order 个连续的物理页,并返回一个指向第一页的 page 结构体指针,假设出错就返回NULL。

void*page_address( struct page *page );

把给定的页转换成它的逻辑地址。假设无须用到 struct page。能够调用:

unsignedlong __get_free_pages( unsigned int gfp_mask, unsigned int order );

这个函数与alloc_pages 作用同样,只是它直接返回所请求的第一个页的逻辑地址。由于页是连续的,因此其它页也会紧随其后。

假设仅仅须要一页,能够用下面两个函数:

structpage *alloc_page( unsigned int gfp_mask );

unsignedlong _get_free_page( unsigned int gfp_mask );

假设须要让返回页的内容全为0,能够使用以下这个函数

unsignedlong get_zeroed_page(unsigned int gfp_mask );

方法

描写叙述

alloc_page(gfp_mask)

仅仅分配一页,返回指向页结构的指针

alloc_pages(gfp_mask, order)

分配 2^order 个页,返回指向第一页页结构的指针

__get_free_page(gfp_mask)

仅仅分配一页,返回指向其逻辑地址的指针

__get_free_pages(gfp_mask, order)

分配 2^order 个页,返回指向第一页逻辑地址的指针

get_zeroed_page(gfp_mask)

仅仅分配一页,让其内容填充为0,返回指向其逻辑地址的指针

当不再须要页时能够使用下面函数来释放它。

void__free_pages( struct page *page, unsigned int order );

voidfree_pages( unsigned long addr, unsigned int order );

voidfree_page( unsigned long addr );

释放页时要慎重,仅仅能释放属于你的页。传递了错误的 struct page 或地址,用了错误的 order 值都可能导致系统崩溃。请记住,内核是全然依赖自己的。

kmalloc()

kmalloc 与 malloc 一族函数很类似,仅仅只是它多了一个 flags 參数。kmalloc在<linux/slab.h>中声明:

void*kmalloc( size_t size, int flags );

这个函数返回一个指向内存块的指针,其内存块至少要有 size 大小。所分配的内存正在物理上是连续的。

在出错时,它返回 NULL。除非没有足够的内存可用。否则内核总能分配成功。

在对 kmalloc 调用之后,你必须检查返回的是不是 NULL,假设是,要适当地处理错误。

在低级页分配函数还是 kmalloc 中,都用到了gfp_mask(分配器标志)。这些标志可分为三类:行为修饰符、区修饰符及类型。

1)行为修饰符表示内核应当怎样分配所需的内存。在某些特定情况下,仅仅能使用某些特定的方法分配内存。

比如。中断处理程序就要求内核在分配内存的过程中不能睡眠(由于中断处理程序不能被又一次调度)。

2)区修饰符指明究竟从哪一区中进行分配。

3)类型标志组合了行为修饰符和区修饰符。将各种可能用到的组合归纳为不同类型。简化了修饰符的使用。

kmalloc 的还有一端就是 kfree,kfree声明于<linux/slab.h>中

voidkfree( const
void *ptr );

kfree 函数释放由 kmalloc分配出来的内存块。

调用 kfree( NULL ) 是安全的。

vmalloc()

vmalloc 的工作方式是类似于 kmalloc。仅仅只是前者分配的内存虚拟地址是连续的,而物理地址则无需连续。这也是用户空间分配函数的工作方式:由malloc()返回的页在进程的虚拟地址空间内是连续的,可是这并不保证他们在物理RAM中也是连续的。kmalloc()函数确保页在物理地址上是连续。vmalloc函数值确保在虚拟地址空间内是连续的。它通过分配非连续的物理内存块,在修订页表,把内存映射到逻辑地址空间的连续区域中,就能做到这点。

大多数情况下,唯独硬件设备须要得到物理地址连续的内存,由于硬件设备存在内存管理单元以外,它根本不理解什么是虚拟地址。虽然只在某些情况下才须要物理上连续的内存块,可是非常多内核都有kmalloc()来获取内存。而不是vmalloc()。这主要出于性能方面的考虑。vmalloc()函数为了把物理上不连续的页转换成虚拟地址空间上连续的页。必须专门建立页表项。糟糕的是,通过vmalloc()获得的页必须一个一个地进行映射。由于这些原因,通常是在为了获得大块内存时。比如当模块被动态插入内核时。就把模块装载到由vmalloc()分配的内存上。

void *vmalloc(unsigned long size)

该函数返回一个指针。指向逻辑上连续的一块内存。其大小至少为size。在错误发生时。函数返回NULL。

函数可能睡眠,因此么不能从中断上下文中进行调用。也不能从其它不同意堵塞的情况下进行调用。

释放通过vfree()函数

void vfree(const void *addr)

slab层

为了便于数据的频繁分配和回收,Linux内核提供了slab层(也就是所谓的slab分配器)。slab分配器扮演了通用数据结构缓存层的角色。

slab层把不同的对象划分为快速缓存。当中每一个快速缓存组中存放的都是不同类型的数据结构对象。比如,一个快速缓存用于存放进程描写叙述符,还有一个快速缓存用于存放i节点。

这些快速缓存又被划分为slab。slab由一个或多个物理上连续的页组成。普通情况下,slab也就只由一页组成。每一个快速缓存能够由多个slab组成。

每一个slab都包括一些对象成员。这里的对象指的是被缓存的数据结构。每一个slab处于三种状态之中的一个:满、部分满或空。当内核的某一部分须要一个对象时。就要由slab分配了,首先考虑的是部分满的slab。假设不存在部分满的slab则去空的slab分配,假设也不存在空的slab。则内核须要申请页又一次分配快速缓存。下图描写叙述了快速缓存、slab及对象之间的关系。来自http://www.cnblogs.com/wang_yb/archive/2013/05/23/3095907.html

整个slab层的原理例如以下:

1.能够在内存中建立各种对象的快速缓存(比方进程描写叙述相关的结构 task_struct
的快速缓存)

2.除了针对特定对象的快速缓存以外,也有通用对象的快速缓存

3.每一个快速缓存中包括多个 slab。slab用于管理缓存的对象

4.slab中包括多个缓存的对象,物理上由一页或多个连续的页组成

每一个快速缓存都是用kmem_cache_s 结构来表示。这个结构包括三个链表 slabs_full。slabs_partial和 slabs_empty。均存放在 kmem_lists 结构内。这些链表包括快速缓存中的全部slab。slab描写叙述符 structslab 用来描写叙述每一个slab:

struct slab {

struct list_head list;         /* 满、部分满或空链表 */

unsigned long colouroff;  /* slab 着色的偏移量   */

void *s_mem;                  /* 在 slab 中的第一个对象 */

unsigned int inuse;          /* 已分配的对象数        */

kmem_bufctl_t tree;         /* 第一个空间对象(假设有的话) */

};

slab分配器的接口

主要有四个

1.  快速缓存的创建
struct kmem_cache * kmem_cache_create (const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *))

2.      从快速缓存中分配对象

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)

3.      释放对象。返回给原先的slab

void kmem_cache_free(struct kmem_cache *cachep, void *objp)

4.快速缓存的销毁

void kmem_cache_destroy(struct kmem_cache *cachep)

slab解决内存碎片

内存碎片存在的方式有两种:a.内部碎片 b.外部碎片

内部碎片的产生:由于全部的内存分配必须起始于可被 4、8
或 16 整除(视处理器体系结构而定)的地址或者由于MMU的分页机制的限制,决定内存分配算法仅能把预定大小的内存块分配给客户。如果当某个客户请求一个 43 字节的内存块时,由于没有适合大小的内存,所以它可能会获得 44字节、48字节等稍大一点的字节,因此由所需大小四舍五入而产生的多余空间就叫内部碎片。

外部碎片的产生:
频繁的分配与回收物理页面会导致大量的、连续且小的页面块夹杂在已分配的页面中间。就会产生外部碎片。

假设有一块一共同拥有100个单位的连续空暇内存空间。范围是0~99。假设你从中申请一块内存,如10个单位,那么申请出来的内存块就为0~9区间。

这时候你继续申请一块内存。比方说5个单位大。第二块得到的内存块就应该为10~14区间。假设你把第一块内存块释放。然后再申请一块大于10个单位的内存块。比方说20个单位。由于刚被释放的内存块不能满足新的请求,所以仅仅能从15開始分配出20个单位的内存块。如今整个内存空间的状态是0~9空暇。10~14被占用。15~24被占用,25~99空暇。当中0~9就是一个内存碎片了。

假设10~14一直被占用,而以后申请的空间都大于10个单位,那么0~9就永远用不上了。变成外部碎片。

解决方法:

slab机制,由于slab预先分配了特定数据结构大小的内存,所以没有内部碎片或者外部碎片。

slab与传统内存管理模式比較:

与传统的内存管理模式相比。 slab 缓存分配器提供了非常多长处。

首先。内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配。slab 缓存分配器通过对类似大小的对象进行缓存而提供这样的功能。从而避免了常见的碎片问题。slab 分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象反复进行初始化。

最后。slab 分配器还能够支持硬件缓存对齐和着色,这防止错误的共享(两个或两个对象虽然位于不同的内存地址,但映射到同样的告诉缓冲行),这能够提高性能。但以添加内存浪费为代价。

在栈上的静态分配

内核栈大小固定。我们在进程时要注意节省栈资源,要控制函数内的局部变量。尽量不要出现大型数组或大型结构体。

尤其对于内核栈,一旦造成溢出,就会影响到内核数据(如thread_info)。所以应当优先考虑动态分配。另外一个进程的内核栈和中断栈是分开的,这样能够减轻内核栈的负担(一个内核栈仅仅占1页或2页)。

高端内存的映射

由于32位的处理器可以寻址达到4GB。一旦这些页被分配。就必须映射到内核的虚拟内存空间上。

高于896MB的全部物理内存的范围大都是高端内存,它不会永久或自己主动的映射到内核虚拟地址空间。

内核地址的虚拟内存大小为1G。当中0-896M的内存与物理内存一一映射,即线性映射。而896MB~1024MB的虚拟内存假设也与物理内存线性映射。那么内核态仅仅能使用1G的物理内存。即使物理内存大于1G(比方4G),这种话就没有充分利用物理内存了。所以内核虚拟内存中的896MB~1024MB与高端内存不会一一映射。详细的映射方式例如以下:

当内核态须要訪问高端物理内存时。在内核虚拟内存空间中的896-1024MB找一段对应大小空暇的逻辑地址空间。借用一会。借用这段逻辑地址空间,建立映射到想要訪问的那段物理内存,暂时用一会,用完后归还。

这样当进程后面又须要訪问其它的高端物理内存时。仍然能够用这段逻辑地址空间。

高端内存的最基本思想:在内核虚拟空间896MB~1024MB的内存中借一段地址空间,建立与高端物理内存的暂时地址映射,用完后释放虚拟空间。达到这段虚拟地址空间能够循环使用,訪问全部物理内存。

高端内存映射有三种方式:



1、映射到“内核动态映射空间”

这样的方式非常easy。由于通过 vmalloc() ,在”内核动态映射空间“申请内存的时候,就可能从高端内存获得页面(參看 vmalloc 的实现),因此说高端内存有可能映射到”内核动态映射空间“ 中。

        2、永久内核映射

        假设是通过alloc_page() 获得了高端内存相应的 page,怎样给它找个线性空间?

内核专门为此留出一块线性空间。从 PKMAP_BASE 到 FIXADDR_START ,用于映射高端内存。在 2.4 内核上,这个地址范围是 4G-8M 到 4G-4M 之间。

这个空间起叫“内核永久映射空间”或者“永久内核映射空间”。

这个空间和其他空间使用相同的页文件夹表,对于内核来说,就是 swapper_pg_dir,对普通进程来说。通过 CR3 寄存器指向。

通常情况下,这个空间是 4M 大小,因此只须要一个页表就可以,内核通过来 pkmap_page_table 寻找这个页表。

        3、暂时映射

当必须创建一个映射而当前的上下文又不能睡眠时。内核提供了暂时映射(也就是原子映射)。有一组保留的映射。他们能够存放新创建的暂时映射。内核能够原子地把高端内存中的一个页映射到某个保留的映射中。因此,暂时映射能够用在不能睡眠的地方,比方中断处理程序中,由于获取映射时绝不会堵塞。

每一个CPU数据

SMP环境下加锁过多的话,会严重影响并行的效率,假设是自旋锁的话。还会浪费其它CPU的运行时间。

所以内核中才有了按CPU分配数据的接口。按CPU分配数据之后。每一个CPU自己的数据不会被其它CPU訪问。尽管浪费了一点内存,可是会使系统更加的简洁高效。

按CPU来分配数据主要有2个长处:

1.最直接的效果就是降低了对数据的锁,提高了系统的性能

2.由于每一个CPU有自己的数据,所以处理器切换时能够大大降低缓存失效的几率。由于假设一个处理器操作某个数据。而这个数据在还有一个处理器的缓存中时,那么存放这个数据的那个处理器必须清理或刷新自己的缓存。持续的缓存失效成为缓存抖动。对系统性能影响非常大。

Linux内核——内存管理的更多相关文章

  1. Linux内核内存管理算法Buddy和Slab: /proc/meminfo、/proc/buddyinfo、/proc/slabinfo

    slabtop cat /proc/slabinfo # name <active_objs> <num_objs> <objsize> <objpersla ...

  2. Linux内核内存管理架构

    内存管理子系统可能是linux内核中最为复杂的一个子系统,其支持的功能需求众多,如页面映射.页面分配.页面回收.页面交换.冷热页面.紧急页面.页面碎片管理.页面缓存.页面统计等,而且对性能也有很高的要 ...

  3. linux内核 内存管理

    以下内容汇总自网络. 在早期的计算机中,程序是直接运行在物理内存上的.换句话说,就是程序在运行的过程中访问的都是物理地址. 如果这个系统只运行一个程序,那么只要这个程序所需的内存不要超过该机器的物理内 ...

  4. Linux内核内存管理子系统分析【转】

    本文转载自:http://blog.csdn.net/coding__madman/article/details/51298718 版权声明:本文为博主原创文章,未经博主允许不得转载. 还是那张熟悉 ...

  5. Linux内核内存管理

    <Linux内核设计与实现>读书笔记(十二)- 内存管理   内核的内存使用不像用户空间那样随意,内核的内存出现错误时也只有靠自己来解决(用户空间的内存错误可以抛给内核来解决). 所有内核 ...

  6. linux内核--内存管理(二)

    一.进程与内存     所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等.不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内 ...

  7. linux内核内存管理(zone_dma zone_normal zone_highmem)

    Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数 ...

  8. Linux内核内存管理-内存访问与缺页中断【转】

    转自:https://yq.aliyun.com/articles/5865 摘要: 简单描述了x86 32位体系结构下Linux内核的用户进程和内核线程的线性地址空间和物理内存的联系,分析了高端内存 ...

  9. linux 内核 内存管理 slub算法 (一) 原理

    http://blog.csdn.net/lukuen/article/details/6935068

随机推荐

  1. leetcode NO.1 两数之和 (python实现)

    来源 https://leetcode-cn.com/problems/two-sum/description/ 题目描述 给定一个整数数组和一个目标值,找出数组中和为目标值的两个数. 你可以假设每个 ...

  2. REDIS基础笔记

    Redis基础笔记 资源链接 简介 简介 安装 五种数据类型及相应命令 1. 字符串类型 2. 散列类型 3. 列表类型 4. 集合类型 5. 有序集合 其他 事务 SORT 生存时间 任务队列 发布 ...

  3. 【转】lightmap

    Shader "Diffuse Lightmap" { Properties { _MainTex ("Texture 1", 2D) = "whit ...

  4. system权限无法访问外挂SD卡

    总结Android应用system权限无法访问SD卡问题 包括android1.6 解决方案http://hi.baidu.com/yunlongchn/item/7d6636d8547317ffca ...

  5. 【转】 Linux中记录终端输出到txt文本文件

    转载: http://blog.csdn.net/tengh/article/details/41823883 一,把命令运行的结果保存到文件当中:用 > 把输出转向就可以了 例子: $ ls ...

  6. The OAuth 2.0 Authorization Framework

      The OAuth 2.0 Authorization Framework Abstract The OAuth 2.0 authorization framework enables a thi ...

  7. 洛谷 P1072 Hankson 的趣味题

    题目描述 Hanks 博士是 BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫 Hankson.现 在,刚刚放学回家的 Hankson 正在思考一个有趣的问题. 今天在课堂上,老师讲 ...

  8. 显示3行,还要省略号(这个属性比较合适WebKit浏览器或移动端(绝大部分是WebKit内核的)浏览器)

    div{ overflow : hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp:; -webkit- ...

  9. flash编程实例源代码下载

    原文发布时间为:2008-08-20 -- 来源于本人的百度文章 [由搬家工具导入] http://library.sx.zj.cn/shgp/ActionScript编程实例详解.rar

  10. 张孝祥JavaScript视频教程flash版

    原文发布时间为:2008-09-16 -- 来源于本人的百度文章 [由搬家工具导入] 之前论坛有人发过RM版的,容量差不多有10G.现在这个是flash版的,只有1.2G共七部分. 第一部分:http ...