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

物理内存由页分配器(page allocator)接管。
内存块的申请、释放过程。
伙伴算法、阶数。
2^0 为1 ,链表上存放的是一个page(4KB)的大小;2^1为 2,链表上存放的是2个page(8KB)的大小;依次类推...;MAX_ORDER 为 11,2^10为1024,所以链表上最大为4MB,如上图中的pageblock就为4MB。
系统刚刚开始的时候,伙伴系统里面什么都没有。伙伴系统用链表来管理4MB的大小的内存快。
伙伴系统是将每个zone(分区)中绝大部分物理页放到伙伴系统中去进行管理的。
每个链表的表头是存放在struct free_area 这个结构体中的,其中 nr_free 代表每个链表中内存快的个数。

从上面的虚拟机上可以看到:
X86系统上,只有一个Node,有三个物理内存分区,DMA,DMA32,Normal,可以看到阶数为11。
然后可以看到在DMA分区下,存放4MB链表上有3个,存放2MB链表上有1个,依次类推...
可以看到在DMA32分区下,存放4MB链表上有0个,.....,存放4KB链表上有2919个。
可以看到在Normal分区下,存放4MB链表上有0个,.....,存放4KB链表上有3061个。
不同的平台上,分区的配置是不一样的,上面是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
};
查看页面类型:cat /proc/pagetypeinfo
MIGRATE_MOVABLE 可以移动的:用户进程申请的内存;用户进程占用一片物理内存,但是这个时候另外一个进程需要很大一块物理内存,那么这个时候系统就会把之前进程占用的那一片物理内存移动到其他地方中去,为后来这个进程腾出来一大片物理内存空间。那么这样的页面就是可以移动的。
MIGRATE_RECLAIMABLE 可回收的:文件系统的page cache
MIGRATE_UNMOVABLE 不可移动的:内核镜像区的物理内存;比如运行一个程序加载进入内存,在编译链接的时候代码地址就是确定的,那么存放代码的这部分内存就是不可移动的。内核中很多物理地址,都是fireware中固定好的,是不能改变的,所以要求它所对应的页是不可移动的。
为什么引入迁移类型?
伙伴系统存在的问题:我们知道伙伴系统可以自动的进行页面合并,在很大的程度上可以避免内存碎片。但是也存在一些极端情况,比如,就是某些时候,需要一大块的连续内存,而这个时候物理内存页快已经满足不了。
引入迁移类型目的就是为了改进伙伴系统。
page migratetype :不同类型的页面分类存储
memory compaction :内存碎片整理,如下图所示,假如,我们需要连续4个页,但是现在已经没有连续4个页了,这个时候如果页面都是不可移动的,那么申请内存肯定会失败。
memory compact 的几种方式:COMPACT_PRIO_SYNC_FULL : 以同步的方式压缩和迁移;COMPACT_PRIO_SYNC_LIGHT :压缩同步,迁移异步;COMPACT_PRIO_SYNC_ASYNC,以异步方式压缩和迁移。
什么时候会触发memory compaction? 1.没有申请到内存的时候。2.当内存阈值不够的时候,后台的 kcompaction守护线程 会被唤醒,会执行内存迁移的操作。 3.memory compaction开销是挺大的,对性能有一定的影响。
Linux下可以这样查看,ls /proc/sys/vm | grep compact_memory

Per-CPU页帧缓存
per-cpu页帧缓存也是对伙伴系统的一种完善和补充。
pcp : per CPU pages
单个物理页的申请与释放
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是伙伴系统提供的页分配内存、页释放内存的接口。
头文件 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
虽然伙伴系统也能够分配连续的物理内存,但是MAX_ORDER最大只能为11,也就是pageblock最大只能为4M。现在电子消费日新月异,比如手机拍照需要的内存是相当大的,所以如果需要的连续的物理内存大于4M的话,这个时候就必须CMA了。
CMA和DMA的区别,在X86架构下,会专门预留一块16M的DMA内存,如果用户程序没有用到这块,那么这16M不就浪费了吗?
所以就引入了CMA,当用户没有用到这块CMA内存的时候,这块CMA内存就会自动移动到伙伴系统上,供其他程序使用。如果有用户用到这块内存的话,就不移动到伙伴系统上。
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管理器
内核如何获取内存的地址,大小?
全局变量:struct memblock memblock;
可用的物理内存:memblock.memory 数组
保留 reverse的物理内存:memblock.reserved数组
内核镜像(.init段除外),dtb,u-boot,页表 都需要保留在物理内存中的。
像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的初始化:
获取物理内存的起始地址和大小
初始化全局变量memblock的两个数组


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

memblock如何释放内存给伙伴系统?
内核源码分析:memblock_free_all

