Linux内存管理详解.docx_第1页
Linux内存管理详解.docx_第2页
Linux内存管理详解.docx_第3页
Linux内存管理详解.docx_第4页
Linux内存管理详解.docx_第5页
已阅读5页,还剩16页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

13.内存管理13.1.引言Linux对物理内存的描述机制有两种:UMA和NUMA。Linux把物理内存划分为三个层次来管理:存储节点(Node)、管理区(Zone)和页面 (Page)。UMA对应一致存储结构,它只需要一个Node就可以描述当前系统中的物理内存,但是NUMA的出现打破了这种平静,此时需要多个 Node,它们被统一定义为一个名为discontig_node_data的数组。为了和UMA兼容,就将描述UMA存储结构的描述符 contig_page_data放到该数组的第一个元素中。内核配置选项CONFIG_NUMA决定了当前系统是否支持NUMA机制。此时无论UMA还 是NUMA,它们都是对应到一个类型为pg_data_t的数组中,便于统一管理。图71.Node Zone和Page的关系上图描述Linux管理物理内存的三个层次之间的拓扑关系。从图中可以看出一个存储节点由pg_data_t描述,一个UMA系统中只有一个Node,而 在NUMA中则可以存在多个Node。它由CONFIG_NODES_SHIFT配置选项决定,它是CONFIG_NUMA的子选项,所以只有配置了 CONFIG_NUMA,该选项才起作用。UMA情况下,NODES_SHIFT被定义为0,MAX_NUMNODES也即为1。 include/linux/numa.h#ifdef CONFIG_NODES_SHIFT#define NODES_SHIFT CONFIG_NODES_SHIFT#else#define NODES_SHIFT 0#endif#define MAX_NUMNODES (1 setup_arch-paging_init-bootmem_init-bootmem_free_node-free_area_init_node-free_area_init_core在理想的计算机体系结构中,一个物理页框就是一个内存存储单元,可用于任何事情:存放内核数据和用户数据,磁盘缓冲数据等。热河中磊的数据页都可以存放在 任何页框中,没有什么限制。但是,实际的计算机体系结构有硬件的制约,这制约页框可以使用的方式。尤其是Linux内核必须处理80x86体系结构的两种 硬件约束: ISA总线的直接内存存取DMA访问控制器只能对RAM的低16MB寻址。 在具有大容量RAM的现代32位计算机中,由于线性地址空间的限制,CPU不能直接访问所有的物理内存。最后一种限制不仅存在于80x86,而存在于所有的体系结构中。为了应对这两种限制,Linux把每个内存节点的物理内存划分为多个(通常为3个)管理区(zone)。在80x86 UMA体系结构中的管理区为: ZONE_DMA,包含低于16MB的内存页框。 ZONE_NORMAL,包含高于16MB且低于896MB的内存页框。 ZONE_HIGHMEM,包含从896MB开始的内存页框。对于ARM来说,ZONE_HIGHMEM被名为ZONE_MOVABLE的宏取代,而ZONE_DMA也不会仅限于最低的16MB,而可能对应所有的内存区域,此时只有内存节点ZONE_DMA有效,所以ZONE_DMA并不一定名副其实的用来作为DMA访问之用。 ZONE_DMA和ZONE_NORMAL区包含内存的常规页框,通过把它们线性的映射到线性地址的第4个 GB(0xc0000000-0xcfffffff),内核就可以直接访问。相反ZONE_HIGHMEM或者ZONE_MOVABLE区包含的内存页不 能由内核直接访问,尽管它们也线性地映射到了线性地址空间的第4个GB。每个内存管理区都有自己的描述符struct zone。它用来保存管理区的跟踪信息:内存使用统计,空闲区,锁定区等。 include/linux/mmzone.hstruct zone /* Fields commonly accessed by the page allocator */ unsigned long pages_min, pages_low, pages_high; unsigned long lowmem_reserveMAX_NR_ZONES; struct per_cpu_pageset pagesetNR_CPUS; struct free_area free_areaMAX_ORDER; ZONE_PADDING(_pad1_) /* Fields commonly accessed by the page reclaim scanner */ spinlock_t lru_lock; struct struct list_head list; unsigned long nr_scan; lruNR_LRU_LISTS; unsigned long recent_rotated2; unsigned long recent_scanned2; unsigned long pages_scanned; /* since last reclaim */ unsigned long flags; /* zone flags, see below */ /* Zone statistics */ atomic_long_t vm_statNR_VM_ZONE_STAT_ITEMS; pages_min,记录管理区中空闲页的数目。 pages_low,回收页框使用的下届,同时也被管理区分配器作为阈值使用。 pages_high,回收页框使用的上届,同时也被管理区分配器作为阈值使用。 lowmem_reserve,指明在处理内存不足的临界情况下每个管理区必须保留的页框数目。 pageset,单一页框的特殊告诉缓存。在申请内存时,会遇到两种情况:如果有足够的空闲页可用,请求就会被立刻满足;否则,必须回收一些内存,并且将发出请求的内核控制路径阻塞,直到有内存被 释放。不过有些内存请求不能被阻塞。这种情况发生在处理中断或在执行临界区内的代码时。在这些情况下,一条内核控制路径应使用原子内存分配请求 (GFP_ATOMIC)。原子请求从不被阻塞;如果没有足够的空闲页,则仅仅是分配失败而已。内核为了尽可能保证一个原子内存分配请求成功,它为原子内存分配请求保留了一个页框池,只有在内存不足时才使用。保留内存的数量存放在min_free_kbytes变量中,单位为KB。 mm/page_alloc.cint min_free_kbytes = 1024;./* min_free_kbytes = sqrt(lowmem_kbytes * 16); */lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE 10);min_free_kbytes = int_sqrt(lowmem_kbytes * 16);min_free_kbytes由当前直接映射区的物理内存数量决定。也即ZONE_DMA和ZONE_NORMAL内存管理区的可用页框数决定,这可以 通过nr_free_buffer_pages获取。尽管可以通过/proc/sys/vm/min_free_kbytes来修改该它的大小,但是 min_free_kbytes的初始值范围必须是128K, 64M。管理区描述符中的pages_min成员存储了管理区内保留页框的数目。这个字段与pages_low和pages_high字段一起被用在内 存分配和回收算法中。pages_low字段总是被设为pages_min的值的5/4,而pages_high则总是被设为pages_min的值的3 /2。这些值在模块快初始化module_init调用的init_per_zone_pages_min中被设置。 表27.页面分配控制名称大小pages_minmin_free_kbytes (PAGE_SHIFT - 10)pages_lowpages_min * 5 / 4pages_highpages_min * 3 / 2free_area_init_core中对管理区初始化的代码部分如下,后续章节将对该函数进一步分析。 zone-spanned_pages = size;zone-present_pages = realsize;zone-name = zone_namesj;spin_lock_init(&zone-lock);spin_lock_init(&zone-lru_lock);zone_seqlock_init(zone);zone-zone_pgdat = pgdat;zone-prev_priority = DEF_PRIORITY;zone_pcp_init(zone);for_each_lru(l) INIT_LIST_HEAD(&zone-lrul.list);zone-lrul.nr_scan = 0;zone-recent_rotated0 = 0;zone-recent_rotated1 = 0;zone-recent_scanned0 = 0;zone-recent_scanned1 = 0;zap_zone_vm_stats(zone);zone-flags = 0;13.2.page管理项struct page unsigned long flags;/* Atomic flags, some possibly * updated asynchronously */atomic_t _count;/* Usage count, see below. */union atomic_t _mapcount;/* Count of ptes mapped in mms, * to show when page is mapped * & limit reverse map searches. */struct /* SLUB */u16 inuse;u16 objects;每一个物理页框都需要一个对应的page结构来进行管理:记录分配状态,分配和回收,互斥以及同步操作。对该结构成员的解释如下: flag域存放当前页框的页标志,它存储了体系结构无关的状态,专门供Linux内核自身使用。该标志可能的值定义在include/linux/page-flags.h中。 原子计数成员_count则指明了当前页框的引用计数,当该值为0时,就说明它没有被使用,此时在新分配内存时它就可以被使用。内核代码应该通过page_count来访问它,而非直接访问。 原子计数成员_mapcount表示在页表中有多少页指向该页框。在SLUB中它被inuse和objects代替。include/linux/page-flags.henum pageflags PG_locked, /* Page is locked. Dont touch. */ PG_error, PG_referenced, PG_uptodate, PG_dirty, PG_lru, PG_active, . _NR_PAGEFLAGS, .以上是页标志位的可能取值,通常不应该直接使用这些标志位,而应该内核预定义好的宏,它们在相同的头文件中被定义,但是它们是被间接定义的,也即通过#连字符来统一对它们进行定义。 #define TESTPAGEFLAG(uname, lname)static inline int Page#uname(struct page *page) return test_bit(PG_#lname, &page-flags); .TESTPAGEFLAG(Locked, locked)PAGEFLAG(Error, error)PAGEFLAG(Referenced, referenced) TESTCLEARFLAG(Referenced, referenced)表28.页标志宏函数宏扩展函数/宏用途TESTPAGEFLAG(uname, lname)Page#uname测试PG_#lname位SETPAGEFLAG(uname, lname)SetPage#uname设置PG_#lname位CLEARPAGEFLAG(uname, lname)aClearPage#uname清除PG_#lname位TESTSETFLAG(uname, lname)TestSetPage#uname测试并设置PG_#lnameTESTCLEARFLAG(uname, lname)TestClearPage#uname测试并清除PG_#lnamePAGEFLAG(uname, lname)bTESTPAGEFLAGSETPAGEFLAGCLEARPAGEFLAG当于同时扩展了三个宏,也即三个函数PAGEFLAG_FALSE(uname)Page#uname永远返回0TESTSCFLAG(uname, lname)TESTSETFLAGTESTCLEARFLAG当于同时扩展了两个宏,也即两个函数SETPAGEFLAG_NOOP(uname)SetPage#uname空操作CLEARPAGEFLAG_NOOP(uname)ClearPage#unam空操作_CLEARPAGEFLAG_NOOP(uname)_ClearPage#uname空操作TESTCLEARFLAG_FALSE(uname)TestClearPage#uname永远返回0a 以上三个宏分别对应test_bit,set_bit和clear_bit,是原子操作,与它们对应的是有三个开头为下划线的同名函数_SETPAGEFLAG等与它们相对应,但不是原子操作,这里不再列出。b 与此对应也有_PAGEFLAG的宏存在。flags实际上为两部分:标志区(Flags Area)从最低处向上扩展到第_NR_PAGEFLAGS位;字段区(Fields Area)则从最高位向低位扩展。字段区用来实现管理区,内存节点和稀疏内存的映射。 | FIELD | . | FLAGS | N-1 0 (_NR_PAGEFLAGS)_count引用计数不应被直接引用,内核提供了一系列的内联函数来操作它,通常它们被定义在include/linux/mm.h中。 表29.页引用计数函数函数名用途page_count读取引用计数get_page引用计数加1init_page_count初始化引用计数为1_mapcount与_count引用计数类似,不应被直接引用,内核提供了一系列的内联函数来操作它,它们也被定义在include/linux/mm.h中。 表30.页引用计数函数函数名用途reset_page_mapcount初始化引用计数为-1apage_mapcount读取引用计数并加1的值page_mapped该函数根据引用计数值是否大于等于0,判断该页框是否被映射。a 没有初始化为0是因为atomic_inc_and_test和atomic_add_negative的操作,对该引用计数的加减是由这两个函数完成的。union struct unsigned long private;/* Mapping-private opaque data: * usually used for buffer_heads* if PagePrivate set; used for * swp_entry_t if PageSwapCache;* indicates order in the buddy* system if PG_buddy is set. */struct address_space *mapping;/* If low bit clear, points to* inode address_space, or NULL.* If page mapped as anonymous* memory, low bit is set, and* it points to anon_vma object:* see PAGE_MAPPING_ANON below.*/ ;#if USE_SPLIT_PTLOCKS spinlock_t ptl;#endif struct kmem_cache *slab;/* SLUB: Pointer to slab */ struct page *first_page;/* Compound tail pages */;union pgoff_t index;/* Our offset within mapping. */void *freelist;/* SLUB: freelist req. slab lock */;struct list_head lru;/* Pageout list, eg. active_list由于内核引入了很多的分配机制,以往间简单的page结构变得越来越复杂。为了无缝引入slub分配器来分配小于1个页面的内存,这里使用共用体将 slab指针和复合页的首页指针与private和mapping公用存储空间。private的用途与flags标志位息息相关。如果设置了 PG_private,那么它被用于buffer_heads;如果设置了PG_swapcache,那么用于swp_entry_t;如果设置了 PG_buddy,则用于伙伴系统中的阶(Order)。 内核可以将多个相邻的页框合并为复合页(Compound Page)。分组中的第一个也成为首页(Head Page),而所有其余各页叫做尾页(Tail Page)。所有尾页对应的管理page数据结构都将first_page指向首页。 mapping指定了页框所在的地址空间。index是页框在mapping映射内部的偏移量。mapping指针通常是对齐到sizeof(long)的,这保证它的最低位为0,但是它并总是如此。可以有以下两种可能: address_space的实例 当最低位为1时,指向anon_vma的实例,此时完成匿名页的逆向映射。lru是一个表头,用于在各种量表上维护该页框,以便将它按不同类别分组,最重要的就是zone-lru_lock保护的活动页框(active_list)和不活动页框。 #if defined(WANT_PAGE_VIRTUAL)void *virtual;/* Kernel virtual address (NULL if not kmapped, ie. highmem) */#endif /* WANT_PAGE_VIRTUAL */;WANT_PAGE_VIRTUAL是由是否需要高端内存决定的,virtual用于寻址高端内存区域中的页框,存储该页的虚拟地址。有些时候高端内存并不映射到任何实际的物理地址页框上,此时它的值为NULL。 13.3.bootmem_free_node在bootmem_init_node函数中,根据struct meminfo参数来初始化内存节点对应的pg_data_t数据结构contig_page_data,并且申请Bootmem机制使用的 bitmap。从该函数使用的参数来看,一个内存节点对应一个struct meminfo的内存块信息。所以一个内存节点有可能对应多个membank,而这些membank的物理地址可能是不连续的,这就是内存孔洞的存在的原 因。contig_page_data成员中的bdata-node_min_pfn和bdata-node_low_pfn参数分别记 录了所有内存块中的最低物理页框地址和最高物理页框地址。 arch/arm/include/asm/setup.hstruct membank unsigned long start; unsigned long size; int node;struct meminfo int nr_banks; struct membank bankNR_BANKS;static unsigned long _init bootmem_init_node(int node, struct meminfo *mi);bootmem_free_node与bootmem_init_node参数类似,它用来初始化特定内存节点的管理区信息。 arch/arm/mm/init.cstatic void _init bootmem_free_node(int node, struct meminfo *mi); 尽管局部zone_size和zhole_size声明为大小为MAX_NR_ZONES的数组,但是只用到了其中的第一个元素。这是由于ARM Linux采用了UMA方式的内存管理机制。 zone_size0被赋值为end_pfn - start_pfn,然后根据zone_size减去meminfo中每个membank中真正的size得到内存孔洞的大小zhole_size0。 通过arch_adjust_zones为特定架构的系统预留内存。通常用它来为特定的限制的DMA寻址预留内存,将这些DMA无法访问的内存放入zone1,而DMA对应zone0,通常DMA可以寻址所有内存。 最后调用free_area_init_node初始化节点对应的pg_data_t描述符信息,并且为每个页表分配struct page结构。#define arch_adjust_zones(node,size,holes) do while (0)图72.bootmem_free_node调用流程13.4.free_area_init_nodemm/page_alloc.cvoid _paginginit free_area_init_node(int nid, unsigned long *zones_size, unsigned long node_start_pfn, unsigned long *zholes_size)free_area_init_node的函数参数解释如下: nid,节点ID号。 zones_size,大小为MAX_NR_ZONES的数组,用来记录当前内存节点中的内存页框数,包含孔洞。 node_start_pfn,当前内存节点中的起始内存页框。 zholes_size,大小为MAX_NR_ZONES的数组,用来记录当前内存节点中的内存孔洞页框数。free_area_init_node完成以下功能: 根据参数nid,确定该节点对应的pgdat,并初始化成员node_id = nid。 pgdat-node_start_pfn = node_start_pfn。 通过calculate_node_totalpages函数,计算pgdat-node_spanned_pages(包含孔洞)和pgdat-node_present_pages(不含孔洞)。 每一个物理页框对应一个struct page结构,通过alloc_node_mem_map为所有的物理页面分配该结构体空间,并将起始页框地址保存在pgdat-node_mem_map中。 调用free_area_init_core,用来初始化内存管理区zone。13.5.free_area_init_coremm/page_alloc.c/* * Set up the zone data structures: * - mark all pages reserved * - mark all memory queues empty * - clear the memory bitmaps */static void _paginginit free_area_init_core(struct pglist_data *pgdat,unsigned long *zones_size, unsigned long *zholes_size);free_area_init_core的函数参数解释如下: pgdat,内存节点对应的pgdat_t类型描述符。 zones_size,大小为MAX_NR_ZONES的数组,用来记录当前内存节点中的内存页框数,包含孔洞。 zholes_size,大小为MAX_NR_ZONES的数组,用来记录当前内存节点中的内存孔洞页框数。free_area_init_core针对单个内存节点内的所有管理区进行初始化,并计算管理内存页所用的struct page数组占用的memmap_pages。 通过pgdat_resize_init函数初始化pgdat自旋锁成员node_size_lock,它与CONFIG_MEMORY_HOTPLUG(内存热插拔)有关。 初始化pgdat-nr_zones为0。 通过init_waitqueue_head函数初始化pgdat-kswapd_wait,它是kswapd页换出守护进程使用的等待队列。 初始化pgdat-kswapd_max_order为0。 通过pgdat_page_cgroup_init函数初始化pgdat-node_page_cgroup为NULL,如果没有打开CONFIG_CGROUP_MEM_RES_CTLR选项,则为空函数。接下来遍历内存节点中的所有管理区,完成以下工作: 计算含有孔洞的页面总数存入size,同时zone-spanned_pages记录该值。 计算不含孔洞的页面总数存入realsize。 根 据size变量计算页面数组所占用的页面数,存入memmap_pages。之所以不使用realsize,是因为在通过 alloc_node_mem_map函数来分配页面管理数组时采用的含有孔洞的页面数,这是为了管理方便,但是在有大量孔洞的内存节点中,这样会浪费大 量struct page页面管理结构,所以通常会使能内存的CONFIG_DISCONTIGMEM选项。 如果处理管理区是DMA区,那么将在realsize中再次为DMA预留内存。也即realsize再次减去dma_reserve。 将realsize减去页面映射使用的页面大小memmap_pages并存入zone-present_pages。 通过is_highmem_idx判断当前内存区是否为高端内存,如果不是,那么将realsize计入内核全局统计信息nr_kernel_pages,它描述了内核所有可以一一映射的页。 将realsize计入nr_all_pages,与nr_kernel_pages类似,它还记录了高端内存页。 如果定义了CONFIG_NUMA,则初始化管理区中的node,min_unmapped_pages和min_slab_pages成员。 为zone-name赋值,它指向zone_names数组中对应的当前管理区的值 使用spin_lock_init初始化管理区中的lock和lru_lock自旋锁。 如果配置了CONFIG_MEMORY_HOTPLUG,那么初始化自旋锁span_seqlock。与lock和lru_lock不同,它通过seqlock_init函数完成初始化。 设置prev_priority为DEF_PRIORITY。 初始化管理区中的回调指针zone_pgdat,显然它指向该区所属的内存节点类型pgdat指针。 zone_pcp_init初始化管理区的per-CPU缓存。 初始化lru成员。 初始化recent_rotated和recent_scanned成0。 通过函数zap_zone_vm_stats初始化vm_stat成员为0。 初始化flags成员为0。 如果打开了CONFIG_HUGETLB_PAGE_SIZE_VARIABLE选项,则通过pageblock_default_order函数获取默认值并设置给全局变量pageblock_order,否则默认值为MAX_ORDER-1。它被用在伙伴系统中。 setup_usemap设置pageblock_flags为NULL。如果该区包含的页框数满足要求,那么为pageblock_flags分配内存并初始化为0。pageblock_flags与伙伴系统的碎片迁移算法有关。 init_currently_empty_zone初始化伙伴系统的free_area列表。 最后通过memmap_init宏间接引用函数memmap_init_zone将属于该管理区的所有page数组都设置为初始默认值。 zone_start_pfn记录下一循环处理的管理区的开始页框地址。13.6.memmap_init_zonememmap_init_zone函数初始化每个管理区中的页帧对应的page数组。 mm/page_alloc.c#ifndef _HAVE_ARCH_MEMMAP_INIT#define memmap_init(size, nid, zone, start_pfn) memmap_init_zone(size), (nid), (zone), (start_pfn), MEMMAP_EARLY)#endifvoid _meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,unsigned long start_pfn, enum memmap_context context);mem_init_zone通过memmap_init宏实现调用,由于一些特定的架构,系统公共的memmap初始化函数无法满足需求,比如IA64。 此时在特定架构的代码中会定义memmap_init。memmap_init_zone顾名思义,它是针对单个管理区对应的page数组来初始化的。 size指明了管理区的页帧数,它包含孔洞。 nid是当前管理区所属的内存节点的编号。 zone指明了当前管理区在内存节点中node_zones数组下标。 zone_start_pfn则提供了当前管理区的第一个页帧的编号。 context是为了指明当前是在系统初始化阶段,还是热插拔阶段对内存管理页的初始化。它只有两个值:MEMMAP_EARLY和MEMMAP_HOTPLUG。memmap_init_zone依次完成了以下功能: 通过end_pfn = start_pfn + size得到终止页帧,然后从start_pfn到end_pfn通过循环一次处理它们对应的struct page。 如果context指定的系统状态是MEMMAP_EARLY,则需要判断当前页帧是否存在,这是因为内存孔洞的存在。10 根据公式page = pfn_to_page(pfn),由页帧得到它对应的struct page管理项。 设 置page中flags成员的Field Area,它由段区,管理区和节点区三部分组成,分别占用的位数由SECTIONS_WIDTH,ZONES_WIDTH和NODES_WIDTH分别表 示。set_page_links函数的作用就是分别通过set_page_zone,set_page_node和set_page_section函 数来设置这些字段区。以后就可以根据这些区域获取当前页帧的位置信息。 mminit_verify_page_links用来验证set_page_links设置的信息是否正确。 通过init_page_count将page-_count成员初始化为1。 通过reset_page_mapcount将page-_mapcount成员初始化为-1。 通过由宏定义展开后的函数SetPageReserved设置PG_reserved标记到page-flags中。 设置所有页面均为MIGRATE_MOVABLE的。 初始化page-lru。 如果配置了WANT_PAGE_VIRTUAL,且不为高端内存则初始化virtual成员。比如SPARC系统。include/asm-generic/memory_model.h#if defined(CONFIG_FLATMEM)#ifndef ARCH_PFN_OFFSET#define ARCH_PFN_OFFSET (0UL)#endif.#define _pfn_to_page(pfn) (mem_map + (pfn) - ARCH_PFN_OFFSET)#define _page_to_pfn(page) (unsigned long)(page) - mem_map) + ARCH_PFN_OFFSET).#ifdef CONFIG_OUT_OF_LINE_PFN_TO_PAGEstruct page;/* this is useful when inlined pfn_to_page is too big */extern struct page *pfn_to_page(unsigned long pfn);extern unsigned long page_to_pfn(struct page *page);#else#define page_to_pfn _page_to_pfn#define pfn_to_page _pfn_to_page#endif /* CONFIG_OUT_OF_LINE_PFN_TO_PAGE */在struct page管理数组是线性分布的时候,pfn_to_page被统一定义为_pfn_to_page。平坦内存中的ARCH_PFN_OFFSET被定义 为0,而mem_map在a

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论