查看: 4875|回复: 9

stm32f107vc金龙开发板 RT_Thread OS例程说明

[复制链接]

该用户从未签到

发表于 2015-5-7 10:31:40 | 显示全部楼层 |阅读模式
分享到:
RT_Thread OS 基本例程说明
        本文档主要针对RT-OS基本例程的说明,不对RT-OS的原理进行讲解,也不做移植的说明,因为RT-OS的论坛以及网站,已经对RT-OS的说明很详细,且有移植好的工程文件,移植教程里面也有详细的说明。所以这里我们只是根据下面链接的帖子里面的例程做一个总结,帖子链接:http://www.rt-thread.org/phpBB3/viewtopic.php?f=28&t=1877,该帖子是依据STM32F1进行编写的,这里我们把部分例程移植到我们的金龙107上面
这些例程所用到的硬件只有串口,以及我们金龙107开发板的一个LED灯和一个按键,只需要用串口线把我们金龙107PC机连接起来,只需要一根串口线就可以完成RT-OS所有试验。其中LED灯是作为指示程序的运行,并没有其他作用,按键在其中一个例程中有用到。
例程已将Lwip移植到金龙107开发板上了,如果需要使用到网络可以在rtconfig.h文件里面将#define RT_USING_LWIP宏打开,同时在application.c文件中注释去掉修改如下:
图片6.png

图片5.png
跳线帽JP923,连接网线设置电脑IP192.168.1.203,开发板IP192.168.1.199。进入电脑命令提示符界面输入ping 192.168.1.199,显示如下信息,说明网络连接正常。
图片4.png
1、动态线程的创建与删除
该例程主要说明动态线程的创建与删除,首先我们要在rtconfig.h中开启两个宏定义:
#define RT_USING_HEAP;开启此项可以创建动态线程和动态信号量,如果使用静态线程和静态信号量,则此项不是必要的。请注意这里的动态和静态,后面的例程大多用到动态线程或者信号量等,所以此项也是一直开启的,后面将不再赘述。
#define RT_USING_CONSOLE;本实验使用rt_kpriintf向串口打印按键信息,因此需要开启此项。后面的实验,都需要向串口输出数据,所以此项是必须的,我们就不再赘述。
另外还有一个宏定义,我们需要注意,就是:#define RT_TICK_PER_SECOND  100,这个宏定义的意思是,操作系统的100Tick1S,则一个OS Tick=10ms。后面的程序中我们使用的都是100,所以后面我们也将不再赘述此选项。
前面说过的,我们不对操作系统的解说,只是对程序的说明,如果对RT-OS不了解的地方,可以参考RT-Thread.pdf和rtthread_manual.zh.pdf这两个文档,其中已经说的很详细。也可以到RT-OS官网上下载这两个文档。
下面开始讲解我们的程序,首先我们要定义两个指向动态线程的指针并建立线程入口函数,如图1-1所示。
图片3.png
1-1
        如图1-1所示,线程1首先打印信息,然后循环进行计数,并打印,延时1S继续循环。线程2首先也是打印信息,然后延时4S,然后删除线程1,并打印信息。
        下面看线程的创建,如图1-2所示。第一个线程是静态线程,上面提到的LED灯的闪烁,指示程序运行的作用。主要看下面的两个线程的创建下面的两个线程采用的是创建,而不是初始化,是不一样的。这是动态的线程,采用的是rt_thread_create()函数,返回的是线程控制块的地址。也就是我们上面定义的线程指针。
第一个参数是线程的名字,可以随意,
第二个参数是线程入口函数,
第三个参数是线程入口函数的参数,
第四个参数是线程的堆栈空间,
第五个参数是线程的优先级,
最后一个参数是时间片。
因为RT-OS支持时间片轮法,当两个线程优先级相同时,则采用时间片轮法进行线程的切换。最后判断线程是否创建成功,创建成功则给线程指针分配地址,则其不为空指针,然后启动线程即可。第二个线程一样。
图片2.png
1-2
结果分析:
        程序的执行结果如图1-3所示。
