当前位置: 首页 > news >正文

Linux中释放初始化内存到伙伴系统free_initmem函数的实现

核心函数功能总结

free_initmem - 主释放函数

  • 功能:释放内核初始化段(.init段)的内存
  • 释放范围__init_begin__init_end 之间的所有页面
  • 关键操作
    • 清除页面保留标志,使其可被重新分配
    • 0xcc模式填充内存,检测后续非法访问
    • 通过free_page将页面返回伙伴系统
    • 更新系统内存统计信息

内存释放调用链

free_page(addr) → free_pages(addr,0) → __free_pages(page,0) → free_hot_page(page) → free_hot_cold_page(page,0)

分层设计

  • 接口层free_page - 用户友好接口
  • 验证层free_pages - 地址有效性检查
  • 逻辑层__free_pages - 引用计数和保留状态检查
  • 优化层free_hot_cold_page - 每CPU缓存管理

每CPU缓存机制

  • 热页面:刚释放的页面,很可能在CPU缓存中
  • 冷页面:较长时间未使用的页面
  • 水位控制:当缓存达到高水位时批量释放到伙伴系统
  • 性能优势:减少锁竞争,提高单页分配/释放效率

内存管理架构层次

1. 物理页面管理(底层)

  • 伙伴系统__free_pages_ok - 处理多页释放
  • 每CPU缓存free_hot_cold_page - 处理单页释放
  • 内存域page_zone - NUMA架构支持

2. 页面状态管理(中层)

  • 标志位PG_reserved, PG_dirty, PG_lru
  • 引用计数_count字段跟踪使用情况
  • 映射关系mapping指针管理地址空间

3. 安全验证(高层)

  • 地址验证virt_addr_valid检查虚拟地址有效性
  • 状态验证free_pages_check确保释放条件满足
  • 调试支持:填充模式和错误报告

释放初始化内存free_initmem

void free_initmem(void)
{unsigned long addr;addr = (unsigned long)(&__init_begin);for (; addr < (unsigned long)(&__init_end); addr += PAGE_SIZE) {ClearPageReserved(virt_to_page(addr));set_page_count(virt_to_page(addr), 1);memset((void *)addr, 0xcc, PAGE_SIZE);free_page(addr);totalram_pages++;}printk (KERN_INFO "Freeing unused kernel memory: %dk freed\n", (__init_end - __init_begin) >> 10);
}

函数功能概述

free_initmem 函数负责释放内核初始化阶段使用的内存,这些内存在系统启动完成后不再需要,可以返回给系统供正常使用

代码详细解析

函数声明和变量定义

