7.21笔记

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!
");
            else
                printf("Test false!
");
            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生成树协议。

网桥协议数据单元(Bridge Protocol Data Unit)。是一种生成树协议问候数据包,它以可配置的间隔发出,用来在网络的网桥间进行信息交换。
当一个网桥开始变为活动时,它的每个端口都是每2s(使用缺省定时值时)发送一个BPDU。然而,如果一个端口收到另外一个网桥发送过来的BPDU,而这个BPDU比它正在发送的BPDU更优,则本地端口会停止发送BPDU。如果在一段时间(缺省为20s)后它不再接收到邻居的更优的BPDU,则本地端口会再次发送BPDU。
BPDU是网桥协议数据单元(Bridge Protocol Data Unit)的英文首字母缩写。
MAC地址:使用组播地址。/*STP PKT 01-80-c2-00-00-00  and  LACP PKT 01-80-c2-00-00-02*/
5、LACP:
1) 在带宽比较紧张的情况下,可以通过逻辑聚合可以扩展带宽到原链路的n倍
2) 在需要对链路进行动态备份的情况下,可以通过配置链路聚合实现同一聚合组各个成员端口之间彼此动态备份.
启用某端口的LACP协议后,该端口将通过发送LACPDU向对端通告自己的系统优先级、系统MAC地址、端口优先级、端口号和操作Key。对端接收到这些信息后,将这些信息与其它端口所保存的信息比较以选择能够汇聚的端口,从而双方可以对端口加入或退出某个动态汇聚组达成一致。
操作Key是在端口汇聚时,LACP协议根据端口的配置(即速率、双工、基本配置、管理Key)生成的一个配置组合。
动态汇聚端口在启用LACP协议后,其管理Key缺省为零。静态汇聚端口在启用LACP后,端口的管理Key与汇聚组ID相同。
对于动态汇聚组而言,同组成员一定有相同的操作Key,而手工和静态汇聚组中,处于Active的端口具有相同的操作Key。
端口汇聚是将多个端口汇聚在一起形成一个汇聚组,以实现出/入负荷在汇聚组中各个成员端口中的分担,同时也提供了更高的连接可靠性。
 LACP,基于IEEE802.3ad标准的LACP(Link Aggregation Control Protocol,链路汇聚控制协议)是一种实现链路动态汇聚的协议。LACP协议通过LACPDU(Link Aggregation Control Protocol Data Unit,链路汇聚控制协议数据单元)与对端交互信息。
*********************
http://blog.chinaunix.net/uid-24148050-id-464587.html?page=2 好文章,必须找时间学习下!
原文地址:https://www.cnblogs.com/foonsun/p/5690619.html