《深入理解Linux内核》笔记5:内存管理

本文介绍内核如何给自己分配物理内存并管理。对应《深入》第8章。

在《深入》第2章“内存寻址”(或者是我博客中的这篇文章,点这里)中,已经介绍了内核如何给自己分配1G的线性地址的。但是物理内存的分配及管理恐怕更复杂而且更有必要:内存分配和释放的速度(由内存管理的算法决定)影响内核的工作效率。

首先介绍概念:

页框:通俗的说就是物理内存上的单元。请注意与“页”的区别。页有两重意思,不过最多的用法是指线性地址的单元。所以一个是实际物理内存的单元,一个是线性地址/虚拟内存的单元。在大多数情况下,页框大小等于页的大小,为4KB,使得一个页框恰好可以容纳一个页的数据。

(1)为什么要进行内存管理?

内存管理的目的总体上说无非是两点:提高时间效率和提高空间效率。时间效率也就是尽量使寻找到空闲内存、分配和释放这块内存的时间更短。空间效率就是指尽量能找到合适大小的空间,并减少内存空间浪费。

关于空间效率,举个简单的例子:切蛋糕。当我们拿到一块完整的蛋糕的话,如果想每个人都吃到完整的一块,那么我们当然不能没有计划的切块。虽然总量是不变的,但是如果随便乱切,横七竖八,势必导致最后剩下的蛋糕都是碎块,那么后面吃蛋糕的同学必然只能把小块小块的蛋糕凑成一大块来吃。势必很不爽啊。。。

(2)关键数据结构:页描述符

如果需要对一个东西进行管理,那么必须要有负责管理的数据结构,这个数据结构中有各个字段,用来提供不同的管理功能。举例子:比如为了保护内核的内存不被用户进程使用,就必须使用一个标志位;用户进程在读写内存时,首先就要检查这个标志位,然后才能读写。

所以内核使用了“页描述符”这个数据结构,页描述符的类型是page,长度为32字节(是字节哦),所有的页描述符放在一个数组mem_map中。我个人觉得好像应该叫“页框描述符”比较好。

(3)为什么要使用内存管理区?

内存本来在物理上是一个实体,并不分区的。有人会问:这样的一个整体进行处理不是很简洁么?为什么要分区呢?

但是由于以下的两种约束,我们迫不得已给自己增加了负担,将内存分区来管理。

约束一:DMA。这个名词不再解释,请自行google。在进行DMA数据传输时,DMA控制器只能对16M的内存进行寻址,所以被迫无奈,只好将这16M的物理内存固定划分给DMA了。

约束二:线性地址有限,无法直接大的物理内存。所以,超出896M的物理内存必须被区别对待。

所以,最终,物理内存被分配成3个管理区:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。

(4)为什么要使用伙伴系统(Buddy system)算法?

使用伙伴系统的直接目的就是为了防止物理内存“外碎片”的产生。关于外碎片,简而言之就是虽然我们一直分配或者释放连续的内存,但是结果就是我们的内存变得很不连续,充满了大大小小的碎片或者是洞,以至于我们无法再分配足够大的一块连续的内存。(回忆下我们刚才说过的蛋糕的例子)

下面举例说明。假设我们要分配一块256个连续页框的内存。

首先考虑一种不好的分配算法作为对比,例如:将所有的空闲连续页框链在一个链表中,我们在分配空间时选取遇到的第一个大于256个连续页框(这个可能很随机,可能是1024个页框,也可能是512个,也可能恰好是256个;而且我们不能奢望在链表的开头遇到大于256个的连续页框,如果链表很长。。。Orz)。假设我们遇到了空闲的1024个页框。这时候我们面临两个选择:

    (a)把这1024个页框都分配出去。这显然是一种巨大的浪费。

    (b)把这1024个页框切开,将256个连续页框脱离链表,然后链表上剩下了768个连续页框的块。这其实和外碎片没有什么区别(因为链表后面很可能有更适合256的连续页框,比如257个或者258个连续页框),最终会导致这个链表被分割的乱七八糟,以至于再也找不到一块合适的连续空间。

(5)伙伴系统

所以,Linux使用了著名的伙伴系统来处理这个问题。那么下面用实例说明伙伴系统是如何改进的。最后我们再总结一下伙伴系统的思想。

伙伴系统的基础是11个连续页框链表,第一个链表上只存储所有空闲的1个页框,第二个链表上只储存所有空闲的2个连续页框,以此类推。这11个链表将不同大小的连续内存块链在不同的链表上,这首先节省了遍历一个很长的链表的时间,可是使我们直奔最符合需求的连续内存。如果是需要256个连续页框,我们可以直接找到第9个链表,取出一个即可。但又出现了几个问题:(q1)如果我们需要254个连续页框呢?(q2)如果我们已经没有了256个的连续页框,即256个连续页框的链表已经空了呢?

