一、一些概念
1.skb_queue是Linux内核网络子系统中管理sk_buff(Socket Buffer,网络数据包)的双向环形队列,由struct sk_buff_head 管理,带自旋锁保护并发访问。
2.核心结构:
二、常用操作带锁或不带锁(在include/linux/skbuff.h中定义)
1.初始化
//初始化队列头(带锁初始化)voidskb_queue_head_init(struct sk_buff_head *list);//仅初始化链表指针,不初始化锁(底层用)void __skb_queue_head_init(struct sk_buff_head *list);
2.入队(带锁版本,安全)
//队头插入(优先处理)voidskb_queue_head(struct sk_buff_head *list, struct sk_buff *newskb);//队尾插入(FIFO)voidskb_queue_tail(struct sk_buff_head *list, struct sk_buff *newskb);底层无锁版本:__skb_queue_head / __skb_queue_tail(需自行加锁)。
3.出队(带锁版本,安全)
//从队头取出并移除skbstruct sk_buff *skb_dequeue(struct sk_buff_head *list);//从队尾取出并移除skbstruct sk_buff *skb_dequeue_tail(struct sk_buff_head *list);底层无锁版本:__skb_dequeue / __skb_dequeue_tail
4.查看(不修改队列)
//查看队头skb(不取出)struct sk_buff *skb_peek(const struct sk_buff_head *list);//查看队尾skb(不取出)struct sk_buff *skb_peek_tail(const struct sk_buff_head *list);//队列是否为空intskb_queue_empty(conststruct sk_buff_head *list);//获取队列长度__u32 skb_queue_len(conststruct sk_buff_head *list);
5.清空队列
//清空队列,释放所有skb(带锁)voidskb_queue_purge(struct sk_buff_head *list);//底层无锁版本(需自行加锁)void __skb_queue_purge(struct sk_buff_head *list);
6. 遍历队列(安全)
//遍历宏(需持有锁)struct sk_buff *skb;skb_queue_walk(list, skb) { //处理skb}//安全遍历(允许遍历中删除)skb_queue_walk_safe(list, skb, tmp) { //处理skb}
三、测试
1.源码
skb_queue_TestDrv.c
#include<linux/module.h>#include<linux/kernel.h>#include<linux/skbuff.h>#include<linux/delay.h>//定义一个全局skb队列static struct sk_buff_head g_skb_queue;//构造一个测试用skbstatic struct sk_buff *alloc_test_skb(int pkt_id){ struct sk_buff *skb; unsigned char *data; int len = 64; skb = alloc_skb(len, GFP_KERNEL); if (!skb) return NULL; //预留头部空间 skb_reserve(skb, 32); //挂载数据 data = skb_put(skb, len); snprintf(data, len, "test_packet_%d", pkt_id); //识别 skb->cb[0] = pkt_id; return skb;}//遍历并打印队列内容(安全遍历)staticvoidshow_queue(constchar *info){ struct sk_buff *skb; printk(KERN_INFO "=== %s (qlen = %u) ===\n", info, skb_queue_len(&g_skb_queue)); skb_queue_walk(&g_skb_queue, skb) { printk(KERN_INFO " skb id: %d, data: %s\n", (int)skb->cb[0], skb->data); }}staticint __init skb_queue_demo_init(void){ struct sk_buff *skb; int i; printk(KERN_INFO "skb_queue demo init\n"); //1.初始化队列(含锁初始化) skb_queue_head_init(&g_skb_queue); //2.入队3个包(尾插:FIFO) for (i = 1; i <= 3; i++) { skb = alloc_test_skb(i); if (skb) { skb_queue_tail(&g_skb_queue, skb); printk(KERN_INFO "enqueue skb id %d\n", i); } } show_queue("after enqueue 3 packets"); //3.从头部出队一个 skb = skb_dequeue(&g_skb_queue); if (skb) { printk(KERN_INFO "dequeue skb id %d\n", (int)skb->cb[0]); kfree_skb(skb); // 必须释放 } show_queue("after dequeue 1 packet"); //4.头部再插入一个(优先级更高) skb = alloc_test_skb(99); if (skb) { skb_queue_head(&g_skb_queue, skb); printk(KERN_INFO "queue_head insert skb 99\n"); } show_queue("after insert to head"); //5.批量出队处理 printk(KERN_INFO "=== start dequeue all ===\n"); while ((skb = skb_dequeue(&g_skb_queue))) { printk(KERN_INFO "process skb id %d\n", (int)skb->cb[0]); kfree_skb(skb); } show_queue("after dequeue all"); return 0;}staticvoid __exit skb_queue_demo_exit(void){ // 清空队列(防止退出时泄漏) skb_queue_purge(&g_skb_queue); printk(KERN_INFO "skb_queue demo exit\n");}module_init(skb_queue_demo_init);module_exit(skb_queue_demo_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("AXUN");MODULE_DESCRIPTION("skb_queue demo");
2.Makefile文件
CC = gcc-12 # 新增:指定编译器为gcc-12obj-m += skb_queue_TestDrv.o # 要编译的模块名(对应 skb_queue_TestDrv.c)KERNELDIR ?= /lib/modules/$(shell uname -r)/build #内核源码路径PWD := $(shell pwd) #当前目录all:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules #编译模块clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean #清理编译产物
3.编译及加载模块(参考第一个驱动程序开发)