🗒️linux源码解析06–常用内存分配函数kmalloc、vmalloc、malloc和mmap实现原理
2024-7-31
| 2024-11-4
字数 4593阅读时长 12 分钟
type
status
date
slug
summary
tags
category
icon
password
URL

1.kmalloc函数

kmem_cache_alloc_trace分配函数
可见,kmalloc()基于slab分配器实现,因此分配的内存,物理上都是连续的。

2.vmalloc函数

核心函数__vmalloc_node_range

可见,vmalloc是临时在vmalloc内存区申请vma,并且分配物理页面,建立映射;直接分配物理页面,至少一个页4K,因此vmalloc适合用于分配较大内存,并且物理内存不一定连续;

3.malloc函数

malloc是C库实现的函数,C库维护了一个缓存,当内存够用时,malloc直接从C库缓存分配,只有当C库缓存不够用;通过系统调用brk,向内核申请,从堆空间申请一个vma;malloc实现流程图:
notion image

__do_sys_brk函数

经过平台相关实现,malloc最终会调用SYSCALL_DEFINE1宏,扩展为__do_sys_brk函数
总结下__do_sys_brk()功能:(1)从旧的brk边界去查询,是否有可用vma,若发现有重叠,直接使用;(2)若无发现重叠,新分配一个vma;(3)应用程序若调用mlockall(),会锁住进程所有虚拟地址空间,防止内存被交换出去,且立刻分配物理内存;否则,物理页面会等到使用时,触发缺页异常分配;

do_brk_flags函数

函数实现:(1)寻找一个可使用的线性地址;(2)查找最适合插入红黑树的节点;(3)寻到的线性地址是否可以合并现有vma,所不能,新建一个vma;(4)将新建vma插入mmap链表和红黑树中

mm_populate()函数

依次调用
当设置VM_LOCKED标志时,表示要马上申请物理页面,并与vma建立映射;否则,这里不操作,直到访问该vma时,触发缺页异常,再分配物理页面,并建立映射;

__get_user_pages()函数

follow_page_pte函数

总结:(1)malloc函数,从C库缓存分配内存,其分配或释放内存,未必马上会执行;(2)malloc实际分配内存动作,要么主动设置mlockall(),人为触发缺页异常,分配物理页面;或者在访问内存时触发缺页异常,分配物理页面;(3)malloc分配虚拟内存,有三种情况:a.malloc()分配内存后,直接读,linux内核进入缺页异常,调用do_anonymous_page函数使用零页映射,此时PTE属性只读;b.malloc()分配内存后,先读后写,linux内核第一次触发缺页异常,映射零页;第二次触发异常,触发写时复制;c.malloc()分配内存后, 直接写,linux内核进入匿名页面的缺页异常,调用alloc_zeroed_user_highpage_movable分配一个新页面,这个PTE是可写的;

4.mmap函数

mmap一般用于用户程序分配内存,读写大文件,链接动态库,多进程内存共享等;实现过程流程图:mmap根据文件关联性和映射区域是否共享等属性,其映射分为4类
  1. 私有匿名映射fd=-1,且flags=MAP_ANONYMOUS|MAP_PRIVATE,创建的mmap映射是私有匿名映射;用途是在glibc分配大内存时,如果需分配内存大于MMAP_THREASHOLD(128KB),glibc默认用mmap代替brk分配内存;
    1. notion image
  1. 共享匿名映射fd=-1,且flags=MAP_ANONYMOUS|MAP_SHARED;常用于父子进程的通信,共享一块内存区域;do_mmap_pgoff()->mmap_region(),最终调用shmem_zero_setup打开/dev/zero设备文件;
    1. 另外直接打开/dev/zero设备文件,然后使用这个句柄创建mmap,也是最终调用shmem模块创建共享匿名映射;
  1. 私有文件映射flags=MAP_PRIVATE;常用场景是,加载动态共享库;
  1. 共享文件映射flags=MAP_SHARED;有两个应用场景;(1)读写文件:内核的回写机制会将内存数据同步到磁盘;(2)进程间通信:多个独立进程,打开同一个文件,互相都可以观察到,可是实现多进程通信;核心函数如下:
总结:
以上的malloc,mmap函数,若无特别设定,默认都是指建立虚拟地址空间,但没有建立虚拟地址空间到物理地址空间的映射;当访问未映射的虚拟空间时,触发缺页异常,linxu内核会处理缺页异常,缺页异常服务程序中,会分配物理页,并建立虚拟地址到物理页的映射;
补充两个问题:1.当mmap重复申请相同地址,为什么不会失败?find_vma_links()函数便利该进程所有的vma,当检查到当前要映射区域和已有vma重叠时,先销毁旧映射区,重新映射,所以第二次申请,不会报错。
2.mmap打开多个文件时,比如播放视频时,为什么会卡顿?mmap只是建立vma,并未实际分配物理页面读取文件内存,当播放器真正读取文件时,会频繁触发缺页异常,再从磁盘读取文件到页面高速缓存中,会导致磁盘读性能较差;
madvise(add,len,MADV_WILLNEED|MADV_SEQUENTIAL)对文件内容进行预读和顺序读;
但是内核默认的预读功能就可以实现;且madvise不适合流媒体,只适合随机读取场景;
能够有效提高流媒体服务I/O性能的方法是最大内核默认预读窗口;内核默认是128K,可以通过“blockdev --setra”命令修改;
 
 
 
  • linux kernel
  • 必看精选
  • linux源码解析05–ioremap原理QUPAC_access的底层逻辑
    Loading...