博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[转]深入理解linux内核list_head
阅读量:4640 次
发布时间:2019-06-09

本文共 9907 字,大约阅读时间需要 33 分钟。

http://blog.chinaunix.net/uid-27122224-id-3277511.html

深入理解linux内核list_head的实现 2012-07-17 17:37:01

分类: LINUX

前言:在linux源代码中有个头文件为list.h。很多linux下的源代码都会使用这个头文件,它里面定义

了一个结构,以及定义了和其相关的一组函数,这个结构是这样的:

点击(此处)折叠或打开

1.struct list_head{

  1. struct list_head *next, *prev;

3.};

那么这个头文件又是有什么样的作用呢,这篇文章就是用来解释它的作用,虽然这是linux下的源代码,但对

于学习C语言的人来说,这是算法和平台没有什么关系。

一、双向链表

学习计算机的人都会开一门课程《数据结构》,里面都会有讲解双向链表的内容。

什么是双向链表,它看起来是这样的:

点击(此处)折叠或打开

1.struct dlist

2.{

  1. int no;
  2. void* data;
  3. struct dlist *prev, *next;

6.};

他的图形结构图:

现在有几个结构体,它们是:

表示人的:

点击(此处)折叠或打开

1.struct person

2.{

  1. int age;
  2. int weight;

5.};

表示动物的:

点击(此处)折叠或打开

1.struct animal

2.{

  1. int age;
  2. int weight;

5.};

如果有一组filename变量和filedata变量,把它们存起来,我们会怎么做,当然就用数组了,但我们想使

用双向链表,让它们链接起来,那该怎么做,唯一可以做的就是给每个结构加如两个成员,如下:

表示人的:

点击(此处)折叠或打开

1.struct person

2.{

  1. int age;
  2. int weight;
  3. struct person *next, *prev;

6.};

表示动物的:

点击(此处)折叠或打开

1.struct animal

2.{

  1. int age;
  2. int weight;
  3. struct animal *next, *prev;

6.};

现在有一个人的一个链表的链头指针person_head (循环双向链表)和动物的链表的链头指针

ainimal_head ,我们要获得特定年龄和特定体重的人或动物(假设不考虑重叠),那么代码看起来可能是这样:

点击(此处)折叠或打开

1.struct person * get_percent(int age, int weight)

2.{

3.…....

  1. struct person *p;
  2. for(p = person_head->next; p != person_head; p=p->next)
  3. {
  4. if(p->age == age && p->weight == weight)
  5. return p;
  6. }

10.…...

11.}

那同理,要获得一个特定年龄和重量的动物的函数get_animal(int age, int weight)的代码也是和上面

的类似。

我们再回过头来看这两个结构,它们的指向前和指向后的指针其实都差不多,那把它们综合起来吧,所以看起

来如下面:

点击(此处)折叠或打开

1.struct list_head{

  1. struct list_head *next, *prev;

3.};

表示人的:

点击(此处)折叠或打开

1.struct person{

  1. int age;
  2. int weight;
  3. struct list_head list;

5.};

动物的:

点击(此处)折叠或打开

1.struct animal

2.{

  1. int age;
  2. int weight;
  3. struct list_head list;

6.};

可能又会有些人会问了,struct list_head都不是struct persion和struct animal类型,怎么可以

做链表的指针呢?其实,无论是什么样的指针,它的大小都是一样的,32位的系统中,指针的大小都是32位

(即4个字节),只是不同类型的指针在解释的时候不一样而已,那么这个struct list_head又是怎么去

做这些结构的链表指针呢,那么就请看下一节吧:)。

二、struct list_head结构的操作

首先,让我们来看下和struct list_head有关的两个宏,它们定义在list.h文件中。

点击(此处)折叠或打开

1.#define LIST_HEAD_INIT(name) { &(name), &(name) }

2.#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)

3.#define INIT_LIST_HEAD(ptr) do {  

  1. (ptr)->next = (ptr); (ptr)->prev = (ptr); \

5.} while (0)

这两个宏是用了定义双向链表的头节点的,定义一个双向链表的头节点,我们可以这样:

点击(此处)折叠或打开

1.struct list_head head;

