Linux Buddy系统算法源码解析

2024-04-20 20:38

本文主要是介绍Linux Buddy系统算法源码解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Linux引导起来之后,伙伴系统分配算法是和物理内存最底层的接口。所有内存分配函数,比如vmalloc/kmalloc最后都是通过伙伴算法对内存进行分配的。接下来我们将解读一下伙伴系统的分配和回收算法。

伙伴系统模块提供了两个主要的接口给上层程序,他们是:

1.         页面请求函数

struct page * fastcall __alloc_pages(gfp_t gfp_mask, unsigned int order, struct zonelist *zonelist)

2.         页面释放函数

fastcall void __free_pages(struct page *page, unsigned int order)

【注】:在这里我对fastcall进行说明一下,他指明了函数参数传递的方式,前8个字节通过寄存器传入,后面多出来的通过栈传入,入栈顺序是从右到左。

 

下面分别对两个函数进行源码级的分析。

1.         页面分配

a)         如果请求的内存大小正好是一个页面,则需要从该CPU的冷热页面队列中进行分配。

       if (likely(order == 0)) {

              struct per_cpu_pages *pcp;

              pcp = &zone_pcp(zone, cpu)->pcp[cold]; // 获取冷热页面队列的指针。

              local_irq_save(flags);

              if (!pcp->count) { // 如果发现页面队列中的页面数为0,需要从伙伴系统中申请一组页面,填充页面队列。

                     pcp->count += rmqueue_bulk(zone, 0,

                                          pcp->batch, &pcp->list);

                     if (unlikely(!pcp->count))

                            goto failed;

              }

              // 从队列中取出一页分配出去

              page = list_entry(pcp->list.next, struct page, lru);

              list_del(&page->lru);

              // 计数器减一

              pcp->count--;

 

b)        如果申请的物理内存大于1个页面,直接从伙伴系统中申请

spin_lock_irqsave(&zone->lock, flags);

              page = __rmqueue(zone, order); // 访问伙伴系统

              spin_unlock(&zone->lock);

              if (!page)

                     goto failed;

 

c)        对刚才分配的页面进行一系列的检查。检查失败需要重新从伙伴系统进行分配。并且对该页面进行相应的初始化。

       if (prep_new_page(page, order))

              goto again;

d)        是否需要对页面进行清零操作

       if (gfp_flags & __GFP_ZERO)

              prep_zero_page(page, order, gfp_flags);

e)         如果从伙伴系统中申请的页面不是一个页面,即order > 1,我们称之为一个compound页面。下面需要初始化compound页面。通过设置页面的标志位来表示他是一个compound页面。

set_bit(PG_compound, &(page)->flags)

f)         如果以上过程页面分配成功,则完成分配,如果不成功,继续下面的尝试。

g)        kswapd内核线程唤醒,换出一些页面。

       do {

              wakeup_kswapd(*z, order);

       } while (*(++z));

h)        从伙伴系统中,尝试再次分配页面。

       page = get_page_from_freelist(gfp_mask, order, zonelist, alloc_flags);

       if (page)

              goto got_pg;

i)          如果发现该任务是专用于分配内存的(PF_MEMALLOC)并且不处于中断处理函数中,则强制性的分配内存,也就是说不管有没有到每个内存区的地水位线,都给他分配,除非是真的没得分配了。

       if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))

                     && !in_interrupt()) {

              if (!(gfp_mask & __GFP_NOMEMALLOC)) {

nofail_alloc:

                     /* go through the zonelist yet again, ignoring mins */

                     page = get_page_from_freelist(gfp_mask, order,

                            zonelist, ALLOC_NO_WATERMARKS);

                     if (page)

                            goto got_pg;

                     if (gfp_mask & __GFP_NOFAIL) {

                            blk_congestion_wait(WRITE, HZ/50);

                            goto nofail_alloc;

                     }

              }

              goto nopage; // 表示没有页面可以分配了。

       }

j)          如果不是特殊任务,则系统尝试将各个区的内存进行一个rebalance的动作,就是回收些内存。

did_some_progress = try_to_free_pages(zonelist->zones, gfp_mask);

然后在尝试分配:

              page = get_page_from_freelist(gfp_mask, order,

                                          zonelist, alloc_flags);

              if (page)

                     goto got_pg;

       如果分配失败,就终止请求页面的进程。

              out_of_memory(zonelist, gfp_mask, order);

 

       我们接下来分析一下从伙伴系统申请页面的函数。

       static struct page *__rmqueue(struct zone *zone, unsigned int order)

       从空闲表中当前order进行查找,找到第一个有空闲块的order,叫做current_order,然后进行分配,有两种情况,第一种情况:刚好current_order就是请求的order,则不需要合并。第二种情况:current_order是大于请求的order的,这种情况,是需要进行页面块的拆分和合并的。调用expand函数。通过设置相邻页面的PG_buddy位来表示他们是伙伴。

       for (current_order = order; current_order < MAX_ORDER; ++current_order) {

              area = zone->free_area + current_order;

              if (list_empty(&area->free_list))

                     continue;

 

              page = list_entry(area->free_list.next, struct page, lru);

              list_del(&page->lru);

              rmv_page_order(page);

              area->nr_free--;

              zone->free_pages -= 1UL << order;

              expand(zone, page, order, current_order, area);

              return page;

       }

 

2.         页面释放

fastcall void __free_pages(struct page *page, unsigned int order)

a)         先测试该页面的引用计数器是不是为1,否则不能释放,因为其他进程可能引用了该页面。

       if (put_page_testzero(page))

b)        如果释放的页面为1,则释放到热页面队列中去。否则直接释放到伙伴系统中去。

              if (order == 0)

                     free_hot_page(page);

              else

                     __free_pages_ok(page, order);

接下来我们分析一下释放一个页面到伙伴系统的代码:

static inline void __free_one_page(struct page *page, struct zone *zone, unsigned int order)

1.         如果是compound页面,先清除页面标志位PG_compound

       if (unlikely(PageCompound(page)))

              destroy_compound_page(page, order);

2.         查找伙伴块,并对伙伴块进行合并,最后将合并后的块插入到新的order中去。这个过程一直持续下去,直到伙伴块合并完为止。

       while (order < MAX_ORDER-1) {

              unsigned long combined_idx;

              struct free_area *area;

              struct page *buddy;

 

              buddy = __page_find_buddy(page, page_idx, order);

              if (!page_is_buddy(buddy, order))

                     break;            /* Move the buddy up one level. */

 

              list_del(&buddy->lru);

              area = zone->free_area + order;

              area->nr_free--;

              rmv_page_order(buddy);

              combined_idx = __find_combined_index(page_idx, order);

              page = page + (combined_idx - page_idx);

              page_idx = combined_idx;

              order++;

       }

 

这篇关于Linux Buddy系统算法源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/921270

相关文章

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

Linux使用cut进行文本提取的操作方法

《Linux使用cut进行文本提取的操作方法》Linux中的cut命令是一个命令行实用程序,用于从文件或标准输入中提取文本行的部分,本文给大家介绍了Linux使用cut进行文本提取的操作方法,文中有详... 目录简介基础语法常用选项范围选择示例用法-f:字段选择-d:分隔符-c:字符选择-b:字节选择--c

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型