文档章节

内存管理

China_OS
 China_OS
发布于 2012/12/08 10:26
字数 2957
阅读 238
收藏 1
点赞 1
评论 0

在内核中分配内存要比在用户空间分配内存复杂的多,接下来学习在内核中如何分配物理内存。


    内核把物理页作为管理内存的基本单位,尽管处理器可以处理的最小单位为字,但是内存管理单元(MMU,管理内存并把虚拟地址转化为物理地址)通常以页为单位进行处理,从虚拟内存角度来看,页就是最小单位。体系结构不同,支持页大小也不同,大多数32位机器支持4KB的页,64位机器支持8KB的页。

    内核中用struct_page()结构体来表示系统中的每个物理页:

struct page {
         unsigned long flags;                                                      
         atomic_t _count;                
         atomic_t _mapcount;          
         unsigned long private;          
         struct address_space *mapping;  
         pgoff_t index;                  
         struct list_head lru;   
         void *virtual;                  
};

    flag存放页状态,flag的每一位可以表示一种状态,所以它至少可以表示32种不同的状态。count存放页的引用计数,这个页被引用了多少次,-1表示该页在内核中没有被引用,可以分配他。内核使用page_count函数分配页。virtual是页的虚拟地址,通常情况下,他就是页在虚拟内存中的地址。

    页面结构和物理页相关,并非与虚拟页相关。内核仅仅使用该数据结构描述在当前物理页中存放的东西,这种数据结构的目的在于描述物理内存本身,而不是描述包含在其中的数据,系统中的每个物理页就要分配这样一个结构体。


    由于硬件限制,内核并不是对所有的页一视同仁,有些页位于内存中特定的物理位置上,由于存在这种限制,所以内核把页划分为不同的区,内核使用具有相似特性的页进行分组。由于硬件缺陷容易引起一下两种问题:
        1 一些硬件只能用某些特定的内存地址来执行DMA
        2 一些体系结构的内存的物理地址寻址范围比虚拟地址寻址范围大很多,这样一来就有一些内存不能永久的映射到内核空间。
       所以,linux使用了四种区:
              ZONE_DMA:这个区的页能用来执行DMA操作
              ZONE_DMA32:和ZONE_DMA类似,不过只能被32位设备访问
              ZONE_NORMAL:这个区包含的都是能正常映射的页
              ZONE_HIFHEM:这个区包含高端内存
    区的实际使用和分布体系结构相关。linux把页划分为区,形成不同的内存池,这样就可以根据用途进行分配了。区的划分没有任何意义,只不过是内核为了管理页而采取的一种逻辑上的分组。某些分配需要从特定的区中获取,而某些分配却可以从多个区中获取页。不是所有的体系结构都定义了全部区,内核中使用struct_zone()表示区。

获得页
    内核提供了一种请求内存的底层机制,提供了对他进行访问的几个接口,所有这些结构都以页为单位进行分配。如下:

           1  struct page * alloc_page(gfp_t gfp_mask,unsingned int order)该函数分配2^order个连续的物理页面,便返回一个指向第一页的结构体指针。
           2  void * page_address(struct page *page)返回一个给定物理页所在的逻辑地址的指针
           3  unsigned long _get_free_page(gfp_t  gfp_mask,unsinged int_order)这个函数域alloc_pages作用相同,不过他返回的是第一个页的逻辑地址。
           4  unsigned long get_zeroed_page(unsigned int gfp_mask) 该函数作用同_get_free_pages一样,不过他把返回的页都填充成0。
           5  void free_pages(unsigned long addr)释放页,由于内核是完全信赖自己的,所以如果参数传入错误可能会导致系统崩溃。

    可以使用以下函数释放他们:


kmalloc()
    kmalloc函数与用户空间的malloc函数类似,只不过他可以获得以字节为单位的一块内核内存。这个函数返回一个指向内存块的指针,所分配的内存在物理上是连续的,如果内存足够可用,内核一般都会分配成功。

kfree()
    kmalloc的另一端就是kfree,他主要是用来释放由kmalloc分配的内存块。如果要释放的内存不是由kmalloc分配的或者已经释放了,则调用kfree会产生严重后果。

