linux内核中list_head使用介绍

list_head结构的介绍
list_head结构定义在

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

有的人可能看到这样的结构会觉得很奇怪这样的结构可以存放资料吗? 当然是不行的棉,因为这个结构根本是拿来让人当资料存的。 首先, 我们先来看看两个macro,

#define LIST_HEAD(name) \
struct list_head name = { &name, &name }
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)

这两个macro在Kernel里也算蛮常出现的, 是用来将list_head做初始化的,它的初始化就是将next和prev这两个栏位设为跟结构的地址相同。 所以, 如果我们在程序里看到这样的程序, 它的意思就是宣告一个list_head结构的变数hello,并将prev和next都设成hello的地址。

LIST_HEAD(hello)

因此, 如果要检查这个list是否是空的, 只要检查hello.next是否等于&hello就可以了。事实上, Linux也提供了一个叫list_empty()的函式来检查list是否为空的。

static __inline__ int list_empty(struct list_head *head)
{
return head->next == head;
}

现在我们来介绍如何加入或删除list_head到上面的hello串行里。 Linux提供二个函式来做这些事, 分别是list_add()和lis_del()。 这两个函式的定义都放在

#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

我们现在来做个实验, 相信各位会更容易了解这个macro的。 请看一下下面这段程序码。

struct HelloWorld {
    int x, y;
    struct list_head list;
} hello;

假设int是4个byte。 那么以下这一行会得到8, 如图5所示

(unsigned long) (&((struct HelloWorld *)0)->list)

有的人会对这一行程序感到奇怪, (struct HelloWorld*)0不就是一个NULL的指标吗? 怎么可以用0->list去参考list这个栏位呢? 难道不怕造成segmentation fault吗? 请注意一下, 我们在0->list的前面还加上了一个&。 如果没有&, 那上面这一行就会segmentation fault了。 如果你加上了&, 那就没问题棉。 Segmentation fault通常是去参考到不合法的记忆体地址内容所造成的, 如果我们加上了&就表示我们没有要去参考这个不合法地址的内容,我们只是要那个栏位的地址而已, 因此, 不会造成segmentation fault。 其实, 结构的配置在记忆体里是连续的。 所以, 如果我们去读取某个栏位时,像&hello->list。 会先取得hello变数的地址, 再然后再计算HelloWorld结构里list栏位所在的offset, 再将hello的地址加上list栏位的offset,求得list栏位真正的地址。 然后再去读list栏位的内容。 这是compiler帮我们做的。 那我们现在就来看看上面那一行究竟是什么意思。 首先, 我们先把上面那一行想象成下面这个样子。

ptr = 0;
(unsigned long) (&((struct HelloWorld *)ptr)->list)

这样是不是容易懂了吗, 就是要取得&ptr->list的地址而已。所以, 如果ptr是100的话, 那会得到100+8=108。 因为前面有二个int, 每一个int是4个byte。 经过转型, 就得到了(unsigned long)型态的108。 如果ptr是0的话, 那同理, 我们会得到0+8=8。 也就是这个栏位在HelloWorld结构里的offset。

现在, 如果我们已经知道了list在HelloWorld结构中的offset,而且我们现在也知道hello这个变数里list的地址的话, 那有没有办法得到hello本身的地址呢? 可以的, 就像图6一样, 如果我们知道list的地址, 只要将list的地址减8就可以知道了hello的地址了嘛。

struct list_head *plist = &hello.list;
printf( "&hello = %x\n", (char*)plist - (unsigned long) 8 ));

而这种方式就是list_head的用法, 它是专门用来当作别的结构的栏位,只要我们得到这个栏位的位置和包含这个栏位的结构是那一种, 我们可以很轻易的算出包含此栏位的结构地址, 图6就是super block在使用list_head所得到的结果。只要我们知道s_list的地址, 只要呼叫

list_entry( &sb1.s_list, struct super_block, s_list)

就可以得到其sb1这个super_block结构的地址。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 内核,`LIST_HEAD` 是一个宏定义,用于定义一个双向链表的头节点。这个头节点并不存储任何数据,仅仅是用来维护链表。 下面是一个例子,假设我们有一个结构体 `person`,其包含了一个 `struct list_head` 类型的成员 `list`,用来将多个 `person` 结构体连接成一个双向链表: ```c #include <linux/list.h> struct person { char name[20]; int age; struct list_head list; }; LIST_HEAD(person_list); ``` 上面的代码,我们首先包含了 `list.h` 头文件,然后定义了一个 `person` 结构体,其包含了一个 `list` 成员,用来连接多个 `person` 结构体。接着,我们使用 `LIST_HEAD` 宏定义了一个名为 `person_list` 的头节点,用来维护这个双向链表。 在代码,我们可以使用 `list_add`、`list_del`、`list_for_each` 等函数来操作这个链表,例如: ```c struct person *p1, *p2, *p3; p1 = kmalloc(sizeof(struct person), GFP_KERNEL); p2 = kmalloc(sizeof(struct person), GFP_KERNEL); p3 = kmalloc(sizeof(struct person), GFP_KERNEL); strcpy(p1->name, "Alice"); p1->age = 20; INIT_LIST_HEAD(&p1->list); strcpy(p2->name, "Bob"); p2->age = 25; INIT_LIST_HEAD(&p2->list); strcpy(p3->name, "Charlie"); p3->age = 30; INIT_LIST_HEAD(&p3->list); list_add(&p1->list, &person_list); list_add(&p2->list, &person_list); list_add(&p3->list, &person_list); struct person *p; list_for_each_entry(p, &person_list, list) { printk(KERN_INFO "Name: %s, Age: %d\n", p->name, p->age); } ``` 上面的代码,我们先分别申请了三个 `person` 结构体,并将它们初始化。然后,我们使用 `list_add` 函数将它们添加到 `person_list` 链表。最后,我们使用 `list_for_each_entry` 函数遍历整个链表,并输出每个 `person` 结构体的姓名和年龄。 这就是 `LIST_HEAD` 的使用方法和一个简单的例子。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值