在ARM实现嵌入式系统的过程_第1页
在ARM实现嵌入式系统的过程_第2页
在ARM实现嵌入式系统的过程_第3页
在ARM实现嵌入式系统的过程_第4页
在ARM实现嵌入式系统的过程_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

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

文档简介

1、实现一个什么都不能做的嵌入式操作系统1. 首先确定 CPU ,在这里为了简单,就选用嵌入式的 CPU ,比如 ARM 系列,之所 以用 RISC (简单指令集类型的 CPU ,其方便之处是没有实模式与保护模式之分,采用线性的统一寻址, 也就是不需要进行段页式内存管理,还有就是芯片内部集成了一些常用外设控制器,比如以太网卡, 串口等等,不需要像在 PC 机的主板上那么多外设芯片2. 确定要实现的模块和功能,为了简单,只实现多任务调度(但有限制,比如最 多不超过 10,实现中断处理(不支持中断优先级,不进行动态 SHELL 交互,不实现动态模块加 载,不实现 fork 之类的动态进程派生和加载 (

2、也就是说要想在你的操作系统上加入用户程序, 只能静 态编译进内核中;不支持文件系统,不支持网络,不支持 PCI , USB ,磁盘等外设(除了支持串口, 呵呵,串口最简单嘛,不支持虚拟内存管理 (也就是说多任务中的每个进程都可以访问到任何地址, 这 样做的话,一个程序死了,那么这个操作系统也就玩完了3. 确定要使用的编译器,这里采用 GCC ,文件采用 ELF 格式,当然,最终的文件 就是 BIN 格式, GCC 和LINUX 有着紧密的联系,自己的操作系统,需要 C 库支持和系统调用支持,所以 需要自己去裁剪 C 库,自己去实现系统调用4. 实现步骤:首先是 CPU 选型,交叉编译环境的建立

3、,然后就是写 BOOTLOADER , 写操作系统。如何实现 BOOTLOADER1. 之所以要实现一个专用的 BOOTLOADER ,一是为了更好的移植和自身的升级, 二是为了方便操作系统的调试, 当然, 你完全可以将这部分所要实现的与操作系统相关的功能集成到操 作系统中去2. 确定一个简单的 BOOTLOADER 所要完成的功能:我们这里只需要完成两个主要 功能,一是将操作系统加载到内存中去运行,二是将自己和操作系统内核固化到 ROM 存储区(这里的 ROM 可以是很多设备,比如嵌入式芯片中的 FLASH , PC 机上的软盘, U 盘,硬盘等3.BOOTLOADER 的编写:第一步:要进

4、行相关硬件的初使化,比如在 at91rm9200这块嵌入式板子上(以 后都使用这一款芯片,主要是我对这款芯片比较熟悉, 嘿嘿 , 大概要做接下来的几方面的工作, 其一:将 CPU 模式切换进系统模式,关闭系统中断,关闭看门狗,根据具体情况进行内存区域映射,初始 化内存控制区,包括所使用的内存条的相关参数,刷新频率等,其二:设定系统运行频率,包括使 用外部晶振,设置CPU 频率,设置总线频率,设置外部设备所采用的频率等。其三:设置系统中断 相关,包括定时器中断,是否使用 FIQ 中断,外部中断等,还有就是中断优先级设置,这里只实现两 个优先级,只有时钟中断高一级, 其它都一样, 而中断向量初始化

5、时都将这些中断向量指向 0x18处, 并关闭这里的所有中断, 如果板子还接有诸如 FLASH 设备的话, 还需要设置诸如 FLASH 相关操制寄存 器,其四:需要关闭CACHE ,到此为止,芯片相关内容就完成初始化了第二步:中断向量表, ARM 的中断与 PC 机芯片的中断向量表有一点差异,嵌入 式设备为了简单,当发生中断时,由 CPU 直接跳入由 0x0开始的一部分区域(ARM 芯片自身决定了它中 断时就会跳入 0x0开始的一片区域内, 具体跳到哪个地址是由中断的模式决定的, 一般用到的就是复位中 断, FIQ , IRQ 中断,SWI 中断,指令异常中断,数据异常中断,预取指令异常中断,而

6、当 CPU 进入 相应的由 0x0开始的向量表中时, 这就需要用户自己编程接管中断处理程序了, 这就是需要用户自己编 写中断向量表,中断向量表里存放的就是一些跳转指令, 比如当 CPU 发生一个 IRQ 中断时, 就会自动 跳入到 0x18处,这里就是用户自己编写的一个跳转指令,假如用户在此编写了一条跳转到 0x20010000处的指令,那么这个地址就是一个总的 IRQ 中断处理入口, 一个 CPU 可能有多个 IRQ 中断, 在这个总的 入口处如何区分不同的中断呢?就由用户编程来决定了, 具体实现请参见以后相关部分, 中断向量表的一 般用一个 vector.S 文件,当然,如何命名那是你自己

7、的喜爱,但有一点需要声 明,那就是在链接时一定要将它定位在 0x0处第三步:设置堆栈, 一般使用三个栈, 一个是 IRQ 栈, 一个是系统模式下的栈 (系 统模式下和用户模式共享寄存器和内存空间, 这主要是为了简单 , 设置栈的目的主要是为了进行函 数调用和局部变量的存放,不可能全用汇编,也不可能不用局部变量第四步:将自己以后的代码段和数据段全部拷贝至内存,并将 BSS 段清零第五步:进行串口的初始化 (主要是为了与用户交互, 进行与 PC 机的文件传输 , FLASH 的初始化(这里在 FLASH 中存放 BOOT 和内核, FLASH 驱动的编写(这里的驱动有别于 平常所说的驱动,由于FL

8、ASH 不像 SDRAM , 只要设定了相关控制器之后就可以直接读写指定地址的数据, 对 FLASH 的写操作是一块一块数据进行,而不是一个字节一个字节地写,具体请查阅相关资料 第六步:等待一定的秒数, 来接收用户进行输入, 如果在指定的秒数内用户未输 入任何字符,那么BOOT 就开始在 FLASH 中的指定位置(可以由自己指定,这么做主要是为了简单 读取内核的所有数据到内存中 (具体是内存中的什么位置由自己指定, 也可以采用 LINUX 之类的做法, 就是在内存的起始位置加上一个 0x8000处,将跳转到内核的第一条代码处;如果用户在指定 的秒数内键入了字符(这主要是为了方便开发, 如果开发

9、定型之后完全可以不要这段代码 , 那么就 在串口与用户进行交互, 接受用户在串口输入的命令, 比如用户要求下载文件在 FLASH 中指定的位置 等,具体内容可参考U-BOOT 之类的开源项目到这里为止, BOOT 部分已完成,这个 BOOT 非常简单,仅仅只是将 PC 机上传下 来的文件固化到 FLASH 中,然后再将 FLASH 中的操作系统内核部分加载进内存中, 并将 CPU 的控制权交给操 作系统,下一页开始讲解如何写一个最简单的操作系统,呵,到现在才开始切入正题呢! 如何实现一个最简单的操作系统这里为了简单,就不考虑可移植性开求,不从 BOOT 部分来接收参数,也不对硬 件进行检测,也

10、不需要进行 DATA 段,代码段的重定位。我只是读了 LINUX 内核相关部分,并 未自己去实现一个操作系统,所以我以下所说的只是概念性的东西:1. 接管系统的中断处理,由于 BOOT 部分的代码决定了那个中断向量表,从而决 定了系统中断之后进入的内存位置,但 BOOT 并不知道操作系统的中断处理函数位置所在啊, 怎么办呢?有几种方法, 其一是:如果你的板子可以重映射地址, 也就是可以将内存条所在 的位置重映射成 0x0开始, 那么在链接内核的时候, 就将操作系统自己的中断向量表定 位在 0x0处并且在 BOOTLOADER 引导结束时就完成映射操作,并让 CPU 跳转到 0x0处执行; 如果

11、没有重映射功能, 我就不晓得怎么办了, 不过我想到一个折衷的办法, 就是在 BOOTLOADER 启动完成时(也就是将 CPU 控制权交给操作系统内核时,重新改写 FLASH 的 0x0区域,就是将操作系统的内核的中断向量表写入 FLASH 区的 0x0处, 比如, 当一个 IRQ 发生时, CPU 决定了会跳入 0x18(假设这里 FLASH 占用地址总线 0x0至 0x0fffffff, 内存占用 0x20000000至 0x2fffffff , 而 BOOTLOADER 在最后将 0x18处的代码修改成了 0x20000000加上 0x18的地址 处的代码,而这个地址就是内核的中断向量表

12、中的相关跳转指令, 就相当于跳转进了内核所关联的 IRQ 处理函数的地址上去执行中断处理函数了, 而这样的不好之处在于:当系统重新上电之后, BOOT 的中断向量表已经被修改, 除非 BOOT 本身不使用中断, 呵, 在这样简单的系统中, BOOT 是不需要中断功能的2. 这里为了简单, 所以没有使用分页内存管理, 就不需要建立页表等操作, 直接 进行操作系统的堆栈设置,同 BOOT 一样的设置过程一样,接着就进行 BSS 段清零操作, 这里的 BSS 段是指操作系统自身的 BSS 段,与 BOOT 的 BSS 段是同一个含义只是用在了不同的 地方了,接着就跳入了 MAIN 函数3. 为了最大

13、可能的简单, 采用静态建立任务结构数组, 比如只建立十个任务, 那 么首先要为这十个任务结构分配段内存, 可以在堆上分配 (这个分配的内存直到操作系统 结束才会被释放, 当然也可以指定一片操作系统的其它地方都用不到的内存区域, 不过这 样写的话就有点外行的味道了, 而符务结构数组的指针却是全局变量, 存放在 BSS 段或者 DATA 段,由于在上一步中已经分配了一个系统堆栈, 那么我们这十个任务就分享这总体的 堆栈区域这里的重点就是如果定义每个任务结构数组里面的结构, 可以参照 LINUX 的相关 部分设计4. 中断处理:在第一步中已经确定了 CPU 进行相关的几类型的中断跳转地址, 而 相同

14、类型的中断却只有一个入口地址,这里的中断处理就会完成以几个动作:其一:入栈操作,包括所有寄存器入栈,至于这个栈,就是在第二步中所设置的 IRQ 栈,其二:屏掉所有中断,呵,这里为了简单起见,所以在处理中断时不允许再次发 生中断其三:读取中断相关的寄存器, 判别是发生了什么中断, 以至于跳进相关的中断处理函数中去执行(在这里只包括两种中断,一是时钟中断,另一个是 SWI 中断,也就 是所谓的系统调用时需要用到的其四:等待中断处理完成,然后就开启中断并出栈,恢复现场,将 CPU 控制权交 给被中断的代码处注意:其一:在 MIAN 中必须首先确定整个系统有哪些需要处理的中断,也就是有哪些 中断处理函

15、数,然后才编写这里的中断处理函数其二:本操作系统不处理虚拟内存, 其至连 CPU 异常都不处理 (一切都为了简单 , 一旦发生异常,系统就死机5. 对 TIMER 的实现, 首先确定时间片, 为了让系统更稳定, 而且我们不需要实时 功能,尽可能让时间片设置长一点,比如我们让一个任务运行 20个时钟滴答数,然后应 根据系统频率来确定每个系统滴答所占用的毫秒,这里使用 5毫秒让系统定时器中断一 次,那么就需要写时钟寄存器, 具体参阅芯片资料, 计算下来, 一个任务最大可能连续运行 100毫秒,注意:我们的操作系统不支持内核抢占,同时只支持两级中断优先级,就是只 有时钟中断的优先级高一点, 其它的优

16、先级都低一级, 但是在中断处理一节中却屏掉了 这个功能因为一进入中断处理, 就禁止中断, 所以不管其它中断优先级有多高都没有用的, 这样做优点是简单了, 但不好之处显而易见, 特别在相关中断处理函数如果进入了死循 环,那么整个系统就死了, 而且时间片也变得不准确了, 反正都不用实时, 也不需要实时 钟支持嘛至于中断优先级设置请参阅芯片资料6. 进程调度的实现,也就是 do_timer函数(时钟中断处理函数,有一个全局 变量指针,指向的就是当前任务结构数组(或者链表,当时钟中断时,就进入此函数中, 首先判断任务结构体中的时间片是否用完,如未用完,就减一,然后退出中断,让 CPU 继续运行当前的任

17、结构, 若用完了时间片, 就重置时间片, 并重新寻找任何结构数组中的下 一个等待运行的任务,若找到了,就切换至新的任务,至于如何切换,请见下一页描述, 如果未找到就切换到 IDLE 任务(类似于 LINUX ,呵呵,所有的处理就是模仿 LINUX ,由于 本人水平太差,所就不能自创一招,注意:为了简单,所以没有实现任务优先级,也未实 现任务休眠等, 也就是说只要静态地决定了有十个任务, 这十个任务就按先后顺序一个 一个执行而且每个任务都不允许结束, 就是说在每个进程中的最后一句代码都必须用死循 环,不然的话系统就跑飞了,还有一点,进程不支持信号,没有休眠与唤醒操作,这个 CPU 就是不停地在运

18、行,呵呵,反正 CPU 又不是人,所以不需要人权的哈!这种调度 是不是简单得不能再简单了?!7. 串口不使用中断, 这就是最大可能的降低难度, 串口使用论询的方式来实现读 写(当然是阻塞的方式了哦, 而且只有写, 不允许读, 因为读的时候需要涉及到采用中 断方式,因为轮询方式有个不好的地方, 那就是正在读的时候, 这里有可能当前进程的时 间片用完了, 系统切换到另一个进程, 这里你在 PC 机的串口输入的数据就丢弃了, 唉, 又是为了简单嘛8,最后一步就是 MIAN 函数的最后一部分,将本进程当作 IDLE 进程(相当于修 改任务结构数组中的数据,开启中断,将当前进程加入一段死循环,以免它退出

19、去。 9. 编译你的 BOOTLOADER , KERNEL ,并烧写至 FLASH ,反复调试10. 至此将你的 at91rm9200(或者是其它相类似的芯片的串口接上 PC 机,打 开超级终端,打开板子电源,说不定你的操作系统就打印出了 "hello,world" 了!一个最 简单的操作系统就出来了任务结构数组(或链表的实现我们的任务结构就采用链表形式吧, 但其长度是限定了的, 头指针是一个全局指 针变量(指针变量是一个无符号整型指针, 其指针本身所在的地址是在 BSS 段, 但其指向 的内容是分配在堆上的一片内存,分配内核内存的函数就用 kmalloc 吧, kmal

20、loc 函数需 要自己编写呵,为了简单,这个函数只接受一个参数,就是所需分配大小,这个函数做得很 简单,首先有一个全局针指, 它在初始化时指向了整个堆的起始位置, 并且固定大小, 就是 所谓的内核堆栈, 在内核堆栈之后就是用户堆栈, 由于总共有十个任务, 当然不包括内核本 身的任务,所以整个堆栈就平均分成十一部分, 注意:在所有任务初始化完成之后, 还有一 个步骤就是将内核这个任务移到用户态, 相当于要将自己的任务结构的堆栈指针修改一下就 行了,判断大小是否超出了内核堆的可分配范围, 还有一点, 需要维护内核堆和其它任 务的堆,需要进行分块,并且有一个全局的内存使用标识,就用数组吧,简单, 0

21、表示相 应的内存部分未占用, 1就表示占用,对应的 kfree 就相当于把标志置 0,对于内存的维护,比较复杂,为了简单,就定为 4K ,并且不能进行大于四 K 的 内存申请,因为大于 4K 之后,由于没有虚拟地址的概念,就不能实现堆上的连续分配地址,当 然在栈上分配是可以大于 4K 的,栈是由编译器和 CPU 所决定了的任务结构包括:1. 所剩的时间片2. 本任务所指向的代码段内存地址,这里也就是函数入口地址3. 本任务所指向的数据段地址, 这里的数据段被包含进了整个内核中, 所以并没 有用,作为保留4. 本任务的函数体是否存在,也就是否会被调度5. 本任务所使用的栈指针6. 本任务所使用的

22、堆指针7. 本任务的标识,用 0代表是 IDLE , 1代表是其它进程8. 所有寄存器的值9. 当前 PC 值,初始化时被置成了函数入口地址首先讲解一下任务数组结构的初始化:将先定义一个全局指针,然后将此指针强制转换为一个任务结构指针,并通过 kmalloc 函在内核所占用的堆 (前而讲过内核的堆的起始就是整个堆的起始 上去分配十个任务结构 所占的内存,这里是绝不会超过 4K 的并且为这十个任务结构赋值,将第一个任务置为 IDLE ,时间片为 20,代码段内 存地址为 main 函数的的地址,数据段地址忽略,函数体存在,可以被调度,栈指针指向的位置根据以下来计算:假定每个给每个任务可使用的堆栈

23、设定为 64K ,而整个堆的起始位置是 0x20030000, 那么第一个堆指针所指向的就是 0x20030000, 栈就是 0x20030000+64K的位置,第二个以后就以 此类推注意:在初始化任务结构之前,不允许系统使用堆,但可以使用栈,那么内核任 务栈部分就分成了两个, 在未进行调度之前, 栈就是上一页中第二步中所设的栈, 那么上一页设置 堆栈的时候就得注意必须将堆栈空间设成十个 64K 再加上在本步骤使用以前的最大可能所需的栈 空间再讲解一下任务切换时所要做的事情:进入整个中断处理入口时, 会将所有寄存器推入 IRQ 栈之中, 并把值拷贝到当前 任务结构相应的字段当中,并取出被中断的

24、进程的当前 PC 值存入当前任务结构中的相应字段中,接 下就判别中断类型,以进入相应的中断处理函数,这里就会进入 do_timer函数中,以下就是进入此 函数之后的流程:内核中还有一个全局指针, 就是当前任务指针, 它本身也是在系统 BSS 段中, 它 的定义如上一步中的那个全局指针一样, 当由系统时钟中断之后, 就取出这个全局指针, 上一步初始 化完成之后,还会把这个指针指向第一个任务结构所在位置,也就是 0x20030000处,那么就取出 这个任务结构中的时间片字段,判断其是否为 0,若为 0, 就进行以下的操作:保存用户态下的栈指针至当前任务结构, 保存堆指针, 并将搜索一下可以被调度

25、的任务结构,并将此任务结构赋给当前任务指针, 置需要进行任务切换标识, 此标识同样是一个全 局变量,但它是被赋了初值,会放在整个系统的 DATA 段中,返回 do_timer函数。若不为 0,就进行以下操作:将时间片减一,返回 do_timer函数接下来判断任务切换标识,若为 0,则进行以下操作:不需要进行任务切换,所有寄存器出栈(这里的栈指的是 IRQ 栈,重新开启中 断,切换到用户模式,加载当前任务结构中的当前 PC 值字段,以退出中断处理程序若此标识为 1,则执行以下操作:就需要进行任务切换,让所有寄存器出栈(这里的栈指的是 IRQ 栈,将当前任 务结构中的所有寄存器的值恢复到相应寄存器

26、中,将用户态下的栈指针恢复至当前任务结构栈指 针,将堆指针恢复至当前任务结构堆指针,并把需要进行任务切换标识恢复为 0,重新开启中断,切 换到用户模式,任务切换是通过加载 PC 值来实现的, 也就是通过加载当前任务结构中的当前 PC 值字 段,以退出中断处理程序系统调用的实现本系统是完全可以不实现系统调用的, 因为没有实现内核态和用户态的保护, 完 全可以不实现自己的 C 库, 所有的函数都像 kmalloc 之类的实现一样, 在内核中直接写函数原 型,但为了以后扩展,还是说一下系统调用,这里以 malloc 系统调用来实现首先说明还有一个堆指针 (前面在 kmalloc 时有一个堆指针, 不

27、过那个堆指针是 为内核任务,中断处理所提供 , 这里这个堆指针是用于用户态的, 它在系统初始化完成之前会 赋上初值,其初值就是第一个任务结构所使用的堆的起始位置,也就是在内核所使用的堆加上 64K 的位置函数库中的 malloc 函数实现步骤如下:1. 首先检测申请大小是否超出了 4K ,若超出 4K ,就返回错误2. 进行系统调用(这里用 _syscall1,并只传递一个参数(所需分配大小 系统调用函数 _syscall1的实现:1. 将寄存器压入堆栈(这里的栈指向就是当前任务的栈2. 将系统调用号 1放至 R0,参数放入 R13. 发出 SWI 指令以产生 SWI 中断(就是所说的软中断,陷阱此时系统发生中断, 会进入 SWI 中断处理入口, 下面说一下 SWI 入口函数的实现1. 取出 R0的值,判断其值,进入相应的分支处理代码段2. 在此进入 _mall

温馨提示

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

评论

0/150

提交评论