void free_initmem(void)
{unsigned long addr;

函数声明:

  • void: 无返回值
  • free_initmem(void): 函数名,明确表示释放初始化内存
  • unsigned long addr: 用于遍历内存页的地址变量

初始化内存范围确定

	addr = (unsigned long)(&__init_begin);

设置起始地址:

  • &__init_begin: 获取初始化内存段的开始地址
  • (unsigned long): 强制转换为无符号长整型
  • __init_begin: 链接器定义的符号,标记初始化代码和数据的开始位置

内存释放循环开始

	for (; addr < (unsigned long)(&__init_end); addr += PAGE_SIZE) {

循环遍历所有初始化内存页:

  • 没有初始表达式,直接使用addr的当前值
  • addr < (unsigned long)(&__init_end): 循环条件,直到达到初始化内存段结束
  • addr += PAGE_SIZE: 每次增加一页的大小
  • PAGE_SIZE: 系统页面大小,通常是4096字节(4KB)
  • __init_end: 链接器定义的符号,标记初始化段的结束位置

清除页面保留标志

		ClearPageReserved(virt_to_page(addr));

取消页面的保留状态:

  • virt_to_page(addr): 将虚拟地址转换为对应的页面结构指针
    • 这个函数完成从虚拟地址到struct page的映射
  • ClearPageReserved(): 清除页面的保留标志位
  • 保留标志作用: 标记这些页面为内核初始化专用,防止被其他用途占用
  • 清除必要性: 在释放前必须清除保留标志,否则页面无法被重新分配

设置页面引用计数

		set_page_count(virt_to_page(addr), 1);

初始化页面引用计数:

  • virt_to_page(addr): 再次获取页面结构指针

  • set_page_count(..., 1): 实际上将页面引用计数设置为1 - 1 = 0

  • 设置为0的原因: 准备将页面放入空闲列表,伙伴系统期望回收页面的计数是0

内存内容填充

		memset((void *)addr, 0xcc, PAGE_SIZE);

用特定模式填充内存:

  • memset((void *)addr, 0xcc, PAGE_SIZE): 用0xcc字节模式填充整个页面
  • 0xcc的特定含义:
    • 在x86架构中,0xcc对应int 3指令(断点指令)
    • 如果错误地执行这些内存,会触发调试断点
    • 用于检测对已释放内存的意外访问

释放页面到系统

		free_page(addr);

核心释放操作:

  • free_page(addr): 将页面返回给伙伴系统(buddy system)
  • 内部操作:
    • 将页面添加到对应的空闲链表
    • 可能进行页面合并(与相邻空闲页面合并成更大的连续块)
    • 更新内存管理器的统计信息
  • 伙伴系统: Linux内核的物理内存管理器

更新系统内存统计

		totalram_pages++;}

增加可用页面计数:

  • totalram_pages++: 增加系统总可用物理页面计数
  • totalram_pages: 全局变量,记录系统中可用的物理页面总数
  • 重要性: 这个计数影响内存分配决策和系统统计信息
  • 循环结束

输出释放信息

	printk (KERN_INFO "Freeing unused kernel memory: %dk freed\n", (__init_end - __init_begin) >> 10);
}

记录释放操作:

  • printk(KERN_INFO ...): 输出内核信息级日志
  • "Freeing unused kernel memory: %dk freed\n": 格式化字符串,显示释放了多少内存
  • (__init_end - __init_begin) >> 10: 计算释放的内存大小
    • __init_end - __init_begin: 初始化段的总字节数
    • >> 10: 右移10位,相当于除以1024,转换为KB单位
  • 函数结束

页面相关宏解释

#define set_page_count(p,v) 	atomic_set(&(p)->_count, v - 1)#define virt_addr_valid(kaddr)	pfn_valid(__pa(kaddr) >> PAGE_SHIFT)
#define __pa(x)			((unsigned long)(x)-PAGE_OFFSET)
#define pfn_valid(pfn)		((pfn) < max_mapnr)#define virt_to_page(kaddr)	pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
#define pfn_to_page(pfn)	(mem_map + (pfn))#define put_page_testzero(p)				\({						\BUG_ON(page_count(p) == 0);		\atomic_add_negative(-1, &(p)->_count);	\})
static __inline__ int atomic_add_negative(int i, atomic_t *v)
{unsigned char c;__asm__ __volatile__(LOCK "addl %2,%0; sets %1":"=m" (v->counter), "=qm" (c):"ir" (i), "m" (v->counter) : "memory");return c;
}

第一部分:页计数设置宏

#define set_page_count(p,v) 	atomic_set(&(p)->_count, v - 1)

功能:设置页面的引用计数值

  • p:指向page结构体的指针
  • v:要设置的计数值
  • 关键点:实际设置的是v - 1,这是因为内核中引用计数从-1开始(0表示空闲页)

第二部分:虚拟地址验证宏

#define virt_addr_valid(kaddr)	pfn_valid(__pa(kaddr) >> PAGE_SHIFT)
#define __pa(x)			((unsigned long)(x)-PAGE_OFFSET)
#define pfn_valid(pfn)		((pfn) < max_mapnr)
  1. __pa(x):将虚拟地址转换为物理地址

    • PAGE_OFFSET:内核虚拟地址空间的起始偏移量(通常是0xC0000000)
    • 通过减法得到对应的物理地址
  2. pfn_valid(pfn):验证页帧号是否有效

    • pfn:页帧号(Page Frame Number)
    • max_mapnr:系统支持的最大页帧号
    • 检查pfn是否在有效范围内
  3. virt_addr_valid(kaddr):综合验证虚拟地址是否有效

    • 先将虚拟地址转物理地址,再右移得到页帧号
    • 最后验证该页帧号是否有效

第三部分:地址到页结构转换

#define virt_to_page(kaddr)	pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
#define pfn_to_page(pfn)	(mem_map + (pfn))

解析

  1. pfn_to_page(pfn):页帧号到page结构体指针的转换

    • mem_map:全局page结构体数组的起始地址
    • 通过数组下标访问得到对应的page结构体
  2. virt_to_page(kaddr):虚拟地址到page结构体指针的转换

    • 先将虚拟地址转为物理地址,再得到页帧号
    • 最后通过页帧号找到对应的page结构体

第四部分:页引用计数减量测试

#define put_page_testzero(p)				\({						\BUG_ON(page_count(p) == 0);		\atomic_add_negative(-1, &(p)->_count);	\})

解析

  • ({ ... }):GCC的语句表达式,允许将多个语句作为一个表达式
  • atomic_add_negative(-1, &(p)->_count):原子地将计数减1并检查结果是否为负

第五部分:atomic_add_negative函数

static __inline__ int atomic_add_negative(int i, atomic_t *v)
{unsigned char c;__asm__ __volatile__(LOCK "addl %2,%0; sets %1":"=m" (v->counter), "=qm" (c):"ir" (i), "m" (v->counter) : "memory");return c;
}

内联汇编详解

  • LOCK:总线锁前缀,确保操作的原子性
  • addl %2,%0:将输入操作数2(i)加到操作数0(v->counter)
  • sets %1:根据符号标志位(SF)设置操作数1©
  • 输出约束
    • "=m" (v->counter):修改内存中的计数器
    • "=qm" (c):结果存入字节寄存器或内存
  • 输入约束
    • "ir" (i):立即数或寄存器
    • "m" (v->counter):内存中的原子变量
  • "memory":内存屏障,防止编译器重排序

整体功能总结

这些代码实现了Linux内核中的页引用计数管理

  1. 引用计数语义:计数从-1开始,0表示最后一个引用被释放
  2. 原子操作:确保多核环境下的线程安全
  3. 地址转换:在虚拟地址、物理地址、页帧号、page结构体之间转换
  4. 引用跟踪:通过put_page_testzero安全地减少引用计数并检测是否归零

页面释放的完整调用链free_page

#define free_page(addr) free_pages((addr),0)fastcall void free_pages(unsigned long addr, unsigned int order)
{if (addr != 0) {BUG_ON(!virt_addr_valid((void *)addr));__free_pages(virt_to_page((void *)addr), order);}
}
fastcall void __free_pages(struct page *page, unsigned int order)
{if (!PageReserved(page) && put_page_testzero(page)) {if (order == 0)free_hot_page(page);else__free_pages_ok(page, order);}
}
void fastcall free_hot_page(struct page *page)
{free_hot_cold_page(page, 0);
}
static void fastcall free_hot_cold_page(struct page *page, int cold)
{struct zone *zone = page_zone(page);struct per_cpu_pages *pcp;unsigned long flags;arch_free_page(page, 0);kernel_map_pages(page, 1, 0);inc_page_state(pgfree);if (PageAnon(page))page->mapping = NULL;free_pages_check(__FUNCTION__, page);pcp = &zone->pageset[get_cpu()].pcp[cold];local_irq_save(flags);if (pcp->count >= pcp->high)pcp->count -= free_pages_bulk(zone, pcp->batch, &pcp->list, 0);list_add(&page->lru, &pcp->list);pcp->count++;local_irq_restore(flags);put_cpu();
}

第一层:free_page 宏

