要移植一个操作系统到一个特定的cpu体系结构上并不是一件很容易的事情,它对移植者有以下要求:
1. 对目标体系结构要有很深了解;
2. 对os原理要有较深入的了解;
3. 对所使用的编译器要有较深入的了解;
4. 对需要移植的操作系统要有相当的了解;
5. 对具体使用的芯片也要一定的了解。
——移植需要编写的文件
根据μc/os-ii的要求,移植μc/os-ii到一个新的体系结构上需要提供2个或3个文件:
os_cpu.h(c语言头文件)
os_cpu_c.c(c程序源文件)
os_cpu_a.asm(汇编程序源文件)
其中os_cpu_a.asm在某些情况下不需要,但极其罕见。不需要os_cpu_a.asm的必须满足以下苛刻条件,而同时满足这些条件的微控制器几乎没有:
1.可以直接使用c语言开关中断;
2.可以直接使用c语言编写中断服务程序;
3.可以直接使用c语言操作堆栈指针;
4.可以直接使用c语言保存cpu的所有寄存器。
——移植代码包括的内容
实际上,还有一个文件很重要,它就是irq.inc,它定义了一个汇编宏,它是μc/os-iiforarm7通用的中断服务程序的汇编与c函数接口代码。时钟节拍中断服务程序也没有移植,因为其与芯片和应用都强烈相关,需要用户自己编写,不过可以通过irq.inc简化用户代码的编写。
关于头文件includes.h和config.h
μc/os-ii要求所有.c文件的都要包含都文件includes.h,这样使得用户项目中的每个.c文件不用分别去考虑它实际上需要哪些头文件。使用includes.h的缺点是它可能会包含一些实际不相关的头文件,这意味着每个文件的编译时间可能会增加,但却增强了代码的可移植性。
在本移植中另外增加了一个头文件config.h,我们要求所有用户程序必须包含config.h,在config.h中包含includes.h和特定的头文件和配置项。而μc/os-ii的系统文件依然只是包含includes.h,即μc/os-ii的系统文件完全不必改动。所有的配置改变包括头文件的增减均在config.h中进行,而includes.h定下来后不必改动(μc/os-ii的系统文件需要包含的东西是固定的)。这样,μc/os-ii的系统文件需要编译的次数大大减少,编译时间随之减少。
编写os_cpu.h
——不依赖于编译的数据类型
μcos-ii不使用c语言中的short、int、long等数据类型的定义,因为它们与处理器类型有关,隐含着不可移植性。代之以移植性强的整数数据类型,这样,既直观又可移植,不过这就成了必须移植的代码。根据ads编译器的特性,这些代码如程序清单7.1所示。
typedef unsigned char boolean;
typedef unsigned char int8u;
typedef signed char int8s;
typedef unsigned short int16u;
typedef signed short int16s;
typedef unsigned int int32u;
typedef signed int int32s;
typedef float fp32;
typedef double fp64;
typedef int32u os_stk;
编写os_cpu.h
——使用软中断swi作底层接口
μcos-ii运行时,处理器可能处于的状态如下图所示:
为了使底层接口函数与处理器状态无关,同时在任务调用相应的函数不需要知道函数位置,本移植使用软中断指令swi作为底层接口,使用不同的功能号区分不同的函数。软中断功能号分配如下表所示,未列出的为保留功能。
用软中断作为操作系统的底层接口就需要在c语言中使用swi指令。在ads中,有一个关键字__swi,用它声明一个不存在的函数,则调用这个函数就在调用这个函数的地方插入一条swi指令,并且可以指定功能号。同时,这个函数也可以有参数和返回值,其传递规则与一般函数一样。
编写os_cpu.h
——堆栈生长方式
μcos-ii使用结构常量os_stk_growth中指定堆栈的生长方式:
置os_stk_growth为0表示堆栈从下往上长。
置os_stk_growth为1表示堆栈从上往下长。
虽然arm处理器核对于两种方式均支持,但ads的c语言编译器仅支持一种方式,即从上往下长,并且必须是满递减堆栈,所以os_stk_growth的值为1。
#define os_stk_growth 1
编写os_cpu_c.c
——ostaskstkinit( )
该函数用于初始化任务堆栈,使任务的堆栈看起来就像刚发生中断一样。即任务被执行时,就像从中断返回一样。
在编写此函数之前,必须先确定任务的堆栈结构。而任务的堆栈结构是与cpu的体系结构、编译器有密切的关联。本移植的堆栈结构如下图所示。
编写os_cpu_c.c
——ostaskstkinit( )
编写os_cpu_c.c
——软件中断异常服务程序
前面介绍过,操作系统与硬件相关的底层函数使用软件中断作为接口,如下表所示。移植代码中一个重要的工作就是为这些软件中断编写服务程序。
编写os_cpu_c.c
——软件中断异常服务程序
编写os_cpu_c.c
——…hook( )函数
在os_cpu_c.c文件中还有许多钩子函数,它们在某个特定的系统动作时被调用,允许执行函数中的用户代码。这些函数默认是空函数,用户根据实际情况添加相关代码。它们分别如下表所示。
编写os_cpu_a.s
在os_cpu_a.s文件中有软件中断的汇编接口程序、任务切换程序、os启动时运行就绪最高优先级任务的程序。
编写os_cpu_a.s
——软件中断汇编接口
在调用软中断之后,处理器切换到arm指令和管理模式下工作。在执行软件中断服务函数之前,要提取中断号和其它入口参数,这些通过软件中断接口程序完成。
编写os_cpu_a.s
——任务切换代码
μcos-ii是抢占式实时操作系统,得到运行的始终是就绪条件下最高优先级的任务。当处于运行状态的任务因为某种脱离就绪态,或者有其它更高优先级的任务进入就绪态,那么操作系统内核就要运行别的就绪任务,这时需要进行任务切换。
任务切换可能发生的情况有两种:
1.当前运行的任务主动交出cpu控制权,通常发生在等待某个事件或是调用系统延时。调用函数os_task_sw( );
2.发生中断,使更高优先级的任务进入就绪状态,内核剥夺当前任务的运行资格。即发生在中断退出时。调用函数osintctxsw( )。
编写os_cpu_a.s
——任务切换代码
虽然os_task_sw( )和osintctxsw( )的执行条件不同,但是它们的功能相同,只要稍作处理就可以它们共用一段任务切换代码。这些处理就是保证在执行任务切换前两者的任务现场是一致的 。共同执行的任务切换代码是“osintctxsw”
其中os_task_sw( )是通过软件中断0完成的,通过前面的分析,可以知道执行任务切换时的现场环境如下所示,同时r3中保存着spsr,它是任务中断前cpsr的备份。
编写os_cpu_a.s
——osintctxsw
编写os_cpu_a.s
——osstarthighrdy
μc/os-ii的多任务环境由函数osstart( ) 启动。用户在调用该函数之前,必须已经建立了一个或更多任务。osstart()最终调用函数osstarthighrdy( )运行多任务启动前优先级最高的任务,而它最终是调用__osstarthighrdy实现的,其代码如下所示:
编写os_cpu_a.s
通过前面的分析,我们可以画出下面这张结构图:
关于中断及时钟节拍
在本移植中,irq是受μc/os-ii管理的中断,而对于fiq不做处理,这是为了提高fiq的响应速度。由于各种arm芯片的中断系统不一样,各个用户的目标板也不一样,对于中断和时钟节拍是需要进一步移植的代码。为此编写了一个汇编宏,它是μc/os-ii for arm7通用的中断服务程序的汇编与c函数接口代码。
注:在不受管理的中断服务程序中不能调用任何系统函数。
关于中断及时钟节拍
关于中断及时钟节拍
关于中断及时钟节拍
中断服务程序的编写
因为中断发生时肯定是允许中断的,所以如果用户在清除中断源之前调用μc/os-ii的系统服务函数就很可能会造成芯片的中断系统工作异常而使程序工作异常。因此在函数开始处关闭中断,或者直接给变量osentersum赋1。如果用户程序没有这种情况,则不需要这个操作。在执行os_exit_critical( )后,中断重新打开,如果在接下来的用户处理程序中发生中断,就可以实现中断嵌套。