一、 红黑树与VMA

红黑树的应用:

  • 广泛用于 C++ 的 STL 中,set 和 map 是用红黑树实现的;
  • Linux 的的进程调度,用红黑树管理进程控制块,进程的虚拟内存空间都存储在一颗红黑树上,每个虚拟内存空间都对应红黑树的一个节点,左指针指向相邻的虚拟内存空间,右指针指向相邻的高地址虚拟内存空间;
  • IO 多路复用的 epoll 采用红黑树组织管理 sockfd,以支持快速的增删改查;
  • Nginx 中用红黑树管理定时器,因为红黑树是有序的,可以很快的得到距离当前最小的定时器;
  • Java 的 TreeMap 的实现;。

用户进程的虚拟地址空间包含了若干区域,这些区域的分布方式是特定于体系结构的,不过所有的方式都包含下列成分:

  • 可执行文件的二进制代码,也就是程序的代码段
  • 存储全局变量的数据段
  • 用于保存局部变量和实现函数调用的栈
  • 环境变量和命令行参数
  • 程序使用的动态库的代码
  • 用于映射文件内容的区域

由此可以看到进程的虚拟内存空间会被分成不同的若干区域,每个区域都有其相关的属性和用途,一个合法的地址总是落在某个区域当中的,这些区域也不会重叠。在linux内核中,这样的区域被称之为虚拟内存区域(virtual memory areas),简称vma。一个vma就是一块连续的线性地址空间的抽象,它拥有自身的权限(可读,可写,可执行等等) ,每一个虚拟内存区域都由一个相关的struct vm_area_struct结构来描述。


