分享

如何从零开始写一个简单的操作系统?

 文明世界拼图 2018-05-30
我来写一个如何在15天内完成一个嵌入式实时操作系统,并移植到stm32单片机的攻略吧。第一次看到这个问题是在大概两个月之前,从那时候开始决定自己也写一个操作系统,于是开始看os的基本概念,进程切换,任务调度,内存管理,任务通信,文件系统等等。


前言:
大约在两个周不到前动手,平均每天7个小时,从完全不知道怎么下手(真的快哭了),到现在完成了一个----基于优先级时间片调度,具有信号量,消息队列,内存管理的嵌入式系统并命名为--LarryOS,并且移植到stm32(Cortex-M3架构)上,还在stm32上实现了一个malloc和free函数,受益匪浅。现在还正在完善。我是用自己命名的,当然,大家写好了也可以用自己命名,图个开心么 ,想想是不是很激动啊。

对于这个问题下的回答的看法:
大神真的好多,几乎都是x86型的,难度真的要比我的大,不得不承认。不过,我觉得对于新手,写那样一个系统真的是太艰辛了,比如我这种入ee坑的在校大三学生,课真的太多了,周内最少三节课,而且和cs没有一毛钱关系,课余时间太少,如果花费一个学期或者三个月的时间来写,可能有些得不偿失。因为许多想写os的朋友,和我一样,一是觉得写os很酷,二是想通过写来理解os,毕竟看书看了就忘,也没有衡量自己学的好坏的标准。因此,选择写嵌入式系统,是一个非常好的选择 。

知识储备:
这个问题想必是很多同学顾虑的,到底写一个嵌入式系统需要什么知识储备?我从自己的经历出发,并加以简化,大家可以参考一下。

自身版:
1c语言要求:我自己在大学里几乎没学过c语言,上机几乎10道题会2道,期末全靠背。后来开始自学了c,看了c语言程序设计现代方法,c和指针 c专家编程 第一本书的课后题,做了3分之2吧,就这样,没了
2汇编要求:会基本的x86指令,仅仅是基本,看了王爽的汇编语言,程序写的很少,仅仅上机课写过,不过能很快写出来。
3微机原理或者计算机组成原理:老师上课太坑了实在,我自己看了csapp的前四章大概,豁然开朗,推荐这个。
4操作系统:看了三个礼拜os的基本概念,仅仅是了解概念,没办法深入,也没地方。。。。
5数据结构:看过浙大的数据结构公开课的3分之2,会写基本的链表,队列,最基本的树和树的基本的遍历办法,图完全不会
6单片机基础:大约两年的单片机基础
7新手看到这里,可能已经慌乱了。。。。不要怕,我说的很多东西,写嵌入式系统用不到啊 ,继续,发一个精简版

精简版
1:c语言 能把我推荐的c书的第一本或者c primer之类的书看个一半,或者自己本身知道指针的概念,知道结构体指针,函数指针,二级指针的用法,仅仅是概念和写法就行了,不用深入,用不了多久。
2汇编:知道有几条常用指令,用法和功能是什么就可以了
3组成原理:知道中断是什么,入栈和出,寄存器,知道汇编对应的计算机大概动作就可以了,用不了一个周
4操作系统:找个公开课或者书,看看大概的os概念,一个周就够了,我现在很多概念还是不知道,后面可以慢慢学
5数据结构:会写链表,队列就行了,我们不写文件系统,不用树
6单片机基础:用过单片机,不用自己写驱动代码,仅仅可以在别人的驱动下,编写一些简单的逻辑代码就行,完全没学过的人,两个礼拜就可以用stm32来编程了,如果之前学过51,一个礼拜不到即可,因为我们只是借助一下单片机,避免使用虚拟机,方便操作系统的调试。因为我们可以用单片机,输出某些信息在串口或者液晶屏,让我们直接看到代码的错误点,方便我们调试。

因为很多大一的新生想写,推出一个极限版~~
极限版
1.学过C,知道有指针这个东西,其他的边做边学。
2.不懂汇编,边做边查,边查边写。
3.知道什么是寄存器,知道中断的概念
4.知道OS是由什么组成的
5.数据结构完全不会,遇到链表,队列时再查,或者直接抄也行
6.不学如何使用单片机,遇到再查


