学嵌入式和C语言编程数据结构|学习日记Day21:内核链表与队列学习

|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 前言:学习完双向普通链表,今天继续进阶学习 Linux 内核链表,还有非常常用的队列结构。这两块在嵌入式 Linux 驱动、任务缓存里面大量使用,是实际项目开发高频用到的数据结构。今天动手手写两套队列完整代码:链式队列、循环顺序队列 ,分.h 头文件、.c 实现文件、main 测试文件,完整工程实践。 |

一、内核链表

1、什么是内核链表

内核链表是 Linux 内核内部大量使用的链表形式,本质是双向循环链表

结构示意图:

|--------------------------------------------------------------------------|
| ppre ← ppre ← ppre ← ppre pnext → pnext → pnext → pnext 首尾结点互相指向对方,形成闭环。 |

2、内核链表 和普通双向链表核心区别

|--------|---------------------------------------|--------------------------------------------|
| 对比项 | 普通双向链表 | Linux 内核链表 |
| 数据存放方式 | 数据存放在链表结点内部;一旦写死结点的数据类型,这套链表只能处理这一种数据 | 链表结点嵌入到业务数据结构体里面,同一套链表接口,能够管理多种不同类型的数据 |
| 复用性 | 差,每种数据类型都要重写一套插入删除遍历接口 | 高,一套通用接口适配任意自定义结构体 |
| 适用场景 | 学习练习、简单小程序 | Linux 内核驱动、嵌入式大型项目 |

3、内核链表两大核心宏

内核提供两个关键宏,是内核链表实现通用的关键:

  • offsetof
  • 获取链表成员的地址,距离整个结构体起始位置的偏移量。
  • container_of
  • 已知链表成员变量的首地址,结合上面得到的偏移量,反向算出整个大结构体的首地址。

|-------------------------------------------------------------------------|
| 💡理解关键点:普通链表:结点包数据;内核链表:数据结构体里面包含链表结点。通过offsetof+container_of 完成地址互相转换。 |

🧠思维导图:链表家族对比

|-----------------------------------------------------------------------------|
| |

二、队列

1、队列基本概念

队列是线性存储结构,一端专门用来插入数据(队尾),另一端专门用来删除数据(队头)

  • 队尾:数据插入的一端,插入操作叫入队
  • 队头:数据删除的一端,删除操作叫出队

✅队列核心特性:FIFO 先进先出,后进后出

先放进去的数据,会优先拿出来。

2、队列实际应用

嵌入式最常用于:数据缓存,比如串口接收缓存、任务消息缓存。

3、队列全套 API 功能清单

|----|--------|------------------|
| 序号 | API 接口 | 功能说明 |
| ① | 创建队列 | 初始化队列,生成空队列实例 |
| ② | 入队 | 在队尾添加新的数据 |
| ③ | 出队 | 从队头取出并删除数据 |
| ④ | 获取队头元素 | 读取队头数据,不做删除 |
| ⑤ | 判空 | 判断队列里面是否没有元素 |
| ⑥ | 销毁队列 | 释放队列全部堆内存,防止内存泄漏 |
| ⑦ | 遍历 | 从头到尾打印队列全部元素 |

队列模型示意:

|--------------------------------------------------|
| 【入队】 → 队尾 | data1 | data2 | data3 | 队头 →【出队】 |

4、链式队列代码实现(linkque)

链式队列依靠链表节点动态分配内存,没有固定容量限制。

linkque.h 头文件

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| c #ifndef LINKQUE_H #define LINKQUE_H typedef int Data_s; typedef struct node { Data_s data; struct node *pnext; }Node_s; typedef struct linkque { Node_s *phead; Node_s *ptail; int clen; }Linkque_s; extern Linkque_s *creat_linkque(); extern int insert_linkque_tail(Linkque_s *pque,Data_s data); extern int pop_link_queue(Linkque_s *pque, Data_s *data); extern int get_link_queue_head(Linkque_s *pque,Data_s *pdata); extern void show_linkque(Linkque_s *pque); extern void destory_linkque(Linkque_s *pque); extern void floor_show_bin_tree(Tnode_s *proot,Linkque_s *plque); #endif |