#define free_page(addr) free_pages((addr),0)

功能:释放单个页面的宏封装

  • addr:要释放的内存虚拟地址
  • 转换为调用free_pagesorder=0表示释放1个页面(2^0 = 1页)

第二层:free_pages 函数

fastcall void free_pages(unsigned long addr, unsigned int order)
{if (addr != 0) {BUG_ON(!virt_addr_valid((void *)addr));__free_pages(virt_to_page((void *)addr), order);}
}
  1. if (addr != 0):检查地址非空(NULL地址不需要释放)
  2. BUG_ON(!virt_addr_valid((void *)addr)):调试检查,确保虚拟地址有效
    • virt_addr_valid:验证地址是否在有效的内核映射范围内
    • 如果地址无效,触发内核Oops
  3. __free_pages(virt_to_page((void *)addr), order)
    • virt_to_page((void *)addr):将虚拟地址转换为对应的page结构体指针
    • 调用下一级释放函数

第三层:__free_pages 函数

fastcall void __free_pages(struct page *page, unsigned int order)
{if (!PageReserved(page) && put_page_testzero(page)) {if (order == 0)free_hot_page(page);else__free_pages_ok(page, order);}
}

关键检查逻辑

  1. !PageReserved(page):检查页面是否不是保留页面
    • 保留页面(如内核代码、页表等)不能被释放
  2. put_page_testzero(page):原子地减少引用计数并检查是否归零
    • 如果返回true,表示这是最后一个引用,可以释放
    • 如果返回false,表示还有其他引用,不能释放

释放路径选择

  • order == 0:单页释放,进入快速路径free_hot_page
  • order > 0:多页释放(复合页),进入标准路径__free_pages_ok

第四层:free_hot_page 函数

void fastcall free_hot_page(struct page *page)
{free_hot_cold_page(page, 0);
}

功能:单页热释放的简单封装

  • 0 表示"热"页面(很可能在CPU缓存中)
  • 热页面更适合快速重用

第五层:free_hot_cold_page 函数

static void fastcall free_hot_cold_page(struct page *page, int cold)
{struct zone *zone = page_zone(page);struct per_cpu_pages *pcp;unsigned long flags;arch_free_page(page, 0);kernel_map_pages(page, 1, 0);inc_page_state(pgfree);if (PageAnon(page))page->mapping = NULL;free_pages_check(__FUNCTION__, page);

初始化阶段

  1. struct zone *zone = page_zone(page):获取页面所属的内存域
  2. struct per_cpu_pages *pcp:每CPU页面缓存指针
  3. unsigned long flags:保存中断状态

页面清理操作

  1. arch_free_page(page, 0):架构相关的页面清理
    • 可能清除缓存、TLB等架构特定状态
  2. kernel_map_pages(page, 1, 0):重置页面的内核映射属性
  3. inc_page_state(pgfree):更新页面释放统计信息
  4. if (PageAnon(page)) page->mapping = NULL:如果是匿名页面,清除映射关系
  5. free_pages_check(__FUNCTION__, page):调试检查,验证页面状态正确
	pcp = &zone->pageset[get_cpu()].pcp[cold];local_irq_save(flags);if (pcp->count >= pcp->high)pcp->count -= free_pages_bulk(zone, pcp->batch, &pcp->list, 0);list_add(&page->lru, &pcp->list);pcp->count++;local_irq_restore(flags);put_cpu();
}

每CPU缓存管理

  1. pcp = &zone->pageset[get_cpu()].pcp[cold]:获取当前CPU的热/冷页面缓存
    • get_cpu():禁用内核抢占并返回当前CPU ID
  2. local_irq_save(flags):保存中断状态并禁用本地中断
    • 防止中断处理程序干扰页面缓存操作

缓存水位控制

  1. if (pcp->count >= pcp->high):检查缓存是否达到高水位线
  2. pcp->count -= free_pages_bulk(zone, pcp->batch, &pcp->list, 0)
    • 如果缓存太满,批量释放部分页面回内存域
    • 更新缓存计数