发一个很装逼的封面助兴


正文:

一、开发环境

对我来言,这倒是很重要的一点。第一次萌生想写OS的想法时,在网上搜索了不少资源,大多数都是在叙述框架,如何构建一个操作系统。然而对于当时的我来说,根本不知道用什么平台来写,如何调试自己的程序,看了一些朋友的发帖,推荐用XX模拟器,在XX平台开发,完全看不懂,界面好像也很老旧,而且大多是英文,当时有点敬而远之。自己手上只有个codeblocks软件,想来想去也不知道怎么用这个开发OS。直到有一天,突然顿悟,OS不就是一堆程序,我还打算让他运行在单片机上,那么用单片机常用的开发工具不就行了!!!---------Keil uVision5

学过单片机的朋友,相信非常熟悉这个软件,使用起来也非常简单,网上随便一查,就可以下载和安装了,这里就不详细展开说了。如果不知道如何使用的,先熟悉一下这个环境,再开始写,相信半个小时即可上手。



二、参考资料

既然是运行在真机上,必然要对它有所了解,我们这里采用的是STM32(Cortex-M3架构),市面上非常火热的一款,资料丰富,大家有什么问题谷歌一下,

1.Cortex-M3权威指南(中文版),这本书会详细的讲解,中断处理,异常,ARM汇编等知识,我们会在任务切换的时候用到。(PDF即可)


2.嵌入式实时操作系统ucos(邵贝贝审校),ucos中有很多我们可以借鉴的地方。

3.谷歌,有一些基础知识遗忘的时候,谷歌可以让你很快补充上来







三、从写一个最简单的os做起

我们这里假设我们写一个支持32个任务并发执行的简易OS

1.任务就绪表

我们假设这里任务有两种状态,就绪态和非就绪态。

我们定义一个32位的变量OSRdyTbl(就绪表),它的最高位(第31位)为最低优先级,最低位(第0位)为最高优先级。OSSetPrioRdy()函数的功能是,你传递一个数(优先级),把这个优先级对应的任务设置为就绪态。同理,见图:

OSDelPrioRdy()函数将某任务从就绪表移除

OSGetHighRdy()函数选出最高优先级的任务

这里我们就完成了,设置某任务为就绪态,从就绪态删除某任务,获得最高优先级任务的任务。

2.任务控制块

想必这个概念大家很清楚了,每个任务都对应一个任务控制块,典型的用处是,任务切换时,通过控制块来获知每个任务的堆栈(因为控制块有指针指向该任务的堆栈)

此外,再定义几个变量。

注释写的很清楚,不解释了!

3.主堆栈

在此STM32中,提供两个堆栈指针,一个是主堆栈(MSP),一个是任务堆栈(PSP),可以通过查Cortex-M3权威指南(中文版)得到。所以我们既要建立一个主堆栈,又要为每个任务建立自己的堆栈(一个任务一个),这里我们先不管任务堆栈,只看主堆栈。

OS_EXCEPT_STK_SIZE是个宏,大家可以自己设定,我这里设的是1024,一定要尽量大一些。为什么?因为裸机下,进入中断服务程序时,系统会把许多寄存器入栈,而且支持中断嵌套,也就是刚入栈完又入栈,所以有可能会堆栈溢出,非常危险。

CPU_ExceptStkBase大家先别管,它指向的是数组最后一个元素。

4.建立一个任务

我们通过Task_Create()函数来建立一个任务,第一个参数用来传递该任务的任务控制块,第二个参数用来传递函数指针,第三个传递该任务的堆栈。tcb->StkPtr=p_stk这句将该任务控制块中应当指向栈顶的指针,指向了该任务的新栈顶(前面定义了TCB,自己可以翻一翻),在写该函数时,一定要看Cortex-M3权威指南,不然你怎么知道有这么多寄存器,而不是仅仅从R1到R7?看到这里,还想看懂的,应该都是真的想写OS的朋友,这里有不懂的去看Cortex-M3权威指南,你会豁然开朗的。这里,Task_End是一个几乎永远不会执行的函数,何时会执行,先不管了,看它的内容。

5.欢迎来到主函数

