ARMlinux内核启动分析._第1页
ARMlinux内核启动分析._第2页
已阅读5页,还剩20页未读 继续免费阅读

下载本文档

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

文档简介

1、ARM linux内核启动分析原创 2007-06-11 10:35:46 发表者:jimmy_leehead-armv.S主支分析head-armv.S是解压后(或未压缩的内核最先执行的一个文件,这个文件位于arch/arm/ kernel/head-armv.S,在与这个文件同目录下还有一个文件head-armo.S与head-armv.S很相似,但从arch/arm/下的Makefile中可以看到区别在哪里:ifeq ($(CONFIG_CPU_26,yPROCESSOR := armoifeq ($(CONFIG_ROM_KERNEL,yDATAADDR = 0 x02080000TE

2、XTADDR = 0 x03800000LDSCRIPT = arch/arm/vmli nu x-armo-ro m.l ds.inelseTEXTADDR = 0 x02080000LDSCRIPT = arch/arm/vmli nu x-armo.lds. inen difen dififeq ($(CONFIG_CPU_32,yPROCESSOR = armvTEXTADDR = 0 xC0008000LDSCRIPT = arch/arm/vmli nu x-armv.lds.in en dif arch/arm/kernel/in it_task.o闲话少说,在进入分析head-

3、armv.S之前,交待一下我所分析的内核版本号以及硬件平台,内核是2.4.19-rmk7-pxa2,对应的硬件平台为pxa 270。开篇说到,head-armv.S是进入内核最先执行的文件,为什么呢?内核可执行文件由许多链接 在一起的对象文件组成。对象文件有许多节,如文本、数据、init数据、bass等等。这些对象 文件都是由一个称为 link script 的文件链接并装入的。这个link script的功能是将输入对象文 件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件 中,将该可执行文件的各节装入到指定地址处。vmlinux-armv.lds就是链接内核用

4、到的link script,它位于 arch/arm/目录下,你可能注意至U了同目录下还有一vmlinux-armv.lds.in文件, 这两文件可是有关系的,答案就在arch/arm/Makefile里。ifeq ($(CONFIG_CPU_32,y /*对于pxa 270来说这里是True */PROCESSOR = armvTEXTADDR = 0 xC0008000LDSCRIPT = arch/arm/vmli nux-armv.lds.inen dif$(wildcard in clude/c on fig/arch/*.hecho Ge nerat ing $sed s/TEXT

5、ADDR/$(TEXTADDR/;s/DATAADDR/$(DATAADDR/ $(LDSCRIPT $从 这 个Makefile中 我 们 可 以 看 到 , 实 际 上arch/arm/vmlinux-armv.lds.in就 是arch/arm/vmlinux-armvds是一个蓝本,在make的时候vmlinux-armvds是由sed命令来替换vmlinux-armv.lds.in文件中的TEXTADDR, DATAADDR为特定的值而生成的。接下来就来真正看一下vmlinux-armv.lds里面的内容:OUTPUT_ARCH(armENTRY(stextSECTIONS.=0 x

6、C0008000;.in it : /* Init code and data*/_stext =.;_ini t_begi n =.;*(.text.i nitproc_in fo_begi n =.;96 /*(.proc.i nfo_proc_i nfo_e nd =.;_archn fo_begin =.;*(.arch.i nfo_arch_i nfo_e nd =.;ENTRY(stext,就是说明了最先执行的第一条指令是从stext 开始,而这个stext就是位于head-armv.S当中,它被定义于放置于.text.init section,而且.text.init secti

7、on在vmlinux.lds文件中也 是被放置于输出文件的起始位置。/* arch/arm/kernel/head-armv.S */93 .section .text.init,#alloc,#execinstr94 .type stext, #function95 ENTRY(stext内核入口点97 mov r0, #098 mov r1, #30099 add r1, r1, #4100 */101 mov r12, r0 /保护r0, r0=0, r12=0./*这中间的都是与XScale平台无关的code */186 mov r0, #F_BIT | l_BIT | MODE_SV

8、C make sure svc mode187 msr cpsr_c, r0 and all irqs disabled188 bl _lookup_processor_type189 teq r10, #0 in valid processor?190 moveq r0, #p yes, error p191 beq _error192 bl _lookup_architecture_type193 teq r7, #0 in valid architecture?194 moveq r0, #a yes, error a195 beq _error196 bl _create_page_t

9、ables197 adr lr, _ret return address198 add pc, r10, #12 in itialise processor199 (return control reg在程序注释中有一段对于入口点的说明,说这个入口点一般是在内核自解压缩代码中被调用(关于内核的自解压缩,我将在以后的文章中进行分析),在进入这个入口点前,须满足以下条件:MMU=off,D-Cache=of,l-Cache=doh t care,r0 =0,r仁machine number (seearch/arm/tools/mach-types.h。Line93,定义一个section,名为.

10、text.init,#alloc表示section is allocatable, #execinstr表示section is executable从前面vmlinux-armv.lds文件中我们可以看到,这个text.init section 会放到 0 xC0008000(在arch/arm/Makefile中TEXTADDR指定这个起始位置。Line95,这里的ENTRY其实是一个宏,这个宏位于linux/linkage.h中,在head-armv.S中就包含了这个头文件。/* linux/linkage.h */#defi ne SYMBOL_NAME(X X #ifdef STDC

11、#defi ne SYMBOL_NAME_LABEL(X X#:#else#defi ne SYMBOL_NAME_LABEL(X X/*/:#en dif#ifdef arm#defi ne _ALIGN .alig n 0#defi ne _ALIGN_STR .align 0 #else#en dif#defi ne ALIGN ALIGN#defi ne ALIGN_STR _ALIGN_STRSYMBOL_NAME_LABEL( nameline186187: rO=Ob11O1OO11 ,用于设置当前程序状态寄存器,以禁止 FIQ, IRQ,进入supervisor 模式。Line

12、188 :跳转到_lookup_processor_type,读取运行的 cpu 的 ID 值,判断此 ID 值是否被内核所支持,如果不支持,返回时r10 为 0。Line189191:如果是无效的 processor,则跳转到_error。Line192:跳转到 _lookup_architecture_type,看 r1 寄存器的 architecture number 值是否支持,如果不支持,则返回时r7=0。Bootloader引导linux kernel时,会传递给r1为machine number。Line193195:如果是无效的(不支持的)体系类型(r7=0,则跳转到_erro

13、r。Line196:创建核心页表。Line197:将标号_ret的地址放入 lr 寄存器。_ret 标号处相关源码 如下:/* arch/arm/kernel/head-armv.S */216 .type _ret, %fun ction217 _ret: Idr lr, _switch_data218 mcr p15, 0, rO, c1, c0219 mrc p15, 0, r0, c1, c0, 0 read it back.220 mov r0, r0221 mov r0, r0222 mov pc, lr也就是说,在将来某个时候有可能会调用mov pc, lr 语句(实际上会在 a

14、rch/arm/mm/proc-xscale.S 文件中_xscale_setup 函数的 line942 调用,这在后面会讲到)会跳转到line217 执行 ldrlr, _switch_data。Line198 : r10+12-pc,也即程序跳转到r10+12 的地址处执行,此时的r10 存储的是在执行_lookup_processor_type 函数后得到的当前处理器信息、是一个 proc_info_list 的结构体信息, 对于我们的 pxa270 平台,r10 指向的就是 arch/arm/mm/proc-xscale.S 文件中 line1099 处那个位 置,也就是说从line

15、10991112 的内容就是一个 proc_info_list 的结构体实例。/* arch/arm/mm/proc-xscale.S */1097 .type _bva0_proc_i nfo,#object1098 _bva0_proc_i nfo:1099 .long 0 x69054110 Bulverde A0: 0 x69054110, A1 : 0 x69054111.1100 .long 0 xfffffff0 and this is the CPU id mask.1101 #if CACHE_WRITE_THROUGH1102 .long 0 x00000c0a1103 #

16、else1104 .long 0 x00000c0e1105 #en dif1106 b _xscale_setup1107ong cpu_arch_ name1108ong cpu_elf_ name1109 .longHWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_XSCALE1110 .long cpu_bva0_ info1111ong xscale_processor_fu nctions1112 .size _bva0_proc_i nfo, . - _bva0_proc_ info而 r10+12

17、 就是 line1106 (b _xscale_setup) _xscale_setup 函数是实际的 CPU 的设置子 程序,它主要是操作协处理器,设置页表目录项基地址,对CACHE 和 BUFFER 的控制位进行一些操作(关于_xscale_setup 的源码分析放在后面的分支分析中)。也就是说line198 执行的224 /*结果是跳转到 arch/arm/mm/proc-xscale.S 的 line1106。(关于该行源码建议读者先分析完_lookup_processor_type 函数再看。_xscale_setup 函数返回程序会跳转到line217。Line217 :将_swi

18、tch_data 位置处的值放入 lr (注意这里是指令 Idr,不是把标号_switch_data 的地址放入 lr),将来在 line222 时会跳转到_swith_data 所指向的地址。_swith_data 标号相 关源码如下:/* arch/arm/kernel/head-armv.S */201 .type _switch_data, %object202 _switch_data:ong _mmap_switched203 .long SYMBOL_NAME(_bss_start204 .lo ng SYMBOL_NAME(_e nd205 .long SYMBOL_NAME(

19、processor_id206 .lo ng SYMBOL_NAME(_machi ne_arch_type207 .long SYMBOL_NAME(cr_alig nment208 .long SYMBOL_NAME(i ni t_task_u nion+8192Line222 :因为 line217 将 lr 的值存储为_swith_data 标号处的值,即 _mmap_switched 标号的地 址,则此行执行的结果是跳转到 _mmap_switched 函数(line233 处。而_mmap_switched 函数相关源码如下:/* arch/arm/kernel/head-armv

20、.S */225 * The followi ng fragme nt of code is executed with the MMU on, and uses226 * absolute addresses; this is not positi on in depe ndent.227 *228 * r0 = processor con trol register229 * r1 = machi ne ID230 * r9 = processor ID231 */232 .align 5233 _mmap_switched:234 #ifdef CONFIG_XIP_KERNEL /对于

21、pxa270此处为false,故code略243 #endif244245 adr r3, _switch_data + 4246 ldmia r3, r4, r5, r6, r7, r8, sp r2 = compat247 sp = stack poin ter248248 mov fp, #0 Clear BSS (and zero fp249 1: cmp r4, r5250 strcc fp, r4,#4251 bcc 1b253252 str r9, r6 Save processor ID253 str r1, r7 Save machi ne type254 #ifdef CO

22、NFIG_ALIGNMENT_TRAP255 orr r0, r0, #2 .A.256 #en dif257 bic r2, r0, #2 Clear A bit258 stmia r8, rO, r2 Save con trol register values259 b SYMBOL_NAME(start_kernelLine233261 :这段代码的作用主要是在进入C 函数前先做一些变量的初始化和保存工作。首先清空 BSS 区域,然后保存处理器ID 和机器类型到各自变量地址,接着保存cr_alignment,最后跳转到 init/main.c 中的 start_kernel 函数运行。再

23、来具体分析一下这里面的代码,line245246,它的结果就是 r4 =_bss_start,r5=_end,r6=processor_id,r7=_machine_arch_type,r8=cr_alignment,sp= init_task_union+8192,这些寄存器存储的都是变量的地址。再看一下line254 , r9之前存储的是在 _lookup_processor_type获取的processor id,这里把 processid 放置在 r6 指向的内存,r6 此时由于在 line246 被赋予了 processor_id 变量的指针,所以这 里把 processid 保存在

24、变量 processor_id 中(processor_id 在 arch/arm/kernel/setup.c 中被定义。同样,line255 , r1 之前存储的是在 kernel booting 前由 bootloader 传递过来的 machine number,在这里把 machine number 放置到 r7 所指向的内存, r7 在 line246 被赋予了指向 _machine_arch_type 变量,所以这里把 machine number 保存在变量 _machine_arch_type 中 (_machine_arch_type 同样也定义在 arch/arm/ker

25、nel/setup.c 中)。ARM linux 内核启动分析(2原创 2007-06-11 10:50:25 发表者:jimmy_lee分支 1: _lookup_processor_type函数分析内核中使用了一个结构 struct proc_i nfo_list,用来记录处理器相关的信息,该结构定义在 kernel/include/asm-arm/procinfo.h头文件中。/* arch/arm/ker nel/head-armv.S */* Note! struct processor is always defi ned if were* using MULTI_CPU, oth

26、erwise this entry is unu sed,* but still exists.* NOTE! The following structure is defined by assembly* Ian guage, NOT C code. For more in formatio n, check:* arch/arm/mm/proc-*.S and arch/arm/kernel/head-armv.S*/struct proc_i nfo_list un sig ned int cpu_val;un sig ned int cpu_mask;un sig ned long _

27、cpu_mmu_flags; /* used by head-armv.S */un sig ned long _cpu_flush; /* used by head-armv.S */const char *arch_ name;const char *elf_ name;un sig ned int elf_hwcap;struct proc_i nfo_item *info;struct processor *proc;在 arch/arm/mm/proc-xscale.S 文件中定义了所有和 xscale 有关的 proc_info_list ,我们使用的 pxa270 定义如下:/*

28、 arch/arm/mm/proc-xscale.S */1027 .sect ion .proc.i nfo, #alloc, #execi nstr10281028 .type _80200_proc_i nfo,#object1029 _80200_proc_info: /每一个 _xxx_proc_info 就对应于 proc_info_list1030 .lo ng 0 x69052000 /cpu_val1031 .long 0 xfffffff0 /cpu_mask1032 #if CACHE_WRITE_THROUGH1033 .long 0 x00000c0a /_cpu_m

29、mu_flags1034 #else1035 .lo ng 0 x00000c0e /_cpu_mmu_flags1036 #en dif1037 b _xscale_setup /_cpu_flush1039ong cpu_arch_ name arch_ name1040ong cpu_elf_ name elf_n ame1041 .long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_XSCALE / elf_hwcap1042 .long cpu_80200_i nfo /info1043ong

30、xscale_processor_f unctions /proc1044 .size _80200_proc_i nfo, . - _80200_proc_i nfo104510961097 .type _bva0_proc_i nfo,#object1098 _bva0_proc_info: /pxa270的 proc_info_list1099 .long 0 x69054110 Bulverde A0: 0 x69054110, A1 : 0 x69054111.1100 .long 0 xfffffff0 and this is the CPU id mask.1101 #if CA

31、CHE_WRITE_THROUGH1102 .long 0 x00000c0a1103 #else1104 .long 0 x00000c0e1105 #en dif1106 b _xscale_setup1107ong cpu_arch_ name1108ong cpu_elf_ name1109 .lo ng HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_XSCALE1110 .long cpu_bvaO_info1111ong xscale_processor_f unctions1112 .size

32、_bvaO_proc_i nfo, . - _bvaO_proc_i nfo在上述 code 中,由于定义了 section,因此_bvaO_proc_info 等在编译时会放入. section 中。 根据 vmlinux-armv.lds , _arch nfo_begin 与 _arch_info_end就分别指向. section的起始和结束,换句话说,在_arrchnfo_begin 与_archnfo_end 所指向的区域,包含着若干个 proc_info_list结构的数据。这个会在 _lookup_processor_type 中用到,下面来

33、看一下 _lookup_processor_type 这个分支代码。/* arch/arm/ker nel/head-armv.S */477 _lookup_processor_type:478 adr r5, 2f / 把标号 2 (行 498)的址址放入 r5 寄存器中479 ldmia r5, r7, r9, r10 /r7 = _proc_i nfo_end/ r9 = _proc_i nfo_begi n/ r10 = 标号 2 (行 498)的地址480 sub r5, r5, r10 convert addresses481 add r7, r7, r5 to our addr

34、ess space482 add r10, r9, r5483 mrc p15, 0, r9, c0, c0 get processor id484 1: ldmia r10, r5, r6, r8 value, mask, mmuflags485 and r6, r6, r9 mask wan ted bits486 teq r5, r6487 moveq pc, Ir488 add r10, r10, #36 sizeof(proc_i nfo_list489 cmp r10, r7490 blt 1b491 mov r10, #0 unknown processor492 mov pc,

35、 lr493493 /*494 * Look in in clude/asm-arm/proc info.h and arch/arm/kernel/arch.ch for495 * more in formatio n about the _proc_i nfo and _archnfo structures.496 */497 2:ong _proc_i nfo_end499ong _proc_i nfo_beg in500 .long 2b501ong _archn fo_begi n502ong _archnfo_endLine478 :将 line498 的地址放到寄存器 r5 中,

36、adr 是小范围的地址读取伪指令。Line479 :将 r5 所指向的数据区的数据读出到 r7,r9,r10。结果就是 r7=_proc_info_end ,r9 = _proc_info_begin,r10 = 标号 2( line498 )的地址。一一_ _Line480482 : r10 = _proc_info_beginoLine483 : mrc 是一个协处理器寄存器到 ARM 寄存器的数据传送指令,它的指令格 式是:MRC coproc, opcodel, Rd, CRn , opcode2对于读取 Processor ID 来说,coproc 为 15, opcode1 为 0

37、, CRn=0, opcode2=0。Line483 读取 processor ID,并放入 r9 寄存器中。Line484490,是一个循环处理过程,由于 r10 一开始是指向_proc_info_begin 的,前面提到_proc_info_begin 指向. section的首地址,所以整个循环就是遍历. section,也就是遍历 proc_infoist结构数组,比较(proc_i nfo_listi-cpu_val = processor ID & proc_i nfo_listi-cpu_mask 是否与当前运行的 processor

38、ID 一致,如果有条件满足的则函数在 line487返回(跳转到 line189,此时 r10 是不为零的(因为函数返回后,要根据 r10 是否为 0,来判断 processor 未知与否);如果遍历完整个. section 都不满足的,则进入line491。2 ARM Linux 启动分析-head-armv.S (上)作者:谷丰,您可以通过emailg u f e n g 7 7 1 2 6. c o m/email 和他联系,转载 请包含以上内容Linux 启动后执行的第一个文件是 arch/arm/kernel 下的 head-($PROCESSOR.S 文件proc

39、esso 代表的 是该 cpu 的类型。ARM 6 及其以后的处理器核心支持 32 位地 址空间。这些处理器可以在 26 位和 32 位 PC 的模式下操作。在 26 位 PC 模式下, R15 寄存器的表现如同在以前的处理器上, 代码只能运行在地址空间的最低的 64M 字节空间中。在 32 位 PC 模式下,32 位的 R15 寄存器被用做程序计数器。使 用独立的状态寄存器来存储处理器模式和状态标志。对于26 位的 arm 处理器类型,linux 用 armo 来表示;对于 32 位的 arm 处理器,使用 armv 表示。在include/linux/autoconf.h 文件中通过#de

40、fi ne CONFIG_CPU_32 1将处理器类型设置为支持 32 位 PC 模式。然后在 arch/arm/Makefile 中通过ifeq ($(CONFIG_CPU_32,yPROCESSOR = armvTEXTADDR = 0 xC0008000LDSCRIPT = arch/arm/vmli nux-armv.lds.i nen dif设置处理器类型为 armv,这样 linux 运行所执行的第一个文件就是 head-armv.S 接着,Makefile 定义了内核中代码和数据所使用的虚拟地址 TEXRADDR,最后, 定义了链接器所使用的脚本文件,这个文件也是与处理器类型相关

41、的。在执行 head-armv.S 文件之前,有一点需要注意的是,bootloader 已经在处理器的 R1寄存器中存放了机器体系结构的类型号。由于在文件的执行过程中将要针对当前的机器体系结构设置相关的参数,如果没有这个步骤,系统将显示“ ERROR”同时停止执行。当然,也可以在 head -arm v.S 文件的开头添加代码,手工对 R1 赋 值,具体的机器类型号在 arch/arm/tools/mach-types 文件中。好了,接下来我们可以开始阅读 head-armv.S 文件了,看看它到底作了些什么事 情。由于篇幅的限制,对一些不是很关键的代码和英文注释予以省略,但是在每段 代码后,

42、我会根据自己的理解给出解释。#if (TEXTADDR & Oxffff != 0 x8000#error TEXTADDR must start at 0 xXXXX8000#en dif.globl SYMBOL_NAME(swapper_pg_dir.equ SYMBOL_NAME(sw apper_pg_dir, TEXTADDR - 0 x4000.macro pgtbl, reg, rambaseadr reg, stextsub reg, reg, #0 x4000.endm.macro krnl adr, rd, pgtable, rambasebic rd, pgta

43、ble, #0 x000ff000.endm首先, 系统确保 TEXTADDR 的地址是以 0 x8000 结尾的, 前面已经提到过,TEXTADDR的地址是 0 xC0008000,是内核所使用的 虚拟地址,而我所使用的PXA255 处理器上支持的 SDRAM 空间是从 0 xA0000000 开始的,这就需要通过 MMU进行虚拟地址到实际物理地址的转换,也就是说将 0 xC0008000 映射到 0 xA0008000。地址转换所使用的页表将存放在从 0 xA0008000 网上的 16K 空间 中,即从 0 xA0004000到 0 xA0008000 这一段。因此,系统必须空出 0 x

44、A0000000 到 0 xA0008000 这一段,存放页表和其它的一些内核将 使用到的数据结构。虽然上 面的代码判断的是 TEXTADDR的地址是否以 0 x8000 结尾,但从效果上说是一样 接着,代码定义了全局变量swapper_pg_dir,它是页表目录项的虚拟地址。前面用 SYMBOL_NAME(修饰,这是因为在有的系 统中,C 编绎器对.C 文件中的符号名 有_前缀,SYMBOL_NAME(可以使汇编代码也适应这种变化。但是在当前的 Linux 中,SYMBOL_NAME 实际上不起任何作用。大家可以参考include/linux/linkage.h 中对该修饰符的定义。然后,代

45、码定义了 pgtbl 和 krnladr 两个宏。Pgtbl 宏得到的是与位置无关的页表目 录项地址,值为 0 xA000800 往上 16k 的地址,即 0 xA0004000。stext 所代表的也是 内核的起始地址,通过 arch/arm/vmlinux-armv.lds.in 的链接脚本可以发现它在内核 中的链接地址和 TEXTADDR 致。 那么为什么页表地址不是 0 xC0004000 呢?因 为我们在定义/reg寄存器时使用的 adr 指令,adr 指令是在当前的 PC 值上+/-个标 号的偏移得到的,所以得到的地址只跟 PC 和标号到 PC 的偏移相关,跟编译地址无 关。在 M

46、MU 打开前,代码要是地 址无关的,会经常用到 adr 指令。由于当前的 PC 运行的地址是从 0 xA0008000开始的地址空间,所以最后得到的页表地址为 0 xA0004000。krnladr 宏需要配合其它代码使用,它的本意是为了使对从 0 xA0000000 开始的内核的地址空间的寻址不会因为MMU 的原因而被映射到其它的地址。因此需要将定义 0 xA0000000 地址转换的页表项中的值的高 20 位定义为0 xA0000,最低的 12 位保存的是页表的标志位。由于该页表项的索引 值是由地址 的最高 12 位所决定的,因此 krnladr 宏将地址的最低 20 位清零。在本文件中,只 清空了第4-11 位,是因为有其它的代码屏蔽了低 12 位的作用。如果将上面的代 码改成 bic rd,pgtable, #0 x000fffff,效果是一样的。.secti on .text.i nit,#alloc,#execi nstr.type s

温馨提示

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

评论

0/150

提交评论