AWorks软件设计,邮箱、消息队列和自旋锁使用方法

ZLG致远电子 2018-06-13 09:13 次阅读

本文导读

本文介绍了邮箱、消息队列和自旋锁的使用方法。信号量只能用于任务间的同步,不能传递更多的信息,为此,AWorks提供了邮箱和消息队列服务,它们的主要区别在于支持的消息长度不同,在邮箱中,每条消息的长度固定为4字节,而在消息队列中,消息的长度可以自定义。本文为《面向AWorks框架和接口的编程(上)》第三部分软件篇——第10章——第3~5小节:邮箱、消息队列和自旋锁。

10.3  邮箱

前面介绍了用于任务间同步的三种信号量,它们相当于资源的钥匙,获取到钥匙的任务可以访问相关的资源,任务以此确定可以运行的时刻。但是,信号量不能够提供更多的信息内容。比如,按键按下时,释放一个信号量,任务获取到该信号量时,只能知道有按键按下了,但不能知道按键相关的更多信息,比如:具体是哪个按键按下了?

当需要在任务间传递的更多信息时,可以使用AWorks提供的邮箱服务。邮箱服务是实时内核中一种典型的任务间通信方法,特点是开销比较低,效率较高。一个邮箱中可以存储多封邮件,邮箱中的每一封邮件只能容纳固定的4字节内容(针对32位处理系统,指针的大小即为4个字节,所以一封邮件恰好能够容纳一个指针)。发送邮件的任务(或中断服务程序)负责将邮件存入邮箱,接收邮件的任务负责从邮箱中提取邮件。示意图详见图10.4。

图10.4 邮箱工作示意图

当邮箱中存在多封邮件时,默认按照先进先出(FIFO)的原则传递给接收邮件的任务。邮件的大小固定为4字节,当需要传递的消息内容大于4字节时,则可以仅将消息的地址作为邮件内容,任务接收到邮件时,通过该地址即可查找到相应的消息。这种方式使得使用邮箱进行消息传递的效率非常高。

AWorks提供了使用邮箱的几个宏,宏的原型详见表10.7。

表10.7 邮箱相关的宏(aw_mailbox.h)

1.  定义邮箱实体

AW_MAILBOX_DECL()和 

AW_MAILBOX_DECL_STATIC()宏均用于定义一个邮箱实体,为邮箱分配必要的内存空间,包括用于存储邮件的空间。它们的原型为:

其中,参数mailbox为邮箱实体的标识名。mail_num表示邮箱的容量,即邮箱中存储邮件的最大条数,由于每封邮件的大小为4字节,因此用于存储邮件的总内存大小为:mail_num×4。

两个宏的区别在于:

AW_MAILBOX_DECL_STATIC() 在定义邮箱所需内存时, 使用了关键字static ,如此一来, 便可以将邮箱实体的作用域限制在模块内(文件内), 从而避免模块之间的邮箱命名冲突,同时,还可以在函数内使用本宏定义邮箱实体。

如使用AW_MAILBOX_DECL()定义一个标识名为mailbox_test的邮箱实体,邮件的最大数目为10,其范例程序详见程序清单10.49。

程序清单10.49 定义邮箱实体的范例程序

使用AW_MAILBOX_DECL()定义邮箱实体时,可以将邮箱的实体嵌入到另一个数据结构中,其范例程序程序清单10.50。

程序清单10.50 将邮箱实体嵌入到结构体中

也可以使用AW_MAILBOX_DECL_STATIC()定义一个标识名为mailbox_test的邮箱实体,其范例程序详见程序清单10.51。

程序清单10.51 定义邮箱实体(静态)的范例程序

2.  初始化邮箱

定义邮箱实体后,必须使用AW_MAILBOX_INIT()初始化后才能使用。其原型为:

其中,mailbox为由AW_MAILBOX_DECL() 或 AW_MAILBOX_DECL_STATIC()定义的邮箱。mail_num表示邮箱可以存储的邮件条数,其值必须与定义邮箱实体时的mail_num相同。options为邮箱的选项,其决定了阻塞于此邮箱(等待消息中)的任务的排队方式,可以按照任务优先级或先进先出的顺序排队,它们对应的宏详见表10.8。

表10.8 邮箱初始化选项宏(aw_mailbox.h)

注意,前面讲述了三种信号量,在初始化时同样可以通过选项指定阻塞于信号量的任务的排队方式,分为按照优先级和先进先出两种方式,它们对应的宏名分别为AW_SEM_Q_PRIORITY 和AW_SEM_Q_FIFO。与邮箱选项的命名不同,不可混用。

通常排队方式都选择按照优先级排队,初始化邮箱的范例程序详见程序清单10.52。

程序清单10.52 初始化邮箱的范例程序

AW_MAILBOX_INIT()用于初始化一个邮箱实体,初始化完毕后,将返回该消邮箱的ID,其类型为aw_mailbox_id_t。定义一个该类型的变量保存返回的ID如下:

aw_mailbox_id_t的具体定义用户无需关心,该ID可作为后文介绍的其它邮箱相关接口函数的参数,用于指定要操作的邮箱。特别地,若返回ID的值为NULL,表明初始化失败。一般地,若无特殊需求,不会使用该ID,可以不用保存该ID。

3.  从邮箱中获取一条信息

从邮箱中获取一条消息的宏原型为:

其中,mailbox为由AW_MAILBOX_DECL() 或 AW_MAILBOX_DECL_STATIC()定义的邮箱。p_data指向用于保存消息的缓冲区,消息获取成功后,将存储到p_data指向的缓冲区中,由于消息的大小固定为4字节(32位),因此缓冲区的大小也必须为4字节,例如,可以是一个指向32位数据的指针。timeout指定了超时时间。该宏的返回值为aw_err_t类型的标准错误号。注意,由于中断服务程序不能被阻塞,因此,该函数禁止在中断中调用。

如果邮箱不为空,包含有效的消息,则本次操作将成功获取到一条消息,同时,会从邮箱中将该条消息删除,有效消息的条数减1。此时,AW_MAILBOX_RECV()的返回值为:AW_OK。

如果邮箱为空,没有任何有效的消息,则不能立即成功获取到消息,接下来具体的行为将由超时时间timeout的值决定。

(1)若timeout的值为

AW_MAILBOX_WAIT_FOREVER。则任务会阻塞于此,一直等待,直到邮箱中有可用的消息,即其它任务或中断发送了消息。范例程序详见程序清单10.53。

