内核链表的分析与使用

原创 2015年11月18日 14:51:47

链表(循环双向链表)是Linux内核中最简单、最常用的一种数据结构。


No.

主要 函数

说明

1. list_add 在 head 之后追加一个节点
2. list_add_tail 在 head 之前追加一个节点, 也就是在末尾追加一个节点
3. list_del 删除一个节点, 并将这个节点的next, prev 置为 NULL
4. list_del_init 删除一个节点并初始化删除的节点
5. list_replace 替换一个节点
6. list_replace_init 替换一个节点, 并初始化被替换的节点
7. list_move 移动节点到 head 之后
8. list_move_tail 移动节点到 head 之前
9. list_is_last 判断节点是否是链表中最后一个
10. list_empty 判断链表是否为空 (即, 是否只有 head 节点)
11. list_is_singular 判断链表中是否只有一个节点 (除了 head 之外)
12. list_cut_position 将1个链表截断为2个链表
13. list_splice 将2个链表合并为1个链表, @list中的所有节点(不包括list)加入到 head 之后
14. list_splice_tail 将2个链表合并为1个链表, @list中的所有节点(不包括list)加入到 head 之前
15. list_splice_init 同 list_splice, 最后会初始化 @list
16. list_splice_tail_init 同 list_splice_tail, 最后会初始化 @list

 

No.

主要 宏

说明

1. list_entry 获取包含此节点的 struct
2. list_first_entry 获取包含此节点的 首个 struct
3. list_for_each 从 head节点之后一个节点开始向后循环
4. list_for_each_prev 从 head节点之前一个节点开始向前循环
5. list_for_each_safe list_for_each 的安全版本, 即, 循环时即使有其它线程删除节点也可正常运行
6. list_for_each_prev_safe list_for_each_prev 的安全版本
7. list_for_each_entry 同 list_for_each, 只是参数不同
8. list_for_each_entry_reverse 同 list_for_each_prev, 只是参数不同
9. list_for_each_entry_continue 同 list_for_each_entry, 但不是从头(head)开始循环的
10. list_for_each_entry_continue_reverse 同 list_for_each_entry_reverse, 但不是从头(head)开始循环的
11. list_for_each_entry_from 从指定位置开始向后循环
12. list_for_each_entry_safe list_for_each_entry 的安全版本
13. list_for_each_entry_safe_continue list_for_each_entry_continue 的安全版本
14. list_for_each_entry_safe_from list_for_each_entry_from 的安全版本
15. list_for_each_entry_safe_reverse list_for_each_entry_reverse 的安全版本
   
       1、链表的定义
            struct list_head {
                struct list_head *next, *prev;
            }
           这个不含数据域的链表,可以嵌入到任何数据结构中,例如可按如下方式定义含有数据域的链表:
            struct my_list {
                void  * mydata;
                struct list_head  list;
            } ;

       2、链表的声明和初始化宏
            struct list_head 只定义了链表结点,并没有专门定义链表头.那么一个链表结点是如何建立起来的?
内核代码 list.h 中定义了两个宏:
            #defind  LIST_HEAD_INIT(name)    { &(name), &(name) }      //仅初始化
            #defind  LIST_HEAD(name)     struct list_head  name = LIST_HEAD_INIT(name)  //声明并初始化
           
            如果要声明并初始化链表头mylist_head,则直接调用:LIST_HEAD(mylist_head),之后,
mylist_head的next、prev指针都初始化为指向自己。这样,就有了一个带头结点的空链表。

             判断链表是否为空的函数:
             static inline int list_empty(const struct list_head  * head) {
                  return head->next  ==  head;
              }    //返回1表示链表为空,0表示不空

      3、在链表中增加一个结点
          (内核代码中,函数名前加两个下划线表示内部函数)
            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 ;
            }  
          
            list.h 中增加结点的两个函数为:
           (链表是循环的,可以将任何结点传递给head,调用这个内部函数以分别在链表头和尾增加结点)
            static inline void list_add(struct list_head *new, struct llist_head *head)
            {
                    __list_add(new, head, head -> next) ;
            }
            static inline void list_add_tail(struct list_head 8new, struct list_head *head)
            {
                     __list_add(new, head -> prev, head) ;
            }
            附:给head传递第一个结点,可以用来实现一个队列,传递最后一个结点,可以实现一个栈。
            static 加在函数前,表示这个函数是静态函数,其实际上是对作用域的限制,指该函数作用域仅局限
           于本文件。所以说,static 具有信息隐蔽的作用。而函数前加 inline 关键字的函数,叫内联函数,表
           示编译程序在调用这个函数时,立即将该函数展开。
            
    4、 遍历链表
           list.h 中定义了如下遍历链表的宏:
           #define   list_for_each(pos, head)    for(pos = (head)-> next ;  pos != (head) ;  pos = pos -> next)  
           这种遍历仅仅是找到一个个结点的当前位置,那如何通过pos获得起始结点的地址,从而可以引用结
点的域?list.h 中定义了 list_entry 宏:
           #define   list_entry( ptr, type, member )  \
              ( (type *) ( (char *) (ptr)  - (unsigned long) ( &( (type *)0 )  ->  member ) ) )
          分析:(unsigned long) ( &( (type *)0 )  ->  member ) 把 0 地址转化为 type 结构的指针,然后获取该
          结构中 member 域的指针,也就是获得了 member 在type 结构中的偏移量。其中  (char *) (ptr) 求
         出的是 ptr 的绝对地址,二者相减,于是得到 type 类型结构体的起始地址,即起始结点的地址,这样
就能读取节点里的成员


参考代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "./kernel_list.h"

struct student {
    int num;
    char name[20];
    struct list_head list;
};

int main(void)
{
    struct student *node = NULL; //node
    struct list_head *pos = NULL,*n = NULL;
    struct student  *pnode = NULL; //

    // creat
    struct student *head = (struct student*)malloc(sizeof(struct student));// root
    if (head == NULL) {
        printf("file,%s line,%d:malloc error!\n",__FILE__,__LINE__);
        exit(1);
    }
    INIT_LIST_HEAD(&head->list);

    // insert
    node = (struct student*)malloc(sizeof(struct student));
    if (node == NULL) {
        printf("file,%s line,%d:malloc error!\n",__FILE__,__LINE__);
        exit(1);
    }
    node->num = 1234;
    strcpy(node->name,"zhangsan");
    list_add_tail(&node->list,&head->list);

    node = (struct student*)malloc(sizeof(struct student));
    if (node == NULL) {
        printf("file,%s line,%d:malloc error!\n",__FILE__,__LINE__);
        exit(1);
    }
    node->num = 1235;
    strcpy(node->name,"lisi");
    list_add_tail(&node->list,&head->list);

    
    node = (struct student*)malloc(sizeof(struct student));
    if (node == NULL) {
        printf("file,%s line,%d:malloc error!\n",__FILE__,__LINE__);
        exit(1);
    }
    node->num = 1233;
    strcpy(node->name,"wangwu");
    list_add(&node->list,&head->list);

    //display
    if (list_empty(&head->list)) {
        printf("list is empty!\n");
    } else {    
        list_for_each(pos,&head->list) {
            pnode = list_entry(pos,struct student,list); // struct student * pnode= (位置 , 大结构体类型 , struct list_head list)
            printf("num:%d,name %s\n",pnode->num,pnode->name);    
        }
    }
    
    // free
    list_for_each_safe(pos,n,&head->list) {
        list_del(pos);
        pnode = list_entry(pos,struct student,list);
        printf("num %d has removed !\n",pnode->num);
        free(pnode);
    }
    //sort
    //可用list_move实现排序


    free(head);


    return 0;
}
可自己参考再分块用函数实现

练习的源代码在

改 :在main()里的 while(1)里 加
if(getchar() == '\n')
   continue;

版权声明:本文为博主原创文章,未经博主允许不得转载。

Linux内核链表分析

从Ubuntu系统中获取内核链表头文件方式: cp list.h /home/gec/Download/linux-2.6.35.7-gec/include/linux /home/gec/D...
  • qq_26501341
  • qq_26501341
  • 2016年12月13日 19:31
  • 300

linux内核链表之实例一

基本知识可以看这个网址深入分析 Linux 内核链表http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/  这个例子包括简单的增、删、遍...
  • lufeiop02
  • lufeiop02
  • 2011年06月04日 21:02
  • 3351

在应用层使用内核链表

linux内核中的shuang
  • linuxjourney
  • linuxjourney
  • 2014年08月12日 23:06
  • 1617

内核链表和普通链表的区别

.内核链表和普通链表的区别          内核链表是一个双向链表,但是与普通的双向链表又有所区别。内核链表中的链表元素不与特定类型相关,具有通用性。          我们先来看一幅图...
  • hj5225244
  • hj5225244
  • 2014年01月12日 12:06
  • 735

内核链表实现分析与使用

TODO
  • famousid
  • famousid
  • 2012年09月20日 15:17
  • 94

用户空间使用linux内核链表list编程

在用户空间编程使用linux内核链表list,hlist宏定义和操作.  linux内核中的list_head和hlist_head/hlist_node是将数据结构串起来成为链表的两个重要链表...
  • chrovery
  • chrovery
  • 2014年10月26日 12:17
  • 1197

linux内核中链表结构及使用方法

声明:在学习linux 内核数据结构之链表部分时参考了http://blog.chinaunix.net/uid-14114479-id-2932024.html。 Linux kernel里面从来...
  • wangliang888888
  • wangliang888888
  • 2016年04月21日 22:45
  • 1823

Linux内核链表VS传统链表

Linux内核中的链表是一个C实现的链表的典型 传统的链表都是数据和指针装在一个节点里: struct node {     type data;     struct node *prev,*nex...
  • bbs375
  • bbs375
  • 2016年09月20日 15:22
  • 359

通用(内核)链表详解

Linux内核中充斥着大量的数据结构,这些数据结构很多都是使用结构体来表示:如cdev结构体用于描述一个字符设备,再如task_struct结构体,是我们所说的进程控制块PCB,用于描述一个进程的所有...
  • liebao_han
  • liebao_han
  • 2016年12月31日 16:21
  • 310

Linux内核链表深度分析

链表简介: 链表是哟中常用的数据结构,它通过指针将一系列数据节点连接成一条数据链。相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插...
  • coding__madman
  • coding__madman
  • 2016年05月07日 16:15
  • 5799
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:内核链表的分析与使用
举报原因:
原因补充:

(最多只允许输入30个字)