LDD读书笔记_内存管理

本部分不仅仅是LDD的介绍部分, 还包括了对linux的内存模型的总结.

一句话总结

伙伴系统是基石, slab基于伙伴系统, kmalloc基于slab.

要点

?伙伴系统是对连续大内存而言, 得到的内存的单位从1个page到211 page, 解决外部碎片问题.

?Slab分配器是针对小内存而言, 从32B到128KB, 解决的是内部碎片问题, kmalloc是基于slab分配器的.

?如果物理内存加上需要映射的IO空间内存的大小加起来超过896M, 则有必要开启highmen的功能.

Agenda

内存模型(zone, 伙伴系统, slab)

获得页(Request Page Frame)

释放页(Release Page Frame)

高端内存访问

?permanent kernel mapping

?temporary kernel mapping

?noncontiguous memory allocation

? linux将内存划分为一个个Node, 每个Node分三个zone

–zone_DMA, 通常对应16M以下空间, ISA总线的限制

–zone_NORMAL

–zone_HIGHMEM, 通常对应896M以上的空间, 这部分空间不会直接映射到内核的第4GB的范围, 应此内核无法直接访问

Buddy System Algorithm

?外部碎片: 频繁的分配/释放导致的, 会造成原本是一整块连续的内存, 变成有很多断断续续的碎片, 导致后续分配连续内存的时候失败, 即使总的剩余空间还是够的.

?解决方法:

–重新将不连续的物理地址映射为连续的线性地址

–开发内存管理技术, 记录空闲连续内存的情况

–内核采用第二种方法

?内核将空闲页框分组为11个块链表, 分别对应1,2,4,8,16,32,64,128,256,512,1024个连续页框, 即最大对应4M的分配大小.

且每个块的起始地址是该块里面的连续页框大小的整数倍.

?内核的做法

–假如要申请256个页框, 内核先检查对应的链表中是否有这样的空闲块.

–如果没有, 就检查512个页框对应的链表, 如果有空闲块, 就把512的页框等分, 一份用来满足请求, 另外一份就插入到256页框的那个链表中.

–如果512页框的那个链表没有空闲块, 就查找1024个页框的链表, 如果找到, 则256个页框用来满足要求. 剩下的786个页框再分成两份, 其中512个页框插入到512那个链表中,

剩下256个插入到256那个链表中.

–如果1024页框的那个链表也没有空闲块, 就返回错误.

–以上的逆过程就是对应的释放过程

?大小为b的页框的伙伴的要求

–也有同样的大小

–物理地址是连续的

–第一个块的第一个页框的物理地址是2 * b * 4K的倍数

Slab

?前面讲到, 伙伴算法采用页框作为基本的内存单元分配, 这适合于大块内存的分配, 对小内存要怎么处理呢?

?内核将小内存的分配再放入到同一个页框中, 但是这样又带来这新的问题.

?内部碎片: 内存请求的大小与分配给他的大小不匹配. 比如说, 需要25 byte, 但是分配了32 byte. 因为内核对小内存的分配也是以2的幂次方来分配的, 默认是13级, 从32 byte到  128K byte.

?怎么办

–频繁请求/释放: slab分配器

–不频繁请求/释放: kmalloc/kfree

?内核函数倾向于反复请求同一类型的内存区, 如果没有slab分配器, 内核需要反复的分配和回收包含同一内存区的页框, 影响效率.

而slab分配器把页框保存在高速缓存中, 可以很快的重复使用.

?现有的普通slab高速缓存

–cache_cache中包含的用于分配kmem_cache_t的缓存

–32B – 128KB的分配类型, 一种用于DMA, 一种用于常规分配(kmalloc)

–在kmem_cache_init中初始化

?内核用到的特殊slab高速缓存

–用kmem_cache_create来创建

–用kmem_cache_destroy来销毁(适用于module形式), 或kmem_cache_shrink

?Slab需要的页框怎么来

–kmem_getpages

?Slab的页框怎么释放

–kmem_freepages

?分配slab对象

–kmem_cache_alloc

?释放slab对象

–kmem_cache_free

Request page frame

?alloc_pages(gfp_mask, order)