页面添加到缓存

  1. list_add(&page->lru, &pcp->list):将页面添加到每CPU缓存链表
  2. pcp->count++:增加缓存页面计数
  3. local_irq_restore(flags):恢复中断状态
  4. put_cpu():启用内核抢占

整体功能总结

这个调用链实现了Linux内核的单页面释放机制,具有以下特点:

核心功能

  1. 安全检查:地址验证、页面保留检查、引用计数检查
  2. 性能优化:热页面快速路径、每CPU缓存减少锁竞争
  3. 内存管理:水位线控制防止缓存过度增长

获取页面的内存区域page_zone

struct zone *zone_table[];
#define NODEZONE_SHIFT (sizeof(page_flags_t)*8 - MAX_NODES_SHIFT - MAX_ZONES_SHIFT)
/** with 32 bit page->flags field, we reserve 8 bits for node/zone info.* there are 3 zones (2 bits) and this leaves 8-2=6 bits for nodes.*/
#define MAX_NODES_SHIFT		6
/* There are currently 3 zones: DMA, Normal & Highmem, thus we need 2 bits */
#define MAX_ZONES_SHIFT		2
static inline struct zone *page_zone(struct page *page)
{return zone_table[page->flags >> NODEZONE_SHIFT];
}
static inline void set_page_zone(struct page *page, unsigned long nodezone_num)
{page->flags &= ~(~0UL << NODEZONE_SHIFT);page->flags |= nodezone_num << NODEZONE_SHIFT;
}

第一部分:常量和宏定义

struct zone *zone_table[];
#define NODEZONE_SHIFT (sizeof(page_flags_t)*8 - MAX_NODES_SHIFT - MAX_ZONES_SHIFT)

逐行解析

  1. struct zone *zone_table[];:声明一个全局的zone指针数组

    • 用于存储所有内存域(zone)的指针
    • 通过索引快速查找zone结构
  2. #define NODEZONE_SHIFT (sizeof(page_flags_t)*8 - MAX_NODES_SHIFT - MAX_ZONES_SHIFT)

    • 计算node和zone信息在page->flags中的起始位位置
    • sizeof(page_flags_t)*8:page->flags的总位数(通常是32位)
    • 减去节点和区域所需的位数,得到起始偏移

第二部分:位数分配定义

/** with 32 bit page->flags field, we reserve 8 bits for node/zone info.* there are 3 zones (2 bits) and this leaves 8-2=6 bits for nodes.*/
#define MAX_NODES_SHIFT		6
#define MAX_ZONES_SHIFT		2
  • 32位的page->flags字段中,保留8位用于存储节点和区域信息
  • 有3个内存区域(需要2位),剩下6位用于节点ID

常量定义

  • MAX_NODES_SHIFT 6:节点ID占用的位数(2^6 = 64个节点)
  • MAX_ZONES_SHIFT 2:区域类型占用的位数(2^2 = 4个区域)

实际位分配

32位 page->flags 布局:
[31...24] [23...8] [7...0]未使用   未使用  节点区域信息

在最低8位中:

[7...2] [1...0]节点ID   区域ID

第三部分:page_zone 函数

static inline struct zone *page_zone(struct page *page)
{return zone_table[page->flags >> NODEZONE_SHIFT];
}
  1. static inline:静态内联函数,减少函数调用开销
  2. struct zone *page_zone(struct page *page):获取页面所属的内存域
  3. page->flags >> NODEZONE_SHIFT:右移得到节点区域索引
    • 假设32位系统:NODEZONE_SHIFT = 32 - 6 - 2 = 24
    • page->flags >> 24 将节点区域信息移到最低8位
  4. zone_table[...]:通过索引在全局表中查找对应的zone指针

第四部分:set_page_zone 函数