图片1.png
1-3
        上面是打印的RT-OS的信息,因为线程1的优先级比较高,所以先执行,打印一个数据,在线程1的循环中有一个延时,所以打印一次信息之后,要进行延时,则线程1挂起,执行线程2,打印成功创建线程2之后,线程2也进行了4S的延时,所以线程2挂起,等待线程1再运行三次,因为每次都是延时1S,当到了线程1打印出thread1 count: 1之后,再延时1S的时候,线程2的延时时间到,则执行线程2中的删除线程1代码,则线程1不再运行,线程2打印出信息之后,运行结束,并自行删除。

回复

使用道具 举报

该用户从未签到

 楼主| 发表于 2015-5-8 14:12:31 | 显示全部楼层
2、静态线程初始化与脱离
该例程主要说明静态线程的初始化和脱离。上一节我们讲的是动态线程,这一节讲的是静态线程,这两种线程的建立有什么不同呢,请看图2-1所示。
图片3.png
2-1
        从图2-1中看出,静态线程的创建首先要定义线程的堆栈空间,以及定义一个线程控制块。而在动态线程建立的时候,只需要定义一个指向线程控制块的指针即可。这个实验和第一个实验动态线程的使用的实验现象基本上是一样的,一个是动态的,一个是静态的。第一个实验当删除线程1的时候,采用的是rt_thread_delete()函数,删除线程1,而这里采用的是rt_thread_detach()函数,使线程1脱离。这是两者不一样的地方,其次是在线程的创建的不一样。
如图2-2所示。我们看到,线程的创建采用的是rt_thread_init()函数,对线程进行初始化,返回的是线程是否创建成功,而动态线程则采用的是rt_thread_create()函数,进行创建线程,返回的是线程控制块的指针。且其中的参数也是不一样的。Rt_thread_init()函数的
第一个参数是线程控制块的地址,也就是上面我们定义的线程控制块的地址。
第二个参数是线程的名字,
第三个参数是线程的入口函数,
第四个参数是线程入口函数的参数,
第五个参数是线程堆栈空间的首地址,
第六个参数是线程堆栈空间的大小,
第七个参数是线程的优先级,
第八个参数是线程的时间片大小。
创建成功,则启动线程。
图片2.png
2-2
结果分析:
        如图2-3所示,是该程序的运行结果:该程序的运行结果和上一个实验的是类似的,也是在线程1运行一定时间之后,用线程2将线程1脱离或者删除。
图片1.png
2-3

回复 支持 反对

使用道具 举报

该用户从未签到

 楼主| 发表于 2015-5-8 15:16:43 | 显示全部楼层
3、线程让出
本节主要说明的是线程的让出,而不是线程的删除或者脱离。线程的删除或者脱离之后,线程将不再运行,除非重新启动线程,而线程的让出,则只是暂时的把当前线程停止,让给其它就绪的线程,多用于有优先级相同的线程,否则,如果高优先级的线程就绪则直接抢占了,低优先级就绪,就算高优先级的线程让出,很快也会再次被高优先级的线程抢占。线程让出之后并不是线程被挂起了,线程依旧是就绪状态。所以说只对有优先级相同的线程有效。
上面两节我们说了静态线程的初始化和脱离,动态线程的创建以及删除。这节我们将不再说线程的创建或初始化等,只对程序和运行结果进行分析。如图3-1所示,是两个线程的入口函数。
图片2.png
3-1
        从图3-1中我们可以看到,线程1和线程2都是循环进行计数以及让出线程,不过线程1是先计数,然后让出线程,而线程2是先让出线程,在计数并打印。
        该例程需要注意的是,上面我们有提到线程的让出,只在相同优先级的任务之间的切换,所以这两个线程的优先级必须是一样的。
