Linux 内核内存布局
64位Linux 一般使用48位来表示虚拟地址空间,45(有的是44 或者 46)位表示物理地址,通过命令:cat /proc/cpuinfo 输出地址大小,具体的其他选项参数: vendor_id : GenuineIntel //厂商 cpu family : 6 //CPU产品代号 mode : 79 //cpu 属于其系列的具体代号 mode name: Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz stepping : 1//cpu 属于制作更新版本 cpu MHz : 2199.997 //cpu 主频 cache size: 56320 KB //二级缓存 physical id : 1 //单个cpu 编号 siblings: 16 //单个cpu逻辑物理核心数 core id : 7 //单前物理核心,在其所处cpu中的编号 cpu cores: 8 //逻辑核所在cpu的物理核数 apicid: 31 //用来区分不同逻辑核的编号 bogomips: 4462.15 //系统内核启动时测算的cpu速度 clflush size: 64 // 每次刷新缓存的大小单位 cache_alignment : 64 //缓存地址空间对齐单位 address sizes: 44 bits physical, 48 bits virtual //可以访问的地址空间位数。
x86_64 内核分布情况
通过cat /proc/meminfo 输出系统架构内存分布情况,具体如下:
- MemTotal: 65901144 kB //所有可用内存空间
- MemFree: 4112612 kB //系统还没有使用的内存
- MemAvailable: 28757984 kB //真正系统可以使用的内存,可以回收
- Buffers: 990772 kB //专用用来给块设备做缓存的内存
- Cached: 23363496 kB //分配给文件缓冲区的内存
- SwapCached: 66376 kB //被调整缓冲缓存使用的交换空间大小
- Active: 38459180 kB //使用高速缓冲存储器页面文件大小
- Inactive: 20560836 kB // 没有经常使用的告诉缓存存储器页面大小
- Active(anon): 32334804 kB // 活跃的匿名内存
- Inactive(anon): 2616968 kB //不活跃的匿名内存
- Active(file): 6124376 kB //活跃的文件使用内存
- Inactive(file): 17943868 kB //不活跃的文件使用内存
- Unevictable: 0 kB // 不能被释放的内存页
- Mlocked: 0 kB //系统调用mlock 允许程序在物理内存上锁住部分或全部地址空间
- SwapTotal: 2097148 kB //交换空间总内存大小
- SwapFree: 843452 kB // 交换空间空闲的内存大小
- Zswap: 0 kB
- Zswapped: 0 kB
- Dirty: 10440 kB //等待被写回到磁盘
- Writeback: 0 kB//正在被写加的大小
- AnonPages: 34600316 kB //未映射页的内存/映射到用户空间的非文件页表大小
- Mapped: 1553816 kB //映射文件内存
- Shmem: 307256 kB // 已经被分配的共享内存
- KReclaimable: 1254992 kB // 可回收的slab 内存
- Slab: 1798984 kB //内存数据结构缓存大小
- CommitLimit: 35047720 kB //系统实际可以分配的内存
- Committed_AS: 72138556 kB //系统当前已经分配的内存
- VmallocTotal: 34359738367 kB // 预留虚拟内存的总量
- VmallocUsed: 288216 kB //已经被使用的虚拟内存
- VmallocChunk: 0 kB //可以分配的最大逻辑地址连续的虚拟内存。
linux 内核动态内存分配通过系统接口分配
alloc_pages/__get_free_page :以页为单位分配
vmalloc :以字节为单位分配虚拟地址连续的内存块
kmalloc : 以字节为单位分配物理地址连续的内存块,以slab为中心。
我们也可以通过vmalloc 分配的内存将它统计输出,具体如下: grep vmalloc /proc/vmallocinfo
ARM64 架构采用48位物理寻址方式,最大可寻找256TB的物理地址空间,对于目前应用完全足够,不需要扩展到64位。虚拟地址也同样最大支持48位寻址,Linux内核在大多数体系结构上将地址空间划分为:用户空间和内核空间。
- 用户空间(User space):0x0000_0000_0000 至 0x0000_FFFF_FFFF_FFFF.
- 内核空间(Kernel space):0xFFFF_0000_0000至0xFFFF_FFFF_FFFF_FFFF.
ARM 架构处理器linux 内核分布
内核空间
线性映射区域
.data
.init
.text
modules
PCI I/O
vmemmap
vmalloc
......
不规范地址(不允许使用)
用户空间
用户
- KASAN(影子区):它是一个动态检测内存错误的工具,原理利用额外的内存标记可用内存的状态(将1/8内存作为影子区)。
- modules:内核模块使用的虚拟地址空间;
- vmalloc :vmalloc 函数使用的虚拟地址空间;
- .text :代码段
- .init: 模块初始化数据
- .data:数据段
- .bss: 静态内存分配段
- fixed:固定映射区域
- PCI I/O PCI设备的I/O地址空间
- vmemmap: 内存的物理地址如果不连续的话,就会存在内存空洞(稀疏内存)vmemmap就用来存放稀疏内存的page结构体的数据和虚拟地址空间。
- memory :线性映射区域
我们可以通过内存布局打印出来:linux 内核初始化完成后,整体布局稳定,通过vexpress 平台输出即可, 首先经过start_kernel 即(asmlinkage __visible void __init start_kernel(void) mem_init()即 mm_init(void) mem_init(); 最后打印 printk();
堆管理
堆是进程中主要用于动态分配变量和数据的内存区域,堆的管理对应程序员不是直接可见的,malloc 和内核之间的经典接口 是brk系统调用,负责扩展/收缩堆。
堆是一个连续的内存区域,在扩展时自下至上增长,其中mm_struct 结构,包含堆在虚拟地址空间的起始和当前结束地址(start_brk和brk)。
内存描述mm_struct 结构体当中,有堆起始地址和结束的成员,具体源码如下:
```
struct mm_struct {
struct {
struct vm_area_struct *mmap;
struct rb_root mm_rb;
u64 vmacache_seqnum;
#ifdef CONFIG_MMU
unsigned long (*get_unmapped_area) (struct file *filp, unsigned long addr, unsigned long len ,unsigned long pgoff, unsigned long flags);
#endif
....
}
}
```
```
unsigned long total_vm;
unsigned long locked_vm;
atomic64_t pinned_vm;
unsigned long data_vm;
unsigned long exec_vm;
unsigned long stack_vm;
unsigned long def_flags;
spinlock_t arg_lock;
unsigned long start_code, end_code ,start_data,end_data;
unsigned long start_brk,brk,start_stack;
unsigned long arg_start ,arg_end,env_start,env_end;
```
- brk 系统调用指定堆在虚拟地址空间中新的结束地址(如果堆将要收缩,当然可以小于当前值) brk系统调用动态分配 ``` SYSCALL_DEFINE1(brk,unsigned long ,brk) { unsigned long retval; unsigned long newbrk,oldbrk,origbrk; struct mm_struct mm = current->mm; struct vm_area_struct next; unsigned long min_brk; bool populate; bool downgraded = false; LIST_HEAD(uf); if (down_write_killable(&mm->mmap_sem)) return -EINTR; origbrk = mm->brk; ...... }
linux 系统当中有两个方法可以创建堆:
brk()是系统调用,实际是设置进程数据段的结束地址,将数据段的结束地址向高地址移动,
mmap() 向操作系统申请一段虚拟地址空间(使用映射到某个文件)。当不用此空间来映射到某个文件时,这块空间成为匿名空间可以用来作为堆空间。
per-CPU 计数器,引入它用来加速SMP系统上计数器操作,部分源码如下:
ifdef CONFIG_SMP
struct percpu_counter { raw_spinlock_t lock; s64 count; #ifdef CONFIG_HOTPLUG_CPU struct list_head list; #endif s32 __percpu *counters; }
## 多核调度分析
SMP 是多核处理器最常见 ,主要是讲一个计算机上集中一组处理器,各个处理器是对等以其系统总线和内存子系统。
1. 根据处理器实际物理属性,cpu域可以分为超线程,多核。
a. 超线程(SMT) linux 内核分类 CONFIG_SCHED_SMT;
b. 多核(MC):linux 内核分类 CONFIG_SCHED_MC
2. linux 内核对cpu 管理主要是通过bitmap 进行实现,并且定义四种状态:possible,online ,active 和present 具体如下:
extern struct cpumask __cpu_possible_mask; extern struct cpumask __cpu_online_mask; extern struct cpumask __cpu_present_mask; extern struct cpumask __cpu_active_mask;
define cpu_possible_mask((const struct cpumask *) & __cpu_possible_mask)//有多少个可以执行的cpu核心
define cpu_online_mask ((const struct cpumask *) & __cpu_online_mask) // 有多少个正在处于运行状态的cpu核心
define cpu_present_mask((const struct cpumask *) &__cpu_present_mask) //有多少个具备online条件的cpu核心,他们不一定处于online有的cpu 可能被热插拔
define cpu_active_mask((const struct cpumask *) & __cpu_active_mask) //有多少个活跃的cpu 核心
``` linux 内核把所有同一个级别的CPU归纳为一个调度组,然后把同一个级别的调度组组成一个调度域。
调度组和调度域
处理器有一个基本的调度域,它是硬件线程调度域,向上一次是核调度域,处理器调度域和NUMA节点调度域。
-
处理器拓扑结构:NUMA 和SMP。 a 核(core):一个处理器包含多个核,每个核有独立的一级缓存,所有核共享二级缓存。 b.硬件线程:也可以叫做虚拟处理器(或者叫做逻辑处理器),一个处理器或者核包含多个硬件线程,硬件线程共享一级缓存和二级缓存。
-
调度域和调度组 软件看到的处理器是最底层的处理器。linux 内核按照处理拓扑层次划分为调度域层次,每个调度域包含多个调度组, 每一个调度域对应一个NUMA 节点,每一个调度组对应numa节点处理器,所以每一个处理器有一个基本的硬件线程调度域