第十二章---内存管理 学习和总结

今年其实看了很多这方面的书了,已经对面上的东西很熟了,一年也快过去了,发现有点像大学时候看的高数吧,那些高数书中的内容都是那么的像,这篇笔记来自《linux内核的设计和实现》,当然这第十二章讲的和《linux设备驱动》第八章的内存分配我觉得其实是完全一样的,既然两个章节是完全一样的内容,两位作者都花了一个章节来介绍这块内容我认为还是有必要取学习和思考的。

注意,这里的任何c代码都是书中的,我没有去新版的linux内核中去找他的实现,因为已经无所谓了,主要领会核心和精髓。

1.页

页基本是linux内存管理最基本的单元了,32位系统是4K,64位系统是8K

32位系统页的大小一般是4k,64位系统页的大小一般是8k

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

下面具体拆解几个参数

1.flag

flag 用来存放页的状态,我们注意flag是unsigned long,在32位系统中,字节数为4字节;在64位系统中,Visual C++和Mingw64字节数为4字节。GCC(POSIX系统以及Cygwin)为8字节,一个字节占用8位,所以最少标识32种不同的状态。

2._count

_count域存放的是引用计数,如果说大于等于0则说明这个页被人在使用,如果说计数器为-1,就说明内核中没人用这一个页了,于是在新分配的页中就可以使用它。

3.virtual

他的参数是void*,对应的是虚拟内存地址,有些内存(高端内存)并不永久的映射到内核地址空间上。在这种情况下,这个域的值为NULL,需要的时候,必须动态映射这些页。

注意其实我们思考用一个结构体去维护一个页,代价是不是很高?对于一个4g的操作系统来说,这其实代价并不高,相对于他的可扩展性而言,这个代价是可接受的。

总结书中提及的页上的三个结构:

2.区

由于内核的限制,不如说一些特殊需要操作硬件的地址,比如说引脚啊,IO一类的,探测得到的一些地址,估计都会存放到一些特殊页里,这就注定了一些页是特殊的,所以内核把页划分为不同的区。还有一些体系结构的内存的物理寻址范围比虚拟寻址范围大的多。

linux主要用了四种区:

1.ZONE_DMA  这个区域的页能用来执行DMA 操作

2.ZONE_DMA32 这些页面智能被32位设备访问

3.ZONE_NORMAL 这个区都是可以被正常映射的页

4.ZONE_HIGHEM 高端内存映射到的地址空间

ZONE_HIGHEM是高于896m的所有物理内存。剩余的内存就是低端内存

读完了这里,我回去思考一些问题:

什么是DMA?DMA 用来做什么? 高端内存和低端内存区域是用来做什么的?

DMA,全称Direct Memory Access,即直接存储器访问。直接依赖硬件系统来控制主存与外设之间的数据传送,传送过程无需cpu干预,传送结束后通过中断方式通知cpu。DMA 不会影响cpu上程序执行,只是暂停当前程序的执行,交出总线控制权,由DMA控制器使用总线,实现数据在主机和外围设备之间的传输。

因此他适合大批量数据的简单传送,如果要对数据进行过滤,对不起,不适合DMA。

然后我们继续看一下struct zone 结构体,所有物理内存都存放在ZONE_DMA和ZONE_NORMAL中。

struct zone {
        /* Read-mostly fields */

        /* zone watermarks, access with *_wmark_pages(zone) macros */
        unsigned long _watermark[NR_WMARK];
        unsigned long watermark_boost;

        unsigned long nr_reserved_highatomic;

        /*
         * We don't know if the memory that we're going to allocate will be
         * freeable or/and it will be released eventually, so to avoid totally
         * wasting several GB of ram we must reserve some of the lower zone
         * memory (otherwise we risk to run OOM on the lower zones despite
         * there being tons of freeable ram on the higher zones).  This array is
         * recalculated at runtime if the sysctl_lowmem_reserve_ratio sysctl
         * changes.
         */
        long lowmem_reserve[MAX_NR_ZONES];
#ifdef CONFIG_NUMA
        int node;
#endif
        struct pglist_data      *zone_pgdat;
        struct per_cpu_pageset __percpu *pageset;
        /*
         * the high and batch values are copied to individual pagesets for
         * faster access
         */
        int pageset_high;
        int pageset_batch;

#ifndef CONFIG_SPARSEMEM
        /*
         * Flags for a pageblock_nr_pages block. See pageblock-flags.h.
         * In SPARSEMEM, this map is stored in struct mem_section
         */
        unsigned long           *pageblock_flags;
#endif /* CONFIG_SPARSEMEM */

