一、链表基础知识
链表本质上是一种数据结构,那么为什么要引入链表呢?要解释这个引入链表的原因,就要从数组这来解释。
数组是若干个类型相同的变量的集合,它有两个特点:
① 所有数据的类型一致
② 数据的个数是有限的
这也就给数组带来了一定的局限性。
首先,当创建一个简单的图书借阅系统时,需要用 char 类型记录图书的书名, int 类型记录图书的编号,float 类型记录图书的价格等等。这种情况下,数组便不再实用。而结构体的引入便解决了类型单一的问题。多个结构体的集合即结构体数组,本质上也是数组。
上面用结构体这种数据类型,解决了数组数据的类型单一的问题,却还是无法避免数据个数有限这一问题。继续以图书借阅系统为例,当图书的总数发生变化,或者我们需要在某两个图书中间插入或删除若干图书信息时,我们无法实时地对存储的图书信息的数组变量的大小进行改变。那么能不能再建立一种数据结构,让相邻的两个数组元素间存在一种联系呢?这样依据这种联系我们便可以根据我们的实际需要,实时地添加或删除某个数据元素。当不需要对数据元素的大小做改变时,对这种联系做一定的处理即可。
链表便是上述数据结构的整合,既能存储多种类型的数据,又能实时增减数据元素。可以把它看作一种功能得到拓展的“数组”,所以用数组能完成的工作用链表也可以完成,链表能完成的工作数组也能完成,但是实现会变得复杂的多。
1.1 单向链表
单向链表:每个节点只有一个 next 指针指向下一个节点,最后一个节点的 next 指针指向NULL。所以,单向链表的遍历只能从头到尾去遍历,按顺序进行。
1.2 双向链表
上小结提到,单向链表的遍历只能从头到尾去遍历,按顺序进行。某个节点访问过一次后若想再次访问,只能重新从头再遍历一次,非常的不方便。既然能从头往后遍历,那就改进一下使得链表的遍历也可以从后往前,这样上述问题便可以得到解决,这就是双向链表的设计思路。
双向链表:每个节点有两个指针 prev 和 next,指针 prev 指向上一个节点,next指向下一个节点。头节点的 prev 和尾节点的 next 为 NULL 。
1.3 循环链表
无论是单链表还是双链表,他们的方向总是单一的,像一条链子,从后往前或者从前往后。而循环链表像是一个“环形的链子”,首尾相连。
循环链表:链表首尾相连,链表尾节点的 next 后继指针指向头节点,整个链表像一个环。其种类又可分为单向循环链表和双向循环链表。
循环链表是一个环状的数据集合,所以在遍历时由任意位置的节点开始,均可找到其他节点。
二、Linux list
2.1 linux 链表总结
2.1.1 结构体定义
Linux的链表一般是双向循环链表,并且链表的节点也只有链表的前驱指针 prev 和 后继指针next。
linux 链表结构体 list_head 定义:
path:kernel/msm-5.4/include/linux/types.h
struct list_head {
struct list_head *next, *prev;
};
可以发现 linux 里链表结构体只定义了指针段的前驱指针 prev 和 后继指针 next,没有数据段。也就是说,在 linux 中使用 list_head 所定义的链表节点只有前趋后继这两个指针,只做为链表串联时的对接机构,不存储实际数据。为了方便理解,我们暂且把这种链表节点叫做“虚节点”。
其实在实际使用过程中,链表的完整构建包括虚节点和宿主结构体两个部分。虚节点里只存放用于和其他链表沟通串联的前趋后继指针 prev 和 next,宿主结构体里存放实际的数据。虚节点作为宿主结构体的成员“寄生”在宿主里,在链表相关操作的过程中代表宿主进行相关操作。
虚节点与宿主结构体的代码示例:
//宿主结构体
struct bigelt {
//用于实际数据存储的其他成员
int id;
char *name;
bool is_use;
struct *bigelt;
struct bigelt_data;
//宿主结构体里的虚节点,作为宿主的成员存在
struct list_head bigelt_list;
};
struct bigelt *bigelt_i;
了解了虚节点和宿主结构体的关系后,一个问题也随之而来了。 以上面的代码试想一下,当我们知道了宿主结构体 bigelt_i后,我们可以通过 bigelt_i->bigelt_list 很方便地找到虚节点,但是实际的情况是我们在使用链表的过程中都是虚节点代表宿主结构体参与链表的添加及遍历,最终知道的是虚节点而不是存放数据的宿主结构体。那怎么可以根据虚节点找到宿主结构体从而访问其内的数据呢?
方法就是:用虚节点减去虚节点相对于宿主结构开头的偏移量即可得到宿主结构
比如,当宿主结构的起始地址是10,虚节点相对于宿主结构的偏移量为8时,虚节点的地址就应该是18。假如我们只知道虚节点地址和偏移量时,也可以根据其关系很容易地算出宿主结构的起始地址:
宿主结构起始地址 = 虚节点地址 - 偏移量
这里可以使用 container_of()、node_to_item() 等函数来完成根据虚节点找到宿主结构体的工作,其原理就是上面讲的偏移量法。container_of()、node_to_item() 等函数的详细介绍这里不再赘述,详情可参考Linux container_of() 函数详解和offsetof 详解这两篇博客。
来看看根据虚节点找到宿主结构体的代码示例:
//使用container_of找到宿主
static void bigelt_find_host_v1() {
struct list_head *head_list;
struct list_head *pos;
struct bigelt *bf;
//获取链表
head_list = bigelt_get_list_head();
//遍历链表
list_for_each(pos, head_list) {
//根据遍历得到的虚节点pos,得到宿主结构体的地址并赋给bf
bf = container_of(pos, struct bigelt, bigelt_list);
if (bf != NULL) {
//对bf里的数据进行操作
if (bf->name != NULL) {
bf->is_use = ture;
bf->id += 1;
}
} else {
printf("%s:Get structure failed!", __func__);
}
}
}
//使用node_to_item找到宿主
static void bigelt_find_host_v2() {
struct list_head *head_list;
struct list_head *pos;
struct bigelt *bf;
//获取链表
head_list = bigelt_get_list_head();
//遍历链表
list_for_each(pos, head_list) {
//根据遍历得到的虚节点pos,得到宿主结构体的地址并赋给bf
bf = node_to_item(pos, struct bigelt, bigelt_list);
if (bf != NULL) {
//对bf里的数据进行操作
if (bf->name != NULL) {
bf->is_use = ture;
bf->id += 1;
}
} else {
printf("%s:Get structure failed!", __func__);
}
}
}
Linux 内核的链表结构都是 struct list_head 类型的,且构建时按照“宿主结构体+虚节点”的设计思路。Android 链表(Audio HAL层)设计也按照“宿主结构体+虚节点”的设计思路,只不过结构的名字有区别,类型为 struct listnode。
path:/system/core/libcutils/include/cutils/list.h
struct listnode
{
struct listnode *next;
struct listnode *prev;
};
相关内容与 linux 内核链表内容基本一致,这里不再细说。
2.1.2 链表的初始化
链表的初始化内核提供了两种方法,一种是使用 LIST_HEAD(name) 静态初始化,另一种是使用INIT_LIST_HEAD(struct list_head *list) 函数动态初始化。
其中 LIST_HEAD 是一个宏,会被宏替换为struct list_head name = LIST_HEAD_INIT(name)。可以发现使用该种方法不需要事先定义链表,只需要给出链表的名字,在宏替换过程中会去定义成 struct list_head 类型的链表。
INIT_LIST_HEAD 是一个静态内联函数,作为函数调用使用。两种方法的作用一致,都是将链表头节点的 prev 和 next 指向自身,完成初始化操作。
path:kernel/msm-5.4/include/linux/list.h
#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)
{
WRITE_ONCE(list->next, list);
list->prev = list;
}
如果静态声明list_head,则应使用LIST_HEAD(name)。
对于动态分配的列表头(通常是另一个结构的一部分),应该使用INIT_LIST_HEAD(struct list_head *list)。
使用示例:
//静态初始化链表static_list
static LIST_HEAD(static_list);
void bigelt_list_func() {
struct bigelt *bigelt_i;
bigelt_i = (struct bigelt*)malloc(sizeof(struct bigelt));
//动态初始化bigelt_i结构体里的bigelt_list链表
INIT_LIST_HEAD(&bigelt_i->bigelt_list);
//获取static_list链表,并将bigelt_list添加到static_list之后
list_add_tail(&bigelt_i->bigelt_list, &static_list);
}
2.1.3 添加链表节点
链表的添加在内核里主要有两个函数:list_add() 和 list_add_tail(),代码如下:
path:kernel/msm-5.4/include/linux/list.h
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
if (!__list_add_valid(new, prev, next))
return;
next->prev = new;
new->next = next;
new->prev = prev;
WRITE_ONCE(prev->next, new);
}
/**
* list_add - add a new entry
* @new: new entry to be added
* @head:list head to add it after
*
* Insert a new entry after the specified head.this is good for implement stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/**
* list_add - add a new entry
* @new: new entry to be added
* @head:list head to add it after
*
* Insert a new entry after the specified head.this is good for implement stacks.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
在这里我们可以发现,无论是 list_add() 还是 list_add_tail() 最终都会调用 __list_add(),只是传入的参数不一样而已。那么为什么在链表的头和尾添加新的节点都可以使用同一个函数呢?
先来看看 __list_add()这个函数。因为形参的名字和前趋指针prev、后继指针next的名字一致,看起来会有干扰,这时候不妨改一下形参的名字:
void __list_add(struct list_head *new,
struct list_head *a,
struct list_head *b)
{
if (!__list_add_valid(new, prev, next))
return;
b->prev = new;
new->next = b;
new->prev = a;
WRITE_ONCE(a->next, new);
}
修饰过后__list_add()的功能就一目了然了。简而言之该函数就像一个公式,公式有三个变量 new, a, b,公式运行的结果就是将新节点 new 放到 a 和 b 之间。下面是其示意图:
搞清楚了__list_add()后,再回过头来看看 list_add() 和 list_add_tail()。
① list_add()
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
list_add()的第二个参数是head, 所以其作用就是:将 new 添加到 head 之后,head->next( head 的下一个节点)之前,即 head->new->(head->next)。
示例:
struct bigelt {
struct *bigelt;
......
struct list_head bigelt_list;
};
void bigelt_list_add() {
struct list_head a,b;
struct bigelt *bigelt_i;
bigelt_i = (struct bigelt*)malloc(sizeof(struct bigelt));
//init the list
INIT_LIST_HEAD(&bigelt->bigelt_list);
//① (bigelt->bigelt_list)->a
list_add(&a, &bigelt->bigelt_list);
//② (bigelt->bigelt_list)->b->a
list_add(&b, &bigelt->bigelt_list);
....
}
第一次调用完 list_add 之后,如注释所写那样会将 a 添加到 bigelt_list 之后(这时候是不是有个疑问,list_add不是在head之前添加新节点吗,后面解答)。而第二次调用完 list_add 后,会将 b 添加到 bigelt_list 之后,注意不是 a 之后!所以两次调用后的结果是:(bigelt->bigelt_list)->b->a。
当然了,linux 里的链表都是循环双向链表,所以 a 的下一个就是 (bigelt->bigelt_list)。
② list_add_tail()
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
list_add_tail()的第二个参数是head->prev, 所以其作用就是:将 new 添加到 head->prev(head的前一个节点)之后,head 之前,即 (head->prev)->new->head。
示例:
struct bigelt {
struct *bigelt;
......
struct list_node bigelt_list;
};
void bigelt_list_add_tail() {
struct list_head a,b;
struct bigelt *bigelt_i;
bigelt_i = (struct bigelt*)malloc(sizeof(struct bigelt));
//init the list
INIT_LIST_HEAD(&bigelt->bigelt_list);
//① a->(bigelt->bigelt_list)
list_add_tail(&a, &bigelt->bigelt_list);
//② a->b->(bigelt->bigelt_list)
list_add_tail(&b, &bigelt->bigelt_list);
....
}
第一次调用完list_add_tail()后,会将 a 添加到 bigelt_list 之前(疑问点),第二次调用结束后会将b 添加到 bigelt_list 之前。最终两次调用的结果是:a->b->(bigelt->bigelt_list)。
调用后的结果跟理解的恰好相反,来解释解释为什么。
以 list_add 为例,前面①里已经说了,linux 里的链表都是循环双向链表,所以 (bigelt->bigelt_list)->b->a 和 a->(bigelt->bigelt_list)->b,和 b->a->(bigelt->bigelt_list) 本质上是一样的。换句话说,这个环状的链表任意一个节点都可以充当头节点,从任意一个节点出发都可以遍历整个链表。所以,若我们以 bigelt->bigelt_list 为固定头节点,并结合循环双向链表的特点来看的话,调用完 list_add(&a, &bigelt->bigelt_list)之后,(bigelt->bigelt_list)->a 就是 a->(bigelt->bigelt_list),即将 a 放在了 (bigelt->bigelt_list) 之前。
list_add_tail 和 list_add 本质一样,不再说明。上述解释也只具有逻辑层面的意义,在实际中双向链表无所谓前后。
2.1.4 删除链表节点
链表删除主要介绍一下 list_del()
/*
* Delete a list entry by making thr prev/next entries
* point to each other.
*
* This is only for internal list manipulation where we know
* the prev/next entry already!
*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
WRITE_ONCE(prev->next, next);
}
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty on entry does not return ture after this, the entry is
* in an undefined state.
*/
static inline void __list_del_entry(struct list_head *entry)
{
if (!__list_del_entry_alid(entry))
return;
__list_del(entry->prev, entry->next);
}
static inline void list_del(struct list_head *entry)
{
__list_del_entry(entry);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
list_del() 里的内容分两部分:__list_del_entry() 对被删除节点的前驱后继进行处理,下面对被删除节点本身进行处理。
__list_del_entry() 的内容很好理解,a->b->c 三个节点,若删除节点 b,那么需要重新将 c 作为 a 的后继节点,a 作为 c 的前趋节点。
再看 entry->next 和 entry->prev ,entry 节点被删除后,前趋后继指针赋值成了 LIST_POISON1/2这个值。
path:kernel/msm-5.4/include/linux/poison.h
/********** include/linux/list.h **********/
/*
* Architectures might want to move the poison pointer offset
* into some well-recognized area such as 0xdead000000000000,
* that is also not mappable by user-space exploits:
*/
#ifdef CONFIG_ILLEGAL_POINTER_VALUE
# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
#else
# define POISON_POINTER_DELTA 0
#endif
/*
* These are non-NULL pointers that will result in page faults
* under normal circumstances, used to verify that nobody uses
* non-initialized list entries.
*/
#define LIST_POISON1 ((void *) 0x100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x200 + POISON_POINTER_DELTA)
LIST_POISON1/2 在poison.h 里进行了定义,是两个不会存在于内核空间的地址,如果使用这两个值,就会产生报错。详情请关注后续博文,这里不再做详细介绍。
2.1.5 链表的遍历
链表的遍历操作都是一些宏,本质上是一些 for 循环。
2.1.5.1 list_for_each
/**
*list_for_each - iterate over a list
*@pos: the &struct list_head to use as a loop cursor
*@head: the head for your list
*/
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
这个宏的作用是从头往后遍历链表,按照后继方向遍历。
这个遍历是从头到尾把链表全部遍历一遍,最后 pos = head,不符合 pos != head 的循环条件,回到头节点完成遍历。
因为是从头到尾遍历,每一个节点都会被遍历,这也就意味着在每一次循环过程中,都可以对正在遍历的节点进行操作,其操作是整体的全部的。当然也可以不对节点进行操作,每次遍历时时只让某个变量自加,完成计算链表节点个数的工作等。
用法示例:
static LIST_HEAD(static_list);
int bigelt_list_num_count() {
struct list_head *pos;
int list_num_count = 0;
//遍历链表
list_for_each(pos, &static_list)
list_num_count++;
return list_num_count;
}
char *bigelt_list_name_read() {
int i = 0;
char *list_name = NUll;
struct list_head *pos;
struct list_head *head_list;
struct bigelt *bigelt_i;
head_list = &static_list;
list_for_each(pos, head_list) {
bigelt_i = container_of(pos, struct bigelt, bigelt_list);
list_name = bigelt_i->name;
}
return list_name;
}
2.1.5.2 list_for_each_prev
/**
*list_for_each_prev - iterate over a list backwards
*@pos: the &struct list_head to use as a loop cursor
*@head: the head for your list
*/
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); pos = pos->prev)
这个宏的作用跟上面的一样,只不过遍历的顺序相反,按照前趋方向从后往前遍历链表。
2.1.5.3 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)
这个宏函数是 list_for_each() 的一个变形形式,对比 list_for_each() 发现它多了一个参数 n 。
分析这部分宏代码,初始条件是 pos 指向 head->next,n 指向 pos 的下个节点即(head->next)->next,这一部分可以理解为 :n 暂存了当前正在遍历的节点后继节点。循环条件同上面一样,依旧是 pos 不是头节点。每次循环后将 n 里暂存的值赋给 pos ,即完成了 list_for_each()里 pos = pos->next 的操作,然后再将下个节点暂存在 n 里。
最后的作用都是让 pos 指向下个节点继续遍历,只不过这里是将下个节点暂存在 n 里,这也就是同 list_for_each()差异的根本所在,可为什么在要这么做呢?
原因是为了防止遍历过程中因链表删除的操作,后继节点丢失导致的遍历出错。在 list_del() 里我们提到过某个节点从链表中被删除后,它的前趋和后继指针会被赋为 LIST_POISON1 和 LIST_POISON2,这时 pos->next 是LIST_POISON2,遍历便会出错。而提前将后一个节点保存在 n 里,即使循环时执行了删除节点的操作,后继节点也不会因此而丢失。循环完成后将 n 下暂存的后继节点传给 pos 即可继续遍历链表。
由此我们也可以看出 list_for_each() 和 list_for_each_safe() 的使用区别:
- 遍历链表时若不删除链表节点,两者都可以使用,效果没有差别;
- 遍历链表时若需要删除链表节点,则必须使用list_for_each_safe(),否则会出错
2.1.5.4 list_for_each_prev_safe
/**
*list_for_each_prev_safe - iterate over a list backwards 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_prev_safe(pos, n, head) \
for (pos = (head)->prev, n = pos->prev; \
pos != (head); \
pos = n, n = pos->prev)
这个宏函数的作用及用法与 list_for_each_safe()一样,只是遍历的方向恰好相反,不再介绍。
2.1.5.5 list_for_each_entry
/**
*list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor
* @head: the head for your list
* @member: the name of the list_head whitin the struct
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_next_entry(pos, member))
这个函数其实也可以看作是 list_for_each 的一种变体,不变的是 for 循环的条件:
① 初始条件:临时指针指向头节点的下一个节点,pos = head->next;
② 终止条件: 临时指针pos != head;
③ 循环变量: pos = pos->next;
变得是循环的对象。在这里通过一层层的宏替换,最终调用的都是 container_of() 函数,得到的是正在被遍历的虚节点的宿主结构体。所以 list_for_each_entry() 这里循环的对象是宿主结构体,而非 list_for_each()里的虚节点。
分析代码可发现,pos 不再是之前定义的 struct list_head 类型的联临时循环指针,而是宿主结构类型的一个临时指针。head 为循环所需的头节点,member 为宿主结构体里虚节点的名字。
例如:
struct bigelt {
int id;
struct *bigelt;
......
//虚节点名 bigelt_list
struct list_node bigelt_list;
};
static LIST_HEAD(list_head);
int bigelt_list_for_each_entry_instance() {
int id;
struct bigelt *bigelt_i;
struct bigelt *n;
bigelt_i = (struct bigelt*)malloc(sizeof(struct bigelt));
bigelt_i->id = 1;
list_add_tail(&bigelt->bigelt_list, &list_head);
//示例
list_for_each_entry(n, &list_head, bigelt_list) {
id = n->id;
}
......
}
相关宏函数:
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* list_first_entry - get the first element from a list
* @ptr: the list head to take the element form
* @type: the type of the struct this is element in
* @member: the name of the list_head within the struct
*
* Note, that list is expected to be not empty
*/
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)
/**
* list_first_entry - get the last element from a list
* @ptr: the list head to take the element form
* @type: the type of the struct this is element in
* @member: the name of the list_head within the struct
*
* Note, that list is expected to be not empty
*/
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->prev, type, member)
/**
* list_next_entry - get the next element in list
* @pos: the type * to cursor
* @member: the name of the list_head within the struct
*/
#define list_next_entry(pos, member) \
list_entry((pos)->member.next, typeof(*(pos)), member)
/**
* list_prev_entry - get the prev element in list
* @pos: the type * to cursor
* @member: the name of the list_head within the struct
*/
#define list_prev_entry(pos, member) \
list_entry((pos)->member.prev, typeof(*(pos)), member)
2.1.5.6 list_for_each_entry_reverse
/**
*list_for_each_entry_reverse - iterate backwards over list of given type.
* @pos: the type * to use as a loop cursor
* @head: the head for your list
* @member: the name of the list_head whitin the struct
*/
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_last_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_prev_entry(pos, member))
这个宏函数的作用及用法与 list_for_each_entry()一样,只是遍历的方向恰好相反,所以后缀reverse,具体使用也不再赘述。
到此差不多就是链表遍历时常用的几个宏函数。可以发现所有的遍历基本上都是 list_for_each() 的演变形式。
2.1.6 链表操作实例
//macro area
#define ok 1
#define err -1
#define DEVICE_MAX 5
#define BigElt_LOG printf
//type area
struct bigelt {
int id;
char *name;
bool is_use;
struct *bigelt;
struct list_head bigelt_list;
};
//data area
static LIST_HEAD(bigelt_list_head);
//function area
struct list_head *bigelt_get_list_head() {
return &bigelt_list_head;
}
int bigelt_device_register(struct bigelt *device, struct list_head *list_head) {
int ret = 0;
if (list_head == NULL)
return err;
if (device != NULL) {
list_add(&device->bigelt_list, list_head);
}
return ok;
}
int bigelt_device_unregister(struct bigelt *device, struct list_head *list_head) {
int ret = 0;
if (list_head == NULL)
return err;
if (device != NULL) {
list_del(&device->bigelt_list, list_head);
}
return ok;
}
int bigelt_list_instance() {
int ret = 0;
struct bigelt *bigelt_i;
struct list_head *pos, *n, *list_head;
struct bigelt *bigelt_for;
bigelt_i = (struct bigelt*)malloc(sizeof(struct bigelt));
if (bigelt_i != NULL) {
INIT_LIST_HEAD(&bigelt_i->bigelt_list);
bigelt_i->is_use = 1;
bigelt_i->id = 1;
bigelt_i->name = "device_one";
} else {
BigElt_LOG("Memory of bigelt_i allocation failed!");
return err;
}
list_head = bigelt_get_list_head();
if (list_head != NULL) {
bigelt_device_register(bigelt_i, list_head);
} else {
BigElt_LOG("Get list head failed!");
return err;
}
list_for_each_safe(pos, n, list_head) {
bigelt_for = container_of(pos, struct bigelt, bigelt_list);
BigElt_LOG("%s: device [name %s, id %d]", __func__, bigelt_for->name, bigelt_for->id);
bigelt_device_unregister(bigelt_for, list_head);
}
return ok;
}
2. linux内核实例
path:kernel/msm-5.4/sound/soc/soc-core.c
struct snd_soc_component {
......
struct list_head list;
struct list_head card_list;
struct dai_list;
......
}
static LIST_HEAD(component_list);
static void snd_soc_component_add(struct snd_soc_component *component)
{
......
list_add(&component->list, &component_list);
INIT_LIST_HEAD(&component->dobj_list);
......
}
struct snd_soc_dai *snd_soc_find_dai(
const struct snd_soc_dai_link_component *dlc)
{
struct snd_soc_component *component;
struct snd_soc_dai *dai;
struct device_node *component_of_node;
/* Find CPU DAI from registered DAIs*/
list_for_each_entry(component, component_list, list) {
component_of_node = component->dev->of_node;
if (!component_of_node && component->dev->parent)
component_of_node = component->dev->parent->of_node;
......
}
3. Android 实例
struct audio_device {
......
struct listnode usecase_list;
......
};
static int adev_open(......) {
struct audio_device *adev;
.......
adev = calloc(1,sizeof(struct audio_device));
if (!adev) {
pthread_mutex_unlock(&adev_init_lock);
return -ENOMEM;
}
......
list_init(&adev->usecase_list);
......
}
int start_output_stream(struct stream_out *out)
{
struct audio_usecase *uc_info;
struct audio_device *adev = out->dev;
......
uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
if (!uc_info) {
ret = -ENOMEM;
goto error_config;
}
......
list_add_tail(&adev->usecase_list, &uc_info->list);
......
}
static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
{
struct audio_device *adev = (struct audio_device *)dev;
......
struct audio_usecase *usecase;
struct listnode *node;
list_for_each(node, &adev->usecase_list) {
usecase = node_to_item(node, struct audio_usecase, list);
struct stream_in *in = usecase->stream.in;
if (usecase->type == PCM_CAPTURE && in != NULL &&
in->source == AUDIO_SOURCE && !in->stanby) {
select_device(adev, in->usecase);
}
}
......
}