struct vm_area_struct {struct mm_struct * vm_mm;    /* 所属的内存描述符 */unsigned long vm_start;    /* vma的起始地址 */unsigned long vm_end;        /* vma的结束地址 *//* 该vma的在一个进程的vma链表中的前驱vma和后驱vma指针,链表中的vma都是按地址来排序的*/struct vm_area_struct *vm_next, *vm_prev;pgprot_t vm_page_prot;       /* vma的访问权限 */unsigned long vm_flags;    /* 标识集 */struct rb_node vm_rb;      /* 红黑树中对应的节点 *//** For areas with an address space and backing store,* linkage into the address_space->i_mmap prio tree, or* linkage to the list of like vmas hanging off its node, or* linkage of vma in the address_space->i_mmap_nonlinear list.*//* shared联合体用于和address space关联 */union {struct {struct list_head list;/* 用于链入非线性映射的链表 */void *parent;   /* aligns with prio_tree_node parent */struct vm_area_struct *head;} vm_set;struct raw_prio_tree_node prio_tree_node;/*线性映射则链入i_mmap优先树*/} shared;/** A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma* list, after a COW of one of the file pages.    A MAP_SHARED vma* can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack* or brk vma (with NULL file) can only be in an anon_vma list.*//*anno_vma_node和annon_vma用于管理源自匿名映射的共享页*/struct list_head anon_vma_node;  /* Serialized by anon_vma->lock */struct anon_vma *anon_vma; /* Serialized by page_table_lock *//* Function pointers to deal with this struct. *//*该vma上的各种标准操作函数指针集*/const struct vm_operations_struct *vm_ops;/* Information about our backing store: */unsigned long vm_pgoff;        /* 映射文件的偏移量,以PAGE_SIZE为单位 */struct file * vm_file;           /* 映射的文件,没有则为NULL */void * vm_private_data;      /* was vm_pte (shared mem) */unsigned long vm_truncate_count;/* truncate_count or restart_addr */#ifndef CONFIG_MMUstruct vm_region *vm_region; /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMAstruct mempolicy *vm_policy;  /* NUMA policy for the VMA */
#endif

进程的若干个vma区域都得按一定的形式组织在一起,这些vma都包含在进程的内存描述符中,也就是struct mm_struct中,这些vma在mm_struct以两种方式进行组织,一种是链表方式,对应于mm_struct中的mmap链表头,一种是红黑树方式,对应于mm_struct中的mm_rb根节点,和内核其他地方一样,链表用于遍历,红黑树用于查找。mm_struct结构描述了一个进程的整个虚拟地址空间。较高层次的结构vm_area_truct描述了虚拟地址空间的一个区间(简称虚拟区)。每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个指向该进程的结构。可以说,mm_struct结构是对整个用户空间的描述。每一个进程都会有自己独立的mm_struct,这样每一个进程都会有自己独立的地址空间.

其中mmap指向vma链表的头节点,mm_rb指向vma红黑树的根节点。map_count是vma的总个数,total_vm是进程地址空间的总大小(以page为单位)。

struct mm_struct {struct vm_area_struct *mmap;           /* 内存区域链表 */struct rb_root mm_rb;                  /* VMA 形成的红黑树 */...struct list_head mmlist;               /* 所有 mm_struct 形成的链表 */...unsigned long total_vm;                /* 全部页面数目 */unsigned long locked_vm;               /* 上锁的页面数据 */unsigned long pinned_vm;               /* Refcount permanently increased */unsigned long shared_vm;               /* 共享页面数目 Shared pages (files) */unsigned long exec_vm;                 /* 可执行页面数目 VM_EXEC & ~VM_WRITE */unsigned long stack_vm;                /* 栈区页面数目 VM_GROWSUP/DOWN */unsigned long def_flags;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;        /* 命令行参数 和 环境变量的 起始地址和结束地址 */.../* Architecture-specific MM context */mm_context_t context;                  /* 体系结构特殊数据 *//* Must use atomic bitops to access the bits */unsigned long flags;                   /* 状态标志位 */.../* Coredumping and NUMA and HugePage 相关结构体 */
};

find_vma()用来寻找一个针对于指定地址的vma,该vma要么包含了指定的地址,要么位于该地址之后并且离该地址最近,或者说寻找第一个满足addr<vma_end的vma。

struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
{struct vm_area_struct *vma = NULL;if (mm) {/* Check the cache first. *//* (Cache hit rate is typically around 35%.) */vma = mm->mmap_cache; //首先尝试mmap_cache中缓存的vma/*如果不满足下列条件中的任意一个则从红黑树中查找合适的vma1.缓存vma不存在2.缓存vma的结束地址小于给定的地址3.缓存vma的起始地址大于给定的地址*/if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {struct rb_node * rb_node;rb_node = mm->mm_rb.rb_node;//获取红黑树根节点vma = NULL;while (rb_node) {struct vm_area_struct * vma_tmp;vma_tmp = rb_entry(rb_node,   //获取节点对应的vmastruct vm_area_struct, vm_rb);/*首先确定vma的结束地址是否大于给定地址,如果是的话,再确定vma的起始地址是否小于给定地址,也就是优先保证给定的地址是处于vma的范围之内的,如果无法保证这点,则只能找到一个距离给定地址最近的vma并且该vma的结束地址要大于给定地址*/if (vma_tmp->vm_end > addr) {vma = vma_tmp;if (vma_tmp->vm_start <= addr)break;rb_node = rb_node->rb_left;} elserb_node = rb_node->rb_right;}if (vma)mm->mmap_cache = vma;//将结果保存在缓存中}}return vma;
}

一个 VMA 的大小必须是页大小的整数倍。虚拟内存空间(VMA),页表项和页桢的关系

二、段错误(segment fault )

段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址等等情况。

1,内存越界踩到了别人家的内存。
2,不小心除零了或者运算浮点数的时候异常了,比如:1/0。
3,堆栈溢出,比如:递归的时候没有设置边界条件。

排查段错误:如果想让系统在信号中断造成的错误时产生core文件, 我们需要在shell中按如下设置 ulimit -c unlimited,发生core dump之后,用gdb进行查看core文件的内容, 以定位文件中引发core dump的行:gdb [exec file] [core file]

产生原因: 在 shell 中启动一个进程,进程访问一个虚拟地址,MMU 将这个虚拟地址转化成物理地址的过程中,发现转化不了,于是产生一个中断。中断的过程即由 IDTR 找到 IDT,执行中断处理函数,最后调到 do_page_fault 函数。do_page_fault 在当前进程的 vma 中找这个地址(task_struct 维护 一颗 vm_area 的红黑树),如果访问的虚拟地址没有落到任何一个 vma 中,那么 do_page_fault 会给进程发送一个 SIGSEGV 信号。SIGSEGV 信号的默认 handler 是 Core,终止这个进程,产生一个 core dump。终止的时候,这个进程把它的 status 给父进程 shell(segfault 的 status 是 139),shell 收到了之后,知道子进程是因 segfault 退出,就在屏幕上打出来一行字 Segmentation fault (core dumped)。


三、VMA与mmap

vma与物理内存关系是:

  • 应用程序访问的虚拟内存virt_addr必须落在一个vma内
  • virt_addr在vma内的不一定在物理内存有内容比如用户调用malloc是分配了vma,但是并没给实际物理内存,
  • virt_addr落在一个vma内的访问权限也不一定是正确的需要看vma和pagetable记录的权限是否匹配

mmap系统调用的实现过程是

1.先通过文件系统定位要映射的文件;

2.权限检查,映射的权限不会超过文件打开的方式,也就是说如果文件是以只读方式打开,那么则不允许建立一个可写映射;

3.创建一个vma对象,并对之进行初始化;

4.调用映射文件的mmap函数,其主要工作是给vm_ops向量表赋值;

5.把该vma链入该进程的vma链表中,如果可以和前后的vma合并则合并;

6.如果是要求VM_LOCKED(映射区不被换出)方式映射,则发出缺页请求,把映射页面读入内存中.

MMAP内存映射实现详细流程:

(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

  • 进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

  • 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址

  • 为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化

  • 将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中

(二)调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系

  • 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。

  • 通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。

  • 内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。

  • 通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

(三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

  • 注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。

  • 进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。

  • 缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。

  • 调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。

  • 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

  • 注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。

VMA与page fault相关推荐

  1. Linux内存page,【原创】(十四)Linux内存管理之page fault处理

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  2. linux那些事之page fault(AMD64架构)(user space)(2)

    do_user_addr_fault 用户空间地址处理是page fault主要处理流程,x86 64位系统主要是do_user_addr_fault()函数 该处理部分是x86架构特有部分 即与架构 ...

  3. Linux系统下深究一个malloc/brk/sbrk新内存后的page fault问题

    有耳可听的,就应当听 -<马可福音> 周四的休假团建又没有去,不因别的,只因年前东北行休假太多了,想缓缓-不过真实原因也确实因为假期剩余无几了-思考了一些问题,写下本文.   本文的缘起来 ...

  4. 页错误 Page Fault /缺页异常 详解

    ​​​​​目录 ​​​​​​ 1. 第一部分:如果你看得懂 1.1 页错误定义 1.2 页错误的处理 2. 第二部分:如果你看不懂上面的,请看这里 2.1. 举例子(背景) 2.1.1 进程及页映射 ...

  5. 图解|什么是缺页错误Page Fault

    1.号外号外 各位老铁,大家好! 上周大白有事停更1次,最近在想如何让大家在10分钟中有所收获,于是准备搞一个"什么是xxx"系列,写一些精悍的知识点. 先抛一道阿里面试题给大家热 ...

  6. page fault in nonpaged area 蓝屏_记一次蓝屏0x00000050

    新机到手不到一周,一个午睡的功夫回来,电脑蓝屏了,风扇嗡嗡的响,长按电源键重启之后系统恢复正常了,总得找到蓝屏的原因吧.想了想电脑蓝屏之前应该是在试图卸载Avast Premium杀毒软件,电脑到手之 ...

  7. linux 内存管理 page fault带来的性能问题

    Linux进程如何访问内存 Linux下,进程并不是直接访问物理内存,而是通过内存管理单元(MMU)来访问内存资源. 原因后面会讲到. 为什么需要虚拟内存地址空间 假设某个进程需要4MB的空间,内存假 ...

  8. 内核中的page fault copy_from_user

    内核态的page fault? 前段时间有同事问了个问题:内核中是否可能发生page fault? 一时没能给出准确答案,当即有种感觉:难道是对内核内存管理的理解还不够,之前在这方面还是比较自信的- ...

  9. linux 内存越界判断_虚拟内存 和 page fault 的解释

    Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的.这样进程就可以很方便地访问内存,更确切地说是访问虚拟内存. 1.什么是虚拟内存 假设某个进程需要100MB的空间,而 ...

最新文章

  1. 2018牛客网暑期ACM多校训练营(第十场)J(二分)
  2. 安卓电视版linux,MythTV 30.0 发布,前端支持选择Android电视设备
  3. 中文设置_虾皮shopee平台怎么变成中文呢?怎么设置成中文
  4. 【BZOJ3712】Fiolki(并查集重构树)
  5. 为什么大部分程序员看不起PHP这门语言?
  6. 个人博客系统的设计与实现_一款小而美的博客系统,专为程序员设计
  7. SharePoint Framework 构建你的第一个web部件(三)
  8. 给你的数据来一个顶层设计
  9. Docker 系列之 常用镜像
  10. 解决Vue编译和打包时频繁内存溢出情况CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory...
  11. 《C++游戏开发》笔记十四 平滑过渡的战争迷雾(二) 实现:真正的迷雾来了
  12. 腾讯QQ认证空间4月27日已全面开放申请,欲进军自媒体
  13. Java程序员要求具备的10项技能
  14. oracle12c开发连接jar包ojdbc7
  15. VM14装deepin-15.6-amd64
  16. 经济基础知识(中级)【9】
  17. c语言int输入1输出60000,数字的秘密问题分析(C语言)
  18. 如何在office2016(word2016)中安装mathtype6.9及相关问题解决方案
  19. 初级程序员最应避免的 7 大错误
  20. JAVA开发(第三方接口授权访问)

热门文章

  1. 抖音服务器维护播放为零,抖音0播放怎么回事,抖音0播放是被屏蔽了吗,为什么抖音播放量一直都是0...
  2. docker镜像的版本(bullseye、buster、slim、alphine)
  3. “放下屠刀,立地成佛”
  4. 保姆级硬核教程:图解Transformer
  5. ArgoCD(四)--Application管理
  6. Javascript 严格模式use strict详解
  7. 权限系统--角色管理
  8. java 监视文件夹下的文件是否发生变化,当发生变时重新获取文件夹里的内容
  9. 《广告学概论》期末考试试卷及答案
  10. IT新闻中的“景德镇”是什么意思?