1、线性结构是最简单最常用的一种数据结构,线性结构的特点是结构中的元素之间满足线性关系,按这个关系可以把所有元素排成一个线性序列.线性表,串,栈和队列都属于线性结构. 而非线性结构是指在该类结构中至少存在一个数据元素,它具有两个或者两个以上的前驱或后继.如树和二叉树等. 简单地说,线性结构是一个数据元素的有序(次序)集合。它有四个基本特征: 1.集合中必存在唯一的一个"第一个元素"; 2.集合中必存在唯一的一个"最后的元素"; 3.除最后元素之外,其它数据元素均有唯一的"后继"; 4.除第一元素之外,其它数据元素均有唯一的"前驱"。 数据结构中线性结构指的是数据元素之间存在着“一对一”的线性关系的数据结构。 如(a1,a2,a3,.....,an),a1为第一个元素,an为最后一个元素,此集合极为一个线性结构的集合。
2、双链表的再次学习:
(1)什么是双链表?
网上很多对双向链表解释的文章都是用这个结构:
它们的连接情况是这样的:
相当的不直观,今天我要从详细的地址出发来解释双向链表的原理。
现定义一个结构体如下:
struct student
{char name;
struct student *next;
struct student *prior;
};
现在有5个人A,B,C,D,E.这五个人构成的链表如下:
(2)哨兵节点:
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
为了练习,我这次设置了哨兵结点,哨兵结点,我个人理解就是我们一般意义上的头结点(是链表的一个附加结点,数据域不存储任何信息的),只不过是链表的两端都放了一个,所以形象的称之为“哨兵”。所以和单链表的基本操作中一样,链表的有效结点应该从第二个开始。
哨兵节点的引入的方便性在于不需要对线性表的第一个节点单独进行处理,比如在某个位置上插入一个节点,一般的方法是找到该位置上原来节点的前一个节点,然后将新节点加在后面,如果没有哨兵节点的话,当插入位置为线性表的第一个位置的时候,需要考虑该位置上原来的节点并没有前驱节点;而如果有哨兵节点的话,线性表的每个位置的节点都有前驱节点,因此可以统一处理。(注意:哨兵节点根本不出现在线性表中,所以虽然它没有前驱,但是前面的那句话并不矛盾)。原文:http://blog.csdn.net/gaoxiang_/article/details/8279536
(3)例子学习:
#include <memLib.h>
#include <stdlib.h>
#include <string.h>
#include <tickLib.h>
#include <hwApiType.h>
#define sal_strlen strlen
#define sal_strcpy strcpy
#define sal_strcmp strcmp
#define sal_memcpy memcpy
#define sal_memset memset
#define sal_memcmp memcmp
#define sal_memmove memmove
enum
{
NP_E_NONE = 0, /*OK,没有错误*/
NP_E_INTERNAL = 1, /*内部错*/
NP_E_MEMORY = 2, /*内存错*/
NP_E_UNIT = 3, /*非法 unit 值*/
NP_E_PARAM = 4, /*非法参数*/
NP_E_EMPTY = 5, /*表空错*/
NP_E_FULL = 6, /*表满错" */
NP_E_NOT_FOUND = 7, /*表项未找到*/
NP_E_EXISTS = 8, /*表项已存在*/
NP_E_TIMEOUT = 9, /*操作超时*/
NP_E_BUSY = 10, /*忙,操作仍在进行*/
NP_E_FAIL = 11, /*操作失败*/
NP_E_DISABLED = 12, /*功能已禁用*/
NP_E_BADID = 13, /*错误 ID*/
NP_E_RESOURCE = 14, /*操作未得到资源*/
NP_E_CONFIG = 15, /*非法配置*/
NP_E_UNAVAIL = 16, /*功能不支持*/
NP_E_INIT = 17, /*功能未初始化*/
NP_E_PORT = 18, /*非法 port 值*/
NP_E_ROLLBACK = 19, /*操作失败,进行了滚回操作*/
NP_E_OTHER = 20, /*其它错误*/
}NP_STATUS_D;
extern void* sal_lib_alloc(unsigned int, char *);
extern void sal_lib_free(void *);
typedef struct sal_node_s
{
struct sal_node_s* next;
struct sal_node_s* previous;
}sal_node_t;
typedef struct sal_list_s
{
sal_node_t* head;
sal_node_t* tail;
}sal_list_t;
#define SAL_LIST_FIRST(pList) (((sal_list_t*)(pList))->head)
#define SAL_LIST_LAST(pList) (((sal_list_t*)(pList))->tail)
#define SAL_LIST_NEXT(pNode) (((sal_node_t*)(pNode))->next)
#define SAL_LIST_PREVIOUS(pNode) (((sal_node_t*)(pNode))->previous)
#define SAL_LIST_EMPTY(pList) (((sal_list_t*)pList)->head == NULL)
typedef int (*NODE_FUN)(sal_node_t*, int);
int sal_ListInit(sal_list_t *pList);
sal_list_t* sal_ListCreate(void);
int sal_ListDelete(sal_list_t* pList);
void sal_ListInsert(sal_list_t* pList, sal_node_t* pPrev, sal_node_t* pNode);
void sal_ListAdd(sal_list_t* pList, sal_node_t* pNode);
void sal_ListRemove(sal_list_t* pList, sal_node_t* pNode);
sal_node_t* sal_ListGet(sal_list_t* pList);
int sal_ListCount(sal_list_t* pList);
int sal_ListEach(sal_list_t* pList, NODE_FUN routine, int routineArg);
int sal_ListClear(sal_list_t* pList, NODE_FUN clear_node_routine, int routineArg);
双链表的head和tail其实记录着前驱和后驱节点的Address,这样就可以很方便的去查找地址处的数据结构。这样理解其使用。
具体使用上面例子的数据结构如下:
typedef struct { sal_list_t outerTrunkData; sal_list_t innerTrunkData; }HW_TRUNK_INFO_CFG_T; typedef struct{ sal_node_t node; UINT32 trunkID; UINT32 trunkPortNum; UINT32 trunkPortList[XXXX]; UINT32 trunkFwdList[XXXX]; UINT32 trunkBlkRuleID[XXXX][2]; }HW_TRUNK_INFO_ENTRY_T; HW_TRUNK_INFO_CFG_T g_trunk_info_cfg;
HW_TRUNK_INFO_CFG_T 作为一个全局记录器,里面使用双链表的头和尾节点,就可以遍历到关于TRUNK的全部数据结构成员。
3、互斥锁和二值信号量的区别
看完网上的各种说的区别,感觉就是一堆鸟屎啊,看完还是糊里糊涂的。看看FreeRTOS里的互斥锁和二值信号量,官网也有英文版的对这两者区别的说明,中文如下:
Freertos 互斥锁
信号量是任务与任务之间,任务与中断之间同步的重要方式,二元信号量与互斥锁十分相像,两者之间的差别在于优先级的继承机制,当另外一个具有更高优先级的任务试图获取同一个互斥锁时,已经获得互斥锁的任务将继承试图获取同一互斥锁的任务的优先级。而信号量没有这种机制,所以二元信号量更适合实现同步,互斥锁更适合实现简单的互斥。需要注意的是互斥锁在使用完资源后必须返还,否则高优先级的任务永远也无法获取互斥锁,低优先级的任务将不会放弃优先级的继承。二元信号量并不需要在得到后立即释放,因此同步操作一般可以通过一个任务或中断持续释放信号量,另一个任务持续获取信号量来实现。
互斥锁主要作用是保护资源,创建一个全局变量test_value作为共享资源,其中任务2每隔十毫秒申请操作资源,将test_value赋值为1,任务1每隔100ms申请互斥锁,成功后将test_value清0,并等待1秒时间,1秒内如果test_value值被改写则打印测试失败,如果值没有被改变则打印测试成功,释放互斥锁。
/* includes. */
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "stm32f10x.h"
#include "semphr.h"
#include "portmacro.h"
static void prvSetupHardware( void );
void vPrintTask(void *pvParameters);
void vTask1(void *pvparameters);
void vTask2(void *pvparameters);
void vLedInit(void);
void UartInit(void);
#define LED1_ON() GPIO_SetBits(GPIOB,GPIO_Pin_0)
#define LED1_OFF() GPIO_ResetBits(GPIOB,GPIO_Pin_0)
SemaphoreHandle_t xSemaphore = NULL;
unsigned int test_value = 0;
int main( void )
{
prvSetupHardware();
/* 创建互斥信号量 */
xSemaphore = xSemaphoreCreateMutex();
xTaskCreate( vTask1, "vTask1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+6, NULL );
xTaskCreate( vTask2, "vTask2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+7, NULL );
vTaskStartScheduler();
return 0;
}
void vTask1(void *pvparameters)
{
TickType_t xLastWakeTime;
while(1)
{
if(xSemaphoreTake( xSemaphore, portMAX_DELAY ) == pdTRUE)
{
test_value = 0;
xLastWakeTime = xTaskGetTickCount();
vTaskDelayUntil( &xLastWakeTime, 1000/portTICK_RATE_MS );
if(!test_value)
printf("Test ok!\r\n");
else
printf("Test false!\r\n");
xSemaphoreGive( xSemaphore );
}
xLastWakeTime = xTaskGetTickCount();
vTaskDelayUntil( &xLastWakeTime, 100/portTICK_RATE_MS );
}
}
void vTask2(void *pvparameters)
{
TickType_t xLastWakeTime;
while(1)
{
if(xSemaphoreTake( xSemaphore, portMAX_DELAY ) == pdTRUE)
{
test_value = 1;
xSemaphoreGive( xSemaphore );
}
xLastWakeTime = xTaskGetTickCount();
vTaskDelayUntil( &xLastWakeTime, 10/portTICK_RATE_MS );
}
}
4\
BPDU是运行STP的交换机之间交换的消息帧。BPDU内包含了STP所需的路径和优先级信息,STP便利用这些信息来确定根桥以及到根桥的路径。
可以用来抑制风暴环回。利用STP生成树协议。