①第一行:在该主函数中,第一行我们让主堆栈指针指向了主堆栈,那么,这个主堆栈是哪里来的呢?很简单,我们自己定义的,哈哈。

很熟悉吧,前面发过这个图,大小你来指定,注意要大!!!!!

②第二、三行:建立一个任务,第一个参数传递了该任务的控制块,第二个参数是该任务的任务函数,第三个是堆栈(数组最后一个)

是不是很好奇,任务1和任务2是什么?一起了来看

大家自己随意定义,开心就好。

Task_Switch()函数又是什么呀?

这里Task_Switch()是我们用来测试的程序,当任务1运行时,完成i++后,将最高优先级设置为任务2,并用OSCtxSw()切换到任务2,OSCtxSw是用汇编写的,是一个隐藏BOOS,我们先不管。

③第四行:程序刚运行时,是没有最高优先级的,所以我们用p_TCBHighRdy=&TCB_Task1;来随意指定一个任务为最高优先级

④:最后一行:OSStartHighRdy()该函数也是汇编,和OSCtxSw()并称为2大BOSS,我们会在后面解密。OSStartHighRdy和OSCtxSw很相似,不过OSStartHighRdy()用于当所有任务都没有运行过时,用于初始化(当然具有任务切换的作用)并成功运行第一个任务,而OSCtxSw()是在OSStartHighRdy()之后,使用OSCtxSw()时,最起码有一个任务已经运行了(或正在运行)。

6.完工?

到这里,从宏观说已经基本完工了,这就是一个简易的OS的基本状况。看到这里,大家可以休息一下了,消化一下,马上有BOSS要出现了,解决那两个BOSS后,就真正做成了一个简易的OS。


7.两大boss之OSStartHighRdy()函数

7.1任务视图

终于开始了任务切换环节,这是我画的一副任务切换图,自我感觉非常好,不过大家应该看起来很困难,字太丑了~~~~

通过分析上面这张图,来确定如何写OSStartHighRdy()函数:

①中:此时有一个任务1,但是任务1我们仅仅是建立了,并没有让它运行。这里我们认为任务1是由三部分组成:任务代码,任务堆栈,该任务的任务控制块。

②中:我们想让任务1运行起来,任务1是什么?就是一堆代码,如何运行起来?-----让PC(程序计数器)指向任务代码即可(依靠出栈POP)。同时,我们还要让SP指向任务的堆栈,这里的SP当然是PSP(任务堆栈)

7.2写OSStartHighRdy()函数

下面开始写OSStartHighRdy(),它的功能就是上图的①和②

2,3,4,5行中的那些数字,是外设对应的地址,我们往相应的地址写值,即可完成某些目的。12,14,15行是我们之前定义过的几个变量,忘了的回头翻一翻。17行是将OSStartHighRdy()函数extern了一下,因为我们在主函数要用。

上图就是OSStartHighRdy的内容,我们一起来看。28,29,30行设置了PendSv异常的优先级,问题来了,什么是PendSv???

Cortex-M3权威指南中其实讲了,很详细,这里为了缩减篇幅,不详细说,大家只用知道PendSV 异常会自动延迟任务切换的请求,直到其它的中断处理程序都完成了处理后才放行。而我们只用触发PendSV异常,并把任务切换的那些步骤,写在PendSv中断处理任务中。

7.2.1PendSv处理程序

经过39和40行,我们往控制器里写值,触发了PendSv异常,现在程序会进入异常处理程序,就是下图:

在此函数中,如果PSP为0,则进入OS_CPU_PendSVHandler_nosave()函数,其实在OSStartHighRdy我们将PSP设置为了0,所以必然会进入OS_CPU_PendSVHandler_nosave()函数。

7.2.2OS_CPU_PendSVHandler_nosave()函数

在该函数中,我们让p_TCB_Cur指向了最高优先级的任务,因为有出栈,所以重新更新了SP。

7.2.3恭喜

到了此处,一个任务已经可以成功运行起来了!!!!!!

BUT only one task!我们需要让它多任务切换

7.3写任务切换OSCtxSw()函数

与OSStartHighRdy非常相似,也是往中断控制器里写值,进入PendSv异常。

7.3.1任务视图

依然是这张喜闻乐见的图!!!!!!!