程序清单10.53 永久阻塞等待邮箱的范例程序

(2)若timeout的值为

AW_MAILBOX_NO_WAIT。则任务不会被阻塞,立即返回,但不会成功获取到消息,此时,AW_MAILBOX_RECV()的返回值为:

-AW_EAGAIN(表示当前资源无效,需要重试)。范例程序详见程序清单10.54。

程序清单10.54 不阻塞等待邮箱的范例程序

(3)若tiemout的值为一个正整数,则表示最长的等待时间(单位为系统节拍),任务会阻塞于此,在timeout规定的时间内,若成功获取到一条消息,则AW_MAILBOX_RECV()的返回值为:AW_OK;若在timeout规定的时间内,没有获取到消息,则返回值为-AW_ETIME(表明超时)。范例程序详见程序清单10.55。

程序清单10.55 等待邮箱的超时时间为500ms的范例程序

4.  发送一条消息到邮箱中

发送消息到邮箱中的宏原型为:

其中,mailbox为由AW_MAILBOX_DECL() 或 AW_MAILBOX_DECL_STATIC()定义的邮箱。data为发送的32位数据。timeout指定了超时时间。priority指定了消息的优先级。该宏的返回值为aw_err_t类型的标准错误号。

若邮箱未满,可以继续存储消息,则该消息发送成功,邮箱中的有效消息条数加1。此时,AW_MAILBOX_SEND()的返回值为:AW_OK。

若邮箱已满,暂时不能继续存储消息,则不能立即成功发送消息,接下来具体的行为将由超时时间timeout的值决定。

(1)若timeout的值为

AW_MAILBOX_WAIT_FOREVER。则任务会阻塞于此,一直等待,直到消息成功放入邮箱中。即其它任务从邮箱中获取了消息,邮箱中留出了空闲空间。范例程序详见程序清单10.56。

程序清单10.56 永久阻塞等待邮箱的范例程序

注意,priority参数指定了消息的优先级,其可能的取值有两个:AW_MAILBOX_PRI_NORMAL和AW_MAILBOX_PRI_URGENT,分别表示普通优先级和紧急优先级。一般地,均使用普通优先级,此时,新的消息按照先进先出的原则,依次排队存入邮箱,该消息将后于当前邮箱中其它消息被取出。若使用紧急优先级,则新的消息插队放在邮箱的最前面,该消息将先于当前邮箱中其它消息被取出,即在下次从邮箱中获取消息时被取出。

(2)若timeout的值为

AW_MAILBOX_NO_WAIT。则任务不会被阻塞,立即返回,但不会成功发送消息,此时,AW_MAILBOX_SEND()的返回值为:-AW_EAGAIN(表示当前资源无效,需要重试)。范例程序详见程序清单10.57。

程序清单10.57 不阻塞等待邮箱的范例程序

(3)若tiemout的值为一个正整数,则表示最长的等待时间(单位为系统节拍),任务会阻塞于此,在timeout规定的时间内,成功发送了消息,则AW_MAILBOX_SEND()的返回值为:AW_OK;若在timeout规定的时间内,没有成功发送消息,则返回值为-AW_ETIME(表明超时)。范例程序详见程序清单10.58。

程序清单10.58 等待邮箱的超时时间为500ms的范例程序

注意,在中断服务程序中(如按键回调函数),可以使用该接口发送消息至邮箱,这是中断和任务之间很重要的一种通信方式:即在中断中发送消息,在任务中接收消息并处理,从而减小中断服务程序的时间。但是,由于中断不能被阻塞,因此,当在中断中发送消息时,timeout标志只能为AW_MAILBOX_NO_WAIT。在这种应用中,为了避免消息丢失,应该尽可能避免邮箱被填满,可以通过增加邮箱的容量以及提高处理消息任务的优先级,使邮箱中的消息被尽快处理。

5.  终止邮箱

当一个邮箱不再使用时,可以终止该邮箱,宏原型为:

其中,mailbox为由AW_MAILBOX_DECL() 或 AW_MAILBOX_DECL_STATIC()定义的邮箱。当邮箱被终止后,若当前系统中还存在等待该邮箱的任务,则任何等待此邮箱的任务将会解阻塞, 并返回-AW_ENXIO(表明资源已不存在),其范例详见程序清单10.59。

程序清单10.59 终止邮箱的范例程序

在讲述计数信号量时,使用了单个按键控制LED翻转,由于只使用到了一个按键,因此,在发送按键消息时,只需要使用计数信号量对按键事件进行计数,无需发送更多的消息。若需要使用多个按键控制LED,则在发送按键消息时,必须携带按键编码信息,以便针对不同的按键作不同的处理。

例如,要实现一个简单的应用,通过LED显示当前按键的编码:

  • KEY_0按下,则LED0熄灭,LED1熄灭,显示“00”;

  • KEY_1按下,则LED0熄灭,LED1点亮,显示“01”;

  • KEY_2按下,则LED0点亮,LED1熄灭,显示“02”;

  • KEY_3按下,则LED0点亮,LED1点亮,显示“03”。

显然,为了区分不同按键,发送按键消息时,需要携带按键的编码信息,由于按键编码是int类型的数据,在32位系统中,其恰好为32位,因此,可以使用邮箱来管理按键消息。范例程序详见程序清单10.60。

程序清单10.60 邮箱使用范例程序

在按键事件回调函数中发送消息,由于只需要处理按键按下事件,因此,仅当按键按下时(key_state不为0),才向邮箱中发送消息(按键编码)。在task_led任务中接收消息,当成功获取到一条消息时,根据消息内容(按键编码)控制LED。

实际上,这里的按键处理程序仅仅用于控制LED灯,耗时时间是非常短的,往往比发送一条消息的时间还短,这里使用邮箱并不能优化程序,仅仅只是作为一个使用邮箱的范例,实际应用按键的处理通常会复杂得多,则建议使用这种通用的模式,即在按键事件的回调函数中,仅仅只是将按键编码发送到邮箱中,实际的处理在任务中完成。这样可以避免长时间占用中断,影响系统的实时性,使其它紧急事务得不到处理,同时,邮箱还有一个缓冲的作用,当按键来不及处理时,可以暂存到邮箱中,后续空闲时再及时去处理,很大程度上避免了“丢键”的可能性,就像PC一样,有时候系统卡顿,显示屏卡住,但按键输入的信息后续还是会显示出来,一般不会丢失。