得到2^order 连续的page frame, 返回满足条件的第一个page的struct page*, 或者NULL

?alloc_page(gfp_mask)

alloc_pages(gfp_mask, 0)

?_ _get_free_pages(gfp_mask, order)

类似alloc_pages, 但是返回第一个page的线性地址

?_ _get_free_page(gfp_mask)

_ _get_free_pages(gfp_mask, 0)

?get_zeroed_page(gfp_mask)

alloc_pages(gfp_mask | _ _GFP_ZERO, 0), 返回线性地址, page填充为0

?_ _get_dma_pages(gfp_mask, order)

_ _get_free_pages(gfp_mask | _ _GFP_DMA, order), 返回给DMA传输用的线性地址

?由于内核经常请求和释放单个页框, 为了提升性能, 每个zone都定义了一个per cpu的页高速缓存, 这个高速缓存包含了预先分配的页框.

?实际上, 这个高速缓存包含两个部分, 一个是hot高速缓存(会存放在cache中), 一个是cold高速(往往用做DMA, 不需要CPU参与)

struct per_cpu_pages {
	int count;		/* number of pages in the list */
	int low;		/* low watermark, refill needed */
	int high;		/* high watermark, emptying needed */
	int batch;		/* chunk size for buddy add/remove */
	struct list_head list;	/* the list of pages */
};

?如果页框的个数超过high, 则从高速缓存中释放batch个页框回伙伴系统, 如果低于low, 则从伙伴系统中分配batch个页框到高速缓存中.

?API

–buffered_rmqueue 通过per cpu页框高速缓存分配

–free_hot_page/free_cold_page 释放页框到per cpu页框高速缓存

常用的GFP MASK的介绍

?Group name                          Corresponding flags

?GFP_ATOMIC                         _ _GFP_HIGH

?GFP_NOIO                             _ _GFP_WAIT

?GFP_NOFS                            _ _GFP_WAIT | _ _GFP_IO

?GFP_KERNEL                       _ _GFP_WAIT | _ _GFP_IO | _ _GFP_FS

?GFP_USER                            _ _GFP_WAIT | _ _GFP_IO | _ _GFP_FS

?GFP_HIGHUSER                  _ _GFP_WAIT | _ _GFP_IO | _ _GFP_FS | _ _GFP_HIGHMEM

__GFP_DMA和__GFP_HIGHMEM被称作管理区修饰符, 表示寻找空闲page frame的时候搜索的zone, 但实际上:

?如果__GFP_DMA被设置, 则只能从DMA zone去获取page frame

?如果__GFP_HIGHMEM没有被设置, 则按优先顺序从normal zone和DMA zone去获取

?如果__GFP_HIGHMEM被设置, 则按优先顺序从highmem zone, normal zone和DMA zone去获取

Release page frame

?_ _free_pages(page, order)

–检查page frame是否是reserved的, 如果不是, 减少counter, 如果counter为0, 这认为连续的2order的page frame都不再使用

?_ _free_page(page)

–_ _free_pages(page, 0)

?free_pages(addr, order)

–和__free_pages类似, 只是接受的是线性地址

?free_page(addr)

–free_pages(addr, 0)

高端内存访问

?由于896M以上的page frame并不映射在内核线性地址空间的第4G的范围, 因此内核不能直接访问.

?也就是说, 返回线性地址的分配函数(__get_free_pages)不能用于高端内存.

?能使用的是alloc_pages, 返回的是page*

?因此,内核线性地址空间的最后128M的一部分专门用来做高端内存的映射, 从而达到访问高端内存的目的.

高端内存映射方法

?permanent kernel mapping

?会阻塞当前进程, 因此不能用在中断和tasklet中

?存放在pkmap_page_table中, 总共有LAST_PKMAP(512/1024)个table, 也就是映射了2M/4M的地址.

?page_address( )函数, 根据page descriptor返回线性地址

–如果是非highmem的, 那么这个地址一定存在, __va(page_to_pfn(page) << PAGE_SHIFT)

–如果是在highmem中, 则到page_address_htable中查找, 如果没有就返回NULL

?Kmap用来建立永久映射(其实调用的是kmap_high)