结果分析:
        如图3-2所示,是该程序的运行结果。从结果中我们可以看出,线程1首先计数并打印了两次,线程2才打印1次,后面的就是线程1和线程2的交替打印。线程1怎么会出现打印两次呢,是因为线程1先建立并启动,所以线程1先执行,首先打印1次,然后让出,线程2执行,线程2则先执行线程让出,这时候又变成线程1执行,然后线程1就又一次进行计数打印,而线程2一次也没有打印。待线程1打印结束之后,再次让出线程,这时候线程2才有机会计数并打印数据。所以后面就是交替的进行数据的打印。
        其实这个程序,打印到后面我们会发现又错了,不是交替打印,因为这个时候线程的时间片到了,所以线程被挂起了。
图片1.png
3-2

回复 支持 反对

使用道具 举报

该用户从未签到

 楼主| 发表于 2015-5-9 10:47:57 | 显示全部楼层
4、线程优先级抢占
        本节我们主要说明线程的优先级的抢占,高优先级的线程对低优先级的线程的抢占。如图4-1所示,线程的入口函数。
图片3.png
4-1
        从图4-1中看到,线程1循环10次进行数据计数以及打印输出,每次循环要延时三秒。而线程2则是循环进行获得系统OS Tick并打印输出,每次延时1S。如图4-2所示,初始化两个线程并启动。要注意的是这两个线程的优先级。线程1的优先级是5,线程2的优先级是6,所以线程1的优先级要比线程2的优先级高。
图片2.png
4-2
结果分析:
        如图4-3所示,是该程序的运行结果。
图片1.png
4-3
因为更高的优先级,thread1率先得到执行,随后它调用延时,时间为3S,于是thread2得到执行。可以从打印结果中发现一个规律,在第一次thread2了打印两次thread1会打印一次之后,接下来的话thread2每打印三次thread1会打印一次。对两个线程的入口程序进行分析可以发现,在thread1 3S的延时里,thread2实际会得到三次执行机会,但显然在thread1的第一个延时内thread2第三次执行并没有执行结束,在第三次延时结束以后,thread2本应该执行第三次打印计数的,但是由于thread1此时的延时也结束了,而其优先级相比thread2要高,所以抢占了thread2的执行而开始执行。当thread1再次进入延时时,之前被抢占的thread2的打印得以继续,然后在经过两次1S延时和两次打印计数后,在第3S结束后又遇到了thread1的延时结束,thread1再次抢占获得执行,所以在这次thread1打印之前,thread2执行了三次打印计数。

回复 支持 反对

使用道具 举报

该用户从未签到

 楼主| 发表于 2015-5-9 13:51:10 | 显示全部楼层
5、优先级相同线程轮转调度
        本例程主要说明的是,优先级相同的线程,时间片的轮转调度。如图5-1所示,线程的入口函数。
图片3.png
5-1
        线程中的任务就是输出打印信息,其中线程1多了一个延时,这个延时函数是以OS Tick为单位的。图5-2所示,初始化并启动两个线程,注意线程的优先级是一样的。我把时间片调小是为了方便看到实验现象
图片2.png
5-2
结果分析:
        如图5-3所示,我们看到当线程的时间片到了之后,则被迫让出,让给另外一个同优先级的就绪状态的线程执行。
图片1.png
5-3

回复 支持 反对

使用道具 举报

该用户从未签到

 楼主| 发表于 2015-5-11 11:06:43 | 显示全部楼层
6、优先级反转
本节主要讲述,操作系统中最常见的优先级反转的问题,由于多线程共享资源,具有最高优先级的线程被低优先级线程阻塞,反而使中优先级线程先于高优先级线程执行,导致系统故障。
优先级反转的一个典型场景为:系统中存在优先级为ABC的三个线程,优先级A > B > C,线程AB处于挂起状态,等待某一事件的发生,线程C正在运行,此时线程C开始使用某一共享资源S。在使用过程中,线程A等待的事件到来,线程A转为就绪态,因为它比线程C优先级高,所以立即执行。但是当线程A要使用共享资源S时,由于其正在被线程C使用,因此线程A被挂起切换到线程C运行。如果此时线程B等待的事件到来,则线程B转为就绪态。由于线程B的优先级比线程C高,因此线程B开始运行,直到其运行完毕,线程C才开始运行。只有当线程C释放共享资源S后,线程A才得以执行。在这种情况下,优先级发生了反转,线程B先于线程A运行。这样便不能保证高优先级线程的响应时间
在该实验中我们用到了动态的信号量,所以要首先定义一个信号量指针static rt_sem_t sem = RT_NULL。接下来要创建该信号量以及三个动态线程,如图6-1所示。从代码中我们看到线程优先级t2>worker>t1,然后是创建了一个信号量。
图片5.png
6-1
        下面看三个线程的入口函数,如图6-26-36-4所示,
