




已阅读5页,还剩44页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第4章 TMS320C6000系列DSP程序开发,TMS320C6000系列DSP的C/C+语言特点,TMS320C6000系列DSP的C/C+语言关键字,pragma伪指令,初始化静态变量和全局变量,TMS320C6000系列DSP的C/C+代码优化,C/C+语言和汇编语言的混合编程,4.1 TMS320C6000系列DSP的C/C+语言特点,4.1.1 TMS320C6000系列DSP的C语言特点 1标识符和常量 标识符的所有字符都是有意义的并且区分大小写,此特征适用于内部和外部的所有标识符; 源(主机)和执行(目标)字符集为ASCII码,不存在多字节字符; 字符常量或者字符串常量中的十六进制或者八进制转义序列或者字符串常量具有高达32位的值; 具有多个字符的字符常量按序列中的最后一个字符编码,例如:abc=c。,4.1.1 TMS320C6000系列DSP的C语言特点,2数据类型 表4-1列出了TMS320C6000编译器中各种标量数据类型、位数表示方式及取值范围,许多取值范围的值可以作为头文件limits.h中的标准宏使用。,4.1.1 TMS320C6000系列DSP的C语言特点,3数据转换 (1)浮点类型到整型的转换,截取0前面的整数部分。 (2)指针类型和整数类型之间可以自由转换。 4表达式 (1)当两个带符号的整数相除时,如果其中有一个为负,则商为负,余数的符号与分子的符号相同。斜杠(/)用来求商,百分号(%)用来求余数。 例如: 10/-3=-3,-10/3=-3 10%-3=1,-10%3=-1 (2)有符号数的右移为算术移位,即保留符号。,4.1.1 TMS320C6000系列DSP的C语言特点,5声明 (1)寄存器存储类对所有的charsshortinteger和pointer类型有效。 (2)结构体成员被打包为字。 (3)整数类型的位段带有符号,位段被打包为从高位开始的字,并且不能超越字的边界。 (4)中断关键字interrupt只能用于没有参数的void型函数。 6预处理器 预处理器忽略任何不支持的#pragma伪指令。,4.1.2 TMS320C6000系列DSP的C+语言特点,TMS320C6000系列DSP编译器支持ISO标准的C+语言,但与标准的C+又存在不同的特点: (1)并不包括完整的C+标准库支持,但是包括C子集和基本的语言支持。 (2)支持C的库工具(C library facilities)的头文件不包括: ,。 (3)所包括的C+标准库头文件为和。 (4)对bad_cast和bad_type_id的支持并不包括在typeinfo文件中。 (5)不支持异常事件的处理。 (6)默认情况下,禁止运行时类型的信息(RTTI)。RTTI允许在运行时确定各种类型的对象。它可以使用-rtti编译选项来使能。 (7)如果两个类不相关,reinterpret_cast类型指向其中一个类成员的指针,不允许这个指针再指向另一个类的成员。 (8)不支持标准中tesp.res和temp.dep里描述的“在模板中绑定的二相名”。 (9)不能实现模板参数。 (10)不能实现模板的export关键字。 (11)用typedef定义的函数类型不包括成员函数cv-qualifiers。 (12)类成员模板的部分说明不能放在类定义的外部。,4.2 TMS320C6000系列DSP的C/C+语言关键字,1const关键字 (1)如果在一个对象定义的同时也指定了关键字volatile(如: volatile const int x),volatile关键字被分配到RAM(程序不会修改一个const volatile 的对象,但是程序外部的对象可能会被修改); (2)对象是auto存储类型(在堆栈中分配)。 在以上的两种情况下,为对象分配存储空间与不使用const关键字时是相同的。 在一个定义中使用const关键字很重要,例如,下面代码的第一句定义了常量指针p为一个整型的变量,第二句定义了一个变量指针q为一个整型常量: int *const p &x; const int*q &x; 使用const关键字,用户可以定义大常量表并将他们分配到系统ROM中。例如,分配一个ROM表,可以使用如下的定义: far const int digits = 0,1,2,3,4,5,6,7,8,9;,4.2 TMS320C6000系列DSP的C/C+语言关键字,2cregister关键字 当对一个对象使用cregister关键字时,编译器将比较对象名和TMS320C6000的标准控制寄存器列表,如果名字匹配,编译器将参照控制寄存器产生相应的代码。如果不匹配,编译器将产生一个错误。控制寄存器列表见表4-2。,4.2 TMS320C6000系列DSP的C/C+语言关键字,一旦声明该寄存器,用户就能够直接使用该寄存器名。下例为控制寄存器的声明和使用: 【例4.1】 定义和使用控制寄存器。 extern cregister volatile unsigned int AMR; extern cregister volatile unsigned int CSR; extern cregister volatile unsigned int IFR; extern cregister volatile unsigned int ISR; extern cregister volatile unsigned int ICR; extern cregister volatile unsigned int IER; extern cregister volatile unsigned int FADCR; extern cregister volatile unsigned int FAUCR; extern cregister volatile unsigned int FMCR; main() printf(”AMR = %xn”, AMR); ,4.2 TMS320C6000系列DSP的C/C+语言关键字,3interrupt关键字 当用户将interrupt关键字使用到函数的定义上时,编译器会按照中断函数要求的寄存器保存规则和中断返回的特殊顺序去保存寄存器,然后生成特殊的返回代码序列。用户可以将interrupt关键字和定义为void但没有参数的函数一起使用。中断函数体可以具有局部变量和自由的使用堆栈或者全局变量。如: interrupt void int_handler() unsighed int flags; ,4.2 TMS320C6000系列DSP的C/C+语言关键字,4near和far关键字 语法上,near和far关键字被看做存储类别的变址数。它们出现在存储类别说明符和类型的前、后和中间。这两个存储器类别的变址数不能用于一个定义中。正确的使用实例代码如下: far static int x; static near int x; static int far x; far int foo(); static far int foo();,4.2 TMS320C6000系列DSP的C/C+语言关键字,5restrict关键字 【例4.2】 对指针使用关键字restrict。 void func1(int* restrict a,int *restrict b) /*此处为函数func1()的代码*/ 该例代码中关键字restrict的使用告诉编译器func1中的指针a和b指向的存储器范围不会交迭,即指针变量a和b对存储器的访问不会冲突,对一个指针变量的写操作不会影响另一个指针变量的读操作。 【例4.3】 对数组使用关键字restrict。 void func2(int crestrict, int drestrict) int i ; for (i = 0 ;i 64;i +) /计算数组的累加和以及数组di的加1操作 ci += di; di += 1; ,4.2 TMS320C6000系列DSP的C/C+语言关键字,6volatile关键字 优化器分析数据流,尽可能地避免存储器的访问。如果用户将依赖于存储器访问的代码写在C/C+程序中,则必须使用volatile关键字以识别这种访问。编译器不会优化任何对volatile变量的引用。 下面的代码中,循环等待一个读为oxFF的单元: unsigned int *ctrl; while(*ctrl!=oxFF); 该代码中,*ctrl是一个循环不变的表达式,因此该循环被优化为一个单存储器读。为了改正这些优化,可以定义*ctrl为: volatile unsigned int * ctrl,4.2 TMS320C6000系列DSP的C/C+语言关键字,7asm语句 TMS320C6000的C/C+编译器可以将TMS320C6000汇编指令或者伪指令直接嵌入编译器输出的汇编语言文件。该功能是对C/C+语言的扩展,即asm语句。asm语句提供了C/C+语言所不能提供的对硬件的访问。asm语句类似于调用一个名为asm的函数,该语句以一个字符串常数为参数,具体语法格式: asm(“assembler text”); 编译器将参数直接复制到编译器的输出文件,汇编正文必须包含在双引号内。所有通常的字符串都保持它们原来的定义。例如,可插入一个包含引号的.string伪指令: asm(“str: .string”abc“”);,4.3 pragma伪指令,pragma伪指令告诉编译器如何处理特定的函数、对象或者代码段。TMS320C6000的C/C+编译器支持下面的伪指令: CODE_SECTION DATA_ALIGN DATA_MEM_BANK DATA_SECTION FUNC_CANNOT_INLINE FUNC_EXT_CALLED FUNC_INTERRUPT_THRESHOLD FUNC_IS_PURE FUNC_IS_SYSTEM FUNC_NEVER_RETURNS FUNC_NO_GLOBAL_ASG FUNC_NO_IND_ASG INTERRUPT MUST_ITERATE NMI_INTERRUPT PROB_ITERATE STRUCT_ALIGN UNROLL,4.3 pragma伪指令,1CODE_SECTION指令 CODE_SECTION指令用于为命名段中的符号指定空间。 该指令在C语言中的语法格式为: #pragma CODE_SECTION (symbol, ”section name”); 该指令在C+语言中的语法格式为: #pragma CODE_SECTION (”section name”); 【例4.4】 CODE_SECTION指令使用。 C源文件: #pragma CODE_SECTION(fn, ”my_sect”) int fn(int x) return x; ,4.3 pragma伪指令,此例使用#pragma CODE_SECTION(fn,“my_sect“),产生my_sect段,并把fn函数指定到my_sect段。 汇编源文件: .sect ”my_sect” .global _fn ;* ;* FUNCTION NAME: _fn * ;* * ;* Regs Modified : SP * ;* Regs Used : A4,B3,SP * ;* Local Frame Size : 0 Args + 4 Auto + 0 Save = 4 byte * ;* _fn: ;* RET .S2 B3 ; |6| SUB .D2 SP,8,SP ; |4| STW .D2T1 A4,*+SP(4) ; |4| ADD .S2 8,SP,SP ; |6| NOP 2 ; BRANCH OCCURS ; |6|,4.3 pragma伪指令,2DATA_SECTION指令 DATA_SECTION指令为命名的段中符号指定空间。 该指令在C语言中的语法格式为: #pragma DATA_SECTION (symbol, “section name”); 该指令在C+语言中的语法格式为: #pragma DATA_SECTION (“section name”);,4.3 pragma伪指令,【例4.5】 DATA_SECTION指令的使用。 C源文件: #pragma DATA_SECTION(bufferB, ”my_sect”) char bufferA512; char bufferB512; C+源文件: char bufferA512; #pragma DATA_SECTION(”my_sect”) char bufferB512; 汇编源文件: .global _bufferA .bss _bufferA,512,4 .global _bufferB _bufferB: .usect ”my_sect”,512,4,4.3 pragma伪指令,3DATA_ALIGN指令 DATA_ALIGN指令把符号对齐到边界。对齐的边界是符号默认的最大界值或常量,常量是2的整数次幂。 该指令在C语言中的语法如下: #pragma DATA_ALIGN (symbol, constant); 该指令在C+中的语法如下: #pragma DATA_ALIGN (constant); 4FUNC_CANNOT_INLINE指令 FUNC_CANNOT_INLINE指令通知编译器,该命名的函数不能扩展为直接插入。任何使用pragma命令的函数会忽略由其他方式指定的直接插入。 该指令必须出现在对函数的任何声明和引用之前。 该指令在C语言中的语法格式为: #pragma FUNC_CANNOT_INLINE (func); 该指令在C+语言中的语法格式为: #pragma FUNC_CANNOT_INLINE;,4.3 pragma伪指令,5FUNC_EXT_CALLED指令 FUNC_EXT_CALLED指令指定优化器保持这些C/C+函数或任何由这些C/C+函数调用的函数。这些函数充当C/C+的入口点。该指令必须出现在对函数的任何声明和引用之前。 在C中,FUNC_EXT_CALLED指令的语法格式如下: #pragma FUNC_EXT_CALLED (func); 在C+中,FUNC_EXT_CALLED指令的语法格式如下: #pragma FUNC_EXT_CALLED;,4.3 pragma伪指令,6FUNC_IS_PURE指令 FUNC_IS_PURE指令通知优化器,该指令命名的函数没有负面效果,允许优化做以下的工作: (1)如果函数的值不需要的话,删除对函数的调用。 (2)删除重复的函数。 该指令必须出现在对函数的任何声明和应用之前。 在C中,该指令的语法格式为: #pragma FUNC_IS_PURE (func); 在C+中,该指令的语法格式为: #pragma FUNC_IS_PURE;,4.3 pragma伪指令,7FUNC_IS_SYSTEM指令 例如,它可以对多函数所使用的寄存器做出假定。不能在已经修改过的ISO函数中使用该指令。该指令必须出现在对函数的任何声明和引用之前。 在C语言中,该指令的语法格式为: #pragma FUNC_IS_SYSTEM (func); 在C+语言中,该指令的语法格式为: #pragma FUNC_IS_SYSTEM;,4.3 pragma伪指令,8FUNC_NEVER_RETURNS指令 FUNC_NEVER_RETURNS指令通知优化器,在所有的情况下,函数不会返回到它的调用处。例如,一个无限循环的函数调用exit(),将不会返回到调用处。当一个函数被该指令标记后,编译器不会产生一个函数的结束。该指令必须出现在对函数的任何声明和引用之前。 在C语言中,该指令的语法格式为: #pragma FUNC_NEVER_RETURNS (func); 在C+语言中,该指令的语法格式为: #pragma FUNC_NEVER_RETURNS;,4.3 pragma伪指令,9FUNC_NO_GLOBAL_ASG指令 FUNC_NO_GLOBAL_ASG指令通知优化器,该函数不会给已经定义的全局变量赋值并且不会包含任何asm语句。该指令必须出现在对函数的任何声明和引用之前。 在C中该指令的语法格式如下: #pragma FUNC_NO_GLOBAL_ASG (func); 在C中该指令的语法格式如下: #pragma FUNC_NO_GLOBAL_ASG;,4.3 pragma伪指令,10FUNC_NO_IND_ASG指令 FUNC_NO_IND_ASG指令通知优化器函数不会通过指针进行赋值且不包含任何asm语句。该指令必须出现在对函数的任何声明和引用之前。 在C语言中,该指令的语法格式如下: #pragma FUNC_NO_IND_ASG (func); 在C+语言中,该指令的语法格式如下: #pragma FUNC_NO_IND_ASG;,4.3 pragma伪指令,11INTERRUPT指令 INTERRUPT指令允许用户直接在C代码中处理中断。 在C语言中,该指令的语法格式如下: #pragma INTERRUPT (func); 在C+语言中,该指令的语法格式如下: #pragma INTERRUPT;,4.4 初始化静态变量和全局变量,如果加载器不预初始化变量,则可以使用连接器在目标文件中将变量预初始化为0。例如,在链接命令文件中,在.bss段中填充0值,代码如下: SECTIONS .bss: fill = 0x00; ,4.4 初始化静态变量和全局变量,带有常数类型限定词const的静态和全局变量的处理方法与其他类型的静态变量和全局变量不同。没有明确初始化const的静态和全局变量与其他静态和全局变量是类似的,因为它们没有被预初始化为0,例如: const int zero; /*不一定初始化为0*/ 然而,由于常量是在名为.const的段中进行声明和初始化的,因此常数、全局和静态变量的初始化是不同的,例如: const int zero = 0; /*保证初始化为0*/ 对应于.const段的入口: .sect .const _zero .word 0,4.5 TMS320C6000系列DSP的C/C+代码优化,4.5.1 C/C+代码的编写 1数据类型 当编写C代码时,需要对数据类型的尺寸仔细的考虑。TMS320C6000编译器的每种数据类型尺寸如下(包括有符号和无符号类型): (1)char(字符型):bit (2)short(短整型):16bit (3)int(整型):32bit (4)long(长整型):40bit (5)float(浮点型):32bit (6)double(双精度型):64bit,4.5.1 C/C+代码的编写,基于每种数据类型的尺寸,在编写C代码时应遵循以下的规则: (1)避免在代码中将int和long类型作为相同的尺寸来处理,因为TMS320C6000编译器对long类型的数据使用40位操作。 (2)对于定点乘法输入,应尽可能使用short类型的数据,因为该数据类型为TMS320C6000的16位乘法器提供最有效的使用。 (3)对循环计数器使用int或者unsigned int数据类型,而不使用short或者unsigned short类型,避免不必要的符号扩展指令。,4.5.1 C/C+代码的编写,2分析C代码的性能 使用以下手段可以分析特定代码段的性能: (1)代码性能的主要衡量方法之一是代码运行所占用的时间。使用C语言中clock()和printf()函数具有计时和显示特定代码的功能,为了达到这一目的,利用独立的软件模拟器运行这段代码。 (2)利用动态调试器(debugger)中的profile模式,可以得到一个关于代码中特定代码段执行情况的统计表。 (3)使用动态调试器中的中断clk寄存器和RUNB命令可以跟踪特定代码段所占用的CPU时钟周期数。 (4)在代码中影响性能的主要代码段通常是循环。优化一个循环,最容易的方法是抽出此循环,使之成为一个单独的可重新编写编译和运行的文件。,4.5.2 编译C/C+代码,编译工具包括一个外壳程序(cl6x),用于编译汇编优化汇编和程序连接。要激活编译外壳程序,输入如下: cl6x options filenames z linker options object files (1)如果编译器不能确定两条指令是否独立,假设它们相关并且顺序安排这两条指令。 (2)如果编译器能确定两条指令是独立的,将安排它们并行执行。 通常编译器很难确定访问存储器的指令是否独立,以下的方法可以帮助编译器确定指令是否独立。 (3)使用关键字const标识变量的存储单元不会被函数改变。const表示一个变量或者变量的存储单元保持不变。尽可能使用const是编写代码的较好方法,因为它使用简单且可以提高代码的性能。 (4)联合使用-pm选项和-o3选项可确定程序级优化,所有的源文件都被编译成称为模块的中间文件。由于编译器访问到整个程序,因此它可以执行几个在文件级优化中很少用的优化手段。 (5)使用-mt选项是向编译器说明,在代码中不存在存储器相关性,即允许编译器在无存储器相关性的假设下改进优化。,4.5.2 编译C/C+代码,下面通过举例说明存储器相关性的概念。例4.6给出了基本矢量和C代码,图4-1给出了其相关图。,4.5.3 优化C代码,1使用内嵌函数 TMS320C6000提供的内嵌函数是一种直接映射为内嵌TMS320C6000汇编指令的特殊函数,可以快速优化C/C+代码。内嵌函数用下划线(_)开头,使用方法同调用普通函数一样。 【例4.7】 没有内嵌函数的饱和加法。 int sadd(int a, int b) int result; result = a + b; if (a b) ,4.5.3 优化C代码,2软件流水 软件用于安排循环指令,使循环的多次迭代以并行方式执行。当使用编译器的-o2和-o3选项时,编译器使用软件流水优化源代码并且从程序中收集相关的优化信息。图4-2为循环的软件流水示意图。,4.5.3 优化C代码,(1)循环计数 下例分别为优化前后的代码: 原始代码: for (i = 0; i N; i+) /* i = 循环计数变量, N = 循环次数 */ 优化后代码: for (i = N; i != 0; i) /* 逆序计数 */ (2)消除冗余循环 有时编译器不能够确定循环是否总是执行大于最小循环次数,因此,编译器将会产出两种版本的循环: 如果循环计数值小于最小循环次数,则执行非软件流水循环版本; 如果循环计数值等于或大于最小循环次数,则执行软件流水的版本。 激活编译器时可以使用以下的选项将循环次数传给编译器: 用-o3和-pm选项允许优化器访问整个程序或者部分,了解循环次数信息; 使用-nassert内核防止冗余循环的产生,或者允许编译器(使用或者未使用-ms选项)软件流水最内层循环以减少代码量。,4.5.3 优化C代码,(3)循环展开 有3种循环展开的方式: 编译器可以自动进行循环展开; 可以通过UNROLL伪指令建议编译器进行循环展开; 可以自己循环展开C/C+代码。 【例4.9】 三个存储器操作的矢量加运算。 void vecsum2(short *restrict sum, const short *restrict in1, const short *restrict in2, unsigned int N) int i; for (i = 0; i N; i+) sumi = in1i + in2i; /循环计算in1i 与 in2i的矢量和 ,4.5.3 优化C代码,【例4.10】 字对齐矢量和。 void vecsum4(short *restrict sum, const short *restrict in1, const short *restrict in2, unsigned int N) int i; #pragma MUST_ITERATE (10); for (i = 0; i (N/2); i+) _amem4( ,4.5.3 优化C代码,(4)投机执行(-mh选项) 在循环嵌套中,只对内层的循环进行软件流水。软件流水的限制如下: 软件流水可以包含内核函数,不能包含函数的调用; 循环中不能有条件中断; 循环中不能有递增循环计数器; 循环体内不能修改循环计数器,因为在循环中修改循环计数器不能将其转换为递减计数的循环; 循环代码量不能过大,因为代码量过大,需要的寄存器超过TMS320C6000的规定个数不能进行软件流水; 寄存器的值生命周期不能太长,否则不能进行软件流水; 循环中不能有复杂的条件代码,如果过于复杂则循环不能进行软件流水。,4.6 C/C+语言和汇编语言的混合编程,4.6.1 在C/C+代码中调用汇编语言模块 C/C+代码可以访问定义在汇编语言中的变量和调用函数,并且汇编代码可以访问C/C+的变量和调用C/C+的函数。汇编语言和C/C+语言接口需遵循如下的规则: 所有的函数,无论是使用C/C+语言编写还是汇编语言编写,都必须遵循寄存器的规定。 必须保存寄存器A10A15、B3和B10B15,同时还要保存A3。如果使用常规的堆栈,则不需要明确保存堆栈。换句话说,只要任何被压入堆栈的值在函数返回之前被弹回,汇编函数就可以自由使用堆栈。任何其他寄存器都可以自由使用而无需首先保存它们。 中断程序必须保存它们使用的所有寄存器。 当从汇编语言中调用一个C/C+函数时,第一个参数必须保存到指定的寄存器,其他的参数置于堆栈中。记住,只有A10A15和B10B15被编译器保存。C/C+函数能修改任何其他寄存器的内容。,4.6.1 在C/C+代码中调用汇编语言模块,函数必须根据C/C+的声明返回正确的值。整型和32位的浮点值返回到A4中。双精度、长双精度、长整型返回到A5:A4中。结构体的返回是将它们复制到A3的地址。 除了全局变量的自动初始化外,汇编模块不能使用.cinit段。在C/C+启动程序假定.cinit段完全由初始化表组成。将其他的信息放入.cinit中将破坏表,并产生不可预料的结果。 编译器将连接名分配到所有的扩展对象。因此,当编写汇编代码时,必须使用编译器分配的相同的连接名。 任何在汇编语言中定义的在C/C+语言中访问或者调用的对象或者函数,都必须以.def或者.global伪指令声明。这样可以将符号定义为外部符号并允许连接器对它识别引用。,4.6.1 在C/C+代码中调用汇编语言模块,【例4.11】 C/C+语言调用汇编函数。 C程序: extern ”C” extern int asmfunc(int a); /* 声明外部函数*/ int gvar = 4; /* 定义全局变量 */ void main()
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 城市配送公司兼职货运司机服务协议
- 《变迁:家乡的足迹》课件-(获奖课件)
- 医学临床试验质量控制合作协议
- 网络游戏IP改编影视作品版权授权合同
- 数据中心绿色环保建设与节能改造合同
- 企业生产安全培训课件
- 《医疗人文关怀》课件
- 《内分泌系统》课件
- 《慢性疼痛与管理》课件
- 《医疗急救知识》课件
- 2025超市承包经营合同
- 遗产委托协议书范本
- (四检)厦门市2025届高三毕业班第四次质量检测英语试卷(含答案)
- 商务场景2025年商务英语考试试题及答案
- 人人讲安全个个会应急全国防灾减灾日主题宣教课件
- 叉车介绍课件
- 2024年Adobe设计师考试网页设计重要性试题及答案
- 《激光切割技术》课件
- 2025届深圳市高三二模英语试题(含答案)
- 2025年有限空间作业安全防护措施测试题库试题
- 北京市昌平区2023-2024学年六年级下学期语文期末毕业考试试卷(含答案)
评论
0/150
提交评论