vmalloc()
    vmalloc的工作方式类似于kmalloc,只不过前者分配的内存虚拟地址是连续的,而物理地址则无需连续,这也是用户空间分配函数的工作方式。由malloc返回的页在进程的虚拟地址空间是连续的,但并不保证在物理空间是连续的。一般情况下只有物理设备需要得到连续的物理内存,为了性能上的考虑内核使用kmalloc分配内存。

gfp_mask标志
    分配器标志可以分为三类:行为修饰符、区修饰符、类型。行为修饰符表示内核应当如何分配所需的内存。区修饰符表示从哪分配内存。类型标志组合了行为修饰符和区修饰符,将各种可能用到的组合归纳为不同的类型。

slab层
    分配和释放数据结构是内核中最普遍的操作之一,为了便于快速分配和回收,coder一般喜欢创建一些空闲链表,空闲链表包含可以使用的,已经分配好的数据块。需要时只要在其中抓去一个即可,不需要时把他放进空闲链表而不是释放,这就相当于高速缓存。空闲链表的问题是不能全局控制,当内存紧缺时,内核无法让空闲链表释放内存。这时linux使用的slab层来扮演了数据结构缓存层的角色。slab试图在几个基本原则之间寻求一种平衡:
        1  频繁使用的数据结构也会频繁的分配和释放,因此应当缓存他们。
        2  频繁的分配和释放会导致内存碎片,为了避免这种情况,空闲链表会连续的存放。回收的对象可以立即投入下一次分配。
        3  如果分配器知道对象大小、页大小和总的高速缓存的大小,他会做出更明智的选择。
        4  如果让部分缓存专属于单个处理器,那么,分配和释放就可以在不加锁的情况下进行。
        5  对存放进行着色,防止多个对象映射到相同的高速缓存行。

slab层设计  
    slab把不同的对象划分为所谓的高速缓存组,每个高速缓存组存放不同类型的对象,每种对象类型对应一个高速缓存。slab由一个或者多个物理上连续的页组成,一般情况下由一页组成,每个高速缓存可以由多个slab组成,每个slab都包含一些对象成员,这里的对象成员是被缓存的数据结构。每个slab处于三种状态:满、部分满、空。每个高速缓存都使用kmem_cache结构体来表示。这个结构包含三个链表:slabs_full、slabs_partial、slabs_empty,均存放在kmem_list3结构内。这些链表包含高速缓存中的所有slab,slab描述符struct slab用来描述每个slab:

struct slab {
        struct list_head  list;       /*满,部分满或空链表*/
        unsigned long     colouroff;  /*slab着色的偏移量*/
        void              *s_mem;     /*在slab中的第一个对象*/
        unsigned int      inuse;      /*已分配的对象数*/
        kmem_bufctl_t     free;       /*第一个空闲对象*/
};

    slab描述符要么在slab之外进行分配,要么就在slab自身开始的地方。slab分配器可以创建新的slab,通过_get_free_pages低级内核页分配器进行,使用该函数来为高速缓存分配足够的内存。当在内存变得紧缺时,系统试图释放出更多的内存以供使用,或者当高速缓存被显示撤销时会调用kmem_freepages释放内存。

在栈上的静态分配
    用户空间能够承担起非常大的栈,而且栈空间还可以动态增长,而内核栈小而且固定。给每个进程分配一个固定大小的栈,可以减少内存消耗,内核也无需负担太重的栈管理任务。每个进程的内核栈大小依赖于体系结构,历史上每个进程都有两页大小的内核栈。

    在2.6内核中,引入了单页内核栈,当激活该选项时,每个进程的内核栈只有一页。这样的好处是:可以让每个进程减少内存消耗,而且随着机器运行时间的增加,分配两个连续的页越来越困难。总的来说内核栈可以是一页也可以是两页,这取决于编译时的配置。