图片4.png
6-2
        线程1首先是获得信号量,然后循环打印数据并延时1S,最后释放信号量
图片3.png
6-3
worker线程首先延时5OS Tick,然后循环计数并打印数据。
图片2.png
6-4
        线程t2首先获得信号量,打印一条信息,然后释放信号量,延时5OS Tick,再次获得信号量,计数,然后打印数据。
结果分析:
        如图6-5所示,因为t2的优先级最高,所以t2先执行,获得信号量然后释放信号量,这时候执行延时,然后执行worker线程,worker线程也进行延时,挂起,然后执行线程t1,线程t1获得信号量,此时线程t2已经延时结束,但是因为获得不了信号量而被挂起,此时worker延时也已经结束,则执行worker线程,循环打印数据。因为worker线程比t1线程优先级高,所以直到循环结束,执行延时,挂起worker线程。此时因为t1线程仍持有信号量,而t2由于等待信号量而被挂起,所以执行t1,直到t1结束循环,释放信号量。则t2获得信号量,执行打印。
图片1.png
6-5
        本实验,因为t2线程迟迟不能获得信号量,而worker线程优先级低,却先执行了,造成了优先级的翻转。

回复 支持 反对

使用道具 举报

该用户从未签到

 楼主| 发表于 2015-5-11 14:48:31 | 显示全部楼层
7、线程优先级反转之优先级继承算法
        本节主要是解决,上一节中遇到的优先级翻转的问题,使高优先级的线程能够尽快的执行,响应系统需求。解决方法就是当高优先级的线程因为共享资源的占有而不能及时响应的时候,就把占有该共享资源的低优先级的线程的优先级临时提高,使其快速的执行完,释放信号量,然后再把优先级降低为原来的优先级。使得高优先级的线程能够及时的执行。
        本节我们采用的是RT OS中的互斥量(mutex)来实现优先级的继承算法,在 RT-Thread 的 mutex 设计中已经实现了优先级继承这种算法,因此,使用 mutex 即可以使用优先级继承的算法解决优先级反转问题。
        如图7-1所示,是三个线程的入口函数。
图片4.png

图片3.png
7-1
        如图7-2所示,线程以及互斥量的初始化以及创建。
图片2.png
7-2
结果分析:
        如图7-3所示,是该程序执行的结果。从图中我们可以看出thread2线程,能够得到及时的执行,而worker线程最后执行。
图片1.png
7-3

回复 支持 反对

使用道具 举报

该用户从未签到

 楼主| 发表于 2015-5-14 11:45:13 | 显示全部楼层
8、线程抢占导致的临界区问题
临界资源是指一次仅允许一个线程使用的共享资源。不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它们进行访问。每个线程中访问临界资源的那段代码称为临界区(Critical Section),每次只准许一个线程进入临界区,进入后不允许其他线程进入。多线程程序的开发方式不同于裸机程序,多个线程在宏观上是并发运行的,因此使用一个共享资源是需要注意,否则就可能出现错误的运行结果。本实验通过一个简单的共享变量来演示多线程中的临界区问题。
本程序首先定义一个共享资源,全局变量static int share_var。如图8-1所示,是两个线程的入口函数。线程1只是对全局变量进行了累加到100,然后延时1个Tick,打印共享变量的值。线程2则仅仅对共享资源进行累加操作,同时延时1个Tick。
图片2.png
8-1
结果分析:
        如图8-2所示,是该程序运行结果。结果中打印的数据是,线程1的输出结果,但是线程1只是累加到100,但是打印出来的确实101,那是因为,线程1执行累加到100之后,进行了一个延时而挂起,而线程2此时又进行了累加,导致打印出来的时候,就变成了101,造成共享资源因为优先级抢占而发生改变。
