1.信号量
在UCOSIII中有可能会有多个任务会访问共享资源,因此信号量最早用来控制任务存取共享资源,现在信号量也被用来实现任务间的同步以及任务和ISR间同步。在可剥夺的内核中,当任务独占式使用共享资源的时候,会出现低优先级的任务先于高优先级任务运行的现象,这个现象被称为优先级反转,为了解决优先级反转这个问题,UCOSIII引入了互斥信号量这个概念。同时,在ucosiii中每个任务都有自己的内嵌信号量。
(1)信号量
信号量像是一种上锁机制,代码必须获得对应的钥匙才能继续执行,一旦获得了钥匙,也就意味着该任务具有进入被锁部分代码的权限。一旦执行至被锁代码段,则任务一直等待,直到对应被锁部分代码的钥匙被再次释放才能继续执行。
信号量分为2种:二进制信号量与计数型信号量,二进制信号量只能取0和1两个值,计数型信号量不止可以取2个值,在共享资源中只有任务可以使用信号量,中断服务程序则不能使用。
二进制信号量
某一资源对应的信号量为1的时候,那么就可以使用这一资源,如果对应资源的信号量为0,那么等待该信号量的任务就会被放进等待信号量的任务表中。在等待信号量的时候也可以设置超时,如果超过设定的时间任务没有等到信号量的话那么该任务就会进入就绪态。任务以“发信号”的方式操作信号量。可以看出如果一个信号量为二进制信号量的话,一次只能一个任务使用共享资源。
计数型信号量
有时候我们需要可以同时有多个任务访问共享资源,这个时候二进制信号量就不能使用了,计数型信号量就是用来解决这个问题的。比如某一个信号量初始化值为10,那么只有前10个请求该信号量的任务可以使用共享资源,以后的任务需要等待前10个任务释放掉信号量。每当有任务请求信号量的时候,信号量的值就会减1,直到减到为0.当有任务释放掉信号量的时候信号量的值就会加1.
信号量的API函数
1.创建信号量
void OSSemCreate (OS_SEM *p_sem,
CPU_CHAR *p_name,
OS_SEM_CTR cnt,
OS_ERR *p_err)
p_sem:指向信号量控制块,我们需要按照如下所示方式定义一个全局信号量,并将这个信号量的指针传递给函数OSSemCreate()。 OS_SEM TestSem;
p_name:指向信号量的名字。
cnt:设置信号量的初始值,如果此值为1,代表此信号量为二进制信号量,如果大于1
的话就代表此信号量为计数型信号量。
p_err:保存调用此函数后的返回的错误码。
2.请求信号量(获得信号量)
当一个任务需要独占式的访问某个特定的系统资源时,需要与其他任务或中断服务程序
同步,或者需要等待某个事件的发生,应该调用函数OSSemPend()
OS_SEM_CTR OSSemPend (OS_SEM *p_sem,
OS_TICK timeout,
OS_OPT opt,
CPU_TS *p_ts,
OS_ERR *p_err)
p_sem:指向一个信号量的指针
timeout:指定等待信号量的超时时间(时钟节拍数),如果在指定时间内没有等到信
号量则允许任务恢复执行。如果指定时间为0的话,任务就会一直等待下去,直到等到
信号量。
opt:用于设置是否使用阻塞模式,有下面2个选项。
OS_OPT_PEND_BLOCKING 指定信号量无效时,任务挂起以等待信号量。
OS_OPT_PEND_NON_BLOCKING 信号量无效时,任务直接返回。
p_ts:指向一个时间戳,用来记录接收到信号量的时刻,如果这个参数赋值NULL,则说
明用户没有要求时间戳。
p_err:保存调用本函数后返回的错误码
3.发送信号量(释放信号量)
任务获得信号量以后就可以访问共享资源了,在任务访问完共享资源以后
必须释放信号量,释放信号量也叫发送信号量,使用函数OSSemPost()发
送信号量。如果没有任务在等待该信号量的话则OSSemPost()函数只是简
单的将信号量加1,然后返回到调用该函数的任务中继续运行。如果有一个
或多个任务在等待这个信号量,则优先级最高的任务获得这个信号量,然
后由调度器来判定刚获得信号量的任务是否为系统中优先级最高的就绪任
务,如果是,则系统将进行任务切换,运行这个就绪任务,
OS_SEM_CTR OSSemPost (OS_SEM *p_sem,
OS_OPT opt,
OS_ERR *p_err)
p_sem:指向一个信号量的指针。
opt:用来选择信号量发送的方式。
OS_OPT_POST_1 仅向等待该信号量的优先级最高的任务发送信号量。
OS_OPT_POST_ALL 向等待该信号量的所有任务发送信号量。
OS_OPT_POST_NO_SCHED 该选项禁止在本函数内执行任务调度操作。即使该函数使得更高优先级的任务结束挂起进入就绪状态,也不会执行任务调度,而是会在其他后续函数中完成任务调度。
p_err:用来保存调用此函数后返回的错误码。
(2)互斥信号量
一般用来解决优先级反转的情况
(3)任务内嵌信号量
前面使用信号量都需要先创建一个信号量,不过在UCOSIII中每个任务都有自己的内嵌的信号量
2.消息传递
有时候一个任务要和另外一个或者几个任务进行“交流”,这个“交流”就是消息的传递,也称为任务间通信,在UCOSIII中消息可以通过消息队列作为中介发布给任务,也可以直接发布给任务
(1)消息队列
**消息一般包含:**指向数据的指针,**表明数据长度的变量和记录消息发布时刻的时间戳,**指针指向的可以是一块数据区或者甚至是一个函数,消息的内容必须一直保持可见性,因为发布数据采用的是引用传递是指针传递而不是值传递,也就是说,发布的数据本身不产生数据拷贝。
消息队列是由用户创建的内核对象
消息队列的API函数
1.创建消息队列
消息队列使得任务或者中断服务程序可以向一个或者多个任务发生消息
void OSQCreate (OS_Q *p_q,
CPU_CHAR *p_name,
OS_MSG_QTY max_qty,
OS_ERR *p_err)
p_q:指向一个消息队列,消息队列的存储空间必须由应用程序分配,我们采用如下的
语句定义一个消息队列。OS_Q Msg_Que;
p_name: 消息队列的名字。
max_qty: 指定消息队列的长度,必须大于0 。当然,如果OS_MSGs缓冲池中没有足够
多的MSGs可用,那么发送消息会失败,并且返回相应的错误码,指明当前没有可用的
MSGs
p_err: 保存调用此函数后返回的错误码。
2.等待消息队列
当一个任务想要从消息队列中接收一个消息就需要使用函数OSQPend(),当任务调用这个
函数的时,如果消息队列中有至少一个消息时,这些消息就会返回给函数调用者。
void *OSQPend (OS_Q *p_q,
OS_TICK timeout,
OS_OPT opt,
OS_MSG_SIZE *p_msg_size,
CPU_TS *p_ts,
OS_ERR *p_err)
p_q: 指向一个消息队。
timeout: 等待消息的超时时间,如果在指定的时间没有接收到消息的话,任务就会被
唤醒,接着运行。这个参数也可以设置为0,表示任务将一直等待下去,直到接收到消
息。
opt: 用来选择是否使用阻塞模式,有2个选项可以选择。 OS_OPT_PEND_BLOCKING
如果没有任何消息存在的话就阻塞任务,一直等待,直到接收到消息。
OS_OPT_PEND_NON_BLOCKING 如果消息队列没有任何消息的话任务就直接返回。
p_msg_size: 指向一个变量用来表示接收到的消息长度(字节数 )。
p_ts: 指向一个时间戳,表明什么时候接收到消息。如果这个指针被赋值为NULL的
话,说明用户没有要求时间戳。
p_err: 用来保存调用此函数后返回的错误码。
如果消息队列中没有任何消息,并且参数opt为OS_OPT_PEND_NON_BLOCKING时,那么
调用OSQPend()函数的任务就会被挂起,直到接收到消息或者超时。如果有消息发送给
消息队列,但是同时有多个任务在等待这个消息,那么UCOSIII将恢复等待中的最高优
先级的任务。
3.向消息队列发送消息
void OSQPost (OS_Q *p_q,
void *p_void,
OS_MSG_SIZE msg_size,
OS_OPT opt,
p_q: 指向一个消息队列。
p_void: 指向实际发送的内容,p_void是一个执行void类型的指针,其具体含义由用
户程序来决定。
msg_size: 设定消息的大小,单位为字节数。
opt: 用来选择消息发送操作的类型,
OS_OPT_POST_ALL将消息发送给所有等待该消息队列的任务,需要和选项
OS_OPT_POST_FIFO或者OS_OPT_POST_LIFO配合使用。
OS_OPT_POST_FIFO待发送消息保存在消息队列的末尾。
OS_OPT_POST_LIFO待发送消息保存在消息队列的开头。
OS_OPT_POST_NO_SCHED禁止在本函数内执行任务调度。
我们可以使用上面4种基本类型来组合出其他几种类型,如下:
OS_OPT_POST_FIFO + OS_OPT_POST_ALL
OS_OPT_POST_LIFO + OS_OPT_POST_ALL
OS_OPT_POST_FIFO + OS_OPT_POST_NO_SCHED
OS_OPT_POST_LIFO + OS_OPT_POST_NO_SCHED
OS_OPT_POST_FIFO + OS_OPT_POST_ALL + OS_OPT_POST_NO_SCHED
OS_OPT_POST_LIFO + OS_OPT_POST_ALL + OS_OPT_POST_NO_SCHED
p_err: 用来保存调用此函数后返回的错误码
(2)任务内建消息队列
和任务信号量一样,UCOSIII中每个任务也都有其内建消息队列
3.事件标志组
我们可以使用信号量来完成任务同步,另外一种任务同步的方法,就是事件标志组,事件标志组用来解决一个任务和多个事件之间的同步。
有时候一个任务可能需要和多个事件同步,这个时候就需要使用事件标志组。事件标志组与任务之间是有2种同步机制:“或”同步和“与”同步,
当任何一个事件发生,任务都被同步的同步机制是“或”同步;
需要所有的事件都能发生任务才会被同步的同步机制是“与”同步,
1.在UCOSIII中事件标志组是OS_FLAG_GRP,在os.h文件中有定义,事
件标志组中也包含了一串任务,这些任务都在等待着事件标志组中的部分(或全部)事件标志被置1或被清零,在使用之前,必须创建事件标志组。
2.任务和ISR(中断服务程序)都可以发布事件标志,但是,只有任务可以创建、删除事件标志组以及取消其他任务对事件标志组的等待。
3.任务可以通过调用函数OSFlagPend()等待事件标志组中的任意个事件标志,调用函数OSFlagPend()的时候可以设置一个超时时间,如果过了超时时间请求的事件还没有被发布,那么任务就会重新进入就绪态。
4.我们可以设置同步机制为“或”同步还是“与”同步。
API函数
1.创建事件标志组
void OSFlagCreate (OS_FLAG_GRP *p_grp,
CPU_CHAR *p_name,
OS_FLAGS flags,
OS_ERR *p_err)
p_grp:指向事件标志组,事件标志组的存储空间需要应用程序进行实际分配,我们可
以按照下面的例子来定义一个事件标志组。 OS_FLAG_GRP EventFlag;
p_name:事件标志组的名字
flags:定义时间标志组的初始值
p_err:用来保存调用此函数后返回的错误码。
2.等待事件标志组
OS_FLAGS OSFlagPend (OS_FLAG_GRP *p_grp,
OS_FLAGS flags,
OS_TICK timeout,
OS_OPT opt,
CPU_TS *p_ts,
OS_ERR *p_err)
flags:bit序列,任务需要等待事件标志组的哪个位就把这个序列对应的位置1,根据
设置这个序列可以是8bit、16bit或者32bit。比如任务需要等待事件标志组的bit0和
bit1时(无论是等待置位还是清零),flag的值就为0X03。
3.向事件标志组发送标志
OS_FLAGS OSFlagPost (OS_FLAG_GRP *p_grp,
OS_FLAGS flags,
OS_OPT opt,
OS_ERR *p_err)
flags:决定对哪些位清零和置位,当opt参数为OS_OPT_POST_FLAG_SET时,参数
flags中置位的位就会在事件标志组中对应的位也将被置位。
当opt为OS_OPT_POST_FLAG_CLR的时候参数flags中置位的位在事件标志组中对应的位
将被清零。
4.同时等待多个内核对象
在前面中任务都是如何等待单个对象,比如信号量、互斥信号量、消息队列和事件标志组等。UCOSIII也可以同时等待多个内核对象,在UCOSIII中只支持同时等待多个信号量和消息队列,不支持同时等待多个事件标志组和互斥信号量。
UCOSIII中一个任务可以同时等待任意数量的信号量或者消息队列,当只要等到其中的任意一个的时候就会导致该任务进入就绪态
API函数:
OSPendMulti()函数
函数OSPendMulti()用来等待多个内核对象,调用OSPendMulti()时,如果这些对象中有多个可用,则**所有可用的信号量和消息队列都将返回给调用者,**如果没有任何对象可用,则OSPendMulti()将挂起当前任务,直到以下任一情况发生:
1.对象变为可用
2.到达设定的超时时间
3.一个或多个任务被删除或被终止
4.一个或多个对象被删除
OS_OBJ_QTY OSPendMulti (OS_PEND_DATA *p_pend_data_tbl,
OS_OBJ_QTY tbl_size,
OS_TICK timeout,
OS_OPT opt,
OS_ERR *p_err)
p_pend_data_tbl:指向OS_PEND_DATA表的指针,调用者通过该表来查询函数的调用
结果。调用该函数的时候首先必须初始化OS_PEND_DATA表中的每个元素的
PendObjPtr,使得各个指针指向被等待的对象。
tbl_size:表p_pend_data_tbl的大小,也就是所等待的内核对象数量。
timeout:设定一个等待超时值(时钟节拍数),用来设置任务等待对象发送的时间,
如果为0,表示这个任务将一直等待下去,直到对象被发送。
opt:用来选择是否使用阻塞模式,有2个选项可以选择
OS_OPT_PEND_BLOCKING 如果没有任何消息存在的话就阻塞任务,一直等待,直到接
收到消息
OS_OPT_PEND_NON_BLOCKING 如果消息队列没有任何消息的话任务就直接返回
p_err:用来保存调用此函数后返回的错误码。
5.存储管理
UCOSIII将存储空间分成区和块,一个存储区由数个固定大小的块组成
一般存储区是固定的,在程序中可以用数组来表示一个存储区,比如u8 buffer[20][10]就表示一个有20个存储块,每个存储块10字节的存储区。如果我们定义的存储区在程序运行期间都不会被删除掉,一直有效,那么存储区内存也可以使用malloc()来分配。在创建存储区以后应用程序就可以获得固定大小的存储块。
在实际使用中我们可以根据应用程序对内存需求的不同建立多个存储区,每个存储区中有不同大小、不同数量的存储块,应用程序可以根据所需内存不同从不同的存储区中申请内存使用,使用完以后再释放到相应的存储区中。
创建存储区
存储区控制块:OS_MEM,结构体OS_MEM
创建函数 OSMemCreate()
void OSMemCreate (OS_MEM *p_mem,
CPU_CHAR *p_name,
void *p_addr,
OS_MEM_QTY n_blks,
OS_MEM_SIZE blk_size,
OS_ERR *p_err)
p_mem:指向存储区控制块地址,一般由用户程序定义一个OS_MEM结构体。
p_name:指向存储区的名字
p_addr:存储区所有存储空间基地址
n_blks:存储区中存储块个数
blk_size:存储块大小
p_err:返回的错误码
注:
1.每个存储块大小不小于一个指针大小,一个指针大小为4字节,因此每个存储块大小要大于4字节
2.存储区空间基地址是否4字节对齐,存储区的存储空间基地址必须4字节对齐(32对齐也可以)
3.存储区中每个存储块的大小要是4的倍数
4存储区中的存储块连成一个空闲存储块链表。每个存储块中保存着下一个存储块的地址,因此在(1)中规定了每个存储块要大于4个字节,因为每个存储块至少要保存一个地址,而一个地址恰恰为4字节
创建存储区如图
调用函数OSMemCreate()创建好存储区后就可以使用创建好的存储块
(1)内存申请
OSMemGet()函数来获取存储块
void *OSMemGet (OS_MEM *p_mem,
OS_ERR *p_err)
p_mem:要使用的存储区
p_err:返回的错误码。
返回值:获取到的存储块地址
(2)内存释放
void OSMemPut (OS_MEM *p_mem,
void *p_blk,
OS_ERR *p_err)
p_mem:指向存储区控制块,也就是要接收存储块的那个存储区。
p_blk:指向存储块,要归还的存储块。
p_err:返回的错误码。