Linux内存管理、伙伴系统(buddy system)等知识点

引入

  1. 之前写过一篇文章将伙伴系统,可以参考:内存池算法简介

  1. 物理内存由页分配器(page allocator)接管。

  1. 内存块的申请、释放过程。

  1. 伙伴算法、阶数。

  1. 2^0 为1 ,链表上存放的是一个page(4KB)的大小;2^1为 2,链表上存放的是2个page(8KB)的大小;依次类推...;MAX_ORDER 为 11,2^10为1024,所以链表上最大为4MB,如上图中的pageblock就为4MB。

  1. 系统刚刚开始的时候,伙伴系统里面什么都没有。伙伴系统用链表来管理4MB的大小的内存快。

  1. 伙伴系统是将每个zone(分区)中绝大部分物理页放到伙伴系统中去进行管理的。

  1. 每个链表的表头是存放在struct free_area 这个结构体中的,其中 nr_free 代表每个链表中内存快的个数。

从上面的虚拟机上可以看到:

  1. X86系统上,只有一个Node,有三个物理内存分区,DMA,DMA32,Normal,可以看到阶数为11。

  1. 然后可以看到在DMA分区下,存放4MB链表上有3个,存放2MB链表上有1个,依次类推...

  1. 可以看到在DMA32分区下,存放4MB链表上有0个,.....,存放4KB链表上有2919个。

  1. 可以看到在Normal分区下,存放4MB链表上有0个,.....,存放4KB链表上有3061个。

  1. 不同的平台上,分区的配置是不一样的,上面是X86平台,如果是ARM平台和X86平台不一样。

struct page {
    atomic_t _mapcount;
    atomic_t _refcount;
    unsigned long private;
}

atomic_t _mapcount; : _mapcount初始值为-1,说明没有发生映射,用户没有使用这块内存,这个时候这块物理页应该在伙伴系统上挂着。

unsigned long private; :这个private 表示当前内存块的大小。page_order =1 代表 2^1

atomic_t _refcount; : 引用计数

枚举类型:migratetype

enum {
    MIGRATE_UNMOVABLE,
    MIGRATE_MOVABLE,
    MIGRATE_RECLAIMABLE,
    MIGRATE_PCPTYPES,    /* the number of types on the pcp lists */
    MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
    /*
     * MIGRATE_CMA migration type is designed to mimic the way
     * ZONE_MOVABLE works.  Only movable pages can be allocated
     * from MIGRATE_CMA pageblocks and page allocator never
     * implicitly change migration type of MIGRATE_CMA pageblock.
     *
     * The way to use it is to change migratetype of a range of
     * pageblocks to MIGRATE_CMA which can be done by
     * __free_pageblock_cma() function.  What is important though
     * is that a range of pageblocks must be aligned to
     * MAX_ORDER_NR_PAGES should biggest page be bigger then
     * a single pageblock.
     */
    MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
    MIGRATE_ISOLATE,    /* can't allocate from here */
#endif
    MIGRATE_TYPES
};
  1. 查看页面类型:cat /proc/pagetypeinfo

  1. MIGRATE_MOVABLE 可以移动的:用户进程申请的内存;用户进程占用一片物理内存,但是这个时候另外一个进程需要很大一块物理内存,那么这个时候系统就会把之前进程占用的那一片物理内存移动到其他地方中去,为后来这个进程腾出来一大片物理内存空间。那么这样的页面就是可以移动的。

  1. MIGRATE_RECLAIMABLE 可回收的:文件系统的page cache

  1. MIGRATE_UNMOVABLE 不可移动的:内核镜像区的物理内存;比如运行一个程序加载进入内存,在编译链接的时候代码地址就是确定的,那么存放代码的这部分内存就是不可移动的。内核中很多物理地址,都是fireware中固定好的,是不能改变的,所以要求它所对应的页是不可移动的。

为什么引入迁移类型?

  1. 伙伴系统存在的问题:我们知道伙伴系统可以自动的进行页面合并,在很大的程度上可以避免内存碎片。但是也存在一些极端情况,比如,就是某些时候,需要一大块的连续内存,而这个时候物理内存页快已经满足不了。

  1. 引入迁移类型目的就是为了改进伙伴系统。

  1. page migratetype :不同类型的页面分类存储

  1. memory compaction :内存碎片整理,如下图所示,假如,我们需要连续4个页,但是现在已经没有连续4个页了,这个时候如果页面都是不可移动的,那么申请内存肯定会失败。

  1. memory compact 的几种方式:COMPACT_PRIO_SYNC_FULL : 以同步的方式压缩和迁移;COMPACT_PRIO_SYNC_LIGHT :压缩同步,迁移异步;COMPACT_PRIO_SYNC_ASYNC,以异步方式压缩和迁移。

  1. 什么时候会触发memory compaction? 1.没有申请到内存的时候。2.当内存阈值不够的时候,后台的 kcompaction守护线程 会被唤醒,会执行内存迁移的操作。 3.memory compaction开销是挺大的,对性能有一定的影响。

  1. Linux下可以这样查看,ls /proc/sys/vm | grep compact_memory

Per-CPU页帧缓存

  1. per-cpu页帧缓存也是对伙伴系统的一种完善和补充。

  1. pcp : per CPU pages

  1. 单个物理页的申请与释放

struct zone {
    long lowmem_reserve[MAX_NR_ZONES]; //还有一部分物理页放在lowmem_reserve中备份
    struct per_cpu_pageset __percpu *pageset;//还有一部分放在pageset这里存放
    struct free_area    free_area[MAX_ORDER]; //物理页面绝大部分是放在 free_area中
}____cacheline_internodealigned_in_smp;

struct free_area free_area[MAX_ORDER]; 大部份物理页是放在伙伴系统的链表上的。

long lowmem_reserve[MAX_NR_ZONES]; 还有一些预留的页

struct per_cpu_pageset __percpu *pageset; 还有一些缓存页,目的就是为了降低锁的开销,提高性能。每一个CPU都有一个本地的缓存,只缓存单个物理页。

__percpu 关键字是内核提供的一种机制,降低锁的开销,提高性能。怎么实现的?申请内存并不是从伙伴系统上去拿,而是到 per_cpu_pages 结构体中 struct list_head lists 这个链表中去拿,如果拿不到,再才会到伙伴系统中去拿;释放流程:不会释放到伙伴系统中,而是释放到 per_cpu_pages 结构体中 struct list_head lists 这个链表中,这样就避免了多个CPU加锁,去锁的开销,也不用去全局到伙伴系统中去拿内存页了,性能就提高了。

页分配/释放接口:alloc_pages、free_page

alloc_pages、free_page是伙伴系统提供的页分配内存、页释放内存的接口。

  1. 头文件 include/linux/gfp.h

static inline struct page *
alloc_pages(gfp_t gfp_mask, unsigned int order)
{
    return alloc_pages_current(gfp_mask, order);
}

参数掩码gfp_mask : 代表在那个区域去找内存?ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM 等?

参数order : 代表伙伴系统的阶数

具体来看一个小demo

#include <linux/init.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/gfp.h>

#define PAGE_ORDER 1

struct page* page;
unsigned long int virt_addr;

static int __init hello_init(void)
{
    page = alloc_pages(GFP_KERNEL,PAGE_ORDER);
    printk("page frame no: %lx\n",page_to_pfn(page));
    printk("physical addr: %x\n",page_to_phys(page));
    printk("virtual addr: %x\n",(unsigned int)page_address(page));
    virt_addr = (unsigned long)page_to_virt(page);
    printk("virtual addr: %lx\n",virt_addr);

    return 0;
}

static void __exit hello_exit(void)
{
    free_pages(virt_addr,PAGE_ORDER);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wj");
MODULE_DESCRIPTION("alloc and free pages from buddy system");

make

root@ubuntu:/home/work# make
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm -C /home/linux-5.10.179 M=/home/work modules
make[1]: Entering directory '/home/linux-5.10.179'
  CC [M]  /home/work/hello.o
  MODPOST /home/work/Module.symvers
  LD [M]  /home/work/hello.ko
make[1]: Leaving directory '/home/linux-5.10.179'
root@ubuntu:/home/work# cp hello.ko /home/nfs/

[root@vexpress ]# ls
a.out     etc       lib       proc      sys       var
bin       hello.c   linuxrc   root      tmp
dev       hello.ko  mnt       sbin      usr
[root@vexpress ]# insmod hello.ko
[ 7072.055759] hello: loading out-of-tree module taints kernel.
[ 7072.062743] page frame no: 61ba4
[ 7072.063257] physical addr: 61ba4000
[ 7072.063640] virtual addr: 81ba4000
[ 7072.064028] virtual addr: 81ba4000
[root@vexpress ]# rmmod hello.ko 
[root@vexpress ]# 

alloc_pages

#ifdef CONFIG_NUMA  // 服务器领域用这个,非一致性存储内存
static inline struct page *
alloc_pages(gfp_t gfp_mask, unsigned int order)
{
    return alloc_pages_current(gfp_mask, order);
}
#else         //嵌入式系统用这个
#define alloc_pages(gfp_mask, order) \
        alloc_pages_node(numa_node_id(), gfp_mask, order)
#endif

嵌入式系统中,用的是UMA(一致性存储内存),下面我们以嵌入式系统为例子,具体来看一下 call-flow

alloc_pages_node

static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,
                        unsigned int order)
{
    if (nid == NUMA_NO_NODE)
        nid = numa_mem_id();

    return __alloc_pages_node(nid, gfp_mask, order);
}

__alloc_pages_node

static inline struct page *
__alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order)
{
    VM_BUG_ON(nid < 0 || nid >= MAX_NUMNODES);
    VM_WARN_ON(!node_online(nid));

    return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
}
__alloc_pages
static inline struct page *
__alloc_pages(gfp_t gfp_mask, unsigned int order,
        struct zonelist *zonelist)
{
    return __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL);
}
__alloc_pages_nodemask
mm/page_alloc.c

struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
            struct zonelist *zonelist, nodemask_t *nodemask)
{
    struct page *page;
    unsigned int alloc_flags = ALLOC_WMARK_LOW;
    gfp_t alloc_mask = gfp_mask; /* The gfp_t that was actually used for allocation */
    struct alloc_context ac = {
        .high_zoneidx = gfp_zone(gfp_mask),
        .zonelist = zonelist,
        .nodemask = nodemask,
        .migratetype = gfpflags_to_migratetype(gfp_mask),
    };

    if (cpusets_enabled()) {
        alloc_mask |= __GFP_HARDWALL;
        alloc_flags |= ALLOC_CPUSET;
        if (!ac.nodemask)
            ac.nodemask = &cpuset_current_mems_allowed;
    }

    gfp_mask &= gfp_allowed_mask;

    lockdep_trace_alloc(gfp_mask);

    might_sleep_if(gfp_mask & __GFP_DIRECT_RECLAIM);

    if (should_fail_alloc_page(gfp_mask, order))
        return NULL;

