当前位置: 首页 > news >正文

Linux 内核中双向链表及list.h 文件分析

在Linux 内核中双向链表有大量的应用。弄清内核中双向链表的使用及常用的操作函数对学习内核至关重要。完全可以对文件稍作修改后,用到用户空间编程中。

list主要定义在内核源码部分的list.h文件中。/usr/src/kernels/$(uname -r) /include/linux/list.h

本文基于内核:2.6.35.14-106.fc14.i686


1、Linux内核实现了一个双向链表的抽象定义:

struct list_head {
        struct list_head *next, *prev;
};

这个链表不含任何数据域,可以嵌入任何结构中,从而形成链表。例如:

struct numlist{
    int num;
    struct list_head list;   
};

注意:一个结构结构中完全可以有多个list域,形成多个相关链表。


2、链表头的初始化

在list.h 对于链表头初始化定了2个宏和一个函数

#define LIST_HEAD_INIT(name) { &(name), &(name) }    /* 仅初始化*/

#define LIST_HEAD(name)   struct list_head name = LIST_HEAD_INIT(name)   /*声明并初始化*/

static inline void INIT_LIST_HEAD(struct list_head *list)   /*初始化*/
{
        list->next = list;
        list->prev = list;
}
其实我们看到,头节点初始化的过程就是将next、prev指向自己,形成了一个空链表。


3、在链表中插入一个节点

static inline void list_add(struct list_head *new, struct list_head *head)       /*头插*/

static inline void list_add_tail(struct list_head *new, struct list_head *head)  /*尾插*/

定义了一个内部函数,供list_add()与list_add_tail()调用,这与面向对象中的封装类似,而且简化了外部调用时的参数。

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;
}
static inline void list_add(struct list_head *new, struct list_head *head)
{
        __list_add(new, head, head->next);
}
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
        __list_add(new, head->prev, head);
}


4、在链表中删除一个节点

static inline void list_del(struct list_head *entry)(常用)

static inline void list_del(struct list_head *entry)
{
        __list_del(entry->prev, entry->next);   /*也定义了一个内部函数__list_del()*/
        entry->next = LIST_POISON1;
        entry->prev = LIST_POISON2;
}

注意:此处删除并没有将节点 entry 删除,释放。只是将指针next、prev指针指向了固定位置。因为list_head 域只是整个struct的一小部分,所以只是单纯的从链表中摘除,并未对整个数据节点产生影响。


另一个删除节点函数:

static inline void list_del_init(struct list_head *entry)
{
        __list_del(entry->prev, entry->next);
        INIT_LIST_HEAD(entry);
}
该函数删除节点后,并初始化该节点作为链表头。


5、遍历链表

list.h中定义了一个宏list_for_each,来实现链表遍历。

#define list_for_each(pos, head) \
        for (pos = (head)->next; prefetch(pos->next), pos != (head); \
                pos = pos->next)
注意:其中pos  是list_head * 类型,是指向当前节点(list_head部分)的指针,head是链表头的指针。

但是,对于试图删除链表节点时,需要进行安全遍历链表,需要使用另一个宏:list_for_each_safe

/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:        the &struct list_head to use as a loop cursor.
 * @n:          another &struct list_head to use as temporary storage
 * @head:       the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
        for (pos = (head)->next, n = pos->next; pos != (head); \
                pos = n, n = pos->next)
可以以上两个宏的区别是,安全遍历链表宏用一个指针n将将要删除的节点保存下来了。

如果试图用list_for_each来遍历删除,那么当删除pos时,pos->next就丢失了,链就中断了。


6、从指向节点链表域(list_head ),获取整个数据节点,对整个数据节点其他部分进行操作

以上我们都是对一个数据节点的list_head (链表域)部分进行操作。但是获取链表域不是我们本身的目的,我们获取链表域只是为了定位数据节点。那么我们如何通过某个节点的链表域得到该节点的整个起始位置呢?


我们需要由pos位置,找到“节点位置”。

list.h 定义了宏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)

/*在kernel.h 中定义了container_of*/		
#define container_of(ptr, type, member) ({			\
        const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
        (type *)( (char *)__mptr - offsetof(type,member) );})
注意:type 是数据节点结构体类型。member 是 list _head 域的名字。

分析:container_of :其中offsetof(type,member) 是member在type中的偏移量。((type *)0)->member ) *__mptr = (ptr); __mprt 存放的是ptr的绝对地址。相减就获取了type类型的起始地址。

这样就获取了type 数据结构体的位置,就可以对数据部分进行操作了。


7、链表中节点替换

static inline void list_replace(struct list_head *old,struct list_head *new)    //new 替换old
static inline void list_replace_init(struct list_head *old,truct list_head *new)  //new替换old,并且对old初始化

/**
 * list_replace - replace old entry by new one
 * @old : the element to be replaced
 * @new : the new element to insert
 *
 * If @old was empty, it will be overwritten.
 */
static inline void list_replace(struct list_head *old,struct list_head *new)
{
        new->next = old->next;
        new->next->prev = new;
        new->prev = old->prev;
        new->prev->next = new;
}

static inline void list_replace_init(struct list_head *old,struct list_head *new)
{
        list_replace(old, new);
        INIT_LIST_HEAD(old);
}
注意:在list_repalce 中虽然new节点成功替换了链表中的old 。但是old节点自身pre、next指针任然指向链表中,这或许是一个不安全的因素。如果old是个NULL那么显然会出现问题了。


在来看另外两个相关的函数:删除一个节点,在头尾插入新的节点

static inline void list_move(struct list_head *list, struct list_head *head)      //将“节点lis”t从链表中删除,然后在链表头部插入“节点head”
static inline void list_move_tail(struct list_head *list,struct list_head *head)    //将“节点lis”t从链表中删除,然后在链表尾部插入“节点head”