在上面的范例程序中,由于按键编码的大小为4字节,邮件恰好可以容纳,因此,可以直接将按键编码作为消息内容,发送到邮箱中(拷贝一份,存储至邮箱中)。若要发送的消息大于4字节,显然不能直接发送了,此时,可以将传输内容的地址作为邮件内容,发送到邮箱中,接收者接收到邮件后,再将邮件内容作为地址,从中取出实际的消息内容。范例程序详见程序清单10.61。

程序清单10.61 邮箱使用范例程序——消息内容大于4字节

程序中,定义了两个任务:task0和task1。task0负责发送消息,每隔1s将count值加1,若count为奇数,则发送__g_str1字符数组中的信息;若count为偶数,则发送__g_str2字符数组中的信息。__g_str1和__g_str2两个字符数组分别存放了字符串:

"The count is an odd number!"和

"The count is an even number!"。显然,字符串的长度超过了4个字节,因此,两个数组的大小也都超过了4字节。在tsak1发送消息时,将字符串数组的地址作为消息发送到了邮箱中。task1用于接收消息,当接收到消息时,将其作为字符数组的地址,使用aw_kprintf()将接收到的字符信息打印出来。以此完成了消息的传递。

由于邮箱仅传输了两个字符数组的地址,为了保证接收任务正确提取地址中的实际消息,必须确保接收任务接收到邮件时,地址中的数据仍然有效。因此,在范例程序中,将两个数组定义为了全局变量,使其内存一直有效。甚至在消息处理完成后,数组的内存空间还是有效的。

在一些应用中,当消息处理完毕后,消息将没有任何实际意义,其地址中对应的数据可以丢弃,以释放相关内存。此时,可以使用动态内存来管理消息:发送者动态获取一段内存空间,填充相关内容后,将这段内存空间的首地址发送到邮箱中,接收者从邮箱中获取到该地址,然后从地址中提取出实际的消息内容进行处理,处理完毕后,释放内存。

例如,在程序清单10.60的基础上,对功能进行简单的修改:当按键按下时,LED显示当前按键的编码,当按键释放时,熄灭所有LED。显然,由于需要对按键按下和释放作不同的处理,这就要求在按键事件产生后,除需要发送按键编码信息外,还要发送按键的状态(按下或释放)。此时,消息就需要包含按键编码和按键状态,共计8字节。范例程序详见程序清单10.62。

程序清单10.62 邮箱使用范例程序——消息内存动态分配

程序中,使用了aw_mem_alloc()和

aw_mem_free()进行消息内存的申请和释放。

aw_mem_alloc()和aw_mem_free()与标准C的malloc()和free()功能相同,用于动态内存的管理。它们在aw_mem.h文件中声明。

aw_mem_alloc()的函数原型为:

其用于分配size字节的内存空间,返回void*类型的指针,该指针即指向分配空间的首地址,若内存分配失败,则返回值为NULL。

aw_mem_free()的函数原型为:

其用于释放由aw_mem_alloc()分配的空间,ptr参数即为内存空间的首地址,其值必须是由aw_mem_alloc()函数返回的。

10.4  消息队列

前面介绍了邮箱服务,邮箱固定了消息的大小为4字节,当需要传输多余4字节的内容时,往往需要使用指针的形式,即使用邮箱传递实际消息的首地址,任务间通过传递的地址共享信息。使用地址共享信息的效率很高。但是,这种情况下,就需要特别小心的进行内存的申请和释放,一个地址中的消息使用完毕后,需要释放相关内存空间。对于初学者来讲,使用起来相对繁琐,容易出错。

为此,AWorks提供了另外一种消息通信机制:消息队列。其和邮箱类似,均用于任务见消息的传输,但其支持的消息大小由用户指定,可以超过4字节。

消息队列可以存放多条消息,发消息的任务负责将消息发送至队列,接收消息的任务负责从队列中提取消息。AWorks提供了使用消息队列的几个宏,宏的原型详见表10.9。

表10.9 消息队列相关的宏(aw_msgq.h)

1.  定义消息队列实体

AW_MSGQ_DECL()和

AW_MSGQ_DECL_STATIC()宏均用于定义一个消息队列实体,为消息队列分配必要的内存空间,包括用于存储消息的空间。它们的原型为:

其中,参数msgq为消息队列实体的标识名。msg_num和msg_size用于分配存储消息的空间,msg_num表示消息的最大条数,msg_size表示每条消息的大小(字节数)。用于存储消息的总内存大小即为:msg_num×msg_size。

两个宏的区别在于,AW_MSGQ_DECL_STATIC() 在定义消息队列所需内存时, 使用了关键字static ,如此一来, 便可以将消息队列实体的作用域限制在模块内(文件内), 从而避免模块之间的消息队列命名冲突,同时,还可以在函数内使用本宏定义消息队列实体。

如使用AW_MSGQ_DECL()定义一个标识名为msgq_test的消息队列实体,消息的最大数目为10,每条消息为一个int类型数据,则消息的大小为4个字节(32位平台中),其范例程序详见程序清单10.63。

程序清单10.63 定义消息队列实体的范例程序

通常,当每条消息为一个int类型的数据时,其长度最好使用sizeof表示,其范例程序详见程序清单10.64。

程序清单10.64 定义消息队列实体的范例程序

使用AW_MSGQ_DECL()定义消息队列实体时,可以将消息队列的实体嵌入到另一个数据结构中,其范例程序程序清单10.65。

程序清单10.65 将消息队列实体嵌入到结构体中

也可以使用AW_MSGQ_DECL_STATIC()定义一个标识名为msgq_test的消息队列实体,其范例程序详见程序清单10.66。

程序清单10.66 定义消息队列实体(静态)的范例程序

2.  初始化消息队列

定义消息队列实体后,必须使用

AW_MSGQ_INIT()初始化后才能使用。其原型为:

其中,msgq为由AW_MSGQ_DECL() 或  

AW_MSGQ_DECL_STATIC()定义的消息队列。msg_num表示消息队列可以存储的消息条数,其值必须与定义消息队列实体时的msg_num相同。msg_size表示每条消息的大小(字节数),其值必须与定义消息队列实体时的msg_size相同。

options为消息队列的选项,其决定了阻塞于此消息队列(等待消息中)的任务的排队方式,可以按照任务优先级或先进先出的顺序排队,它们对应的宏详见表10.10。

表10.10 消息队列初始化选项宏(aw_msgq.h)