2.LIST_HEAD_INIT(head);

又或者直接这样:

点击(此处)折叠或打开

1.LIST_HEAD(head);

这样,我们就定义并初始化了一个头节点。

点击(此处)折叠或打开

1.#define LIST_HEAD_INIT(name) { &(name), &(name) }

就是用head的地址初始化其两个成员next和prev ,使其都指向自己。

我们再看下和其相关的几个函数,这些函数都作为内联函数也都定义list.h中,这里要说明一下linux源码

的一个风格,在下面的这些函数中以下划线开始的函数是给内部调用的函数,而以符开始的函数就是对外使用

的函数,这些函数一般都是调用以下划线开始的函数,或是说是对下划线开始的函数的封装。

2.1 增加节点的函数

点击(此处)折叠或打开

1.static inline void __list_add();

2.static inline void list_add();

3.static inline void list_add_tail();

其实看源代码是最好的讲解了,这里我再简单的讲一下。

点击(此处)折叠或打开

1./**

    • __list_add - Insert a new entry between two known consecutive entries.
    • @new:
    • @prev:
    • @next:
    • This is only for internal list manipulation where we know the prev/next
    • entries
  1. */

10.static inline void __list_add(struct list_head * new,

  1. struct list_head * prev, struct list_head * next)

12.{

  1. next->prev = new;
  2. new->next = next;
  3. new->prev = prev;
  4. prev->next = new;

17.}

18.//这个函数在prev和next间插入一个节点new。

20./**

    • list_add - add a new entry
    • @new: new entry to be added
    • @head: list head to add it after
    • Insert a new entry after the specified head.
    • This is good for implementing stacks.
  1. */

点击(此处)折叠或打开

1.static inline void list_add(struct list_head new, struct list_head head)

2.{

  1. __list_add(new, head, head->next);

4.}

5.//这个函数在head节点后面插入new节点。

7./**

    • 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.
  1. */

15.static inline void list_add_tail(struct list_head new, struct list_head head)

16.{

  1. __list_add(new, head->prev, head);
    18.}
    这个函数和上面的那个函数相反,它在head节点的前面插入new节点。

2.2 从链表中删除节点的函数

点击(此处)折叠或打开

1./**

    • __list_del -
    • @prev:
    • @next:
    • 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
  1. */

11.static inline void __list_del(struct list_head * prev,

  1. struct list_head * next)

13.{

  1. next->prev = prev;
  2. prev->next = next;

16.}

18./**

    • 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.
  1. */

24.static inline void list_del(struct list_head *entry)

25.{

  1. __list_del(entry->prev, entry->next);

27.}

29./**

    • list_del_init - deletes entry from list and reinitialize it.
    • @entry: the element to delete from the list.
  1. */

33.static inline void list_del_init(struct list_head *entry)

34.{

  1. __list_del(entry->prev, entry->next);
  2. INIT_LIST_HEAD(entry);

37.}

这里简单说一下,list_del(struct list_head *entry)是从链表中删除entry节点。

list_del_init(struct list_head *entry) 不但从链表中删除节点,还把这个节点的向前向后指针都指

向自己,即初始化。

那么,我们怎么判断这个链表是不是空的呢!上面我说了,这里的双向链表都是有一个头节点,而我们上面看到,定义一个头节点时我们就初始化了,即它的prev和next指针都指向自己。所以这个函数是这样的。

点击(此处)折叠或打开

1./**

    • list_empty - tests whether a list is empty
    • @head: the list to test.
  1. */

5.static inline int list_empty(struct list_head *head)

6.{

  1. return head->next == head;

8.}

讲了这几个函数后,这又到了关键了,下面讲解的一个宏的定义就是对第一节中,我们所要说的为什么在一个

结构中加入struct list_head变量就把这个结构变成了双向链表呢,这其中的关键就是怎么通过这个

struct list_head变量来获取整个结构的变量,下面这个宏就为你解开答案:

点击(此处)折叠或打开

1./**

    • 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.
  1. */

7.#define list_entry(ptr, type, member)  

  1. ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

乍一看下,不知道这个宏在说什么,没关系,我举个例子来为你一一解答 :)

首先,我们还是用上面的结构:

点击(此处)折叠或打开

1.struct person

2.{

  1. int age;
  2. int weight;
  3. struct list_head list;

6.};

我们一看到这样的结构就应该知道它定义了一个双向链表,下面来看下。

我们有一个指针:

struct list_head *pos;

现在有这个指针,我们怎么去获得这个指针所在的结构的变量(即是struct person变量,其实是struct

person指针)呢?看下面这样使用:

点击(此处)折叠或打开

1.struct person *one = list_entry(pos, struct person, list);

不明白是吧,展开一下 list_entry结构如下:

点击(此处)折叠或打开

1.((struct person )((char )(pos) - (unsigned long)(&((struct person *)0)->list)))

我慢慢的分解,首先分成两部分(char )(pos)减去(unsigned long)(&((struct person )0)-

list)然后转 成(struct person *)类型的指针。

(char )(pos):是将pos由struct list_head转 成char* ,这个好理解。

(unsigned long)(&((struct person )0)->list):先看最里面的(struct person )0),它是把0

地址转 成struct person指针,然后(struct person *)0)->list就是指向list变量,之后是

&((struct person *)0)->list是取这个变量的地址,最后是(unsigned long)(&((struct person

*)0)->list)把这个变量的地址值变成一个整形数!

这么复杂啊,其实说白了,这个(unsigned long)(&((struct person *)0)->list)的意思就是取list

变量在struct person结构中的偏移量。

用个图形来说(unsigned long)(&((struct person *)0)->list,如下:

而(unsigned long)(&((struct person *)0)->list就是获取这个offset的值。

((char )(pos) - (unsigned long)(&((struct person )0)->list))

就是将pos指针往前移动offset位置,即是本来pos是struct list_head类型,它即是list。即是把

pos指针往struct person结构的头地址位置移动过去,如上图的pos和虚箭头。

当pos移到struct person结构头后就转 成(struct person *)指针,这样就可以得到struct person

*变量了。

所以我们再回到前面的句子

点击(此处)折叠或打开

1.struct person *one = list_entry(pos, struct person, list);

2.//就是由pos得到pos所在的结构的指针,动物就可以这样:

3.struct animal *one = list_entry(pos, struct animal, list);

下面我们再来看下和struct list_head相关的最后一个宏。
2.3 list_head 的遍历的宏

点击(此处)折叠或打开

1./**

    • list_for_each - iterate over a list
    • @pos: the &struct list_head to use as a loop counter.
    • @head: the head for your list.
  1. */

6.#define list_for_each(pos, head)  

  1. for (pos = (head)->next; pos != (head); pos = pos->next)

9./**

    • list_for_each_safe - iterate over a list safe against removal of list entry
    • @pos: the &struct list_head to use as a loop counter.
    • @n: another &struct list_head to use as temporary storage
    • @head: the head for your list.
  1. */

15.#define list_for_each_safe(pos, n, head)  

  1. for (pos = (head)->next, n = pos->next; pos != (head); \
  2. pos = n, n = pos->next)

list_for_each(pos, head)是遍历整个head链表中的每个元素,每个元素都用pos指向。

list_for_each_safe(pos, n, head)是用于删除链表head中的元素,不是上面有删除链表元素的函数了

吗,为什么这里又要定义一个这样的宏呢。看下这个宏后面有个safe字,就是说用这个宏来删除是安全的,

直接用前面的那些删除函数是不安全的。这个怎么说呢,我们看下下面这个图,有三个元素a ,b ,c。

点击(此处)折叠或打开

1.list_for_each(pos, myhead)

2.{

  1. if (pos == b)
  2. list_del_init(pos);
  3. //break;
  4. }
  5. 。。。

13.}

上面的算法是不安全的,因为当我们删除b后,如下图这样:

上删除pos即b后,list_for_each要移到下一个元素,还需要用pos来取得下一个元素,但pos的指向已

经改变,如果不直接退出而是在继续操作的话,就会出错了。

而 list_for_each_safe就不一样了,如果上面的代码改成这样:

点击(此处)折叠或打开

1.struct list_head pos, n;

2.list_for_each_safe(pos, n, myhead)

3.{

  1. if (pos == b)
  2. list_del_init(pos);
  3. //break;
  4. }
  5. 。。。

14.}