图片1.png
8-2

回复 支持 反对

使用道具 举报

该用户从未签到

 楼主| 发表于 2015-5-14 14:04:07 | 显示全部楼层
9、信号量基本使用
        上一节的问题,引出了信号量的使用,当一个线程在使用共享资源的时候,为了不让其他线程改变共享资源,我们设计了信号量,当一个线程要修改共享资源的时候,必须要先得到该信号量,否则不能修改该共享资源,当一个线程使用完该共享资源之后,必须释放与共享资源对应的信号量,使其他线程能够及时的执行。
        本实验的主要设计目的是帮助读者快速了解信号量相关API,包括静态信号量初始化/脱离,动态信号量创建/删除,信号量获取释放相关API,为了简化起见,我们将这些API放在同一个线程中调用。这里我们再次提到动态和静态的使用,注意创建静态和动态所使用的函数是不一样的。
        如图9-1所示,是静态信号量的测试,要注意的是静态信号量的使用和脱离,所调用的函数。
图片4.png
9-1
如图9-2所示,是动态信号量的测试,注意动态信号量的使用,以及删除所用的函数。
图片3.png
9-2
在线程初始化的函数中,我们初始化了静态信号量和创建了动态信号量。如图9-3所示。需要注意的是,静态信号量的初始化中,也可以初始化时,就可以给它赋一定的值,也就是第三个参数。动态信号量创建时,也可以是有一定的初值的,不一定必须是0.创建函数的第二个参数就是动态信号量的初值。
图片2.png
9-3
结果分析:
如图9-4所示,是该程序的执行结果。
图片1.png
9-4

回复 支持 反对

使用道具 举报

该用户从未签到

 楼主| 发表于 2015-5-15 14:04:04 | 显示全部楼层
10、互斥量、邮箱、消息队列、事件的基本使用
互斥量的使用和信号量的使用,其程序执行过程和信号量的过程是一样的,读者可以参考信号量的使用,可以参考程序,对照信号量的程序分析。
需要注意的是,在前面我们提到优先级翻转的问题,在 RT-Thread 的互斥量 mutex 的设计中已经实现了优先级继承这种算法,所以当我们出现优先级翻转的问题时,可以采用互斥量来解决。
另外要解释的就是,互斥量也分为静态的和动态,也要注意其创建或者初始化,以及脱离和删除的操作。
下面的几节中是关于邮箱,信息队列以及事件的使用,只是对相关的API进行介绍,对这些通信机制的使用,不需要做过多的分析,大家详细可以参考程序。
要强调的是当我们用到信号量、互斥量、邮箱、信息队列、以及事件的时候,在rtconfig.h文件中,需要开启这些功能。
#define RT_USING_SEMAPHORE //信号量的使用
#define RT_USING_MUTEX//互斥量的使用
#define RT_USING_MESSAGEQUEUE//信息队列的使用
#define RT_USING_MAILBOX//邮箱的使用
#define RT_USING_EVENT//事件的使用
互斥量的使用程序运行结果如图10-1所示:
图片4.png
10-1
邮箱使用的运行结果如图10-2所示。
图片3.png
10-2
消息队列使用的程序运行结果如图10-3所示
图片2.png
10-3
事件使用的程序运行结果如图10-4所示。
图片1.png
10-4

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /4 下一条



手机版|小黑屋|与非网

GMT+8, 2024-11-25 11:30 , Processed in 0.200054 second(s), 34 queries , MemCache On.

ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

苏公网安备 32059002001037号

Powered by Discuz! X3.4

Copyright © 2001-2024, Tencent Cloud.