注意,前面讲述了三种信号量,在初始化时同样可以通过选项指定阻塞于信号量的任务的排队方式,分为按照优先级和先进先出两种方式,它们对应的宏名分别为AW_SEM_Q_PRIORITY 和AW_SEM_Q_FIFO。与消息队列的命名不同,不可混用。

通常排队方式都选择按照优先级排队,初始化消息队列的范例程序详见程序清单10.67。

程序清单10.67 初始化消息队列的范例程序

AW_MSGQ_INIT()用于初始化一个消息队列实体,初始化完毕后,将返回该消息队列的ID,其类型为aw_msgq_id_t。定义一个该类型的变量保存返回的ID如下:

aw_msgq_id_t的具体定义用户无需关心,该ID可作为后文介绍的其它消息队列相关接口函数的参数,用于指定要操作的消息队列。特别地,若返回ID的值为NULL,表明初始化失败。一般地,若无特殊需求,不会使用该ID,可以不用保存该ID。

3.  从消息队列中获取一条信息

从消息队列中获取一条消息的宏原型为:

其中,msgq为由AW_MSGQ_DECL() 或  

AW_MSGQ_DECL_STATIC()定义的消息队列。p_buf指向用于保存消息的缓冲区,消息成功获取后,将存储到p_buf指向的缓冲区中。nbytes指定了缓冲区的大小,缓冲区大小必须能够容纳一条消息,其值不得小于定义消息队列实体时指定的一条消息的长度,通常情况下,nbytes与一条消息的长度是相等的,例如,msgq_test中的消息长度为4字节,则nbytes的值也为4,即p_buf指向的缓存区大小为4字节。timeout指定了超时时间。该宏的返回值为aw_err_t类型的标准错误号。注意,由于中断服务程序不能被阻塞,因此,该函数禁止在中断中调用。

如果消息队列不为空,包含有效的消息,则本次操作将成功获取到一条消息,同时,会从消息队列中将该条消息删除,有效消息的条数减1。此时,AW_MSGQ_RECEIVE()的返回值为:AW_OK。

如果消息队列为空,没有任何有效的消息,则不能立即成功获取到消息,接下来具体的行为将由超时时间timeout的值决定。

(1)若timeout的值为

AW_MSGQ_WAIT_FOREVER。则任务会阻塞于此,一直等待,直到消息队列中有可用的消息,即其它任务或中断发送了消息。范例程序详见程序清单10.68。

程序清单10.68 永久阻塞等待消息队列的范例程序

(2)若timeout的值为AW_MSGQ_NO_WAIT。则任务不会被阻塞,立即返回,但不会成功获取到消息,此时,AW_MSGQ_RECEIVE()的返回值为:-AW_EAGAIN(表示当前资源无效,需要重试)。范例程序详见程序清单10.69。

程序清单10.69 不阻塞等待消息队列的范例程序

(3)若tiemout的值为一个正整数,则表示最长的等待时间(单位为系统节拍),任务会阻塞于此,在timeout规定的时间内,若成功获取到一条消息,则AW_MSGQ_RECEIVE()的返回值为:AW_OK;若在timeout规定的时间内,没有获取到消息,则返回值为-AW_ETIME(表明超时)。范例程序详见程序清单10.33。

程序清单10.70 等待消息队列的超时时间为500ms的范例程序

4.  发送一条消息到消息队列中

发送消息到消息队列中的宏原型为:

其中,msgq为由AW_MSGQ_DECL() 或  

AW_MSGQ_DECL_STATIC()定义的消息队列。p_buf指向待发送的消息缓冲区。nbytes为消息缓冲区的大小,消息缓冲区的长度不得不得大于定义消息队列实体时指定的一条消息的长度,通常情况下,nbytes与一条消息的长度是相等的,例如,msgq_test中的消息长度为4字节,则nbytes的值也为4,即p_buf指向的缓存区大小为4字节。timeout指定了超时时间。priority指定了消息的优先级。该宏的返回值为aw_err_t类型的标准错误号。

若消息队列未满,可以继续存储消息,则该消息发送成功,消息队列的有效消息条数加1。此时,AW_MSGQ_SEND()的返回值为:AW_OK。

若消息队列已满,暂时不能继续存储消息,则不能立即成功发送消息,接下来具体的行为将由超时时间timeout的值决定。

(1)若timeout的值为

AW_MSGQ_WAIT_FOREVER。则任务会阻塞于此,一直等待,直到消息成功放入消息队列。即其它任务从消息队列中获取了消息,消息队列留出空闲空间。范例程序详见程序清单10.71。

程序清单10.71 永久阻塞等待消息队列的范例程序

注意,priority参数指定了消息的优先级,其可能的取值有两个:AW_MSGQ_PRI_NORMAL和AW_MSGQ_PRI_URGENT,分别表示普通优先级和紧急优先级。一般地,均使用普通优先级,此时,新的消息按照队列的组织形式,放在队列的尾部,将最后被取出。若使用紧急优先级,则新的消息放在队列的头部,将在下次从消息队列中获取消息时被取出。

(2)若timeout的值为AW_MSGQ_NO_WAIT。则任务不会被阻塞,立即返回,但不会成功发送消息,此时,AW_MSGQ_SEND()的返回值为:-AW_EAGAIN(表示当前资源无效,需要重试)。范例程序详见程序清单10.72。

程序清单10.72 不阻塞等待消息队列的范例程序

(3)若tiemout的值为一个正整数,则表示最长的等待时间(单位为系统节拍),任务会阻塞于此,在timeout规定的时间内,成功发送了消息,则AW_MSGQ_SEND()的返回值为:AW_OK;若在timeout规定的时间内,没有成功发送消息,则返回值为-AW_ETIME(表明超时)。范例程序详见程序清单10.73。

程序清单10.73 等待消息队列的超时时间为500ms的范例程序

注意,在中断服务程序中(如按键回调函数),可以使用该接口发送消息至消息队列,这是中断和任务之间很重要的一种通信方式:即在中断中发送消息,在任务中接收消息并处理,从而减小中断服务程序的时间。但是,由于中断不能被阻塞,因此,当在中断中发送消息时,timeout标志只能为AW_MSGQ_NO_WAIT。在这种应用中,为了避免消息丢失,应该尽可能避免消息队列被填满,可以通过增加消息队列的大小以及提高处理消息任务的优先级,使消息队列中的消息被尽快处理。

5.  终止消息队列