void * kmap_high(struct page * page)
{
   unsigned long vaddr;
   spin_lock(&kmap_lock);
   vaddr = (unsigned long)page_address(page);
   if (!vaddr)
      vaddr = map_new_virtual(page);
   pkmap_count[(vaddr-PKMAP_BASE) >> PAGE_SHIFT]++;
   spin_unlock(&kmap_lock);
   return (void *) vaddr;
} 

?Kunmap用来去除永久映射(其实调用的是kunmap_high)

void kunmap_high(struct page * page)
{
 spin_lock(&kmap_lock);
 if ((--pkmap_count[((unsigned long) page_address(page) -  PKMAP_BASE)>>PAGE_SHIFT]) == 1)
   if (waitqueue_active(&pkmap_map_wait))
      wake_up(&pkmap_map_wait);
 spin_unlock(&kmap_lock);
} 

?temporary kernel mapping

– 不会阻塞, 但是要避免在使用相同的映射

– 数目很有限, 一般是每个CPU才13个映射的window

enum km_type {
D(0)	KM_BOUNCE_READ,
D(1)	KM_SKB_SUNRPC_DATA,
D(2)	KM_SKB_DATA_SOFTIRQ,
D(3)	KM_USER0,
D(4)	KM_USER1,
D(5)	KM_BIO_SRC_IRQ,
D(6)	KM_BIO_DST_IRQ,
D(7)	KM_PTE0,
D(8)	KM_PTE1,
D(9)	KM_IRQ0,
D(10) KM_IRQ1,
D(11) KM_SOFTIRQ0,
D(12) KM_SOFTIRQ1,
D(13) KM_TYPE_NR
};

– 使用接口kmap_atomic 和 kunmap_atomic

void * kmap_atomic(struct page * page, enum km_type type)
{
    enum fixed_addresses idx;
    unsigned long vaddr;

    current_thread_info( )->preempt_count++;
    if (!PageHighMem(page))
        return page_address(page);
    idx = type + KM_TYPE_NR * smp_processor_id( );
    vaddr = fix_to_virt(FIX_KMAP_BEGIN + idx);
    set_pte(kmap_pte-idx, mk_pte(page, 0x063));
    _ _flush_tlb_single(vaddr);
    return (void *) vaddr;
}
void kunmap_atomic(void *kvaddr, enum km_type type)
{
#ifdef CONFIG_DEBUG_HIGHMEM
	unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK;
	enum fixed_addresses idx = type + KM_TYPE_NR*smp_processor_id();

	if (vaddr < FIXADDR_START) { // FIXME
		dec_preempt_count();
		preempt_check_resched();
		return;
	}

	if (vaddr != __fix_to_virt(FIX_KMAP_BEGIN+idx))
		BUG();
	pte_clear(kmap_pte-idx);
	__flush_tlb_one(vaddr);
#endif

	dec_preempt_count();
	preempt_check_resched();
}

?noncontiguous memory allocation

?vmalloc/vmalloc_32(只能从normal/DMA zone分配)

?vmap(前提是已经调用get_vm_area得到vm_struct描述符了)

?ioremap

?vfree

?vunmap

?iounmap

时间: 2024-11-04 16:53:11

LDD读书笔记_内存管理的相关文章

《Linux内核设计与实现》读书笔记之内存管理

1.页 内核把物理页作为内存管理的基本单位,MMU(内存管理单元)以页为单位来管理系统中的页表,从虚拟内存的角度来看,页就是最小单位. 内核用struct page结构来标识系统中的每一个物理页,它的定义如下: flag域用来存放页的状态(是不是脏的,是不是被锁定在内存中等等)._count表示这一页被引用了多少次,当次数为0时,表示此页没有被引用,于是在新的分配中就可以使用它.virtual域是页的虚拟地址. 2.获得页 内核提供了一种请求内核的底层机制,并提供了对它进行访问的几个接口.所有这

黑马程序员_IOS开发_Objective-C学习笔记_内存管理