下面介绍如何妥善处理剩余部分,也就是解决问题q1。我们只需要254个连续页框时,我们把这个256个连续页框从链表上切除,但是只分配出去254个页框,剩余的两个连续页框链入属于它的链表,也就是存储所有两个连续页框的链表。这样我们看到,我们一个页框都没有浪费。

有人可能会问,如果需要258个连续页框怎么办(258>256)?这个问题其实与q1等价,但是此时最符合需求的是512个连续页框,但是剩下的部分只是要切割多次。但我们无需证明最后一个页框都不会浪费(因为至少最后都会链到1个页框的链表里)。q2其实与这种情况很类似,仍然要去更大的一级链表中寻找。

那么伙伴系统与开始介绍的那种不好的算法到底有什么本质区别呢?

简而言之,伙伴系统直奔最符合需求的连续内存,然后它妥善的处理了剩余部分。首先我们使用了空间换时间的算法:维护这11个块链表的开销,相对于一个好的内存分配算法根本算不上什么。其次,伙伴系统是类似“贪心”的。它寻找当前认为最好的。最后,它把分配后剩余的部分妥善的移动到它们最应该去的地方。

(6)使用的函数

分配:alloc_pages()/alloc_page()/__alloc_pages()

释放:__free_pages()/free_pages()/free_page()

==============================

下面插入两个概念,内存区(memory area)与内碎片。内存区这个词很容易让人产生误会,它不是指整个内存,而是指连续的任意长度的物理内存区。而内碎片是相对于外碎片讲的,外碎片是以页框为单位的,在页框之外;而内碎片就是在页框内部的碎片。产生内碎片的原因其实与外碎片一致:虽然我们所有的分配和释放都是连续的,但最后结果却是不连续的物理内存。

===============================

(7)为什么要使用slab分配器?

我们先不管slab是什么,先考虑为什么要使用它:它的出现就是为了解决内碎片的问题。

当然,有人肯定会想,使用伙伴系统解决内碎片不是一样么?只不过把单位从页框改为字节。

早期Linux确实是这么做的。但是遇到了一些问题(详见《深入》p.324),以我的理解就是:当分配和释放的粒度变细时,被迫需要考虑更多的因素,比如说需要考虑到数据类型。所以伙伴系统在处理内碎片时效率并不是最好的。

于是采用了具有面向对象思想(考虑到数据类型,就已经是一种面向对象的思想了)的slab系统。

(8)slab系统的思想的。。。呃 猜测

这里,限于篇幅和个人能力有限,就不详细介绍slab系统了。我的理解是:slab系统提供了一种快速查找并分配特定类型的数据(面向对象)的细粒度连续内存空间的方法。因为它使用了下图所示的这种层级结构,让我不由得联想到了数据结构中的B树(B树也是用来快速查找,减少树的层数的,当然也付出了一定的空间复杂度;当然我还不知道我这种联想合理不合理)。

c4a7ab3e06c638ca55e723f3.jpg

(9)slab所使用的函数

分配和释放slab:kmem_cache_alloc()/kmem_cache_free()

分配和释放对象:kmalloc()/kfree()。

======================================

参考资料:

这里有一篇IBM关于slab的技术文章:Linux slab 分配器剖析


转载于:https://www.cnblogs.com/microgrape/archive/2011/05/12/2043909.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/405288.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt 设置指定列数显示

