| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> linux内核list -> 正文阅读 |
|
[数据结构与算法]linux内核list |
linux list详解1 链表结构定义 首先 看链表的定义,位于:include\linux\types.h
一般将该数据结构嵌入到其他的数据结构中,从而使得内核可以通过链表的方式管理新的数据结构,比如struct device中:
? 2 链表的定义和初始化 有两种方式来定义和初始化链表头: (1)利用宏LIST_HEAD (2)利用宏LIST_HEAD_INIT 例如定义链表mylist: 方法1:定义并初始化链表 LIST_HEAD(mylist); 方法2:先定义再初始化链表 struct list_head mylist;??// 定义一个链表 INIT_LIST_HEAD(&mylist); // 用INIT_LIST_HEAD函数初始化链表。 看宏LIST_HEAD就知道就是用宏 INIT_LIST_HEAD
再看宏INIT_LIST_HEAD的定义:
定义的mylist链表,宏展开就是
链表list_head结构只有两个成员:next和prev。next和prev都被赋值为链表mylist的地址,也就是说,链表初始化后next和prev都是指向自己的。 对于struct device 中嵌入的list成员devres_head的初始化如下这样:
所以链表结点在初始化时,就是将prev和next指向自己。对链表的初始化非常重要,因为如果使用一个未被初始化的链表结点,很有可能会导致内核异常。 3 list的操作: 对链表常用的操作,一般就是添加、删除、遍历等。内核还会有其他的操作,比如替换、移动等,但这些的操作一般都是以添加、删除等操作为基础完成的。 3.1 链表的添加 添加有两种: (1)list_add?将一个新链表结点插入到一个已知结点的后面; (2)list_add_tail?将一个新链表结点插入到一个已知结点的前面 看他们的定义:
上面链表添加的两种方式是以不同的参数调用_list_add,看_list_add的定义:
_list_add函数将new结点插入到prev结点和next之间。 (1)?list_add函数中以new、head、head->next为参数调用__list_add,将new结点插入到head和head->next之间,即是把new结点插入到已知结点head的后面。 ?(2)list_add_tail函数则以new、head->prev、head为参数调用__list_add,将new结点插入到head->prev和head之间,即是把new结点插入到已知结点head的前面。 3.2 链表的删除 删除也是有两种方式: (1)list_del 删除链表中的一个结点。 (2)list_del_init 删除链表中的一个结点,并初始化被删除的结点(使被删除的结点的prev和next都指向自己); 分别看它们的定义:
_list_del_entry
两者都是调用了_list_del,让prev结点和next结点互相指向。
(1)list_del?函数中以entry->prev和entry->next为参数调用__list_del函数,使得entry结点的前、后结点绕过entry直接互相指向,然后将entry结点的前后指针指向LIST_POISON1和LIST_POISON2,从而完成对entry结点的删除。此函数中的LIST_POISON1和LIST_POISON2是内核的处理定义如下。一般删除entry后,应该让entry的prev和next指向NULL的。
(2)list_del_init?函数将entry结点删除后,与_list_del不同的是:还会对entry结点初始化,使entry结点的prev和next都指向其自己。 4 链表在内核中的应用 list_for_each_entry 首先看定义,位于:include\linux\list.h
实际上是一个 for 循环,用传入的 pos 作为循环变量,从表头 head 开始,逐项向后(next 方向)移动 pos,一直到回到head.。 (1)变量的初始化:pos = list_entry((head)->next, typeof(*pos), member),每次pos拿到的都是链表中一个成员,注意这个成员实际上是一个结构体 (2)执行条件? &pos->member != (head),确定拿到的成员不是head,是head的话,表示list已遍历完。 (3)每循环一次执行 pos = list_entry(pos->member.next, typeof(*pos), member)),是pos指定链表中下一个成员,注意其实际上还是结构体 以上中用到typeof(),它是取变量的类型,这里是取指针pos所指向数据的类型。 4.1 看宏list_entry的定义:
4.2 调用了container_of。
container_of是根据一个结构体变量中的一个成员变量指针ptr,来获取指向这个结构体变量type的指针。member是结构体type中成员ptr的变量名。下面分解一步一步分析: 4.2.1 先分析第一句:const typeof( ((type *)0)->member ) *__mptr = (ptr);?? (1)(type *)0) 将0强转为一个地址,这个地址(0x0000)指向的是类型type的数据,就是指向结构体。当然,这只是个技巧,并不是真的在地址0x0000存放了数据。 (2)((type *)0)->member :‘->’是指针指取结构体成员的操作。指针就是刚才通过0强转的地址,即结构体指针。相当于地址0x0000 是结构体类型type的首地址,通过->’取其中的成员变量member。 (3)typeof( ((type *)0)->member ) *__mptr = (ptr):拿到了member成员的类型,然后定义一个指针变量__mptr,指向的类型就是结构体成员member的类型,初始化值为ptr。 4.2.2 再分析第二句:(type *)( (char *)__mptr - offsetof(type,member) ); (1)offsetof(type,member) : 定义如下:
((TYPE *)0) :将整型常量0强制转换为TYPE型的指针,即结构体类型。且这个指针指向的地址为0,也就是将地址0开始的一块存储空间映射为TYPE型的对象。 ((TYPE *)0)->MEMBER :指向结构体中MEMBER成员。 &((TYPE *)0)->MEMBER):对结构体中MEMBER成员进行取址,而整个TYPE结构体的首地址是0,这里获得的地址就是MEMBER成员在TYPE中的相对偏移量。 (size_t) &((TYPE *)0)->MEMBER:最后将这个偏移量强制转换成size_t型数据也就是无符号整型。? ? 所以offsetof的作用就是求出结构体成员变量member在结构体中的偏移量。 (2)(char *)__mptr - offsetof(type,member):member类型的指针减去member在结构体中的偏移量,就是结构体的起始位置,即指向结构体。 至此综上所述,list_entry 也就是container_of的作用就是:根据结构体中的成员变量和此变量的指针,拿到结构体的指针。 4.3 再回到list_for_each_entry
list_for_each_entry在内核中的应用也很常见,通常用于遍历某一个链表。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/25 17:21:26- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |