《嵌入式实时操作系统实验报告(共24页).doc》由会员分享,可在线阅读,更多相关《嵌入式实时操作系统实验报告(共24页).doc(24页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、精选优质文档-倾情为你奉上嵌入式操作系统实验报告系别: 班级: 学号: 姓名: 2013.12实验一 嵌入式开发环境的建立一、实验目的通过此实验系统,读者可以了解嵌入式实时操作系统 uC/OS-II 的内核机制和运行原理。本实验系统展示了 uC/OS-II 各方面的管理功能,包括信号量、队列、内存、时钟等。在各个实验中具体介绍了 uC/OS-II 的相关函数。读者在做实验的同时能够结合理论知识加以分析,了解各个函数的作用和嵌入式应用程序的设计方法,最终对整个 uC/OS-II 和嵌入式操作系统的应用有较为清楚的认识。二、实验步骤1. 安装集成开发环境LambdaEDU集成开发环境LambdaE
2、DU 的安装文件夹为 LambdaEDU ,其中有一个名为“Setup.exe”的文件,直接双击该文件便可启动安装过程。具体的安装指导请看“LambdaEDU 安装手册.doc”文件。当 LambdaEDU 安装完毕之后,我们看到的是一个空的界面,现在就开始一步一步地将我们的实验项目建立并运行起来。2. 建立项目为了我们的实验运行起来,需要建立1 个项目基于x86 虚拟机的标准应用项目。通过点击“文件”、“新建”、“项目”开始根据向导创建一个项目。在随后出现的对话框中选择“Tool/标准应用项目”,点击下一步,开始创建一个标准的可执行的应用程序项目。在随后出现的对话框中填入项目名称“ucos_
3、x86_demo”。点击“下一步”。选择“pc386 uC/OS-II 应用(x86)”作为该项目的应用框架。点击“下一步”选择“pc386_elf_tra_debug”作为该项目的基本配置。点击“完成”。新创建的项目“ucos_x86_demo”将会被添加到项目列表。src 文件夹下保存了该项目中包含的源文件。ucos2 文件夹中包含了移植到x86 虚拟机的全部代码。init.c 文件是基于ucos2和本虚拟机的一个应用程序。在进行ucos2 内核实验中,只需要替换init.c 文件,即可。文件名不限,但是文件名中最好不要使用英文符号和数字以外的其他字符,3. 构建项目到这里,项目配置全部完
4、成。接下来就可以进行构建项目了。第一次构建本项目,在此项目上点击右键,选择“重建BSP 及项目”。即可开始构建。之后弹出的对话框显示了构建的进度。可以点击“在后台运行”,以隐藏该对话框在构建的同时,在右下角的“构建信息”视图输出构建过程中的详细信息:注:“重新构建”将本项目中的全部源代码进行一次完全的编译和连接,花费时间较多。“构建项目”则仅仅将新修改过的源代码进行编译和连接,花费时间最少。“重建BSP及项目”,不但要完成“重新构建”的全部工作,另外还要编译与该项目有关的的LambdaEDU中内置的部分代码,花费时间最多。但是在项目刚建立后,第一次构建时需要选择“重建BSP 及项目”。以后的构
5、建中选择“重新构建”或“构建项目”即可。另外,在替换了源代码中的文件后,需要选择“重新构建”来完成该项目的构建。4. 配置虚拟机和目标机代理(1) 制作X86启动盘在 LambdaEDU 中依次点击“工具”、“Bochs”、“制作虚拟机启动映象”。对启动盘进行一些参数设置后(如下图所示),系统将自动为你生成一个PC 虚拟机的启动盘映像。(2) 配置虚拟机选择使用的网络适配器(网卡)后,点击“确定”完成配置。注意:如果计算机上有多网卡,请将其他网卡停用(包括 VMware 虚拟机添加的虚拟网卡)。(3) 创建目标机代理配置好虚拟机后,创建目标机代理:点击LambdaEDU 左下方窗口中绿色的十字
6、符号,在弹出的窗口中选择“基于TA 的连接方式”,并点击“下一步”。在弹出的“新目标机连接配置中”的这些参数,应该与之前制作启动盘时设置的参数一致。注意:名字:输入目标机的名字(缺省是 default),注意如果和现有目标机重名的话,改个名字。连接类型:默认选择 UDPIP地址:这里输入目标机(在本实验系统中是虚拟机)的 IP地址;最后点击“确定”,在目标机管理窗口中,可以看到新增加了一个名为default 的目标机节点(4) 调试应用启动虚拟机。虚拟机启动后的画面如下(其中显示的IP 地址创建虚拟机启动盘时填入的IP 地址)中设置的IP 地址):在成功完成构建的项目ucos_x86_demo
7、 中的“pc386_elf_tra_debug”上点击鼠标右键,在弹出的菜单中选择“调试”,启动调试器调试生成的程序:第一次进行调试/运行,需要选择目标机,如下图,选择“Default”,点击“确定”,开始向目标机(虚拟机)下载应用程序。程序下载完成后,会弹出一个“确认透视图切换”对话框,选择“是”,切换到调试透视图。调试的界面如下:点击绿色的按钮,全速运行。注意:全速运行后,程序不能够被暂停和停止。三、实验过程中遇到的问题及体会在设置IP地址时,要求该IP地址与本计算机在同一个子网中,同时要求该 IP地址没有被网络上其他计算机使用。此外,通过构建开发环境,处次体验到了嵌入式开发工作的乐趣。专
8、心-专注-专业实验二 任务的基本管理一、实验目的1.理解任务管理的基本原理,了解任务的各个基本状态及其变迁过程;2.掌握 uC/OS-II 中任务管理的基本方法(创建、启动、挂起、解挂任务);3. 熟练使用 uC/OS-II 任务管理的基本系统调用。二、实验原理及程序结构1. 实验设计为了展现任务的各种基本状态及其变迁过程,本实验设计了 Task0、Task1 两个任务:任务 Task0 不断地挂起自己,再被任务 Task1 解挂,两个任务不断地切换执行。通过本实验,读者可以清晰地了解到任务在各个时刻的状态以及状态变迁的原因。2. 运行流程描述如下:(1)系统经历一系列的初始化过程后进入 bo
9、ot_card()函数,在其中调用 ucBsp_init()进行板级初始化后,调用 main()函数;(2)main()函数调用 OSInit()函数对 uC/OS-II 内核进行初始化,调用 OSTaskCreate 创建起始任务 TaskStart;(3)main()函数调用函数 OSStart()启动 uC/OS-II 内核的运行,开始多任务的调度,执行当前优先级最高的就绪任务 TaskStart;(4)TaskStart 完成如下工作:a、安装时钟中断并初始化时钟,创建 2 个应用任务;b、挂起自己(不再被其它任务唤醒),系统切换到当前优先级最高的就绪任务Task0。之后整个系统的运行
10、流程如下:l t1 时刻,Task0 开始执行,它运行到 t2 时刻挂起自己;l t2 时刻,系统调度处于就绪状态的优先级最高任务 Task1 执行,它在 t3 时刻唤醒Task0,后者由于优先级较高而抢占 CPU;l Task0 执行到 t4 时刻又挂起自己,内核调度 Task1 执行;l Task1 运行至 t5 时刻再度唤醒 Task0;l 3. C/OS-中的任务描述一个任务通常是一个无限的循环 ,由于任务的执行是由操作系统内核调度的,因此任务是绝不会返回的,其返回参数必须定义成 void。在C/OS-中,当一个运行着的任务使一个比它优先级高的任务进入了就绪态,当前任务的 CPU 使用
11、权就会被抢占,高优先级任务会立刻得到 CPU 的控制权(在系统允许调度和任务切换的前提下)。C/OS-可以管理多达 64 个任务,但目前版本的C/OS-有两个任务已经被系统占用了(即空闲任务和统计任务)。必须给每个任务赋以不同的优先级,任务的优先级号就是任务编号(ID),优先级可以从 0 到 OS_LOWEST_PR10-2。优先级号越低,任务的优先级越高。C/OS-总是运行进入就绪态的优先级最高的任务。4. 源程序说明(1) TaskStart任务TaskStart 任务负责安装操作系统的时钟中断服务例程、初始化操作系统时钟,并创建所有的应用任务:UCOS_CPU_INIT(); /* In
12、stall uC/OS-IIs clock tick ISR */ UCOS_TIMER_START(); /*Timer 初始化*/ TaskStartCreateTasks(); /* Create all the application tasks */ OSTaskSuspend(OS_PRIO_SELF); 具体负责应用任务创建的 TaskStartCreateTasks 函数代码如下,它创建了两个应用任务Task0 和 Task1:void TaskStartCreateTasks (void) INT8U i; for (i = 0; i N_TASKS; i+) / Creat
13、e tasks TaskDatai = i; / Each task will display itsown information OSTaskCreate(Task0, (void *)&TaskData0, &TaskStk0TASK_STK_SIZE - 1, 5); OSTaskCreate(Task1, (void *)&TaskData1, &TaskStk1TASK_STK_SIZE - 1, 6); TaskStart 任务完成上述操作后将自己挂起,操作系统将调度当前优先级最高的应用任务Task0 运行。(2) 应用任务应用任务 Task0 运行后将自己挂起,之后操作系统就会
14、调度处于就绪状态的优先级最高的任务,具体代码如下:void Task0 (void *pdata) INT8U i; INT8U err; i=*(int *)pdata; for (;) printf(Application tasks switched %d times!nr,+count); printf(TASK_0 IS RUNNING.nr); printf(task_1 is suspended!nr); printf(*nr); err=OSTaskSuspend(5); / suspend itself 应用任务 Task1 运行后将 Task0 唤醒,使其进入到就绪队列中:
15、void Task1 (void *pdata) INT8U i; INT8U err; i=*(int *)pdata; for (;) OSTimeDly(150); printf(Application tasks switched %d times!nr,+count); printf(task_0 is suspended!nr); printf(TASK_1 IS RUNNING.nr); printf(*nr); OSTimeDly(150); err=OSTaskResume(5); /* resume task0 */ 三、运行及观察应用输出信息按照本实验手册第一部分所描述的
16、方法建立应用项目并完成构建 ,当我们在LambdaEDU 调试器的控制下运行构建好的程序后,将看到在C/OS-内核的调度管理下,两个应用任务不断切换执行的情形:四、本实验中用到的C/OS-相关函数4.1 OSTaskCreate() OSTaskCreate()建立一个新任务。任务的建立可以在多任务环境启动之前,也可以在正在运行的任务中建立。中断处理程序中不能建立任务。一个任务必须为无限循环结构,且不能有返回点。OSTaskCreate()是为与先前的C/OS 版本保持兼容,新增的特性在 OSTaskCreateExt()函数中。无论用户程序中是否产生中断,在初始化任务堆栈时,堆栈的结构必须与
17、 CPU 中断后寄存器入栈的顺序结构相同。详细说明请参考所用处理器的手册。函数原型:INT8U OSTaskCreate( void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio ); 参数说明: task 是指向任务代码首地址的指针。pdata 指向一个数据结构,该结构用来在建立任务时向任务传递参数。ptos 为指向任务堆栈栈顶的指针。任务堆栈用来保存局部变量,函数参数,返回地址以及任务被中断时的 CPU 寄存器内容。任务堆栈的大小决定于任务的需要及预计的中断嵌套层数。计算堆栈的大小,需要知道任务的局部变量所占的空间,可能产
18、生嵌套调用的函数,及中断嵌套所需空间。如果初始化常量 OS_STK_GROWTH 设为 1,堆栈被设为从内存高地址 向 低 地 址 增 长 , 此时 ptos 应 该 指 向任 务堆 栈 空 间 的 最 高 地 址 。 反 之 , 如 果OS_STK_GROWTH 设为 0,堆栈将从内存的低地址向高地址增长。prio 为任务的优先级。每个任务必须有一个唯一的优先级作为标识。数字越小,优先级越高。返回值: OSTaskCreate()的返回值为下述之一:l OS_NO_ERR:函数调用成功。l OS_PRIO_EXIST:具有该优先级的任务已经存在。l OS_PRIO_INVALID:参数指定的
19、优先级大于 OS_LOWEST_PRIO。l OS_NO_MORE_TCB:系统中没有 OS_TCB 可以分配给任务了。注意: 任务堆栈必须声明为 OS_STK 类型。在任务中必须调用C/OS 提供的下述过程之一:延时等待、任务挂起、等待事件发生(等待信号量,消息邮箱、消息队列),以使其他任务得到 CPU。用 户 程 序 中 不 能 使 用 优 先 级 0 , 1 , 2 , 3 , 以 及 OS_LOWEST_PRIO-3, OS_LOWEST_PRIO-2, OS_LOWEST_PRIO-1, OS_LOWEST_PRIO。这些优先级C/OS 系统保留,其余的 56 个优先级提供给应用程序
20、。4.2 OSTaskSuspend() OSTaskSuspend () 无条件挂起一个任务 。调用此函数的任务也可以传递参数OS_PRIO_SELF,挂起调用任务本身。当前任务挂起后,只有其他任务才能唤醒。任务挂起后,系统会重新进行任务调度,运行下一个优先级最高的就绪任务。唤醒挂起任务需要调用函数 OSTaskResume ()。任务的挂起是可以叠加到其他操作上的。例如,任务被挂起时正在进行延时操作,那么任务的唤醒就需要两个条件:延时的结束以及其他任务的唤醒操作。又如,任务被挂起时正在等待信号量,当任务从信号量的等待对列中清除后也不能立即运行,而必须等到被唤醒后。函数原型:INT8U OS
21、TaskSuspend( INT8U prio); 参数说明: prio 为指定要获取挂起的任务优先级,也可以指定参数 OS_PRIO_SELF,挂起任务本身。此时,下一个优先级最高的就绪任务将运行。返回值: OSTaskSuspend()的返回值为下述之一:l OS_NO_ERR:函数调用成功。l OS_TASK_ SUSPEND_IDLE:试图挂起 C/OS-II 中的空闲任务(Idle task)。此为非法操作。l OS_PRIO_INVALID :参数指定的优先级大于 OS_LOWEST_PRIO 或没有设定l OS_PRIO_SELF 的值。l OS_TASK_ SUSPEND _P
22、RIO:要挂起的任务不存在。注意: 在程序中 OSTaskSuspend()和 OSTaskResume ()应该成对使用。用 OSTaskSuspend()挂起的任务只能用 OSTaskResume ()唤醒。4.3 OSTaskResume() OSTaskResume ()唤醒一个用 OSTaskSuspend()函数挂起的任务。OSTaskResume()也是唯一能“解挂”挂起任务的函数。函数原型:INT8UOSTaskResume ( INT8U prio); 参数说明: prio 指定要唤醒任务的优先级。返回值: OSTaskResume ()的返回值为下述之一:l OS_NO_E
23、RR:函数调用成功。l OS_TASK_RESUME_PRIO:要唤醒的任务不存在。l OS_TASK_NOT_SUSPENDED:要唤醒的任务不在挂起状态。l OS_PRIO_INVALID:参数指定的优先级大于或等于 OS_LOWEST_PRIO。五、实验过程中遇到的问题及体会实验过程中体会到了嵌入式开发的乐趣,对上课老师所讲的内容有了进一步的认识与理解。实验三 信号量:哲学家就餐问题的实现一、实验目的掌握在基于嵌入式实时操作系统 uC/OS-II 的应用中,任务使用信号量的一般原理。通过经典的哲学家就餐实验,了解如何利用信号量来对共享资源进行互斥访问。二、实验原理及程序结构1. 实验设计
24、掌握在基于嵌入式实时操作系统 uC/OS-II 的应用中,任务使用信号量的一般原理。通过经典的哲学家就餐实验,了解如何利用信号量来对共享资源进行互斥访问。2. 源程序说明五个哲学家任务(ph1、ph2、ph3、ph4、ph5)主要有两种过程:思考(即睡眠一段时间)和就餐。每个哲学家任务在就餐前必须申请并获得一左一右两支筷子,就餐完毕后释放这两支筷子。五个哲学家围成一圈,每两人之间有一支筷子。一共有五支筷子,在该实验中用了五个互斥信号量来代表。每个任务的代码都一样,如下所示:void Task (void *pdata) INT8U err; INT8U i; INT8U j; i=*(int
25、*)pdata; j=(i+1) % 5; uC/OS-II 实验指导书- 47 - for (;) TaskThinking2Hungry(i); OSSemPend(forki, 0, &err); OSSemPend(forkj, 0, &err); /* Acquire semaphores to eat */ TaskEat(i); OSSemPost(forkj); OSSemPost(forki); /* Release semaphore */ OSTimeDly(200); /* Delay 10 clock tick */ 操作系统配置修改 uC_OS-II/OS_CFG.
26、h: : :#define OS_MAX_EVENTS 10 /*最多可以有 10 个事件*/ #define OS_MAX_FLAGS 5 /*最多可以有 5 个事件标志*/ #define OS_MAX_MEM_PART 5 /*最多可以划分 5 个内存块*/ #define OS_MAX_QS 2 /*最多可以使用 2 个队列*/ #define OS_MAX_TASKS 8 /*最多可以创建 8 个任务*/ #define OS_LOWEST_PRIO 14 /*任务优先级不可以大于 14*/ #define OS_TASK_IDLE_STK_SIZE 1024 /*空闲任务堆栈大小*
27、/ #define OS_TASK_STAT_EN 1 /*是否允许使用统计任务*/ #define OS_TASK_STAT_STK_SIZE 1024 /*统计任务堆栈大小*/ #define OS_FLAG_EN 1 /*是否允许使用事件标志功能*/ #define OS_FLAG_WAIT_CLR_EN 1 /*是否允许等待清除事件标志*/ #define OS_FLAG_ACCEPT_EN 1 /*是否允许使用 OSFlagAccept()*/ #define OS_FLAG_DEL_EN 1 /*是否允许使用 OSFlagDel()*/ #define OS_FLAG_QUERY_
28、EN 1 /*是否允许使用 OSFlagQuery()*/ #define OS_MBOX_EN 0 /*是否允许使用邮箱功能*/ #define OS_MEM_EN 0 /*是否允许使用内存管理的功能*/ #define OS_MUTEX_EN 0 /*是否允许使用互斥信号量的功能*/ #define OS_Q_EN 0 /*是否允许使用队列功能*/#define OS_SEM_EN 1 /*是否允许使用信号量功能*/ #define OS_SEM_ACCEPT_EN 1 /*是否允许使用 OSSemAccept()*/ #define OS_SEM_DEL_EN 1 /*是否允许使用OSS
29、emDel() */ #define OS_SEM_QUERY_EN 1 /*是否允许使用OSSemQuery()*/ #define OS_TASK_CHANGE_PRIO_EN 1 /* 是 否 允 许 使 用OSTaskChangePrio()*/ #define OS_TASK_CREATE_EN 1 /*是否允许使用 OSTaskCreate()*/ #define OS_TASK_CREATE_EXT_EN 1 /*是否允许使用 OSTaskCreateExt()*/ #define OS_TASK_DEL_EN 1 /*是否允许使用 OSTaskDel()*/ #define O
30、S_TASK_SUSPEND_EN 1 /* 是 否 允 许 使 用 OSTaskSuspend() and OSTaskResume()*/ #define OS_TASK_QUERY_EN 1 /*是否允许使用 OSTaskQuery()*/ #define OS_TIME_DLY_HMSM_EN 1 /* 是 否 允 许 使 用OSTimeDlyHMSM()*/ #define OS_TIME_DLY_RESUME_EN 1 /* 是 否 允 许 使 用OSTimeDlyResume()*/ #define OS_TIME_GET_SET_EN 1 /* 是否允许使用 OSTimeGet
31、() 和OSTimeSet()*/ #define OS_SCHED_LOCK_EN 1 /* 是 否 允 许 使 用 OSSchedLock() 和OSSchedUnlock()*/ #define OS_TICKS_PER_SEC 200 /*设置每秒之内的时钟节拍数目*/三、运行及观察应用输出信息开始,所有的哲学家先处于 thinking 状态,然后都进入 hungry 状态:后首先获得两个信号量的 1、3 号哲学家开始 eating,待他们释放相关信号量之后,哲学家 2、5、4 获得所需的信号量并 eating:应用如此这般地循环执行程序下去四、本实验中用到的C/OS-相关函数4.1
32、OSSemCreate() OSSemCreate()函数建立并初始化一个信号量。信号量的作用如下:l 允许一个任务和其他任务或者中断同步l 取得设备的使用权l 标志事件的发生 函数原型:OS_EVENT *OSSemCreate( ( (WORD value) ) )参数说明: value 参数是所建立的信号量的初始值,可以取 0 到 65535 之间的任何值。返回值: OSSemCreate()函数返回指向分配给所建立的信号量的控制块的指针。如果没有可用的控制块,OSSemCreate()函数返回空指针。注意:必须先建立信号量,然后使用。4.2 OSSemPend() OSSemPend(
33、)函数用于任务试图取得设备的使用权,任务需要和其他任务或中断同步,任务需要等待特定事件的发生的场合。如果任务调用 OSSemPend()函数时,信号量的值大于零,OSSemPend()函数递减该值并返回该值。如果调用时信号量等于零,OSSemPend()函数函数将任务加入该信号量的等待队列。OSSemPend()函数挂起当前任务直到其他的任务或中断置起信号量或超出等待的预期时间。如果在预期的时钟节拍内信号量被置起,C/OS-默认最高优先级的任务取得信号量恢复执行。一个被 OSTaskSuspend()函数挂起的任务也可以接受信号量,但这个任务将一直保持挂起状态直到通过调用OSTaskResum
34、e()函数恢复任务的运行。函数原型: : :Void OSSemPend ( OS_EVNNT *pevent, INT16U timeout, int8u *err ); 参数说明: : :pevent 是指向信号量的指针。该指针的值在建立该信号量时可以得到。( 参考OSSemCreate()函数)。Timeout 允许一个任务在经过了指定数目的时钟节拍后还没有得到需要的信号量时恢复就绪状态。如果该值为零表示任务将持续地等待信号量,最大的等待时间为 65535 个时钟节拍。这个时间长度并不是非常严格的,可能存在一个时钟节拍的误差。Err 是指向包含错误码的变量的指针。OSSemPend()函
35、数返回的错误码可能为下述几种:l OS_NO_ERR :信号量不为零。 l OS_TIMEOUT :信号量没有在指定数目的时钟周期内被设置。l OS_ERR_PEND_ISR :从中断调用该函数。虽然规定了不允许从中断调用该函数,但 C/OS-仍然包含了检测这种情况的功能。l OS_ERR_EVENT_TYPE :pevent 不是指向信号量的指针。返回值:无注意:必须先建立信号量,然后使用。不允许从中断调用该函数。4.3 OSSemPost() OSSemPost()函数置起指定的信号量。如果指定的信号量是零或大于零,OSSemPost() 函数递增该信号量并返回。如果有任何任务在等待信号量
36、,最高优先级的任务将得到信号量并进入就绪状态。任务调度函数将进行任务调度,决定当前运行的任务是否仍然为最高优先级的就绪状态的任务。函数原型:INT8U OSSemPost(OS_EVENT *pevent); 参数说明: pevent 是指向信号量的指针。该指针的值在建立该信号量时可以得到。( 参考OSSemCreate()函数)。返回值: OSSemPost()函数的返回值为下述之一:l OS_NO_ERR :信号量被成功地设置l OS_SEM_OVF :信号量的值溢出l OS_ERR_EVENT_TYPE :pevent 不是指向信号量的指针注意: 必须先建立信号量,然后使用。4.4 OS
37、TimeDly() OSTimeDly()将一个任务延时若干个时钟节拍。如果延时时间大于 0,系统将立即进行任务调度。延时时间的长度可从 0 到 65535 个时钟节拍。延时时间 0 表示不进行延时,函数将立即返回调用者。延时的具体时间依赖于系统每秒钟有多少时钟节拍(由文件SO_CFG.H 中的常量 OS_TICKS_PER_SEC 设定)。函数原型: void OSTimeDly ( INT16U ticks); 参数说明: ticks 为要延时的时钟节拍数。返回值: 无注意: 注意到延时时间 0 表示不进行延时操作,而立即返回调用者。为了确保设定的延时时间,建议用户设定的时钟节拍数加 1。例如,希望延时 10 个时钟节拍,可设定参数为 11。五、实验过程中遇到的问题及体会在实验前要对该问题进行深入的理解,即五个哲学家任务(ph1、ph2、ph3、ph4、ph5)主要有两种过程:思考(即睡眠一段时间)和就餐。每个哲学家任务在就餐前必须申请并获得一左一右两支筷子,就餐完毕后释放这两支筷子。五个哲学家围成一圈,每两人之间有一支筷子。只有理解了,才能更好的进行实验。