数据结构--链表--结合Linux内核中 list常见使用方法
链表结构,结合Linux内核中 list_head 常见使用方法
list_head 定义
list_head 结构体定义,kernel/inclue/linux/types.h 如下:
struct list_head {
struct list_head *next, *prev;
};
然后就开始围绕这个结构开始构建链表,然后插入、删除节点 ,遍历整个链表等等,其实内核已经提供好了现成的接口,接下来就让我们进入kernel/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;
}
所以可以通过这个方法来初始化一个链表,如下是将listhead作为一个结构体成员存放了,为什么要这么做呢?后面在实际的例子应用中会使用到
struct data_list {
int data_len;
char buf[64];
struct list_head list_node;
};
struct data_list * iic_data_head;
static void init_data_head(void*) {
iic_data_head = malloc(sizeof(struct data_list ));
if (!iic_data_head) {
KLOGD("no mem\n");
return -ENOMEM;
}
memset(iic_data_head, 0x0, sizeof(struct data_list ));
INIT_LIST_HEAD(&iic_data_head->**list_node**);
}
二. 添加节点
内核已经提供了添加节点的接口了
1. list_add
如下所示。 根据注释可知,是在链表头head后方插入一个新节点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 implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
list_add再调用__list_add接口
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
#ifndef CONFIG_DEBUG_LIST
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;
}
#else
extern void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next);
#endif
其实就是在head 链表头后和链表头后第一个节点之间插入一个新节点。然后这个新的节点就变成了链表头后的第一个节点了。
2. list_add_tail 接口
上面所讲的list_add接口是从链表头header后添加的节点。 同样,内核也提供了从链表尾处向前添加节点的接口list_add_tail. 让我们来看一下它的具体实现。
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
list_add_tail 也是调用了 __list_add ,不过传递的参数同list_add 方法不一样,
所以,很清楚明了, list_add_tail就相当于在链表头前方依次插入新的节点(也可理解为在链表尾部开始插入节点,此时,header节点既是为节点,保持不变)
三. 删除节点
内核同样在list.h文件中提供了删除节点的接口 list_del(), 让我们看一下它的实现流程
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
#ifndef CONFIG_DEBUG_LIST
static inline void __list_del_entry(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
#else
extern void __list_del_entry(struct list_head *entry);
extern void list_del(struct list_head *entry);
#endif
利用list_del(struct list_head *entry) 接口就可以删除链表中的任意节点了,但需注意,前提条件是这个节点是已知的,既在链表中真实存在,切prev,next指针都不为NULL。
四. 链表遍历
内核是同过下面这些宏定义来完成对list_head链表进行遍历的,如下 :
/**
* 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)
/**
* 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)
/**
* 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_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.h 中也提供了list_replace( 节点替换) list_move(节点移位) ,翻转,查找等接口,有需要可以往对应源码中查看。
五. 宿主结构
1.找出宿主结构 list_entry(ptr, type, member)
上面的所有操作都是基于list_head这个链表进行的,涉及的结构体也都是:
struct list_head {
struct list_head *next, *prev;
};
如前面一开始创建链表的时候定义的结构体 data_list 我们这里把它叫做list_head的宿主结构体
struct data_list{
int data_len;
char buf[64];
struct list_head list_node;
};
list.h中提供了list_entry宏来实现对应地址的转换,但最终还是调用了container_of宏
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_head within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
而offsetof定义在 kernel/include/linux/stddef.h ,如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
看下container_of宏的注释:
(1)根据结构体重的一个成员变量地址导出包含这个成员变量mem的struct地址。
(2)参数解释:
ptr : 成员变量mem的地址
type: 包含成员变量mem的宿主结构体的类型
member: 在宿主结构中的mem成员变量的名称
Demo:
struct data_list {
int data_len;
char buf[64];
struct list_head list_node;
};
struct data_list* test_list;
container_of(&test_list->list_node, struct data_list, list_node); 这个调用返回的是 test_list 的指针
//ptr:&test_list->list_node
//type:struct data_list
//member:list_node
2. 宿主结构的遍历
因为我们可以根据结构体中成员变量的地址找到宿主结构的地址, 并且我们可以对成员变量所建立的链表进行遍历,那我们是不是也可以通过某种方法对宿主结构进行遍历呢?
其实内核中有很多这种用法,ex:Asoc中的control.c, 内核在list.h中提供了下面的宏:
/**
* 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_struct within 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_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_struct within 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_first_entry 和 list_next_entry宏都定义在list.h中,分别代表:获取第一个真正的宿主结构的地址; 获取下一个宿主结构的地址。它们的实现都是利用list_entry宏。
/**
* list_first_entry - get the first element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct 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_next_entry - get the next element in list
* @pos: the type * to cursor
* @member: the name of the list_struct within the struct.
*/
#define list_next_entry(pos, member) \
list_entry((pos)->member.next, typeof(*(pos)), member)
最终使用 for 循环实现了宿主结构的遍历
首先pos定位到第一个宿主结构地址,然后循环获取下一个宿主结构地址,如果查到宿主结构中的member成员变量(宿主结构中struct list_head定义的字段)地址为head,则退出,从而实现了宿主结构的遍历。如果要循环对宿主结构中的其它成员变量进行操作,这个遍历操作就显得特别有意义了。
List_head 实现队列用法
这是个demo
static struct my_data_list * iic_data_head;
static int my_queue_data_lists(u8* data, int data_len)
{
struct my_data_list * list_node_data = NULL;
int ret = 0;
if (data==NULL || data_len<=0) {
KLOGD("error data to add to queue");
ret = -EINVAL;
}
list_node_data = malloc(sizeof(struct my_data_list));
list_node_data->data_buff.data = malloc(data_BUFFER_SIZE);
if (list_node_data) {
list_node_data->list_node.next = NULL;
list_node_data->list_node.prev = NULL;
pthread_mutex_lock(&queue_mutex);
list_node_data->data_buff.len = data_len;
memcpy(list_node_data->data_buff.data, data, data_len);
list_add_tail(&list_node_data->list_node, &iic_data_head->list_node);
KLOGD("Add list_node:%p value:%s len:%d cmd:0x%02x%02x\n",
iic_data_head->list_node.prev,
list_node_data->data_buff.data, list_node_data->data_buff.len,
data[data_CMD_H_INDEX], data[data_CMD_L_INDEX]);
pthread_mutex_unlock(&queue_mutex);
} else {
ret = -ENOMEM;
}
return ret;
}
/**
\* container_of - cast a member of a structure out to the containing structure
\* @ptr: the pointer to the member.
\* @type: the type of the container struct this is embedded in.
\* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
static int my_dequeue_data_lists(u8* value)
{
struct list_head* list_node;
struct my_data_list* list_node_data;
int value_len = 0;
// mutex_lock(&iic_data_head->read_mutex);
pthread_mutex_lock(&queue_mutex);
if (!list_empty(&iic_data_head->list_node))
{
list_node = iic_data_head->list_node.next;
list_node_data = container_of(list_node,struct my_data_list, list_node);
value_len = list_node_data->data_buff.len;
memcpy(value, list_node_data->data_buff.data, value_len);
KLOGD("Out list_node:%p value:%s len:%d cmd:0x%02x%02x\n",
list_node, list_node_data->data_buff.data,
list_node_data->data_buff.len,
value[data_CMD_H_INDEX], value[data_CMD_L_INDEX]);
list_del(list_node);
free(list_node_data->data_buff.data);
free(list_node_data);
}
// mutex_unlock(&iic_data_head->read_mutex);
pthread_mutex_unlock(&queue_mutex);
return value_len;
}
static int my_init_list_head(void)
{
iic_data_head = malloc(sizeof(struct my_data_list));
memset(iic_data_head, 0x0, sizeof(struct my_data_list));
if (!iic_data_head) {
KLOGD("no mem\n");
return -ENOMEM;
}
INIT_LIST_HEAD(&iic_data_head->list_node);
return 0;
}