Android中的双向链表

本文深入探讨了Android系统中双向链表listnode的应用及其与宿主结构的关系,详细解释了如何通过list_head指针找到宿主结构的方法,并提供了一个具体的代码示例。

1.看源代码必须搞懂Android的数据结构。在init源代码中双向链表listnode使用非常多,它仅仅有prev和next两个指针,没有不论什么数据成员。这个和linux内核的list_head如出一辙,由此可见安卓深受linux内核的影响的。本来来分析一下这个listnode数据结构。

这里须要考虑的一个问题是,链表操作都是通过listnode进行的,但是那只是是个连接件。假设我们手上有个宿主结构,那当然知道了它的某个listnode在哪里,从而以此为參数调用list_add和list_del函数。但是,反过来。当我们顺着链表取得当中一项的listnode结构时,又如何找到其宿主结构呢?在listnode结构中并没有指向其宿主结构的指针啊。毕竟。我们我真正关心的是宿主结构。而不是连接件。对于这个问题,我们举例内核中的list_head的样例来解决。内核的page结构体含有list_head成员,问题是:知道list_head的地址。如何获取page宿主的地址?以下是取自mm/page_alloc.c中的一行代码:

page = memlist_entry(curr, struct page, list);

这里的memlist_entry将一个list_head指针curr换算成其宿主结构的起始地址,也就是取得指向其宿主page结构的指针。读者可能会对memlist_entry()的实现感到困惑。

#define memlist_entry list_entry

而list_entry定义则在include/linux/list.h中

135 /**
136 * list_entry get
the struct for this entry
137 * @ptr: the &struct list_head pointer.
138 * @type: the type of the struct this is embedded in.
139 * @member: the name of the list_struct within the struct.
140 */
141 #define list_entry(ptr, type, member) \
142 ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

这样我们应该就明确了。curr是一个page结构内部成分list的地址,而我们所须要的却是那个page结构本身的地址,所以要从curr减去一个偏移量,即成分list在page内部的位移量。

那么这个位移量怎么求?&((struct page*)0)->list就表示当结构page正好在地址0上时其成分list的地址,这就是所求的偏移量。

2.測试代码

#include<stdio.h>
#include<stddef.h>

typedef struct _listnode
{
        struct _listnode *prev;
        struct _listnode *next;
}listnode;

#define node_to_item(node,container,member) \
(container*)(((char*)(node))-offsetof(container,member))

//向list双向链表尾部加入node节点,list始终指向双向链表的头部(这个头部仅仅含有prev/next)
void list_add_tail(listnode *list,listnode *node)
{
        list->prev->next=node;
        node->prev=list->prev;
        node->next=list;
        list->prev=node;
}
//定义一个測试的宿主结构
typedef struct _node
{
        int data;
        listnode list;
}node;

int main()
{
        node n1,n2,n3,*n;
        listnode list,*p;
        n1.data=1;
        n2.data=2;
        n3.data=3;
        list.prev=&list;
        list.next=&list;
        list_add_tail(&list,&n1.list);
        list_add_tail(&list,&n2.list);
        list_add_tail(&list,&n3.list);
        for(p=list.next;p!=&list;p=p->next)
        {
                n=node_to_item(p,node,list);
                printf("%d\n",n->data);
        }
        return 0;
}



转载于:https://www.cnblogs.com/yxwkf/p/5302902.html

### Android内核链表实现使用 #### 定义基本结构 在Android内核环境中,`list_head` 是用于表示双向链表的核心数据结构[^3]。此结构不包含任何数据成员,仅由两个指向前驱和后继节点的指针组成: ```c struct list_head { struct list_head *next, *prev; }; ``` 这种设计使得 `list_head` 可以被嵌入到其他任意的数据结构中作为其一部分,从而实现了高度的灵活性。 #### 初始化链表 为了创建一个新的链表实例,可以利用宏定义来进行初始化操作。通常有两种方式来完成这一过程: - **静态声明时初始化** ```c static LIST_HEAD(my_list); ``` - **动态分配内存后的初始化** ```c INIT_LIST_HEAD(&my_new_node->list); ``` 这两种方法都可以确保新建立的链表处于有效状态,即前向指针(`next`)指向自身而后续指针(`prev`)也指向自身。 #### 插入删除节点 对于插入操作而言,在指定位置之前或之后加入一个新节点是非常常见的需求。为此提供了如下几个辅助函数: - 将新节点添加至目标节点之前:`list_add_prev()` - 把新节点放置于给定节点后面:`list_add_next()` 或者常用的 `list_add()` 当涉及到移除某个特定节点时,则可调用 `list_del()` 函数;如果希望进一步重置已被摘下的节点以便重复利用的话还可以考虑采用 `list_del_init()` 方法。 #### 替换现有节点 除了常规增删改查外,有时也需要执行更为复杂的逻辑——例如直接更换整个列表项而不改变前后连接关系。此时便需要用到专门针对这种情况设计好的接口如 `list_replace()` 和带有额外功能(即将旧节点自环化处理)版本 `list_replace_init()` 来简化编程工作量并提高效率[^5]。 #### 遍历访问元素 遍历时一般会借助循环语句配合迭代器模式逐步获取各个结点的信息直至结束标志为止。下面给出了一种安全可靠的遍历方案: ```c struct list_head *pos; // 单层保护防止并发修改破坏链表结构 spin_lock_irqsave(&lock, flags); list_for_each(pos, &head) { // 对当前 pos 所指向的对象做适当处理... } spin_unlock_irqrestore(&lock, flags); ``` 这里需要注意的是,在多线程环境下应当采取必要的同步措施以免发生竞态条件等问题影响程序稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值