伙伴系统之伙伴系统概述--Linux内存管理(十五)
日期 | 内核版本 | 架构 | 作者 | GitHub | CSDN |
---|---|---|---|---|---|
2016-09-02 | Linux-4.7 | X86 & arm | gatieme | LinuxDeviceDrivers | Linux内存管理 |
1 前景回顾
1.1 Linux内存管理的层次结构
Linux把物理内存划分为三个层次来管理
层次 | 描述 |
---|---|
存储节点(Node) | CPU被划分为多个节点(node), 内存则被分簇, 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存簇bank,即每个内存簇被认为是一个节点 |
管理区(Zone) | 每个物理内存节点node被划分为多个内存管理区域, 用于表示不同范围的内存, 内核可以使用不同的映射方式映射物理内存 |
页面(Page) | 内存被细分为多个页面帧, 页面是最基本的页面分配的单位 | |
为了支持NUMA模型,也即CPU对不同内存单元的访问时间可能不同,此时系统的物理内存被划分为几个节点(node), 一个node对应一个内存簇bank,即每个内存簇被认为是一个节点
首先, 内存被划分为结点. 每个节点关联到系统中的一个处理器, 内核中表示为
pg_data_t
的实例. 系统中每个节点被链接到一个以NULL结尾的pgdat_list
链表中<而其中的每个节点利用pg_data_tnode_next
字段链接到下一节.而对于PC这种UMA结构的机器来说, 只使用了一个成为contig_page_data的静态pg_data_t结构.接着各个节点又被划分为内存管理区域, 一个管理区域通过struct zone_struct描述, 其被定义为zone_t, 用以表示内存的某个范围, 低端范围的16MB被描述为ZONE_DMA, 某些工业标准体系结构中的(ISA)设备需要用到它, 然后是可直接映射到内核的普通内存域ZONE_NORMAL,最后是超出了内核段的物理地址域ZONE_HIGHMEM, 被称为高端内存. 是系统中预留的可用内存空间, 不能被内核直接映射.
最后页帧(page frame)代表了系统内存的最小单位, 堆内存中的每个页都会创建一个struct page的一个实例. 传统上,把内存视为连续的字节,即内存为字节数组,内存单元的编号(地址)可作为字节数组的索引. 分页管理时,将若干字节视为一页,比如4K byte. 此时,内存变成了连续的页,即内存为页数组,每一页物理内存叫页帧,以页为单位对内存进行编号,该编号可作为页数组的索引,又称为页帧号.
1.2 内存结点pg_data_t
在LINUX中引入一个数据结构struct pglist_data
,来描述一个node,定义在include/linux/mmzone.h
文件中。(这个结构被typedef pg_data_t)。
对于NUMA系统来讲, 整个系统的内存由一个node_data的pg_data_t指针数组来管理
对于PC这样的UMA系统,使用struct pglist_data contig_page_data ,作为系统唯一的node管理所有的内存区域。(UMA系统中中只有一个node)
可以使用NODE_DATA(node_id)来查找系统中编号为node_id的结点, 而UMA结构下由于只有一个结点, 因此该宏总是返回全局的contig_page_data, 而与参数node_id无关.
NODE_DATA(node_id)查找编号node_id的结点pg_data_t信息 参见NODE_DATA的定义
extern struct pglist_data *node_data[];
#define NODE_DATA(nid) (node_data[(nid)])
在UMA结构的机器中, 只有一个node结点即contig_page_data, 此时NODE_DATA直接指向了全局的contig_page_data, 而与node的编号nid无关, 参照include/linux/mmzone.h?v=4.7, line 858
extern struct pglist_data contig_page_data;
#define NODE_DATA(nid) (&contig_page_data)
1.3 物理内存区域
因为实际的计算机体系结构有硬件的诸多限制, 这限制了页框可以使用的方式. 尤其是, Linux内核必须处理80x86体系结构的两种硬件约束.
ISA总线的直接内存存储DMA处理器有一个严格的限制 : 他们只能对RAM的前16MB进行寻址
在具有大容量RAM的现代32位计算机中, CPU不能直接访问所有的物理地址, 因为线性地址空间太小, 内核不可能直接映射所有物理内存到线性地址空间, 我们会在后面典型架构(x86)上内存区域划分详细讲解x86_32上的内存区域划分
因此Linux内核对不同区域的内存需要采用不同的管理方式和映射方式, 因此内核将物理地址或者成用zone_t表示的不同地址区域
对于x86_32的机器,管理区(内存区域)类型如下分布
类型 | 区域 |
---|---|
ZONE_DMA | 0~15MB |
ZONE_NORMAL | 16MB~895MB |
ZONE_HIGHMEM | 896MB~物理内存结束 |
1.4 物理页帧
内核把物理页作为内存管理的基本单位. 尽管处理器的最小可寻址单位通常是字, 但是, 内存管理单元MMU通常以页为单位进行处理. 因此,从虚拟内存的上来看,页就是最小单位.
页帧代表了系统内存的最小单位, 对内存中的每个页都会创建struct page的一个实例. 内核必须要保证page结构体足够的小,否则仅struct page就要占用大量的内存.
内核用struct page(include/linux/mm_types.h?v=4.7, line 45)结构表示系统中的每个物理页.
出于节省内存的考虑,struct page中使用了大量的联合体union.
mem_map
是一个struct page的数组,管理着系统中所有的物理内存页面。在系统启动的过程中,创建和分配mem_map的内存区域, mem_map定义在mm/page_alloc.c?v=4.7, line 6691
UMA体系结构中,free_area_init函数在系统唯一的struct node对象contig_page_data中node_mem_map成员赋值给全局的mem_map变量
1.5 启动过程中的内存初始化
在初始化过程中, 还必须建立内存管理的数据结构, 以及很多事务. 因为内核在内存管理完全初始化之前就需要使用内存. 在系统启动过程期间, 使用了额外的简化悉尼股市的内存管理模块, 然后在初始化完成后, 将旧的模块丢弃掉.
因此我们可以把linux内核的内存管理分三个阶段。
阶段 | 起点 | 终点 | 描述 |
---|---|---|---|
第一阶段 | 系统启动 | bootmem或者memblock初始化完成 | 此阶段只能使用memblock_reserve函数分配内存, 早期内核中使用init_bootmem_done = 1标识此阶段结束 |
第二阶段 | bootmem或者memblock初始化完 | buddy完成前 | 引导内存分配器bootmem或者memblock接受内存的管理工作, 早期内核中使用mem_init_done = 1标记此阶段的结束 |
第三阶段 | buddy初始化完成 | 系统停止运行 | 可以用cache和buddy分配内存 |
系统启动过程中的内存管理
首先我们来看看start_kernel是如何初始化系统的, start_kerne定义在init/main.c?v=4.7, line 479
其代码很复杂, 我们只截取出其中与内存管理初始化相关的部分, 如下所示
asmlinkage __visible void __init start_kernel(void)
{/* 设置特定架构的信息* 同时初始化memblock */setup_arch(&command_line);mm_init_cpumask(&init_mm);setup_per_cpu_areas();/* 初始化内存结点和内段区域 */build_all_zonelists(NULL, NULL);page_alloc_init();/** These use large bootmem allocations and must precede* mem_init();* kmem_cache_init();*/mm_init();kmem_cache_init_late();kmemleak_init();setup_per_cpu_pageset();rest_init();
}
函数 | 功能 |
---|---|
setup_arch | 是一个特定于体系结构的设置函数, 其中一项任务是负责初始化自举分配器 |
mm_init_cpumask | 初始化CPU屏蔽字 |
setup_per_cpu_areas | 函数(查看定义)给每个CPU分配内存,并拷贝.data.percpu段的数据. 为系统中的每个CPU的per_cpu变量申请空间. 在SMP系统中, setup_per_cpu_areas初始化源代码中(使用per_cpu宏)定义的静态per-cpu变量, 这种变量对系统中每个CPU都有一个独立的副本. 此类变量保存在内核二进制影像的一个独立的段中, setup_per_cpu_areas的目的就是为系统中各个CPU分别创建一份这些数据的副本 在非SMP系统中这是一个空操作 |
build_all_zonelists | 建立并初始化结点和内存域的数据结构 |
mm_init | 建立了内核的内存分配器, 其中通过mem_init停用bootmem分配器并迁移到实际的内存管理器(比如伙伴系统) 然后调用kmem_cache_init函数初始化内核内部用于小块内存区的分配器 |
kmem_cache_init_late | 在kmem_cache_init之后, 完善分配器的缓存机制, 当前3个可用的内核内存分配器slab, slob, slub都会定义此函数 |
kmemleak_init | Kmemleak工作于内核态,Kmemleak 提供了一种可选的内核泄漏检测,其方法类似于跟踪内存收集器。当独立的对象没有被释放时,其报告记录在 /sys/kernel/debug/kmemleak中, Kmemcheck能够帮助定位大多数内存错误的上下文 |
setup_per_cpu_pageset | 初始化CPU高速缓存行, 为pagesets的第一个数组元素分配内存, 换句话说, 其实就是第一个系统处理器分配 由于在分页情况下,每次存储器访问都要存取多级页表,这就大大降低了访问速度。所以,为了提高速度,在CPU中设置一个最近存取页面的高速缓存硬件机制,当进行存储器访问时,先检查要访问的页面是否在高速缓存中. |
1.6 伙伴系统
在内核初始化完成之后, 内存管理的责任就由伙伴系统来承担. 伙伴系统基于一种相对简单然而令人吃惊的强大算法.
Linux内核使用二进制伙伴算法来管理和分配物理内存页面, 该算法由Knowlton设计, 后来Knuth又进行了更深刻的描述.
伙伴系统是一个结合了2的方幂个分配器和空闲缓冲区合并计技术的内存分配方案, 其基本思想很简单. 内存被分成含有很多页面的大块, 每一块都是2个页面大小的方幂. 如果找不到想要的块, 一个大块会被分成两部分, 这两部分彼此就成为伙伴. 其中一半被用来分配, 而另一半则空闲. 这些块在以后分配的过程中会继续被二分直至产生一个所需大小的块. 当一个块被最终释放时, 其伙伴将被检测出来, 如果伙伴也空闲则合并两者.
内核如何记住哪些内存块是空闲的
分配空闲页面的方法
影响分配器行为的众多标识位
内存碎片的问题和分配器如何处理碎片
2 伙伴系统的结构
2.1 伙伴系统数据结构
系统内存中的每个物理内存页(页帧),都对应于一个struct page实例, 每个内存域都关联了一个struct zone的实例,其中保存了用于管理伙伴数据的主要数数组
// http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L324
struct zone
{/* free areas of different sizes */struct free_area free_area[MAX_ORDER];
};
struct free_area是一个伙伴系统的辅助数据结构, 它定义在include/linux/mmzone.h?v=4.7, line 88
struct free_area {struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
字段 | 描述 |
---|---|
free_list | 是用于连接空闲页的链表. 页链表包含大小相同的连续内存区 |
nr_free | 指定了当前内存区中空闲页块的数目(对0阶内存区逐页计算,对1阶内存区计算页对的数目,对2阶内存区计算4页集合的数目,依次类推 |
伙伴系统的分配器维护空闲页面所组成的块, 这里每一块都是2的方幂个页面, 方幂的指数称为阶.
阶是伙伴系统中一个非常重要的术语. 它描述了内存分配的数量单位. 内存块的长度是
zone->free_area[MAX_ORDER]数组中阶作为各个元素的索引, 用于指定对应链表中的连续内存区包含多少个页帧.
数组中第0个元素的阶为0, 它的free_list链表域指向具有包含区为单页(
20=1 )的内存页面链表数组中第1个元素的free_list域管理的内存区为两页(
21=2 )第3个管理的内存区为4页, 依次类推.
直到
2MAXORDER−1 个页面大小的块
2.2 最大阶MAX_ORDER与FORCE_MAX_ZONEORDER配置选项
一般来说MAX_ORDER
默认定义为11, 这意味着一次分配可以请求的页数最大是2^11=2048, 参见include/linux/mmzone.h?v=4.7, line 22
/* Free memory management - zoned buddy allocator. */
#ifndef CONFIG_FORCE_MAX_ZONEORDER
#define MAX_ORDER 11
#else
#define MAX_ORDER CONFIG_FORCE_MAX_ZONEORDER
#endif
#define MAX_ORDER_NR_PAGES (1 << (MAX_ORDER - 1))
但如果特定于体系结构的代码设置了FORCE_MAX_ZONEORDER
配置选项, 该值也可以手工改变
例如,IA-64系统上巨大的地址空间可以处理MAX_ORDER = 18
的情形,而ARM或v850系统则使用更小的值(如8或9). 但这不一定是由计算机支持的内存数量比较小引起的,也可能是内存对齐方式的要求所导致
可以参考一些架构的Kconfig文件如下
arm | arm64 |
---|---|
arch/arm/Kconfig?v=4.7, line 1696 | arch/arm64/Kconfig?v=4.7, line 679 |
比如arm64体系结构的Kconfig配置文件的描述 |
config FORCE_MAX_ZONEORDER
int
default "14" if (ARM64_64K_PAGES && TRANSPARENT_HUGEPAGE)
default "12" if (ARM64_16K_PAGES && TRANSPARENT_HUGEPAGE)
default "11"`
2.3 内存区是如何连接的
内存区中第1页内的链表元素, 可用于将内存区维持在链表中。因此,也不必引入新的数据结构来管理物理上连续的页,否则这些页不可能在同一内存区中. 如下图所示
伙伴不必是彼此连接的. 如果一个内存区在分配其间分解为两半, 内核会自动将未用的一半加入到对应的链表中.
如果在未来的某个时刻, 由于内存释放的缘故, 两个内存区都处于空闲状态, 可通过其地址判断其是否为伙伴. 管理工作较少, 是伙伴系统的一个主要优点.
基于伙伴系统的内存管理专注于某个结点的某个内存域, 例如, DMA或高端内存域. 但所有内存域和结点的伙伴系统都通过备用分配列表连接起来.
下图说明了这种关系.
最后要注意, 有关伙伴系统和当前状态的信息可以在/proc/buddyinfo中获取
上述输出给出了各个内存域中每个分配阶中空闲项的数目, 从左至右, 阶依次升高. 上面给出的信息取自4 GiB物理内存的AMD64系统.
传统伙伴系统算法
在内核分配内存时, 必须记录页帧的已分配或空闲状态, 以免两个进程使用同样的内存区域. 由于内存分配和释放非常频繁, 内核还必须保证相关操作尽快完成. 内核可以只分配完整的页帧. 将内存划分为更小的部分的工作, 则委托给用户空间中的标准库. 标准库将来源于内核的页帧拆分为小的区域, 并为进程分配内存.
内核中很多时候要求分配连续页. 为快速检测内存中的连续区域, 内核采用了一种古老而历经检验的技术: 伙伴系统
系统中的空闲内存块总是两两分组, 每组中的两个内存块称作伙伴. 伙伴的分配可以是彼此独立的. 但如果两个伙伴都是空闲的, 内核会将其合并为一个更大的内存块, 作为下一层次上某个内存块的伙伴.
下图示范了该系统, 图中给出了一对伙伴, 初始大小均为8页. 即系统中所有的页面都是8页的.
内核对所有大小相同的伙伴(1、2、4、8、16或其他数目的页),都放置到同一个列表中管理. 各有8页的一对伙伴也在相应的列表中.
如果系统现在需要8个页帧, 则将16个页帧组成的块拆分为两个伙伴. 其中一块用于满足应用程序的请求, 而剩余的8个页帧则放置到对应8页大小内存块的列表中.
如果下一个请求只需要2个连续页帧, 则由8页组成的块会分裂成2个伙伴, 每个包含4个页帧. 其中一块放置回伙伴列表中,而另一个再次分裂成2个伙伴, 每个包含2页。其中一个回到伙伴系统,另一个则传递给应用程序.
在应用程序释放内存时, 内核可以直接检查地址, 来判断是否能够创建一组伙伴, 并合并为一个更大的内存块放回到伙伴列表中, 这刚好是内存块分裂的逆过程。这提高了较大内存块可用的可能性.
在系统长期运行时,服务器运行几个星期乃至几个月是很正常的,许多桌面系统也趋向于长期开机运行,那么会发生称为碎片的内存管理问题。频繁的分配和释放页帧可能导致一种情况:系统中有若干页帧是空闲的,但却散布在物理地址空间的各处。换句话说,系统中缺乏连续页帧组成的较大的内存块,而从性能上考虑,却又很需要使用较大的连续内存块。通过伙伴系统可以在某种程度上减少这种效应,但无法完全消除。如果在大块的连续内存中间刚好有一个页帧分配出去,很显然这两块空闲的内存是无法合并的.
在内核版本2.6.24之后, 增加了一些有效措施来防止内存碎片.
3 避免碎片
在第1章给出的简化说明中, 一个双链表即可满足伙伴系统的所有需求. 在内核版本2.6.23之前, 的确是这样. 但在内核2.6.24开发期间, 内核开发者对伙伴系统的争论持续了相当长时间. 这是因为伙伴系统是内核最值得尊敬的一部分,对它的改动不会被大家轻易接受
3.1 内存碎片
伙伴系统的基本原理已经在第1章中讨论过,其方案在最近几年间确实工作得非常好。但在Linux内存管理方面,有一个长期存在的问题:在系统启动并长期运行后,物理内存会产生很多碎片。该情形如下图所示
假定内存由60页组成,这显然不是超级计算机,但用于示例却足够了。左侧的地址空间中散布着空闲页。尽管大约25%的物理内存仍然未分配,但最大的连续空闲区只有一页. 这对用户空间应用程序没有问题:其内存是通过页表映射的,无论空闲页在物理内存中的分布如何,应用程序看到的内存
似乎总是连续的。右图给出的情形中,空闲页和使用页的数目与左图相同,但所有空闲页都位于一个连续区中。
但对内核来说,碎片是一个问题. 由于(大多数)物理内存一致映射到地址空间的内核部分, 那么在左图的场景中, 无法映射比一页更大的内存区. 尽管许多时候内核都分配的是比较小的内存, 但也有时候需要分配多于一页的内存. 显而易见, 在分配较大内存的情况下, 右图中所有已分配页和空闲页都处于连续内存区的情形,是更为可取的.
很有趣的一点是, 在大部分内存仍然未分配时, 就也可能发生碎片问题. 考虑图3-25的情形.
只分配了4页,但可分配的最大连续区只有8页,因为伙伴系统所能工作的分配范围只能是2的幂次.
我提到内存碎片只涉及内核,这只是部分正确的。大多数现代CPU都提供了使用巨型页的可能性,比普通页大得多。这对内存使用密集的应用程序有好处。在使用更大的页时,地址转换后备缓冲器只需处理较少的项,降低了TLB缓存失效的可能性。但分配巨型页需要连续的空闲物理内存!
很长时间以来,物理内存的碎片确实是Linux的弱点之一。尽管已经提出了许多方法,但没有哪个方法能够既满足Linux需要处理的各种类型工作负荷提出的苛刻需求,同时又对其他事务影响不大。
3.2 依据可移动性组织页
在内核2.6.24开发期间,防止碎片的方法最终加入内核。在我讨论具体策略之前,有一点需要澄清。
文件系统也有碎片,该领域的碎片问题主要通过碎片合并工具解决。它们分析文件系统,重新排序已分配存储块,从而建立较大的连续存储区. 理论上,该方法对物理内存也是可能的,但由于许多物理内存页不能移动到任意位置,阻碍了该方法的实施。因此,内核的方法是反碎片(anti-fragmentation), 即试图从最初开始尽可能防止碎片.
反碎片的工作原理如何?
为理解该方法,我们必须知道内核将已分配页划分为下面3种不同类型。
页面类型 | 描述 | 举例 |
---|---|---|
不可移动页 | 在内存中有固定位置, 不能移动到其他地方. | 核心内核分配的大多数内存属于该类别 |
可移动页 | 可以随意地移动. | 属于用户空间应用程序的页属于该类别. 它们是通过页表映射的 如果它们复制到新位置,页表项可以相应地更新,应用程序不会注意到任何事 |
可回收页 | 不能直接移动, 但可以删除, 其内容可以从某些源重新生成. | 例如,映射自文件的数据属于该类别 kswapd守护进程会根据可回收页访问的频繁程度,周期性释放此类内存. , 页面回收本身就是一个复杂的过程. 内核会在可回收页占据了太多内存时进行回收, 在内存短缺(即分配失败)时也可以发起页面回收. |
页的可移动性,依赖该页属于3种类别的哪一种. 内核使用的反碎片技术, 即基于将具有相同可移动性的页分组的思想.
为什么这种方法有助于减少碎片?
由于页无法移动, 导致在原本几乎全空的内存区中无法进行连续分配. 根据页的可移动性, 将其分配到不同的列表中, 即可防止这种情形. 例如, 不可移动的页不能位于可移动内存区的中间, 否则就无法从该内存区分配较大的连续内存块.
想一下, 上图中大多数空闲页都属于可回收的类别, 而分配的页则是不可移动的. 如果这些页聚集到两个不同的列表中, 如下图所示. 在不可移动页中仍然难以找到较大的连续空闲空间, 但对可回收的页, 就容易多了.
但要注意, 从最初开始, 内存并未划分为可移动性不同的区. 这些是在运行时形成的. 内核的另一种方法确实将内存分区, 分别用于可移动页和不可移动页的分配, 我会下文讨论其工作原理. 但这种划分对这里描述的方法是不必要的
3.3 避免碎片数据结构
3.3.1 迁移类型
尽管内核使用的反碎片技术卓有成效,它对伙伴分配器的代码和数据结构几乎没有影响。内核定义了一些枚举常量(早期用宏来实现)来表示不同的迁移类型, 参见include/linux/mmzone.h?v=4.7, line 38
enum {MIGRATE_UNMOVABLE,MIGRATE_MOVABLE,MIGRATE_RECLAIMABLE,MIGRATE_PCPTYPES, /* the number of types on the pcp lists */MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA/** MIGRATE_CMA migration type is designed to mimic the way* ZONE_MOVABLE works. Only movable pages can be allocated* from MIGRATE_CMA pageblocks and page allocator never* implicitly change migration type of MIGRATE_CMA pageblock.** The way to use it is to change migratetype of a range of* pageblocks to MIGRATE_CMA which can be done by* __free_pageblock_cma() function. What is important though* is that a range of pageblocks must be aligned to* MAX_ORDER_NR_PAGES should biggest page be bigger then* a single pageblock.*/MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATIONMIGRATE_ISOLATE, /* can't allocate from here */
#endifMIGRATE_TYPES
};
宏 | 类型 |
---|---|
MIGRATE_UNMOVABLE | 不可移动页 |
MIGRATE_MOVABLE | 可移动页 |
MIGRATE_RECLAIMABLE | 可回收页 |
MIGRATE_PCPTYPES | 是per_cpu_pageset, 即用来表示每CPU页框高速缓存的数据结构中的链表的迁移类型数目 |
MIGRATE_HIGHATOMIC | = MIGRATE_PCPTYPES, 在罕见的情况下,内核需要分配一个高阶的页面块而不能休眠.如果向具有特定可移动性的列表请求分配内存失败,这种紧急情况下可从MIGRATE_HIGHATOMIC中分配内存 |
MIGRATE_CMA | Linux内核最新的连续内存分配器(CMA), 用于避免预留大块内存 |
MIGRATE_ISOLATE | 是一个特殊的虚拟区域, 用于跨越NUMA结点移动物理内存页. 在大型系统上, 它有益于将物理内存页移动到接近于使用该页最频繁的CPU. |
MIGRATE_TYPES | 只是表示迁移类型的数目, 也不代表具体的区域 |
对于MIGRATE_CMA类型, 其中在我们使用ARM等嵌入式Linux系统的时候, 一个头疼的问题是GPU, Camera, HDMI等都需要预留大量连续内存,这部分内存平时不用,但是一般的做法又必须先预留着. 目前, Marek Szyprowski和Michal Nazarewicz实现了一套全新的Contiguous Memory Allocator. 通过这套机制, 我们可以做到不预留内存,这些内存平时是可用的,只有当需要的时候才被分配给Camera,HDMI等设备. 参照宋宝华–Linux内核最新的连续内存分配器(CMA)——避免预留大块内存, 内核为此提供了函数is_migrate_cma来检测当前类型是否为MIGRATE_CMA, 该函数定义在include/linux/mmzone.h?v=4.7, line 69
/* In mm/page_alloc.c; keep in sync also with show_migration_types() there */
extern char * const migratetype_names[MIGRATE_TYPES];#ifdef CONFIG_CMA
# define is_migrate_cma(migratetype) unlikely((migratetype) == MIGRATE_CMA)
#else
# define is_migrate_cma(migratetype) false
#endif
对伙伴系统数据结构的主要调整, 是将空闲列表分解为MIGRATE_TYPE个列表, 可以参见free_area的定义include/linux/mmzone.h?v=4.7, line 88
struct free_area
{struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
- nr_free统计了所有列表上空闲页的数目,而每种迁移类型都对应于一个空闲列表
宏for_each_migratetype_order(order, type)可用于迭代指定迁移类型的所有分配阶
#define for_each_migratetype_order(order, type) \for (order = 0; order < MAX_ORDER; order++) \for (type = 0; type < MIGRATE_TYPES; type++)
3.3.2 迁移备用列表fallbacks
如果内核无法满足针对某一给定迁移类型的分配请求, 会怎么样?
此前已经出现过一个类似的问题, 即特定的NUMA内存域无法满足分配请求时. 我们需要从其他内存域中选择一个代价最低的内存域完成内存的分配, 因此内核在内存的结点pg_data_t中提供了一个备用内存域列表zonelists.
内核在内存迁移的过程中处理这种情况下的做法是类似的. 提供了一个备用列表fallbacks, 规定了在指定列表中无法满足分配请求时. 接下来应使用哪一种迁移类型, 定义在mm/page_alloc.c?v=4.7, line 1799
/** This array describes the order lists are fallen back to when* the free lists for the desirable migrate type are depleted* 该数组描述了指定迁移类型的空闲列表耗尽时* 其他空闲列表在备用列表中的次序*/
static int fallbacks[MIGRATE_TYPES][4] = {// 分配不可移动页失败的备用列表[MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, MIGRATE_TYPES },// 分配可回收页失败时的备用列表[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE, MIGRATE_TYPES },// 分配可移动页失败时的备用列表[MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
#ifdef CONFIG_CMA[MIGRATE_CMA] = { MIGRATE_TYPES }, /* Never used */
#endif
#ifdef CONFIG_MEMORY_ISOLATION[MIGRATE_ISOLATE] = { MIGRATE_TYPES }, /* Never used */
#endif
};
该数据结构大体上是自明的 :
每一行对应一个类型的备用搜索域的顺序, 在内核想要分配不可移动页
MIGRATE_UNMOVABLE
时, 如果对应链表为空, 则遍历fallbacks[MIGRATE_UNMOVABLE], 首先后退到可回收页链表MIGRATE_RECLAIMABLE
, 接下来到可移动页链表MIGRATE_MOVABLE
, 最后到紧急分配链表MIGRATE_TYPES
.
3.3.3 pageblock_order变量
全局变量和辅助函数尽管页可移动性分组特性总是编译到内核中,但只有在系统中有足够内存可以分配到多个迁移类型对应的链表时,才是有意义的。由于每个迁移链表都应该有适当数量的内存,内核需要定义”适当”的概念. 这是通过两个全局变量pageblock_order和pageblock_nr_pages提供的. 第一个表示内核认为是”大”的一个分配阶, pageblock_nr_pages则表示该分配阶对应的页数。如果体系结构提供了巨型页机制, 则pageblock_order通常定义为巨型页对应的分配阶. 定义在include/linux/pageblock-flags.h?v=4.7, line 44
#ifdef CONFIG_HUGETLB_PAGE#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE/* Huge page sizes are variable */extern unsigned int pageblock_order;#else /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE *//* Huge pages are a constant size */#define pageblock_order HUGETLB_PAGE_ORDER#endif /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */#else /* CONFIG_HUGETLB_PAGE *//* If huge pages are not used, group by MAX_ORDER_NR_PAGES */#define pageblock_order (MAX_ORDER-1)#endif /* CONFIG_HUGETLB_PAGE */#define pageblock_nr_pages (1UL << pageblock_order)
在IA-32体系结构上, 巨型页长度是4MB, 因此每个巨型页由1024个普通页组成, 而HUGETLB_PAGE_ORDER则定义为10. 相比之下, IA-64体系结构允许设置可变的普通和巨型页长度, 因此HUGETLB_PAGE_ORDER的值取决于内核配置.
如果体系结构不支持巨型页, 则将其定义为第二高的分配阶, 即MAX_ORDER - 1
/* If huge pages are not used, group by MAX_ORDER_NR_PAGES */
#define pageblock_order (MAX_ORDER-1)
如果各迁移类型的链表中没有一块较大的连续内存, 那么页面迁移不会提供任何好处, 因此在可用内存太少时内核会关闭该特性. 这是在build_all_zonelists函数中检查的, 该函数用于初始化内存域列表. 如果没有足够的内存可用, 则全局变量page_group_by_mobility_disabled
设置为0, 否则设置为1.
内核如何知道给定的分配内存属于何种迁移类型?
我们将在以后讲解, 有关各个内存分配的细节都通过分配掩码指定.
内核提供了两个标志,分别用于表示分配的内存是可移动的(__GFP_MOVABLE)或可回收的(__GFP_RECLAIMABLE).
3.3.4 gfpflags_to_migratetype函数
如果这些标志都没有设置, 则分配的内存假定为不可移动的. 辅助函数gfpflags_to_migratetype可用于转换分配标志及对应的迁移类型, 该函数定义在include/linux/gfp.h?v=4.7, line 266
static inline int gfpflags_to_migratetype(const gfp_t gfp_flags)
{VM_WARN_ON((gfp_flags & GFP_MOVABLE_MASK) == GFP_MOVABLE_MASK);BUILD_BUG_ON((1UL << GFP_MOVABLE_SHIFT) != ___GFP_MOVABLE);BUILD_BUG_ON((___GFP_MOVABLE >> GFP_MOVABLE_SHIFT) != MIGRATE_MOVABLE);if (unlikely(page_group_by_mobility_disabled))return MIGRATE_UNMOVABLE;/* Group based on mobility */return (gfp_flags & GFP_MOVABLE_MASK) >> GFP_MOVABLE_SHIFT;
}
linux-2.6.x的内核中转换分配标志及对应的迁移类型的辅助函数为allocflags_to_migratetype, 这个名字会有歧义的, 让我们误以为参数的标识中有alloc flags, 但是其实并不然, 因此后来的内核中将该函数更名为gfpflags_to_migratetype, 参见Rename it to gfpflags_to_migratetype()
在2.6.25中为如下接口
/* Convert GFP flags to their corresponding migrate type */
static inline int allocflags_to_migratetype(gfp_t gfp_flags)
{WARN_ON((gfp_flags & GFP_MOVABLE_MASK) == GFP_MOVABLE_MASK);if (unlikely(page_group_by_mobility_disabled))return MIGRATE_UNMOVABLE;/* Group based on mobility */return (((gfp_flags & __GFP_MOVABLE) != 0) << 1) |((gfp_flags & __GFP_RECLAIMABLE) != 0);
}
如果停用了页面迁移特性, 则所有的页都是不可移动的. 否则. 该函数的返回值可以直接用作free_area.free_list的数组索引.
3.3.5 pageblock_flags变量与其函数接口
最后要注意, 每个内存域都提供了一个特殊的字段, 可以跟踪包含pageblock_nr_pages个页的内存区的属性. 即zone->pageblock_flags字段, 当前只有与页可移动性相关的代码使用, 参见include/linux/mmzone.h?v=4.7, line 367
struct zone
{
#ifndef CONFIG_SPARSEMEM/** Flags for a pageblock_nr_pages block. See pageblock-flags.h.* In SPARSEMEM, this map is stored in struct mem_section*/unsigned long *pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
};
在初始化期间, 内核自动确保对内存域中的每个不同的迁移类型分组, 在pageblock_flags中都分配了足够存储NR_PAGEBLOCK_BITS个比特位的空间。当前,表示一个连续内存区的迁移类型需要3个比特位, 参见include/linux/pageblock-flags.h?v=4.7, line 28
/* Bit indices that affect a whole block of pages */
enum pageblock_bits {PB_migrate,PB_migrate_end = PB_migrate + 3 - 1,/* 3 bits required for migrate types */PB_migrate_skip,/* If set the block is skipped by compaction *//** Assume the bits will always align on a word. If this assumption* changes then get/set pageblock needs updating.*/NR_PAGEBLOCK_BITS
};
内核提供set_pageblock_migratetype
负责设置以page为首的一个内存区的迁移类型, 该函数定义在mm/page_alloc.c?v=4.7, line 458, 如下所示
void set_pageblock_migratetype(struct page *page, int migratetype)
{if (unlikely(page_group_by_mobility_disabled &&migratetype < MIGRATE_PCPTYPES))migratetype = MIGRATE_UNMOVABLE;set_pageblock_flags_group(page, (unsigned long)migratetype,PB_migrate, PB_migrate_end);
}
migratetype
参数可以通过上文介绍的gfpflags_to_migratetype
辅助函数构建. 请注意很重要的一点, 页的迁移类型是预先分配好的, 对应的比特位总是可用, 与页是否由伙伴系统管理无关. 在释放内存时,页必须返回到正确的迁移链表。这之所以可行,是因为能够从get_pageblock_migratetype
获得所需的信息. 参见include/linux/mmzone.h?v=4.7, line 84
#define get_pageblock_migratetype(page) \get_pfnblock_flags_mask(page, page_to_pfn(page), \PB_migrate_end, MIGRATETYPE_MASK)
最后请注意, 在各个迁移链表之间, 当前的页面分配状态可以从/proc/pagetypeinfo
获得.
初始化基于可移动性的分组
在内存子系统初始化期间, memmap_init_zone负责处理内存域的page实例. 该函数定义在mm/page_alloc.c?v=4.7, line 5139, 该函数完成了一些不怎么有趣的标准初始化工作,但其中有一件是实质性的,即所有的页最初都标记为可移动的. 参见mm/page_alloc.c?v=4.7, line 5224
/** Initially all pages are reserved - free ones are freed* up by free_all_bootmem() once the early boot process is* done. Non-atomic initialization, single-pass.*/
void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,unsigned long start_pfn, enum memmap_context context)
{/* ...... */for (pfn = start_pfn; pfn < end_pfn; pfn++) {/* ...... */
not_early:if (!(pfn & (pageblock_nr_pages - 1))) {struct page *page = pfn_to_page(pfn);__init_single_page(page, pfn, zone, nid);set_pageblock_migratetype(page, MIGRATE_MOVABLE);} else {__init_single_pfn(pfn, zone, nid);}}
}
在分配内存时, 如果必须”盗取”不同于预定迁移类型的内存区, 内核在策略上倾向于”盗取”更大的内存区. 由于所有页最初都是可移动的, 那么在内核分配不可移动的内存区时, 则必须”盗取”.
实际上, 在启动期间分配可移动内存区的情况较少, 那么分配器有很高的几率分配长度最大的内存区, 并将其从可移动列表转换到不可移动列表. 由于分配的内存区长度是最大的, 因此不会向可移动内存中引入碎片.
总而言之, 这种做法避免了启动期间内核分配的内存(经常在系统的整个运行时间都不释放)散布到物理内存各处, 从而使其他类型的内存分配免受碎片的干扰,这也是页可移动性分组框架的最重要的目标之一.
4 分配器API
4.1 分配内存的接口
就伙伴系统的接口而言, NUMA或UMA体系结构是没有差别的, 二者的调用语法都是相同的.
所有函数的一个共同点是 : 只能分配2的整数幂个页.
因此,接口中不像C标准库的malloc函数或bootmem和memblock分配器那样指定了所需内存大小作为参数. 相反, 必须指定的是分配阶, 伙伴系统将在内存中分配
内存分配函数 | 功能 | 定义 |
---|---|---|
alloc_pages(mask, order) | 分配 | NUMA-include/linux/gfp.h, line 466 UMA-include/linux/gfp.h?v=4.7, line 476 |
alloc_page(mask) | 是前者在order = 0情况下的简化形式,只分配一页 | include/linux/gfp.h?v=4.7, line 483 |
get_zeroed_page(mask) | 分配一页并返回一个page实例,页对应的内存填充0(所有其他函数,分配之后页的内容是未定义的) | mm/page_alloc.c?v=4.7, line 3900 |
__get_free_pages(mask, order) __get_free_page(mask) | 工作方式与上述函数相同,但返回分配内存块的虚拟地址,而不是page实例 | |
get_dma_pages(gfp_mask, order) | 用来获得适用于DMA的页. | include/linux/gfp.h?v=4.7, line 503 |
在空闲内存无法满足请求以至于分配失败的情况下,所有上述函数都返回空指针(比如alloc_pages和alloc_page)或者0(比如get_zeroed_page、__get_free_pages和__get_free_page).
因此内核在各次分配之后都必须检查返回的结果. 这种惯例与设计得很好的用户层应用程序没什么不同, 但在内核中忽略检查会导致严重得多的故障
内核除了伙伴系统函数之外, 还提供了其他内存管理函数. 它们以伙伴系统为基础, 但并不属于伙伴分配器自身. 这些函数包括vmalloc和vmalloc_32, 使用页表将不连续的内存映射到内核地址空间中, 使之看上去是连续的.
还有一组kmalloc类型的函数, 用于分配小于一整页的内存区. 其实现.
释放函数
有4个函数用于释放不再使用的页,与所述函数稍有不同
内存释放函数 | 描述 |
---|---|
free_page(struct page ) free_pages(struct page , order) | 用于将一个或2order页返回给内存管理子系统。内存区的起始地址由指向该内存区的第一个page实例的指针表示 |
__free_page(addr) __free_pages(addr, order) | 类似于前两个函数,但在表示需要释放的内存区时,使用了虚拟内存地址而不是page实例 |
4.2 分配掩码(gfp_mask标志)
4.2.1 分配掩码
前述所有函数中强制使用的mask参数,到底是什么语义?
我们知道Linux将内存划分为内存域. 内核提供了所谓的内存域修饰符(zone modifier)(在掩码的最低4个比特位定义), 来指定从哪个内存域分配所需的页.
内核使用宏的方式定义了这些掩码, 一个掩码的定义被划分为3个部分进行定义, 我们会逐步展开来讲解, 参见include/linux/gfp.h?v=4.7, line 12~374, 共计26个掩码信息, 因此后面__GFP_BITS_SHIFT = 26.
4.2.2 掩码分类
Linux中这些掩码标志gfp_mask
分为3种类型 :
类型 | 描述 |
---|---|
区描述都符 | 内核把物理内存分为多个区, 每个区用于不同的目的, 区描述符指明到底从这些区中的哪一区进行分配 |
行为修饰符 | 表示内核应该如何分配所需的内存. 在某些特定情况下, 只能使用某些特定的方法分配内存 |
类型标志 | 组合了行为修饰符和区描述符, 将这些可能用到的组合归纳为不同类型 |
4.2.3 内核中掩码的定义
内核中的定义方式
// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7/* line 12 ~ line 44 第一部分* 定义可掩码所在位的信息, 每个掩码对应一位为1* 定义形式为 #define ___GFP_XXX 0x01u*/
/* Plain integer GFP bitmasks. Do not use this directly. */
#define ___GFP_DMA 0x01u
#define ___GFP_HIGHMEM 0x02u
#define ___GFP_DMA32 0x04u
#define ___GFP_MOVABLE 0x08u
/* ...... *//* line 46 ~ line 192 第二部分* 定义掩码和MASK信息, 第二部分的某些宏可能是第一部分一个或者几个的组合* 定义形式为 #define __GFP_XXX ((__force gfp_t)___GFP_XXX)*/
#define __GFP_DMA ((__force gfp_t)___GFP_DMA)
#define __GFP_HIGHMEM ((__force gfp_t)___GFP_HIGHMEM)
#define __GFP_DMA32 ((__force gfp_t)___GFP_DMA32)
#define __GFP_MOVABLE ((__force gfp_t)___GFP_MOVABLE) /* ZONE_MOVABLE allowed */
#define GFP_ZONEMASK (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)/* line 194 ~ line 260 第三部分* 定义掩码* 定义形式为 #define GFP_XXX __GFP_XXX*/
#define GFP_DMA __GFP_DMA
#define GFP_DMA32 __GFP_DMA32
其中GFP缩写的意思为获取空闲页(get free page), __GFP_MOVABLE不表示物理内存域, 但通知内核应在特殊的虚拟内存域ZONE_MOVABLE进行相应的分配.
定义掩码位
我们首先来看第一部分, 内核源代码中定义在include/linux/gfp.h?v=4.7, line 18 ~ line 44, 共计26个掩码信息.
/* Plain integer GFP bitmasks. Do not use this directly. */
// 区域修饰符
#define ___GFP_DMA 0x01u
#define ___GFP_HIGHMEM 0x02u
#define ___GFP_DMA32 0x04u// 行为修饰符
#define ___GFP_MOVABLE 0x08u /* 页是可移动的 */
#define ___GFP_RECLAIMABLE 0x10u /* 页是可回收的 */
#define ___GFP_HIGH 0x20u /* 应该访问紧急分配池? */
#define ___GFP_IO 0x40u /* 可以启动物理IO? */
#define ___GFP_FS 0x80u /* 可以调用底层文件系统? */
#define ___GFP_COLD 0x100u /* 需要非缓存的冷页 */
#define ___GFP_NOWARN 0x200u /* 禁止分配失败警告 */
#define ___GFP_REPEAT 0x400u /* 重试分配,可能失败 */
#define ___GFP_NOFAIL 0x800u /* 一直重试,不会失败 */
#define ___GFP_NORETRY 0x1000u /* 不重试,可能失败 */
#define ___GFP_MEMALLOC 0x2000u /* 使用紧急分配链表 */
#define ___GFP_COMP 0x4000u /* 增加复合页元数据 */
#define ___GFP_ZERO 0x8000u /* 成功则返回填充字节0的页 */
// 类型修饰符
#define ___GFP_NOMEMALLOC 0x10000u /* 不使用紧急分配链表 */
#define ___GFP_HARDWALL 0x20000u /* 只允许在进程允许运行的CPU所关联的结点分配内存 */
#define ___GFP_THISNODE 0x40000u /* 没有备用结点,没有策略 */
#define ___GFP_ATOMIC 0x80000u /* 用于原子分配,在任何情况下都不能中断 */
#define ___GFP_ACCOUNT 0x100000u
#define ___GFP_NOTRACK 0x200000u
#define ___GFP_DIRECT_RECLAIM 0x400000u
#define ___GFP_OTHER_NODE 0x800000u
#define ___GFP_WRITE 0x1000000u
#define ___GFP_KSWAPD_RECLAIM 0x2000000u
定义掩码
然后第二部分, 相对而言每一个宏又被重新定义如下, 参见include/linux/gfp.h?v=4.7, line 46 ~ line 192
/*
* Physical address zone modifiers (see linux/mmzone.h - low four bits)
*
* Do not put any conditional on these. If necessary modify the definitions
* without the underscores and use them consistently. The definitions here may
* be used in bit comparisons.
* 定义区描述符
*/
#define __GFP_DMA ((__force gfp_t)___GFP_DMA)
#define __GFP_HIGHMEM ((__force gfp_t)___GFP_HIGHMEM)
#define __GFP_DMA32 ((__force gfp_t)___GFP_DMA32)
#define __GFP_MOVABLE ((__force gfp_t)___GFP_MOVABLE) /* ZONE_MOVABLE allowed */
#define GFP_ZONEMASK (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)/*
* Page mobility and placement hints
*
* These flags provide hints about how mobile the page is. Pages with similar
* mobility are placed within the same pageblocks to minimise problems due
* to external fragmentation.
*
* __GFP_MOVABLE (also a zone modifier) indicates that the page can be
* moved by page migration during memory compaction or can be reclaimed.
*
* __GFP_RECLAIMABLE is used for slab allocations that specify
* SLAB_RECLAIM_ACCOUNT and whose pages can be freed via shrinkers.
*
* __GFP_WRITE indicates the caller intends to dirty the page. Where possible,
* these pages will be spread between local zones to avoid all the dirty
* pages being in one zone (fair zone allocation policy).
*
* __GFP_HARDWALL enforces the cpuset memory allocation policy.
*
* __GFP_THISNODE forces the allocation to be satisified from the requested
* node with no fallbacks or placement policy enforcements.
*
* __GFP_ACCOUNT causes the allocation to be accounted to kmemcg (only relevant
* to kmem allocations).
*/
#define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE)
#define __GFP_WRITE ((__force gfp_t)___GFP_WRITE)
#define __GFP_HARDWALL ((__force gfp_t)___GFP_HARDWALL)
#define __GFP_THISNODE ((__force gfp_t)___GFP_THISNODE)
#define __GFP_ACCOUNT ((__force gfp_t)___GFP_ACCOUNT)/*
* Watermark modifiers -- controls access to emergency reserves
*
* __GFP_HIGH indicates that the caller is high-priority and that granting
* the request is necessary before the system can make forward progress.
* For example, creating an IO context to clean pages.
*
* __GFP_ATOMIC indicates that the caller cannot reclaim or sleep and is
* high priority. Users are typically interrupt handlers. This may be
* used in conjunction with __GFP_HIGH** __GFP_MEMALLOC allows access to all memory. This should only be used when* the caller guarantees the allocation will allow more memory to be freed* very shortly e.g. process exiting or swapping. Users either should* be the MM or co-ordinating closely with the VM (e.g. swap over NFS).** __GFP_NOMEMALLOC is used to explicitly forbid access to emergency reserves.* This takes precedence over the __GFP_MEMALLOC flag if both are set.*/
#define __GFP_ATOMIC ((__force gfp_t)___GFP_ATOMIC)
#define __GFP_HIGH ((__force gfp_t)___GFP_HIGH)
#define __GFP_MEMALLOC ((__force gfp_t)___GFP_MEMALLOC)
#define __GFP_NOMEMALLOC ((__force gfp_t)___GFP_NOMEMALLOC)/** Reclaim modifiers** __GFP_IO can start physical IO.** __GFP_FS can call down to the low-level FS. Clearing the flag avoids the* allocator recursing into the filesystem which might already be holding* locks.** __GFP_DIRECT_RECLAIM indicates that the caller may enter direct reclaim.* This flag can be cleared to avoid unnecessary delays when a fallback* option is available.** __GFP_KSWAPD_RECLAIM indicates that the caller wants to wake kswapd when* the low watermark is reached and have it reclaim pages until the high* watermark is reached. A caller may wish to clear this flag when fallback* options are available and the reclaim is likely to disrupt the system. The* canonical example is THP allocation where a fallback is cheap but* reclaim/compaction may cause indirect stalls.** __GFP_RECLAIM is shorthand to allow/forbid both direct and kswapd reclaim.** __GFP_REPEAT: Try hard to allocate the memory, but the allocation attempt* _might_ fail. This depends upon the particular VM implementation.** __GFP_NOFAIL: The VM implementation _must_ retry infinitely: the caller* cannot handle allocation failures. New users should be evaluated carefully* (and the flag should be used only when there is no reasonable failure* policy) but it is definitely preferable to use the flag rather than* opencode endless loop around allocator.** __GFP_NORETRY: The VM implementation must not retry indefinitely and will* return NULL when direct reclaim and memory compaction have failed to allow* the allocation to succeed. The OOM killer is not called with the current* implementation.*/
#define __GFP_IO ((__force gfp_t)___GFP_IO)
#define __GFP_FS ((__force gfp_t)___GFP_FS)
#define __GFP_DIRECT_RECLAIM ((__force gfp_t)___GFP_DIRECT_RECLAIM) /* Caller can reclaim */
#define __GFP_KSWAPD_RECLAIM ((__force gfp_t)___GFP_KSWAPD_RECLAIM) /* kswapd can wake */
#define __GFP_RECLAIM ((__force gfp_t)(___GFP_DIRECT_RECLAIM|___GFP_KSWAPD_RECLAIM))
#define __GFP_REPEAT ((__force gfp_t)___GFP_REPEAT)
#define __GFP_NOFAIL ((__force gfp_t)___GFP_NOFAIL)
#define __GFP_NORETRY ((__force gfp_t)___GFP_NORETRY)/** Action modifiers** __GFP_COLD indicates that the caller does not expect to be used in the near* future. Where possible, a cache-cold page will be returned.** __GFP_NOWARN suppresses allocation failure reports.** __GFP_COMP address compound page metadata.** __GFP_ZERO returns a zeroed page on success.** __GFP_NOTRACK avoids tracking with kmemcheck.** __GFP_NOTRACK_FALSE_POSITIVE is an alias of __GFP_NOTRACK. It's a means of* distinguishing in the source between false positives and allocations that* cannot be supported (e.g. page tables).** __GFP_OTHER_NODE is for allocations that are on a remote node but that* should not be accounted for as a remote allocation in vmstat. A* typical user would be khugepaged collapsing a huge page on a remote* node.*/
#define __GFP_COLD ((__force gfp_t)___GFP_COLD)
#define __GFP_NOWARN ((__force gfp_t)___GFP_NOWARN)
#define __GFP_COMP ((__force gfp_t)___GFP_COMP)
#define __GFP_ZERO ((__force gfp_t)___GFP_ZERO)
#define __GFP_NOTRACK ((__force gfp_t)___GFP_NOTRACK)
#define __GFP_NOTRACK_FALSE_POSITIVE (__GFP_NOTRACK)
#define __GFP_OTHER_NODE ((__force gfp_t)___GFP_OTHER_NODE)/* Room for N __GFP_FOO bits */
#define __GFP_BITS_SHIFT 26
#define __GFP_BITS_MASK ((__force gfp_t)((1 << __GFP_BITS_SHIFT) - 1))
给出的常数,其中一些很少使用,因此我不会讨论。其中最重要的一些常数语义如下所示
其中在开始的位置定义了对应的区修饰符, 定义在include/linux/gfp.h?v=4.7, line 46 ~ line 57
区修饰符标志 | 描述 |
---|---|
__GFP_DMA | 从ZONE_DMA中分配内存 |
__GFP_HIGHMEM | 从ZONE_HIGHMEM活ZONE_NORMAL中分配内存 |
__GFP_DMA32 | 从ZONE_DMA32中分配内存 |
__GFP_MOVABLE | 从__GFP_MOVABLE中分配内存 |
其次还定义了我们程序和函数中所需要的掩码MASK的信息, 由于其中__GFP_DMA, __GFP_DMA32, __GFP_HIGHMEM, __GFP_MOVABLE是在内存中分别有对应的内存域信息, 因此我们定义了内存域的掩码GFP_ZONEMASK, 参见include/linux/gfp.h?v=4.7, line 57
#define GFP_ZONEMASK (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)
接着内核定义了行为修饰符
/* __GFP_WAIT表示分配内存的请求可以中断。也就是说,调度器在该请求期间可随意选择另一个过程执行,或者该请求可以被另一个更重要的事件中断. 分配器还可以在返回内存之前, 在队列上等待一个事件(相关进程会进入睡眠状态).
虽然名字相似,但__GFP_HIGH与__GFP_HIGHMEM毫无关系,请不要弄混这两者\
行为修饰符 | 描述 |
---|---|
__GFP_RECLAIMABLE __GFP_MOVABLE | 是页迁移机制所需的标志. 顾名思义,它们分别将分配的内存标记为可回收的或可移动的。这影响从空闲列表的哪个子表获取内存 |
__GFP_WRITE | |
__GFP_HARDWALL | 只在NUMA系统上有意义. 它限制只在分配到当前进程的各个CPU所关联的结点分配内存。如果进程允许在所有CPU上运行(默认情况),该标志是无意义的。只有进程可以运行的CPU受限时,该标志才有效果 |
__GFP_THISNODE | 也只在NUMA系统上有意义。如果设置该比特位,则内存分配失败的情况下不允许使用其他结点作为备用,需要保证在当前结点或者明确指定的结点上成功分配内存 |
__GFP_ACCOUNT | |
__GFP_ATOMIC | |
__GFP_HIGH | 如果请求非常重要, 则设置__GFP_HIGH,即内核急切地需要内存时。在分配内存失败可能给内核带来严重后果时(比如威胁到系统稳定性或系统崩溃), 总是会使用该标志 |
__GFP_MEMALLOC | |
__GFP_NOMEMALLOC | |
__GFP_IO | 说明在查找空闲内存期间内核可以进行I/O操作. 实际上, 这意味着如果内核在内存分配期间换出页, 那么仅当设置该标志时, 才能将选择的页写入硬盘 |
__GFP_FS | 允许内核执行VFS操作. 在与VFS层有联系的内核子系统中必须禁用, 因为这可能引起循环递归调用. |
__GFP_DIRECT_RECLAIM | |
__GFP_KSWAPD_RECLAIM | |
__GFP_RECLAIM | |
__GFP_REPEAT | 在分配失败后自动重试,但在尝试若干次之后会停止 |
__GFP_NOFAIL | 在分配失败后一直重试,直至成功 |
__GFP_NORETRY | 在分配失败后不重试,因此可能分配失败 |
__GFP_COLD | 如果需要分配不在CPU高速缓存中的“冷”页时,则设置__GFP_COLD |
__GFP_NOWARN | 在分配失败时禁止内核故障警告。在极少数场合该标志有用 |
__GFP_COMP | 添加混合页元素, 在hugetlb的代码内部使用 |
__GFP_ZERO | 在分配成功时,将返回填充字节0的页 |
__GFP_NOTRACK | |
__GFP_NOTRACK_FALSE_POSITIVE __GFP_NOTRACK | |
__GFP_OTHER_NODE |
那自然还有__GFP_BITS_SHIFT来表示我们所有的掩码位, 由于我们共计26个掩码位
/* Room for N __GFP_FOO bits */
#define __GFP_BITS_SHIFT 26
#define __GFP_BITS_MASK ((__force gfp_t)((1 << __GFP_BITS_SHIFT) - 1))
可以同时指定这些分配标志, 例如
ptr = kmalloc(size, __GFP_IO | __GFP_FS);
说明页分配器(最终会调用alloc_page)在分配时可以执行I/O, 在必要时还可以执行文件系统操作. 这就让内核有很大的自由度, 以便它尽可能找到空闲的内存来满足分配请求. 大多数分配器都会执行这些修饰符, 但一般不是这样直接指定, 而是将这些行为描述符标志进行分组, 即类型标志
掩码分组
最后来看第三部分, 由于这些标志几乎总是组合使用,内核作了一些分组,包含了用于各种标准情形的适当的标志. 称之为类型标志, 定义在include/linux/gfp.h?v=4.7, lien 194 ~ line 258
类型标志指定所需的行为和区描述符以安城特殊类型的处理, 正因为这一点, 内核总是趋于使用正确的类型标志, 而不是一味地指定它可能用到的多种描述符. 这么做既简单又不容易出错误.
如果有可能的话, 在内存管理子系统之外, 总是把下列分组之一用于内存分配. 在内核源代码中, 双下划线通常用于内部数据和定义. 而这些预定义的分组名没有双下划线前缀, 点从侧面验证了上述说法.
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO (__GFP_RECLAIM)
#define GFP_NOFS (__GFP_RECLAIM | __GFP_IO)
#define GFP_TEMPORARY (__GFP_RECLAIM | __GFP_IO | __GFP_FS | \__GFP_RECLAIMABLE)
#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA __GFP_DMA
#define GFP_DMA32 __GFP_DMA32
#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE (GFP_HIGHUSER | __GFP_MOVABLE)
#define GFP_TRANSHUGE ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \__GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN) & \~__GFP_RECLAIM)/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
#define GFP_MOVABLE_SHIFT 3
掩码组 | 描述 |
---|---|
GFP_ATOMIC | 用于原子分配,在任何情况下都不能中断, 可能使用紧急分配链表中的内存, 这个标志用在中断处理程序, 下半部, 持有自旋锁以及其他不能睡眠的地方 |
GFP_KERNEL | 这是一种常规的分配方式, 可能会阻塞. 这个标志在睡眠安全时用在进程的长下文代码中. 为了获取调用者所需的内存, 内核会尽力而为. 这个标志应该是首选标志 |
GFP_KERNEL_ACCOUNT | |
GFP_NOWAIT | 与GFP_ATOMIC类似, 不同之处在于, 调用不会退给紧急内存池, 这就增加了内存分配失败的可能性 |
GFP_NOIO | 这种分配可以阻塞, 但不会启动磁盘I/O, 这个标志在不能引发更多的磁盘I/O时阻塞I/O代码, 这可能导致令人不愉快的递归 |
GFP_NOFS | 这种分配在必要时可以阻塞, 但是也可能启动磁盘, 但是不会启动文件系统操作, 这个标志在你不鞥在启动另一个文件系统操作时, 用在文件系统部分的代码中 |
GFP_TEMPORARY | |
GFP_USER | 这是一种常规的分配方式, 可能会阻塞. 这个标志用于为用户空间进程分配内存时使用 |
GFP_DMA GFP_DMA32 | 用于分配适用于DMA的内存, 当前是__GFP_DMA的同义词, GFP_DMA32也是__GFP_GMA32的同义词 |
GFP_HIGHUSER | 是GFP_USER的一个扩展, 也用于用户空间. 它允许分配无法直接映射的高端内存. 使用高端内存页是没有坏处的,因为用户过程的地址空间总是通过非线性页表组织的 |
GFP_HIGHUSER_MOVABLE | 用途类似于GFP_HIGHUSER,但分配将从虚拟内存域ZONE_MOVABLE进行 |
GFP_TRANSHUGE |
* 其中GFP_NOIO和GFP_NOFS, 分别明确禁止I/O操作和访问VFS层, 但同时设置了__GFP_RECLAIM,因此可以被回收
- 而GFP_KERNEL和GFP_USER. 分别是内核和用户分配的默认设置。二者的失败不会立即威胁系统稳定性, GFP_KERNEL绝对是内核源代码中最常使用的标志 |
最后内核设置了碎片管理的可移动依据组织页的MASK信息GFP_MOVABLE_MASK, 参见include/linux/gfp.h?v=4.7, line 262
/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
#define GFP_MOVABLE_SHIFT 3
在你编写的绝大多数代码中, 用么用到的是GFP_KERNEL, 要么是GFP_ATOMIC, 当然各个类型标志也均有其应用场景
情形 | 相应标志 |
---|---|
进程上下文, 可以睡眠 | 使用GFP_KERNEL |
进程上下文, 不可以睡眠 | 使用GFP_KERNEL, 在你睡眠之前或之后以GFP_KERNEL执行内存分配 |
中断处理程序 | 使用GFP_ATMOIC |
软中断 | 使用GFP_ATMOIC |
tasklet | 使用GFP_ATMOIC |
需要用于DMA的内存, 可以睡眠 | 使用(GFP_DMA GFP_KERNEL) |
需要用于DMA的内存, 不可以睡眠 | 使用(GFP_DMA GFP_ATOMIC), 或在你睡眠之前执行内存分配 |
4.2.5 总结
bit result
=================
0x0 => NORMAL
0x1 => DMA or NORMAL
0x2 => HIGHMEM or NORMAL
0x3 => BAD (DMA+HIGHMEM)
0x4 => DMA32 or DMA or NORMAL
0x5 => BAD (DMA+DMA32)
0x6 => BAD (HIGHMEM+DMA32)
0x7 => BAD (HIGHMEM+DMA32+DMA)
0x8 => NORMAL (MOVABLE+0)
0x9 => DMA or NORMAL (MOVABLE+DMA)
0xa => MOVABLE (Movable is valid only if HIGHMEM is set too)
0xb => BAD (MOVABLE+HIGHMEM+DMA)
0xc => DMA32 (MOVABLE+DMA32)
0xd => BAD (MOVABLE+DMA32+DMA)
0xe => BAD (MOVABLE+DMA32+HIGHMEM)
0xf => BAD (MOVABLE+DMA32+HIGHMEM+DMA)GFP_ZONES_SHIFT must be <= 2 on 32 bit platforms.
很有趣的一点是,没有__GFP_NORMAL常数,而内存分配的主要负担却落到ZONE_NORMAL内存域
内核考虑到这一点, 提供了一个函数gfp_zone来计算与给定分配标志兼容的最高内存域. 那么内存分配可以从该内存域或更低的内存域进行, 该函数定义在include/linux/gfp.h?v=4.7, line 394
static inline enum zone_type gfp_zone(gfp_t flags)
{enum zone_type z;int bit = (__force int) (flags & GFP_ZONEMASK);z = (GFP_ZONE_TABLE >> (bit * GFP_ZONES_SHIFT)) &((1 << GFP_ZONES_SHIFT) - 1);VM_BUG_ON((GFP_ZONE_BAD >> bit) & 1);return z;
}
其中GFP_ZONES_SHIFT的定义如下, 在include/linux/gfp.h?v=4.7, line 337
#if defined(CONFIG_ZONE_DEVICE) && (MAX_NR_ZONES-1) <= 4
/* ZONE_DEVICE is not a valid GFP zone specifier */
#define GFP_ZONES_SHIFT 2
#else
#define GFP_ZONES_SHIFT ZONES_SHIFT
#endif#if 16 * GFP_ZONES_SHIFT > BITS_PER_LONG
#error GFP_ZONES_SHIFT too large to create GFP_ZONE_TABLE integer
#endif
由于内存域修饰符的解释方式不是那么直观, 表3-7给出了该函数结果的一个例子, 其中DMA和DMA32内存域相同. 假定在下文中没有设置__GFP_MOVABLE修饰符.
修饰符 | 扫描的内存域 |
---|---|
无 | ZONE_NORMAL、ZONE_DMA |
__GFP_DMA | ZONE_DMA |
__GFP_DMA & __GFP_HIGHMEM | ZONE_DMA |
__GFP_HIGHMEM | ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA |
* 如果__GFP_DMA和__GFP_HIGHMEM都没有设置, 则首先扫描ZONE_NORMAL, 后面是ZONE_DMA
如果设置了__GFP_HIGHMEM没有设置__GFP_DMA,则结果是从ZONE_HIGHMEM开始扫描所有3个内存域。=
如果设置了__GFP_DMA,那么__GFP_HIGHMEM设置与否没有关系. 只有ZONE_DMA用于3种情形. 这是合理的, 因为同时使用__GFP_HIGHMEM和__GFP_DMA没有意义. 高端内存从来都不适用于DMA
设置__GFP_MOVABLE不会影响内核的决策,除非它与__GFP_HIGHMEM同时指定. 在这种情况下, 会使用特殊的虚拟内存域ZONE_MOVABLE满足内存分配请求. 对前文描述的内核的反碎片策略而言, 这种行为是必要的.
除了内存域修饰符之外, 掩码中还可以设置一些标志.
下图中给出了掩码的布局,以及与各个比特位置关联的常数. __GFP_DMA32出现了几次,因为它可能位于不同的地方.
与内存域修饰符相反, 这些额外的标志并不限制从哪个物理内存段分配内存, 但确实可以改变分配器的行为. 例如, 它们可以修改查找空闲内存时的积极程度.
4.3 分配页
4.3.1 内存分配统一到alloc_pages接口
通过使用标志、内存域修饰符和各个分配函数,内核提供了一种非常灵活的内存分配体系.尽管如此, 所有接口函数都可以追溯到一个简单的基本函数(alloc_pages_node)
分配单页的函数alloc_page
和__get_free_page
, 还有__get_dma_pages
是借助于宏定义的.
// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L483
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L500
#define __get_free_page(gfp_mask) \__get_free_pages((gfp_mask), 0)`// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L503
#define __get_dma_pages(gfp_mask, order) \__get_free_pages((gfp_mask) | GFP_DMA, (order))
get_zeroed_page
的实现也没什么困难, 对__get_free_pages
使用__GFP_ZERO
标志,即可分配填充字节0的页. 再返回与页关联的内存区地址即可.
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L3900
unsigned long get_zeroed_page(gfp_t gfp_mask)
{return __get_free_pages(gfp_mask | __GFP_ZERO, 0);
}
EXPORT_SYMBOL(get_zeroed_page);
__get_free_pages
调用alloc_pages
完成内存分配, 而alloc_pages又借助于alloc_pages_node
__get_free_pages
函数的定义在mm/page_alloc.c?v=4.7, line 3883
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L3883
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{struct page *page;/** __get_free_pages() returns a 32-bit address, which cannot represent* a highmem page*/VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0);page = alloc_pages(gfp_mask, order);if (!page)return 0;return (unsigned long) page_address(page);
}
EXPORT_SYMBOL(__get_free_pages);
在这种情况下, 使用了一个普通函数而不是宏, 因为alloc_pages
返回的page
实例需要使用辅助
函数page_address
转换为内存地址. 在这里,只要知道该函数可根据page
实例计算相关页的线性内存地址即可. 对高端内存页这是有问题的
这样, 就完成了所有分配内存的API函数到公共的基础函数alloc_pages
的统一
所有体系结构都必须实现的标准函数clear_page
, 可帮助alloc_pages对页填充字节0, 实现如下表所示
x86 | arm |
---|---|
arch/x86/include/asm/page_32.h?v=4.7, line 24 | arch/arm/include/asm/page.h?v=4.7#L14 arch/arm/include/asm/page-nommu.h |
4.3.2 alloc_pages函数分配页
既然所有的内存分配API函数都可以追溯掉alloc_page
函数, 从某种意义上说,该函数是伙伴系统主要实现的”发射台”.
alloc_pages
函数的定义是依赖于NUMA或者UMA架构的, 定义如下
#ifdef CONFIG_NUMA// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L465
static inline struct page *
alloc_pages(gfp_t gfp_mask, unsigned int order)
{return alloc_pages_current(gfp_mask, order);
}#else// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L476
#define alloc_pages(gfp_mask, order) \alloc_pages_node(numa_node_id(), gfp_mask, order)
#endif
UMA结构下的alloc_pages
是通过alloc_pages_node
函数实现的, 下面我们看看alloc_pages_node
函数的定义, 在include/linux/gfp.h?v=4.7, line 448
// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L448
/** Allocate pages, preferring the node given as nid. When nid == NUMA_NO_NODE,* prefer the current CPU's closest node. Otherwise node must be valid and* online.*/
static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,unsigned int order)
{if (nid == NUMA_NO_NODE)nid = numa_mem_id();return __alloc_pages_node(nid, gfp_mask, order);
}
它只是执行了一个简单的检查, 如果指定负的结点ID(不存在, 即NUMA_NO_NODE = -1), 内核自动地使用当前执行CPU对应的结点nid = numa_mem_id();, 然后调用__alloc_pages_node
函数进行了内存分配
__alloc_pages_node
函数定义在include/linux/gfp.h?v=4.7, line 435), 如下所示
// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L435
/** Allocate pages, preferring the node given as nid. The node must be valid and* online. For more general interface, see alloc_pages_node().*/
static inline struct page *
__alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order)
{VM_BUG_ON(nid < 0 || nid >= MAX_NUMNODES);VM_WARN_ON(!node_online(nid));return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
}
内核假定传递给改alloc_pages_node函数的结点nid是被激活, 即online的.但是为了安全它还是检查并警告内存结点不存在的情况. 接下来的工作委托给__alloc_pages, 只需传递一组适当的参数, 其中包括节点nid的备用内存域列表zonelist.
现在__alloc_pages
函数没什么特别的, 它直接将自己的所有信息传递给__alloc_pages_nodemask
来完成内存的分配
// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L428
static inline struct page *
__alloc_pages(gfp_t gfp_mask, unsigned int order,struct zonelist *zonelist)
{return __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL);
}
4.3.3 伙伴系统的心脏__alloc_pages_nodemask
内核源代码将__alloc_pages
称之为”伙伴系统的心脏”(`the ‘heart’ of the zoned buddy allocator“), 因为它处理的是实质性的内存分配.
由于”心脏”的重要性, 我将在下文详细介绍该函数.
__alloc_pages
函数定义在include/linux/gfp.h?v=4.7#L428
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L3779
/** This is the 'heart' of the zoned buddy allocator.*/
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,struct zonelist *zonelist, nodemask_t *nodemask)
{struct page *page;unsigned int cpuset_mems_cookie;unsigned int alloc_flags = ALLOC_WMARK_LOW|ALLOC_FAIR;gfp_t alloc_mask = gfp_mask; /* The gfp_t that was actually used for allocation */struct alloc_context ac = {.high_zoneidx = gfp_zone(gfp_mask),.zonelist = zonelist,.nodemask = nodemask,.migratetype = gfpflags_to_migratetype(gfp_mask),};if (cpusets_enabled()) {alloc_mask |= __GFP_HARDWALL;alloc_flags |= ALLOC_CPUSET;if (!ac.nodemask)ac.nodemask = &cpuset_current_mems_allowed;}gfp_mask &= gfp_allowed_mask;lockdep_trace_alloc(gfp_mask);might_sleep_if(gfp_mask & __GFP_DIRECT_RECLAIM);if (should_fail_alloc_page(gfp_mask, order))return NULL;/** Check the zones suitable for the gfp_mask contain at least one* valid zone. It's possible to have an empty zonelist as a result* of __GFP_THISNODE and a memoryless node*/if (unlikely(!zonelist->_zonerefs->zone))return NULL;if (IS_ENABLED(CONFIG_CMA) && ac.migratetype == MIGRATE_MOVABLE)alloc_flags |= ALLOC_CMA;retry_cpuset:cpuset_mems_cookie = read_mems_allowed_begin();/* Dirty zone balancing only done in the fast path */ac.spread_dirty_pages = (gfp_mask & __GFP_WRITE);/** The preferred zone is used for statistics but crucially it is* also used as the starting point for the zonelist iterator. It* may get reset for allocations that ignore memory policies.*/ac.preferred_zoneref = first_zones_zonelist(ac.zonelist,ac.high_zoneidx, ac.nodemask);if (!ac.preferred_zoneref) {page = NULL;goto no_zone;}/* First allocation attempt */page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);if (likely(page))goto out;/** Runtime PM, block IO and its error handling path can deadlock* because I/O on the device might not complete.*/alloc_mask = memalloc_noio_flags(gfp_mask);ac.spread_dirty_pages = false;/** Restore the original nodemask if it was potentially replaced with* &cpuset_current_mems_allowed to optimize the fast-path attempt.*/if (cpusets_enabled())ac.nodemask = nodemask;page = __alloc_pages_slowpath(alloc_mask, order, &ac);no_zone:/** When updating a task's mems_allowed, it is possible to race with* parallel threads in such a way that an allocation can fail while* the mask is being updated. If a page allocation is about to fail,* check if the cpuset changed during allocation and if so, retry.*/if (unlikely(!page && read_mems_allowed_retry(cpuset_mems_cookie))) {alloc_mask = gfp_mask;goto retry_cpuset;}out:if (kmemcheck_enabled && page)kmemcheck_pagealloc_alloc(page, order, gfp_mask);trace_mm_page_alloc(page, order, alloc_mask, ac.migratetype);return page;
}
EXPORT_SYMBOL(__alloc_pages_nodemask);
4.4 __free_pages
类似地,内存释放函数也可以归约到一个主要的函数(__free_pages), 只是用不同的参数调用而已
前面我们讲过内核释放的两个主要函数有__free_page和free_page, 它们的定义在include/linux/gfp.h?v=4.7#L519
// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L519
#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)
而free_pages是通过__free_pages来完成内存释放的, 参见mm/page_alloc.c?v=4.7#L3918
void free_pages(unsigned long addr, unsigned int order)
{if (addr != 0) {VM_BUG_ON(!virt_addr_valid((void *)addr));__free_pages(virt_to_page((void *)addr), order);}
}
free_pages
和__free_pages
之间的关系通过函数而不是宏建立, 因为首先必须将虚拟地址转换为指向struct page
的指针
virt_to_page
将虚拟内存地址转换为指向page实例的指针. 基本上, 这是讲解内存分配函数时介绍的page_address辅助函数的逆过程.
下图以图形化方式综述了各个内存释放函数之间的关系
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 嵌入式系统及其在医疗仪器设备应用
嵌入式系统及其在医疗仪器设备应用 嵌入式系统是计算机技术、通信技术、半导体技术、微电子技术、语音图像数据传输技术,甚至传感器等先进技术和具体应用对象相结合后的更新换代产品,反映当代最新技术的先进水平。嵌入式系统是当今非常热门的研究领域,在PC市场已趋于稳定…...
2024/4/12 12:02:40 - 关于chrome浏览器不能正常访问百度的解决方法
关于chrome浏览器不能正常访问百度的解决方法 最近一段时间,使用chrome的时候,经常会出现不能正常访问百度的问题,搞得我每次都想怒卸chrome,回归foxfire。以下是几种解决方案。 对于我来说最有用的方案 使用管理员权限打开cmd,输入 netsh winsock reset,随后重启电脑。 …...
2024/4/12 12:02:46 - Nginx-window下入门
Nginx - Windows下Nginx初入门 转载http://www.cnblogs.com/nick-huang/p/4638398.html下载目前(2015-07-11),nginx的稳定版本是1.8.0,在官网下载先,windows版的nginx 下载zip文件,解压后即可使用启动 绿色文件,无须安装,直接即可启动。 据我所知,3种启动途径,其实都…...
2024/4/12 12:02:58 - 谈谈优惠券系统的设计
版权声明: 本文为博主原创文章,未经博主允许不得转载。关注公众号技术汇(ID: jishuhui_2015)可联系到作者。优惠券系统的核心在于各种券种的管理,发放和使用。通常的设计角度是从终端用户出发,所谓“所见即所得”,终端用户所见到的形形色色的优惠券,正是开发整个系统的挑…...
2024/4/18 18:04:49 - Linux分页机制之概述--Linux内存管理(六)
日期内核版本架构作者GitHubCSDN2016-09-01Linux-4.7X86 & armgatiemeLinuxDeviceDriversLinux内存管理1 分页机制在虚拟内存中,页表是个映射表的概念, 即从进程能理解的线性地址(linear address)映射到存储器上的物理地址(phisical address). 很显然,这个页表是需要常驻…...
2024/4/12 12:02:46 - install logicalDoc
数据库配置create database logicaldoc; GRANT ALL PRIVILEGES ON logicaldoc.* TO logicaldoc@% identified by fieFN539; flush privileges; exit;yumyum -y install ImageMagick openssl clamav ghostscriptPdftohtmlwget ftp://ftp.foolabs.com/pub/xpdf/xpdfbin-linux-3.0…...
2024/4/18 16:26:12 - windows重置网络解决浏览器无法访问
舍友电脑无法使用浏览器打开网络。我试了下,ping了学校网站,有反应,这说明至少在网络层,是没有问题。浏览器工作在应用层,这中间有多个环节可以出现问题,换了个浏览器试试,也是一个。后面想了下,既然网络层的连接没问题,那就直接快刀斩乱麻,重置所有的网络设置得了。…...
2024/4/12 12:02:52 - Nginx教程(四) Location配置与ReWrite语法
Nginx教程(四)Location配置与ReWrite语法1 Location语法规则 1.1 Location规则语法规则: location [=|~|~*|^~] /uri/ {…}首先匹配 =,其次匹配^~,其次是按文件中顺序的正则匹配,最后是交给/通用匹配。当有匹配成功时候,停止匹配,按当前匹配规则处理请求。符号含义== 开头…...
2024/4/12 21:32:31 - Linux内存描述之内存区域zone--Linux内存管理(三)
日期内核版本架构作者GitHubCSDN2016-08-31Linux-4.7X86 & armgatiemeLinuxDeviceDriversLinux内存管理2017-11-05 最新更新 更新了 struct zone 结构体的字段描述, 更详细, 更清晰1 前景回顾前面我们讲到服务器体系(SMP, NUMA, MPP)与共享存储器架构(UMA和NUMA)1.1 UM…...
2024/4/20 15:40:28 - 2006年我的常用工具列表
数据库管理:PLSQL Developer:oracle 开发者必备工具Toad for Oracle 9.0: oracle dba必备工具Navicat MySQL:操作mysql 很简单的工具 系统管理:Xmanager Enterprise:登录aix,linux x window的工具SecureCRT 5.2:支持ssh, telnet等;SecureFX 4.0:支持sftp,ftp等;UltraVN…...
2024/4/12 12:02:58 - phpexcel出现无法访问此网站,ERR_INVALID_RESPONSE
最近在做phpexcel导出的时候,发现不能下载文件首先查看是否没有加 header都加了的,还是没法导出。 google谈起原因是之后发现在导出excel之前要清空缓存区,于是在调用save("php://output")之前调用ob_end_clean()ob_end_clean(); $objWriter->save(php://output…...
2024/4/18 17:02:35 - 数据库课程设计 ——酒店管理系统
一、 需求分析 1.软件需求 (1)酒店管理系统用于满足酒店工作人员和管理人员的需求。 (2)酒店管理人员和工作人员可以为酒店房间加入入住和退房记录,并生成相应的报表用于查阅,确认和保存,酒店工作人员可以浏览、查询、统计、添加酒店房间的入住离开信息。管理员可以查询…...
2024/4/9 17:37:22 - windows系统打开火狐浏览器提示“无法加载您的firefox配置文件。它可能已经丢失,或是无法访问。” 解决办法
Windows系统自带IE浏览器,可是有很多用户使用不习惯,选择下载第三方浏览器,比如:火狐浏览器、谷歌浏览器、360浏览器等。最近有朋友在重新安装火狐浏览器后发现打不开,并提示“无法加载你的firefox配置文件 。它可能已经丢失,或是无法访问”的故障,就算是重装也没有用,…...
2024/4/22 19:20:16 - dwg文件怎么打开?dwg文件用什么打开?
dwg文件怎么打开dwg文件打开方法汇总:第一步:安装AutoCAD,专业的CAD绘图软件,因为该软件设计出来的图纸保存的文件格式就是dwg文件。第二步:下载安装dwg文件浏览器由于不少朋友只是查看图纸,本身对CAD并不了解,而AutoCAD绘图软件输入3D大型制图软件,如果仅仅只是查看图…...
2024/4/18 10:15:53 - 【LINUNX】常用的Nginx命令
/usr/local/webserver/nginx/sbin/nginx # 启动Nginx /usr/local/webserver/nginx/sbin/nginx -t # 检查配置文件nginx.conf的正确性 /usr/local/webserver/nginx/sbin/nginx -s reload # 重新载入配置文件 /usr/local/webs…...
2024/4/20 8:14:36 - linux内存管理概述
linux内存管理建立在基本的分页机制基础上,在linux内核中RAM的某些部分将会永久的分配给内核,并用来存放内核代码以及静态内核数据结构。RAM的其余部分称为动态内存,这不仅是进程所需的宝贵资源,也是内核本身所需的宝贵资源。实际上,整个系统的性能取决于如何有效地管理动…...
2024/4/24 3:33:02 - 关于Google的BlogSpot Blog不能访问
关于Google的BlogSpot blog不能访问连续好几天了,blogspot都不能访问,这次的原因肯定不是因为地震震断海底光缆了,我想大家都清楚作为一介布衣,我是非常的气愤!我对政治不关心,就算有什么反动信息我也没有兴趣去看的!!此地无银三百两!幸好找到一个BlogSpot 登录器,输…...
2024/4/24 10:19:04 - 浏览器无法访问网页、播放视频、上传下载附件等问题原因即解决
前言我们上网浏览各种网站,使用浏览器的时候可能会遇到一些问题,为了解决这些问题而在网上四处的找答案,于是,为了方便大家,我总结了一些的问题原因及解决方法,可能不太全,以后添加。一、浏览器无法访问网页1.浏览器的版本太老 (浏览器的版本可能导致某些网站的网页无…...
2024/4/25 16:43:51 - Centos7安装Nginx教程
Nginx下载 1.下载nginx nginx官方下载地址Windows要使用nginx的话,只需要下载解压就可以。 2.安装nginx 将nginx.tar.gz传到centos上,默认会传到/root目录下 输入下面指令,检查并自动安装必备的运行库 yum install gcc openssl openssl-devel pcre pcre-devel zlib zlib-dev…...
2024/4/12 12:03:35 - dwg文件怎么打开呢?dwg是啥呢?
学习过绘图AUTO CAD的朋友对dwg文件再熟悉不过了,dwg是二维或三维图形,一般的建筑图纸通信图纸电气图纸都是用cad绘制的,但对于没学习过CAD绘图的朋友对*.dwg文件都是一脸茫然,今天刚好有量产网网友问了编辑dwg文件怎么打开,编辑这里介绍下常用的打开dwg文件方法汇总,其实…...
2024/4/19 12:29:27
最新文章
- PHP机制
PHP有以下几个重要的机制: 解析器:PHP解析器负责将PHP源代码转换为可执行的字节码或者直接执行。PHP的解析器支持多种解析模式,包括解释模式和编译模式。解释模式直接将源代码逐行解释执行,而编译模式将源代码编译成字节码&#x…...
2024/4/26 18:14:11 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - OpenHarmony开发-连接开发板调试应用
在 OpenHarmony 开发过程中,连接开发板进行应用调试是一个关键步骤,只有在真实的硬件环境下,我们才能测试出应用更多的潜在问题,以便后续我们进行优化。本文详细介绍了连接开发板调试 OpenHarmony 应用的操作步骤。 首先…...
2024/4/23 6:14:00 - Redis分区
Redis分区是一种数据分片技术,用于将数据分布到多个Redis实例(节点)上以提高性能和扩展性。分区使得Redis能够处理比单个实例更大的数据集,并允许并行处理客户端请求。 原理: Redis分区通过一致性哈希算法(…...
2024/4/23 6:11:42 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/4/26 18:09:39 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/4/25 18:39:24 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/4/25 18:38:39 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/4/25 18:39:23 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/4/25 18:39:22 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/4/25 18:39:22 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/4/25 18:39:20 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/4/25 16:48:44 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/4/26 16:00:35 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/4/25 18:39:16 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/4/25 18:39:16 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/4/25 0:00:17 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/4/25 4:19:21 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/4/25 18:39:14 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/4/25 18:39:12 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/4/25 2:10:52 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/4/25 18:39:00 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/4/25 13:19:01 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/4/25 18:38:58 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/4/25 18:38:57 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57