当一个消息队列不再使用时,可以终止该消息队列,宏原型为:

其中,msgq为由AW_MSGQ_DECL() 或 AW_MSGQ_DECL_STATIC()定义的消息队列。当消息队列被终止后,若当前系统中还存在等待该消息队列的任务,则任何等待此消息队列的任务将会解阻塞, 并返回-AW_ENXIO(表明资源已不存在),其范例详见程序清单10.74。

程序清单10.74 终止消息队列的范例程序

在程序清单10.62中,使用了动态内存分配来管理消息的存储空间,为了避免使用动态内存分配,可以使用消息队列,将每条消息的长度定义为8,以便存储按键编码和按键状态。范例程序详见程序清单10.75。

程序清单10.75 消息队列使用范例程序

该程序与程序清单10.62所示的程序分别使用邮箱和消息队列实现了相同的功能。消息队列避免了使用动态内存分配,在定义消息队列实体时,就完成了相关内存的静态分配。避免了使用动态内存分配的种种缺点。但是,当使用消息队列时,若定义的容量过大,可能造成不必要的内存浪费。

此外,邮箱和消息队列发送消息的方式是不同的,对于邮箱,其仅仅发送了消息的首地址,接收者接收到地址后,直接从地址中取出相应的消息,这种方式下,消息传递的效率很高。

但对于消息队列,发送消息时,是将整个消息内容(如按键编码和按键状态)拷贝到消息队列的缓冲区中,接收消息时,再将存储在消息队列缓冲区中的消息完整的拷贝到用户缓冲区中。由此可见,一次消息传输存在两次消息内容的完全拷贝过程,这种传输方式效率很低,特别是对于一条消息很大的情况。

因此,建议当一条消息很大时,使用邮箱;而当一条消息较小时,消息的拷贝对性能的影响较弱,则使用消息队列更加方便快捷。

当使用邮箱时,为了避免使用常规动态内存分配方法造成内存碎片、内存泄漏、分配效率等问题。可以使用AWorks提供的静态内存池管理技术,其为了避免内存碎片和分配效率等问题,将每次分配内存的大小设定为一个固定值。内存池管理技术将在“内存管理”章节中详细介绍。

10.5  自旋锁

互斥信号量用于任务间对共享资源的互斥访问,在一个任务获取互斥信号量时,若互斥信号量无效,需要等待时,则任务会主动释放CPU,内核调度器进而调度CPU去执行其它任务,当互斥信号量恢复有效时,再重新调度CPU继续执行之前的任务。这样,在任务等待互斥信号量有效的这段时间里,CPU可以被充分利用,去处理其他任务。

但是,调度过程是需要耗费一定时间的,有些时候,对共享资源的访问可能非常简单,消耗CPU的时间很短,也就是说,一个任务占用共享资源的时间非常短,其获得互斥信号量后很快就会释放。这种情况下,当一个任务获取互斥信号量时,即使当前的信号量无效,也意味着该信号量很快就会被释放,变为有效。若任务在此时释放CPU,执行任务调度,很可能在任务调度过程中,信号量就被释放了,系统又不得不在调度结束后重新将CPU再调度回来,这使系统在任务调度上花费了太多的时间成本。这种情况下,任务不释放CPU将是一种更好的选择,可以提高任务执行的效率。

AWorks提供了自旋锁,所谓“自旋”,就是一个“自我轮询检查”,当获取自旋锁时,若自旋锁处于无效(被锁)状态,则不会释放CPU,而是轮询检查自旋锁,直到自旋锁被释放(解锁)。检查到自旋锁被释放后,立即获取该自旋锁,使之成为锁住状态,接着尽快迅速完成对共享资源的访问,访问结束后,释放自旋锁。

由于当自旋锁不可用时,任务将一直循环检查自旋锁的状态直到可用而不会释放CPU, CPU在轮询等待期间不做任何其它有效的工作,因此,只有在共享资源占用时间极短的情况下,使用自旋锁才是合理的。否则,应该使用互斥信号量。需要特别注意的是,自旋锁不支持递归使用。

AWorks提供了使用自旋锁的通用接口,接口的原型详见表10.11。

表10.11 自旋锁通用接口(aw_spinlock.h)

在AWorks中,自旋锁可以在中断中使用,因而在接口命名中,含有“isr”关键字。之所以可以在中断中使用,是由于在获取到自旋锁后,会关闭总中断,释放自旋锁时,再打开总中断,使得在访问自旋锁保护的共享资源时,可以独占CPU,保证其不会被中断打断。否则,若任务在获取到自旋锁还未释放时被中断打断,在中断上下文中再次获取自旋锁将造成“死锁”:任务未释放自旋锁,中断只能等待;中断占用了CPU,任务只有等待中断结束返回后才能继续执行,以释放自旋锁。

换句话说,在AWorks中,自旋锁可以在中断中使用,任务和中断对共享资源的访问是互斥的,当任务访问共享资源时,中断会被关闭,以实现互斥。

1.  定义自旋锁实体

在使用自旋锁前,必须先使用aw_spinlock_isr_t类型定义自旋锁实体,该类型在aw_spinlock.h中定义,具体类型的定义用户无需关心,仅需使用该类型定义自旋锁实体,即:

其地址即可作为各个接口中p_lock参数的实参传递,表示具体要操作的自旋锁。

2.  初始化自旋锁

定义自旋锁实体后,必须使用该接口初始化后才能使用。其原型为:

其中,p_lock指向待初始化的自旋锁。flags为自旋锁的标志,当前无任何可用标志,该值需设置为0。初始化自旋锁的范例程序详见程序清单10.76。

程序清单10.76 初始化自旋锁

3.  获取自旋锁

获取自旋锁的函数原型为:

其中,p_lock指向需要获取的自旋锁。若自旋锁有效,则获取成功,并将自旋锁设置为无效状态;若自旋锁无效,则会轮询等待(不会像互斥信号量那样释放CPU),直到自旋锁有效(占用该锁的任务释放自旋锁)后返回。该接口可以在中断上下文中使用。获取自旋锁的范例程序详见程序清单10.77。

程序清单10.77 获取自旋锁

4.  释放自旋锁

释放自旋锁的函数原型为:

其中,p_lock指向需要释放的自旋锁。自旋锁的获取和释放操作应该成对出现,即在一个任务(或中断上下文)中,先获取自旋锁,再访问由该自旋锁保护的共享资源,访问结束后释放自旋锁。不可一个任务(或中断上下文)仅获取自旋锁,另一个任务(或中断上下文)仅释放自旋锁。释放自旋锁的范例程序详见程序清单10.78。