    /*
     * Check the zones suitable for the gfp_mask contain at least one
     * valid zone. It's possible to have an empty zonelist as a result
     * of __GFP_THISNODE and a memoryless node
     */
    if (unlikely(!zonelist->_zonerefs->zone))
        return NULL;

    if (IS_ENABLED(CONFIG_CMA) && ac.migratetype == MIGRATE_MOVABLE)
        alloc_flags |= ALLOC_CMA;

    /* Dirty zone balancing only done in the fast path */
    ac.spread_dirty_pages = (gfp_mask & __GFP_WRITE);

    /*
     * The preferred zone is used for statistics but crucially it is
     * also used as the starting point for the zonelist iterator. It
     * may get reset for allocations that ignore memory policies.
     */
    ac.preferred_zoneref = first_zones_zonelist(ac.zonelist,
                    ac.high_zoneidx, ac.nodemask);
    if (!ac.preferred_zoneref->zone) {
        page = NULL;
        /*
         * This might be due to race with cpuset_current_mems_allowed
         * update, so make sure we retry with original nodemask in the
         * slow path.
         */
        goto no_zone;
    }

    /* First allocation attempt */
    page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
    if (likely(page))
        goto out;

no_zone:
    /*
     * Runtime PM, block IO and its error handling path can deadlock
     * because I/O on the device might not complete.
     */
    alloc_mask = memalloc_noio_flags(gfp_mask);
    ac.spread_dirty_pages = false;

    /*
     * Restore the original nodemask if it was potentially replaced with
     * &cpuset_current_mems_allowed to optimize the fast-path attempt.
     */
    if (unlikely(ac.nodemask != nodemask))
        ac.nodemask = nodemask;

    page = __alloc_pages_slowpath(alloc_mask, order, &ac);

out:
    if (memcg_kmem_enabled() && (gfp_mask & __GFP_ACCOUNT) && page &&
        unlikely(memcg_kmem_charge(page, gfp_mask, order) != 0)) {
        __free_pages(page, order);
        page = NULL;
    }

    if (kmemcheck_enabled && page)
        kmemcheck_pagealloc_alloc(page, order, gfp_mask);

    trace_mm_page_alloc(page, order, alloc_mask, ac.migratetype);

    return page;
}

get_page_from_freelist

最关键就是这两个函数,导致是走 fastpath ,还是 slowpath

fastpath : 从伙伴系统的空闲链表中获取 page

/* First allocation attempt */
    page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);

__alloc_pages_slowpath

slowpath : 对内存进行整理,迁移,压缩,交换,腾出大块连续的内存。会导致一定的性能问题。

page = __alloc_pages_slowpath(alloc_mask, order, &ac);

连续内存分配器:CMA

为什么要引入连续内存分配器:CMA

  1. 虽然伙伴系统也能够分配连续的物理内存,但是MAX_ORDER最大只能为11,也就是pageblock最大只能为4M。现在电子消费日新月异,比如手机拍照需要的内存是相当大的,所以如果需要的连续的物理内存大于4M的话,这个时候就必须CMA了。

  1. CMA和DMA的区别,在X86架构下,会专门预留一块16M的DMA内存,如果用户程序没有用到这块,那么这16M不就浪费了吗?

  1. 所以就引入了CMA,当用户没有用到这块CMA内存的时候,这块CMA内存就会自动移动到伙伴系统上,供其他程序使用。如果有用户用到这块内存的话,就不移动到伙伴系统上。

  1. CMA初始化:内核配置、设备树dts文件

其中,cma中这个成员变量的含义如下所示:

unsigned int order_per_bit; /* Order of pages represented by one bit */

order_per_bit =1 ==> 代表已分配4KB

order_per_bit =11 ==> 代表已分配4MB

/home/linux-5.10.179/arch/arm/boot/dts$ gedit vexpress-v2p-ca9.dts

我们可以看到,在设备树文件中,有一块reserved-memory 单独拿出来使用。

reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        /* Chipselect 3 is physically at 0x4c000000 */
        vram: vram@4c000000 {
            /* 8 MB of designated video RAM */
            compatible = "shared-dma-pool";
            reg = <0x4c000000 0x00800000>;  //起始地址 大小
            no-map;
        };
    };

内核会解析reg = <0x4c000000 0x00800000> 对应的起始地址,大小,

并对struct cma {

unsigned long base_pfn;

unsigned long count;

........

}

进行初始化。

伙伴系统初始化(一):memblock管理器

  1. 内核如何获取内存的地址,大小?

  1. 全局变量:struct memblock memblock;

  1. 可用的物理内存:memblock.memory 数组

  1. 保留 reverse的物理内存:memblock.reserved数组

  1. 内核镜像(.init段除外),dtb,u-boot,页表 都需要保留在物理内存中的。

  1. 像GPU,camera,音视频编解码 也需要保留一些物理内存的,供驱动程序使用的。

memblock接口

[root@vexpress ]# cd /sys/kernel/debug/
[root@vexpress debug]# cd memblock/
[root@vexpress memblock]# ls
memory    reserved
[root@vexpress memblock]# cat memory 
   0: 0x60000000..0x7fffffff
[root@vexpress memblock]# cat reserved 
   0: 0x60004000..0x60007fff
   1: 0x60100000..0x60df34a7
   2: 0x7e800000..0x7fbfffff
   3: 0x7fe6e000..0x7fe71fff
   4: 0x7ff1a000..0x7ffe6fff
   5: 0x7ffe9a98..0x7fffefff
   6: 0x7ffff0c0..0x7ffff483
   7: 0x7ffff4c0..0x7ffff81b
   8: 0x7ffff840..0x7ffff89f
   9: 0x7ffff8c0..0x7ffffa43
  10: 0x7ffffa80..0x7ffffc5b
  11: 0x7ffffc80..0x7ffffd0f
  12: 0x7ffffd40..0x7ffffd4f
  13: 0x7ffffd80..0x7ffffd83
  14: 0x7ffffdc0..0x7ffffe3c
  15: 0x7ffffe40..0x7ffffebc
  16: 0x7ffffec0..0x7ffffec3
  17: 0x7ffffed4..0x7fffff06
  18: 0x7fffff08..0x7fffff22
  19: 0x7fffff24..0x7fffff3e
  20: 0x7fffff40..0x7fffff5f
  21: 0x7fffff64..0x7fffff7e
  22: 0x7fffff80..0x7fffffff
[root@vexpress memblock]# 

memblock简单管理逻辑

memblock的初始化:

  1. 获取物理内存的起始地址和大小

  1. 初始化全局变量memblock的两个数组

伙伴系统初始化(二):memblock内存释放

memblock如何释放内存给伙伴系统?

内核源码分析:memblock_free_all

代码流程分析:

伙伴系统初始化(三):.init内存释放

memblock中的reserved memory,这些reserved memory 是不被伙伴系统管理的

  1. 内核的代码段(.text/.data/.bss)(.init除外)

  1. initrd

  1. dtb

  1. 设备树中的reserved-memory区域(CMA除外)

  1. 零时页表

  1. reserved memory的初始化

.init段内存释放

  1. 为什么要释放? 这些函数/变量 只被调用一次,相当于初始化一次。

  1. 里面包含了那些内容?在函数前面加了 __init 关键字的都是在 .init区的。

  1. 释放到哪里?释放到伙伴系统中。

伙伴系统初始化(四):CMA内存释放

CMA内存如何释放到伙伴系统

slab、slob和slub分配器

  1. 为什么要引入slab缓存:对伙伴系统的改进和补充。我们知道伙伴系统是以页(4K)为单位进行内存分配的,如果程序需要比较小的内存怎么办呢?比如,只需要32字节,64字节,那么这个时候就需要引入slab了,slab专门存放这些小的字节快的。如下图所示:每个slab缓存切割成的内存快大小是不一样的。

  1. slab的工作机制

  1. 三种分配器:slab、slob、slub

slab核心数据结构关联

  1. kmem_cache、kmem_cache_cpu、kmem_cache_node

  1. 内存的申请和释放分析

可以通过这个命令:sudo cat /proc/slabinfo 来查看内核中 slab 的信息。