代码流程分析:
伙伴系统初始化(三):.init内存释放
memblock中的reserved memory,这些reserved memory 是不被伙伴系统管理的
内核的代码段(.text/.data/.bss)(.init除外)
initrd
dtb
设备树中的reserved-memory区域(CMA除外)
零时页表
reserved memory的初始化
.init段内存释放
为什么要释放? 这些函数/变量 只被调用一次,相当于初始化一次。
里面包含了那些内容?在函数前面加了 __init 关键字的都是在 .init区的。
释放到哪里?释放到伙伴系统中。

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


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

slab核心数据结构关联
kmem_cache、kmem_cache_cpu、kmem_cache_node
内存的申请和释放分析

可以通过这个命令: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编程接口
如何创建一个kmem_cache?
如何创建一个slab?
如何申请一个内存object?
如何释放一个内存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机制实现分析
为什么要引入kmalloc这个接口?因为slab提供的接口比较麻烦,kmalloc对slab接口进行了一次封装,让接口变得简单易用。
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:
kmalloc能申请的最大内存是多少?是MAXORDER-1 那个阶数的 大小,也就是一个pageblock,大小为4M。
kmalloc返回的地址对齐方式?
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工作原理
虚拟地址的基本概念
为什么需要虚拟地址?
虚拟地址怎么向物理地址转化的?
线性地址是什么?逻辑地址是什么?线性地址,逻辑地址是针对X86,32平台而言的;在64位系统上已经没有这两个概念了。
总线地址是什么?
MMU的作用?

MMU工作原理
虚拟页号,物理页号
页表
使能MMU
TLB、Table Walk Unit


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

二级页表的工作原理
二级页表的优势?
不需要大量连续的物理内存
一个进程不会映射所有的虚拟地址空间
随着页表级数增加,可以节省物理内存

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

二级页表下的地址转换:

揭开页表神秘的面纱(上)
页表存储在内存的什么地方?
页表的格式是怎么样的?
不同的映射方式(section/page),页表有什么变化?
页表的初始化过程分析
新建一个页表项的过程分析
以section进行映射




思考:既然section映射可以节省内存,为什么内核启动后还要以page为单位进行二级映射呢?
因为内存管理的粒度是以物理内存page(4k)为单位的,这样就可以很高效的完成从物理地址到虚拟地址的转化。
在内核刚刚加载起来的时候,会创建零食的页表,这个阶段还是以section进行映射的,完成各种初始化操作。然后再进行二级映射。
揭开页表神秘的面纱(下)
使能MMU之前,页表要准备好
页表的创建过程分析:__create_page_tables
页表的大小,用途? 页表在内存中的地址?页表的创建过程?


一级页表映射:section


TLB 和 Table Walk Unit


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

虚拟内存划分
用户空间和内核空间划分:3:1、2:2、1:3 。思考:为什么会有不同的划分比例?
内核虚拟空间划分:线性映射区、vmalloc、fixmap、pkmap、modules等
ARM32下的内存布局:

思考:为什么要将虚拟内存空间划分为不同的区域?为了分类管理,需要那个区就映射那个区就行,如果不需要这个区,那么就不用配置这个区就行,这样就比较灵活。
虚拟内存管理:线性映射区
PAGE_OFFSET
PHYS_OFFSET
使用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
低端内存和高端内存的边界划分
线性映射区的大小
低端内存和高端内存如何划分?
PAGE_OFFSET 设置不同时,会影响划分吗?
物理内存1GB,对应的虚拟内存布局是怎么样的?
物理内存3GB,对应的虚拟内存布局是怎么样的?
结论:
线性映射区的大小,和PAGE_OFFSET和物理内存相关
3GB/1GB划分,[3G,3G+760MB]为线性映射区
2GB/2GB划分,[2G,2G+1760MB]为线性映射区
64位系统虚拟空间足够大,全部映射到物理内存
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);
二级页表的创建过程分析(上)
二级页表的创建过程分析(中)
内存中的页表类型
前提:memblock已经被初始化
前提:页表的地址存放在哪里?
ARM PTE : ARM MMU 硬件页表
Linux PTE : 内核使用的软件页表
软件页表与硬件页表如何协同工作?
二级页表的创建过程分析(下)
//.........
//.........

虚拟内存管理:vmalloc区
vmalloc区
kmalloc vs vmalloc
内存申请接口:vmalloc/vfree
VMALLOC_START 到 VMALLOC_END 之间的一段区域
虚拟空间是连续的,物理空间可以不连续
vmalloc主要用在什么地方?
vmalloc最大能申请多大的内存
vmalloc区的大小怎么计算的?默认大小呢?
vmalloc区域的页表映射
vmalloc区当用户真正需要使用到这片内存区域的时候,才会去建立映射。和上面的线性映射区不一样。
vmalloc实现机制分析
从VMALLOC_START 到 VMALLOC_END查找一片虚拟地址空间
根据内存的大小从伙伴系统申请多个物理页帧page
把每个申请到的物理页帧逐页映射到虚拟地址空间

我们来看一个简单的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 : 也是会分配一片虚拟内存,但是不会分配物理内存,直接将虚拟内存和寄存器进行映射。
高端内存映射