一、需求 将由string生成的ByteArry打印到textEdit中,指定显示的列数。 二、代码 void MainWindow::on_pushButton_convert_clicked() { QString Str ui->textEdit_string->toPlainText(); int Strszie Str.length(); QString Showstr"";…

Objective-C设计模式——单例Singleton(对象创建)

单例 和其它语言的单例产不多,可以说是最简单的一种设计模式了。但是有几个点需要注意下,单例就是一个类只有一个实例。 所以我们要想办法阻止该类产生别的实例,一般语言中都会将构造函数写为private。但是OC中的函数并没有限定符&#xff0c…

基于SSM在线协同过滤汽车推荐销售系统

SSM毕设分享 基于SSM在线协同过滤汽车推荐销售系统 1 项目简介 Hi,各位同学好,这里是郑师兄! 今天向大家分享一个毕业设计项目作品【】 师兄根据实现的难度和等级对项目进行评分(最低0分,满分5分) 难度系数:3分 工作…

Keil中的Code,RO,RW,ZI分别表示什么?

在使用keil开发STM32应用程序时,点击Build后在Build Output窗口中经常会有如下信息:以前一直好奇这几个参数和实际使用的STM32芯片中Flash和SRAM的对应关系,于是上网搜了一圈,做如下总结:这些参数的单位是Byte图中几个…

linux mmap 内存映射 mmap() vs read()/write()/lseek()

From: http://www.perfgeeks.com/?p723 通过strace统计系统调用的时候,经常可以看到mmap()与mmap2()。系统调用mmap()可以将某文件映射至内存(进程空间),如此可以把对文件的操作转为对内存的操作,以此避免更多的lseek()与read()、write()操作…

[react] React组件的构造函数是必须的吗?

[react] React组件的构造函数是必须的吗? 构造函数并不是必须的,对于无状态组件,内部没有维护自己的state,只接收外部传入的props 是不需要声明构造函数的 个人简介 我是歌谣,欢迎和大家一起交流前后端知识。放弃很容易&#x…

为什么dos下的com文件都要org 0100h呢?为什么系统启动时要org 07c00h呢

这是因为 .com 载入内存后的起始偏址就是100h. 前面的100h字节是该程序的PSP 部分. 所以, 为了程序中对地址引用的正确, 必需加上org 100h语句。----------------------------------------------------------------------------------------------------------------…

Qt中的TableWidget初始化表头、行高、选中、自动扩展和接受修改

一、需求 设置QT中的TableWidget样式,初始化表头,行高和颜色,行选中,是否修改,是否自动扩展宽度等。 二、代码 void MainWindow::TimerListInit() {QStringList InfHeader;int columnNum;columnNum 7;ui->table…

JTAG、SWD接口定义

版权声明:本文为博主原创文章,转载请注明。 博客已转到 http://blog.csdn.net/upc_xbt https://blog.csdn.net/u014124220/article/details/50829713Jlink仿真器接口仿真器端口连接目标板备注1. VCCMCU电源VCCVCC2. VCCMCU电源VCCVCC3. TRSTTRSTTest ReS…

[leedcode 215] Kth Largest Element in an Array

Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element. For example,Given [3,2,1,5,6,4] and k 2, return 5. Note: You may assume k is always valid, 1 ≤ k ≤ arrays lengt…

Ubuntu 修改默认的PDF打开方式

Ubuntu自带了可以打开PDF的文档编辑器,如GIMP和文档查看器,但这些自带的文档编辑器并不是很好用,很可能会产生乱码,以至于不方便…… 由于在WIN下对foxit的产品感觉很满意,并且知道它也为linux做了一个PDF查看器&#…

[react] React组件的构造函数有什么作用?

[react] React组件的构造函数有什么作用? 在react的新的写法中,每一个组件都是一个类,这个很符合es6的语法规范,在es6中要想创建一个对象,就要调用相应的构造函数, react的组件渲染有两种情况,第一种情况是…

高级Linux程序设计第五章:进程间通信

From: http://www.cnblogs.com/forfuture1978/archive/2010/04/29/1723417.html 五种进程间通信的方式: 共享内存(shared memory):其允许多个进程通过读写同一块内存地址来相互通信。 内存映射(Mapped memory):其和共享内存相似,…

Qt 给应用程序添加图标

一、需求 给应用程序.exe添加图标。 二、代码 1、资源文件中添加进去main.ico 32X32pt 2、xxxx.pro文件中 添加RC_ICONS main.ico

#pragma pack(push,1)与#pragma pack(1)的区别

这是给编译器用的参数设置,有关结构体字节对齐方式设置, #pragma pack是指定数据在内存中的对齐方式。#pragma pack (n) 作用:C编译器将按照n个字节对齐。#pragma pack () 作用:取消自定义字节对齐方式…

Drainage Ditches - poj 1273(网络流模板)

题意:1是源点,m是汇点,求出来最大流量,没什么好说的就是练习最大流的模板题 ************************************************************** 先用Edmonds-Karp的算法做一下试试吧重边贡献了 1W,要加上所有的重边才算…

sawmill全方位日志分析大师

Sawmill 是一套崭新的集中式(中文)日志报表系统,除了提供收集设备或服务的日志,整合、分析成有效报表外,并能降低企业成本与信息管理人员减少学习报表时间,且能于全球各地上网即可获得IT信息。 集中式 : 集中整合各式设…

[react] React中在哪捕获错误?

[react] React中在哪捕获错误? 在react 15 极其以前的版本中,组件内的UI异常将中断组件内部状态,导致下一次渲染时触发隐藏异常。React并未提供友好的异常捕获和处理方式,一旦发生异常,应用将不能很好的运行。而React 16版本有所…

VC6.0 控件Radio Button的使用

From: http://www.cppblog.com/Lee7/archive/2007/09/13/32152.html 使用方法: 1.建立一个基于对话框的用用程序,在其中加入三个Radio Button,ID分别为: IDC_RADIO1,IDC_RADIO2,IDC_RADIO3 2.控件的初始化: 在对话框类的OnInitDialog中加入代码: …

Qt 实现QT控件中的QLabel显示图片并自适应显示

一、需求 实现QT控件中的QLabel显示图片,并自适应显示。 二、代码 QImage Image; Image.load(":/image/image/logo.jpg"); QPixmap pixmap QPixmap::fromImage(Image); int with ui->label_logo->width(); int height ui->label_logo->…