20175202 ucosii-2(选做)
Posted gexvyang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20175202 ucosii-2(选做)相关的知识,希望对你有一定的参考价值。
一、任务详情:
阅读附件中的代码,回答:
- ucos是如何分层的?
2.HAL都有哪些代码? - 分析任务是如何切换的。
二、ucos是如何分层的?
1.三层,分别是:上层访问抽象接口层、设备管理核心数据结构层、硬件设备驱动模块层。
2.上层访问抽象接口层: 一般的抽象层设计会直接在这一层提供5个访问接口API: DeviceOpen、DevGetch、DevPutch、DevControl. DeviceClose,分别用于打开设备、读设备、写设备、设备控制和关闭设备。而在这个设计里面更改了这种定义模式提供两个公用的接口DeviceOpen 和DeviceClose,同时为不同的外设分别提供特定的抽象接口,在移植的时候利
用这些抽象接口的不变性保证应用程序的可移植能力。这样做的优点更适合于有单片机开发经验的工程人员直接调用。
3.设备管理核心数据结构层:这是通用驱动框架的核心,主要用每个设备分配一个设备控制块,通过链表形式进行管理,该链表定义为设备控制块链表DEV_CONTROL_BLOCK* HvlConList。 在这一层, 为系统中的每个硬件设备分配唯一的设备ID。上层应用程序通过将设备ID作为参数传递给DeviceOpen函数实现对相应设备的核心管理数据结构的定位搜索,通过搜索,DeviceOpen函数找到相应设备控制块,申请设备的使用权限,获得相应硬件设备的操作句柄,该句柄指向具体的外设底层操作函数列表,返回该设备句柄;再通过上层抽象接口层提供的接口函数对设备进行访问。
4.硬件设备驱动模块层:这-一层是硬件设备驱动模块功能的实现层,对各个硬件设备的驱动在相应的硬件设备驱动模块中完成。各个硬件设备驱动模块,原则上需要实现如下几个函数: DevGetch、 DevPut ch、DevControl,分别完成相应设备的读、写、控制,当然,可以根据具体设备的特性,只实现3个驱动函数的其中一部分,例如,如果某设备不支持写操作,那么就不用实现DevPutch函数。
三、HAL都有哪些代码?
1.
2.HAL的作用是将操作系统的其余部分表示为抽象的硬件设备,特别是去除了真正硬件所富含的瑕疵和特质。这些设备表现为操作系统的其它部分和设备可以使用的独立于机器的服务的形式(函数调用和宏)。通过使用HAL服务和间接硬件寻址,当移植到新的硬件上时,驱动程序和核心只需做很少的改动。移植HAL本身是直接的,因为所有的机器相关代码都集中在一个地方,并且移植的目标是充分定义的,即实现所有的HAL服务。选择HAL中的服务是和主板上的芯片相关的,因为这些芯片从一个机器到另一个机器的变化是具有可预见限度的。换句话说,设计它是为了隐藏不同厂商主板之间的差别,而不是X86和Alpha之间的差别。HAL服务包括对设备寄存器的访问、总线独立的设备寻址、中断处理和复位、DMA传输、定时器和实时时钟的控制、底层的自旋锁(Spin Lock)和多处理机同步、Bios接口以及CMOS配置内存。HAL没有提供对特殊I/O设备(如键盘、鼠标、硬盘和内存管理单元)的抽象或服务。
举一个例子来说明硬件抽象层的功能。考虑内存映射I/O和I/O端口的对比。一些机器具有前者,一些机器具有后者。驱动程序该怎样编写?是否使用内存映射呢?强制选择会使驱动程序无法移植到另一种实现方式的机器上,为此,硬件抽象层专为驱动程序的编写者提供了三个读设备寄存器的函数和另外三个写寄存器的函数:
uc=READ_PORT_UCHAR(port); WRITE_PORT_UCHAR(port, uc)
us=READ_PORT_USHORT(port); WRITE_PORT_USHORT(port, us)
ul=READ_PORT_ULONG(port); WRITE_PORT_LONG(port, ul)
这些函数分别读写无符号8位、16位、32位的证书到特定的端口。由HAL决定是否需要内存映射I/O,这样,一个驱动程序可以不被修改而在具有不同设备寄存器实现的机器间移植。
驱动程序常由于各种原因而访问特定的I/O设备。在这个硬件层上,一个设备的某个总线上会有一个或多个地址。由于现代计算机常有多种总线(PCI、PCI-E、SCSI、USB等),很可能两个或更多设备具有相同的总线地址,因此需要通过某种方式来区分它们。HAL提供了一个服务,该服务通过将总线相连的设备地址映射到系统范围内的逻辑地址来识别设备。这样,驱动程序就不需要知道哪条总线上有哪个设备了。这些逻辑地址与操作系统为用户程序提供的指向文件和其他系统资源的句柄是类似的。这种机制也使总线结构的属性和寻址方式对于高层不可见。
中断也存在类似的问题——它们也是总线相关的。同样,在这里,HAL为系统范围内的中断提供命名服务,并允许驱动程序以可移植的方法将中断服务例程和中断联系起来而不用知道哪个中断向量对应于哪条总线。此外,中断请求级别管理也在HAL处理。
HAL提供的另一项服务是以一种设备独立的方式设置并管理DMA传输。系统范围内的DMA引擎与特定I/O卡上的DMA引擎都可以操作。对设备的访问是通过其逻辑地址进行的。HAL还实现了软件的分散、聚集(scatter/gather)(对非连续的物理存储块进行写或读)。
此外,HAL还以一种可移植的方式管理时钟与定时器。时间记录以100ns为单位(起始于1601年1月1日),这样就比以2s为单位(起始于1980年1月1日)的MS-DOS事件记录精确得多,HAL还为许多发生于17.18.19世纪的计算机相关事件的记录提供了致贺词。这种时间服务将驱动程序从始终运行的实际频率中分离出来。
内核组件(Kernel Component)有时需要在非常低的层次上同步,特别是为了避免多处理机系统中的竞争状态。HAL提供了一些原子方法来管理这种同步,如自旋锁——一个CPU仅仅等待一个由其他CPU占用的系统资源被释放,尤其是在资源只被几条机器指令所占用的情况下。
最后,当系统启动以后,HAL与BIOS进行对话,并检查CMOS配置内存(如果有的话),以查明该系统包含了哪些总线和I/O设备,以及他们是如何配置的。之后这个信息会被存入注册表,这样,其他系统组件就能够查询它,而不必了解BIOS或配置内存如何工作。
由于HAL高度依赖于机器,它必须与其所装入的系统完全匹配,因此,Windows的安装光盘上提供了许多种版本的HAL。系统安装时,选择一种合适的HAL并以hal.dll为名复制到硬盘上的系统目录windowssystem32或winntsystem32下。之后所有的启动都使用该版本的HAL,删除这个文件将导致系统无法启动。
3.两种架构:
4.两种访问方式:
四、任务是如何切换的?
1.【函数周期与死循环】
一般函数的生命周期很简单,从开始调用函数起,直到函数返回,即结束。这样一来就完成了这个函数的使命,它也就不再需要了。对于一般的函数就是这样,但是回过头想想,对于一个系统、OS、或者工业控制中的一个控制器重的系统个,函数返回是很轻易很随便的就能返回吗?返回就意味着函数结束,死亡,若是想系统这样一个很大的函数,它的返回就意味着系统结束。因此,对于系统的函数返回有些时候我们不希望它返回,返回时是需要好好设计的,像嵌入式中的控制程序我们也并不需要它返回,直接关机就好了。因此,一个系统往往就是一个很大的循环,不停的扫描,而我们编程的时候对于这个死循环是需要好好设计的。考虑以下一个控制要求,
@.按键控制电机启、停、正转反转,并每秒发送CAN报文报告当前情况。
我们可以有多种方法实现这一要求:
方法一:每次在循环体重扫描当前按键的电平,从而进入对应的控制电机函数,如果所有电平都没有信号则直接进入下一个循环。发送CAN报文就直接用一个定时中断。这样的好处就是编程简单直白,每次循环进入不同的电机控制函数,坏处很明显,一定要等待到下一个循环才能进入其他的电机控制函数,每次循环的时间不好控制,不管你用函数指针还是if/else来判断,每次循环一定要等待电机动作结束才能进入下一个循环。
方法二:改用外部中断来处理按键。仅当按键按下时触发外部中断,从而控制响应的电机进行操作。这样的好处就是循环体简单,可以仅仅就是一个计数器加一,所有控制都等中断来实现。但这样带来的问题也很明显,就是中断嵌套问题。比如当电机正转时按下停止按钮,这时由于是在中断中,停止按钮是否真的能够得到响应?这就涉及到中断嵌套问题,并不见得所有CPU都能支持中断嵌套,我的这一篇文章对中断嵌套问题进行了一个讨论。
方法三:采用RTOS的思想,加入任务调度系统。每次任务调度系统就是一个小小的循环,对于各个任务进行轮询,当这次轮询发现某任务是已经就绪的优先级最高的任务,则交给CPU处理,所有中断与任务,任务与任务之间有通讯机制可以交换信息。当然实际上并不仅仅只在任务调度器轮询时才进行任务的切换,实际上的操作比这个复杂一些,我的这篇文章就想以uCOS-II为例讨论RTOS的任务调度系统是怎样执行的。
2.【@.2 uCOS-II中的任务调度】
回到前面的方法一,二,我们将这种任务称作前后台任务。
后台就是指程序的大循环,程序一定要等到前台任务(可以说中断或某个功能函数)返回才能继续运行下去。而在RTOS中每个任务都有自己的控制块指向改任务,由任务调度器来决定这个时候该运行哪个任务。
所有这些任务控制快(TCB)构成一个双向链表,每个TCB中都有一些控制字,比如一个指向堆栈的指针(sp),一个表明当前任务状态的位(State),指明任务被挂起等待的超时时间(dly),任务的优先级(Prio),指向事件控制块的指针(Event,事件机制后面会讨论)。一个全局的任务就续表记录了当前任务是否就绪,任务调度器就靠查询任务就绪表寻找到就绪任务中优先级最高的一个任务。
每一个任务都是一个死循环,并且必须在循环内调用系统函数来释放CPU控制权,比如调用系统的延时函数OSTimeDly()延时一段时间,这个时候系统就会知道这个任务被延时了,延时时间记录在TCB中的超时时间dly中,任务调度器将其他任务予以运行。
实际的任务调度有两种机制:
1.中断级任务调度:任何中断返回时必须调用一个系统函数OSIntExit(void),进行一次任务调度。比如一般任务调度器通常是一个定时中断,比如1000次每秒,成为OS时钟。每次OS时钟要返回时都会调用OSInitExit()进行任务切换,运行当前就绪的优先级最高任务。一般这个OS时钟的优先级很低,为整个系统优先级倒数第二低(倒数第一低的是Idle Task,空闲任务),因此实际上这个OS时钟最终并不会返回。
2.任务级任务调度:在任务运行中执行一次OS的特定函数,比如前面提到的OSTimeDly(),此函数在返回之前会执行一次任务调度。
因此总的任务调度次数会远远高于OS时钟的轮询频率的。
以上是关于20175202 ucosii-2(选做)的主要内容,如果未能解决你的问题,请参考以下文章