sudo cat /proc/slabinfo
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
AF_VSOCK              50     50   1280   25    8 : tunables    0    0    0 : slabdata      2      2      0
nfs4_layout_stateid      0      0    296   27    2 : tunables    0    0    0 : slabdata      0      0      0
nfsd4_delegations     32     32    248   16    1 : tunables    0    0    0 : slabdata      2      2      0
nfsd4_files           52     52    304   26    2 : tunables    0    0    0 : slabdata      2      2      0
nfsd4_lockowners       0      0    392   20    2 : tunables    0    0    0 : slabdata      0      0      0
nfsd4_openowners      18     18    432   18    2 : tunables    0    0    0 : slabdata      1      1      0
nfsd4_clients         25     25   1304   25    8 : tunables    0    0    0 : slabdata      1      1      0
rpc_inode_cache       69     69    704   23    4 : tunables    0    0    0 : slabdata      3      3      0
ext4_groupinfo_4k    336    336    192   21    1 : tunables    0    0    0 : slabdata     16     16      0
fsverity_info          0      0    256   16    1 : tunables    0    0    0 : slabdata      0      0      0
fscrypt_info           0      0    136   30    1 : tunables    0    0    0 : slabdata      0      0      0
MPTCPv6                0      0   2048   16    8 : tunables    0    0    0 : slabdata      0      0      0
ip6-frags             44     44    184   22    1 : tunables    0    0    0 : slabdata      2      2      0
PINGv6                 0      0   1216   26    8 : tunables    0    0    0 : slabdata      0      0      0
RAWv6                572    572   1216   26    8 : tunables    0    0    0 : slabdata     22     22      0
UDPv6                 96     96   1344   24    8 : tunables    0    0    0 : slabdata      4      4      0
tw_sock_TCPv6          0      0    248   16    1 : tunables    0    0    0 : slabdata      0      0      0
request_sock_TCPv6      0      0    304   26    2 : tunables    0    0    0 : slabdata      0      0      0
TCPv6                 52     52   2432   13    8 : tunables    0    0    0 : slabdata      4      4      0
kcopyd_job             0      0   3240   10    8 : tunables    0    0    0 : slabdata      0      0      0
dm_uevent              0      0   2888   11    8 : tunables    0    0    0 : slabdata      0      0      0
scsi_sense_cache    1504   1504    128   32    1 : tunables    0    0    0 : slabdata     47     47      0
mqueue_inode_cache     34     34    960   17    4 : tunables    0    0    0 : slabdata      2      2      0
fuse_request         104    104    152   26    1 : tunables    0    0    0 : slabdata      4      4      0
fuse_inode            38     38    832   19    4 : tunables    0    0    0 : slabdata      2      2      0
ecryptfs_inode_cache      0      0   1024   16    4 : tunables    0    0    0 : slabdata      0      0      0
ecryptfs_file_cache      0      0     16  256    1 : tunables    0    0    0 : slabdata      0      0      0
ecryptfs_auth_tok_list_item      0      0    832   19    4 : tunables    0    0    0 : slabdata      0      0      0
fat_inode_cache       21     21    776   21    4 : tunables    0    0    0 : slabdata      1      1      0
fat_cache              0      0     40  102    1 : tunables    0    0    0 : slabdata      0      0      0
squashfs_inode_cache     92     92    704   23    4 : tunables    0    0    0 : slabdata      4      4      0
jbd2_journal_head    952   1020    120   34    1 : tunables    0    0    0 : slabdata     30     30      0
jbd2_revoke_table_s    256    256     16  256    1 : tunables    0    0    0 : slabdata      1      1      0
ext4_fc_dentry_update      0      0     80   51    1 : tunables    0    0    0 : slabdata      0      0      0
ext4_inode_cache  110781 110781   1176   27    8 : tunables    0    0    0 : slabdata   4103   4103      0
ext4_allocation_context    112    112    144   28    1 : tunables    0    0    0 : slabdata      4      4      0
ext4_io_end          256    256     64   64    1 : tunables    0    0    0 : slabdata      4      4      0
ext4_pending_reservation    896    896     32  128    1 : tunables    0    0    0 : slabdata      7      7      0
ext4_extent_status  25602  25602     40  102    1 : tunables    0    0    0 : slabdata    251    251      0
mbcache              365    365     56   73    1 : tunables    0    0    0 : slabdata      5      5      0
kioctx                 0      0    576   28    4 : tunables    0    0    0 : slabdata      0      0      0
userfaultfd_ctx_cache      0      0    192   21    1 : tunables    0    0    0 : slabdata      0      0      0
dnotify_struct       128    128     32  128    1 : tunables    0    0    0 : slabdata      1      1      0
pid_namespace         30     30    136   30    1 : tunables    0    0    0 : slabdata      1      1      0
UNIX                 926    960   1088   30    8 : tunables    0    0    0 : slabdata     32     32      0
ip4-frags              0      0    200   20    1 : tunables    0    0    0 : slabdata      0      0      0
MPTCP                  0      0   1920   17    8 : tunables    0    0    0 : slabdata      0      0      0
request_sock_subflow_v6      0      0    376   21    2 : tunables    0    0    0 : slabdata      0      0      0
request_sock_subflow_v4      0      0    376   21    2 : tunables    0    0    0 : slabdata      0      0      0
xfrm_dst_cache         0      0    320   25    2 : tunables    0    0    0 : slabdata      0      0      0
xfrm_state             0      0    768   21    4 : tunables    0    0    0 : slabdata      0      0      0
ip_fib_trie          340    340     48   85    1 : tunables    0    0    0 : slabdata      4      4      0
ip_fib_alias         292    292     56   73    1 : tunables    0    0    0 : slabdata      4      4      0
PING                   0      0   1024   16    4 : tunables    0    0    0 : slabdata      0      0      0
RAW                  672    672   1024   16    4 : tunables    0    0    0 : slabdata     42     42      0
tw_sock_TCP           32     32    248   16    1 : tunables    0    0    0 : slabdata      2      2      0
request_sock_TCP      52     52    304   26    2 : tunables    0    0    0 : slabdata      2      2      0
TCP                   56     56   2240   14    8 : tunables    0    0    0 : slabdata      4      4      0
hugetlbfs_inode_cache     48     48    664   24    4 : tunables    0    0    0 : slabdata      2      2      0
dquot                 80     80    256   16    1 : tunables    0    0    0 : slabdata      5      5      0
ep_head             1024   1024     16  256    1 : tunables    0    0    0 : slabdata      4      4      0
dax_cache             19     19    832   19    4 : tunables    0    0    0 : slabdata      1      1      0
bio_crypt_ctx        204    204     40  102    1 : tunables    0    0    0 : slabdata      2      2      0
request_queue         60     60   2128   15    8 : tunables    0    0    0 : slabdata      4      4      0
biovec-max           116    136   4096    8    8 : tunables    0    0    0 : slabdata     17     17      0
biovec-128            64     64   2048   16    8 : tunables    0    0    0 : slabdata      4      4      0
biovec-64             64     64   1024   16    4 : tunables    0    0    0 : slabdata      4      4      0
khugepaged_mm_slot    108    108    112   36    1 : tunables    0    0    0 : slabdata      3      3      0
user_namespace        78     78    624   26    4 : tunables    0    0    0 : slabdata      3      3      0
dmaengine-unmap-256     15     15   2112   15    8 : tunables    0    0    0 : slabdata      1      1      0
dmaengine-unmap-128     30     30   1088   30    8 : tunables    0    0    0 : slabdata      1      1      0
sock_inode_cache    2071   2071    832   19    4 : tunables    0    0    0 : slabdata    109    109      0
skbuff_ext_cache     105    105    192   21    1 : tunables    0    0    0 : slabdata      5      5      0
skbuff_fclone_cache     80     80    512   16    2 : tunables    0    0    0 : slabdata      5      5      0
skbuff_head_cache    464    464    256   16    1 : tunables    0    0    0 : slabdata     29     29      0
file_lock_cache       72     72    216   18    1 : tunables    0    0    0 : slabdata      4      4      0
file_lock_ctx        365    365     56   73    1 : tunables    0    0    0 : slabdata      5      5      0
fsnotify_mark_connector   6016   6016     32  128    1 : tunables    0    0    0 : slabdata     47     47      0
buffer_head        64155  64155    104   39    1 : tunables    0    0    0 : slabdata   1645   1645      0
x86_lbr                0      0    800   20    4 : tunables    0    0    0 : slabdata      0      0      0
taskstats             92     92    352   23    2 : tunables    0    0    0 : slabdata      4      4      0
proc_dir_entry      1911   1911    192   21    1 : tunables    0    0    0 : slabdata     91     91      0
pde_opener           408    408     40  102    1 : tunables    0    0    0 : slabdata      4      4      0
proc_inode_cache   10213  10580    712   23    4 : tunables    0    0    0 : slabdata    460    460      0
seq_file             136    136    120   34    1 : tunables    0    0    0 : slabdata      4      4      0
sigqueue            6018   6018     80   51    1 : tunables    0    0    0 : slabdata    118    118      0
bdev_cache            80     80   1600   20    8 : tunables    0    0    0 : slabdata      4      4      0
shmem_inode_cache   1962   2079    760   21    4 : tunables    0    0    0 : slabdata     99     99      0
kernfs_node_cache  67164  67392    128   32    1 : tunables    0    0    0 : slabdata   2106   2106      0
mnt_cache           2181   2275    320   25    2 : tunables    0    0    0 : slabdata     91     91      0
filp                9026   9600    256   16    1 : tunables    0    0    0 : slabdata    600    600      0
inode_cache        45386  45550    640   25    4 : tunables    0    0    0 : slabdata   1822   1822      0
dentry            208134 208278    192   21    1 : tunables    0    0    0 : slabdata   9918   9918      0
names_cache           80     80   4096    8    8 : tunables    0    0    0 : slabdata     10     10      0
net_namespace         28     28   4352    7    8 : tunables    0    0    0 : slabdata      4      4      0
iint_cache             0      0    120   34    1 : tunables    0    0    0 : slabdata      0      0      0
lsm_file_cache    179860 179860     24  170    1 : tunables    0    0    0 : slabdata   1058   1058      0
uts_namespace         54     54    432   18    2 : tunables    0    0    0 : slabdata      3      3      0
nsproxy              224    224     72   56    1 : tunables    0    0    0 : slabdata      4      4      0
vm_area_struct     44592  44840    208   19    1 : tunables    0    0    0 : slabdata   2360   2360      0
mm_struct            240    240   1088   30    8 : tunables    0    0    0 : slabdata      8      8      0
files_cache          368    368    704   23    4 : tunables    0    0    0 : slabdata     16     16      0
signal_cache         756    756   1152   28    8 : tunables    0    0    0 : slabdata     27     27      0
sighand_cache        615    615   2112   15    8 : tunables    0    0    0 : slabdata     41     41      0
task_struct          918    965   6400    5    8 : tunables    0    0    0 : slabdata    193    193      0
cred_jar            1554   1554    192   21    1 : tunables    0    0    0 : slabdata     74     74      0
anon_vma_chain     21374  22272     64   64    1 : tunables    0    0    0 : slabdata    348    348      0
anon_vma           13264  13767    104   39    1 : tunables    0    0    0 : slabdata    353    353      0
pid                 1856   1856    128   32    1 : tunables    0    0    0 : slabdata     58     58      0
Acpi-Operand       11200  11200     72   56    1 : tunables    0    0    0 : slabdata    200    200      0
Acpi-ParseExt        156    156    104   39    1 : tunables    0    0    0 : slabdata      4      4      0
Acpi-State           816    816     80   51    1 : tunables    0    0    0 : slabdata     16     16      0
numa_policy          155    155    264   31    2 : tunables    0    0    0 : slabdata      5      5      0
perf_event            27     27   1208   27    8 : tunables    0    0    0 : slabdata      1      1      0
trace_event_file    3496   3496     88   46    1 : tunables    0    0    0 : slabdata     76     76      0
ftrace_event_field  12920  12920     48   85    1 : tunables    0    0    0 : slabdata    152    152      0
pool_workqueue       688    688    256   16    1 : tunables    0    0    0 : slabdata     43     43      0
radix_tree_node    25508  25508    584   28    4 : tunables    0    0    0 : slabdata    911    911      0
task_group           150    150    640   25    4 : tunables    0    0    0 : slabdata      6      6      0
vmap_area           3679   4352     64   64    1 : tunables    0    0    0 : slabdata     68     68      0
dma-kmalloc-8k         0      0   8192    4    8 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-4k         0      0   4096    8    8 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-2k         0      0   2048   16    8 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-1k         0      0   1024   16    4 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-512        0      0    512   16    2 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-256        0      0    256   16    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-128        0      0    128   32    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-64         0      0     64   64    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-32         0      0     32  128    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-16         0      0     16  256    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-8          0      0      8  512    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-192        0      0    192   21    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-96         0      0     96   42    1 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-8k         0      0   8192    4    8 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-4k         0      0   4096    8    8 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-2k         0      0   2048   16    8 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-1k         0      0   1024   16    4 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-512        0      0    512   16    2 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-256        0      0    256   16    1 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-192        0      0    192   21    1 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-128      224    224    128   32    1 : tunables    0    0    0 : slabdata      7      7      0
kmalloc-rcl-96       546    546     96   42    1 : tunables    0    0    0 : slabdata     13     13      0
kmalloc-rcl-64      1984   1984     64   64    1 : tunables    0    0    0 : slabdata     31     31      0
kmalloc-rcl-32         0      0     32  128    1 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-16         0      0     16  256    1 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-8          0      0      8  512    1 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-cg-8k         16     16   8192    4    8 : tunables    0    0    0 : slabdata      4      4      0
kmalloc-cg-4k         69     88   4096    8    8 : tunables    0    0    0 : slabdata     11     11      0
kmalloc-cg-2k        160    160   2048   16    8 : tunables    0    0    0 : slabdata     10     10      0
kmalloc-cg-1k        495    544   1024   16    4 : tunables    0    0    0 : slabdata     34     34      0
kmalloc-cg-512       400    400    512   16    2 : tunables    0    0    0 : slabdata     25     25      0
kmalloc-cg-256       160    160    256   16    1 : tunables    0    0    0 : slabdata     10     10      0
kmalloc-cg-192       252    252    192   21    1 : tunables    0    0    0 : slabdata     12     12      0
kmalloc-cg-128       128    128    128   32    1 : tunables    0    0    0 : slabdata      4      4      0
kmalloc-cg-96        168    168     96   42    1 : tunables    0    0    0 : slabdata      4      4      0
kmalloc-cg-64        512    512     64   64    1 : tunables    0    0    0 : slabdata      8      8      0
kmalloc-cg-32        512    512     32  128    1 : tunables    0    0    0 : slabdata      4      4      0
kmalloc-cg-16       2560   2560     16  256    1 : tunables    0    0    0 : slabdata     10     10      0
kmalloc-cg-8        2560   2560      8  512    1 : tunables    0    0    0 : slabdata      5      5      0
kmalloc-8k           160    168   8192    4    8 : tunables    0    0    0 : slabdata     42     42      0
kmalloc-4k          1724   1784   4096    8    8 : tunables    0    0    0 : slabdata    223    223      0
kmalloc-2k          2059   2112   2048   16    8 : tunables    0    0    0 : slabdata    132    132      0
kmalloc-1k          2167   2192   1024   16    4 : tunables    0    0    0 : slabdata    137    137      0
kmalloc-512        26994  27024    512   16    2 : tunables    0    0    0 : slabdata   1689   1689      0
kmalloc-256         9278   9312    256   16    1 : tunables    0    0    0 : slabdata    582    582      0
kmalloc-192        13390  13440    192   21    1 : tunables    0    0    0 : slabdata    640    640      0
kmalloc-128         3246   3392    128   32    1 : tunables    0    0    0 : slabdata    106    106      0
kmalloc-96          3059   3150     96   42    1 : tunables    0    0    0 : slabdata     75     75      0
kmalloc-64         13504  13504     64   64    1 : tunables    0    0    0 : slabdata    211    211      0
kmalloc-32         27759  28800     32  128    1 : tunables    0    0    0 : slabdata    225    225      0
kmalloc-16         14592  14592     16  256    1 : tunables    0    0    0 : slabdata     57     57      0
kmalloc-8          12800  12800      8  512    1 : tunables    0    0    0 : slabdata     25     25      0
kmem_cache_node      256    256     64   64    1 : tunables    0    0    0 : slabdata      4      4      0
kmem_cache           208    208    256   16    1 : tunables    0    0    0 : slabdata     13     13      0