高端内存的映射
    在高端内存中包含的页不能被永久的映射到内核的地址空间上,因此通过alloc_pages获取的页不可能有逻辑地址,在X86体系上,高于896M的物理内存的范围都是高端内存,他并不会永久的映射到内核地址空间,一旦这些页被分配就必须映射到内核的逻辑地址空间上。

    要映射一个给定的page到内核地址空间,可以使用kmap函数,这个函数在高端内存和低端内存上都可以使用,如果page对应的是低端内存中的页,函数只会单纯的返回该页的虚拟地址,如果page位于高端内存,则会建立一个永久映射,再返回地址,这个函数可以睡眠,因此只能用在进程上下文中,当不需要映射时使用kunmap函数解除映射。当必须映射一个页面而又不能睡眠时,内核提供了临时映射,有一组保留的临时映射,他们可以存放新建的临时映射。例如在中断处理中就会使用临时映射。

每个CPU分配
    支持SMP的现代操作系统使用每个cpu上的数据,对于每个给定的处理器其数据是唯一的。一般来说,每个cpu的数据存放在一个数组中。数组中的每一项对应着系统上一个存在的处理器。2.6内核为了方便创建和操作每个cpu数据,引进了新的操作接口,称作precpu,该接口简化了创建和操作每个淳朴的数据。使用每个cpu数据有不少好处,首先减少了数据锁定,因为按照每个处理器访问每个cpu数据的逻辑,你不要任何锁。这只是一个单纯的编程约定,系统本身并不存在任何措施禁止你从事欺骗活动。第二个好处是每个cpu数据可以大大减少缓存失效,持续不断的缓存失效称为缓存抖动,这对系统性能影响较大。综上,使用每个cpu数据会省去数据上锁,他唯一的要求就是要求禁止内核抢占,这个过程接口会自动帮你完成。每个cpu数据在进程上下文和进程上下文中都很安全,但是不要睡眠,否则,你醒来后可能就在其他cpu上了。



© 著作权归作者所有

共有 人打赏支持
China_OS
粉丝 403
博文 438
码字总数 487778
作品 0
徐汇
技术主管
深入理解Linux内存管理-之-目录导航

转自:https://blog.csdn.net/gatieme/article/details/52384965 1 内存描述 2 页表管理 3 初始化内存管理

zwfgogo
04/20
0
0
#内存管理的艺术# 之 Nginx slab的实现 --- 第一篇“基本布局”

访问这里,获取更多原创内容。 说明:本系列的文章基于Nginx-1.5.0版本代码。 Nginx slab分配器用于管理和分配小于一页的内存申请,但实际上大于一页的内存分配也是统一实现的, 具体代码在c...

nodouble
2016/05/10
615
0
mina中的allocate和directAllocate区别

allocate和directAllocate 区别在于内存的类型,allocate分配的内存在jvm管理范围内,directAllocate分配的内存则不是由jvm管理,可以理解成是类似C++那种分配的内存,大一定会说那由directA...

JavaGG
2009/07/08
344
0
#内存管理的艺术# 之 Nginx slab的实现 --- 第四篇“基于块的内存释放”

访问这里,获取更多原创内容。 说明:本系列的文章基于Nginx-1.5.0版本代码。 本篇开始将涉及到Nginx slab内存管理中与内存释放相关的内容,紧跟上一篇的步伐,趁热打铁,就从“基于块的内存...

nodouble
2016/05/19
197
0
指针的意义和linux的内存回收艺术

linux的内存回收机制设计得简直是一种艺术,精通c语言的不一定不会把c语言玩得导致内存泄漏,精通java的虽然再也不用玩指针了,那也不能完全相信java的内存回收机制,毕竟一个java程序出事了...

晨曦之光
2012/04/10
441
0
Memcached与Redis(三)

3. Memcached和Redis关键技术对比 作为内存数据缓冲系统,Memcached和Redis均具有很高的性能,但是两者在关键实现技术上具有很大差异,这种差异决定了两者具有不同的特点和不同的适用条件。下...

liujing07
06/26
0
0
菜鸟之驱动开发11

在本节中,我们将学习驱动层的内存管理,介绍内存管理中常用的API,并与应用层内管理API相对应。 内核中常用的内存管理API与应用层内存管理API对应关系如下: 内核API 应用层C API 说明 RtlC...

