当前位置:首页>Linux>深入浅出Linux内核(内存篇):用户内存空间之VMA

深入浅出Linux内核(内存篇):用户内存空间之VMA

  • 2026-07-01 11:42:44
深入浅出Linux内核(内存篇):用户内存空间之VMA
大家好,我是蟹老板~

做了10年Linux内核开发,踩过最坑的坑,一半都和VMA有关——当年刚接触内核内存管理,对着struct vm_area_struct看了一周,还是没搞懂malloc到底怎么通过它申请内存,现在回头看,其实就是把简单的逻辑搞复杂了。

你有没有思考过:当一个进程启动后,那几G的虚拟地址空间是怎么被内核“切块”管理的?为啥malloc随便一调,就能从堆里抠出一块内存,却不会跟栈或者代码段打架?

这一切的核心,就是VMA——Virtual Memory Area,虚拟内存区域。简单说,它就是内核给进程地址空间里每一段连续虚拟地址打上的“标签合同”。每一段都有自己的起始地址、结束地址、权限、是不是映射了文件……缺一不可。

本文内核版本为Linux5.6.4。

一、VMA是啥?

咱们写用户态程序时,总爱用malloc、mmap申请内存,可你们有没有想过,这些函数底层到底在搞啥?真不是直接从物理内存里划一块出来那么简单,内核得先给进程的地址空间“分区”,而负责描述这些分区的,就是struct vm_area_struct,咱们简称VMA。

每个进程都有自己的内存描述符mm_struct,里面就管着所有VMA——一方面靠mm->mmap链表把所有VMA串起来,另一方面用mm->mm_rb红黑树存着,链表方便遍历,红黑树方便快速查找合并。

不得不说,内核这设计是真的绝了。

struct mm_struct {struct vm_area_struct *mmap;		/* 所有VMA的链表头,串起所有分区 */struct rb_root mm_rb;				/* VMA红黑树的根节点,查得快 */int map_count;			/* VMA的数量,多了也会影响性能 */	……}// 补充一句,当年我调试一个内存泄漏bug,就是因为map_count异常飙升,查了半天才发现是VMA没释放干净

你看啊,mmap链表是按VMA->vm_start递增排序的,从头到尾走一遍,就能拿到进程所有的地址分区;而mm_rb红黑树,就是为了解决链表查找慢的问题——要是进程有上百个VMA,链表遍历得耗多久?红黑树O(logn)的效率,瞬间就把速度提上来了。map_count更简单,就是记着当前进程有多少个VMA,超过阈值内核还会报警。

说了这么多,VMA本身到底长啥样?

看下真正的 VMA 本体:

/* * 这个结构体就是VMA的核心,每个VMA对应进程地址空间的一块区域 * 比如代码段、数据段、共享库,都有自己的VMA,各自有不同的访问规则 */struct vm_area_struct {/* 第一行缓存里存的是红黑树遍历需要的信息,不用深究,知道就行 *//* VMA描述的线性地址范围,起始和结束都是虚拟地址,重点! */unsigned long vm_start;		/* 分区起始地址,比如0x100000 */unsigned long vm_end;		/* 分区结束地址,注意是结束地址的下一个字节					   比如vm_start=0x100000,vm_end=0x200000,实际范围是0x100000~0x1fffff *//* 进程的VMA链表,前后相连,按地址大小排序,遍历全靠它 */        struct vm_area_struct *vm_next, *vm_prev;        /* 把VMA当作一个节点,挂到mm->mm_rb红黑树里,用于快速查找 */        struct rb_node vm_rb;        /*         * 这个字段是用来优化内存分配的,记录当前VMA左边最大的空闲内存间隙         * 内核找空闲内存时,靠它能快速定位合适大小的区域,省得瞎找         */        unsigned long rb_subtree_gap;        /* 第二行缓存开始,下面的字段也很重要 */        /* 指向当前VMA所属的mm_struct,相当于给VMA找个“主人” */        struct mm_struct *vm_mm;数据段是可读可写,违规访问就会报段错误         */        pgprot_t vm_page_prot;                /* 访问权限,底层和页表相关 */        unsigned long vm_flag         * 针对文件映射的VMA,会挂到文件的地址空间树里         * 比如mmap映射一个文件,这个结构体就用来关联文件和VMA         */        struct {                struct rb_node rb;;        /*         * 关的,用来通过物理页找到对应的VMA         * 内存回收的时候特别有用,当年我做内存优化,天天  */        struct list_head anon_vma_chain; /* 受mmap_sem和page_table_lock保护,不用纠结锁机制 */     _vma *anon_vma;        /* 同样是RMAP相关,锁保/* VMA的操作方法,比如打开、关闭VMA,内核会调用这些函数 */     t struct vm_operations_struct *vm_ops;      文件的偏移量,单位是页大小(PAGE_SIZE) */        /* 匿名映射(比如m的内存),这里存的是vm_start / PAGE_SIZE */        unsigned long vm_pgoff;                /*        stle * vm_file;               映射为NULL */  oid * vm_private_data;             vm_pte,现在用途更灵活 */#ifdef CONFIG_SWAP        long_t swap_readahead_info;        /读相关,不用深究 */#endif#ifndef CONFIG_MMU        strregion *vm_region;        /* 的,大部分场景用不到 */#endif#ifdef CONFIG_NUMA   ruct mempolicy *vm_policy;  * NUMA架构的内存策略,非NUMA系统忽略 */#endif     ct vm_userfaultfd_ctx vm_userfaultfd_ctx;    用户态缺页相关,新手可以先跳过 */} __randomize_layout;  * 地址随机化,防溢出攻击,内核安全特性 */      /     /*   stru      /     st无MMU架构用uct vm_* 交换分区预atomic_   /* 私有数据,早年是      v /* 指向映射的文件,匿名ruct fi 文件映射偏移,匿名映射有特殊含义 */alloc申请  /* 映射   cons护 */           struct anon       跟这个打交道这个是反向映射(RMAP)相                unsigned long rb_subtree_last;        } shareds;                /* VMA的标志位,后面专门讲,重点中的重点 */        /*        /* 所属的进程内存描述符 */        /*         * VMA的访问权限,比如可读、可写、可执行,内核会根据这个做权限检查         * 比如代码段是可读可执行,

vm_start到vm_end定义了一段连续虚拟地址,属性必须一致。代码段通常r-x,堆rw,栈也差不多。文件映射的so库或者数据文件,就通过vm_file挂上去。

注释我已经尽量写详细了。接触内核久了,看这些结构体就像看老朋友的脸,每个字段后面都有无数踩坑的故事。vm_page_prot 和 vm_flags 这种权限控制,当年我在一个写时复制的 bug 上栽过跟头,就是没有仔细区分 VM_WRITE 和真正页表项的可写位,导致 fork 出来的子进程写数据把父进程给搞崩了。

二、VMA的那些Flag

这一堆 flag 到底都是啥意思呢?

VMA的标志位vm_flags直接决定这块内存到底能不能读、能不能写、能不能执行。

内核里定义了一堆宏,比如:

/* * vm_flags的定义,在mm_types.h里,改的时候要同步更更新trace里的定义 * 下面只列常用的,冷门的就不写了,写了也用不上,还占地方 */#define VM_NONE   x00000000     任何权限,基本不用 *//* 核心权限标志,可读、可写、可执行、可共享,这四个最常用 */#define VM_READ   x00000001 /* 页面可读,少了这个读内存会报段错误 */#define VM_WRITE 0x00000002    页面可写,写只读内存会报段错误 */#define VM_EXEC  0x00000004        行,代码段必须有这个标志 */#define VM_SHARED 0x00000008    可共享,多个进程能共用这个VMA,比如共享库 *//* 权限限制标志,mprotect函数会用到,和上面的权限标志对应 */#define VM_MAYREAD        010        /* 允许设置为可ect时用 */#define VM_MAYWRITE    0000020        /*写 */#define VM_MAYEXEC        0x0        /* 允许设置为可执行 ine VM_MAYSHARE        0x00000080        /* 允许设置为可共享 *//* 通用标志,日常调试会遇到 */#define VM_GROWSDOWN      栈空间就是这个标志,栈是从高地址往低地址扩的 */#define VM_UFFD_MISSING        0x00000200        /* 缺页跟踪相关,用户态缺页用 */#define VM_PFNMAP        0x00000400        /* 直接用物理页号管理,不通过struct page,特殊场景用 */#define VM_DENYWRITE        0x00000800        /* 禁止写操作,比如映射的只读文件,写的时候会报ETXTBSY */#define VM_UFFD_WP        0x00001000        /* 写保护跟踪,和用户态缺页配合 */#define VM_LOCKED        0x00002000        /* 锁定页面,不让页面被换出到交换分区,实时进程会用 */#define VM_IO           0x00004000        /* 内存映射I/O,比如硬件寄存器映射,特殊场景 */                                        /* sys_madvise函数用到的标志,提示内核内存访问模式 */#define VM_SEQ_READ        0x00      /* fork时不复制这个VMA,父子进程不共享 */#define VM_DONTEXPAND    0040000 /* 不能用mremap扩展大小,固定分区 */#define VM_LOCKONFAULT 0x00080000    缺页时自动锁定页面,不用手动lock */#define VM_ACCOUNT     100000  * 内存记账,内核会统计这个VMA的内存使用 */#define VM_NORESERVE        0x0    不预留内存,内存不足时会报OOM */#define VM_HUGETLB        000    大页VMA,用大页内存,提升性能 */#define VM_SYNC  0x00800000 /* 同步缺页,缺页时阻塞等待,不异步处理 */#define VM_ARCH_1   01000000 /* 架构相关标志,不同架构用途不一样 */#define VM_WIPEONFORK 0x02000000  * fork时清空VMA内容,子进程看不到父进程的内容 */#define VM_DONTDUMP 0x04000000 /* core dump时不包含这个VMA,比如敏感数据 */#ifdef CONFIG_MEM_SOFT_DIRTYdefine VM_SOFTDIRTY  x08000000  * 软脏标记,内存跟踪用,不用管 */#elsedefine VM_SOFTDIRTY 0#endif#define VM_MIXEDMAP     000000        ,既有struct page,又有纯物理页号 */#define VM_HUGEPAGE  x20000000    MADV_HUGEPAGE标记的VMA,启用大页 */#define VM_NOHUGEPAGE 0x40000000 /* MADV_NOHUGEPAGE标记的VMA,禁用大页 */#define VM_MERGEABLE  x80000000  * 可合并,KSM会合并相同内容的页面,节省内存 */      /      0                  /*       0/* 混合映射   0x10             /      0                    /                   0x                          /*0x00400     /*0020000      /   0x00    /*                   0x00008000        /* 顺序读取,比如读文件,内核会预读优化 */#define VM_RAND_READ        0x00010000        /* 随机读取,预读没用,内核会关闭预读 */#define VM_DONTCOPY        0x0002000  0x00000100        /* 向下生长,*/#def0000004 允许设置为可    0x0读,mprotx000000    /*        /* 页面可执                   /*                    0          /* 无             0

其实平时我们写应用,mprotect 或者 mmap 里传的 prot 到不了这么底层,但你要理解,最终 mmap 的 flag 和 prot 都会转成这些位组合。

比如说你 mmap 了一块匿名内存,可读可写,那么 VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE 这些都会被置上。而 VM_GROWSDOWN 只在主线程栈那个区域能看到,我当初发现这个的时候,恍然大悟为什么 /proc/pid/maps 里栈区域叫做 [stack],而且还可以自动扩展。

三、VMA 的查找

VMA 的查找是内核里的一个高频操作。缺页异常时,内核拿到出错的虚拟地址,得在几十上百个 VMA 里迅速判断这个地址合不合法。要是每个缺页都去遍历链表,那系统啥也别干了。所以 find_vma 及其兄弟函数被设计得非常精悍,内核给我们提供了三个常用的查找函数:

// 三个核心查找函数,记熟这三个,基本能应对大部分场景struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr);static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr);struct vm_area_struct *find_vma_prev(struct mm_struct *mm, unsigned long addr,struct vm_area_struct **pprev);

最常用的就是 find_vma(struct mm_struct *mm, unsigned long addr),它要找的是第一个满足 vma->vm_end > addr 的 VMA。注意啊,它不要求 addr 一定落在找到的 VMA 里面。比如地址在某个空洞里,找到的就是紧跟着这个空洞、且结束地址大于 addr 的那块 VMA。我以前调试一个栈溢出问题,误以为 find_vma 返回 NULL 才表示地址无效,结果死在空指针上,又交了一波学费。

举个例子,就像下面这张图,addrA和addrB都调用find_vma,返回的都是VMA2。addrB在VMA2里,没问题;可addrA不在任何VMA里,为啥也返回VMA2?因为VMA2是第一个vm_end大于addrA的VMA啊,这逻辑,当年我愣了半天才想明白,是不是有点绕?

find_vma_intersection这个函数是找和指定地址区间重叠的第一个VMA。入参是mm、start_addr和end_addr,只要这两个地址组成的区间,和某个VMA的vm_start、vm_end有重叠,就返回这个VMA,没有就返回NULL。

还是看下图,用addrA_start和addrA_end调用这个函数,会返回VMA2,因为两者有重叠的部分[vma_start, addrA_end];但用addrB_start和addrB_end调用,就返回NULL,因为addrB的区间和VMA2完全不搭边。这个函数在处理内存映射重叠检查时特别常用,我当年做共享内存开发,天天用它判断映射是否冲突。

find_vma_prev,这个最简单,就是先调用find_vma找到VMA,然后返回这个VMA的前一个节点(vma->vm_prev)。其实find_vma_intersection都是基于find_vma实现的,咱们看代码就懂了,特别简单。

/* 查找和start_addr~end_addr-1区间重叠的第一个VMA,没有就返回NULL,前提是start_addr < end_addr */static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr){  truct vm_area_struct * vma = find_vma(mm,start_addr);    找到了VMA,但指定区间的结束地址比VMA的起始地址还小,说明没有重叠,返回NULL */  * 就像上图里的addrB,end_addr < VMA2-&gt;vm_start,所以返回NULL */ if (vma && end_addr <= vma->vm_start)     a = NULL;  eturn vma;}// 是不是很简单?核心就是先找start_addr对应的VMA,再判断是否重叠      r            vm             /    /*       s

这里划个重点,find_vma的实现逻辑,这里面藏着一个优化点——vma cache,当年我就是没注意这个,调试时走了很多弯路。咱们先看代码,再慢慢说。

/* 查找第一个满足addr < vm_end的VMA,没有就返回NULL */struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr){        struct rb_node e;   ruct vm_area_struct *vma; /* 先查vma cache,缓存里有就直接返回,省得遍历红黑树 */ /* 这个缓存是关键,能提升查找效率,当年我没注意,以为每次都走红黑树,调试半天没找到问题 */ vma = vmacache_find(mm, addr);  f (likely(vma))/* likely表示大概率能找到,内核常用的优化手段,提示编译器 */  return vma;      de = mm->mm_rb.rb_node;/* 缓存没找到,就遍历红黑树,从根节点开始 */  hile (rb_node) {     uct vm_area_struct *tmp;  tmp = rb_entry(rb_node, struct vm_area_struct, vm_rb);/* 从红黑树节点拿到VMA */  if (tmp->vm_end > addr) {       = tmp;/* 找到一个vm_end大于addr的,先暂存 */                 art <= addr)/* 如果addr在这个VMA里,直接退出,就是它了 */    break;   /* 不在的话,继续向左遍历,红黑树左子树是小地址,右子树是大地址 */         e = rb_node->rb_left;    else    * vm_end <= addr,说明这个VMA在addr左边,向右遍历找更大的VMA */      node = rb_node->rb_right;     * 红黑树里找到了,就更新vma cache,下次查找能直接用 */ if (vma)    acache_update(addr, vma);   turn vma;}// 这里有个小细节,红黑树的遍历逻辑,左小右大,记准这个,就能看懂查找过程     re            vm          }        /                  rb_                    /            }  rb_nod                                                                      if(tmp->vm_st   vma                                               str             w  rb_no                    i                          st*rb_nod

vma cache 这个优化太经典了。每个线程的 task_struct 里嵌了一个 struct vmacache,可以缓存最近用过的 4 个 VMA。搞程序的都知道局部性原理,大多数缺页访问的地址要么在刚才的 VMA 旁边,要么就是同一个,,避免每次都遍历红黑树,所以这个 cache 命中率奇高。实现方式是用地址的 PMD(如果没有 MMU 就用页)做 hash 索引。

#define VMACACHE_BITS 2#define VMACACHE_SIZE (1U << VMACACHE_BITS)    4个缓存项,1左移2位就是4 */#define VMACACHE_MASK (VMACACHE_SIZE - 1)    掩码,用于计算缓存索引 */struct vmacache {      eqnum;/* 序列号,用于同步VMA链表和缓存,避免缓存失效 */    uct vm_area_struct *vmas[VMACACHE_SIZE];/* 缓存数组,存4个VMA指针 */};struct task_struct {        /有自己的vmacache,线程共享 */ struct vmacache       ache;}// 这里要注意,线程是共享进程的vmacache的,所以多线程访问时要注意同步,但内核已经处理好了,咱们不用管                 vmac       * 每个进程都    str  u64 s     /*     /*

缓存的更新和查找,靠的是两个函数:vmacache_update和vmacache_find。先看更新函数,找到VMA后,用HASA算法(其实就是哈希)计算索引,把VMA存到缓存数组里。

/* 1. 有MMU的架构,用PMD_SHIFT计算哈希,命中率更高(空间局部性好) 2. 没有MMU的,用PAGE_SHIFT,这个不用深究,知道就行 */#ifdef CONFIG_MMU#define VMACACHE_SHIFT    _SHIFT#else#define VMACACHE_SHIFT      SHIFT#endif#define VMACACHE_HASH(addr) ((addr >> VMACACHE_SHIFT) & VMACACHE_MASK)/* 计算缓存索引 */voidvmacache_update(unsignedlong addr, struct vm_area_struct *newvma){    检查缓存对应的mm是否有效,有效就更新缓存 if (vmacache_valid_mm(newvma->vm_mm))   urrent->vmacache.vmas[VMACACHE_HASH(addr)] = newvma;}// current是当前进程的指针,内核里常用,新手记着就行             c            //  PAGE_    PMD

里面这个计算缓存槽位的算法叫VMACACHE_HASH(估计原作者当时手滑想打HASH来着,写成了HASA。管他呢,源码里就是这么叫的)。它根据地址的PMD或者PAGE级别做移位操作算个索引出来,命中率奇高。

再看查找函数,先计算索引,再检查缓存是否有效(如果VMA有变化,缓存会失效),然后遍历缓存数组,找到匹配的VMA。遍历4个元素,比遍历红黑树快多了,这就是缓存的意义。

struct vm_area_struct *vmacache_find(struct mm_struct *mm, unsigned long addr){         = VMACACHE_HASH(addr);/* 用同样的哈希算法,找到缓存索引 */  nt i; /* 检查缓存是否有效,如果VMA链表变了(比如新增、删除VMA),缓存就失效了 */       话,就返回NULL,去遍历红黑树 */ if (!vmacache_valid(mm))     urn NULL;    遍历缓存数组,4个元素,很快 */ for (i = 0; i < VMACACHE_SIZE; i++) {                struct  *vma = current->vmacache.vmas[idx];  if (vma) {     ……// 省略一些权限检查,核心是判断addr是否在VMA里      (vma->vm_start <= addr && vma->vm_end > addr) {    return vma; /* 找到匹配的,直接返回 */           if (++idx == VMACACHE_SIZE)    dx = 0;/* 循环遍历,索引到4就回到0 */      eturn NULL;/* 缓存里没找到,返回NULL,去遍历红黑树 */}// 这里有个小坑,当年我调试时,VMA变了但缓存没更新,导致找到的是旧VMA,后来才知道是seqnum同步的问题  }        r                    i   }                                  }                                                       if                   ……              vm_area_struct            /*           ret        /* 失效的             iint idx

四、VMA插入

前面我们找到位置了,得把新的VMA塞进去吧?VMA插入,就是把struct vm_area_struct插入到mm->mmap链表和mm->mm_rb红黑树里,内核用insert_vm_struct函数实现。

/* 把VMA插入到进程的链表和红黑树里,按地址排序 1. 如果VMA关联了文件,会在这里加锁(i_mmap_rwsem),保证线程安全 2. 插入失败返回错误码,成功返回0 */intinsert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma){    uct vm_area_struct *prev;/* 待插入VMA的前一个节点 */     ct rb_node **rb_link, *rb_parent;/* rb_link是插入位置,rb_parent是父节点 */        找插入位置,靠find_vma_links函数,这个函数是核心 */     find_vma_links(mm, vma->vm_start, vma->vm_end,        rev, &rb_link, &rb_parent))         -ENOMEM;/* 找到位置失败,比如VMA重叠,返回内存不足错误 */     查内存是否足够,有VM_ACCOUNT标志的话,要做内存记账  f ((vma->vm_flags & VM_ACCOUNT) &&         urity_vm_enough_memory_mm(mm, vma_pages(vma)))  return -ENOMEM;      * 匿名VMA的vm_pgoff,其实没啥用,直到第一次写缺页才会设置    这里提前设置成vm_start对应的页号,是为了/proc/pid/maps显示一致    不然你看maps文件时,匿名VMA的偏移会乱,不好调试           名映射(没有关联文件),设置vm_pgoff */ if (vma_is_anonymous(vma)) {     _ON(vma->anon_vma);/* BUG_ON,条件成立就崩溃,用于调试 */        m_pgoff = vma->vm_start >> PAGE_SHIFT;/* 右移PAGE_SHIFT,就是除以页大小 */       第二步:把VMA插入链表和红黑树,靠vma_link函数 */       nk(mm, vma, prev, rb_link, rb_parent); return 0;}// 插入的核心就是两步:找位置、插节点,和链表、红黑树的基础操作一样,只是内核做了更多安全检查        vma_li }        /*         vma->v    BUG                 /* 匿    */      *      *  /*                           sec      i   // 检 return                          &p   if (/* 第一步:   stru    str

找插入位置的find_vma_links函数,参数有点多,6个。

入参是mm、vma的起始地址addr、结束地址end;出参是prev(前一个节点)、rb_link(插入位置)、rb_parent(父节点)。

逻辑很简单:从红黑树根节点开始遍历,插入的VMA起始地址小于当前节点的vm_end,就向左遍历(左子树是小地址);否则向右遍历(右子树是大地址),直到找到没有子节点的位置,这个位置就是插入点。父节点就是最后遍历到的节点,插入位置就是父节点的左或右子节点。

staticintfind_vma_links(struct mm_struct *mm, unsignedlong addr,unsigned long end, struct vm_area_struct **pprev,struct rb_node ***rb_link, struct rb_node **rb_parent){struct rb_node **__rb_link, *__rb_parent, *rb_prev;	__rb_link = &mm->mm_rb.rb_node;/* 从红黑树根节点开始 */	rb_prev = __rb_parent = NULL;/* 遍历红黑树,找合适的插入位置 */while (*__rb_link) {struct vm_area_struct *vma_tmp;		__rb_parent = *__rb_link;		vma_tmp = rb_entry(__rb_parent, struct vm_area_struct, vm_rb);/* 拿到当前节点的VMA *//* 插入的VMA起始地址小于当前节点的vm_end,向左遍历 */if (vma_tmp->vm_end > addr) {/* 如果当前节点和插入的VMA重叠,返回失败,VMA不能重叠 */if (vma_tmp->vm_start < end)return -ENOMEM;			__rb_link = &__rb_parent->rb_left;else {/* 插入的VMA起始地址大于等于当前节点的vm_end,向右遍历 */			rb_prev = __rb_parent;/* 更新前一个节点,就是当前节点 */			__rb_link = &__rb_parent->rb_right;		}	}/* 给pprev赋值,就是前一个节点的VMA,如果rb_prev为空,说明插入的是头节点 */	*pprev = NULL;if (rb_prev)		*pprev = rb_entry(rb_prev, struct vm_area_struct, vm_rb);	*rb_link = __rb_link;/* 插入位置,父节点的左或右子节点指针 */	*rb_parent = __rb_parent;/* 父节点 */return 0;}// 这里要注意,VMA不能重叠,一旦重叠就返回-ENOMEM

可能有人还是没明白prev、rb_link、rb_parent和待插入VMA的关系,看下面这张图就清楚了——待插入VMA的父节点是rb_parent,插入位置是rb_parent的左子节点(rb_link=rb_parent->rb_left),前一个节点pprev就是父节点对应的VMA,是不是一下子就懂了?

找到位置后,就靠vma_link函数把VMA插入链表和红黑树,它会调用__vma_link做具体的插入操作,还会处理文件映射相关的逻辑(如果VMA关联了文件)。

staticvoidvma_link(struct mm_struct *mm, struct vm_area_struct *vma,struct vm_area_struct *prev, struct rb_node **rb_link,struct rb_node *rb_parent){struct address_space *mapping = NULL;// 如果VMA关联了文件,加写锁,保证文件映射的线程安全if (vma->vm_file) {		mapping = vma->vm_file->f_mapping;i_mmap_lock_write(mapping);	}	__vma_link(mm, vma, prev, rb_link, rb_parent);/* 插入链表和红黑树 */	__vma_link_file(vma);/* 把VMA添加到文件的地址空间树里,文件映射用 */// 解锁if (mapping)i_mmap_unlock_write(mapping);	mm->map_count++;/* VMA数量加1 */validate_mm(mm);/* 验证mm_struct的有效性,调试用 */}// __vma_link又调用两个函数,分别处理链表和红黑树static void__vma_link(struct mm_struct *mm, struct vm_area_struct *vma,struct vm_area_struct *prev, struct rb_node **rb_link,struct rb_node *rb_parent){	__vma_link_list(mm, vma, prev);/* 插入mm->mmap链表 */	__vma_link_rb(mm, vma, rb_link, rb_parent);/* 插入mm->mm_rb红黑树 */}// 是不是很清晰?分层设计,各司其职,内核代码都这样,看着复杂,拆解开就简单了

链表插入很简单,就是普通的双向链表操作,__vma_link_list函数实现就更直白了,我就不啰嗦了,直接上代码,大家一看就能明白。

void __vma_link_list(struct mm_struct *mm, struct vm_area_struct *vma,		struct vm_area_struct *prev){	struct vm_area_struct *next;	vma->vm_prev = prev;/* 给VMA的前一个节点赋值 */if (prev) {/* prev不为空,插入到prev后面 */next = prev->vm_next;		prev->vm_next = vma;else {/* prev为空,说明插入的是链表头,更新mm->mmap */next = mm->mmap;		mm->mmap = vma;	}	vma->vm_next = next;/* 给VMA的后一个节点赋值 */if (next) /* next不为空,更新next的前一个节点为当前VMA */		next->vm_prev = vma;}// 双向链表的标准插入操作,没什么复杂的

红黑树插入稍微复杂一点,但内核已经给我们封装好了,__vma_link_rb函数里,调用rb_link_node把VMA的vm_rb节点插入到指定位置,然后更新红黑树的平衡,不用我们自己处理红黑树的旋转,是不是突然觉得内核大佬们很贴心?哈哈哈

void __vma_link_rb(struct mm_struct *mm, struct vm_area_struct *vma,struct rb_node **rb_link, struct rb_node *rb_parent){/* 更新VMA后面的空闲间隙,优化内存分配 */if (vma->vm_next)		vma_gap_update(vma->vm_next);else		mm->highest_vm_end = vm_end_gap(vma);/*	 * 这段注释不用深究,大概意思是:插入前不知道VMA的前一个节点,所以先把rb_subtree_gap设为0	 * 插入后再更新为正确的值,最后再平衡红黑树	 */	rb_link_node(&vma->vm_rb, rb_parent, rb_link);/* 插入红黑树节点 */        vma->rb_subtree_gap = 0;        vma_gap_update(vma);/* 更新b_insert(vma, &mm->mm_rb);/* 平衡红黑树 */}// 红黑树的平衡操作,内核已经实现好了,我们不用关心细节,知道怎么调用就行空闲间隙 */        vma_r

五、VMA的合并

进程运行过程中,会频繁申请、释放内存,导致地址空间里有很多相邻的VMA,如果不合并,VMA数量会越来越多,查找、管理都会变慢。内核会自动合并前后衔接、权限和属性相同的VMA,靠的就是vma_merge函数。

合并的情况简单并复杂着?得看这俩块地的权限一样不、映射的文件一样不、偏移量连不连得上。

源码里硬生生列出了8种合并的场景。你没看错,8种!

 /* The following mprotect cases have to be considered, where AAAA is * the area passed down from mprotect_fixup, never extending beyond one * vma, PPPPPP is the prev vma specified, and NNNNNN the next vma after: * *     AAAA             AAAA                   AAAA *    PPPPPPNNNNNN    PPPPPPNNNNNN       PPPPPPNNNNNN *    cannot merge    might become       might become *                    PPNNNNNNNNNN       PPPPPPPPPPNN *    mmap, brk or    case 4 below       case 5 below *    mremap move: *                        AAAA               AAAA *                    PPPP    NNNN       PPPPNNNNXXXX *                    might become       might become *                    PPPPPPPPPPPP 1 or  PPPPPPPPPPPP 6 or *                    PPPPPPPPNNNN 2 or  PPPPPPPPXXXX 7 or *                    PPPPNNNNNNNN 3     PPPPXXXXXXXX 8 */
  1. 跟前面的兄弟合体。

  2. 跟后面的兄弟合体。

  3. 前后通吃,三合一。

  4. 把前兄弟的一部分割给后兄弟。

  5. 把后兄弟的一部分割给前兄弟。

  6. 把后兄弟整个吞了,跟前兄弟和下下兄弟连在一块。

  7. 把后兄弟吞了,跟前兄弟合体。

  8. 把后兄弟吞了,跟下下兄弟合体。

合并后的效果,看下面这张图,每种情况对应的合并结果都标得很清楚,不用死记硬背,知道有这些情况就行,实际调试时,遇到合并相关的问题,回头看这两张图就够了。

struct vm_area_struct *vma_merge(struct mm_struct *mm,    truct vm_area_struct *prev, unsigned long addr,     signed long end, unsigned long vm_flags,    truct anon_vma *anon_vma, struct file *file,           pgoff, struct mempolicy *policy,    truct vm_userfaultfd_ctx vm_userfaultfd_ctx){  goff_t pglen = (end - addr) >> PAGE_SHIFT;/* 计算VMA的页面数量,右移PAGE_SHIFT就是除以页大小 */        vm_area_struct *area, *next; int err;  *      标志的VMA不合并,比如VM_SPECIAL,这些VMA有特殊用途,合并会出问题  */ if (vm_flags & VM_SPECIAL)  return NULL;  * 找到后一个VMA,prev为空,next就是链表头 */      rev)  next = prev->vm_next;  lse   ext = mm->mmap;    a = next;    (area && area->vm_end == end)  /* 对应case6、7、8,后一个VMA的结束地址和新VMA一样 */    xt = next->vm_next;     面这几个警告,是调试用的,参数不对就会报警,内核常用的调试手段 */   _WARN_ON(prev && addr <= prev->vm_start); VM_WARN_ON(area && end > area->vm_end);  M_WARN_ON(addr >= end);  * 第一步:判断能不能和前一个VMA合并         前一个VMA的结束地址 == 新VMA的起始地址,且权限、属性都相同,就能合并 */  * mpol_equal在非NUMA系统中始终返回TRUE,不用纠结 */ /* can_vma_merge_after判断前一个VMA能不能和后面的合并 */ if (prev && prev->vm_end == addr &&     ol_equal(vma_policy(prev), policy) &&    an_vma_merge_after(prev, vm_flags,         anon_vma, file, pgoff,         vm_userfaultfd_ctx)) {  /*   * 能和前一个合并,再看看能不能和后一个也合并   */   * 后一个VMA的起始地址 == 新VMA的结束地址,且属性相同 */  if (next && end == next->vm_start &&        _equal(policy, vma_policy(next)) &&     an_vma_merge_before(next, vm_flags,           anon_vma, file,           pgoff+pglen,            m_userfaultfd_ctx) &&      _mergeable_anon_vma(prev->anon_vma,            next->anon_vma, NULL)) {       /* 对应case1、6,和前后都合并 */       = __vma_adjust(prev, prev->vm_start,          ->vm_end, prev->vm_pgoff, NULL,                      } e        /* 对5、7,只和前一个合并 */       = __vma_adjust(prev, prev->vm_start,             ev->vm_pgoff, NULL, prev);  if (err)    eturn NULL;   hugepaged_enter_vma_merge(prev, vm_flags);/* 大页相关,不用管 */                return pMA,就是前一个VMA(已经扩展了) */     /*     二步:判断能不能和后一个VMA合并  */ if (next && end == next->vm_start &&   mpol_equal(policy, vma_policy(next)) &&   can_vma_merge_before(next, vm_flags,          anon_vma, file, pgoff+pglen,          vm_userfaultfd_ctx)) {  /* 前一个VMA的结束地址 > 新VMA的起始地址,重叠部分覆盖,和后一个合并(case4) */  if (prev && addr < prev->vm_end) /* case 4 */   err = __vma_adjust(prev, prev->vm_start,         r, prev->vm_pgoff, NULL, next);             /* 对应case3、8,只和后一个合并 */   err = __vma_adjust(area, addr, next->vm_end,      next->vm_pgoff - pglen, NULL, next);      a = next;/* 合并后的VMA是后一个VMA(已经扩展了) */      if (err)      urn NULL;  khugepaged_enter_vma_merge(area, vm_flags);    turn area;        urn NULL;/* 不能合并,返回NULL */}// 这段代码看着长,其实逻辑很简单:先试能不能和前一个合并,再试能不能和后一个合并,能合并就调用__vma_adjust调整,不能就返回NULL// 当年我调试一个VMA合并失败的bug,就是因为vm_flags不一样,导致can_vma_merge_after返回false,查了好久才发现}        ret            re                                ret                  }            are                                                                         else {                                                   add                                                                                                                                                                                                               * 第   }        rev;/* 返回合并后的V             k                    r                                          end, pr                 err 应case2、lse                                                           prev);                                       next                  err                                                                                                is                           v                                                                                                                             c                        mpol                                         /                                                                                                                                           c     mp                                  /*/        /*             /*      V            VM   /* 下            ne                   if    are             n      e                if (p      /                               * 特殊      /        struct      p                    s              pgoff_t                    s                   un             s       

其实你看啊,写内核代码的大佬也是普通人,无非就是被极其苛刻的性能和碎片问题逼出来的强迫症。为了快那么一点点,加缓存、用红黑树;为了省那么一点点内存,搞出8种合并姿势。这帮老炮儿!!

写到这儿,我忽然有点感慨。当年刚入行翻《Understanding the Linux Kernel》,VMA那一章真看得我头大。现在回想,其实核心很简单:VMA是内核跟进程签的“内存使用合同”,每一段虚拟地址的规则都写得清清楚楚。malloc背后glibc小块走brk/堆,大块偷偷mmap,内核插VMA、可能merge,一切就这么运转。

我也不知道这么说对不对,反正这些年被VMA坑过也爽过。VMA数量爆炸、merge失败、flags设错……每个坑都让我多长点记性。

深入浅出 Linux 内核(进程篇):进程调度

深入浅出 Linux 内核(进程篇):CFS调度

深入浅出 Linux 内核(进程篇):进程创建与退出

深入浅出 Linux 内核(进程篇):进程切换之ARM体系架构

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-04 09:35:32 HTTP/2.0 GET : https://f.mffb.com.cn/a/490216.html
  2. 运行时间 : 0.106490s [ 吞吐率:9.39req/s ] 内存消耗:4,641.77kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=ff747801fdfe5afd07960099ba1f8fdd
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000635s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000668s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.003379s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.003684s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000624s ]
  6. SELECT * FROM `set` [ RunTime:0.002575s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000691s ]
  8. SELECT * FROM `article` WHERE `id` = 490216 LIMIT 1 [ RunTime:0.002092s ]
  9. UPDATE `article` SET `lasttime` = 1783128932 WHERE `id` = 490216 [ RunTime:0.007783s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000355s ]
  11. SELECT * FROM `article` WHERE `id` < 490216 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000590s ]
  12. SELECT * FROM `article` WHERE `id` > 490216 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000801s ]
  13. SELECT * FROM `article` WHERE `id` < 490216 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.005801s ]
  14. SELECT * FROM `article` WHERE `id` < 490216 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.001395s ]
  15. SELECT * FROM `article` WHERE `id` < 490216 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.004309s ]
0.108082s