1、链表
链表是linux内核中最简单同时也是应用最广泛的数据结构。链表是一种存放和操作可变数量元素的数据结构,动态创建,无需在内存中占用连续内存区。Linux内核的标准链表采用循环双向链表实现的。
(1)单向链表和双向链表
struct list_element{
void *data; /*有效数据*/
struct list_element *next; /*指向下一个元素的指针*/
/* struct list_element *prev; 加上就变成双向链表*/
};
(2)Linux内核中的实现
Linux内核中不是将数据结构加入链表,而是将链表节点加入数据结构。这样如何访问数据结构中的其他成员呢?C语言中,一个给定结构中的变量偏移在编译时地址就被固定下来了,使用container_of()可以通过链表指针找到父结构fox中的任何变量。这样做的好处是管理链表并不需要知道list_head所嵌入对象的数据结构。
struct list_head{
struct list_head *next;
struct list_head *prev;
};
struct fox{
unsigned long tail_length; /*尾巴长度*/
struct list_head list; /*所有fox结构体形成链表*/
};
(3)操作链表
- list_add(struct list_head *new,struct list_head *head):添加节点
- list_add_tail(struct list_head *new,struct list_head *head):添加节点到链尾
- list_del(struct list_head *entry):删除一个节点
- list_del_init()st_del_init(struct list_head *entry):删除一个节点并重新初始化。
- list_move(struct list_head *list,struct list_head *head):将list链表添加到head后面。
- list_move_tail(struct list_head *list,struct list_head *head):将list项插入到head项前
- list_empty(struct list_head *head):检查链表是否为空。
- list_splice(struct list_head *list,struct list_head *head):将list指向的链表插入到head的后面。
- list_splice_init(struct list_head *list,struct list_head *head):功能基本相同,不过重新初始化了原来的链表。
- list_for_each(p,list){}:遍历表中的全部元素。
- list_for_each_entry(pos,head,member):遍历节点,
- list_for_each_entry_reverse(pos,head,member):反向遍历链表。
- list_for_each_entry_safe(pos,next,head,member):遍历的同时删除,标准操作中是只允许修改,不能删除否则会掉链。这里启动next指针将下一项存入表中。使得当前表项能够安全删除。
- list_for_each_entry_safe_reverse(pos,next,head,member):反向遍历并安全删除。
2、队列
Linux内核通用队列实现称为kfifo。和多数其他队列实现类似,提供了两个主要操作:enqueue(入队)和dequeue(出队列)。kfifo对象维护了两个偏移量:入口偏移和出口偏移。enqueue操作拷贝数据到队列中的入口偏移位置。dequeue操作从队列中出口偏移处拷贝数据。
内核kfifo简约高效,匠心独运,有一下特点:
- 保证缓冲区大小为2的次幂,不是的向上取整为2的次幂。
- 使用无符号整数保存输入(in)和输出(out)的位置,在输入输出时不对in和out的值进行模运算,而让其自然溢出,并能够保证in-out的结果为缓冲区中已存放的数据长度。
- 将需要取模的运算用 & 操作代替(a%size=(a&(size−1))), 这需要size保证为2的次幂。
- 使用内存屏障(Memory Barrier)技术,实现单消费者和单生产者对kfifo的无锁并发访问,多个消费者、生产者的并发访问还是需要加锁的
操作队列:
- int kfifo_alloc(struct kfifo *kfifo,unsigned int size, gfp_t gfp_mask):初始化一个大小为size的kfifo队列。也可以使用静态的声明DECLARE_KFIFO(name,size)
- void kfifo_init(strcut kfifo *fifo,void *buffer,unsigned int size):自己分配缓冲区,也可以使用INIT_KFIFO(name)。
- unsigned int kfifo_in(struct kfifo *fifo,const void *from,unsigned int len):数据入队列。把from指向的len字节的数据拷贝到fifo所指的队列中。
- unsigned int kfifo_out(struct kfifo *fifo,const void *to,unsigned int len):将fifo所指向的队列中拷贝出长度为len字节的数据到to所指的缓冲区中。相当于pop_back();
- unsigned int kfifo_out_peek(struct kfifo *fifo,const void *to,unsigned int len,unsigned offset):获取队列中的元素,相当与pop()
- static inline unsigned int kfifo_size(struct kfifo *fifo):返回用于存储kfifo队列的空间的总体大小
- static inline unsigned int kfifo_len(struct kfifo *fifo):返回用于存储kfifo队列的已经推入的数据大小
- static inline unsigned int kfifo_avail(struct kfifo *fifo):返回用于存储kfifo队列的剩余空间的大小
- static inline int kfifo_is_empty(struct kfifo *fifo):给定的kfifo是否为空
- static inline int kfifo_is_full(struct kfifo *fifo):是否已满
- static inline void kfifo_reset(struct kfifo *fifo):抛弃数据重置内容。
- static inline void kfifo_free(struct kfifo *fifo):重新释放内存
3、映射
就是C++中的Map和set等映射操作的集合。Linux 内核提供了简单、有效的映射数据结构。但是它并非一个通用的映射:映射一个唯一的标识数(UID)到一个指针。映射的add操作中实现了allocate操作,不但向map中加入了键值对,而且还可以产生UID。Linux内核中使用idr数据结构映射用户空间的UID,比如将inodify watch的描述符或者POSIX的定时器ID映射到内核中相关联的数据结构上。如inotify_watch或者k_itimer结构。这里的UID相当于关键子key。
操作映射:
- void idr_init(struct idr *idp):初始化一个idr结构体
- int idr_pre_get(struct idr *idp,gfp_t gfp_mask):调整idp指向的idr的大小(如果需要)。使用内存分配标识符gfp_mask。返回0(失败),1(成功)
- int idr_get_new(struct idr *idp,void *ptr,int *id):使用idp所指向的idr去分配一个新的UID,并且将其关联到指针ptr上。新的UID存于id。相当与存入一个值
- int idr_get_new_above(struct idr *idp,void *ptr,int starting_id,int *id):返回UID大于starting_id的最小值。相当于查找一个值。
- void *idr_find(struct idr *idp,int id):查找一个指定的值。
- void *idr_remove(struct idr *idp,int id):删除id以及与id关联的指针。
- void *idr_destroy(struct idr *idp):释放idr中未使用的内存。它并不释放当前已经分配的内存。
- void idr_remove_all(struct idr *idp):彻底释放对应的内存。
4、二叉树
Linux实现的红黑树称为rbtree,详细源码实现说明见红黑树(rbtree)。Linux的rbtree类似于经典红黑树,即保持了平衡性,所以插入效率为O(logn)。
5、数据结构及选择
链表:
- 遍历数据,没有数据结构可以提供比线性复杂度更好的遍历元素。
- 性能并非首要考虑因素时,或者存储较少的数据项
队列:
- 代码符合生产者/消费者模式,则使用队列。
- 想要一个定长缓冲,队列添加和删除项的工作简单有效。
映射:
- 映射UID到一个对象,就使用映射。
红黑树:
- 存储大量数据,并且检索迅速。