爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      linux源码解析06–常用内存分配函数kmalloc、vmalloc、malloc和mmap实现原理

      首页 知识中心 软件开发 文章详情页

      linux源码解析06–常用内存分配函数kmalloc、vmalloc、malloc和mmap实现原理

      2023-06-16 06:12:13 阅读次数:100

      linux

      1.kmalloc函数

      static __always_inline void *kmalloc(size_t size, gfp_t flags)
      {
      	if (__builtin_constant_p(size)) {
      #ifndef CONFIG_SLOB
      		unsigned int index;
      #endif
      		if (size > KMALLOC_MAX_CACHE_SIZE)
      			return kmalloc_large(size, flags);
      #ifndef CONFIG_SLOB
      		index = kmalloc_index(size);  ///查找使用的哪个slab缓冲区
      
      		if (!index)
      			return ZERO_SIZE_PTR;
      
      		return kmem_cache_alloc_trace(    ///从slab分配内存
      				kmalloc_caches[kmalloc_type(flags)][index],
      				flags, size);
      #endif
      	}
      	return __kmalloc(size, flags);
      }
      

      kmem_cache_alloc_trace分配函数

      void *
      kmem_cache_alloc_trace(struct kmem_cache *cachep, gfp_t flags, size_t size)
      {
      	void *ret;
      
      	ret = slab_alloc(cachep, flags, size, _RET_IP_);  ///分配slab缓存
      
      	ret = kasan_kmalloc(cachep, ret, size, flags);
      	trace_kmalloc(_RET_IP_, ret,
      		      size, cachep->size, flags);
      	return ret;
      }
      

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

      2.vmalloc函数

      vmalloc()
      	->__vmalloc_node_flags()
      	->__vmalloc_node()
      	->__vmalloc_node_range()
      

      核心函数__vmalloc_node_range

      static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
      				 pgprot_t prot, unsigned int page_shift,
      				 int node)
      {
      	const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
      	unsigned long addr = (unsigned long)area->addr;
      	unsigned long size = get_vm_area_size(area);   ///计算vm_struct包含多少个页面
      	unsigned long array_size;
      	unsigned int nr_small_pages = size >> PAGE_SHIFT;
      	unsigned int page_order;
      	struct page **pages;
      	unsigned int i;
      
      	array_size = (unsigned long)nr_small_pages * sizeof(struct page *);
      	gfp_mask |= __GFP_NOWARN;
      	if (!(gfp_mask & (GFP_DMA | GFP_DMA32)))
      		gfp_mask |= __GFP_HIGHMEM;
      
      	/* Please note that the recursion is strictly bounded. */
      	if (array_size > PAGE_SIZE) {
      		pages = __vmalloc_node(array_size, 1, nested_gfp, node,
      					area->caller);
      	} else {
      		pages = kmalloc_node(array_size, nested_gfp, node);
      	}
      
      	if (!pages) {
      		free_vm_area(area);
      		warn_alloc(gfp_mask, NULL,
      			   "vmalloc size %lu allocation failure: "
      			   "page array size %lu allocation failed",
      			   nr_small_pages * PAGE_SIZE, array_size);
      		return NULL;
      	}
      
      	area->pages = pages;  ///保存已分配页面的page数据结构的指针
      	area->nr_pages = nr_small_pages;
      	set_vm_area_page_order(area, page_shift - PAGE_SHIFT);
      
      	page_order = vm_area_page_order(area);
      
      	/*
      	 * Careful, we allocate and map page_order pages, but tracking is done
      	 * per PAGE_SIZE page so as to keep the vm_struct APIs independent of
      	 * the physical/mapped size.
      	 */
      	for (i = 0; i < area->nr_pages; i += 1U << page_order) {
      		struct page *page;
      		int p;
      
      		/* Compound pages required for remap_vmalloc_page */
      		page = alloc_pages_node(node, gfp_mask | __GFP_COMP, page_order); ///分配物理页面
      		if (unlikely(!page)) {
      			/* Successfully allocated i pages, free them in __vfree() */
      			area->nr_pages = i;
      			atomic_long_add(area->nr_pages, &nr_vmalloc_pages);
      			warn_alloc(gfp_mask, NULL,
      				   "vmalloc size %lu allocation failure: "
      				   "page order %u allocation failed",
      				   area->nr_pages * PAGE_SIZE, page_order);
      			goto fail;
      		}
      
      		for (p = 0; p < (1U << page_order); p++)
      			area->pages[i + p] = page + p;
      
      		if (gfpflags_allow_blocking(gfp_mask))
      			cond_resched();
      	}
      	atomic_long_add(area->nr_pages, &nr_vmalloc_pages);
      
      	if (vmap_pages_range(addr, addr + size, prot, pages, page_shift) < 0) { ///建立物理页面到vma的映射
      		warn_alloc(gfp_mask, NULL,
      			   "vmalloc size %lu allocation failure: "
      			   "failed to map pages",
      			   area->nr_pages * PAGE_SIZE);
      		goto fail;
      	}
      
      	return area->addr;
      
      fail:
      	__vfree(area->addr);
      	return NULL;
      }
      

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

      3.malloc函数

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

      linux源码解析06–常用内存分配函数kmalloc、vmalloc、malloc和mmap实现原理

      __do_sys_brk函数

      经过平台相关实现,malloc最终会调用SYSCALL_DEFINE1宏,扩展为__do_sys_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;    ///brk记录动态分配区的当前底部
      
      #ifdef CONFIG_COMPAT_BRK
      	/*
      	 * CONFIG_COMPAT_BRK can still be overridden by setting
      	 * randomize_va_space to 2, which will still cause mm->start_brk
      	 * to be arbitrarily shifted
      	 */
      	if (current->brk_randomized)
      		min_brk = mm->start_brk;
      	else
      		min_brk = mm->end_data;
      #else
      	min_brk = mm->start_brk;
      #endif
      	if (brk < min_brk)
      		goto out;
      
      	/*
      	 * Check against rlimit here. If this check is done later after the test
      	 * of oldbrk with newbrk then it can escape the test and let the data
      	 * segment grow beyond its set limit the in case where the limit is
      	 * not page aligned -Ram Gupta
      	 */
      	if (check_data_rlimit(rlimit(RLIMIT_DATA), brk, mm->start_brk,
      			      mm->end_data, mm->start_data))
      		goto out;
      
      	newbrk = PAGE_ALIGN(brk);
      	oldbrk = PAGE_ALIGN(mm->brk);
      	if (oldbrk == newbrk) {
      		mm->brk = brk;
      		goto success;
      	}
      
      	/*
      	 * Always allow shrinking brk.
      	 * __do_munmap() may downgrade mmap_sem to read.
      	 */
      	if (brk <= mm->brk) {  ///请求释放空间
      		int ret;
      
      		/*
      		 * mm->brk must to be protected by write mmap_sem so update it
      		 * before downgrading mmap_sem. When __do_munmap() fails,
      		 * mm->brk will be restored from origbrk.
      		 */
      		mm->brk = brk;
      		ret = __do_munmap(mm, newbrk, oldbrk-newbrk, &uf, true);
      		if (ret < 0) {
      			mm->brk = origbrk;
      			goto out;
      		} else if (ret == 1) {
      			downgraded = true;
      		}
      		goto success;
      	}
      
      	/* Check against existing mmap mappings. */
      	next = find_vma(mm, oldbrk);
      	if (next && newbrk + PAGE_SIZE > vm_start_gap(next))   ///发现有重叠,不需要寻找
      		goto out;
      
      	/* Ok, looks good - let it rip. */
      	if (do_brk_flags(oldbrk, newbrk-oldbrk, 0, &uf) < 0)  ///无重叠,新分配一个vma
      		goto out;
      	mm->brk = brk;   ///更新brk地址
      
      success:
      	populate = newbrk > oldbrk && (mm->def_flags & VM_LOCKED) != 0;
      	if (downgraded)
      		up_read(&mm->mmap_sem);
      	else
      		up_write(&mm->mmap_sem);
      	userfaultfd_unmap_complete(mm, &uf);
      	if (populate)  ///调用mlockall()系统调用,mm_populate会立刻分配物理内存
      		mm_populate(oldbrk, newbrk - oldbrk);
      	return brk;
      
      out:
      	retval = origbrk;
      	up_write(&mm->mmap_sem);
      	return retval;
      }
      

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

      do_brk_flags函数

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

      /*
       *  this is really a simplified "do_mmap".  it only handles
       *  anonymous maps.  eventually we may be able to do some
       *  brk-specific accounting here.
       */
      static int do_brk_flags(unsigned long addr, unsigned long len, unsigned long flags, struct list_head *uf)
      {
      	struct mm_struct *mm = current->mm;
      	struct vm_area_struct *vma, *prev;
      	struct rb_node **rb_link, *rb_parent;
      	pgoff_t pgoff = addr >> PAGE_SHIFT;
      	int error;
      	unsigned long mapped_addr;
      
      	/* Until we need other flags, refuse anything except VM_EXEC. */
      	if ((flags & (~VM_EXEC)) != 0)
      		return -EINVAL;
      	flags |= VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT | mm->def_flags;  ///默认属性,可读写
      
      	mapped_addr = get_unmapped_area(NULL, addr, len, 0, MAP_FIXED); ///返回未使用过的,未映射的线性地址区间的,起始地址
      	if (IS_ERR_VALUE(mapped_addr))
      		return mapped_addr;
      
      	error = mlock_future_check(mm, mm->def_flags, len);
      	if (error)
      		return error;
      
      	/* Clear old maps, set up prev, rb_link, rb_parent, and uf */
      	if (munmap_vma_range(mm, addr, len, &prev, &rb_link, &rb_parent, uf)) ///寻找适合插入的红黑树节点
      		return -ENOMEM;
      
      	/* Check against address space limits *after* clearing old maps... */
      	if (!may_expand_vm(mm, flags, 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? */  ///检查是否能合并addr到附近的vma,若不能,只能新建一个vma
      	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 = vm_area_alloc(mm);
      	if (!vma) {
      		vm_unacct_memory(len >> PAGE_SHIFT);
      		return -ENOMEM;
      	}
      
      	vma_set_anonymous(vma);
      	vma->vm_start = addr;
      	vma->vm_end = addr + len;
      	vma->vm_pgoff = pgoff;
      	vma->vm_flags = flags;
      	vma->vm_page_prot = vm_get_page_prot(flags);
      	vma_link(mm, vma, prev, rb_link, rb_parent);  ///新vma添加到mmap链表和红黑树
      out:
      	perf_event_mmap(vma);
      	mm->total_vm += len >> PAGE_SHIFT;
      	mm->data_vm += len >> PAGE_SHIFT;
      	if (flags & VM_LOCKED)
      		mm->locked_vm += (len >> PAGE_SHIFT);
      	vma->vm_flags |= VM_SOFTDIRTY;
      	return 0;
      }
      

      mm_populate()函数

      依次调用

      mm_populate()
      	->__mm_populate()
      	->populate_vma_page_range()
      	->__get_user_pages()
      

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

      __get_user_pages()函数

      static long __get_user_pages(struct mm_struct *mm,
      		unsigned long start, unsigned long nr_pages,
      		unsigned int gup_flags, struct page **pages,
      		struct vm_area_struct **vmas, int *locked)
      {
      	long ret = 0, i = 0;
      	struct vm_area_struct *vma = NULL;
      	struct follow_page_context ctx = { NULL };
      
      	if (!nr_pages)
      		return 0;
      
      	start = untagged_addr(start);
      
      	VM_BUG_ON(!!pages != !!(gup_flags & (FOLL_GET | FOLL_PIN)));
      
      	/*
      	 * If FOLL_FORCE is set then do not force a full fault as the hinting
      	 * fault information is unrelated to the reference behaviour of a task
      	 * using the address space
      	 */
      	if (!(gup_flags & FOLL_FORCE))
      		gup_flags |= FOLL_NUMA;
      
      	do {  ///依次处理每个页面
      		struct page *page;
      		unsigned int foll_flags = gup_flags;
      		unsigned int page_increm;
      
      		/* first iteration or cross vma bound */
      		if (!vma || start >= vma->vm_end) {
      			vma = find_extend_vma(mm, start);  ///检查是否可以扩增vma
      			if (!vma && in_gate_area(mm, start)) {
      				ret = get_gate_page(mm, start & PAGE_MASK,
      						gup_flags, &vma,
      						pages ? &pages[i] : NULL);
      				if (ret)
      					goto out;
      				ctx.page_mask = 0;
      				goto next_page;
      			}
      
      			if (!vma) {
      				ret = -EFAULT;
      				goto out;
      			}
      			ret = check_vma_flags(vma, gup_flags);
      			if (ret)
      				goto out;
      
      			if (is_vm_hugetlb_page(vma)) {  ///支持巨页
      				i = follow_hugetlb_page(mm, vma, pages, vmas,
      						&start, &nr_pages, i,
      						gup_flags, locked);
      				if (locked && *locked == 0) {
      					/*
      					 * We've got a VM_FAULT_RETRY
      					 * and we've lost mmap_lock.
      					 * We must stop here.
      					 */
      					BUG_ON(gup_flags & FOLL_NOWAIT);
      					BUG_ON(ret != 0);
      					goto out;
      				}
      				continue;
      			}
      		}
      retry:
      		/*
      		 * If we have a pending SIGKILL, don't keep faulting pages and
      		 * potentially allocating memory.
      		 */
      		if (fatal_signal_pending(current)) {  ///如果当前进程收到SIGKILL信号,直接退出
      			ret = -EINTR;
      			goto out;
      		}
      		cond_resched();  //判断是否需要调度,内核中常用该函数,优化系统延迟
      
      		page = follow_page_mask(vma, start, foll_flags, &ctx);  ///查看VMA的虚拟页面是否已经分配物理内存,返回已经映射的页面的page
      		if (!page) {
      			ret = faultin_page(vma, start, &foll_flags, locked); ///若无映射,主动触发虚拟页面到物理页面的映射
      			switch (ret) {
      			case 0:
      				goto retry;
      			case -EBUSY:
      				ret = 0;
      				fallthrough;
      			case -EFAULT:
      			case -ENOMEM:
      			case -EHWPOISON:
      				goto out;
      			case -ENOENT:
      				goto next_page;
      			}
      			BUG();
      		} else if (PTR_ERR(page) == -EEXIST) {
      			/*
      			 * Proper page table entry exists, but no corresponding
      			 * struct page.
      			 */
      			goto next_page;
      		} else if (IS_ERR(page)) {
      			ret = PTR_ERR(page);
      			goto out;
      		}
      		if (pages) {
      			pages[i] = page;
      			flush_anon_page(vma, page, start);  ///分配完物理页面,刷新缓存
      			flush_dcache_page(page);
      			ctx.page_mask = 0;
      		}
      next_page:
      		if (vmas) {
      			vmas[i] = vma;
      			ctx.page_mask = 0;
      		}
      		page_increm = 1 + (~(start >> PAGE_SHIFT) & ctx.page_mask);
      		if (page_increm > nr_pages)
      			page_increm = nr_pages;
      		i += page_increm;
      		start += page_increm * PAGE_SIZE;
      		nr_pages -= page_increm;
      	} while (nr_pages);
      out:
      	if (ctx.pgmap)
      		put_dev_pagemap(ctx.pgmap);
      	return i ? i : ret;
      }
      
      follow_page_mask函数返回已经映射的页面的page,最终会调用follow_page_pte函数,其实现如下:
      
      follow_page_pte函数
      static struct page *follow_page_pte(struct vm_area_struct *vma,
      		unsigned long address, pmd_t *pmd, unsigned int flags,
      		struct dev_pagemap **pgmap)
      {
      	struct mm_struct *mm = vma->vm_mm;
      	struct page *page;
      	spinlock_t *ptl;
      	pte_t *ptep, pte;
      	int ret;
      
      	/* FOLL_GET and FOLL_PIN are mutually exclusive. */
      	if (WARN_ON_ONCE((flags & (FOLL_PIN | FOLL_GET)) ==
      			 (FOLL_PIN | FOLL_GET)))
      		return ERR_PTR(-EINVAL);
      retry:
      	if (unlikely(pmd_bad(*pmd)))
      		return no_page_table(vma, flags);
      
      	ptep = pte_offset_map_lock(mm, pmd, address, &ptl);  ///获得pte和一个锁
      	pte = *ptep;
      	if (!pte_present(pte)) {  ///处理页面不在内存中,作以下处理
      		swp_entry_t entry;
      		/*
      		 * KSM's break_ksm() relies upon recognizing a ksm page
      		 * even while it is being migrated, so for that case we
      		 * need migration_entry_wait().
      		 */
      		if (likely(!(flags & FOLL_MIGRATION)))
      			goto no_page;
      		if (pte_none(pte))
      			goto no_page;
      		entry = pte_to_swp_entry(pte);
      		if (!is_migration_entry(entry))
      			goto no_page;
      		pte_unmap_unlock(ptep, ptl);
      		migration_entry_wait(mm, pmd, address);   ///等待页面合并完成再尝试
      		goto retry;
      	}
      	if ((flags & FOLL_NUMA) && pte_protnone(pte))
      		goto no_page;
      	if ((flags & FOLL_WRITE) && !can_follow_write_pte(pte, flags)) {
      		pte_unmap_unlock(ptep, ptl);
      		return NULL;
      	}
      
      	page = vm_normal_page(vma, address, pte); ///根据pte,返回物理页面page(只返回普通页面,特殊页面不参与内存管理)
      	if (!page && pte_devmap(pte) && (flags & (FOLL_GET | FOLL_PIN))) { ///处理设备映射文件
      		/*
      		 * Only return device mapping pages in the FOLL_GET or FOLL_PIN
      		 * case since they are only valid while holding the pgmap
      		 * reference.
      		 */
      		*pgmap = get_dev_pagemap(pte_pfn(pte), *pgmap);
      		if (*pgmap)
      			page = pte_page(pte);
      		else
      			goto no_page;
      	} else if (unlikely(!page)) {   ///处理vm_normal_page()没返回有效页面情况
      		if (flags & FOLL_DUMP) {
      			/* Avoid special (like zero) pages in core dumps */
      			page = ERR_PTR(-EFAULT);
      			goto out;
      		}
      
      		if (is_zero_pfn(pte_pfn(pte))) {   ///系统零页,不会返回错误
      			page = pte_page(pte);
      		} else {
      			ret = follow_pfn_pte(vma, address, ptep, flags);
      			page = ERR_PTR(ret);
      			goto out;
      		}
      	}
      
      	/* try_grab_page() does nothing unless FOLL_GET or FOLL_PIN is set. */
      	if (unlikely(!try_grab_page(page, flags))) {
      		page = ERR_PTR(-ENOMEM);
      		goto out;
      	}
      	/*
      	 * We need to make the page accessible if and only if we are going
      	 * to access its content (the FOLL_PIN case).  Please see
      	 * Documentation/core-api/pin_user_pages.rst for details.
      	 */
      	if (flags & FOLL_PIN) {
      		ret = arch_make_page_accessible(page);
      		if (ret) {
      			unpin_user_page(page);
      			page = ERR_PTR(ret);
      			goto out;
      		}
      	}
      	if (flags & FOLL_TOUCH) { ///FOLL_TOUCH, 标记页面可访问
      		if ((flags & FOLL_WRITE) &&
      		    !pte_dirty(pte) && !PageDirty(page))
      			set_page_dirty(page);
      		/*
      		 * pte_mkyoung() would be more correct here, but atomic care
      		 * is needed to avoid losing the dirty bit: it is easier to use
      		 * mark_page_accessed().
      		 */
      		mark_page_accessed(page);
      	}
      	if ((flags & FOLL_MLOCK) && (vma->vm_flags & VM_LOCKED)) {
      		/* Do not mlock pte-mapped THP */
      		if (PageTransCompound(page))
      			goto out;
      
      		/*
      		 * The preliminary mapping check is mainly to avoid the
      		 * pointless overhead of lock_page on the ZERO_PAGE
      		 * which might bounce very badly if there is contention.
      		 *
      		 * If the page is already locked, we don't need to
      		 * handle it now - vmscan will handle it later if and
      		 * when it attempts to reclaim the page.
      		 */
      		if (page->mapping && trylock_page(page)) {
      			lru_add_drain();  /* push cached pages to LRU */
      			/*
      			 * Because we lock page here, and migration is
      			 * blocked by the pte's page reference, and we
      			 * know the page is still mapped, we don't even
      			 * need to check for file-cache page truncation.
      			 */
      			mlock_vma_page(page);
      			unlock_page(page);
      		}
      	}
      out:
      	pte_unmap_unlock(ptep, ptl);
      	return page;
      no_page:
      	pte_unmap_unlock(ptep, ptl);
      	if (!pte_none(pte))
      		return NULL;
      	return no_page_table(vma, flags);
      }
      

      总结: (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一般用于用户程序分配内存,读写大文件,链接动态库,多进程内存共享等; 实现过程流程图: linux源码解析06–常用内存分配函数kmalloc、vmalloc、malloc和mmap实现原理 mmap根据文件关联性和映射区域是否共享等属性,其映射分为4类 1.私有匿名映射 fd=-1,且flags=MAP_ANONYMOUS|MAP_PRIVATE,创建的mmap映射是私有匿名映射; 用途是在glibc分配大内存时,如果需分配内存大于MMAP_THREASHOLD(128KB),glibc默认用mmap代替brk分配内存;

      2.共享匿名映射 fd=-1,且flags=MAP_ANONYMOUS|MAP_SHARED; 常用于父子进程的通信,共享一块内存区域; do_mmap_pgoff()->mmap_region(),最终调用shmem_zero_setup打开/dev/zero设备文件;

      另外直接打开/dev/zero设备文件,然后使用这个句柄创建mmap,也是最终调用shmem模块创建共享匿名映射;

      3.私有文件映射 flags=MAP_PRIVATE; 常用场景是,加载动态共享库;

      4.共享文件映射 flags=MAP_SHARED;有两个应用场景; (1)读写文件: 内核的会写机制会将内存数据同步到磁盘; (2)进程间通信: 多个独立进程,打开同一个文件,互相都可以观察到,可是实现多进程通信; 核心函数如下:

      unsigned long mmap_region(struct file *file, unsigned long addr,
      		unsigned long len, vm_flags_t vm_flags, unsigned long pgoff,
      		struct list_head *uf)
      {
      	struct mm_struct *mm = current->mm;
      	struct vm_area_struct *vma, *prev, *merge;
      	int error;
      	struct rb_node **rb_link, *rb_parent;
      	unsigned long charged = 0;
      
      	/* Check against address space limit. */
      	if (!may_expand_vm(mm, vm_flags, len >> PAGE_SHIFT)) {
      		unsigned long nr_pages;
      
      		/*
      		 * MAP_FIXED may remove pages of mappings that intersects with
      		 * requested mapping. Account for the pages it would unmap.
      		 */
      		nr_pages = count_vma_pages_range(mm, addr, addr + len);
      
      		if (!may_expand_vm(mm, vm_flags,
      					(len >> PAGE_SHIFT) - nr_pages))
      			return -ENOMEM;
      	}
      
      	/* Clear old maps, set up prev, rb_link, rb_parent, and uf */
      	if (munmap_vma_range(mm, addr, len, &prev, &rb_link, &rb_parent, uf))
      		return -ENOMEM;
      	/*
      	 * Private writable mapping: check memory availability
      	 */
      	if (accountable_mapping(file, vm_flags)) {
      		charged = len >> PAGE_SHIFT;
      		if (security_vm_enough_memory_mm(mm, charged))
      			return -ENOMEM;
      		vm_flags |= VM_ACCOUNT;
      	}
      
      	/*
      	 * Can we just expand an old mapping?
      	 */
      	vma = vma_merge(mm, prev, addr, addr + len, vm_flags,   ///尝试合并vma
      			NULL, file, pgoff, NULL, NULL_VM_UFFD_CTX);
      	if (vma)
      		goto out;
      
      	/*
      	 * Determine the object being mapped and call the appropriate
      	 * specific mapper. the address has already been validated, but
      	 * not unmapped, but the maps are removed from the list.
      	 */
      	vma = vm_area_alloc(mm);   ///分配一个新vma
      	if (!vma) {
      		error = -ENOMEM;
      		goto unacct_error;
      	}
      
      	vma->vm_start = addr;
      	vma->vm_end = addr + len;
      	vma->vm_flags = vm_flags;
      	vma->vm_page_prot = vm_get_page_prot(vm_flags);
      	vma->vm_pgoff = pgoff;
      
      	if (file) {   ///文件映射  
      		if (vm_flags & VM_DENYWRITE) {
      			error = deny_write_access(file);
      			if (error)
      				goto free_vma;
      		}
      		if (vm_flags & VM_SHARED) {
      			error = mapping_map_writable(file->f_mapping);
      			if (error)
      				goto allow_write_and_free_vma;
      		}
      
      		/* ->mmap() can change vma->vm_file, but must guarantee that
      		 * vma_link() below can deny write-access if VM_DENYWRITE is set
      		 * and map writably if VM_SHARED is set. This usually means the
      		 * new file must not have been exposed to user-space, yet.
      		 */
      		vma->vm_file = get_file(file);
      		error = call_mmap(file, vma);
      		if (error)
      			goto unmap_and_free_vma;
      
      		/* Can addr have changed??
      		 *
      		 * Answer: Yes, several device drivers can do it in their
      		 *         f_op->mmap method. -DaveM
      		 * Bug: If addr is changed, prev, rb_link, rb_parent should
      		 *      be updated for vma_link()
      		 */
      		WARN_ON_ONCE(addr != vma->vm_start);
      
      		addr = vma->vm_start;
      
      		/* If vm_flags changed after call_mmap(), we should try merge vma again
      		 * as we may succeed this time.
      		 */
      		if (unlikely(vm_flags != vma->vm_flags && prev)) {
      			merge = vma_merge(mm, prev, vma->vm_start, vma->vm_end, vma->vm_flags,
      				NULL, vma->vm_file, vma->vm_pgoff, NULL, NULL_VM_UFFD_CTX);
      			if (merge) {
      				/* ->mmap() can change vma->vm_file and fput the original file. So
      				 * fput the vma->vm_file here or we would add an extra fput for file
      				 * and cause general protection fault ultimately.
      				 */
      				fput(vma->vm_file);
      				vm_area_free(vma);
      				vma = merge;
      				/* Update vm_flags to pick up the change. */
      				vm_flags = vma->vm_flags;
      				goto unmap_writable;
      			}
      		}
      
      		vm_flags = vma->vm_flags;
      	} else if (vm_flags & VM_SHARED) {   ///共享映射
      		error = shmem_zero_setup(vma);   ///共享匿名映射
      		if (error)
      			goto free_vma;
      	} else {
      		vma_set_anonymous(vma);  ///匿名映射
      	}
      
      	/* Allow architectures to sanity-check the vm_flags */
      	if (!arch_validate_flags(vma->vm_flags)) {
      		error = -EINVAL;
      		if (file)
      			goto unmap_and_free_vma;
      		else
      			goto free_vma;
      	}
      
      	vma_link(mm, vma, prev, rb_link, rb_parent);   ///vma加入mm系统
      	/* Once vma denies write, undo our temporary denial count */
      	if (file) {
      unmap_writable:
      		if (vm_flags & VM_SHARED)
      			mapping_unmap_writable(file->f_mapping);
      		if (vm_flags & VM_DENYWRITE)
      			allow_write_access(file);
      	}
      	file = vma->vm_file;
      out:
      	perf_event_mmap(vma);
      
      	vm_stat_account(mm, vm_flags, len >> PAGE_SHIFT);
      	if (vm_flags & VM_LOCKED) {
      		if ((vm_flags & VM_SPECIAL) || vma_is_dax(vma) ||
      					is_vm_hugetlb_page(vma) ||
      					vma == get_gate_vma(current->mm))
      			vma->vm_flags &= VM_LOCKED_CLEAR_MASK;
      		else
      			mm->locked_vm += (len >> PAGE_SHIFT);
      	}
      
      	if (file)
      		uprobe_mmap(vma);
      
      	/*
      	 * New (or expanded) vma always get soft dirty status.
      	 * Otherwise user-space soft-dirty page tracker won't
      	 * be able to distinguish situation when vma area unmapped,
      	 * then new mapped in-place (which must be aimed as
      	 * a completely new data area).
      	 */
      	vma->vm_flags |= VM_SOFTDIRTY;
      
      	vma_set_page_prot(vma);
      
      	return addr;
      
      unmap_and_free_vma:
      	fput(vma->vm_file);
      	vma->vm_file = NULL;
      
      	/* Undo any partial mapping done by a device driver. */
      	unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end);
      	charged = 0;
      	if (vm_flags & VM_SHARED)
      		mapping_unmap_writable(file->f_mapping);
      allow_write_and_free_vma:
      	if (vm_flags & VM_DENYWRITE)
      		allow_write_access(file);
      free_vma:
      	vm_area_free(vma);
      unacct_error:
      	if (charged)
      		vm_unacct_memory(charged);
      	return error;
      }
      

      总结: 以上的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”命令修改;

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.51cto.com/u_7784550/6050890,作者:luteresa,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:Spring项目&Spring-boot-starter 组件&Spring Cloud 生态圈以及微服务简介

      下一篇:JDK源码之Integer类—toUnsignedString0()方法

      相关文章

      2025-05-19 09:04:53

      查看RISC-V版本的gcc中默认定义的宏

      查看RISC-V版本的gcc中默认定义的宏

      2025-05-19 09:04:53
      c++ , linux
      2025-04-01 10:28:37

      小课2:筛选信息命令

      小课2:筛选信息命令

      2025-04-01 10:28:37
      bash , linux , 升序 , 服务器 , 运维
      2025-03-26 09:31:12

      shell脚本实现查询代码中定义了多少宏的方法

      shell脚本实现查询代码中定义了多少宏的方法

      2025-03-26 09:31:12
      bash , linux , 运维
      2025-03-06 09:15:26

      spring cloud系统安装涉及的技术说明

      spring cloud系统安装涉及的技术说明

      2025-03-06 09:15:26
      docker , linux , 安装 , 技术
      2025-03-05 09:24:43

      【Python】使用numpy库实现Tic-Tac-Toe井字棋

      【Python】使用numpy库实现Tic-Tac-Toe井字棋

      2025-03-05 09:24:43
      linux , 右键 , 安装 , 打开 , 输入
      2025-02-21 08:56:54

      【Linux】内存分配小结--malloc、brk、mmap

      【Linux】内存分配小结--malloc、brk、mmap

      2025-02-21 08:56:54
      malloc , 内存 , 分配 , 地址
      2025-02-13 08:41:02

      使用 Rust 实现零拷贝数据处理:性能优化的极致探索

      在计算机科学中,零拷贝是一种优化技术,指的是在程序中减少或完全避免数据在内存中的拷贝操作,从而提高性能。

      2025-02-13 08:41:02
      Rust , 内存 , 性能 , 拷贝 , 文件
      2025-02-10 08:53:59

      【linux】linux C 程序 注册信号处理函数

      【linux】linux C 程序 注册信号处理函数  

      2025-02-10 08:53:59
      linux , 函数 , 注册 , 程序
      2025-01-17 09:07:21

      课时3:处理信息命令

      课时3:处理信息命令

      2025-01-17 09:07:21
      linux , shell , 数据库 , 服务器 , 运维
      2024-12-06 06:39:06

      SpringBoot项目在linux下部署脚本实例

      SpringBoot项目在linux下部署脚本实例

      2024-12-06 06:39:06
      linux , 示例 , 脚本
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5233991

      查看更多

      最新文章

      shell脚本实现查询代码中定义了多少宏的方法

      2025-03-26 09:31:12

      【Python】使用numpy库实现Tic-Tac-Toe井字棋

      2025-03-05 09:24:43

      【linux】linux C 程序 注册信号处理函数

      2025-02-10 08:53:59

      [接口测试 - http.client篇] 16 基于http.client之POM实战一下

      2024-09-25 10:15:32

      创建你的第一个webdriver python代码

      2024-09-25 10:15:15

      [Java基础] StringBuffer 和 StringBuilder 类应用及源码分析

      2024-09-25 10:14:09

      查看更多

      热门文章

      程序员之路:Linux压缩命令

      2023-03-21 02:52:11

      Linux下查看进程线程数的方法

      2023-05-15 10:04:01

      Python编程:paramiko模块远程登录

      2023-02-21 08:02:44

      基于jenkins和jmeter实现接口自动化过程

      2023-06-08 06:18:44

      简单的内核编译安装图解

      2023-03-16 07:49:58

      linux网络编程初步

      2023-05-10 06:17:58

      查看更多

      热门标签

      java Java python 编程开发 代码 开发语言 算法 线程 Python html 数组 C++ 元素 javascript c++
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      CentOS8基础篇12:使用RPM管理telnet-server软件包

      C#编程:用Substring获取文件路径文件名扩展名

      Linux脚本练习之script011-当执行程序时,让使用者选择 `boy` 或者 `girl`,如果使用者输入 `B` 或者 `b`,则显示 `He is a boy`。

      ElasticSearch之Health API

      [Java基础] StringBuffer 和 StringBuilder 类应用及源码分析

      jprofiler Alpine 容器集成

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号