程序清单10.78 释放自旋锁

在互斥信号量的范例程序中(详见程序清单10.25),使用了两个任务互斥访问共享资源(调试串口)进行了举例说明,由于调试串口输出信息的速度慢,输出一条字符串信息耗时往往在毫秒级别,因此,这种情况下,使用自旋锁是不合适的。一般来讲,操作硬件设备都不建议使用自旋锁,自旋锁往往用于互斥访问类似于全局变量的共享资源。

例如,有两个任务task1和task2。在task1中,每隔50ms对全局变量进行加1操作,在task2中,检查全局变量的值,若达到10,则将全局变量的值重置为0,并翻转一次LED。

由于两个任务均需对全局变量进行操作,为了避免冲突,需要两个任务互斥访问该全局变量,显然,加值操作是非常快的,占用时间极短,可以使用自旋锁实现互斥访问,范例程序详见程序清单10.79。

程序清单10.79 自旋锁使用范例程序

原文标题:AWorks软件篇 — 实时内核(邮箱、消息队列和自旋锁)

文章出处:【微信号:ZLG_zhiyuan,微信公众号:ZLG致远电子】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
分享:

评论

相关推荐

NB-IoT在智慧城市中的发挥了什么作用

经过2017年“物联网元年”的爆发,目前物联网技术已经渗透各行各业,比如生产制造、交通物流、健康医疗....

的头像 人间烟火123 发表于 09-05 17:03 1385次 阅读
NB-IoT在智慧城市中的发挥了什么作用

CANDT一致性测试系统发布 保障CAN总线安全

CAN总线已经成为新能源汽车、军工、航空等行业的主控系统应用总线,ZLG致远电子致力于构建CAN总线....

的头像 ZLG致远电子 发表于 08-29 09:54 791次 阅读
CANDT一致性测试系统发布 保障CAN总线安全

LoRa模块和Sub-G模块在无线传输中选择考量

当无线数据的传输越来越广泛,我们仿佛才意识到无线传输的技术真真切切的就在我们的身边。本文简单介绍两种....

的头像 ZLG致远电子 发表于 08-10 09:36 2537次 阅读
LoRa模块和Sub-G模块在无线传输中选择考量

ZLG致远电子发布了三款全新一代测量测试设备

ZLG致远电子高级产品经理刘玉才在“功率变换单元的研发测试技术”的技术分享中,提出功率变换单元的测试....

的头像 电子技术应用ChinaAET 发表于 08-09 09:20 1075次 阅读
ZLG致远电子发布了三款全新一代测量测试设备

如何进行CAN通信设备的批量老化测试

老化测试是产品生产中必不可少的环节,对于CAN通信设备如何进行批量高效的老化测试呢?本文将从成本及方....

的头像 人间烟火123 发表于 08-08 15:19 962次 阅读
如何进行CAN通信设备的批量老化测试

500M示波器中颠覆性实现以太网分析功能

周立功教授领导的国内一流仪器开发团队,经过十几年如一日的技术攻关和创新,又一次在500M示波器中颠覆....

的头像 ZLG致远电子 发表于 08-08 10:14 1483次 阅读
500M示波器中颠覆性实现以太网分析功能

如何针对微带线拐角进行详细仿真

微带线拐角是微带线不连续结构之一,一些有经验的工程师甚至某些大型通信公司的工艺规范用所谓的经验告诉你....

的头像 ZLG致远电子 发表于 08-06 09:11 3304次 阅读
如何针对微带线拐角进行详细仿真

用于管理文件方法和数据结构案例分析

文件系统是在存储设备中(SD Card、NAND Flash…)组织文件的方法和数据结构,用于管理文....

的头像 周立功单片机 发表于 08-03 09:28 2143次 阅读
用于管理文件方法和数据结构案例分析

数据采集记录仪如何准确测试温度?

使用热电偶测量温度最常见的测温方法,但是由于热电偶冷端温度不为0℃,直接测量往往会造成较大误差。ZL....

的头像 ZLG致远电子 发表于 08-02 11:03 1137次 阅读
数据采集记录仪如何准确测试温度?

NFC核心板为例讲述基于AWorks平台开发的具体方法

ZLG致远电子推出了一系列“MCU+无线”核心板,本文以NFC核心板为例讲述基于AWorks平台开发....

的头像 周立功单片机 发表于 08-01 10:23 1276次 阅读
NFC核心板为例讲述基于AWorks平台开发的具体方法

关注MVB通信内容以及稳定性为何十分必要

MVB技术广泛应用于现代城市轨道交通车辆通信,对维护车辆通信效益具有直接影响。本文围绕MVB技术展开....

的头像 ZLG致远电子 发表于 07-30 09:05 1607次 阅读
关注MVB通信内容以及稳定性为何十分必要

从接口定义和实现两个方面,深入理解AWbus-lite

在使用AWBus-lite对设备进行管理时,无论设备处于 AWBus-lite拓扑结构中的哪个位置,....

的头像 周立功单片机 发表于 07-23 09:08 1425次 阅读
从接口定义和实现两个方面,深入理解AWbus-lite

如何改善接线过长导致编程失败或不稳定等一系列问题

在线烧录,集烧录与测试一体,得到了越来越多的人重视。为了加快烧录的效率,往往会制作烧录工装或夹具,而....

的头像 ZLG致远电子 发表于 07-20 09:23 1424次 阅读
如何改善接线过长导致编程失败或不稳定等一系列问题

针对汽车行业的通用型MCU应用

S32K系列汽车MCU是NXP推出的针对汽车行业的通用型MCU,采用亲民的ARM内核,可以覆盖从M0....

的头像 ZLG致远电子 发表于 07-12 08:55 2296次 阅读
针对汽车行业的通用型MCU应用

ZDS3000/4000系列示波器最新固件使用指南

一根网线一个IP,接下来所有操作交给IE浏览器。无论是看波形、截图、还是固件升级全部搞定,就这么简单....

的头像 ZLG致远电子 发表于 07-11 09:12 1134次 阅读
ZDS3000/4000系列示波器最新固件使用指南

M6708U-T系列工控核心板隔离设计指南

GPIO作为常用个开关量控制信号,广泛应用于工业领域的数据采集和驱动控制。当GPIO配置为DI和DO....

的头像 ZLG致远电子 发表于 07-10 09:04 1478次 阅读
M6708U-T系列工控核心板隔离设计指南

CAN隔离网桥中继器集线器系列应用方案

充电桩变多了,充电站变大了,通讯线变长了,那如何才能确保通讯能正常进行就变得重要了。

的头像 ZLG致远电子 发表于 07-06 09:16 1282次 阅读
CAN隔离网桥中继器集线器系列应用方案

教你使用功率计及功率分析仪测试

在使用功率计或者功率分析仪测试时,一般我们要求选择合适的量程,这样测量比较准确,一般要求测量数值在量....

的头像 ZLG致远电子 发表于 07-06 09:13 1339次 阅读
教你使用功率计及功率分析仪测试

通过CSI摄像头为示例,浅析如何通过普通摄像头识别二维码

M1052跨界核心板作为二维码扫描设备的主控方案,搭载NXP i.MX RT105x处理器,主频高达....

的头像 ZLG致远电子 发表于 07-04 09:07 2355次 阅读
通过CSI摄像头为示例,浅析如何通过普通摄像头识别二维码

基于AWBus-lite总线拓扑结构的设备管理框架

在使用AWBus-lite对设备进行管理时,无论设备处于 AWBus-lite拓扑结构中的哪个位置,....

的头像 ZLG致远电子 发表于 07-02 09:20 1326次 阅读
基于AWBus-lite总线拓扑结构的设备管理框架

IC安全烧录量产设计的一种机制

当产品正式转产批量生产烧录时,您是否担心过批量烧录过程能否真正保证核心敏感数据的安全,是否困扰过如果....

的头像 ZLG致远电子 发表于 07-02 09:17 925次 阅读
IC安全烧录量产设计的一种机制

Linux文件“哲学”是否与Windows相同呢?

“一切皆是文件”是Unix/Linux的基本哲学之一,目录、字符设备、块设备、套接字等在Unix/L....

的头像 ZLG致远电子 发表于 06-28 09:43 1164次 阅读
Linux文件“哲学”是否与Windows相同呢?

以NFC核心板为例讲述基于AWorks平台开发的具体方法

ZLG致远电子推出了一系列“MCU+无线”核心板,本文以NFC核心板为例讲述基于AWorks平台开发....

的头像 ZLG致远电子 发表于 06-22 09:16 1381次 阅读
以NFC核心板为例讲述基于AWorks平台开发的具体方法

AWBus-lite的拓扑结构及应用设计

为了管理系统中各式各样的硬件设备(或虚拟硬件设备),AWorks推出了领先的轻量级总线管理框架:AW....

的头像 ZLG致远电子 发表于 06-21 09:10 1324次 阅读
AWBus-lite的拓扑结构及应用设计

AWorks外围器件:驱动EEPROM和SPI Nor Flash存储器

一个应用的实现往往离不开大量的外围器件,如存储设备,RTC设备、显示设备等等。为了建立完整的生态系统....

的头像 ZLG致远电子 发表于 06-20 09:01 1454次 阅读
AWorks外围器件:驱动EEPROM和SPI Nor Flash存储器

带看门狗功能的专用复位芯片设计方案

在产品规划中,如使用芯片内部集成的DCDC模块给系统的各个子模块(如内存、电子硬盘、硬件看门狗等)供....

的头像 周立功单片机 发表于 06-13 09:16 1377次 阅读
带看门狗功能的专用复位芯片设计方案

面向AWorks框架和接口编程温度检测模块的软件详细资料概述

AWorks作为一个物联网生态系统,底层需要大量的信号(温度、电压、电流……)采集模块,以实现对外部....

的头像 ZLG致远电子 发表于 06-12 19:27 797次 阅读
面向AWorks框架和接口编程温度检测模块的软件详细资料概述

设备驱动在AWbus-lite中驱动设备正常工作

一个硬件设备正常工作的前提是系统中存在对应的驱动。AWorks提供了大量常用硬件设备的驱动,用户通常....

的头像 ZLG致远电子 发表于 06-12 09:06 978次 阅读
设备驱动在AWbus-lite中驱动设备正常工作

AWorks中开发设备驱动一般方法

本文详细介绍了AWorks中开发设备驱动的一般方法。基于这些通用的方法,用户可以尝试独立开发一些设备....

的头像 ZLG致远电子 发表于 06-11 08:59 1007次 阅读
AWorks中开发设备驱动一般方法

怎样才能设计出稳定可靠电源

目前市面上的电源模块品类繁多,初期应用都能满足要求,但随着时间的考验就开始经不起考验了。电源作为系统....

的头像 ZLG致远电子 发表于 06-05 09:24 2561次 阅读
怎样才能设计出稳定可靠电源

面向AWorks框架管理文件的方法和数据结构

文件系统是在存储设备中(SD Card、NAND Flash…)组织文件的方法和数据结构,用于管理文....

的头像 ZLG致远电子 发表于 06-05 09:18 1134次 阅读
面向AWorks框架管理文件的方法和数据结构

面向AWorks框架时间管理程序设计

实际应用中,时间管理往往是必不可少的。例如:定时完成某件事情、周期性地触发某一动作、测量评估程序运行....

的头像 ZLG致远电子 发表于 05-30 09:33 1653次 阅读
面向AWorks框架时间管理程序设计

I²C总线、UART总线和A/D转换器应用设计

在传统的基于寄存器的开发模式中,使用一个外设往往要阅读英文手册,理解寄存器每一位的含义,一步一步操作....

的头像 ZLG致远电子 发表于 05-28 08:57 4044次 阅读
I²C总线、UART总线和A/D转换器应用设计

一个高集成度的掉电检测电路应用

对于核心板的嵌入式系统来说,异常掉电情况下数据不丢失,稳定性是根本。本文基于CAT706介绍一种能够....

的头像 周立功单片机 发表于 05-25 09:35 2438次 阅读
一个高集成度的掉电检测电路应用

AWorks对常见的外部通用设备接口应用

AWorks对常用的硬件设备(LED、按键、蜂鸣器、数码管等等)进行了抽象,分别为每一类设备提供了通....

的头像 周立功单片机 发表于 05-24 09:44 2073次 阅读
AWorks对常见的外部通用设备接口应用

M105x系列跨界硬件核心板嵌入式开发

迄今为止,知道AWorks的开发者越来越多,却只有极少数了解其精髓,在即将发布之际,周立功教授及其团....

