链表

    技术2024-11-19  10

    (本文系原创,欢迎转载,请注明出处,谢谢)

    链表是操作系统实现中基本的、最重要的数据结构。操作系统中的链表一般都选择双向链表的设计,RTEMS~也不例外。由于面向对象设计思想在~RTEMS~中的运用,使得链表在具体的运用中多了一些变化。链表的控制块和链表节点的定义分列如下:    /*链表控制块定义*/    typedef struct {        Chain_Node *first;        Chain_Node *permanent_null;        Chain_Node *last;    } Chain_Control;    /*链表节点定义*/    typedef struct Chain_Node_struct Chain_Node;    struct Chain_Node_struct {      Chain_Node *next;      Chain_Node *previous;    };从定义上看,Chain_Control~中的~first~是指向链表的第一个节点;last~指向链表的最后一个节点;permanent_null~始终指向~NULL。Chain_Node~中的~previous~指向该节点的前驱;next~指向该节点的后继。RTEMS~的确是这样使用链表的,但有两个特殊情况需要作说明:一是链表为空时,Chain_Control~中指针的指向。如图/ref{emptychain}所示,first~指针指向~permanent_null~的地址,last~指针指向~first~指针的地址。特别注意,first~指针和~last~指针都不为空,但是~*first~为空,*last~不为空(原文出错,由网友szu_wsg纠正,感谢szu_wsg);另外是链表中的第一个节点的前驱指针和最后一个节点的后继指针的指向。如图/ref{fullchain}所示,第一个节点的~previous~指向链表控制块~first~指针的地址,最后一个节点的~next~指针指向~permanent_null~的地址。如果链表中的指针指向了~permanent_null~指针地址,它即为空;链表是否为空,判断~first~是否指向了~permanent_null~的地址即可。

     

     

    在链表为空时,first~指向~permanent_null~的地址,那么~first->next~为空指针,first->previous~指回~first~的地址;last~指向~first~的地址,last->next~指向~permanent_null~的地址,last->previous~为空指针。在链表不为空时,first~指向第一个节点,first->next~指向第二个节点的地址~(如果链表只有一个节点,则指向~permanent_null),first->previous~指向~first~的地址,first->previous->previous~为空指针;last~指向最后一个节点,last->next~指向~permanent_null,那么~last->next->next~为空指针,last->previous~指向倒数第二节点(如果只有一个节点,则指向~first)。如图所示,控制块的设计像是在链表中插入了一个头节点和尾节点,我们将其称为虚节点~(Virtual Node,头节点称为虚头~Virtual Head,尾节点称为虚尾~Virtual Tail)。正由于这两个虚节点的存在,%使得这个双向的链表变为了一个环形的双向链表;使得插入删除操作不需考虑节点所处位置,也无需对控制块修改。

     

     

    链表节点没有数据域,不同的类型只需将链表节点与其它数据结合起来构成数据节点。如~Objects_Control~的定义,链表节点在带有数据节点地址的起始位置。链表灵活的将不同的数据插入链表内,使用数据时,只需要将节点根据其类型强制转换成其实际类型即可使用。这种方法在~C~语言中是常见的设计技巧,从面向对象角度观察,这实际上是继承和多态思想的实际运用。    typedef struct {        Chain_Node     Node;        Objects_Id     id;        Objects_Name   name;    } Objects_Control;

     

     

    RTEMS~为链表提供了丰富的操作,分为链表初始化、节点的访问、状态查询三类。链表初始化主要包含以下两个操作: rtems_chain_initialize,将一段内存分割成设定的尺寸,然后将这些小内存块串成链表; rtems_chain_initialize_empty,将链表初始化为空链表,结果如图/ref{emptychain}所示。rtems_chain_initialize~的函数原型是:    void rtems_chain_initialize(        rtems_chain_control *the_chain,        void                *starting_address,        size_t              number_nodes,        size_t              node_size);以~starting_address~开始的内存,尺寸不小于~number_nodes * node_size,否则程序会访问到不该访问的内存,产生非常隐蔽的错误。代码级是无法提供这种越界检查的,需要靠工程师自己保证。一个节点的尺寸~(node_size)~不能小于一个~Chain_Node~的尺寸,否则会引起非常严重的错误。是由于~RTEMS~并未对~node_size~大小做检查,尽管检查代码很容易实现。内存块经过初始化后,形成如图/ref{initchain}所示的链表结构。链表的节点访问操作较多,主要分列如下: rtems_chain_head,获取一个链表的头节点地址,函数返回的是虚头节点的地址,    即~first~的地址,而不是第一个节点的地址; rtems_chain_tail,获取一个链表的尾节点地址,函数返回的是虚尾节    点的地址,即~permanent_null~的地址,而不是最后一个节点的地址; rtems_chain_extract,将指定的节点从链表中解链; rtems_chain_get,从链表中获取第一个节点,并从链表中解链; rtems_chain_insert,将一个新节点插入指定的节点之后; rtems_chain_append,将一个新节点添加在指定链表的末尾; rtems_chain_prepend,将一个新节点插入链表开始的位置。以上这些函数是线程安全的。在一些情况下,链表本身只有一个任务访问或者访问链表的环境就是线程安全的,需要一些非线程安全的函数,RTEMS~为用户提供了诸如~rtems_chain_append_unprotected~带~_unprotected~后缀的函数,以增加执行速度。注意:用户在应用程序中使用如~rtems_chain_head~带~rtems_~前缀的函数,在编写内核代码时或内部接口时应使用如~_Chain_Head~带~_~前缀的函数。链表的状态查询主要用于判断链表当前的状态,决定程序对链表的后续操作。主要操作有: rtems_chain_is_head,判断节点是否为指定链表的头节点; rtems_chain_is_tail,判断节点是否为指定链表的尾节点; rtems_chain_is_first,判断节点是否为链表中的第一个节点,注意,由于虚节点的存在,链表中的首节点并不会为真,只有虚头节点才会为真; rtems_chain_is_last,判断节点是否为链表中最后一个节点,同上,只有虚尾节点才会为真; rtems_chain_is_empty,判断链表是否为空; rtems_chain_are_nodes_equal,判断两个节点指针是否相等,即两个节点指针是否指向同一个节点; rtems_chain_has_only_one_node,判断链表是否只有一个节点; rtems_chain_is_null_node,判断节点指针是否为空指针,即用节点指针和~NULL~做比较。链表的设计思想精巧,但是操作的实现却不复杂。读者可自行阅读相关的源代码,这里不再赘述。

    (本文系原创,欢迎转载,请注明出处,谢谢)

    最新回复(0)