vmalloc简单学习-程序员宅基地

技术标签: linux  内核  

vmalloc 主要用于给内核从高端内存中以页为单位分配大块内存,对应关系如下所示,主要分配 896M~1G 的物理地址空间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BYa4wp1X-1653630238073)(linux内存管理源码深入探究.assets/v2-903ce9a2c1d5203183f78ad56a41eb66_720w.jpg)]

vmalloc 的执行流程简述为:

  1. 分配小块内存。实例化vmalloc内存分配器的核心数据描述符 vmap_area|vm_struct|pages,为 vmalloc 的工作提供内存基础。分配合适的虚拟内存区间 hole 从空闲红黑树找到合适大小的hole,隔离分配给 vmalloc

  2. 分配物理内存。调用alloc_page系列函数,从 pcp 或者 buddy 0 阶每次分配1页物理内存,直到分配满足需求为止。

  3. 更新页表映射。将物理内存与虚拟内存一一建立映射,然后返回虚拟起始地址给用户,完成分配动作

vmalloc

vmalloc 调用链如下

vmalloc 
    --- __vmalloc
    	--- __vmalloc_node
    		--- get_vm_area_node : 获取一个 vm_struct
                --- __get_vm_area_node
    		--- __vmalloc_area_node : 为 vm_struct 映射 pages

__get_vm_area_node 用于申请 vm_struct ,vm_struct 用于管理如下图所示的 vmalloc 区对应的虚拟地址,属于高端内存的非连续映射区域。所有的 vmalloc 区由 vmlist 串联起来,因此,想要根据某个地址区间使用 vmalloc 区,只需要遍历 vmlist 即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wvd3Kd1M-1653630238076)(linux内存管理源码深入探究.assets/v2-ed8a67338be62f15e02df9dddde4d7af_720w.jpg)]

具体地,__get_vm_area_node 实现如下

  1. 首先,调用 kmalloc_node 为 vm_struct 结构申请内存(这部分内存为低端内存)
  2. 根据 size+addr < tmp->addr 遍历 vmlist 找出待申请地址对应的下一个 vm_struct
  3. 初始化申请好的 vm_struct ,其表示的便是申请的地址段对应的虚拟地址
struct vm_struct *__get_vm_area_node(unsigned long size, unsigned long flags,
				unsigned long start, unsigned long end, int node)
{
    
	struct vm_struct **p, *tmp, *area;
	unsigned long align = 1;
	unsigned long addr;
	// ...
	addr = ALIGN(start, align);
	size = PAGE_ALIGN(size);

	area = kmalloc_node(sizeof(*area), GFP_KERNEL, node);
	size += PAGE_SIZE;

	write_lock(&vmlist_lock);
	for (p = &vmlist; (tmp = *p) != NULL ;p = &tmp->next) {
    
		if ((unsigned long)tmp->addr < addr) {
    
			if((unsigned long)tmp->addr + tmp->size >= addr)
				addr = ALIGN(tmp->size + 
					     (unsigned long)tmp->addr, align);
			continue;
		}
		if ((size + addr) < addr)
			goto out;
		if (size + addr <= (unsigned long)tmp->addr)
			goto found;
		addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);
		if (addr > end - size)
			goto out;
	}

found:
	area->next = *p;
	*p = area;

	area->flags = flags;
	area->addr = (void *)addr;
	area->size = size;
	area->pages = NULL;
	area->nr_pages = 0;
	area->phys_addr = 0;
	write_unlock(&vmlist_lock);

	return area;

out:
	write_unlock(&vmlist_lock);
	kfree(area);
	if (printk_ratelimit())
		printk(KERN_WARNING "allocation failed: out of vmalloc space - use vmalloc=<size> to increase size.\n");
	return NULL;
}

接着通过 __vmalloc_area_node 完成 page 申请,及 page 与 vm_struct 的映射。

  1. 通过 alloc_page 挨个申请 page ,并存入 pages 数组中
  2. map_vm_area 将 vm_struct 与 pages 进行映射