slab编程接口

  1. 如何创建一个kmem_cache?

  1. 如何创建一个slab?

  1. 如何申请一个内存object?

  1. 如何释放一个内存object?

来看一个简单的demo

#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>

struct student {
    int id;
    int age;
    float score;
    void (*print_score)(int id);
    void (*print_age)(int id);
};

struct kmem_cache* stu_cache;
struct student* p;

void stu_ctor(void* p)
{
    ;  //init objects here
}

static int __init hello_init(void)
{
    stu_cache = kmem_cache_create("student",sizeof(struct student),0,SLAB_PANIC|SLAB_ACCOUNT,stu_ctor);
    BUG_ON(stu_cache == NULL);
    printk("stu_cache = %x\n",(unsigned int)&stu_cache);

    //printk("p object size = %x\n",sizeof(struct student));
    //printk("p object size = %d\n",sizeof(struct student));

    p = kmem_cache_alloc(stu_cache,GFP_KERNEL);
    if(p){
        printk("p object size = %x\n",sizeof(*p));
        printk("p object size = %d\n",sizeof(struct student));
    }

    return 0;
}

static void __exit hello_exit(void)
{
    kmem_cache_free(stu_cache,p);
    kmem_cache_destroy(stu_cache);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wj");
MODULE_DESCRIPTION("alloc and free pages from buddy system");

build

root@ubuntu:/home/work# make
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm -C /home/linux-5.10.179 M=/home/work modules
make[1]: Entering directory '/home/linux-5.10.179'
  CC [M]  /home/work/hello.o
  MODPOST /home/work/Module.symvers
  CC [M]  /home/work/hello.mod.o
  LD [M]  /home/work/hello.ko
make[1]: Leaving directory '/home/linux-5.10.179'
root@ubuntu:/home/work# cp hello.ko /home/nfs/
[root@vexpress ]# ls
a.out     etc       lib       proc      sys       var
bin       hello.c   linuxrc   root      tmp
dev       hello.ko  mnt       sbin      usr
[root@vexpress ]# insmod hello.
hello.c   hello.ko
[root@vexpress ]# insmod hello.ko
[ 6958.838589] hello: loading out-of-tree module taints kernel.
[ 6958.845054] stu_cache = 7f002280
[ 6958.845308] p object size = 14
[ 6958.845502] p object size = 20

可以看到,新定义出来的结构体在 在 /proc/slabinfo中。

kmalloc机制实现分析

  1. 为什么要引入kmalloc这个接口?因为slab提供的接口比较麻烦,kmalloc对slab接口进行了一次封装,让接口变得简单易用。

  1. kmalloc和slab,伙伴系统的关联

GFP_KERNEL : 可以睡眠,如果申请不到内存是可以睡眠等待的,等到有合适的内存快的时候,再被唤醒。

kmalloc实现源码分析:

定义在 mm/slab.c

void *kmalloc(size_t size, gfp_t flags)

static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
    if (__builtin_constant_p(size)) {
#ifndef CONFIG_SLOB
        unsigned int index;
#endif
        if (size > KMALLOC_MAX_CACHE_SIZE) //如果申请内存大于8KB,直接从伙伴系统中申请物理内存
            return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
        index = kmalloc_index(size);

        if (!index)
            return ZERO_SIZE_PTR;

        return kmem_cache_alloc_trace(
                kmalloc_caches[kmalloc_type(flags)][index],
                flags, size);
#endif
    }
    return __kmalloc(size, flags);
}

kmalloc 根据定义的大小以及类型不同,到slab 或者 伙伴系统中去拿内存。

notes:

  1. kmalloc能申请的最大内存是多少?是MAXORDER-1 那个阶数的 大小,也就是一个pageblock,大小为4M。

  1. kmalloc返回的地址对齐方式?

  1. kmalloc返回的是虚拟地址,还是物理地址?虚拟地址

来看一个简单的demo

#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>

struct student {
    int age;
    char sex;
    char name[10];
    float score;
};

struct student* p;

static int __init hello_init(void)
{
    p = kmalloc(sizeof(struct student),GFP_KERNEL);
    if(!p){
        return -ENOMEM;
    }
    printk("p = %x\n",(unsigned int)p);

    return 0;
}

