之
by francis_hao yinghao1991@126.com
学习 ucosii 也有一段时间了,说实话,刚开始学习的时候感觉
很浮躁,恨不得一两天就把它学会。因为感觉自己能学习的时间不多
了。想趁着这段时间多学点东西。可是越着急越乱。教材匆匆翻了好
几遍却没留下什么印象。对一些基本的概念依旧不了解。忽然发现没
有了学习的感觉,因为之前在一些比赛中,需要在很短的时间里把所
需要的模块开发出来,即使你不明白其中原理。这样久了,我便也懒
惰了。知其然,不知其所以然。其实,快速开发是一种能力,却不是
学习的方法。平时的学习还是要踏实一点。步步精通才是好的。
我认真总结了下,便开始以新的态度对待它。把教材上的每一章
节都细细品读。我的教材主要是以下两本:
西安电子科技大学出版社的《嵌入式实时操作系统 uc/os-ii》
任哲老师的《嵌入式实时操作系统 uc/os-ii 原理及应用》
第一本是我们学校的教材,但是我感觉里面的内容过于详细,对
于初学者来说反而会造成适得其反的效果,让人不知就里。但作为手
册看还是很好的。任哲老师的书想必大家都熟悉,它的框架性很强,
看过一遍后,便对这个系统有了总体的认识。书中语言简洁明了,适
合初学者。
对于 ucosii 的学习就不说了,主要谈谈 ucosii 在 pc 上的移植,
在 VC++6.0 环境下的使用。
作为初学者,总是力求简洁明了,我在学习的时候,一定要把为
什么这样做想清楚。知道这样做的原因,或者还有没有其他的方法。
也算是从零开始吧,除去我那点薄弱的 c 语言功底。
开始的时候是从网上下载的工程,直接编译运行就可以了。当时
很高兴,觉得自己很厉害了,可是这毕竟是别人的东西。怎样建工程
自己还不会。于是我想只用 ucos-ii 源码,自己建一个工程,看看能
不能运行。如果能运行了,那么自己离成功也就不远了。
内核文件总共有 16 个,11 不需要改动。有三个与处理器有关,
两个与应用程序相关。我在工程中建了一个 source 文件夹,所有的
源文件都放在里面,那 11 个不需改动的文件放在 source\core 文件
夹中,与处理器有关的文件被改成了 os_cpu.h、os_cpu_c.c、
os_trace.c、os_trace.h 我们拿来用就好了,放在 source\cpu 中。
还有就是与应用有关的文件了,放在 source 文件夹下就好了。这样
的分类好处是方便对各类文件的管理。
我下载的工程的结构是这样的:
这个可以编译,运行一切正常,但是因为里面没有那些内核文件,
我感觉很费解,便在自己建的工程中将所有的文件都加进去了。
但是内核文件有个 os_dbg_r.c。这个我们过会再讲。
现在这样的文件结构才是作为初学者的我所能接受的,于是很兴
奋的点下编译,事情就变成这个样子了:
晕了,怎么会有这么多错误呢?而且好像都是连接错误。变量重
定义了。是 ucos_ii.obj 中的问题,.obj 文件存在就说明文件本身
编译没有错误。是连接的时候出的问题。我在网上搜索资料的时候发
现一篇讲编译器原理的文章,写的很好,把编译和连接讲的很形象,
文章放在附录中,在此表示感谢。
ucos-ii.c 有问题,于是把这个文件打开看
就是这个样子了,这个文件包含了所有的内核文件,怪不得会出
现重复定义。Jean J. Labrosse 是为了让工程更简洁。在建工程的
时候只需将 ucso_ii.c 添加在工程中即可。我们尝试一下
怎么样?验证了你的猜想了吧,这感觉,像做了一次福尔摩斯。
爽了。但是我们这里的真相不止一个,还可以这样:
添加除去 ucso_ii.c 外所有的内核文件也是可以的。
两种方法的区别:第一种方法,在文件中定义的结构体不会出现
在 Class View 中,第二种则会显示出来。
还有一个问题就是
os_dbg_r.c 这个文件干嘛用的???
我也不知道,具体的道理我不知道,只知道是关于调试的,如果
你知道,麻烦发到我邮箱。万分感谢!
不过现在。。。 管它呢,我觉得不需要,所以我把它删掉。编译
一下,出现问题了。果然不是随便能删的。
在文件中找“OSDebugInit”:
好了,到文件中看看去。
条件编译!!! 追踪到 OS_DEBUG_EN 中的定义中:
啊,原来是这样。那么我们把这个调试允许位改为 0 会是怎么样呢?
恩。如你所想,一切 OK。好了,这个工程这样就算是建好了,你可
以在上面做各种实验。开启你的 ucos 之旅吧!
附录:
变量、函数、类,分为声明和定义,至于格式,不再多说。
编译器编译的时候,先单独编译每一个 cpp 文件,然后用连接器把他们连起来。
一个 cpp 文件要能完成自己范围内的东西的定义,即,运用自己已经定义的东西,或者
声明了的东西。声明了的东西等于告诉编译器,虽然这个东西我没定义,但是在其他地方定
义了(文件内或文件外),你就先将就我告诉你的它的特征(类型,函数返回类型,参数等)
来用它就好了。比如我的 cpp 声明了 int number,现在需要完成一个函数的定义,
void func()
{
Number++;
}
好了,这个 number,虽然编译器在编译 cpp 的时候没看到定义(number=?),但是编
译器看到了声明,好吧,我就当做你保证了它(number)的存在(会在其他地方定义),先
帮你编译,不过如果最后也找不到它的定义的话,可别怪我翻脸哦(连接的时候没有找到定
义)。
好了,当每个 cpp 文件都编译好了之后,链接器开始链接它们了,怎么链接呢?说白了
就是核对各个文件里声明的东西到底有没有定义。如果发现某个文件只有声明没有定义,那
么就报错(好小子,说好了世界上存在长这个样儿的家伙,现在人在哪里呢?)。
记得当初学 c++的时候,让要把文件分开,说是方便组织,然后是要分为.h 和.cpp,而
且严格要求一个写声明,一个写定义,现在知道了,并非非这样不可,这是一种类似于约定
的东西,这样你的一个 cpp 文件可以单独地包含其他文件的.h,即严格地包含了其他东西的
声明,而不包含其他东西的定义。为什么这样,后面会说。至于我们经常用到的#ifndef #define
#endif,则保证了在头文件互相包含的过程中不会重复包含。注意,这仅仅保证了一个文件
(A)里不会重复包含多个相同文件(B),但并不表示同一个文件(B)不会被其他多个文件(A,C,D…)
包含!
这意味着什么呢?如果一个文件重复包含头文件(声明),会事代码膨胀而且没有意义,
所以要避免。而对于 B 同时被 A,C,D 等包含的情况,如果 B 是仅包含声明,并无大碍,仅仅
表示 A,C,D 都要用到 B 里面声明的东西,如此而已。但是如果 B 是一个包含定义的文件,麻
烦来了,B 文件里声明的东西,因为 A,C,D 均包含了它的定义(编译器检查不到,前面说了,
对每个 cpp 文件来说,编译是单独进行的,编译器假设每个声明都会定义,所以不检查),
于是出现了所谓的“重定义”,如果某个文件 E 包含了 B 里面被重定义的东西的声明,那么
在链接 E 的时候,该把这个声明链接到哪个定义上呢(虽然逻辑上你知道每个定义都是一样
的,因为都是包含相同的文件得到的,但是链接器并不知道,但不要因此认为它 SB)?显
然,这个时候只好报错。
现在可以解释为什么约定.h 只放声明,.cpp 只放定义了。.h 放声明,方便其他文件引用
它的声明部分而不会包含到其定义部分,避免了重定义,而将定义全部放在.cpp 里,避免与
声明混淆。
严格按照规范写程序的人(h cpp 声明 定义分离)可能难得遇到这种错误,所以深刻
理解的机会就少,可能偶尔遇到,也弄不懂是什么意思…(这段话自己理解)