void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
				pgprot_t prot, int node)
{
    
	struct page **pages;
	unsigned int nr_pages, array_size, i;

	nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
	array_size = (nr_pages * sizeof(struct page *));

	area->nr_pages = nr_pages;
	//...
	for (i = 0; i < area->nr_pages; i++) {
    
		if (node < 0)
			area->pages[i] = alloc_page(gfp_mask);
		else
			area->pages[i] = alloc_pages_node(node, gfp_mask, 0);
		if (unlikely(!area->pages[i])) {
    
			/* Successfully allocated i pages, free them in __vunmap() */
			area->nr_pages = i;
			goto fail;
		}
	}

	if (map_vm_area(area, prot, &pages))
		goto fail;
	return area->addr;

fail:
	vfree(area->addr);
	return NULL;
}

map_vm_area 主要用于创建页表。这里梳理一下如何建立页表映射。linux 中默认才用四级页表,不同的体系结构根据需求来使用不同级页表。创建页表只需要逐级递归创建

五级页表一共分为如下所示,对应了接下来的创建代码

在这里插入图片描述

整个修改发生于内核页表上,因此 pgd 为内核页表的 pgd ,通过 pgd_offset_k 可以获得,其通过 init_mm->pgd_t 进行获取

调用 pgd_addr_end 获得其需要遍历的下一个 页表项,通过 vmap_pud_range 进行 pud 的创建。整个过程循环进行,直到所有范围内的 pgd 都创建完成。

#define pgd_offset(mm,addr)	((mm)->pgd + pgd_index(addr))
#define pgd_offset_k(address) pgd_offset(&init_mm, address)

int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page ***pages)
{
    
	pgd_t *pgd;
	unsigned long next;
	unsigned long addr = (unsigned long) area->addr;
	unsigned long end = addr + area->size - PAGE_SIZE;
	int err;

	BUG_ON(addr >= end);
	pgd = pgd_offset_k(addr);
	do {
    
		next = pgd_addr_end(addr, end);
		err = vmap_pud_range(pgd, addr, next, prot, pages);
		if (err)
			break;
	} while (pgd++, addr = next, addr != end);
	flush_cache_vmap((unsigned long) area->addr, end);
	return err;
}

vmap_pud_range 中进行 pud 的创建,由于是全新的页表,因此需要通过 pud_alloc 申请 page 进行存放,首地址为 pud 。遍历申请好的 pud 调用 vmap_pmd_range 进行 pmd 的递归创建

static inline int vmap_pud_range(pgd_t *pgd, unsigned long addr,
			unsigned long end, pgprot_t prot, struct page ***pages)
{
    
	pud_t *pud;
	unsigned long next;

	pud = pud_alloc(&init_mm, pgd, addr);
	if (!pud)
		return -ENOMEM;
	do {
    
		next = pud_addr_end(addr, end);
		if (vmap_pmd_range(pud, addr, next, prot, pages))
			return -ENOMEM;
	} while (pud++, addr = next, addr != end);
	return 0;
}

vmap_pmd_range 与 vmap_pud_range 类似,最后调用 vmap_pte_range

static inline int vmap_pmd_range(pud_t *pud, unsigned long addr,
			unsigned long end, pgprot_t prot, struct page ***pages)
{
    
	pmd_t *pmd;
	unsigned long next;

	pmd = pmd_alloc(&init_mm, pud, addr);
	if (!pmd)
		return -ENOMEM;
	do {
    
		next = pmd_addr_end(addr, end);
		if (vmap_pte_range(pmd, addr, next, prot, pages))
			return -ENOMEM;
	} while (pmd++, addr = next, addr != end);
	return 0;
}

vmap_pte_range 用于创建 pte 并设置物理地址,调用 set_pte_at 完成物理地址的赋值