长平狐
2012/08/13
78
0
Transparent Huge Pages相关概念及对mysql的影响

之前在弄tokudb的时候,在centos 6上面,需要执行 echo never > /sys/kernel/mm/redhattransparenthugepage/enabled关闭Transparent Huge Pages,当时未作深究,现在重新翻出来看看。 说Tra...

刘伟
2014/04/25
0
1
操作系统----->>>>>关于段页式内存管理的总结

内存管理的技术主要有: 固定分区内存管理、可变分区内存管理、简单页内存管理、虚拟内存页式管理、简单段内存管理、虚拟段内存管理 这6中技术。其实还有一种是结合了段和页式内存管理的方案...

LinuxCPlusPlus
2016/01/26
465
0
iOS中的内存管理(上)

下列行为都会增加一个app的内存占用: 1、创建一个OC对象; 2、定义一个变量; 3、调用一个函数或者方法。 如果app占用内存过大,系统可能会强制关闭app,造成闪退现象,影响用户体验。如何让...

傲风凌寒
2014/05/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

shell及python脚本方式登录服务器

一、问题 在工作过程中,经常会遇见需要登录服务器,并且因为安全的原因,需要使用交互的方式登录,而且shell、python在工作中也经常用到,并且可以提供交互的功能。都是利用了expect、spawn...

yangjianzhou
11分钟前
0
0
upstream sent too big header while reading...

nginx 报错:1736 upstream sent too big header while reading response header from upstream 1. 一般处理 location ~ \.php$ { #增加下面两句 fastcgi_buffer_size 128k; ......

dubox
22分钟前
0
0
Python解析配置文件模块:ConfigPhaser

import configparser as pa# [SectionA]# a = aa# b = bb# c = cc# [SectionB]# optionint = 1# optionfloat = 1.1# optionstring = string#https://www.cnblogs.com/a......

易野
29分钟前
0
0
Java基础——面向对象

声明:本栏目所使用的素材都是凯哥学堂VIP学员所写,学员有权匿名,对文章有最终解释权;凯哥学堂旨在促进VIP学员互相学习的基础上公开笔记。 Object的方法: clone() Object 克隆 to Strin...

凯哥学堂
31分钟前
0
0
rabbitmq学习记录(八)消息发布确认机制

RabbitMQ服务器崩了导致的消息数据丢失,已经持久化的消息数据我们可以通过消息持久化来预防。但是,如果消息从生产者发送到vhosts过程中出现了问题,持久化消息数据的方案就无效了。 Rabbit...

人觉非常君
35分钟前
0
0
毕业5年,我是怎么成为年薪30W的运维工程师

#转载# 我在大学读的是计算机专业,但大学毕业之后,进入到一家私企进行工作,工作的内容类似于网管,会经常的去修电脑,去做水晶头等内容。刚开始工作,也没想太多,最想的是丰富自己的工作...

Py爱好
42分钟前
1
0
大数据基础知识,大数据学习,涉及的知识点

一、什么是大数据 一种规模大到在获取、存储、管理、分析方面大大超出了传统数据库软件工具能力范围的数据集合,具有海量的数据规模、快速的数据流 转、多样的数据类型和价值密度低四大特征。...

董黎明
57分钟前
0
0
Linux CentOS 7上安装极点五笔

话说几天前在新买的惠普笔记本上成功地安装了Linux CentOS 7操作系统、Nvidia Quandro P600驱动程序及X Window,并在VMware下安装Red Hat教学环境,彻底跳出Windows的苦海,但仍然有一件事不...

大别阿郎
今天
17
0
2018年7月20日集群课程

一、集群介绍 集群,简单地说是指一组(若干个)相互独立的计算机,利用高速通信网络组成一个较大的计算机服务系统,每个集群节点(即集群中的每台计算机)都是运行各自服务的独立服务器。 ...

人在艹木中
今天
0
0
spark开发机中调试snappy

目的 在Idea中的点击运行,使spark可以直接读取snappy 自己编译hadoop,以支持snappy的压缩。 自己编译的目的就是要得到支持snappy文件读写的动态链接库。如果可以在网上下载,可以跳过自行编...

benny周
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部