DSP

细读内核双链表

2019-07-13 17:02发布

    快乐虾 http://blog.csdn.net/lights_joy/ lights@hb165.com      本文适用于 ADI bf561 DSP 优视科技BV561EVB开发板 uclinux-2008r1-rc8 (移植到vdsp5) Visual DSP++ 5.0     欢迎转载,但请保留作者信息   双链表是uClinux2.6内核中使用非常多的一种数据结构,它将双链表的数据和操作独立出来,使得双链表的一些操作不涉及到具体的数据。 本文所举的例子来自于fs/super.c、include/linux/fs.h和include/linux/list.h。 1.1.1   定义 双链表的定义及其基本操作的实现都在include/linux/list.h中,下面就是它的定义:   /*  * Simple doubly linked list implementation.  *  * Some of the internal functions ("__xxx") are useful when  * manipulating whole lists rather than single entries, as  * sometimes we already know the next/prev entries and we can  * generate better code by using them directly rather than  * using the generic single-entry routines.  */   struct list_head {      struct list_head *next, *prev; }; uClinux还提供了一个宏来定义一个双链表的表头: #define LIST_HEAD_INIT(name) { &(name), &(name) }   #define LIST_HEAD(name) /      struct list_head name = LIST_HEAD_INIT(name) 从上述定义可以看出初始化时表头的next和prev指针都是指向自身的。 下面是super.c中的例子: LIST_HEAD(super_blocks); 在内核中将所有的super_blocks通过双链表链接在一起,这就是这个双链表的表头。 1.1.2   链表与数据的结合 从链表的定义中看不出具体的数据,那么这个双链表是如何使用的呢?还是看看super.c的使用吧。 struct super_block {      struct list_head   s_list;       /* Keep this first */      struct list_head   s_instances;      .. }; 这就是要用双链表链接起来的数据结构,在此结构体中声明了一个list_head的list,这个成员将用于此结构的链接。 /**  *   sget -    find or create a superblock  *   @type:   filesystem type superblock should belong to  *   @test:   comparison callback  *   @set:    setup callback  *   @data:   argument to each of them  */ struct super_block *sget(struct file_system_type *type,               int (*test)(struct super_block *,void *),               int (*set)(struct super_block *,void *),               void *data) {      struct super_block *s = NULL;      …….          s = alloc_super(type);      …….      list_add_tail(&s->s_list, &super_blocks);      …….      return s; } 正是通过list_add_tail这个函数就将具体的数据结构与双链表的表头链接起来了。看看list_add_tail函数: /**  * list_add_tail - add a new entry  * @new: new entry to be added  * @head: list head to add it before  *  * Insert a new entry before the specified head.  * This is useful for implementing queues.  */ static inline void list_add_tail(struct list_head *new, struct list_head *head) {      __list_add(new, head->prev, head); } 因为双链表是一个环形链表,即表尾连接到表头,因此通过将new插入到head的前一个节点和head之间,实际就是插入到表尾。 /*  * Insert a new entry between two known consecutive entries.  *  * This is only for internal list manipulation where we know  * the prev/next entries already!  */ static inline void __list_add(struct list_head *new,                     struct list_head *prev,                     struct list_head *next) {      next->prev = new;      new->next = next;      new->prev = prev;      prev->next = new; }   1.1.3   双链表成员的访问 在有了双链表的节点后,应该如何访问其成员呢?下面是super.c的例子: /**  *   sget -    find or create a superblock  *   @type:   filesystem type superblock should belong to  *   @test:   comparison callback  *   @set:    setup callback  *   @data:   argument to each of them  */ struct super_block *sget(struct file_system_type *type,               int (*test)(struct super_block *,void *),               int (*set)(struct super_block *,void *),               void *data) {      struct super_block *s = NULL;      struct list_head *p;      int err;   retry:      spin_lock(&sb_lock);      if (test) list_for_each(p, &type->fs_supers) {          struct super_block *old;          old = list_entry(p, struct super_block, s_instances);           ………..      }      ……….. } 秘密就在list_entry这个宏定义:   /**  * list_entry - get the struct for this entry  * @ptr: the &struct list_head pointer.  * @type: the type of the struct this is embedded in.  * @member:   the name of the list_struct within the struct.  */ #define list_entry(ptr, type, member) /      container_of(ptr, type, member)     /**  * container_of - cast a member of a structure out to the containing structure  * @ptr: the pointer to the member.  * @type: the type of the container struct this is embedded in.  * @member:   the name of the member within the struct.  *  */ #define container_of(ptr, type, member) ({              /         const typeof( ((type *)0)->member ) *__mptr = (ptr); /         (type *)( (char *)__mptr - offsetof(type,member) );}) 此处需要注意的是typeof这个关键字的使用,在Visual DSP的文档中有一个说明: The typeof keyword is an extension to C originally implemented in the GCC compiler. It should be used with caution because it is not compatible with other dialects of C or C++ and has not been adopted by the more recent C99 standard. offsetof的定义就比较好理解了: #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 1.1.4   节点的遍历 在上一节的例子中,使用了一个叫list_for_each的宏来进行链表的遍历:   /**  * list_for_each   -    iterate over a list  * @pos: the &struct list_head to use as a loop cursor.  * @head: the head for your list.  */ #define list_for_each(pos, head) /      for (pos = (head)->next; prefetch(pos->next), pos != (head); /         pos = pos->next) 与此相似的遍历的宏还有list_for_each_entry: /**  * list_for_each_entry -    iterate over list of given type  * @pos: the type * to use as a loop cursor.  * @head: the head for your list.  * @member:   the name of the list_struct within the struct.  */ #define list_for_each_entry(pos, head, member)               /      for (pos = list_entry((head)->next, typeof(*pos), member);   /           prefetch(pos->member.next), &pos->member != (head);     /           pos = list_entry(pos->member.next, typeof(*pos), member)) 二者的区别在于后一个宏可以直接访问指定成员,而前者必须自己使用list_entry宏来取结构体。如下所示: static void do_emergency_remount(unsigned long foo) {      struct super_block *sb;        spin_lock(&sb_lock);      list_for_each_entry(sb, &super_blocks, s_list) {          sb->s_count++;          ……………      }      …………… }   1.1.5   删除链表中的一个节点 在list.h中提供了一个list_del函数将一个节点从双链表中删除,如下所示: /*  * Delete a list entry by making the prev/next entries  * point to each other.  *  * This is only for internal list manipulation where we know  * the prev/next entries already!  */ static inline void __list_del(struct list_head * prev, struct list_head * next) {      next->prev = prev;      prev->next = next; }   /**  * list_del - deletes entry from list.  * @entry: the element to delete from the list.  * Note: list_empty() on entry does not return true after this, the entry is  * in an undefined state.  */ static inline void list_del(struct list_head *entry) {      __list_del(entry->prev, entry->next);      entry->next = LIST_POISON1;      entry->prev = LIST_POISON2; } 在这里LIST_POISON1LIST_POISON2是定义好的两个常量: /*  * These are non-NULL pointers that will result in page faults  * under normal circumstances, used to verify that nobody uses  * non-initialized list entries.  */ #define LIST_POISON1 ((void *) 0x00100100) #define LIST_POISON2 ((void *) 0x00200200) 用以判断一个节点是否为空节点。 与此相似的是一个叫list_del_init的函数: /**  * list_del_init - deletes entry from list and reinitialize it.  * @entry: the element to delete from the list.  */ static inline void list_del_init(struct list_head *entry) {      __list_del(entry->prev, entry->next);      INIT_LIST_HEAD(entry); } static inline void INIT_LIST_HEAD(struct list_head *list) {      list->next = list;      list->prev = list; } 与上一个函数不同的是,它将节点的next和prev指针指向自身,而不是一个固定的值,这也是list节点初始化的状态。 下面是super.c中的例子: /**  *   generic_shutdown_super -    common helper for ->kill_sb()  *   @sb: superblock to kill  *  *   generic_shutdown_super() does all fs-independent work on superblock  *   shutdown. Typical ->kill_sb() should pick all fs-specific objects  *   that need destruction out of superblock, call generic_shutdown_super()  *   and release aforementioned objects. Note: dentries and inodes _are_  *   taken care of and do not need specific handling.  *  *   Upon calling this function, the filesystem may no longer alter or  *   rearrange the set of dentries belonging to this super_block, nor may it  *   change the attachments of dentries to inodes.  */ void generic_shutdown_super(struct super_block *sb) {      const struct super_operations *sop = sb->s_op;      ………………….      spin_lock(&sb_lock);      /* should be initialized for __put_super_and_need_restart() */      list_del_init(&sb->s_list);      list_del(&sb->s_instances);      spin_unlock(&sb_lock);      up_write(&sb->s_umount); }   1.1.6   链表合并 内核中提供了以下函数进行链表的合并: static inline void __list_splice(struct list_head *list,                     struct list_head *head) {      struct list_head *first = list->next;      struct list_head *last = list->prev;      struct list_head *at = head->next;        first->prev = head;      head->next = first;        last->next = at;      at->prev = last; }   /**  * list_splice - join two lists  * @list: the new list to add.  * @head: the place to add it in the first list.  */ static inline void list_splice(struct list_head *list, struct list_head *head) {      if (!list_empty(list))          __list_splice(list, head); }   /**  * list_splice_init - join two lists and reinitialise the emptied list.  * @list: the new list to add.  * @head: the place to add it in the first list.  *  * The list at @list is reinitialised  */ static inline void list_splice_init(struct list_head *list,                        struct list_head *head) {      if (!list_empty(list)) {          __list_splice(list, head);          INIT_LIST_HEAD(list);      } } 可以看到list_splice函数将list插入到了head的表头。   1.1.7   优点 uclinux这种双链表的实现方式的优点在于用很少的空间代价(8个字节的链表头)取得了代码的很大简化。因为如果只用一个指针来存储双链表的表头,那么在每次进行插入或者删除操作的时候都必须判断此头指针是否为空!