初探Linux内核中的内存管理
Linux内核设计与实现之内存管理的读书笔记
初探Linux内核管理
- 内核本身不像用户空间那样奢侈的使用内存;
- 内核不支持简单快捷的内存分配机制, 用户空间支持? 这种简单快捷的内存分配机制是什么呢?
- 内核不能睡眠;
- 内核空间和用户空间分配内存是不一样的, 差一点在哪里呢?
- 内核是如何管理内存?
- 内核把物理页作为内存管理的基本单位;
- 因为内存管理单元通常以页为单位进行处理;
- 从内存管理单元的角度来看, 页是最小的单位;
- 什么是内存管理单元(MMU) -- 就是把虚拟地址转换为物理地址的硬件; 那什么是虚拟地址呢?
- 体系结构不同, 页的大小也可能不同; 体系结构不同代表的含义可能是芯片的架构不一样, 32位机, 64位机;
- 虚拟内存和物理内存有什么区别?
- 内核把物理页作为内存管理的基本单位;
- 高端内存并不永久的映射到内核地址空间上, 什么是高端内存?
- 内核用page数据结构来描述当前时刻在相关物理内存中存放的东西;
- page结构体的目的在于描述物理内存本身, 而不是描述包含其中的数据;
- 内核用page结构管理所有的页, 内核需要知道每一个页是否空闲, 如果已经被分配, 内核需要知道谁拥有这个页;
- 拥有者可能是用户空间进程, 动态分配的内核数据, 高速缓存等;
- 每一个物理页都对应一个page结构体
- 区的概念?
- 内核不能对所有的页一视同仁, 一些页位于特定的物理内存上, 用于特定的任务;
- 内核会把页分成区; 内核使用区对相似的页进行分组;
- 内核必须处理两种情况(由硬件缺陷引起的)
- 一些硬件只能使用某些特定的内存来执行DMA(直接内存访问);
- 一些体系结构的内存的物理寻址范围比虚拟寻址范围大得多, 有一些内存就不能永久的映射到内核空间上;
- 四种不同的分区
- ZONE_DMA
- ZONE_DMA32
- ZONE_NORMAL
- ZONE_HIGHEM -- 高端内存中的页不能永久的映射到内核空间中, 32位体系结构中有用;
- 其他体系结构上, 所有内存都被直接映射;
- 区的实际分布和使用和体系结构相关
- Linux把系统的页划分为区, 形成不同的内存池, 根据不同的用途进行分配;
- 区的划分没有任何物理意义, 只是内核为了管理页而采取的一种逻辑上的分组;
- 内核不能对所有的页一视同仁, 一些页位于特定的物理内存上, 用于特定的任务;
- 64位机上的zone结构体有一个自旋锁, 它防止该结构体被并发访问;
- 内核提供了一种请求内存的底层机制, 并提供了对它的访问接口;
- alloc_pages函数分配2^order个连续的物理页, 返回的指针指向第一个页的page结构体;
- page_address把页转换为逻辑地址, 返回当前页的逻辑地址;
- __get_free_pages直接返回的是所请求的第一个页的物理地址, 应该封装了alloc_pages和page_address函数;
- 如果分配的页是给用户空间的, 调用get_zeroed_page来分配全为0的页;
- 释放页, 只能释放属于自己的页;
- __free_pages, free_pages, free_page
- 内核是完全相信自己的;
- 如果以字节为单位分配内存, 直接调用内核函数kmalloc就可以了, 返回的是连续的物理地址;
- kfree释放由kmalloc分配出来的内存块;
- 注意的是调用kfree(NULL)是安全的;
- 分配器标志-gfp_mask标志
- 行为修饰符 -- 内核应该如何分配所需的内存
- 区修饰符 -- 表示从哪里分配内存
- 类型 -- 指定所需的行为和区描述符以完成特殊类型的处理;
- 内核最常用的标志是GFP_KERNEL, 这种分配会引起睡眠, 它使用的是普通优先级(在没有锁被持有等情况下使用);
- vmalloc也是以字节为单位分配内存, 但它与kmalloc不同的是, 分配的内存虚拟地址是连续的, 物理地址不一定是连续的;
- 和malloc类似的, malloc返回的页在进程的虚拟地址空间内是连续的, 但不能保证在物理RAM中也是连续的;
- 只有硬件设备需要得到物理空间连续的地址;
- 对内存而言, 所有的内存看起来都是逻辑上连续的;
- 出于性能考虑, 内核很多代码都是直接调用kmalloc来获得内存, 而不是调用vmalloc;
- vmalloc为了把物理上不连续的页转换为虚拟空间中连续的页, 必须建立专门的页表;
- 更糟糕的是vmalloc获得页需要一个一个地进行映射;
- 导致TLB(一种硬缓冲区)抖动;
- 什么时候用vmalloc, 获得大内存块时, 模块动态地插入到内核中, 把模块装载到由vmalloc分配的内存上;
- vmalloc函数可能会睡眠, 不能在中断上下文进行调用;
- slab 层(slab分配器, )
- slab层是为了便于数据的频繁分配和回收;
- 空闲链表包含可用的, 已经分配好的数据结构体块, 代码直接从空闲链表中获得一个数据块, 不用重新分配, 不用了再放回空闲链表;
- 空闲链表相当于对象高速缓存 -- 快速存储频繁使用的对象类型;
- 空闲链表不能全局控制;
- slab分配器扮演了通用数据结构缓存层的角色;
- SMP(对称多处理结构)锁
- slab层把不同的对象划分为高速缓存组, 每个缓存组存放不同类型的对象;
- 一个高速缓存用于存放进程描述符, 一个高速缓存存放索引节点对象;
- kmalloc接口建立在slab层上, 使用了一组通用的高速缓存;
- 高速缓存又被划分为slab, 这里的slab是由一个或多个连续的页组成, 一般slab仅仅是由一页组成, 但每个高速缓存可以由多个slab组成;
- slab有三种状态: 满, 部分满或空, --- 最终目的是为了减少内存碎片;
- inode结点是磁盘索引节点在内存中的体现, 会频繁的创建和释放, 用slab来管理inode节点;
- 每个slab包含尽可能多的inode对象, 创建一个对象, 从部分满或者空的slab中返回一个指向已分配但未使用的结构的指针。
- 内核用完一个对象后, slab分配器就把该对象标记为空闲;
- 高速缓存 >> slab >> 具体对象
- 高速缓存是较大的内存空间, 里面包含了几个slab;
- 而一个slab中又包含好几个同一类的对象;
- 每个高速缓存都使用kmem_cache结构来表示, 包含三个链表:
- slabs_full
- slabs_partial
- slabs_empty
- slab分配器可以创建新的slab, 通过__get_free_pages低级内核页分配器进行;
- __get_free_pages为高速缓存分配足够多的内存, 分配的页为2的幂次方;
- NUMA系统上较好的性能(非一致的内存访问);
- slab的管理是在每个高速缓存的基础上, 并提供内核一个简单的接口;
- 通过接口可以创建和撤销新的高速缓存, 并在高速缓存内分配和释放对象;
- 高速缓存及其内部slab的复杂管理完全通过slab层的内部机制来处理;
- 当创建一个高速缓存后, slab层所起得作用就像一个专用的分配器, 可以为具体的对象类型进行分配;
- 要理解slab层的具体实现;
- slab分配器的接口
- 新建一个高速缓存 -- kmem_cache_create;
- 对齐越严格, 浪费的内存越多
- 撤销一个高速缓存 -- kmem_cache_destroy, 可能睡眠;
- 调用kmem_cache_destroy过程中(调用之后), 不再访问这个高速缓存;
- 调用者必须确保这种同步;
- 从缓存中分配: kmem_cache_alloc, 从给定的高速缓存中返回对象的指针;
- 如果高速缓存的所有slab中都没有空闲的对象, 那么slab层必须通过kmem_getpages来获取新的页;
- 释放一个对象 -- kmem_cache_free;
- 进程描述符是内核的核心组成部分;
- slab层负责内存紧缺情况下所有底层的对齐, 着色, 分配, 和回收等;
- 如果要频繁的创建很多相同类型的对象, 应该考虑使用slab高速缓存, 不要自己去实现空闲链表;
- 在栈上的静态分配
- 用户空间的内存分配在栈上进行静态分配;
- 用户空间能够非常奢侈的负担起非常大的栈, 而且栈空间还可以动态增长;
- 内核栈小而固定, 给每个进程分配一个固定大小的小栈后, 不但可以减少内存的消耗, 而且内核也无需负担太重的栈管理任务;
- 每个进程都有两页的内核栈, 32位机一页为4K, 64位机一页为8K, 所以内核栈的大小分别为8KB和16KB;
- 随着机器运行时间的增加, 寻找两个未分配的, 连续的页变得越来越困难, 内存渐渐变成碎片, 因此给一个新进程分配一个虚拟内存(VM)的压力也在增大;
- 中断栈: 中断栈为每个进程提供一个用于中断处理程序的栈;
- 中断处理程序不用再和被中断的进程共享一个内核栈, 它可以使用自己的栈;
- 内核栈可以是一页, 也可以是两页, 所以栈的大小为4~16KB;
- 再具体的函数中, 让所有局部变量(即所谓的自动变量)所占空间之和不要超过几百字节;
- 在栈上进行大量的静态分配是很危险的(分配大型数组或者大型结构体);
- 用户空间的内存分配在栈上进行静态分配;
- 高端内存的映射
- 高端内存中的页不能永久地映射到内核地址空间上;
- 通过alloc_pages函数以__GFP_HIGHMEM标志获得的页不可能有逻辑地址;
- x86体系结构上, 896MB以上的物理内存大都是高端内存, 它并不会永久地或自动地映射到内核地址空间;
- 一旦这些页被分配, 就必须映射到内核的逻辑地址空间上;
- 在x86上, 高端内存中的页被映射到3G~4G;
- 什么是永久映射?
- kmap -- 映射一个给定的page结构到内核地址空间, 高端内存和低端内存中都能使用;
- 函数返回该页的虚拟地址;
- 可以永久地映射高端内存;
- kunmap解除映射;
- kmap -- 映射一个给定的page结构到内核地址空间, 高端内存和低端内存中都能使用;
- 临时映射
- 临时映射可以用到不能睡眠的地方(中断处理程序);
- kmap_atomic -- 建立一个临时映射;
- kunmap_atomic -- 取消映射;
- 临时映射可以用到不能睡眠的地方(中断处理程序);
- 每个CPU的分配
- SMP(对称多处理器)的现代操作系统使用每个CPU上的数据, 对于给定的处理器其数据是唯一的
- 一般来说, 每个CPU的数据存放着一个数组中, 数组的每一项对应着系统上一个存在的处理器;
- 按处理器号, 确定这个数组的当前元素;
- my_percpu[NR_CPUS];
- get_cpu -- 获得当前处理器, 并禁止内核抢占;
- put_cpu -- 激活内核抢占;
- 新的每个CPU接口
- percpu 简化了创建和操作每个CPU的数据;
- 更适合大型对称多处理器计算机的要求;
- 编译时定义每个CPU数据
- DEFINE_PER_CPU(type, name); -- 为每个处理器创建一个类型为type, 名字为name的变量实例;
- DECLARE_PER_CPU -- 声明变量;
- get_cpu_var(增加该处理器上的 name变量的值)和put_cpu_var(完成: 重新激活内核的抢占);
- per_cpu(name, cpu)++, 增加指定处理器上的name变量的值;
- 不会禁止内核抢占, 也不会提供任何形式的锁保护;
- 编译时每个CPU数据的例子并不能在模块内使用, 因为链接程序实际上将他们创建在一个唯一的可执行段中(.data.percpu)
- percpu 简化了创建和操作每个CPU的数据;
- SMP(对称多处理器)的现代操作系统使用每个CPU上的数据, 对于给定的处理器其数据是唯一的
- 分配函数的选择
- 如果需要连续的物理页, 就可以使用某个低级页分配器或者kmalloc;
- 要创建和撤销很多大的数据结构, 那么考虑建立slab高速缓存;
- 虚拟文件系统(VFS) -- 负责管理文件系统且为用户空间程序提供一致性接口的内核子系统;
初探Linux内核中的内存管理的更多相关文章
- KSM剖析——Linux 内核中的内存去耦合
简介: 作为一个系统管理程序(hypervisor),Linux® 有几个创新,2.6.32 内核中一个有趣的变化是 KSM(Kernel Samepage Merging) 允许这个系统管理程序通 ...
- Linux内核中常见内存分配函数(一)
linux内核中采 用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系 统中,用到了四级页表. * 页全局目录(Page Global Dir ...
- Linux内核中常见内存分配函数【转】
转自:http://blog.csdn.net/wzhwho/article/details/4996510 1. 原理说明 Linux内核中采用了一种同时适用于32位和64位系统的内存分页 ...
- Linux内核中常见内存分配函数
1. 原理说明 Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表,如图2-1所示.四级页表分 ...
- linux内核分析之内存管理
1.struct page /* Each physical page in the system has a struct page associated with * it to keep tra ...
- linux内核--用户态内存管理
在上一篇博客“内核内存管理”中,描述的内核内存管理的相关算法和数据结构,在这里简单描述用户态内存管理的数据结构和算法. 一,相关结构体 与进程地址空间相关的全部信息都包含在一个叫做“内存描述符”的数据 ...
- Linux内核中常见内存分配函数(三)
ioremap void * ioremap (unsigned long offset, unsigned long size) ioremap是一种更直接的内存“分配”方式,使用时直接指定物理起始 ...
- Linux内核剖析 之 内存管理
1. 内存管理区 为什么分成不同的内存管理区? ISA总线的DMA处理器有严格的限制:仅仅能对物理内存前16M寻址. 内核线性地址空间仅仅有1G,CPU不能直接訪问全部的物理内存. ZONE_DMA ...
- [转]linux内核分析笔记----内存管理
转自:http://blog.csdn.net/Baiduluckyboy/article/details/9667933 内存管理,不用多说,言简意赅.在内核里分配内存还真不是件容易的事情,根本上是 ...
随机推荐
- 10.外观模式(Facade Pattern)
动机(Motivate): 在软件开发系统中,客户程序经常会与复杂系统的内部子系统之间产生耦合,而导致客户程序随着子系统的变化而变化.那么如何简化客户程序与子系统之间的交互接口?如何将复杂系统的 ...
- spring boot集成redis的血泪史
首先说明环境不是我搭建的,然后因项目需要添加redis的时候,麻烦来了.springboot 用的是1.5.9因为以前弄过redis,所以直接拿过来,麻烦了首先是莫名的错误,连项目都启动不了.但是最后 ...
- React 记录(2)
入门教程:https://www.reactjscn.com/tutorial/tutorial.html 慢慢学习:对照教程文档,逐句猜解,截图 React官网:https://reactjs.or ...
- Tornado的异步非阻塞
阻塞和非阻塞Web框架 只有Tornado和Node.js是异步非阻塞的,其他所有的web框架都是阻塞式的. Tornado阻塞和非阻塞两种模式都支持. 阻塞式: 代表:Django.Flask.To ...
- 《区块链生存训练2.0》PDF
<区块链生存训练>2.0于2018年2月28日在我的知识星球里发布.时过1年多,很多内容又过时了,准备修改新的版本,现在把PDF公布出来. 下载地址: 链接: https://pan.ba ...
- linux 下shell脚本备份文件
以下是shell自动备份用的: 主要功能: 1)将pathSrc目录中的文件拷贝到pathDst目录中去. 具体步骤:先查询源目录和目标目录中的文件,分别存在fileSrc和fileDst中. -&g ...
- MYSQL数据库安装记
这回安装数据库装,做下记录. 1.下载mysql源安装包 shell> wget http://dev.mysql.com/get/mysql57-community-release-el7-8 ...
- 【LeetCode】108. Convert Sorted Array to Binary Search Tree
Problem: Given an array where elements are sorted in ascending order, convert it to a height balance ...
- sql leetcode -Duplicate Emails
第一种解法: select distinct p1.Email as Email from Person p1, Person p2 where p1.Email=p2.Email and p1.Id ...
- faster rcnn相关内容
转自: https://zhuanlan.zhihu.com/p/31426458 faster rcnn的基本结构 Faster RCNN其实可以分为4个主要内容: Conv layers.作为一种 ...