1.linux内核链表
链表和数组的对比:
数组优于链表的:
-
1.内存空间占用的少,因为链表节点会附加上一块或两块下一个节点的信息。
-
2.数组内的数据可随机访问,但链表不具备随机访问性。数组在内存里是连续的空间。链表在内存地址可能是分散的,所以必须通过上一节点中的信息找能找到下一个节点
链表优于数组的:
-
1.插入与删除的操作.如果数组的中间插入一个元素,那么这个元素后的所有元素的内存地址都要往后移动.删除的话同理.链表只需要更改有必要更改的节点内的节点信息就够了.并不需要更改节点的内存地址.
-
2.内存地址的利用率方面.不管内存里还有多少空间,如果没办法一次性给出数组所需的要空间,那就会提示内存不足,而链表可以是分散的空间地址.
链表用法示例:
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
struct person
{
struct list_head list;
int age;
};
int main(int argc,char **argv)
{
int i;
struct person *p; //新节点等待插入
struct person head; //被插入的节点,头结点
struct list_head *pos; //链表头
INIT_LIST_HEAD(&head.list); //初始化被插入的节点
for (i = 0;i < 5;i++) {
p = (struct person *)malloc(sizeof(struct person ));
p->age=i*10;
list_add(&p->list,&head.list); //list_add插入节点,注意参数
}
list_for_each(pos, &head.list) { //list_for_each遍历节点,注意参数
printf("age = %d\n",((struct person *)pos)->age); //注意此时可强制转换,因为链表list的地址,和struct person的地址是一致的
}
return 0;
}
输出结果:40 30 20 10 0
注意 :后插入的先打印(类似栈),调用另一个函数list_add_tail时先插入的先打印(类似队列)
上述list_for_each适用于链表list的地址和struct person的地址是一致的情况,若不一致,需要使用list_for_each_entry。list_for_each_entry实际会调用container_of宏,作用是获取包含链表容器结构的地址。
list_for_each_entry(pos,&head.list,list){
printf("age=%d\n",pos->age);
}
使用 list_del 函数删除节点:
list_for_each_entry(pos,&head.list,list){
if(pos->age=30) {
list_del(&pos->list);
break; //注意需要break,不用break可用函数list_for_each_entry_safe
}
}
list_for_each_entry(pos,&head.list,list){
printf("age=%d\n",pos->age);
}
最后了解一下 list_replace 替换节点,list_move 移除节点,list_for_each_entry_reverse 往前依次遍历。
2.linux内核队列
队列也称为FIFO,FIFO是先进先出的缩写,使用 kfifo_alloc 创建队列示例:
struct kfifo fifo;
int ret;
ret = kfifo_alloc(&fifo,PAGE_SIZE,GFP_KERNEL);
if(ret)
return ret;
或使用 kfifo_init 指定缓冲区:
void kfifo_init(struct kfifo *fifo, void *buffer, unsigned int size);
使用 kfifo_in 堆入数据:
unsigned int kfifo_in(struct kfifo *fifo, void *from, unsigned int len);
- 该函数把from指针所指的len字节数据包括到kfifo所指的队列中,如果成功,则返回推入数据的字节大小。如果队列中的空闲字节小于len,返回值小于len或0。
使用 kfifo_out 摘取数据:
unsigned int kfifo_out(struct kfifo *fifo, void *to, unsigned int len);
- 该函数从fifo所指向的队列中拷贝处长度为len字节的数据到to所指的缓冲中。
如果仅仅想"偷窥"队列中的数据,而不真想删除它,使用 kfifo_out_peek 方法:
unsigned int kfifo_out_peek(struct kfifo *fifo, void *to, unsigned int len,unsigned offset);
- 参数offset指向队列中的索引位置
获得用于存储kfifo队列的空间的总体大小(以字节为单位),可调用方法 kfifo_size:
static inline __must_check unsigned int kfifo_size(struct kfifo *fifo);
kfifo_len 方法返回kfifo队列中推入的数据大小:
static inline unsigned int kfifo_len(struct kfifo *fifo);
kfifo_avail 得到kfifo队列中还有多少可用空间
static inline __must_check unsigned int kfifo_avail(struct kfifo *fifo);
kfifo_is_empty 和 kfifo_is_full。如果给定的kfifo分别是空或者满,它们返回非0值。
kfifo_reset 重置kfifo,意味着抛弃所有队列中的内容。
kfifo_free撤销一个使用kfifo_alloc()分配的队列。
3.linux hash 链表
哈希链表的用法与链表很相似,只不过将链表的表头hlist_head放在一个hash散列表(数组)中,表头和节点的数据结构定义如下:
struct hlist_head {
struct hlist_node *first;
}
struct hlist_node {
struct hlist_node *next,**pprev;
}
注意表头里面只存放一个hlist_node的指针,指向链表。
程序示例:
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "hlistdemo.h"
#define log(fmt, arg...) printf(""fmt,##arg)
#define MAX_ADDR 256
void main(int argc, char **argv){
struct hlist_head htable[MAX_ADDR]; //表头hash数组
struct hdata_node *hnode = NULL; //父结构体
struct hlist_node *hlist = NULL, *n = NULL; //链表节点
int i = 0, quit = 1, opt = 0, key;
unsigned int data;
for(i = 0; i < MAX_ADDR; i++)
INIT_HLIST_HEAD(&htable[i]); //表头hash数组动态初始化
log("*********************\n\n"
"input options:\n"
"1: insert\n" //插入
"2: serach\n" //查询
"3: delete\n" //删除
"0: quit\n"
"\n*********************\n");
while(quit != 0){
log("\ninput options:");
scanf("%d", &opt);
switch(opt){
case 1: //插入
hnode = calloc(1, sizeof(struct hdata_node)); //分配父结构体节点内存
if(!hnode){
log("insert fail \n");
break;
}
INIT_HLIST_NODE(&hnode->list); //父结构体结点初始化
log("\ninput data:");
scanf("%d", &hnode->data);
key = hnode->data % MAX_ADDR;
hlist_add_head(&hnode->list, &htable[key]); //添加到链表首部 hlist_add_head
break;
case 2: //查询
log("\ninput data:");
scanf("%d", &data);
key = data % MAX_ADDR;
if(hlist_empty(&htable[key]))
log("data not exist \n");
else{
//遍历对应的槽位,匹配值就打印
hlist_for_each_entry(hnode, hlist, &htable[key], list){
if(hnode->data == data)
log("find data : %u \n", hnode->data);
}
}
break;
case 3: //删除
log("\ninput data:");
scanf("%d", &data);
key = data % MAX_ADDR;
if(hlist_empty(&htable[key]))
log("data not exist ,delete fail \n");
else{
//遍历对应的槽,匹配值就删除
hlist_for_each_entry_safe(hnode, hlist, n, &htable[key], list){
if(hnode->data == data){
hlist_del(&hnode->list);
break;
}
}
}
log("delete fail\n");
break;
case 0:
quit = 0;
break;
default:
log("unrecognized option!");
break;
}
}
//退出程序前释放资源
for(i=0; i < MAX_ADDR; i++){
//遍历每一个槽,有结点就删除
hlist_for_each_entry_safe(hnode, hlist, n, &htable[i], list){
hlist_del(&hnode->list);
log("delete %u \n", hnode->data);
free(hnode);
hnode = NULL;
}
}
log("exit\n");
}
4.树结构
参考 https://xiaozhuanlan.com/topic/5036471892
5.映射
作用:将标识数UID与指针联系起来。
示例代码:
struct idr id_huh;
idr_init(&id_huh); //初始化idr结构体
int id;
do {
if (!idr_pre_get(&idr_huh, GFP_KERNEL)) //调整后备树大小
return -ENOSPC;
ret = idr_get_new(&idr_huh, ptr, &id); //将id和ptr指针关联起来
} while(ret == -EAGAIN);
查找id:
void *idr_find(struct idr *idp, int id); //查找id对应的指针
移除id:
void idr_remove(struct idr *idp, int id); //移除id
释放idr:
void idr_destroy(struct idr *idp); //释放idr中未使用的内存
void idr_remove_all(struct idr *idp); //移除所有id