static int vmap_pte_range(pmd_t *pmd, unsigned long addr,
			unsigned long end, pgprot_t prot, struct page ***pages)
{
    
	pte_t *pte;

	pte = pte_alloc_kernel(pmd, addr);
	if (!pte)
		return -ENOMEM;
	do {
    
		struct page *page = **pages;
		WARN_ON(!pte_none(*pte));
		if (!page)
			return -ENOMEM;
		set_pte_at(&init_mm, addr, pte, mk_pte(page, prot));
		(*pages)++;
	} while (pte++, addr += PAGE_SIZE, addr != end);
	return 0;
}

上述迭代过程,结合最开始的页表结构就很好理解了。并且需要清楚的是,因为是新的页表项,需要申请 page 进行存放

最后说一下进程内核页表的更新。从进程被创建时,除了复制一份父进程的页表还会复制一份内核页表。而内核页表必须保证每个进程使用的都是一样的,在 vmalloc 中,已经触发了内核页表的修改(所谓内核页表就是 init 进程的 mm_struct),那其他进程如何同步呢?

答案是通过缺页中断来实现延时同步。因为当其他进程想要访问新映射的 vmalloc 区虚拟地址时,由于其内核页表没更新,因此会匹配不到,触发缺页中断。进而此时再将修改的内核页表同步到该进程中即可。意味着如果没有使用到这部分地址,也不会触发内核页表同步。可以说很巧妙了

最后放一张画得很好地关系图如下,图片来自 https://mp.weixin.qq.com/s/92vE6kfAVxUayJkC_5mflQ

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_37637511/article/details/125002362

智能推荐

jquery图片轮播带缩略图_jq带缩略图的呼吸轮播-程序员宅基地

文章浏览阅读618次。(1)html 标签:div class="lb_list"> span class="btn left_btn">span> div class="large_box"> ul> li> img src="images/img1.jpg" width="530" height="350"> _jq带缩略图的呼吸轮播

熊海cms渗透测试-程序员宅基地

文章浏览阅读1.8k次。熊海cms渗透测试_熊海cms

什么是软件测试-程序员宅基地

文章浏览阅读7k次,点赞2次,收藏32次。1.软件测试的定义在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。(1)规定的条件(2)目的:发现程序错误,衡量软件质量通俗定义:软件测试是一种实际输出与预期输出之间的审核或者比较过程。设计过程当中:预期结果<------>实际输出需求测试是保证实际输出与预期输出之间的审核或比较过程,及时发现软件Bug与缺陷,避免项目上线由于Bug给公司带来损失。2.软件开发模型瀑布模型、快速原型模型、螺旋模型(1)_什么是软件测试

不会写只会说是不是一种才啊_不会说只会写-程序员宅基地

文章浏览阅读307次。写代码没有耐心,何况自己水平又那么烂,只会空想着这要有什么功能,指点着,自己又不会,不知道别人是不是很讨厌我啊?看同学个个有了工作,自己还没有,望着工作要求而叹,什么都不会,只知道一点点。每次有个好的IDear,感觉上都会实现的,只是自己不会编程啊,要是手里有个团队就好了,这个功能XX去做,XXX你去做这个部分,看来我只是做狗头军师的料,不知道我这只千里马何人才能相中啊!!_不会说只会写

根据银行卡号码获取银行卡归属行以及logo图标_支付宝获取银行图标-程序员宅基地

文章浏览阅读1.2k次。转自:https://blog.csdn.net/qq_28268507/article/details/68941754之前做商城的时候遇到过根据银行卡号码获取银行归属地信息以及银行logo的需求,当初参考的是网上的一篇博客android根据银行卡卡号判断银行后来觉得数据不够权威或者数据信息不全,最近又发现了一个新的api接口,支付宝提供的根据银行卡号码获取银行卡归属地信息接口地址:..._支付宝获取银行图标

[前端网站源码]基于HTML5,DIV CSS实现的酒店运营与推广(静态网页设计)_酒店网页设计动态效果代码csdn-程序员宅基地