        /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
        unsigned long           zone_start_pfn;
 /*
         * spanned_pages is the total pages spanned by the zone, including
         * holes, which is calculated as:
         *      spanned_pages = zone_end_pfn - zone_start_pfn;
         *
         * present_pages is physical pages existing within the zone, which
         * is calculated as:
         *      present_pages = spanned_pages - absent_pages(pages in holes);
         *
         * managed_pages is present pages managed by the buddy system, which
         * is calculated as (reserved_pages includes pages allocated by the
         * bootmem allocator):
         *      managed_pages = present_pages - reserved_pages;
         *
         * So present_pages may be used by memory hotplug or memory power
         * management logic to figure out unmanaged pages by checking
         * (present_pages - managed_pages). And managed_pages should be used
         * by page allocator and vm scanner to calculate all kinds of watermarks
         * and thresholds.
         *
         * Locking rules:
         *
         * zone_start_pfn and spanned_pages are protected by span_seqlock.
         * It is a seqlock because it has to be read outside of zone->lock,
         * and it is done in the main allocator path.  But, it is written
         * quite infrequently.
         *
         * The span_seq lock is declared along with zone->lock because it is
         * frequently read in proximity to zone->lock.  It's good to
         * give them a chance of being in the same cacheline.
         *
         * Write access to present_pages at runtime should be protected by
         * mem_hotplug_begin/end(). Any reader who can't tolerant drift of
         * present_pages should get_online_mems() to get a stable value.
         */
        atomic_long_t           managed_pages;
        unsigned long           spanned_pages;
        unsigned long           present_pages;

        const char              *name;
#ifdef CONFIG_MEMORY_ISOLATION
        /*
         * Number of isolated pageblock. It is used to solve incorrect
         * freepage counting problem due to racy retrieving migratetype
         * of pageblock. Protected by zone->lock.
         */
        unsigned long           nr_isolate_pageblock;
#endif

#ifdef CONFIG_MEMORY_HOTPLUG
        /* see spanned/present_pages for more description */
        seqlock_t               span_seqlock;
#endif

        int initialized;

        /* Write-intensive fields used from the page allocator */
        ZONE_PADDING(_pad1_)

        /* free areas of different sizes */
        struct free_area        free_area[MAX_ORDER];

        /* zone flags, see below */
        unsigned long           flags;

        /* Primarily protects free_area */
        spinlock_t              lock;

        /* Write-intensive fields used by compaction and vmstats. */
        ZONE_PADDING(_pad2_)

        /*
         * When free pages are below this point, additional steps are taken
         * when reading the number of free pages to avoid per-cpu counter
         * drift allowing watermarks to be breached
         */
        unsigned long percpu_drift_mark;

#if defined CONFIG_COMPACTION || defined CONFIG_CMA
        /* pfn where compaction free scanner should start */
        unsigned long           compact_cached_free_pfn;
        /* pfn where compaction migration scanner should start */
        unsigned long           compact_cached_migrate_pfn[ASYNC_AND_SYNC];
        unsigned long           compact_init_migrate_pfn;
        unsigned long           compact_init_free_pfn;
#endif

#ifdef CONFIG_COMPACTION
        /*
         * On compaction failure, 1<<compact_defer_shift compactions
         * are skipped before trying again. The number attempted since
         * last failure is tracked with compact_considered.
         * compact_order_failed is the minimum compaction failed order.
         */
        unsigned int            compact_considered;
        unsigned int            compact_defer_shift;
        int                     compact_order_failed;
#endif

#if defined CONFIG_COMPACTION || defined CONFIG_CMA
        /* Set to true when the PG_migrate_skip bits should be cleared */
        bool                    compact_blockskip_flush;
#endif

        bool                    contiguous;

        ZONE_PADDING(_pad3_)
        /* Zone statistics */
        atomic_long_t           vm_stat[NR_VM_ZONE_STAT_ITEMS];
        atomic_long_t           vm_numa_stat[NR_VM_NUMA_STAT_ITEMS];
} ____cacheline_internodealigned_in_smp;

注意这里的name 有三个值 分别对应三个区的名字 “DMA","Normal","HIGHMEM"

四大区域:

 其实这里我有一个疑问,区是如何管理页的?书中没有明确说

3.获得页

内核提供了请求内存的底层机制。所有接口分配内存都以页为单位

这几个api的调用其实主要是 调用参数的不同,没什么大区别

注意这个order 返回的页数是2的order次方页 。

这个函数像calloc,申请页的时候会清零里面的内存。

4.释放页 

注意传入对的页地址

5.kmalloc 

很多时候我们不需要申请连续的内存页,我们申请内存总是以字节为单位,这时候可以使用kmalloc

我们看kmalloc 在 <linux/slab.h>中的定义

void* kmalloc(size_t size, gfp_t glags);

我们看gfp_mask 标志,分为三大类:

1.行为修饰符:

_GFP_WAIT(分配器可以睡眠)

_GFP_HIGH(分配器可以访问紧急事件缓冲池)

_GFP_IO(分配器可以启动磁盘IO)

_GFP_FS(分配器可以启动文件IO)

_GFP_COLD(分配器可以使用高速缓冲区快要被淘汰的页)

_GFP_NOWARN(分配器失败将不会打印告警)