/**
 * list_move - delete from one list and add as another's head
 * @list: the entry to move
 * @head: the head that will precede our entry
 */
static inline void list_move(struct list_head *list, struct list_head *head)
{
        __list_del(list->prev, list->next);
        list_add(list, head);
}

/**
 * list_move_tail - delete from one list and add as another's tail
 * @list: the entry to move
 * @head: the head that will follow our entry
 */
static inline void list_move_tail(struct list_head *list,
                                  struct list_head *head)
{
        __list_del(list->prev, list->next);
        list_add_tail(list, head);
}



8、判断一个链表是否为空

static inline int list_empty(const struct list_head *head)  /*为空返回1*/

static inline int list_empty(const struct list_head *head)
{
        return head->next == head;
}
来看另外一个判空函数:

static inline int list_empty_careful(const struct list_head *head)  /*稍稍安全一点,但是局限性*/

/**
 * list_empty_careful - tests whether a list is empty and not being modified
 * @head: the list to test
 *
 * Description:
 * tests whether a list is empty _and_ checks that no other CPU might be
 * in the process of modifying either member (next or prev)
 *
 * NOTE: using list_empty_careful() without synchronization
 * can only be safe if the only activity that can happen
 * to the list entry is list_del_init(). Eg. it cannot be used
 * if another CPU could re-list_add() it.
 */
static inline int list_empty_careful(const struct list_head *head)
{
        struct list_head *next = head->next;
        return (next == head) && (next == head->prev);
}
我们看到这个判空的方法是:检查自己的下一个是否指向自己,并且自己的下一个节点的prev也指向自己。

从注释了解到:该函数主要是为了防止判空的同时另一个cpu正在修改链表,使判空不准确。但是注释也说明,仅当另一个CPU做list_del_init操作时是安全的。其实还是需要加锁。


9、判断节点是否为链表的最后一个节点。

static inline int list_is_last(const struct list_head *list, const struct list_head *head)   /*是最后一个返回1*/

/**
 * list_is_last - tests whether @list is the last entry in list @head
 * @list: the entry to test
 * @head: the head of the list
 */
static inline int list_is_last(const struct list_head *list,
                                const struct list_head *head)
{
        return list->next == head;
}



10、测试链表是否只有一个节点(除头节点外)

static inline int list_is_singular(const struct list_head *head)  /*判断除头结点外,是否只剩一个节点*/

/**
 * list_is_singular - tests whether a list has just one entry.
 * @head: the list to test.
 */
static inline int list_is_singular(const struct list_head *head)
{
        return !list_empty(head) && (head->next == head->prev);
}

11、链表的左旋转

static inline void list_rotate_left(struct list_head *head)  /*将head从头节点旋转到尾节点*/

/**
 * list_rotate_left - rotate the list to the left
 * @head: the head of the list
 */
static inline void list_rotate_left(struct list_head *head)
{
        struct list_head *first;

        if (!list_empty(head)) {
                first = head->next;
                list_move_tail(first, head);
        }
}
看到类似于将一个从左->右的链表,将头节点右半部分“甩”到头节点的左边。这时头结点已然成为了最后一个节点。




后续继续分析

相关文章:

  • 提升ArcGIS Server for Java的REST访问切片图效率
  • 转转带你玩转企业虚拟化
  • 数据结构与算法[LeetCode]—3Sum 求数组中和为0 的三个数的组合
  • 摩尔庄园为啥这么火?
  • [LeetCode]—Rotate Image 矩阵90度翻转
  • “国家使命”图书第一批权威发布
  • Windows 7 ship party
  • LeetCode]—Rotate List 循环右移链表
  • 推荐阅读:太极拳的奥妙-专访七十肖维佳老翁现场展示
  • 一个真实的项目经历,很多东西大家可以借鉴下
  • 无法启动调试 未安装silverlight
  • [LeetCode]—Copy List with Random Pointer 深度复制带“任意指针”的链表
  • DB2使用Data Studio连接报ERRORCODE=-4499 SQLSTATE=08001
  • [LeetCode]—Implement strStr() 寻找子串匹配第一个位置 (KMP)
  • MFC图形函数(转载)
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • 【跃迁之路】【444天】程序员高效学习方法论探索系列(实验阶段201-2018.04.25)...
  • Android单元测试 - 几个重要问题
  • crontab执行失败的多种原因
  • ECS应用管理最佳实践
  • ES6系列(二)变量的解构赋值
  • GraphQL学习过程应该是这样的
  • HTTP中GET与POST的区别 99%的错误认识
  • laravel5.5 视图共享数据
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • vue 配置sass、scss全局变量
  • 番外篇1:在Windows环境下安装JDK
  • 飞驰在Mesos的涡轮引擎上
  • 给自己的博客网站加上酷炫的初音未来音乐游戏?
  • 官方解决所有 npm 全局安装权限问题
  • 温故知新之javascript面向对象
  • 一文看透浏览器架构
  • 栈实现走出迷宫(C++)
  • HanLP分词命名实体提取详解
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (007)XHTML文档之标题——h1~h6
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (1)(1.11) SiK Radio v2(一)
  • (1)Android开发优化---------UI优化
  • (20050108)又读《平凡的世界》
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (笔试题)分解质因式
  • (论文阅读30/100)Convolutional Pose Machines
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • (转)iOS字体
  • (转)大道至简,职场上做人做事做管理
  • (轉貼) 蒼井そら挑戰筋肉擂台 (Misc)
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .gitignore文件—git忽略文件
  • .net 生成二级域名
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)