③:任务1要切换到任务2,因为待会要进入任务2,会破坏任务1,所以我们要保存任务1的现场,把寄存器入栈

④:因为任务1有入栈动作,栈顶肯定变了,我们修改任务1控制块的值,让它指向新的栈顶

⑤:什么都没有,只是想说明。我们建立了一个任务2,仅仅是建立了。

⑥:很简单,要想让任务2运行,则让PC和SP分别指向任务2的任务代码和任务堆栈即可。

⑦:什么都没有,只是想说明,任务2活的很开心,可以运行了

7.3.2由OSCtxSw()进入PendSv异常

与OSStartHighRdy不同的是,这时的PSP肯定不为0了,所以不会跳转,会顺序执行55行以后的程序,也就是任务视图里的③,继续执行④。执行完60行的程序后,继续顺序执行,执行下面程序:

和OSStartHighRdy函数的后续步骤几乎一样,找到优先级最高的任务,让指针指向它即可!!!!

1.大功告成!!!!!!!!

到这里,我们已经彻底完成了一个简易OS的设计!!!!

8.1如何查看成果?

8.1.1增添程序

我们在主函数中加入一个函数

大家可以把它理解为一个库,调用之后,我们就可以在串口(屏幕)显示某些字符了。

同理,在任务1和任务2中加入printf函数

8.1.2编译并下载程序

8.1.3利用串口调试助手观察

网上搜串口调试助手,会有很多工具,随意下一个就OK!

可以看到,按照我们的预期,任务1和任务2轮流输出字符在屏幕上!

8.2如何调试程序?

8.2.1调试器

必然是神器--J-link或者ST-link,一个大概50,嵌入式开发神器,记得以前刚开始玩单片机的时候,调试全靠打印消息在屏幕,觉得好用的不得了,经常有人给我说,你用调试器调试啊,我都鄙夷的回一句,需要么?(哈哈,那时确实不需要) 等开始写OS时才知道,这东西真是救命稻草,没有它,怎么看寄存器的值和异常返回的值呢?

8.2.2界面

点击DEBUG后,你可以看到寄存器的值。想想我们之前要入栈,出栈,如果哪一步错了,自己估计把串口吃了,也看不出来吧,哈哈!!!!!!

单步调试什么的,不说了,用的很多。


9.写到此处的感想

答主因为写这个OS,在凳子上久坐了两周,这两天腰疼,只好躺着写这个回答了!哈哈!

也正好因为腰疼,感觉时间比较多。不过和想象的真的不一样,本来觉得可以一气呵成,结果短短的篇幅,就写的自己的思维都大乱了,而且也挺费时间的,前后用了有6个小时了。想写OS的朋友,参考上面的步骤,加上自己琢磨,应该也能写一个出来。如果哪里有不清楚的,不要心急,如果真的是一两天就写成了,还有什么锻炼的意义,有点失去初衷了。不懂的就去查权威指南和OS的书籍,相信你会收获的非常多!




为什么要写这个回答?
这是我写了6个小时多以后来补充的问题,因为我自己也纳闷了,放着自己的事不做,跑来写这么一堆干什么........刚才路上走着想到答案了----------想留个纪念。还有不到两个月就要寒假了,我打算考研,也就是说这是大学里做的最后一个项目了(毕设除外),真的挺伤感。从大一一路折腾过来,现在要突然一年不能折腾,简直泪流满面!!!!!!!!!!!!!!!!!!希望我这个简单的开头,能让想写OS的新人得到一丝启发。

操作系统能学到什么?
这也是我想写这个回答的原因,对我的改变挺大
前几天有个好朋友(大一开始和我一起学单片机的朋友,后来他一直在做单片机项目,却没有补过任何基础知识),打电话问我XX模块怎么用,其实是很简单的一个问题,直接百度都可以,但是他还是想要来问下我,我说很简单,就XX做就可以,有问题你再百度,但是他好像不想听到这种回答,想让我说到他一听就知道怎么做了,才敢开始做,不然就不敢启动项目。那时我才突然意识到,以前的我就是这样啊,做什么项目做什么东西,老是想要集成的模块,资料丰富的模块,如果没有什么源驱动代码,我就不敢做了,生怕买回来,用不了之类的,甚至有源码也不敢买,因为源码和我的机型不一样,连修改源码都怕。开始写OS,我还蛮恐惧的,因为不是科班,没学过,不懂。涉及的东西也很广,从编程语言到数据结构,组成原理,操作系统,怕拿不下来,也找不到好的资源,不知道怎么写起。通过这次项目,我想应该学到了一下东西:

1.遇到不懂的没有那种很强的抵触感了,开始学会查芯片手册,查原理书,开始配合谷歌查BUG,现在即使是拿来我没有接触过的模块,无论最后做的出来与否,我肯定先去了解它是什么,概念是什么,再去找厂家提供的资料,提供的源码,去一行一行的看,自己修改或者重写,这个比下面说到的什么具体的知识,我想都重要的多。我现在还记得第一次和第二次,第三次打开Cortex-M3权威指南(中文版)时的情景,看了几页就吓得我关了,简直是天书啊,其实耐心看,真的能看懂。
2.把自己数据结构里学的东西,真正带到了项目,虽然也写过队列等这些数据结构的题,但是在以前的嵌入式开发里,几乎用不到,有时候觉得好没用啊这些。这次通过实现OS的消息队列,看linux的文件系统,知道了这些在实际的巨大用处。
3.对OS的基本概念和运转有了认识
4.对C语言的理解深刻了许多
5.每个人的体验都不一样,没什么补充的了O(∩_∩)O哈哈~

四、简单几步将简易OS改造为--优雅的简易OS

1.为什么不优雅?

在该函数中,任务1执行完,立马就会切换到任务2,然而在实际中,我们希望是这样的:

任务1每100毫秒执行一次

2.系统节拍

几乎每个实时操作系统都需要一个周期性的时钟源,称为时钟节拍或者系统节拍,用来跟踪任务延时和任务等待延时

我们在main函数中输入这样一句

这里我们配置了定时器中断,每5毫秒一次中断。中断后会进入中断处理函数,下面来写中断处理函数:

大家只用管if里面的函数即可:我们在某个函数中将TCB_Task[i].Dly置为x,中断处理函数会每5毫秒,将非0的TCB_Task[i].Dly减一。如果TCB_Task[i].Dly非0,对应的任务是不会运行的(因为被我们删除就绪态了,这里看不到),当TCB_Task[i].Dly减为0,我们才将该任务置为就绪态

3.编写OSTimeDly()函数

也就是1中图片所示的函数,它可以让某任务指定每X毫秒运行一次。

第65行,可以关闭中断,同理,第68行,开启中断。为什么要关闭中断?因为中断会影响我们执行下面的代码,先关闭中断,执行完后再打开。66行将该任务从就绪态变为非就绪态,将要延迟的时间赋值为TCB_Task[OSPrioCur].Dly,然后调度(也就是切换任务)

4.调度函数OSSched()

非常简单,刚才我们将某个任务变为了非就绪态,紧接着就找就绪态任务中优先级最高的任务,然后切换

5.是否完成了呢?----空闲任务

乍一看,好像完成了,实则不然,虽然我们任务1每X毫秒运行一次,任务2每Y毫秒运行一次,但终归里面有空闲时间:任务1和任务2都不在运行,所以我们需要创建一个空闲任务,当CPU没有东西可以运行时,运行空闲任务。以下就是空闲任务:

6.来到主函数:

其他的倒是没问题,72行有个陌生的函数:OS_TASK_Init();

其实就是之前的OSStartHighRdy()函数的升级版,非常简单

先创建一个空闲函数,再获取优先级最高的任务,然后执行最高优先级的任务。

7.一个优雅的简易OS诞生了

不好看?想加个界面?没问题-----其实已经有了,大家观察58行,就是让液晶屏显示一句话,我们把背景修改为红色,醒目一点:



很开心能有这么多朋友喜欢,非常感谢 。我开了一个简单的头,相信真的喜欢os的朋友,只要认真去做,一定也能实现一个更好的作品。后面的暂时不打算写,如果有了新的思路,一定会再写出来。授人以鱼不如授人以渔,看到这里已经有动手的能力了,想写的朋友不要害怕,尽管去做!!!加油


五、加入信号量
六、加入消息队列
七、内存管理
八、实现一个free和malloc玩玩?

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多