1.内存管理概述 1.1什么是内存管理:内存管理是程序设计中常用的资源管理的一部分,每个计算机系统可供程序使用的内存都是有限的. 1.2为什么要使用内存管理:当我们的程序运行结束的时候,操作系统将回收其我们程序占用内存.但是,只要程序还在运行,它就会一直占用内存.如果不进行及时清理不用的内存,内存最终将被耗尽.每个程序都会使用内存,我们必须确保在需要的时候分配内存,而在程序运行结束时释放占用的内存.如果我们只分配而不释放内存,将发生内存泄漏. 1.3引用计数:1.3.1只有当你对一个对象了all

Objective-C学习笔记_内存管理(二)

一.属性的内部实现原理 assign的属性内部实现 setter方法: // setter方法 @property (nonatomic, assign) NSString *name; - (void)setName:(NSString *)name { _name = name; } getter方法: // getter方法 - (NSString *)name { return _name; } 观察下面代码会出现什么问题? NSString *name = [[NSString all

Objective-C学习笔记_内存管理(一)

一.内存管理的?式 大家都去过图书馆,而图书馆里的书是可以借出的.我们来设想这样一个场景,大家都去借书,但是从来没有人去还书,那么最后,这个图书馆会因为无书可借而倒闭,每个人都没法再使用图书馆.计算机也是这样,当程序运行结束时,操作系统将回收其占用的资源.但是,只要程序运行就会占用资源,如果不进行清理已经不用的资源,资源最终将被耗尽,程序将崩溃. 学会内存管理我们就明白什么时候由你释放对象,什么时候你不能释放.C语言中通过malloc.calloc.realloc和free搭配对内存进行管理.但

&lt;&lt;深入分析javaWeb技术内幕&gt;&gt;读书笔记-JVM内存管理2

JVM垃圾回收策略 1.静态内存分配和回收 编译时已经确定了内存空间大小,程序被加载后则一次性分配好内存空间.程序结束后,则对应栈帧撤销,分配的静态内存空间则被回收. 2.动态内存分配和回收 程序运行动态分配内存空间,回收时则由垃圾收集器负责. 3.垃圾收集器 A.正确的检测出垃圾对象(关键功能) B.释放垃圾对象占用的内存空间 4.基于分代的垃圾收集算法(hostpot) 算法设计思路: 把对象按照寿命的长短进行分组(年轻代,年老代),新创建的对象分配在年轻代中,对象经过几次垃圾回收后,仍然存

LDD读书笔记_调试技术

先写一个个人比较喜欢的调试技巧. 1. menuconfig中打开CONFIG_DEBUG_KERNEL 2. objdump -d -S(大写) *.o > file 可以得到混合C和汇编的代码 或者 make *.lst 也能得到 3. addr2line -f -e vmlinux address(0xcxxxxxxxx) 能得到address对应的函数名和所在的文件中的行数 4. 根据OOPS信息, 查看R13(SP), R14(LR), R15(PC)寄存器的值, ARM架构 ?pri

LDD读书笔记_时间,延迟和延缓操作

Agenda ?如何获得当前时间 ?如何度量时间差, 如何比较时间 ?如何将操作延迟指定的一段时间 ?如何调度异步函数到指定的时间之后执行 ?如何获得当前时间 ?HZ: 宏定义, 每秒的时间中断次数 ?Jiffies变量: 系统引导时初始化为0, 每发生一次时间中断就加1 –#include <linux/jiffies.h> –unsigned long j, stamp_1, stamp_half, stamp_n; –j = jiffies; /* current value */ –s

Querying Microsoft SQL Server 2012 读书笔记:查询和管理XML数据 1 -使用FOR XML返回XML结果集

原文:Querying Microsoft SQL Server 2012 读书笔记:查询和管理XML数据 1 -使用FOR XML返回XML结果集 XML 介绍 <CustomersOrders> <Customer custid="1" companyname="Customer NRZBB"> <Order orderid="10692" orderdate="2007-10-03T00:00:00&

《python源码剖析》笔记 pythonm内存管理机制

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.内存管理架构 Python的内存管理机制都有两套实现:debug模式和release模式 Python内存管理机制的层次结构: 图16-1 第0层是操作系统提供的内存管理接口,如malloc.free 第1层是Python基于第0层操作系统的内存管理接口包装而成的,主要是为了处理与平台相关的内存分配行为. 实现是一组以PyMem_为前缀的函数族 两套接口:函数和宏. 宏,可以避免函数调