static inline void set_page_zone(struct page *page, unsigned long nodezone_num)
{page->flags &= ~(~0UL << NODEZONE_SHIFT);page->flags |= nodezone_num << NODEZONE_SHIFT;
}
  1. page->flags &= ~(~0UL << NODEZONE_SHIFT):清除节点区域信息位

    • ~0UL:全1的掩码(32位系统:0xFFFFFFFF)
    • ~0UL << NODEZONE_SHIFT:左移得到高位的掩码
      • 32位系统:0xFFFFFFFF << 24 = 0xFF000000
    • ~(...):取反得到低位的掩码:0x00FFFFFF
    • &=:用掩码清除高8位,保留低24位
  2. page->flags |= nodezone_num << NODEZONE_SHIFT:设置新的节点区域信息

    • nodezone_num << NODEZONE_SHIFT:将节点区域编号移到高位
    • |=:设置到page->flags中

整体功能总结

核心功能

  1. 快速查找:通过page->flags中的编码快速找到页面所属的内存域
  2. 空间优化:在有限的page->flags中高效存储节点和区域信息
  3. 性能优化:使用位运算和数组查找,避免复杂的计算

设计优势

  • 位字段压缩:在32位flags中只用8位存储节点+区域信息
  • 快速转换:简单的移位和数组索引操作
  • 扩展性:支持最多64个节点和4个内存区域
  • 内存效率:不需要在page结构体中存储额外的指针

inc_page_state/PageAnon/free_pages_check

#define inc_page_state(member)	mod_page_state(member, 1UL)
#define mod_page_state(member, delta)					\do {								\unsigned long flags;					\local_irq_save(flags);					\__get_cpu_var(page_states).member += (delta);		\local_irq_restore(flags);				\} while (0)
static inline int PageAnon(struct page *page)
{return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
}
static inline void free_pages_check(const char *function, struct page *page)
{if (	page_mapped(page) ||page->mapping != NULL ||page_count(page) != 0 ||(page->flags & (1 << PG_lru	|1 << PG_private |1 << PG_locked	|1 << PG_active	|1 << PG_reclaim	|1 << PG_slab	|1 << PG_swapcache |1 << PG_writeback )))bad_page(function, page);if (PageDirty(page))ClearPageDirty(page);
}

第一部分:页面状态统计宏

#define inc_page_state(member)	mod_page_state(member, 1UL)

功能:增加页面状态统计计数