static void __exit hello_exit(void)
{
    kfree(p);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wj");
MODULE_DESCRIPTION("kmalloc/kfree demo");

build

root@ubuntu:/home/work# make
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm -C /home/linux-5.10.179 M=/home/work modules
make[1]: Entering directory '/home/linux-5.10.179'
  CC [M]  /home/work/hello.o
  MODPOST /home/work/Module.symvers
  LD [M]  /home/work/hello.ko
make[1]: Leaving directory '/home/linux-5.10.179'
root@ubuntu:/home/work# cp hello.ko /home/nfs/
[root@vexpress ]# insmod hello.ko
[   47.109983] hello: loading out-of-tree module taints kernel.
[   47.115833] p = 81bba540
[root@vexpress ]# rmmod hello.ko

kmalloc 不管是从 slub中拿到小内存(小于8KB),还是从伙伴系统中拿到大内存(大于8KB),最终都会调用 page_to_virt 将物理地址转化为虚拟地址。

虚拟地址和MMU工作原理

虚拟地址的基本概念

  1. 为什么需要虚拟地址?

  1. 虚拟地址怎么向物理地址转化的?

  1. 线性地址是什么?逻辑地址是什么?线性地址,逻辑地址是针对X86,32平台而言的;在64位系统上已经没有这两个概念了。

  1. 总线地址是什么?

  1. MMU的作用?

MMU工作原理

  1. 虚拟页号,物理页号

  1. 页表

  1. 使能MMU

  1. TLB、Table Walk Unit

虚拟地址到物理地址的转换原理。下图为了说明原理,简化了页表,实际操作系统中不可能这么简单。

二级页表的工作原理

二级页表的优势?

  1. 不需要大量连续的物理内存

  1. 一个进程不会映射所有的虚拟地址空间

  1. 随着页表级数增加,可以节省物理内存

实际的系统中用二级/三级页表比较多一点。

二级页表下的地址转换:

揭开页表神秘的面纱(上)

  1. 页表存储在内存的什么地方?

  1. 页表的格式是怎么样的?

  1. 不同的映射方式(section/page),页表有什么变化?

  1. 页表的初始化过程分析

  1. 新建一个页表项的过程分析

以section进行映射

思考:既然section映射可以节省内存,为什么内核启动后还要以page为单位进行二级映射呢?

  1. 因为内存管理的粒度是以物理内存page(4k)为单位的,这样就可以很高效的完成从物理地址到虚拟地址的转化。

  1. 在内核刚刚加载起来的时候,会创建零食的页表,这个阶段还是以section进行映射的,完成各种初始化操作。然后再进行二级映射。

揭开页表神秘的面纱(下)

  1. 使能MMU之前,页表要准备好

  1. 页表的创建过程分析:__create_page_tables

  1. 页表的大小,用途? 页表在内存中的地址?页表的创建过程?

一级页表映射:section

TLB 和 Table Walk Unit

Linux虚拟内存管理

32位系统下的虚拟内存经典布局,如下图所示:

虚拟内存划分

  1. 用户空间和内核空间划分:3:1、2:2、1:3 。思考:为什么会有不同的划分比例?

  1. 内核虚拟空间划分:线性映射区、vmalloc、fixmap、pkmap、modules等

ARM32下的内存布局:

思考:为什么要将虚拟内存空间划分为不同的区域?为了分类管理,需要那个区就映射那个区就行,如果不需要这个区,那么就不用配置这个区就行,这样就比较灵活。

虚拟内存管理:线性映射区

  1. PAGE_OFFSET

  1. PHYS_OFFSET

  1. 使用kmalloc(...) 、伙伴系统管理的区域都是线性映射区

地址转换

__virt_to_phys 这个宏代表的含义是: 虚拟地址转化为物理地址。

来看一下具体的demo

#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>

struct student {
    int age;
    char sex;
    char name[10];
    float score;
};

struct student* p;

static int __init hello_init(void)
{
    p = kmalloc(sizeof(struct student),GFP_KERNEL);
    if(!p){
        return -ENOMEM;
    }
    printk("p = %x\n",(unsigned int)p);

    printk("phys p =0x%x\n",(unsigned int)__virt_to_phys((unsigned int)p));

    return 0;
}

static void __exit hello_exit(void)
{
    kfree(p);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wj");
MODULE_DESCRIPTION("kmalloc/kfree demo");

build

root@ubuntu:/home/work# make
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm -C /home/linux-5.10.179 M=/home/work modules
make[1]: Entering directory '/home/linux-5.10.179'
  CC [M]  /home/work/hello.o
  MODPOST /home/work/Module.symvers
  LD [M]  /home/work/hello.ko
make[1]: Leaving directory '/home/linux-5.10.179'
root@ubuntu:/home/work# cp hello.ko /home/nfs/
[root@vexpress ]# insmod hello.ko
[   16.822739] hello: loading out-of-tree module taints kernel.
[   16.838306] p = 81b934c0
[   16.838988] phys p =0x61b934c0

低端内存和高端内存的边界划分

线性映射区的大小

  1. 低端内存和高端内存如何划分?

  1. PAGE_OFFSET 设置不同时,会影响划分吗?

  1. 物理内存1GB,对应的虚拟内存布局是怎么样的?

  1. 物理内存3GB,对应的虚拟内存布局是怎么样的?

结论:

  1. 线性映射区的大小,和PAGE_OFFSET和物理内存相关

  1. 3GB/1GB划分,[3G,3G+760MB]为线性映射区

  1. 2GB/2GB划分,[2G,2G+1760MB]为线性映射区

  1. 64位系统虚拟空间足够大,全部映射到物理内存

  1. ARM32/64 正在渐渐舍弃高端内存的概念...

arch/arm/mm/mmu.c

void __init adjust_lowmem_bounds(void) //根据实际物理内存的大小,来调整vmalloc区和线性映射区的比例大小。

void __init adjust_lowmem_bounds(void)
{
    phys_addr_t memblock_limit = 0;
    u64 vmalloc_limit;
    struct memblock_region *reg;
    phys_addr_t lowmem_limit = 0;

    /*
     * Let's use our own (unoptimized) equivalent of __pa() that is
     * not affected by wrap-arounds when sizeof(phys_addr_t) == 4.
     * The result is used as the upper bound on physical memory address
     * and may itself be outside the valid range for which phys_addr_t
     * and therefore __pa() is defined.
     */
    //vmalloc的物理起始地址
    vmalloc_limit = (u64)(uintptr_t)vmalloc_min - PAGE_OFFSET + PHYS_OFFSET;

    /*
     * The first usable region must be PMD aligned. Mark its start
     * as MEMBLOCK_NOMAP if it isn't
     */
    for_each_memblock(memory, reg) {
        if (!memblock_is_nomap(reg)) {
            if (!IS_ALIGNED(reg->base, PMD_SIZE)) {
                phys_addr_t len;

                len = round_up(reg->base, PMD_SIZE) - reg->base;
                memblock_mark_nomap(reg->base, len);
            }
            break;
        }
    }

    for_each_memblock(memory, reg) {
        phys_addr_t block_start = reg->base;
        phys_addr_t block_end = reg->base + reg->size;

        if (memblock_is_nomap(reg))
            continue;

        if (reg->base < vmalloc_limit) {
            if (block_end > lowmem_limit)
                /*
                 * Compare as u64 to ensure vmalloc_limit does
                 * not get truncated. block_end should always
                 * fit in phys_addr_t so there should be no
                 * issue with assignment.
                 */
                lowmem_limit = min_t(u64,
                             vmalloc_limit,
                             block_end);

            /*
             * Find the first non-pmd-aligned page, and point
             * memblock_limit at it. This relies on rounding the
             * limit down to be pmd-aligned, which happens at the
             * end of this function.
             *
             * With this algorithm, the start or end of almost any
             * bank can be non-pmd-aligned. The only exception is
             * that the start of the bank 0 must be section-
             * aligned, since otherwise memory would need to be
             * allocated when mapping the start of bank 0, which
             * occurs before any free memory is mapped.
             */
            if (!memblock_limit) {
                if (!IS_ALIGNED(block_start, PMD_SIZE))
                    memblock_limit = block_start;
                else if (!IS_ALIGNED(block_end, PMD_SIZE))
                    memblock_limit = lowmem_limit;
            }

        }
    }

    arm_lowmem_limit = lowmem_limit;

    high_memory = __va(arm_lowmem_limit - 1) + 1;

    if (!memblock_limit)
        memblock_limit = arm_lowmem_limit;

    /*
     * Round the memblock limit down to a pmd size.  This
     * helps to ensure that we will allocate memory from the
     * last full pmd, which should be mapped.
     */
    memblock_limit = round_down(memblock_limit, PMD_SIZE);

    if (!IS_ENABLED(CONFIG_HIGHMEM) || cache_is_vipt_aliasing()) {
        if (memblock_end_of_DRAM() > arm_lowmem_limit) {
            phys_addr_t end = memblock_end_of_DRAM();

            pr_notice("Ignoring RAM at %pa-%pa\n",
                  &memblock_limit, &end);
            pr_notice("Consider using a HIGHMEM enabled kernel.\n");

            memblock_remove(memblock_limit, end - memblock_limit);
        }
    }

    memblock_set_current_limit(memblock_limit);
}

//边界底线240M
static void * __initdata vmalloc_min =
    (void *)(VMALLOC_END - (240 << 20) - VMALLOC_OFFSET);

二级页表的创建过程分析(上)

二级页表的创建过程分析(中)

内存中的页表类型

  1. 前提:memblock已经被初始化

  1. 前提:页表的地址存放在哪里?

  1. ARM PTE : ARM MMU 硬件页表

  1. Linux PTE : 内核使用的软件页表

  1. 软件页表与硬件页表如何协同工作?

二级页表的创建过程分析(下)

//.........

//.........

虚拟内存管理:vmalloc区

vmalloc区

  1. kmalloc vs vmalloc

  1. 内存申请接口:vmalloc/vfree

  1. VMALLOC_START 到 VMALLOC_END 之间的一段区域

  1. 虚拟空间是连续的,物理空间可以不连续

  1. vmalloc主要用在什么地方?

  1. vmalloc最大能申请多大的内存

  1. vmalloc区的大小怎么计算的?默认大小呢?

  1. vmalloc区域的页表映射

  1. vmalloc区当用户真正需要使用到这片内存区域的时候,才会去建立映射。和上面的线性映射区不一样。

vmalloc实现机制分析

  1. 从VMALLOC_START 到 VMALLOC_END查找一片虚拟地址空间

  1. 根据内存的大小从伙伴系统申请多个物理页帧page

  1. 把每个申请到的物理页帧逐页映射到虚拟地址空间

我们来看一个简单的demo,看一下vmalloc的具体使用。

q = kmalloc(5*1024*1024,GFP_KERNEL); //因为我们知道,kmalloc是从伙伴系统,或者slub中去拿内存的,而伙伴系统最大的内存是一个 page_block 为4MB,当kmalloc申请的内存超过4MB,那么肯定会报错。

// kmalloc 最大的申请的内存为4MB,这样申请为5MB,肯定会报错。我们来做实验来具体看一下。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>

unsigned int* p = NULL;
unsigned int* q = NULL;

unsigned long vmalloc_to_pfn(const void*);

static int __init hello_init(void)
{
    unsigned int phys_addr;
    unsigned int pfn;
    int i;

/*
    p = vmalloc(5*1024*1024);
    if(!p){
        printk("vmalloc failed\n");
        return -ENOMEM;
    }else{
        for(i=0;i<100;i++){
            p = p+1024;  //test : change 1024 to 4096 8192
            pfn = vmalloc_to_pfn(p);
            phys_addr = (pfn<<12) | ((unsigned int)p & 0xfff);
            printk("virt_addr: %x pfn: %x phys_addr : %x\n",(unsigned int)p,pfn,(unsigned int)phys_addr);
        }
    }

*/
    printk("-------------------------------------------------------------------------------\n");

    q = kmalloc(5*1024*1024,GFP_KERNEL);  // 
    if(!q){
        printk("kmalloc failed\n");
        return -ENOMEM;
    }else{
        for(i=0;i<100;i++){
            q=q+1024;
            phys_addr = virt_to_phys(q);
            pfn = (unsigned long)phys_addr >> 12;  // page frame
            printk("virt_addr = %x pfn = %x phys_addr = %x\n",(unsigned int)q,pfn,(unsigned int)phys_addr);
        }
    }

    return 0;
}

static void __exit hello_exit(void)
{
    //vfree(p);
    kfree(q);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wj");
MODULE_DESCRIPTION("vmalloc/vfree demo");

build

root@ubuntu:/home/work# make
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm -C /home/linux-5.10.179 M=/home/work modules
make[1]: Entering directory '/home/linux-5.10.179'
  CC [M]  /home/work/hello.o
  MODPOST /home/work/Module.symvers
  LD [M]  /home/work/hello.ko
make[1]: Leaving directory '/home/linux-5.10.179'
root@ubuntu:/home/work# cp hello.ko /home/nfs/

可以看到已经出错了,打印如下backtarce出来了。

[root@vexpress ]# insmod hello.ko
[    7.499532] hello: loading out-of-tree module taints kernel.
[    7.504475] -------------------------------------------------------------------------------
[    7.505102] ------------[ cut here ]------------
[    7.505643] WARNING: CPU: 0 PID: 91 at mm/page_alloc.c:4975 __alloc_pages_nodemask+0x794/0x1158
[    7.506037] Modules linked in: hello(O+)
[    7.506548] CPU: 0 PID: 91 Comm: insmod Tainted: G           O      5.10.179 #1
[    7.506859] Hardware name: ARM-Versatile Express
[    7.507546] [<8010f62c>] (unwind_backtrace) from [<8010b418>] (show_stack+0x10/0x14)
[    7.508016] [<8010b418>] (show_stack) from [<808c5d10>] (dump_stack+0x98/0xac)
[    7.508376] [<808c5d10>] (dump_stack) from [<80120370>] (__warn+0x88/0xe4)
[    7.508656] [<80120370>] (__warn) from [<808c1d48>] (warn_slowpath_fmt+0x64/0xc8)
[    7.508998] [<808c1d48>] (warn_slowpath_fmt) from [<8025a94c>] (__alloc_pages_nodemask+0x794/0x1158)
[    7.509364] [<8025a94c>] (__alloc_pages_nodemask) from [<802352c8>] (kmalloc_order+0x30/0x90)
[    7.509659] [<802352c8>] (kmalloc_order) from [<80235344>] (kmalloc_order_trace+0x1c/0xcc)
[    7.510354] [<80235344>] (kmalloc_order_trace) from [<7f005020>] (hello_init+0x20/0x1000 [hello])
[    7.511180] [<7f005020>] (hello_init [hello]) from [<80102018>] (do_one_initcall+0x58/0x260)
[    7.511748] [<80102018>] (do_one_initcall) from [<801acf80>] (do_init_module+0x44/0x204)
[    7.512093] [<801acf80>] (do_init_module) from [<801af444>] (load_module+0x2204/0x2610)
[    7.512298] [<801af444>] (load_module) from [<801af994>] (sys_init_module+0x144/0x184)
[    7.512676] [<801af994>] (sys_init_module) from [<80100060>] (ret_fast_syscall+0x0/0x54)
[    7.513052] Exception stack(0x81b65fa8 to 0x81b65ff0)
[    7.513430] 5fa0:                   00000000 000161d8 002154d0 000161d8 001fdfd0 00000000
[    7.513892] 5fc0: 00000000 000161d8 00000000 00000080 7ece6e48 7ece6e4c 001fdfd0 001e967c
[    7.514385] 5fe0: 7ece6b18 7ece6b08 000367d0 00011350
[    7.515088] ---[ end trace ce287e13abe96d16 ]---
[    7.515297] kmalloc failed
insmod: can't insert 'hello.ko': Cannot allocate memory

q = kmalloc(4*1024*1024,GFP_KERNEL); //我们把kmalloc 最大的申请的内存改为4MB,然后重新编译运行,可以看到得到如下结果。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>

unsigned int* p = NULL;
unsigned int* q = NULL;

unsigned long vmalloc_to_pfn(const void*);

static int __init hello_init(void)
{
    unsigned int phys_addr;
    unsigned int pfn;
    int i;

/*
    p = vmalloc(5*1024*1024);
    if(!p){
        printk("vmalloc failed\n");
        return -ENOMEM;
    }else{
        for(i=0;i<100;i++){
            p = p+1024;  //test : change 1024 to 4096 8192
            pfn = vmalloc_to_pfn(p);
            phys_addr = (pfn<<12) | ((unsigned int)p & 0xfff);
            printk("virt_addr: %x pfn: %x phys_addr : %x\n",(unsigned int)p,pfn,(unsigned int)phys_addr);
        }
    }

*/
    printk("-------------------------------------------------------------------------------\n");

    q = kmalloc(4*1024*1024,GFP_KERNEL);  // 
    if(!q){
        printk("kmalloc failed\n");
        return -ENOMEM;
    }else{
        for(i=0;i<100;i++){
            q=q+1024;
            phys_addr = virt_to_phys(q);
            pfn = (unsigned long)phys_addr >> 12;  // page frame
            printk("virt_addr = %x pfn = %x phys_addr = %x\n",(unsigned int)q,pfn,(unsigned int)phys_addr);
        }
    }

    return 0;
}

static void __exit hello_exit(void)
{
    //vfree(p);
    kfree(q);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wj");
MODULE_DESCRIPTION("vmalloc/vfree demo");

root@ubuntu:/home/work# make
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm -C /home/linux-5.10.179 M=/home/work modules
make[1]: Entering directory '/home/linux-5.10.179'
  CC [M]  /home/work/hello.o
  MODPOST /home/work/Module.symvers
  LD [M]  /home/work/hello.ko
make[1]: Leaving directory '/home/linux-5.10.179'
root@ubuntu:/home/work# cp hello.ko /home/nfs/

可以看到 页帧 pfn 是连续的,因为kmalloc申请的虚拟地址和物理地址之间的映射 只需要经过一定的偏差得到即可。

[root@vexpress ]# insmod hello.ko
[  364.926817] -------------------------------------------------------------------------------
[  364.927302] virt_addr = 82001000 pfn = 62001 phys_addr = 62001000
[  364.927578] virt_addr = 82002000 pfn = 62002 phys_addr = 62002000
[  364.927797] virt_addr = 82003000 pfn = 62003 phys_addr = 62003000
[  364.928124] virt_addr = 82004000 pfn = 62004 phys_addr = 62004000
[  364.928431] virt_addr = 82005000 pfn = 62005 phys_addr = 62005000
[  364.929028] virt_addr = 82006000 pfn = 62006 phys_addr = 62006000
[  364.929404] virt_addr = 82007000 pfn = 62007 phys_addr = 62007000
[  364.929714] virt_addr = 82008000 pfn = 62008 phys_addr = 62008000
[  364.930031] virt_addr = 82009000 pfn = 62009 phys_addr = 62009000
[  364.930358] virt_addr = 8200a000 pfn = 6200a phys_addr = 6200a000
[  364.930695] virt_addr = 8200b000 pfn = 6200b phys_addr = 6200b000
[  364.931032] virt_addr = 8200c000 pfn = 6200c phys_addr = 6200c000
[  364.931347] virt_addr = 8200d000 pfn = 6200d phys_addr = 6200d000
[  364.931705] virt_addr = 8200e000 pfn = 6200e phys_addr = 6200e000
[  364.932055] virt_addr = 8200f000 pfn = 6200f phys_addr = 6200f000
[  364.932413] virt_addr = 82010000 pfn = 62010 phys_addr = 62010000
[  364.932733] virt_addr = 82011000 pfn = 62011 phys_addr = 62011000
[  364.933421] virt_addr = 82012000 pfn = 62012 phys_addr = 62012000
[  364.934141] virt_addr = 82013000 pfn = 62013 phys_addr = 62013000
[  364.934867] virt_addr = 82014000 pfn = 62014 phys_addr = 62014000
[  364.935512] virt_addr = 82015000 pfn = 62015 phys_addr = 62015000
[  364.936806] virt_addr = 82016000 pfn = 62016 phys_addr = 62016000
[  364.937959] virt_addr = 82017000 pfn = 62017 phys_addr = 62017000
[  364.938380] virt_addr = 82018000 pfn = 62018 phys_addr = 62018000
[  364.938736] virt_addr = 82019000 pfn = 62019 phys_addr = 62019000
[  364.939875] virt_addr = 8201a000 pfn = 6201a phys_addr = 6201a000
[  364.941053] virt_addr = 8201b000 pfn = 6201b phys_addr = 6201b000
[  364.942180] virt_addr = 8201c000 pfn = 6201c phys_addr = 6201c000
[  364.943642] virt_addr = 8201d000 pfn = 6201d phys_addr = 6201d000
[  364.944103] virt_addr = 8201e000 pfn = 6201e phys_addr = 6201e000
[  364.944461] virt_addr = 8201f000 pfn = 6201f phys_addr = 6201f000
[  364.944974] virt_addr = 82020000 pfn = 62020 phys_addr = 62020000
[  364.946306] virt_addr = 82021000 pfn = 62021 phys_addr = 62021000
[  364.947039] virt_addr = 82022000 pfn = 62022 phys_addr = 62022000
[  364.948058] virt_addr = 82023000 pfn = 62023 phys_addr = 62023000
[  364.950375] virt_addr = 82024000 pfn = 62024 phys_addr = 62024000
[  364.951339] virt_addr = 82025000 pfn = 62025 phys_addr = 62025000
[  364.952487] virt_addr = 82026000 pfn = 62026 phys_addr = 62026000
[  364.953659] virt_addr = 82027000 pfn = 62027 phys_addr = 62027000
[  364.955012] virt_addr = 82028000 pfn = 62028 phys_addr = 62028000
[  364.955438] virt_addr = 82029000 pfn = 62029 phys_addr = 62029000
[  364.955798] virt_addr = 8202a000 pfn = 6202a phys_addr = 6202a000
[  364.956386] virt_addr = 8202b000 pfn = 6202b phys_addr = 6202b000
[  364.957824] virt_addr = 8202c000 pfn = 6202c phys_addr = 6202c000
[  364.959130] virt_addr = 8202d000 pfn = 6202d phys_addr = 6202d000
[  364.960591] virt_addr = 8202e000 pfn = 6202e phys_addr = 6202e000
[  364.961950] virt_addr = 8202f000 pfn = 6202f phys_addr = 6202f000
[  364.963131] virt_addr = 82030000 pfn = 62030 phys_addr = 62030000
[  364.963263] virt_addr = 82031000 pfn = 62031 phys_addr = 62031000
[  364.963644] virt_addr = 82032000 pfn = 62032 phys_addr = 62032000
[  364.964045] virt_addr = 82033000 pfn = 62033 phys_addr = 62033000
[  364.964231] virt_addr = 82034000 pfn = 62034 phys_addr = 62034000
[  364.964412] virt_addr = 82035000 pfn = 62035 phys_addr = 62035000
[  364.964586] virt_addr = 82036000 pfn = 62036 phys_addr = 62036000
[  364.964965] virt_addr = 82037000 pfn = 62037 phys_addr = 62037000
[  364.965311] virt_addr = 82038000 pfn = 62038 phys_addr = 62038000
[  364.965523] virt_addr = 82039000 pfn = 62039 phys_addr = 62039000
[  364.965735] virt_addr = 8203a000 pfn = 6203a phys_addr = 6203a000
[  364.966026] virt_addr = 8203b000 pfn = 6203b phys_addr = 6203b000
[  364.966356] virt_addr = 8203c000 pfn = 6203c phys_addr = 6203c000
[  364.966781] virt_addr = 8203d000 pfn = 6203d phys_addr = 6203d000
[  364.967187] virt_addr = 8203e000 pfn = 6203e phys_addr = 6203e000
[  364.967386] virt_addr = 8203f000 pfn = 6203f phys_addr = 6203f000
[  364.967795] virt_addr = 82040000 pfn = 62040 phys_addr = 62040000
[  364.968085] virt_addr = 82041000 pfn = 62041 phys_addr = 62041000
[  364.968521] virt_addr = 82042000 pfn = 62042 phys_addr = 62042000
[  364.968858] virt_addr = 82043000 pfn = 62043 phys_addr = 62043000
[  364.969243] virt_addr = 82044000 pfn = 62044 phys_addr = 62044000
[  364.969452] virt_addr = 82045000 pfn = 62045 phys_addr = 62045000
[  364.969675] virt_addr = 82046000 pfn = 62046 phys_addr = 62046000
[  364.969800] virt_addr = 82047000 pfn = 62047 phys_addr = 62047000
[  364.970437] virt_addr = 82048000 pfn = 62048 phys_addr = 62048000
[  364.970592] virt_addr = 82049000 pfn = 62049 phys_addr = 62049000
[  364.970777] virt_addr = 8204a000 pfn = 6204a phys_addr = 6204a000
[  364.971143] virt_addr = 8204b000 pfn = 6204b phys_addr = 6204b000
[  364.971270] virt_addr = 8204c000 pfn = 6204c phys_addr = 6204c000
[  364.971379] virt_addr = 8204d000 pfn = 6204d phys_addr = 6204d000
[  364.971487] virt_addr = 8204e000 pfn = 6204e phys_addr = 6204e000
[  364.971648] virt_addr = 8204f000 pfn = 6204f phys_addr = 6204f000
[  364.971763] virt_addr = 82050000 pfn = 62050 phys_addr = 62050000
[  364.971894] virt_addr = 82051000 pfn = 62051 phys_addr = 62051000
[  364.972199] virt_addr = 82052000 pfn = 62052 phys_addr = 62052000
[  364.972430] virt_addr = 82053000 pfn = 62053 phys_addr = 62053000
[  364.972559] virt_addr = 82054000 pfn = 62054 phys_addr = 62054000
[  364.972697] virt_addr = 82055000 pfn = 62055 phys_addr = 62055000
[  364.972835] virt_addr = 82056000 pfn = 62056 phys_addr = 62056000
[  364.973138] virt_addr = 82057000 pfn = 62057 phys_addr = 62057000
[  364.973499] virt_addr = 82058000 pfn = 62058 phys_addr = 62058000
[  364.973743] virt_addr = 82059000 pfn = 62059 phys_addr = 62059000
[  364.974163] virt_addr = 8205a000 pfn = 6205a phys_addr = 6205a000
[  364.974402] virt_addr = 8205b000 pfn = 6205b phys_addr = 6205b000
[  364.974665] virt_addr = 8205c000 pfn = 6205c phys_addr = 6205c000
[  364.974886] virt_addr = 8205d000 pfn = 6205d phys_addr = 6205d000
[  364.975207] virt_addr = 8205e000 pfn = 6205e phys_addr = 6205e000
[  364.975544] virt_addr = 8205f000 pfn = 6205f phys_addr = 6205f000
[  364.975891] virt_addr = 82060000 pfn = 62060 phys_addr = 62060000
[  364.976104] virt_addr = 82061000 pfn = 62061 phys_addr = 62061000
[  364.976356] virt_addr = 82062000 pfn = 62062 phys_addr = 62062000
[  364.976723] virt_addr = 82063000 pfn = 62063 phys_addr = 62063000
[  364.976987] virt_addr = 82064000 pfn = 62064 phys_addr = 62064000
[root@vexpress ]# 

我们再来看 vmalloc

#include <linux/init.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>

unsigned int* p = NULL;
unsigned int* q = NULL;

unsigned long vmalloc_to_pfn(const void*);

static int __init hello_init(void)
{
    unsigned int phys_addr;
    unsigned int pfn;
    int i;


    p = vmalloc(5*1024*1024); //vmalloc 申请的内存区域可以大于 4MB
    if(!p){
        printk("vmalloc failed\n");
        return -ENOMEM;
    }else{
        for(i=0;i<100;i++){
            p = p+1024;  //test : change 1024 to 4096 8192
            pfn = vmalloc_to_pfn(p);
            phys_addr = (pfn<<12) | ((unsigned int)p & 0xfff);
            printk("virt_addr: %x pfn: %x phys_addr : %x\n",(unsigned int)p,pfn,(unsigned int)phys_addr);
        }
    }


    printk("-------------------------------------------------------------------------------\n");

/*
    q = kmalloc(4*1024*1024,GFP_KERNEL);  // 
    if(!q){
        printk("kmalloc failed\n");
        return -ENOMEM;
    }else{
        for(i=0;i<100;i++){
            q=q+1024;
            phys_addr = virt_to_phys(q);
            pfn = (unsigned long)phys_addr >> 12;  // page frame
            printk("virt_addr = %x pfn = %x phys_addr = %x\n",(unsigned int)q,pfn,(unsigned int)phys_addr);
        }
    }
*/

    return 0;
}

static void __exit hello_exit(void)
{
    vfree(p);
    //kfree(q);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wj");
MODULE_DESCRIPTION("vmalloc/vfree demo");

build

root@ubuntu:/home/work# make
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm -C /home/linux-5.10.179 M=/home/work modules
make[1]: Entering directory '/home/linux-5.10.179'
  CC [M]  /home/work/hello.o
  MODPOST /home/work/Module.symvers
  LD [M]  /home/work/hello.ko
make[1]: Leaving directory '/home/linux-5.10.179'
root@ubuntu:/home/work# cp hello.ko /home/nfs/

可以看到物理 页帧pfn 不连续。vmalloc可以申请到的虚拟地址空间是非常大的。

[root@vexpress ]# insmod hello.ko
[   22.691711] hello: loading out-of-tree module taints kernel.
[   22.704806] virt_addr: ab382000 pfn: 61b6d phys_addr : 61b6d000
[   22.705235] virt_addr: ab383000 pfn: 61b6c phys_addr : 61b6c000
[   22.705558] virt_addr: ab384000 pfn: 61b6b phys_addr : 61b6b000
[   22.705853] virt_addr: ab385000 pfn: 61b6a phys_addr : 61b6a000
[   22.706161] virt_addr: ab386000 pfn: 61b69 phys_addr : 61b69000
[   22.706499] virt_addr: ab387000 pfn: 61b68 phys_addr : 61b68000
[   22.706805] virt_addr: ab388000 pfn: 61b9f phys_addr : 61b9f000
[   22.707112] virt_addr: ab389000 pfn: 61b9e phys_addr : 61b9e000
[   22.707435] virt_addr: ab38a000 pfn: 61b9d phys_addr : 61b9d000
[   22.707727] virt_addr: ab38b000 pfn: 61b9c phys_addr : 61b9c000
[   22.708019] virt_addr: ab38c000 pfn: 61b2f phys_addr : 61b2f000
[   22.708338] virt_addr: ab38d000 pfn: 61b2e phys_addr : 61b2e000
[   22.708643] virt_addr: ab38e000 pfn: 61b2d phys_addr : 61b2d000
[   22.708948] virt_addr: ab38f000 pfn: 61b2c phys_addr : 61b2c000
[   22.709267] virt_addr: ab390000 pfn: 61b53 phys_addr : 61b53000
[   22.709572] virt_addr: ab391000 pfn: 61b52 phys_addr : 61b52000
[   22.709876] virt_addr: ab392000 pfn: 61b55 phys_addr : 61b55000
[   22.710232] virt_addr: ab393000 pfn: 61b54 phys_addr : 61b54000
[   22.710529] virt_addr: ab394000 pfn: 61ab1 phys_addr : 61ab1000
[   22.710809] virt_addr: ab395000 pfn: 61ba9 phys_addr : 61ba9000
[   22.711087] virt_addr: ab396000 pfn: 61baa phys_addr : 61baa000
[   22.711428] virt_addr: ab397000 pfn: 61bab phys_addr : 61bab000
[   22.711741] virt_addr: ab398000 pfn: 61bac phys_addr : 61bac000
[   22.712052] virt_addr: ab399000 pfn: 61bad phys_addr : 61bad000
[   22.712693] virt_addr: ab39a000 pfn: 61bae phys_addr : 61bae000
[   22.713023] virt_addr: ab39b000 pfn: 61baf phys_addr : 61baf000
[   22.713421] virt_addr: ab39c000 pfn: 61bb0 phys_addr : 61bb0000
[   22.713753] virt_addr: ab39d000 pfn: 61bb1 phys_addr : 61bb1000
[   22.714114] virt_addr: ab39e000 pfn: 61bb2 phys_addr : 61bb2000
[   22.714610] virt_addr: ab39f000 pfn: 61bb3 phys_addr : 61bb3000
[   22.714936] virt_addr: ab3a0000 pfn: 61bb4 phys_addr : 61bb4000
[   22.715322] virt_addr: ab3a1000 pfn: 61bb5 phys_addr : 61bb5000
[   22.715631] virt_addr: ab3a2000 pfn: 61bb6 phys_addr : 61bb6000
[   22.715951] virt_addr: ab3a3000 pfn: 61bb7 phys_addr : 61bb7000
[   22.716295] virt_addr: ab3a4000 pfn: 61bb8 phys_addr : 61bb8000
[   22.716602] virt_addr: ab3a5000 pfn: 61bb9 phys_addr : 61bb9000
[   22.716916] virt_addr: ab3a6000 pfn: 61bba phys_addr : 61bba000
[   22.717229] virt_addr: ab3a7000 pfn: 61bbb phys_addr : 61bbb000
[   22.717521] virt_addr: ab3a8000 pfn: 61bbc phys_addr : 61bbc000
[   22.717827] virt_addr: ab3a9000 pfn: 61bbd phys_addr : 61bbd000
[   22.718137] virt_addr: ab3aa000 pfn: 61bbe phys_addr : 61bbe000
[   22.718463] virt_addr: ab3ab000 pfn: 61bbf phys_addr : 61bbf000
[   22.718755] virt_addr: ab3ac000 pfn: 61bc0 phys_addr : 61bc0000
[   22.719062] virt_addr: ab3ad000 pfn: 61bc1 phys_addr : 61bc1000
[   22.719363] virt_addr: ab3ae000 pfn: 61bc2 phys_addr : 61bc2000
[   22.719655] virt_addr: ab3af000 pfn: 61bc3 phys_addr : 61bc3000
[   22.720035] virt_addr: ab3b0000 pfn: 61bc4 phys_addr : 61bc4000
[   22.720437] virt_addr: ab3b1000 pfn: 61bc5 phys_addr : 61bc5000
[   22.720727] virt_addr: ab3b2000 pfn: 61bc6 phys_addr : 61bc6000
[   22.721001] virt_addr: ab3b3000 pfn: 61bc7 phys_addr : 61bc7000
[   22.721320] virt_addr: ab3b4000 pfn: 61bc8 phys_addr : 61bc8000
[   22.721811] virt_addr: ab3b5000 pfn: 61bc9 phys_addr : 61bc9000
[   22.722073] virt_addr: ab3b6000 pfn: 61bca phys_addr : 61bca000
[   22.722454] virt_addr: ab3b7000 pfn: 61bcb phys_addr : 61bcb000
[   22.722751] virt_addr: ab3b8000 pfn: 61bcc phys_addr : 61bcc000
[   22.723047] virt_addr: ab3b9000 pfn: 61bcd phys_addr : 61bcd000
[   22.723351] virt_addr: ab3ba000 pfn: 61bce phys_addr : 61bce000
[   22.723684] virt_addr: ab3bb000 pfn: 61bcf phys_addr : 61bcf000
[   22.723994] virt_addr: ab3bc000 pfn: 61bd0 phys_addr : 61bd0000
[   22.724325] virt_addr: ab3bd000 pfn: 61bd1 phys_addr : 61bd1000
[   22.724612] virt_addr: ab3be000 pfn: 61bd2 phys_addr : 61bd2000
[   22.724917] virt_addr: ab3bf000 pfn: 61bd3 phys_addr : 61bd3000
[   22.725281] virt_addr: ab3c0000 pfn: 61bd4 phys_addr : 61bd4000
[   22.725557] virt_addr: ab3c1000 pfn: 61bd5 phys_addr : 61bd5000
[   22.725879] virt_addr: ab3c2000 pfn: 61bd6 phys_addr : 61bd6000
[   22.726071] virt_addr: ab3c3000 pfn: 61bd7 phys_addr : 61bd7000
[   22.726442] virt_addr: ab3c4000 pfn: 61bd8 phys_addr : 61bd8000
[   22.726824] virt_addr: ab3c5000 pfn: 61bd9 phys_addr : 61bd9000
[   22.727139] virt_addr: ab3c6000 pfn: 61bda phys_addr : 61bda000
[   22.727459] virt_addr: ab3c7000 pfn: 61bdb phys_addr : 61bdb000
[   22.727768] virt_addr: ab3c8000 pfn: 61bdc phys_addr : 61bdc000
[   22.728075] virt_addr: ab3c9000 pfn: 61bdd phys_addr : 61bdd000
[   22.728455] virt_addr: ab3ca000 pfn: 61bde phys_addr : 61bde000
[   22.728764] virt_addr: ab3cb000 pfn: 61bdf phys_addr : 61bdf000
[   22.729071] virt_addr: ab3cc000 pfn: 61be0 phys_addr : 61be0000
[   22.729412] virt_addr: ab3cd000 pfn: 61be1 phys_addr : 61be1000
[   22.729717] virt_addr: ab3ce000 pfn: 61be2 phys_addr : 61be2000
[   22.730029] virt_addr: ab3cf000 pfn: 61be3 phys_addr : 61be3000
[   22.730337] virt_addr: ab3d0000 pfn: 61be4 phys_addr : 61be4000
[   22.730642] virt_addr: ab3d1000 pfn: 61be5 phys_addr : 61be5000
[   22.731360] virt_addr: ab3d2000 pfn: 61be6 phys_addr : 61be6000
[   22.731726] virt_addr: ab3d3000 pfn: 61be7 phys_addr : 61be7000
[   22.732054] virt_addr: ab3d4000 pfn: 61be8 phys_addr : 61be8000
[   22.732491] virt_addr: ab3d5000 pfn: 61be9 phys_addr : 61be9000
[   22.732801] virt_addr: ab3d6000 pfn: 61bea phys_addr : 61bea000
[   22.733092] virt_addr: ab3d7000 pfn: 61beb phys_addr : 61beb000
[   22.733413] virt_addr: ab3d8000 pfn: 61bec phys_addr : 61bec000
[   22.733719] virt_addr: ab3d9000 pfn: 61bed phys_addr : 61bed000
[   22.734083] virt_addr: ab3da000 pfn: 61bee phys_addr : 61bee000
[   22.734410] virt_addr: ab3db000 pfn: 61bef phys_addr : 61bef000
[   22.734717] virt_addr: ab3dc000 pfn: 61bf0 phys_addr : 61bf0000
[   22.735036] virt_addr: ab3dd000 pfn: 61bf1 phys_addr : 61bf1000
[   22.735339] virt_addr: ab3de000 pfn: 61bf2 phys_addr : 61bf2000
[   22.735645] virt_addr: ab3df000 pfn: 61bf3 phys_addr : 61bf3000
[   22.735951] virt_addr: ab3e0000 pfn: 61bf4 phys_addr : 61bf4000
[   22.736294] virt_addr: ab3e1000 pfn: 61bf5 phys_addr : 61bf5000
[   22.736585] virt_addr: ab3e2000 pfn: 61bf6 phys_addr : 61bf6000
[   22.736976] virt_addr: ab3e3000 pfn: 61bf7 phys_addr : 61bf7000
[   22.737342] virt_addr: ab3e4000 pfn: 61bf8 phys_addr : 61bf8000
[   22.737726] virt_addr: ab3e5000 pfn: 61bf9 phys_addr : 61bf9000
[   22.738034] -------------------------------------------------------------------------------

可以看到物理 页帧pfn 不连续,那么vmalloc 是怎么实现 虚拟地址 到物理地址这样的映射的呢??

vmalloc分配器

接下来,我们看一下具体的 call-flow

mm/vmalloc.c

void *vmalloc(unsigned long size)

/**
 * vmalloc - allocate virtually contiguous memory
 * @size:    allocation size
 *
 * Allocate enough pages to cover @size from the page level
 * allocator and map them into contiguous kernel virtual space.
 *
 * For tight control over page level allocator and protection flags
 * use __vmalloc() instead.
 *
 * Return: pointer to the allocated memory or %NULL on error
 */
void *vmalloc(unsigned long size)
{
    return __vmalloc_node(size, 1, GFP_KERNEL, NUMA_NO_NODE,
                __builtin_return_address(0));
}

__vmalloc_node

/**
 * __vmalloc_node - allocate virtually contiguous memory
 * @size:        allocation size
 * @align:        desired alignment
 * @gfp_mask:        flags for the page level allocator
 * @node:        node to use for allocation or NUMA_NO_NODE
 * @caller:        caller's return address
 *
 * Allocate enough pages to cover @size from the page level allocator with
 * @gfp_mask flags.  Map them into contiguous kernel virtual space.
 *
 * Reclaim modifiers in @gfp_mask - __GFP_NORETRY, __GFP_RETRY_MAYFAIL
 * and __GFP_NOFAIL are not supported
 *
 * Any use of gfp flags outside of GFP_KERNEL should be consulted
 * with mm people.
 *
 * Return: pointer to the allocated memory or %NULL on error
 */
void *__vmalloc_node(unsigned long size, unsigned long align,
                gfp_t gfp_mask, int node, const void *caller)
{
    return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
                gfp_mask, PAGE_KERNEL, 0, node, caller);
}

__vmalloc_node_range

/**
 * __vmalloc_node_range - allocate virtually contiguous memory
 * @size:          allocation size
 * @align:          desired alignment
 * @start:          vm area range start
 * @end:          vm area range end
 * @gfp_mask:          flags for the page level allocator
 * @prot:          protection mask for the allocated pages
 * @vm_flags:          additional vm area flags (e.g. %VM_NO_GUARD)
 * @node:          node to use for allocation or NUMA_NO_NODE
 * @caller:          caller's return address
 *
 * Allocate enough pages to cover @size from the page level
 * allocator with @gfp_mask flags.  Map them into contiguous
 * kernel virtual space, using a pagetable protection of @prot.
 *
 * Return: the address of the area or %NULL on failure
 */
void *__vmalloc_node_range(unsigned long size, unsigned long align,
            unsigned long start, unsigned long end, gfp_t gfp_mask,
            pgprot_t prot, unsigned long vm_flags, int node,
            const void *caller)
{
    struct vm_struct *area;
    void *addr;
    unsigned long real_size = size;

    size = PAGE_ALIGN(size);
    if (!size || (size >> PAGE_SHIFT) > totalram_pages())
        goto fail;
    //从VMALLOC_START 和 VMALLOC_END的虚拟地址范围,查找空闲的虚拟地址空间(hole)
    area = __get_vm_area_node(real_size, align, VM_ALLOC | VM_UNINITIALIZED |
                vm_flags, start, end, node, gfp_mask, caller);
    if (!area)
        goto fail;
    //为申请到的虚拟空间分配page物理页,以page为单位,并建立映射关系,填充页表项
    addr = __vmalloc_area_node(area, gfp_mask, prot, node);
    if (!addr)
        return NULL;

    /*
     * In this function, newly allocated vm_struct has VM_UNINITIALIZED
     * flag. It means that vm_struct is not fully initialized.
     * Now, it is fully initialized, so remove this flag here.
     */
    clear_vm_uninitialized_flag(area);

    kmemleak_vmalloc(area, size, gfp_mask);

    return addr;

fail:
    warn_alloc(gfp_mask, NULL,
              "vmalloc: allocation failure: %lu bytes", real_size);
    return NULL;
}
__get_vm_area_node
static struct vm_struct *__get_vm_area_node(unsigned long size,
        unsigned long align, unsigned long flags, unsigned long start,
        unsigned long end, int node, gfp_t gfp_mask, const void *caller)
{
    struct vmap_area *va;
    struct vm_struct *area;
    unsigned long requested_size = size;

    BUG_ON(in_interrupt());
    size = PAGE_ALIGN(size);
    if (unlikely(!size))
        return NULL;

    if (flags & VM_IOREMAP)
        align = 1ul << clamp_t(int, get_count_order_long(size),
                       PAGE_SHIFT, IOREMAP_MAX_ORDER);

    area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
    if (unlikely(!area))
        return NULL;

    if (!(flags & VM_NO_GUARD))
        size += PAGE_SIZE;

    va = alloc_vmap_area(size, align, start, end, node, gfp_mask);
    if (IS_ERR(va)) {
        kfree(area);
        return NULL;
    }

    kasan_unpoison_vmalloc((void *)va->va_start, requested_size);

    setup_vmalloc_vm(area, va, flags, caller);

    return area;
}
__vmalloc_area_node
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
                 pgprot_t prot, int node)
{
    const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
    unsigned int nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
    unsigned int array_size = nr_pages * sizeof(struct page *), i;
    struct page **pages;

    gfp_mask |= __GFP_NOWARN;
    if (!(gfp_mask & (GFP_DMA | GFP_DMA32)))
        gfp_mask |= __GFP_HIGHMEM;

    /* Please note that the recursion is strictly bounded. */
    if (array_size > PAGE_SIZE) {
        pages = __vmalloc_node(array_size, 1, nested_gfp, node,
                    area->caller);
    } else {
        pages = kmalloc_node(array_size, nested_gfp, node);
    }

    if (!pages) {
        remove_vm_area(area->addr);
        kfree(area);
        return NULL;
    }

    area->pages = pages;
    area->nr_pages = nr_pages;

    for (i = 0; i < area->nr_pages; i++) {
        struct page *page;

        if (node == NUMA_NO_NODE)
            page = alloc_page(gfp_mask);
        else
            page = alloc_pages_node(node, gfp_mask, 0);

        if (unlikely(!page)) {
            /* Successfully allocated i pages, free them in __vfree() */
            area->nr_pages = i;
            atomic_long_add(area->nr_pages, &nr_vmalloc_pages);
            goto fail;
        }
        area->pages[i] = page;
        if (gfpflags_allow_blocking(gfp_mask))
            cond_resched();
    }
    atomic_long_add(area->nr_pages, &nr_vmalloc_pages);

    if (map_kernel_range((unsigned long)area->addr, get_vm_area_size(area),
            prot, pages) < 0)
        goto fail;

    return area->addr;

fail:
    warn_alloc(gfp_mask, NULL,
              "vmalloc: allocation failure, allocated %ld of %ld bytes",
              (area->nr_pages*PAGE_SIZE), area->size);
    __vfree(area->addr);
    return NULL;
}

寄存器映射:ioremap

ioremap 和 vmalloc 有些类似,

不同的是:vmalloc是分配一片虚拟内存,然后分配一片物理内存,然后将虚拟内存和物理内存进行映射。

ioremap : 也是会分配一片虚拟内存,但是不会分配物理内存,直接将虚拟内存和寄存器进行映射。

高端内存映射

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

repinkply

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值