Linux 内核内存布局与堆管理

2022/1/7 7:03:26

本文主要是介绍Linux 内核内存布局与堆管理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

内核内存布局

64位Linux一般使用48位来表示虚拟地址空间,43位表示物理地址, 通过命令:cat /proc/cpuinfo。
cpuinfo

cat /proc/meminfo
meminfo
ARM64架构处理器采用48位物理寻址机制,最大可寻找256TB的物理地址空间。对于目前应用完全足够,不需要扩展到64位的物理寻址。虚拟地址也同样最大支持48位寻址,所以 在处理器架构设计上,把虚拟地址空间划分为两个空间,每个空间最大支持256TB,linux内核在大多数体系结构上都把两个地址划分为:用户空间和内核空间。
用户空间:0x0000_0000_0000_0000至0x0000_ffff_ffff_ffff。
内核空间:0xffff_0000_0000_0000至0xffff_ffff_ffff_ffff。

内存布局

QEMU平台,可以打印ARM64架构linux内核内存分布情况。
img

堆管理

堆是进程中主要用于动态分配变量和数据 的内存区域,堆的管理对应程序员不是直接可见的。因为它依赖标准库提供的各个辅助函数(其 中最重要的是malloc)来分配任意长度的内存区。 malloc和内核之间的经典接口是brk系统调用,负责扩展/收缩堆。 堆是一个连续的内存区域,在扩展时自下至上增长。其中mm_struct结构,包含堆在虚拟地 址空间中的起始和当前结束地址(start_brk和brk)。

sys_brk流程

img
brk机制基于匿名映射实现,以减少内部的开销。在检查过用于brk的值的新地址未超出堆的限制之后,sys_brk第一个重要的操作是将请求的地址按页长对齐。
brk()用于用户进程想内核申请空间,用于扩展用户堆栈空间,或者回收用户堆栈空间。
malloc()为小空间申请,brk()为大空间申请。do_brk()用于增长动态分配区。do_munmap()释放动态分配区。

do_brk()源码分析

/*
 *  this is really a simplified "do_mmap".  it only handles
 *  anonymous maps.  eventually we may be able to do some
 *  brk-specific accounting here.
 */
// 将addr位置向后继续申请len字节长度,用于扩展堆的内存长度
static unsigned long do_brk(unsigned long addr, unsigned long len)
{
	struct mm_struct *mm = current->mm;
	struct vm_area_struct *vma, *prev;
	unsigned long flags;
	struct rb_node **rb_link, *rb_parent;
	pgoff_t pgoff = addr >> PAGE_SHIFT;
	int error;
	// 对len这个长度进行页面对齐,判断页面对齐之后是否超界
	len = PAGE_ALIGN(len);
	if (!len)
		return addr;

	flags = VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT | mm->def_flags;
	// 检查是否有足够的内存空间来分析len大小的内存。判断虚拟地址空间是否有足够的空间
	error = get_unmapped_area(NULL, addr, len, 0, MAP_FIXED);
	if (offset_in_page(error))
		return error;

	error = mlock_future_check(mm, mm->def_flags, len);
	if (error)
		return error;

	/*
	 * mm->mmap_sem is required to protect against another thread
	 * changing the mappings in case we sleep.
	 */
	verify_mm_writelocked(mm);

	/*
	 * Clear old maps.  this also does some error checking for us
	 */
	// 循环遍历用户进程红黑树中的VMA,根据addr查找合适的插入点
	while (find_vma_links(mm, addr, addr + len, &prev, &rb_link,
			      &rb_parent)) {
		if (do_munmap(mm, addr, len))
			return -ENOMEM;
	}

	/* Check against address space limits *after* clearing old maps... */
	// 检查是否要对虚拟区间进行扩充
	if (!may_expand_vm(mm, len >> PAGE_SHIFT))
		return -ENOMEM;

	if (mm->map_count > sysctl_max_map_count)
		return -ENOMEM;
	// 判断系统是否有足够的内存
	if (security_vm_enough_memory_mm(mm, len >> PAGE_SHIFT))
		return -ENOMEM;

	/* Can we just expand an old private anonymous mapping? */
	// 判断是否可以合并 
	vma = vma_merge(mm, prev, addr, addr + len, flags,
			NULL, NULL, pgoff, NULL, NULL_VM_UFFD_CTX);
	if (vma)
		goto out;

	/*
	 * create a vma struct for an anonymous mapping
	 */
	// 没有办法合并 新创建一个VMA VMA地址空间是[addr, addr+len]
	vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
	if (!vma) {
		vm_unacct_memory(len >> PAGE_SHIFT);
		return -ENOMEM;
	}

	INIT_LIST_HEAD(&vma->anon_vma_chain); //指向匿名域指针
	vma->vm_mm = mm; 
	// 分别保存虚拟内存空间的首地址 末地址 单位字节
	vma->vm_start = addr;
	vma->vm_end = addr + len;
	vma->vm_pgoff = pgoff; //文件映射的偏移量 单位页面
	// VM_EXEC:可以被执行
	// VM_IO:这个区间映射一个设备的I/O地址空间
	// VM_SHM:页用于IPC内存共享
	// VM_SHARED:页可以被多个进程共享
	vma->vm_flags = flags; //保存VMA标志位
	vma->vm_page_prot = vm_get_page_prot(flags); //访问权限
	vma_link(mm, vma, prev, rb_link, rb_parent); 
out: //增加进程地址空间长度
	perf_event_mmap(vma);
	mm->total_vm += len >> PAGE_SHIFT;
	if (flags & VM_LOCKED)
		mm->locked_vm += (len >> PAGE_SHIFT);
	vma->vm_flags |= VM_SOFTDIRTY;
	return addr;
}

大内核锁BKL

  • 递归锁:嵌套上锁解锁。
  • 自动释放特性。
  • 本质上自旋锁,不同在于自旋锁不可以递归获取锁(会导致死锁),大内核锁可以递归获取锁,保护整个内核。保持锁的时间太长,严重影响系统性能和可伸缩性,因而被淘汰。
    【注意】
    原子操作对整数操作,自旋锁和信号量应用比较广泛。当临界区小应该选择自旋锁,反之应该信号量。

per-CPU计数器

引入目的是加速SMP系统上计数器操作。
基本原理:
计数器的准确值存储在内存中的某一个地址,准确值所在内存位置之后是一个数组,每个数组想对应于系统中的一个CPU。
Linux系统尤其是针对SMP或者NUMA架构的多CPU系统的时候,主要描述每个CPU私有数据时,系统提供该机制per-cpu。
三种CPU模式:x86 x64 ia64
X86表示基于X86指令安装模式;
x64表示64位系统程序;
ia64主要用于企业级服务器,inter安腾架构基于a64处理器架构的服务器,64位运算能力、寻址空间,数据处理能力方面突破性提高。

总结

本文介绍了内核的内存布局,分布情况,堆管理,malloc与brk区别,大内核锁,per-CPU计数器等。

技术参考

https://ke.qq.com/webcourse/3294666/103425320#taid=11144568258053578&vid=5285890815567502792



这篇关于Linux 内核内存布局与堆管理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程