linkque.c 功能实现文件

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| c #include <stdio.h> #include <stdlib.h> #include "linkque.h" Linkque_s *creat_linkque() { Linkque_s *pque=malloc(sizeof(Linkque_s)); if(NULL==pque) { printf("malloc error\n"); return NULL; } pque->phead=NULL; pque->ptail=NULL; pque->clen=0; return pque; } int is_empty_linkque(Linkque_s *pque) { return NULL==pque->phead; } int insert_linkque_tail(Linkque_s *pque,Data_s data) { Node_s *pinsert=malloc(sizeof(Node_s)); if(NULL==pinsert) { printf("malloc error\n"); return -1; } pinsert->data=data; pinsert->pnext=NULL; if(is_empty_linkque(pque)) { pque->phead=pinsert; pque->ptail=pinsert; } else { pque->ptail->pnext=pinsert; pque->ptail=pinsert; } pque->clen++; return 0; } int pop_link_queue(Linkque_s *pque, Data_s *data) { if(is_empty_linkque(pque)) { return -1; } Node_s *pfree=pque->phead; pque->phead=pfree->pnext; if(data!=NULL) { *data=pfree->data; } free(pfree); if(NULL==pque->phead) { pque->ptail=NULL; } pque->clen--; return 0; } int get_link_queue_head(Linkque_s *pque,Data_s *pdata) { if(is_empty_linkque(pque)) { return -1; } *pdata=pque->phead->data; return 0; } void show_linkque(Linkque_s *pque) { if(is_empty_linkque(pque)) { return ; } Node_s *ptmp=pque->phead; while (NULL!=ptmp) { printf("%d ",ptmp->data); ptmp=ptmp->pnext; } printf("\n"); } void destory_linkque(Linkque_s *pque) { while(!is_empty_linkque(pque)) { pop_link_queue(pque,NULL); } free(pque); return ; } |

main.c 链式队列测试代码

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| c #include <stdio.h> #include "linkque.h" int main(void) { Linkque_s *pque=NULL; pque=creat_linkque(); for(Data_s data=1;data<6;++data) { insert_linkque_tail(pque,data); } show_linkque(pque); printf("pque->clen = %d\n",pque->clen); Data_s data; int ret=pop_link_queue(pque, &data); show_linkque(pque); printf("pque->clen = %d\n",pque->clen); Data_s pdata; get_link_queue_head(pque,&pdata); printf("head_data = %d\n",pdata); destory_linkque(pque); printf("pque->clen = %d\n",pque->clen); return 0; } |

5、循环顺序队列代码实现(seqque)

顺序队列基于数组实现,是循环队列,存在最大容量SEQ_MAX_LEN。

seqque.h 头文件

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| c #ifndef SEQQUE_H #define SEQQUE_H #define SEQ_MAX_LEN 10 typedef int Data_s; typedef struct seqque { Data_s *pbase; int head; int tail; }Seqque_s; extern Seqque_s *create_seqque(); extern int push_seq_queue(Seqque_s *psq,Data_s *data); extern int pop_seq_queue(Seqque_s *psq,Data_s *data); extern int is_full_seqque(Seqque_s *psq); extern int is_empty_seqque(Seqque_s *psq); extern int get_head_seq_queue(Seqque_s *psq); extern void show_seq_queue(Seqque_s *psq); extern void destory_seq_queue(Seqque_s *psq); #endif |

seqque.c 实现文件

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| c #include <stdio.h> #include <stdlib.h> #include "seqque.h" Seqque_s *create_seqque() { Seqque_s *psq=malloc(sizeof(Seqque_s)); if(NULL==psq) { printf("malloc error\n"); return NULL; } psq->pbase=malloc(SEQ_MAX_LEN*sizeof(Data_s)); if(NULL==psq->pbase) { printf("malloc error\n"); return NULL; } psq->head=0; psq->tail=0; return psq; } int is_full_seqque(Seqque_s *psq) { if((psq->tail+1)==psq->head) { return 1; } return 0; } int is_empty_seqque(Seqque_s *psq) { if(psq->head==psq->tail) { return 1; } return 0; } int push_seq_queue(Seqque_s *psq,Data_s *data) { if(is_full_seqque(psq)) { return -1; } psq->pbasepsq-\>tail=*data; psq->tail=(psq->tail+1)%SEQ_MAX_LEN; return 0; } int pop_seq_queue(Seqque_s *psq,Data_s *data) { if(is_empty_seqque(psq)) { return -1; } *data=psq->pbasepsq-\>head; psq->head=(psq->head+1)%SEQ_MAX_LEN; return 0; } int get_head_seq_queue(Seqque_s *psq) { if(is_empty_seqque(psq)) { return -1; } printf("head_base%d = %d\n",psq->head,psq->pbasepsq-\>head); } void show_seq_queue(Seqque_s *psq) { if(is_empty_seqque(psq)) { return; } int ptmp=psq->head; while(ptmp!=psq->tail) { printf("%d ",psq->pbaseptmp); ptmp=(ptmp+1)%SEQ_MAX_LEN; } printf("\n"); } void destory_seq_queue(Seqque_s *psq) { free(psq->pbase); free(psq); return ; } |