_GFP_REPEAT(分配器在分配失败的时候会重复分配,但是分配存在失败的可能性)

_GFP_NOFAIL(分配器在分配失败的时候会重复分配,但是分配存在失败的可能性)

_GFP_NOREPEAT(分配失败的时候不会重新分配)

_GFP_NO_GROW(slab层使用)

_GFP_COMP(添加混合页元数据,在hugetlb的代码使用)

2.区修饰符

分配可以从任何区域开始,内核优先从ZONE_NORMAL 开始,这样可以确保其他地方有充足的空闲页使用。

区修饰符

_GFP_DMA  从ZONE_DMA 开始分配

_GFP_DMA32  只在ZONE_DMA32 分配

_GFP_HIGHMEM 从ZONE_HIGHMEM 或者 ZONE_NORMAL 中分配

如果没有任何区标志内核从ZONE_HIGHMEM 或者 ZONE_NORMAL中分配优先从ZONE_NORMAL 中分配,get_free_pages和kalloc 不能分配高端内存,只有alloc_pages 他们分配的只是逻辑地址

物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址

逻辑地址:CPU所生成的地址。逻辑地址是内部和编程使用的、并不唯一。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址(偏移地址),不和绝对物理地址相干。

3.类型标志

GFP_ATOMIC(__GFP_HIGH)

这个标志用在中断的处理程序,下半部,持有自旋锁但是不能睡眠的地方

GFP_NOWAIT(0)

和GFP_ATOMIC类似,不同之处是不会退给紧急内存池,增加了分配失败的可能性。

GFP_NOIO(__GFP_WAIT)

这种分配可能阻塞,但是不会IO

GFP_NOFS(__GFP_WAIT|__GFP_IO)

这种分配可能阻塞,也可能启动磁盘io,但是不会启动文件操作系统。

GFP_KERNEL(__GFP_WAIT|__GFP_IO|_GFP_GS)

这是用的最多的一种方式,可能会阻塞。这个标志在睡眠安全时候用在进程上下文中,为了获得内存,内核会尽力满足,这是首选标志


GFP_USER(__GFP_WAIT|__GFP_IO|_GFP_GS)

可能会阻塞这个标志用于用户空间分配内存中

GFP_HIGHUSER(__GFP_WAIT|__GFP_IO|_GFP_GS|__GFP_HIGHMEM)

从ZONE_HIGHMEM进行分配,可能会阻塞,用于用户空间分配

GFP_DMA(__GFP_DMA)

从ZONE_DMA进行分配

GFP_KERNEL 可能会引起睡眠,但是成功率很高,它可以让调用者 睡眠、交换、刷新一些页到硬盘等,因此分配得到内存成功率比较高。

GFP_AUTOMAIC调用者不能休眠,因此失败率比GFP_KERNEL大、

5.kfree

kfree 输入地址就ok了 

6.vmalloc 和 vfree

vmalloc 分配的虚拟地址是连续的,但是物理地址不一定连续,但是kmalloc申请的物理地址是连续的。vmalloc 需要建立虚拟地址和物理地址的映射关系,性能比较差,一般在申请大块内存时候才会用,vmalloc 也可能引起睡眠

vfree 就不说了

7.slab

slab内存是为了避免频繁申请和释放,避免出现内存碎片更好利用缓存的。

slab的分配顺序是先从未满的slab中分配,再从空闲的slab分配,最后才会从满的slab分配,创建新的cache是通过get_free_pages进行的。

slab 创建接口

struct kmem_cache *kmem_cache_create(const char *, size_t, size_t,---------创建slab描述符kmem_cache,此时并没有真正分配内存
            unsigned long, void (*)(void *));
void *kmem_cache_alloc(struct kmem_cache *, gfp_t flags);------------------分配slab缓存对象
void kmem_cache_free(struct kmem_cache *, void *);-------------------------释放slab缓存对象
void kmem_cache_destroy(struct kmem_cache *);-----------------------------销毁slab描述符

8.高端内存分配

我们可以通过kmap 创建一个高端内存分配,虚拟地址会建立和高端内存的映射,当然也可以使用kunmap 这个函数释放内存,kemap可能会造成进程上下文休眠。之所以用这个函数是因为kmalloc拿到的是物理地址,而不是逻辑地址

kunmap 可以释放kmap 使用的内存

如果我们不想休眠可以使用kmap_atomic, 也可以使用kmap_atomic来释放内存,

9.cpu接口

我们有的时候需要操作cpu 上的数据,他不需要锁,而且可以防止缓存失效,linux当然提供了这些接口

获取:

get_cpu_var(var)

put_cpu_var(var)

申请:

动态申请:

 

10.总结:

 如果你想申请连续物理地址用kmalloc

如果你想从高端内存分配页 用alloc_pages

如果你想得到逻辑地址用 kmap

如果你想要分配大内存,但是又不连续,vmalloc可以获得逻辑地址

内存频繁申请释放用 slab

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值