这里我们使用了n作为一个临时的指针,当pos被删除后,还可以用n来获得下一个元素的位置。

说了那么多关于list_head的东西,下面应该总结一下,总结一下第一节想要解决的问题.

三、 总例

我用一个程序来说明在struct person中增加了struct list_head变量后怎么来操作这样的双向链表。

点击(此处)折叠或打开

1.#include <stdio.h>

2.#include "list.h"

4.struct person

5.{

  1. int age;
  2. int weight;
  3. struct list_head list;

10.};

12.int main(int argc, char* argv[])

13.{

  1. struct person *tmp;
  2. struct list_head *pos, *n;
  3. int age_i, weight_j;
  4. // 定义并初始化一个链表头
  5. struct person person_head;
  6. INIT_LIST_HEAD(&person_head.list);
  7. for(age_i = 10, weight_j = 35; age_i < 40; age_i += 5, weight_j += 5)
  8. {
  9. tmp =(struct person*)malloc(sizeof(struct person));
  10. tmp->age = age_i;
  11. tmp->weight = weight_j;
  12. // 把这个节点链接到链表后面
  13. // 这里因为每次的节点都是加在person_head的后面,所以先加进来的节点就在链表里的最

30.后面

  1. // 打印的时候看到的顺序就是先加进来的就在最后面打印
  2. list_add(&(tmp->list), &(person_head.list));
  3. }
  4. // 下面把这个链表中各个节点的值打印出来
  5. printf("\n");
  6. printf("=========== print the list ===============\n");
  7. list_for_each(pos, &person_head.list)
  8. {
  9. // 这里我们用list_entry来取得pos所在的结构的指针
  10. tmp = list_entry(pos, struct person, list);
  11. printf("age:%d, weight: %d \n", tmp->age, tmp->weight);
  12. }
  13. printf("\n");
  14. // 下面删除一个节点中,age为20的节点
  15. printf("========== print list after delete a node which age is 20

50.==========\n");

  1. list_for_each_safe(pos, n, &person_head.list)
  2. {
  3. tmp = list_entry(pos, struct person, list);
  4. if(tmp->age == 20)
  5. {
  6. list_del_init(pos);
  7. free(tmp);
  8. }
  9. }
  10. list_for_each(pos, &person_head.list)
  11. {
  12. tmp = list_entry(pos, struct person, list);
  13. printf("age:%d, weight: %d \n", tmp->age, tmp->weight);
  14. }
  15. // 释放资源
  16. list_for_each_safe(pos, n, &person_head.list)
  17. {
  18. tmp = list_entry(pos, struct person, list);
  19. list_del_init(pos);
  20. free(tmp);
  21. }
  22. return 0;

78.}

转载于:https://www.cnblogs.com/fastwave2004/p/4940166.html

你可能感兴趣的文章
linux下面升级 Python版本并修改yum属性信息
查看>>
局域网内通讯APP
查看>>
Unity Shader 图片流光效果实现(纯计算方式)
查看>>
POJ 2002 Squares
查看>>
Java 内存分配
查看>>
ObjectDataSource控件执行Delete操作时,出现“未能找到带参数的非泛型方法”的解决方案...
查看>>
Ubuntu17.10 React Native 环境搭建
查看>>
Atitit 基于sql编程语言的oo面向对象大规模应用解决方案attilax总结
查看>>
jQuery-2.1.4.min.js:4 Uncaught TypeError: Illegal invocation
查看>>
jvm-监控指令-jdump
查看>>
maven安装与配置
查看>>
偶记:mysql5.7的官方doc也有错误啊:写的是vc runtime 2010,但实际上必须是 vc runtime 2013。坑...
查看>>
费马小定理,欧拉定理,指数循环节
查看>>
数据类型以的相互转化及赋值操作符,常用数学函数
查看>>
React-Redux之API
查看>>
bzoj千题计划266:bzoj4872: [六省联考2017]分手是祝愿
查看>>
jquery显示隐藏操作
查看>>
JAVA基础-数组
查看>>
【区间DP】能量项链
查看>>
trove 开发者阅读翻译
查看>>