的头像 周立功单片机 发表于 05-23 09:18 1817次 阅读
M105x系列跨界硬件核心板嵌入式开发

全新的物联网操作系统AWorks IoT OS

Wi-Fi、zigbee、NFC、LoRa等无线技术使用得十分广泛,但无线电路设计存在诸多难点:天线....

的头像 ZLG致远电子 发表于 05-22 08:57 3311次 阅读
全新的物联网操作系统AWorks IoT OS

基于ARM9、Cortex-A系列高性能SoC无线核心板设计

基于AWorks平台的,集MCU、DDR2、NandFlash、硬件看门狗、无线芯片(Wi-Fi、N....

的头像 ZLG致远电子 发表于 05-21 09:39 2060次 阅读
基于ARM9、Cortex-A系列高性能SoC无线核心板设计

i.MX28x系列无线工控板中的IoT-A28LI主板整体布局

基于AWorks平台的ARM9与Cortex-A系列工控主板,具有丰富的扩展接口,可以扩展zigbe....

的头像 ZLG致远电子 发表于 05-19 08:56 1668次 阅读
i.MX28x系列无线工控板中的IoT-A28LI主板整体布局

基于AWorks平台A3352系列无线核心板设计方案

基于AWorks平台的A6G2C(Cortex-A7内核)与A3352(Cortex-A8内核)系列....

的头像 ZLG致远电子 发表于 05-18 08:56 1305次 阅读
基于AWorks平台A3352系列无线核心板设计方案

M3352核心板及评估底板及产品设计框图

为了便于用户快速开发产品,ZLG致远电子研发设计了基于AWorks平台的,集MCU、DDR3、Nan....

的头像 周立功单片机 发表于 05-16 09:09 1933次 阅读
M3352核心板及评估底板及产品设计框图

集MCU、DDR、NandFlash、硬件看门狗等等于一体核心板

为了便于用户快速开发产品,ZLG致远电子研发设计了基于AWorks平台的,集MCU、DDR、Nand....

的头像 周立功单片机 发表于 05-15 09:15 1987次 阅读
集MCU、DDR、NandFlash、硬件看门狗等等于一体核心板

AWorks开发者越来越多,却只有极少数了解其精髓

迄今为止,知道AWorks的开发者越来越多,却只有极少数了解其精髓,在即将发布之际,周立功教授撰写了....

的头像 ZLG致远电子 发表于 05-07 08:34 3100次 阅读
AWorks开发者越来越多,却只有极少数了解其精髓

周立功的AWorks哲学思想

苹果之所以成为全球最赚钱的公司,关键在于产品的性能超越了用户的预期,且因为大量可重用的核心领域知识,....

的头像 人间烟火123 发表于 05-04 14:38 2338次 阅读
周立功的AWorks哲学思想

基于Ametal平台开发,快速完成开发设计!

传统旋钮采用机械式结构,面板上需要开孔,长时间使用易磨损、进灰尘等,采用新型吸附式磁旋钮可以轻松解决....

的头像 周立功单片机 发表于 05-04 09:11 2075次 阅读
基于Ametal平台开发,快速完成开发设计!

labview上位机,周立功USBCAN1卡,传输指令给电机驱动器

用周立功的CAN卡,labview编制上位机,给电机驱动器发送速度值,电机不转这是为什么?附件程序是速度控制的PDO程序,请了解...

发表于 01-07 13:00 880次 阅读
labview上位机,周立功USBCAN1卡,传输指令给电机驱动器

周立功:基于平台的产品开发战略

引言  产品的BOM(物料清单)成本很低,而毛利又很高,为何四成上市公司的年利润却不够北上深广的一套房?房子到底被谁买走了,...

发表于 12-11 16:03 915次 阅读
周立功:基于平台的产品开发战略

LPC入门起步探讨,感觉LPCOpen例程包最好用

我是玩NXP LPC82x的,经验也不多,分享一下感受。 最近几天,抽时间分析了手头上的几个入门软件包,希望有经验的大哥给看...

发表于 11-27 01:06 2085次 阅读
LPC入门起步探讨,感觉LPCOpen例程包最好用

基于周立功CAN卡例程,显示某一特定ID报文数据的实时性问题

修改了例程,例如想显示当ID为0x101时,最后两个字节。 但现在发现当接收到大量报文时,显示就会有延时,有什么办法能提高实时...

发表于 10-19 09:42 916次 阅读
基于周立功CAN卡例程,显示某一特定ID报文数据的实时性问题

【下载】新书速递:周立功教授心血新力作《面向AMetal框架与接口的编程(上)》

概要:产品的BOM成本很低,而毛利又很高,为何很多上市公司的年利润却买不起一套房?房子到底是被谁买走了,这个问题值得我...

发表于 08-22 17:56 5452次 阅读
【下载】新书速递:周立功教授心血新力作《面向AMetal框架与接口的编程(上)》

新书速递:周立功教授心血新力作《面向AMetal框架与接口的编程(上)》

概要:产品的BOM成本很低,而毛利又很高,为何很多上市公司的年利润却买不起一套房?房子到底是被谁买走了,这个问题值得我们...

发表于 08-21 14:58 992次 阅读
新书速递:周立功教授心血新力作《面向AMetal框架与接口的编程(上)》

庆祝周立功顺利入驻硬之城,优势料号大放送!

高考刚结束台风也悄悄走了 朋友圈也跟着刷疯了 [一场大水冲走了车公庙] 纷纷表示,台风走了,又要重新开工 说真的!! 没为...

发表于 06-16 14:01 800次 阅读
庆祝周立功顺利入驻硬之城,优势料号大放送!

【下载】ARM嵌入式系统基础教程(周立功第2版)

简介: 《ARM嵌入式系统基础教程》是《ARM嵌入式系统系列教程》中的理论课教材。以PHILIPS公司LPC2000系...

发表于 06-12 17:59 7879次 阅读
【下载】ARM嵌入式系统基础教程(周立功第2版)

ARM嵌入式系统基础教程(第二版)周立功

知识分享,请勿转载……

发表于 05-24 15:00 1747次 阅读
ARM嵌入式系统基础教程(第二版)周立功

【完整资料】《程序设计与数据结构》周立功数十年心血力作

近日,周立功教授公开了数十年之心血力作《程序设计与数据结构》,此书在4月28日落笔,电子版已无偿性分享到电子工程师与高校群体...

发表于 05-16 16:43 50485次 阅读
【完整资料】《程序设计与数据结构》周立功数十年心血力作