解析

  • inc_page_state(member):将指定的页面状态计数器增加1
  • member:页面状态结构体中的成员名(如pgfree
  • 1UL:无符号长整型1,确保类型正确

第二部分:页面状态修改宏

#define mod_page_state(member, delta)					\do {								\unsigned long flags;					\local_irq_save(flags);					\__get_cpu_var(page_states).member += (delta);		\local_irq_restore(flags);				\} while (0)

功能:原子地修改每CPU页面状态计数器

  1. unsigned long flags;:保存中断状态的变量

  2. local_irq_save(flags);关键操作

    • 保存当前中断状态到flags
    • 禁用本地CPU的中断
    • 防止中断处理程序干扰统计操作
  3. __get_cpu_var(page_states).member += (delta);核心操作

    • __get_cpu_var(page_states):获取当前CPU的page_states变量
    • .member:访问特定的统计成员
    • += (delta):增加指定的增量值
  4. local_irq_restore(flags);:恢复中断状态

第三部分:匿名页面检查函数

static inline int PageAnon(struct page *page)
{return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
}

功能:检查页面是否为匿名页面

  1. (unsigned long)page->mapping:将mapping指针转换为数值

    • page->mapping通常指向address_space结构体
    • 但对于匿名页面,这个字段被特殊编码
  2. & PAGE_MAPPING_ANON:与匿名映射标志位进行位与操作

    • PAGE_MAPPING_ANON通常是最低位(如0x1)
  3. != 0:检查结果是否非零

    • 如果设置了匿名标志,返回true(1)
    • 否则返回false(0)

第四部分:页面释放检查函数

static inline void free_pages_check(const char *function, struct page *page)
{if (	page_mapped(page) ||page->mapping != NULL ||page_count(page) != 0 ||(page->flags & (1 << PG_lru	|1 << PG_private |1 << PG_locked	|1 << PG_active	|1 << PG_reclaim	|1 << PG_slab	|1 << PG_swapcache |1 << PG_writeback )))bad_page(function, page);

功能:在释放页面前的完整性检查

1. 页面仍被映射

page_mapped(page)
  • 检查页面是否还在页表中有映射
  • 如果仍被映射,不能释放

2. 映射指针非空

page->mapping != NULL
  • 对于要释放的页面,mapping应该为NULL
  • 非空表示页面还在使用中

3. 引用计数非零

page_count(page) != 0
  • 页面引用计数应该为0才能释放
  • 非零表示还有其他引用

4. 非法标志位检查

(page->flags & (1 << PG_lru	|1 << PG_private |1 << PG_locked	|1 << PG_active	|1 << PG_reclaim	|1 << PG_slab	|1 << PG_swapcache |1 << PG_writeback ))

检查以下不应该设置的标志位

  • PG_lru:页面在LRU链表中(应该先移除)
  • PG_private:页面有私有数据
  • PG_locked:页面被锁定
  • PG_active:页面在活跃链表中
  • PG_reclaim:页面正在被回收
  • PG_slab:页面属于slab分配器
  • PG_swapcache:页面在交换缓存中
  • PG_writeback:页面正在回写

错误处理

bad_page(function, page);
  • 如果任何检查失败,调用坏页处理函数
  • 记录调试信息,可能触发内核错误

第五部分:脏页清理

	if (PageDirty(page))ClearPageDirty(page);
}

功能:清理页面的脏标志

  1. PageDirty(page):检查页面是否被标记为脏

    • 脏页表示内容已被修改,需要写回存储设备
  2. ClearPageDirty(page):清除脏标志

    • 即使页面实际上是脏的,在释放时也清除标志
    • 防止后续错误检查
http://www.dtcms.com/a/554058.html

相关文章:

  • 58同城类似的网站开发云服务器网站建站
  • 商城类网站模板网站建好更新
  • Redis:我是如何与客户端进行通信的
  • 定制您的专属建站方案教育培训加盟
  • 高质量发展的“显著成效”:解读“十五五”对质与量协同增长的新要求
  • 网站建设生产或运营潭州学院网站建设报名
  • 做网站销售 优帮云市场推广计划方案
  • 移动的智慧:复合机器人开启24小时不间断上下料新纪元
  • Android Compose Dialog设置为底部显示
  • 衡水网站托管如何做seo网站
  • PCB繪製實操寄錄
  • LangChain企业知识库权限控制方案
  • 网站建设主要推广方式wordpress 登录 404
  • 4-Azido-L-phenylalanine,CAS号:33173-53-4,分子结构特点
  • 网站建设及解决方案在网站开发中如何设置用户登录
  • Android 12 模块编译的常用命令小结(更新中)
  • 如何使用 Python 转换 Excel 工作表到 PDF 文档
  • 网站建设伍金手指下拉2公众号图片到wordpress
  • 亚远景-在开发中的 “功能安全(ISO 26262)” 与 “网络安全(ISO/SAE 21434)”关联实践
  • wordpress关闭谷歌北京百度seo代理
  • Python中如何安全地存储和验证密码
  • fixed-bug:JPA 关联关系的对象序列化循环引用问题
  • Nginx入门基础-访问配置
  • 装饰网站建设辽宁省建设工程信息网官网新网站
  • 【LeetCode热题100(56/100)】组合总和
  • 什么是离线语音识别芯片(离线语音识别芯片有哪些优点)
  • 代前导页的网站定制网站建设案例课堂
  • 四川住房城乡建设厅官方网站天津制作企业网站的
  • 图的邻接矩阵实现以及遍历
  • 伟淼科技发布11月营销前瞻:解码 “温暖狂欢感恩” 增长公式