文章浏览阅读34次。该项目使用HTML5、CSS3、DIV开发的静态网站,是针对需要做前段网页设计相关毕设课设的同学,前段相关知识点老师都有讲过可以免费学习,以及怎么改项目中图片和文字都有相关教程,教同学们怎么将项目改成独一无二的一份,就算是零基础的同学也可以非常轻松搞定前段网页毕设课设项目,对于基础不好的同学是不错的选择。..._酒店网页设计动态效果代码csdn

随便推点

VO:简单的视觉里程计代码注释(代码可运行)_视觉里程计开源代码-程序员宅基地

文章浏览阅读5.5k次,点赞6次,收藏65次。走完SLAM十四讲前端之后,代码都已经注释完,但还是感觉有点迷茫,所以专门参考冯兵的博客,实现简单的视觉里程计。收获是又重新认识到了C++基础的薄弱,决定之后的晚上要刷牛客题。不过就SLAM前端而言这部分基本可以理解代码了,这篇对VO代码进行注释。基本过程:1、获取图像2、对图像进行处理3、通过FAST算法对图像进行特征检测,通过KLT光流法跟踪图像的特征,如果跟踪的特征有所丢失,特..._视觉里程计开源代码

@mapperscan 匹配一个或多个包_一起做ROSDEMO:基于find_object_2d的目标模板匹配识别...-程序员宅基地

文章浏览阅读1k次。转载文章标注:本文转自CSDN,作者:跃动的风原文链接:https://blog.csdn.net/qq_23670601/article/details/93663974我们希望机器人能够更加智能一点,抓住我们想要的任何东西,而不是通过贴标签(ar_makrer)或者简单的颜色过滤分割(比如固定识别某纯色物体)来进行目标物体的识别。所以我们打算采用其他的方法来进行目标的识别识别。目前我..._@mapperscan后缀匹配

react 项目使用highcharts滚动条来展示数据_highstock react-程序员宅基地

文章浏览阅读1.6k次。在使用图表画图的时候总会,由于展示的面积有限,无法将数据完全展示到图表中,这个时候就可能考虑使用滚动条来滑动展示数据。 讲一下过程,我首先找资源,找到了这个,发现很适合我的需求。 highcharts很有意思,他单独的为react创立了一个包叫react-highcharts,如果你引入这个包,并且你想使用滚动条来实现,要引入: var ReactHighstock = require(..._highstock react

04.SSM框架整合-Mybatis映射文件的存放路径注意事项_ssm映射资源文件夹-程序员宅基地

文章浏览阅读2.2k次,点赞6次,收藏13次。一: Mapper接口与映射文件在同一目录下方式一:同一目录,位置不同在Spring的核心配置文件中,配置了在指定包下批量扫描mapper映射文件: <!-- 批量扫描mapper包,创建代理对象 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.lg.mapper"/>_ssm映射资源文件夹

loadrunner运行场景时,常见错误及解决方法_loadrunner报错link调试-程序员宅基地

文章浏览阅读1.8k次。目录1、Error -27727:.32、Error -27728:.33、Error -27791:.34、Error -27492:.45、Error -27498:.46、Error -26612:.47、Error -27496:.58、Error -27995:.59、Error -27279:...510、Error -27796.5..._loadrunner报错link调试

计算机维修知识论文,计算机维修论文2000字-程序员宅基地

文章浏览阅读210次。如何保养和维护好一台计算机,最大限度地延长计算机的使用寿命,这是我们非常关心和经常面临的问题。下面是答.案.网 ZQNF.Com小编给大家推荐的计算机维修论文2000字,希望大家喜欢!计算机维修论文2000字篇一  《浅谈计算机硬件的维护维修》【摘要】计算机维护分硬件维护和软件维护两部分,硬件维护主要是指计算机上主板、显卡、处理器、硬盘、显示器等硬件设备的维护。【关键词】计算机硬件;故障;维护维修..._基于知识库的计算机硬件维修服务系统论文