我所做OS实验的描述及代码
2013.06.22
实验1:任务:
本任务参考sample
01.Task
同样在TaskSample任务中创建三个任务:TaskA,TaskB,TaskC,优先级分别为24,26,28。之后启动优先级最低的任务TaskC。在任务TaskC中分别启动TaskA和TaskB。TaskA和TaskB完成一段标志信息的输出后进入休眠状态等待唤醒。
TaskC完成上述动作后,就开始等待输入了。我使用了这样的代码:
if('e' == tm_getchar(-1))
{
break;
}
else
{
tm_putstring((UB*)"**************************************\n");
tm_putstring((UB*)"task a will wup\n");
tk_wup_tsk(TaskID_A);
tm_putstring((UB*)"task b will wup\n");
tk_wup_tsk(TaskID_B);
}
如果输入为字母‘e’,则系统退出。否则唤醒TaskA和TaskB。
这里要特别注意函数tm_getchar。因为如果tm_getchar没有完成,OS的中断就是一直关着的。不能进行任务调度,即使此时TaskA或者TaskB都等待了tmout时间,已经处于就绪状态。
这个实验设计的有一个缺点。任务C中使用到的tm_getchar关闭了系统中断,导致系统停止调度。不能很好地表现任务优先级的关系。
如果UserMain创建的任务(TaskSample)的优先级高于TaskA、TaskB、TaskC会怎样?
在我的实验中,如果TaskSample的优先级高于TaskC,那么由于TaskSample创建TaskC之后,Task将不会立即执行。TaskSample继续执行,即在执行完tk_sta_tsk(TaskID_C,4);之后,继续执行 return E_OK;。程序返回,继续执行inittask中的代码。即:tk_ext_sys();退出OS。
以下是我的实验代码:
【实验1.rar】
实验中使用到的任务处理函数:
tk_sta_sys
启动内核。这个东西是uT自己加进去的,必须在其他系统调用之前执行。需要有一个任务做完参数。启动内核时会立即创建和启动这个任务,即将CPU的控制权交给这个任务。
tk_ext_sys
退出内核
tk_sta_tsk
启动任务,将任务从静止状态转换成就绪状态。需要提供TaskID和另外一个参数stacd。参数Stacd没有什么大用处。
tk_slp_tsk
将任务休眠。参数tmout指的是OS时钟的周期数。如果这个周期结束前调用了tk_wup_tsk
,那么任务被唤醒。如果在tmout周期内没有收到tk_wup_tsk唤醒,那么任务自动唤醒。但会返回一个错误代码。
tk_wup_tsk
唤醒由tk_slp_tsk休眠的任务。将任务从等待状态释放
tk_dly_tsk
暂停调用这个函数的任务的执行。但是这时,这个任务的状态还是等待状态而不是休眠状态。。如果要提前终止这个等待时间,可以用tk_rel_wai来执行。
tk_ter_tsk
终止其他任务。强制将tskid指定的任务变为静止状态。任务使用的资源不会自动释放。
tk_del_tsk
删除任务。将任务从静止状态转为不存在状态。所删除的任务必须是静止状态
tk_ext_tsk
退出调用任务。就是调用这个函数的任务自己把自己退出了。自杀,变成静止状态。但是这个函数不会释放任务所使用的资源,需要手动释放。
实验1完成
------------------------------------------华------丽------的------分-----隔---符---------------------------------------
2013.06.23
实验2:信号量
关于信号的量申请:
如果一次需要获得多个信号量,但是信号量又不够。比如我需要3个信号量,但剩余的信号量只有1个。那么用tk_wai_sem申请信号量时候,不会改变信号量计数,然后开始等待其他信号量。
实验验证:
创建三个任务:任务A、B、C。其中任务A需要三个信号量,任务B需要4个信号量。任务A的优先级大于任务B。任务C为最低优先级的空闲任务。
创建任务后,先启动任务B,任务B先申请4个信号量。然后启动任务A。
由于任务A的优先级高于任务B,任务B被中断,任务A开始执行。由于系统中没有可用信号量,任务A申请信号量失败 进入等待状态。
之后回到任务B,任务B释放四个信号量。此时,系统中有足够的信号量供任务A使用,任务A被唤醒。继续执行。
任务A执行一次之后进入休眠,任务B开始执行。任务B申请并获得4个信号量,完成LED反转。之后释放信号量进入休眠。
两个任务都进入等待状态,任务C开始执行。任务C是一个死循环。用于防止OS没有正在运行的任务而退出。
实验代码如下:
ER SemSample(void)
{
ER ercd = E_OK;
T_CTSK ctsk;
T_CSEM csem;
ctsk.exinf = NULL;
ctsk.task = SemSampleTaskA;
ctsk.tskatr = TA_HLNG |
TA_RNG0;
ctsk.stksz = 512;
ctsk.bufptr = NULL;
ctsk.itskpri = 24;
TaskID_A =
tk_cre_tsk(&ctsk);
if(TaskID_A< E_OK)
{
ercd = TaskID_A;
return ercd;
}
ctsk.itskpri = 26;
ctsk.stksz = 256;
ctsk.task = SemSampleTaskB;
TaskID_B =
tk_cre_tsk(&ctsk);
if(TaskID_B < E_OK)
{
ercd = TaskID_B;
return ercd;
}
ctsk.itskpri = 28;
ctsk.stksz = 256;
ctsk.task = SemSampleTaskC;
TaskID_C =
tk_cre_tsk(&ctsk);
if(TaskID_C < E_OK)
{
ercd = TaskID_C;
return ercd;
}
//创建一个信号量
csem.exinf = NULL;
csem.isemcnt = 4;
csem.maxsem = 4;
csem.sematr = TA_TFIFO |
TA_FIRST;
semid =
tk_cre_sem(&csem);
if(semid < E_OK)
{
ercd = semid;
return ercd;
}
SemSamplePutCnt();
//启动任务B
tk_sta_tsk(TaskID_B,5);
return TRUE;
}
void SemSampleTaskA(W stacd,VP exinf)
{
ER ercd = E_OK;
T_RSEM rsem;
while(1)
{
tm_putstring((UB*)"任务A开始申请3个信号量\n");
SemSamplePutCnt();
ercd =
tk_wai_sem(semid,3,500);
SemSamplePutCnt();
if(ercd == E_OK)
{
LEDTog(LED1);
LEDTog(LED2);
LEDTog(LED3);
}
tm_putstring((UB*)"任务A开始释放3个信号量\n");
tk_sig_sem(semid,3);
SemSamplePutCnt();
tk_slp_tsk(500);
}
}
void SemSampleTaskB(W stacd,VP exinf)
{
ER ercd = E_OK;
T_RSEM rsem;
ercd =
tk_sta_tsk(TaskID_C,0);
SemSamplePutCnt();
tk_wai_sem(semid,4,-1);
ercd =
tk_sta_tsk(TaskID_A,0);
if(E_OK == ercd)
{
tm_putstring((UB*)"Start TaskA sucessfuly.\n");
}
else
{
tm_putstring((UB*)"TaskB Failed start Task A.\n");
}
tk_sig_sem(semid,4);
//任务循环
while(1)
{
//任务B需要4个信号量
SemSamplePutCnt();
tm_putstring((UB*)"任务B开始申请4个信号量\n");
if(tk_wai_sem(semid,4,-1)
== E_OK)
{
tm_putstring((UB*)"任务B成功获得4个信号量\n");
SemSamplePutCnt();
LEDTog(LED1);
LEDTog(LED2);
LEDTog(LED3);
LEDTog(LED4);
if(tk_sig_sem(semid,4)
== E_OK)
{
tm_putstring((UB*)"任务B成功释放4个信号量\n");
}
else
{
tm_putstring((UB*)"任务B释放信号量失败\n");
}
}
else
{
tm_putstring((UB*)"任务B获取信号量失败\n");
}
tk_slp_tsk(200);
}
}
//防止任务全部休眠导致OS退出的空闲任务
void SemSampleTaskC(W stacd,VP exinf)
{
B b;
while(1)
{
tm_putstring((UB*)"this is in Task C\n");
for(b = 0;b<200;b++);
}
}
实验时串口输出信息:
----------------------------------------------------
micro Tenux Version
1.6.00(build 0180)
Supported MCU is
ST STM32F407VG
Copyright(c) 2008-2013 by
Dalian uLoong Co.,Ltd.
----------------------------------------------------
当前的可用信号量数为: 4
当前的可用信号量数为: 4
任务A开始申请3个信号量
当前的可用信号量数为: 0
Start TaskA sucessfuly.
当前的可用信号量数为: 1
任务A开始释放3个信号量
当前的可用信号量数为: 4
当前的可用信号量数为: 4
任务B开始申请4个信号量
任务B成功获得4个信号量
当前的可用信号量数为: 0
任务B成功释放4个信号量
this is in Task C
当前的可用信号量数为: 4
任务B开始申请4个信号量
任务B成功获得4个信号量
当前的可用信号量数为: 0
任务B成功释放4个信号量
当前的可用信号量数为: 4
任务B开始申请4个信号量
任务B成功获得4个信号量
………………………………………………….
实验总结:
1、当高优先级的任务获得足够的信号量时,立即中断当前任务开始执行。
2、要在系统中添加优先级最低的空闲任务,防止系统任务全部休眠。
3、任务属性sematr的设置:
TA_TFIFO任务按FIFO的顺序排队
TA_TPRI 任务按优先级顺序排队
TA_FIRST队列中第一个任务最先获得资源
TA_CNT 请求越少的任务越先获得资源
4、谁先获得信号量,由OS根据sem的属性进行调度。
实验代码及输出
【实验2.rar】
------------------------------------------华------丽------的------分-----隔---符---------------------------------------
2013.06.25
实验3:事件标志
事件结构体:
typedef struct t_cflg {
VP exinf; /* 事件标志扩展信息*/
ATR flgatr; /* 事件标志的属性*/
UINT iflgptn; /* 事件标志的初始值 */
UB dsname[8]; /* 对象名,没啥用 */
} T_CFLG;
其中事件属性flgatr,可设置为以下的一个或多个值:
TA_TFIFO 任务按FIFO的顺序排队
TA_TPRI 任务按优先级顺序排队
TA_WSGL 不允许等待多个任务(等待一个任务)
TA_WMUL 允许等待多个任务(等待多个任务)
TA_DSNME 设定DS对象名
OS根据事件标志的属性对使用这个事件标志的任务进行调度。
或等待:等待任意一个条件满足,与等待:等待所有条件满足
我的理解,事件标志就是一个二进制数。因为事件标志本身就是个UINT类型的数字。设置事件标志就是将这个数的某一位置位,清除事件标志就是将这个数的某一位清零。同一个事件标志有多个位,多个任务可以同时使用一个事件标志。
创建事件标志,OS会建立一个事件标志的控制块,用于调度。创建事件标志的时候,会为这个时间标志设置个初始值:iflgtn。与任务和信号量相同,FlgID只是这个事件标志的代表,并不是事件标志的值。
事件标志的属性中如果TA_WMUL设置为1,那么就允许多个任务同时等待一个事件标志。当一个事件标志被设置时,如果多个任务都满足条件,此时这些任务都会被释放。进入等待状态,有系统调度决定哪个先执行哪个后执行。
实验描述:
创建一个事件标志:FlgID,FlgID事件标志的初始值设置为1. 如果初始值iflgtn被设置为0x02,那么TaskB将先执行一遍循环,然后进入等待后TaskA开始执行。
创建两个优先级相同的任务:EventflagSampleTaskA(TaskA)EventflagSampleTaskB(TaskB)
创建任务后启动TaskB,在TaskB中启动TaskA。此时由于TaskB的优先权较大,TaskB继续执行,遇到等待事件标志时候停止执行。此时TaskA开始执行。
TaskA首先为TaskB设置事件标志(0x02),然后申请事件标志Flag的0x01。条件满足,继续执行清除这个事件标志(0x01)。循环执行,重新等待事件标志,此时事件标志没有被设置,所以TaskA进入休眠状态。TaskB继续执行。
TaskB获得优先权开始执行,完成FlagID(0x02)的清除,之后继续等待资格事件。当然,同样没等到进入休眠,任务A继续。
如此往复。。。
【实验代码和输出】
实验三:事件标志.rar
【事件标志实验总结】
事件标志应该是为了等待两个任务同步而设立的。每个任务完成一定的工作之后,通过事件标志通知另一个任务可以执行了。从而使得两个任务之间协调完成一项工作。
------------------------------------------华------丽------的------分-----隔---符---------------------------------------
实验4:邮箱
每个邮箱都包含一个用来发送消息的消息队列和一个用于等待接收消息的任务队列
关于void型指针 VP。
创建邮箱时,需要提供一个邮箱消息的指针T_MSG。
这个指针指向存放消息的地址。查看定义可发现,他是这样定义的:
typedef struct t_msg {
VP msgque[1]; /* Area for message queue */
} T_MSG;
而VP的定义是:
typedef void *VP;
这里就用到了一个void型的指针。在示例程序中,将自己定义的消息类型强制转换成T_MSG型的指针。即void型的指针。
Void类型的指针是无类型的指针,可以指向任何结构。因为它就是一个指针!
在引用这个指针时候,需要预先知道这个指针指向地址的结构。不然编译器无法知道所指数据的类型而报错
这样的消息传递,使得mbx所传递的消息类型不再固定。需要使用者手动定义。
发送消息时,发送任务不会进入等待状态。即:只要把发送的消息往邮箱已仍就可以了,至于消息到哪里去了,交给OS处理了。当邮箱是空的时候,消息直接进邮箱。邮箱不空,消息进入该邮箱的消息队列。这些,发送任务都不关心。
接收消息时候,使用tm_rcv_mbx完成。提供邮箱ID和消息数据包起始地址,以及一个等待超时时间。如果要接收的邮箱没有消息,那么接收任务进入等待状态。
问题,关于邮箱发送消息和接收消息函数中使用的空类型指针void*
两个函数的定义是这样的:
ERercd= tk_snd_mbx(IDmbxid,T_MSG*pk_msg);
ERercd= tk_rcv_mbx(IDmbxid,T_MSG**ppk_msg,TMOtmout);
其中T_MSG的定义如下:
typedef struct t_msg {
VP msgque[1]; /* Area for message queue */
} T_MSG;
其中VP就是 typedef
void *VP;
也就是说,T_MSG这个结构本身包含了一个空类型的指针
下面是我对这里使用空类型指针的理解,求鉴定。
先明确的地方:消息传递中,传递的只是地址。
1、tk_snd_mbx使用T_MSG*
T_MSG* 是指向消息结构的指针,与msgque是相同的。这个指针是一个void*类型的指针。可以指向任意类型的数据。
所以tk_snd_mbx(IDmbxid,T_MSG*pk_msg);
这种用法,就是要向函数传递一个空类型的指针。
2、tk_rcv_mbx中的使用。
这里是一个指向指针的指针(T_MSG** ppk_msg)。关于ppk_msg内核规范中有这么个说明:ppk_msg是包含消息的数据包(包括消息头在内)的起始地址。
也就是说ppk_msg是一个地址(地址1)。* ppk_msg是就是地址中的数据,这个数据也是个地址(地址2)。于是T_MSG** ppk_msg 就是地址2中存储的数据。这个数据是T_MSG类型的。(不知对错,求鉴定)
在示例工程中,有这么个用法:
tk_rcv_mbx(MbxID_S,(T_MSG**)&pk_rcvmsg,-1);
其中pk_rcvmsg 的定义是这样的:U_MSG
*pk_rcvmsg; 也是个指针。
那么&pk_rcvmsg,就是取这个指针的地址。(T_MSG**)&pk_rcvmsg就成了将地址pk_rcvmsg中存储的地址所指向的数据块,强制转换成T_MSG*类型。
刚好与函数定义部分相同。
【实验描述】
参考示例代码。首先创建两个任务TaskA和TaskB优先级分别为24和25,两个邮箱Mbx_S和Mbx_R。之后启动TaskA。在TaskA中启动TaskB。然后向Mbx_S发送一个消息,然后开始等待Mbx_R中的消息。由于Mbx_R中没有消息,TaskA进入休眠。
此时TaskB开始执行,TaskB先从Mbx_S中取消息,然后输出消息。最后再向Mbx_R中发送消息。Mbx_R中有了消息,TaskA释放等待进入Ready就绪状态。TaskA优先级高于TaskB,抢占TaskB开始执行,继续完成以后的消息处理代码
【代码和输出】
实验4:邮箱.rar