main.c 顺序队列测试代码

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| c #include <stdio.h> #include "seqque.h" int main(void) { Seqque_s *psq=NULL; psq=create_seqque(); for(Data_s i=1;i<10;++i) { push_seq_queue(psq,&i); } show_seq_queue(psq); Data_s data; int ret=0; ret=pop_seq_queue(psq,&data); if(ret==0) { printf("pop : %d\n",data); } show_seq_queue(psq); ret=pop_seq_queue(psq,&data); if(ret==0) { printf("pop : %d\n",data); } show_seq_queue(psq); get_head_seq_queue(psq); for(Data_s i=10;i<12;++i) { push_seq_queue(psq,&i); } show_seq_queue(psq); get_head_seq_queue(psq); destory_seq_queue(psq); return 0; } |

链式队列 vs 顺序循环队列对比

|------|-------------------|--------------------------|
| 对比项 | 链式队列 linkque | 循环顺序队列 seqque |
| 底层存储 | 链表 malloc 动态申请节点 | 数组内存块 |
| 容量 | 无硬性上限,受堆内存限制 | 固定最大 SEQ_MAX_LEN |
| 内存开销 | 每个节点额外保存 pnext 指针 | 只需要数组空间,无指针额外开销 |
| 满判断 | 不需要判满 | 需要判断(tail+1)%MAX == head |
| 适用场景 | 数据量波动大,嵌入式动态消息 | 串口缓冲区,已知最大缓存大小 |

三、今日学习小结

|------------------------------------------------|
| 1. 重点记忆两个宏:offsetof求偏移,container_of反向拿到结构体首地址。 |

  1. 普通双向链表虽然可以双向遍历,但是通用性差,每一种结构体都要重新写一套链表代码;Linux 内核链表通过把链表结点嵌入结构体,实现一套接口管理任意类型数据,内核驱动开发经常遇见。
  2. 队列遵循先进先出 FIFO 规则,在嵌入式项目多用于数据缓冲。队列的整套 API 和链表思路类似,但是有严格的访问规则:只能队尾入队、队头出队。
  3. 今天完整手写两套队列实现:链式队列、循环顺序队列。
  • 链式队列动态 malloc 节点,销毁的时候循环调用出队释放内存;
  • 顺序循环队列利用取模运算实现循环,牺牲一个位置区分队列满和队列空。

|------------------------------------------------------------------------------------|
| 踩坑记录:main 链式队列测试代码一开始漏掉 printf,直接写("head_data = %d\n",pdata);编译报错,调试的时候发现这个小 bug。 |

相关推荐
不正经学生1 小时前
C语言结构体:自定义数据类型,让变量打包出行
java·c语言·开发语言·数据结构·算法
老一岁1 小时前
c问题总结(1)
c语言·开发语言
一木 之林2 小时前
三、内存管理、指针与堆栈
c语言·jvm·c++
笨笨饿2 小时前
#110_SCH3000 平台图传系统配置指南:从 DVP 摄像头到 UVC/WiFi 双通道传输
c语言·stm32·单片机·嵌入式硬件·物联网·vim
程与留2 小时前
06_Qt 常用数据类型与容器:QString、QList、QVector、QMap 的性能与实现分析
数据结构·qt
林森lsjs2 小时前
队列 FIFO 原理 & 手撕两种队列实现(链表 + 循环数组)—数据结构陆
java·开发语言·数据结构·队列
caimouse3 小时前
ReactOS 窗口系统分析(20):光标与图标 — cursoricon.c
c语言·开发语言
H_oRIZoN_3 小时前
Linux入门DAY23(C语言树形结构)
linux·c语言·数据结构
间歇性努力持续性发呆的野生快乐选手3 小时前
stl容器使用和算法简介
数据结构·c++