专业编程基础技术教程

网站首页 > 基础教程 正文

基于ARM的自制最小操作系统Open-OS:源起

ccvgpt 2024-11-26 00:56:19 基础教程 1 ℃

嵌入式软件开发是一种软硬件结合非常紧密的职业,门槛较高,需要掌握的知识点较广(软件和硬件相关都要学习),汇编、C语言、编译相关、计算机硬件相关、操作系统原理、数字电路相关、驱动相关等等。

而学习过程中最困难的当属操作系统和处理器相关。不管是流行的linux操作系统还是ARM微处理器,源码和数据手册都是以千计、万计,体量太大,学习起来容易迷失在这个“大森林”里,也容易望而生畏,导致失去兴趣。对此,很多初学者或者单片机转linux的开发人员应该是深有体会。可能很多读者学习linux kernel一上来就从_strart_kernel_函数开始,细节越展开发现需要的知识点越多,最后自己也不知道该从那里学起。同样我也深有感触,学习嵌入式相关以及操作系统一定要有个整体认识,然后再深入细节。因此想写些相关的技术文章,一是对过去自己知识的总结和回顾,二来也希望能够帮助到正在学习的嵌入式的朋友。

基于ARM的自制最小操作系统Open-OS:源起

ARM Open OS

为了能够让读者有一个更好的学习体验,帮助读者理解操作系统的一些基本概念和实现原理,特别的针对ARM处理器设计了一个最小的简单的操作系统Open OS。在介绍ARM Open OS之前,本文将介绍一些基本的概念,为后续OS的编写打下基础。

ARM处理器基础

计算机的一般模型

在进行嵌入式编程之前,首先就要清楚的认识处理器的一般模型,需要从程序员编程的角度去理解。一般的处理器模型(存储程序计算机)如图1所示:

中央处理器或者常说的CPU是通过寄存器来运行程序和处理数据的,不同的处理器所包含的寄存器的数量和名字都是不一样的,但是CPU的寄存器的功能基本都是一致的,因此CPU寄存器的值基本就决定了CPU的行为。例如CPU的内部有个寄存器PC程序计数器,PC寄存器的值就决定了程序的走向(CPU根据PC寄存器的值来取指令,程序状态寄存器决定了指令的相关跳转等等)。

上述可以得出一个很重要的结论,相关寄存器的值代表了当前时刻CPU的行为

ARM处理器模型

ARM处理器内核如图2所示,箭头代表了数据的流向,上下两边的直线代表了总线。

  1. 图中有两条总线,Data和Address。数据通过Data总线进入到寄存器(这里的数据包含了指令和数据)。数据和指令共享一条Data总线(冯.渃伊曼结构)。
  2. 对于存储器(内存)访问,只有load、store、swap指令可以对存储器中的数据进行访问。Load指令从存储器复制数据到CPU寄存器。Store指令从CPU寄存器复制数据到存储器。没有直接操作存储器中的数据的指令,数据的处理只能在CPU的寄存器中进行。

ARM流水线

ARM处理器使用流水线来增加处理器指令执行的速度,不管是3级流水线还是5级流水线、13级流水线。再复杂的指令的流水线都包含了下面3个阶段执行。


  • 取指
  • 译码
  • 执行

3阶段流水线如图3所示。

这里需要重点指出的是:程序计数器(PC)指向被取的指令,而不是指向正在执行的指令。也就是说:PC=当前指令的执行地址+8。这个是ARM所有流水线的结构特征。

CPU正常的操作过程中,在执行一条指令的同时,对下一条指令进行译码,并将第三条指令从存储器中取出。

说明:PC的值非常重要,在异常切换时常常需要通过PC来计算返回地址。

ARM处理器有两种操作状态:

ARM状态和Thumb状态,这两种状态的PC值计算存在区别,后续我们都是围绕ARM状态来学习。

ARM汇编语言基础

本节仅描述gcc内联汇编的基本语法,具体的ARM指令读者可参考其他相关的资料。

GCC内联汇编的一般格式:

asm volatile(

汇编语句

: 输出运算符列表

: 输入运算符列表

: 被更改资源列表

);

volatile:可选

举例:

int main(void){

unsigned int sp=0,ip=0;

asm volatile(

"mov sp,%0\n\t"

"mov pc,%1\n\t"

:

:"r" (sp),"r" (ip)

);

return 0;

}

上面的代码中第一条mov指令,该指令将%0赋值给sp。这里符号%0代表出现在输入运算符列表和输出运算符列表中的第一个值。%1代表出现在列表中的第二个值,依此类推。所以,在该段代码中,%0代表的就是“r”(sp)这个表达式的值了。

“r”(sp)这个表达式中,sp代表的正是C语言向内联汇编输入的变量,操作符“r”则代表sp的值会通过某一个寄存器来传递。在GCC中与之相类似的操作符还包括“m”、“I”,等等,其含义见下表:

多任务机制

Windows任务管理器

现实生活中使用电脑,我们能够做到可以一边视频网站看视频,同时一边可以通过社交软件和网友聊天,是因为windows操作系统支持多任务机制。在windows的任务管理器下可以看到多个任务的列表。

那么如何简单的理解多任务机制呢?

假设你是雇主,家里只有一个保姆(CPU),那么保姆上班时间干些什么将由你来决定。现在需要给保姆安排两个任务:客厅打扫卫生和厨房洗菜。可按照下图5来安排。

在图5中,首先将保姆的上班时间(假设是8个小时)分成等长小段时间,然后按照时间长度轮流执行安排的两个任务(打扫卫生和洗菜)。只要这个保姆做事的速度足够快,切换时间足够短,你会感觉到这名保姆同时在客厅和厨房里干活。这就是宏观上的并行执行,也就是多任务的运行机制。

在T4时刻,两项任务都已经完成了,那么保姆没事可干了,就处于空闲状态,保姆等待雇主重新安排任务。

假设现在需要给这名保姆安排三个任务:客厅打扫卫生和厨房洗菜、洗衣服。多任务机制演示如图6所示。


任务上下文

什么是任务上下文?

简单的理解了多任务机制之后,再来看一个关键的概念:任务上下文。

要理解任务上下文,首先就要理解什么是任务?按照上面的例子,任务就是保姆现在正在做的事情(例如正在客厅打扫卫生),是一个动态的概念。

那么任务上下文就是保姆在客厅打扫卫生过程中某一时刻的状态,这个状态就是卫生打扫到那个具体位置数据(例如客厅拖地拖到了一半的位置)。有了这个具体位置的数据,雇主可以随时的打断保姆的客厅任务执行或者安排新的任务。当保姆再次执行客厅打扫任务时,只要按照上次的打扫位置记录继续执行就可以了。

延伸

任务和进程的关系? 其实某种程序进程就是任务,任务就是进程。进程就是进行中的程序。

linux的进程描述符(task命名)
task_struct{
... ...
}

处理器的角度

前文提到了一个非常重要的一点:寄存器的值代表了当前时刻CPU的行为

任务可以看作是用户程序在处理器上的运行,是一个动态的概念。任务上下文可以理解为CPU的“寄存器数据的快照”。任务上下文是和CPU密切相关的概念,不同的处理器有不同的处理器上下文定义。

比如在ARM处理器中,设计多任务操作系统时,会把大部分的硬件寄存器作为任务上下文的内容。ARM处理器的寄存器程序员视图如图7所示。

寄存器的具体含义这里就不介绍了,感兴趣的读者可查阅相关的资料。

任务切换

有了任务和任务上下文的概念,那么任务切换就很好理解了,任务切换其实就是上下文切换。

任务切换的过程

还是以上文保姆的例子来讲,假设保姆在执行“客厅打扫卫生”的T1时刻,保姆停止任务执行,同时将当前任务的上下文(也就是客厅打扫卫生的具体位置数据记录并保存下来),然后保姆进入厨房查看任务“厨房洗菜”的上下文(菜洗到哪里数据),根据“厨房洗菜”的上下文的内容继续执行“厨房洗菜”任务。这个过程就叫任务切换。

ARM处理器的角度来看下任务切换的具体过程:

从上文我们知道任务的上下文其实就是寄存器的数据快照。那么ARM处理器中比较重要的寄存器是PC和SP。PC是当前任务运行的具体位置,SP寄存器表示的栈的指针,当然还有一些其他的重要寄存器,这里不一一列出。

任务A和任务B的切换

假设系统中有两个任务A和B;当前的处理器正在运行A任务,那么此时任务A的上下文情况如图8所示:

这时发生任务切换,那么首先做的就是保存当前任务A的上下文(寄存器数据快照),将CPU的寄存器内容保存到任务A的栈中,这时任务A的运行情况如图9所示:

任务A的上下文保存完成之后,接下来要做的就是将任务B的上下文内容恢复到ARM处理的寄存器中(退栈操作),图10所示。

最后,退栈完成后,ARM CPU开始执行任务B,此时CPU的运行情况入图11所示。

最后

到了这一步,相信读者已经对多任务操作系统有了一个基本的理解了,那么下一步如何编程去实现呢?

下一步计划:编写汇编代码,使用qemu模拟ARM处理器来实现最简单的OS。敬请期待!

关于操作系统的一些概念的描述,本文描述的不可能尽善尽美,但是基本已经做到了通俗易懂。如有错误或者不妥之处,敬请广大读者批评指正。

参考文献:

ARM System Developer's Guide

ARM汇编程序分析与设计

Ucos操作系统分析

Tags:

最近发表
标签列表