一、内核链表
内核链表是一种在操作系统内核中使用的数据结构,主要用于管理和组织内核对象。它是有头双向链表的一种实现。
内核链表的特点
-
双向链表: 内核链表的每个节点都包含指向前一个节点和后一个节点的指针,这使得在链表中进行插入和删除操作时更加高效。
-
高效性: 内核链表的操作通常在内核空间中进行,避免了用户空间和内核空间之间的上下文切换,从而提高了性能。
-
简化的接口: 内核链表通常提供了一组宏和函数来简化链表的操作,例如添加、删除和遍历节点。这些操作通常是以宏的形式实现,以减少函数调用的开销。
1. offsetof
offsetof
是一个宏,用于获取结构体中某个成员相对于结构体起始位置的字节偏移量。它的定义通常如下:
#define offsetof(type, member) ((size_t) &((type *)0)->member)
用法
-
参数:
type
: 结构体的类型。member
: 结构体中的成员名。
-
返回值: 返回指定成员相对于结构体起始位置的字节偏移量。
2. container_of
container_of
是一个宏,用于根据结构体成员的指针获取包含该成员的结构体的指针。它的定义通常如下:
#define container_of(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member)))
用法
-
参数:
ptr
: 指向结构体成员的指针。type
: 结构体的类型。member
: 结构体中的成员名。
-
返回值: 返回指向包含该成员的结构体的指针。
二、内核链表的实现
KNode_t:
代表链表中的一个节点。
包含两个指针:
ppre: 指向前一个节点的指针。
pnext: 指向下一个节点的指针。
typedef struct knode
{
struct knode *ppre;
struct knode *pnext;
} KNode_t;
KLink_t:
代表整个链表。
包含以下成员:
phead: 指向链表头节点的指针。
clen: 当前链表中的节点数量。
mutex: 用于线程安全的互斥锁,确保在多线程环境中对链表的操作是安全的。
typedef struct klink
{
KNode_t *phead;
int clen;
pthread_mutex_t mutex;
} KLink_t;
1. create_klink
KLink_t *create_klink()
{
KLink_t *pklink = malloc(sizeof(KLink_t));
if (NULL == pklink)
{
perror("fail malloc");
return NULL;
}
pklink->phead = NULL;
pklink->clen = 0;
pthread_mutex_init(&(pklink->mutex), NULL);
return pklink;
}
功能: 创建一个新的链表。
步骤:
使用 malloc 分配内存。
检查内存分配是否成功。
初始化链表头指针为 NULL,节点计数为 0,并初始化互斥锁。
返回值: 返回指向新链表的指针。
2. push_klink_head
int push_klink_head(KLink_t *pklink, void *p)
{
KNode_t *pnode = (KNode_t *)p;
pnode->pnext = NULL;
pnode->ppre = NULL;
pnode->pnext = pklink->phead;
if (pklink->phead != NULL)
{
pklink->phead->ppre = pnode;
}
pklink->phead = pnode;
pklink->clen++;
return 0;
}
功能: 将一个节点插入到链表的头部。
步骤:
将传入的节点指针 p 转换为 KNode_t 类型。
设置新节点的前后指针。
将新节点插入到链表头部,并更新头指针。
增加链表的节点计数。
返回值: 返回 0 表示成功。
3. klink_for_each
void klink_for_each(KLink_t *pklink, void (*pfun)(void *))
{
KNode_t *pnode = pklink->phead;
while (pnode != NULL)
{
pfun(pnode);
pnode = pnode->pnext;
}
printf("\n");
}
功能: 遍历链表并对每个节点执行指定的函数。
参数:
pklink: 指向链表的指针。
pfun: 指向处理每个节点的函数的指针。
步骤:
从链表头开始遍历,调用传入的函数处理每个节点。
4. push_klink_tail
int push_klink_tail(KLink_t *pklink, void *q)
{
KNode_t* pnode = (KNode_t*)q;
pnode->pnext = NULL;
pnode->ppre = NULL;
if (NULL == pklink->phead)
{
pklink->phead = pnode;
}
KNode_t* p = pklink->phead;
while (p->pnext)
{
p = p->pnext;
}
p->pnext = pnode;
pnode->ppre = p;
return 0;
}
功能: 将一个节点插入到链表的尾部。
步骤:
将传入的节点指针 q 转换为 KNode_t 类型。
设置新节点的前后指针。
如果链表为空,将新节点设置为头节点。
否则,遍历到链表的最后一个节点,并将新节点添加到尾部。
返回值: 返回 0 表示成功。
5. pop_klink_head
int pop_klink_head(KLink_t *pklink)
{
if (NULL == pklink->phead)
{
return 0;
}
KNode_t* pnode = pklink->phead;
pklink->phead = pnode->pnext;
if (pklink->phead != NULL)
{
pklink->phead->ppre = NULL;
}
free(pnode);
return 0;
}
功能: 从链表的头部删除一个节点。
步骤:
检查链表是否为空。
保存当前头节点,并将头指针更新为下一个节点。
如果新的头节点不为空,更新其前指针。
释放被删除节点的内存。
返回值: 返回 0 表示成功。
三、内核链表和普通双向链表的区别
1. 目的和使用场景
-
内核链表:
- 主要用于操作系统内核中,处理任务调度、资源管理、设备驱动等。
- 需要高效的插入、删除和遍历操作,以满足实时性和性能要求。
-
普通双向链表:
- 通常用于应用程序中,处理数据存储、缓存、队列等。
- 设计上更关注易用性和灵活性,而不是极端的性能优化。
2. 结构和实现
-
内核链表:
- 通常使用更简洁的结构,可能不包含数据部分,节点结构只包含指向前后节点的指针。
- 数据部分通常与链表分开,链表节点只负责链接,数据通过其他方式管理。
-
普通双向链表:
- 节点结构通常包含数据部分和指向前后节点的指针。
- 每个节点都是完整的,包含数据和指针,便于直接操作。
3. 线程安全
-
内核链表:
- 设计时通常考虑到多线程环境,可能会使用互斥锁或其他同步机制来确保线程安全。
- 需要处理并发访问,确保数据一致性。
-
普通双向链表:
- 通常不考虑线程安全,使用时需要开发者自行管理。
- 在单线程环境中使用时,简单易用,但在多线程环境中可能会出现数据竞争问题。