Linux内核之数据双链表_linux内核侵入式链表(1)

static LIST_HEAD(misc_list);


它实际上是对用list\_head 类型定义的变量的扩展。



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



**然后使用宏 LIST\_HEAD\_INIT 进行初始化,**

这会使用变量name 的地址来填充prev和next 结构体的两个变量。



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



**现在来看看注册杂项设备的函数misc\_register。**

它在一开始就用函数 INIT\_LIST\_HEAD 初始化了miscdevice->list。



INIT_LIST_HEAD(&misc->list);



**作用和宏LIST\_HEAD\_INIT一样。**


static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}



**接下来,在函数device\_create 创建了设备后,**

我们就用下面的语句将设备添加到设备链表:



list_add(&misc->list, &misc_list);



**内核文件list.h 提供了向链表添加新项的 API 接口。**

我们来看看它的实现:



static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}



**实际上就是使用3个指定的参数来调用了内部函数\_\_list\_add:** 

new - 新项。  
 head - 新项将会插在head的后面  
 head->next - 插入前,head 后面的项。  
 \_\_list\_add的实现非常简单:



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;
}



**这里,我们在prev和next 之间添加了一个新项。**

所以我们开始时用宏LIST\_HEAD\_INIT定义的misc 链表会包含指向miscdevice->list 的向前指针和向后指针。  
 这儿还有一个问题:如何得到列表的内容呢?这里有一个特殊的宏:



#define list_entry(ptr, type, member)
container_of(ptr, type, member)



**使用了三个参数:**

ptr - 指向结构 list\_head 的指针;  
 type - 结构体类型;  
 member - 在结构体内类型为list\_head 的变量的名字;



**比如:**


const struct miscdevice *p = list_entry(v, struct miscdevice, list)


然后我们就可以使用p->minor 或者 p->name来访问miscdevice。让我们来看看list\_entry 的实现:



#define list_entry(ptr, type, member)
container_of(ptr, type, member)


如我们所见,它仅仅使用相同的参数调用了宏container\_of。初看这个宏挺奇怪的:



#define container_of(ptr, type, member) ({
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})



**首先你可以注意到花括号内包含两个表达式。**

编译器会执行花括号内的全部语句,然后返回最后的表达式的值。



**比如:**


#include <stdio.h>
int main() {
int i = 0;
printf(“i = %d\n”, ({++i; ++i;}));
return 0;
}


最终会打印出2。



**下一点就是typeof,它也很简单。**

就如你从名字所理解的,它仅仅返回了给定变量的类型。当我第一次看到宏container\_of的实现时,让我觉得最奇怪的就是表达式((type \*)0)中的0。实际上这个指针巧妙的计算了从结构体特定变量的偏移,这里的0刚好就是位宽里的零偏移。



**比如:**


#include <stdio.h>
struct s {
int field1;
char field2;
char field3;
};
int main() {
printf(“%p\n”, &((struct s*)0)->field3);
return 0;
}


结果显示0x5。



**下一个宏offsetof会计算从结构体起始地址到某个给定结构字段的偏移。**

它的实现和上面类似:  
 #define offsetof(TYPE, MEMBER) ((size\_t) &((TYPE \*)0)->MEMBER)  
 现在我们来总结一下宏container\_of。只需给定结构体中list\_head类型 字段的地址、名字和结构体容器的类型,它就可以返回结构体的起始地址。在宏定义的第一行,声明了一个指向结构体成员变量ptr的指针\_\_mptr,并且把ptr 的地址赋给它。现在ptr 和\_\_mptr 指向了同一个地址。从技术上讲我们并不需要这一行,但是它可以方便地进行类型检查。第一行保证了特定的结构体(参数type)包含成员变量member。第二行代码会用宏offsetof计算成员变量相对于结构体起始地址的偏移,然后从结构体的地址减去这个偏移,最后就得到了结构体。



**当然了list\_add 和 list\_entry不是<linux/list.h>**

提供的唯一功能。双向链表的实现还提供了如下API:



list_add
list_add_tail
list_del
list_replace
list_move
list_is_last
list_empty
list_cut_position
list_splice
list_for_each
list_for_each_entry
先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前在阿里

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上运维知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以点击这里获取!

-1714281534181)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上运维知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以点击这里获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值