Linux内核中链表的实现与应用【转】

转自:http://blog.chinaunix.net/uid-27037833-id-3237153.html

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

       

       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 类型结构体的起始地址,即起始结点的地址。

 

   5、链表的应用

         一个用以创建、增加、删除和遍历一个双向链表的Linux内核模块

点击(此处)折叠或打开

  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/slab.h>
  4. #include <linux/list.h>
  5. MODULE_LICENCE("GPL");
  6. MODULE_AUTHOR("LUOTAIJIA");
  7. #define N 10
  8. struct numlist {
  9.     int num;
  10.     struct list_head list;
  11. };
  12. struct numlist numhead;
  13. static int __init doublelist_init(void)
  14. {
  15.     //初始化头结点
  16.     struct numlist * listnode; //每次申请链表结点时所用的指针
  17.     struct list_head * pos;
  18.     struct numlist * p;
  19.     int i;
  20.     printk("doublelist is starting...\n");
  21.     INIT_LIST_HEAD(&numhead.list);
  22.     /*
  23.      * static inline void INIT_LIST_HEAD(struct list_head *list)
  24.      * {
  25.      * list->next = list;
  26.      * list->prev = list;
  27.      * }
  28.      */
  29.     //建立N个结点,依次加入到链表当中
  30.     for (i=0; i<N; i++) {
  31.         listnode = (struct numlist *)kmalloc(sizeof(struct numlist), GFP_KERNEL);
  32.         //void *kmalloc(size_t size, int flages)
  33.         //分配内存,size 要分配内存大小,flags 内存类型
  34.         listnode->num = i+1;
  35.         list_add_tail(&listnode->list, &numhead.list);
  36.         printk("Node %d has added to the doublelist...\n", i+1);
  37.     }
  38.     //遍历链表
  39.     i = 1;
  40.     list_for_each(pos, &numhead.list) {
  41.         p = list_entry(pos, struct numlist, list);
  42.         printk("Node %d's data: %d\n", i, p->num);
  43.         i++;
  44.     }
  45.     return 0;
  46. }
  47. static void __exit doublelist_exit(void)
  48. {
  49.     struct list_head *pos, *n;
  50.     struct numlist *p;
  51.     int i;
  52.     //依次删除N个结点
  53.     i = 1;
  54.     list_for_each_safe(pos, n, &numhead.list) {
  55.         //为了安全删除结点而进行的遍历
  56.         list_del(pos); //从链表中删除当前结点
  57.         p = list_entry(pos, struct numlist, llist);
  58.         //得到当前数据结点的首地址,即指针
  59.         kfree(p); //释放该数据结点所占空间
  60.         printk("Node %d has removed from the doublelist...\n", i++);
  61.     }
  62.     printk("doublelist is exiting...\n");
  63. }
  64. module_init(doublelist_init);
  65. module_exit(doublelist_exit);
  66.  

 

参考资料:Linux操作系统原理与应用(第2版)    陈莉君、康华 编著

时间: 2024-09-19 04:58:12

Linux内核中链表的实现与应用【转】的相关文章

基本数据结构和算法在Linux内核中使用

基本数据结构和算法在Linux内核中使用 gaufunga day ago 搬运工 Linux内核(源代码的链接在github). 1.链表.双向链表.无锁链表. 2.B+ 树,这是一些你无法在教科书上找到的说明. 一个相对简单的B+树的实现.我把它作为一个学习练习来帮助理解B+树是如何工作的.这同样也被证明是有用的. ... 一个在教科书中并不常见的技巧.最小的值在右侧而不是在左侧.所有在一个节点里用到的槽都在左侧,所有没有用到的槽包含了空值(NUL).大多数操作只简单地遍历所有的槽一次并在第

Linux内核中双向链表的经典实现

概要 前面一章"介绍双向链表并给出了C/C++/Java三种实现",本章继续对双向链表进行探讨,介绍的内容是Linux内核中双向链表的经典实现和用法.其中,也会涉及到Linux内核中非常常用的两个经典宏定义offsetof和container_of.内容包括: 1. Linux中的两个经典宏定义 2. Linux中双向链表的经典实现 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3562146.html 更多内容: 数据结构与算法系列 目录

没有容量的容器——linux内核的链表(sina博客移入)

在看linux内核源代码的时候,经常在一些结构里看见struct list_head结构.找了一下源代码,在list.h中,有对这个结构的定义,这个就是linux内核中的链表结构. 仔细看看这个结构,就可以发现它和以前在讲数据结构的时候的链表有很大的差别--没有数据.list_head结构中仅仅包含了两个自己结构的指针,用来组建双向循环链表.最大的疑问就是,这个链表结构如何保存数据呢? 在list.h中,定义了list_entry宏.这个宏就是用来提取包含链表项的结构的指针.从list_entr

linux内核中的C语言常规算法(前提:你的编译器要支持typeof和type)

学过C语言的伙伴都知道,曾经比较两个数,输出最大或最小的一个,或者是比较三个数,输出最大或者最小的那个,又或是两个数交换,又或是绝对值等等,其实这些算法在linux内核中通通都有实现,以下的代码是我从linux内核源码的kernel.c中抠出来的代码,我们来看看: 我们直接上代码: #include <stdio.h> #include <stdlib.h> /* * min()/max() macros that also do * strict type-checking..

Linux内核中常见内存分配函数(一)

linux内核中采 用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系 统中,用到了四级页表. * 页全局目录(Page Global Directory) * 页上级目录(Page Upper Directory) * 页中间目录(Page Middle Directory) * 页表(Page Table) 页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址 ,而页中间目录又包含若干页表的地址,每一个页表项指

Linux内核中SPI总线驱动分析

本文主要有两个大的模块:一个是SPI总线驱动的分析 (研究了具体实现的过程): 另一个是SPI总线驱动的编写(不用研究具体的实现过程).  1 SPI概述       SPI是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口,是Motorola首先在其MC68HCXX系列处理器上定义的.SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间.SPI是一种高速的,全双工,同步的通信总线,并且在芯片的

Linux内核中的内存管理浅谈

 [十月往昔]--Linux内核中的内存管理浅谈 为什么要叫做"十月往昔"呢?是为了纪念我的原博客. 不知道为什么,突然想来一个新的开始--而那个博客存活至今刚好十个月,也有十个月里的文档. 十月往昔,总有一些觉得珍贵的,所以搬迁到这里来. 而这篇文章是在09.04.20-09.04.21里写的. Jason Lee   ------------–cut-line   1.基本框架(此处主要谈页式内存管理) 4G是一个比较敏感的字眼,早些日子,大多数机器(或者说操作系统)支持的内存上限

详解Linux内核中的container_of函数_unix linux

前言 在linux 内核中,container_of 函数使用非常广,例如 linux内核链表 list_head.工作队列work_struct中. 在linux内核中大名鼎鼎的宏container_of() ,其实它的语法很简单,只是一些指针的灵活应用,它分两步:       第一步,首先定义一个临时的数据类型(通过typeof( ((type *)0)->member )获得)与ptr相同的指针变量__mptr,然后用它来保存ptr的值.       第二步,用(char *)__mptr

Linux内核中的list.h浅谈

[十月往昔]--Linux内核中的list.h浅谈 为什么要叫做"十月往昔"呢,是为了纪念我的原博客. 不知道为什么,突然想来一个新的开始--而那个博客存活至今刚好十个月,也有十个月里的文档. 十月往昔,总有一些觉得珍贵的,所以搬迁到这里来. 而这篇文章是在09.04.10里写的. Jason Lee   ------------–cut-line /*------------------------------- include/linux/list.h -2.6.29 */ 该文件